aboutsummaryrefslogtreecommitdiff
path: root/dhcp6s.c
diff options
context:
space:
mode:
authorSUZUKI, Shinsuke <suz@kame.net>2005-12-01 06:35:48 +0000
committerSUZUKI, Shinsuke <suz@kame.net>2005-12-01 06:35:48 +0000
commit48c68dfb9b77069425980bd155e331e64dc3c64e (patch)
tree0a824722c4c612d5390716f01242faa520bb7af1 /dhcp6s.c
imported KAME-DHCPv6 snapshot at 20051201KAME_20051201
Diffstat (limited to 'dhcp6s.c')
-rw-r--r--dhcp6s.c3068
1 files changed, 3068 insertions, 0 deletions
diff --git a/dhcp6s.c b/dhcp6s.c
new file mode 100644
index 0000000..6a448e1
--- /dev/null
+++ b/dhcp6s.c
@@ -0,0 +1,3068 @@
+/* $KAME: dhcp6s.c,v 1.162 2005/10/04 11:53:32 suz Exp $ */
+/*
+ * Copyright (C) 1998 and 1999 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+#include <errno.h>
+
+#include <net/if.h>
+#ifdef __FreeBSD__
+#include <net/if_var.h>
+#endif
+
+#include <netinet/in.h>
+#ifdef __KAME__
+#include <netinet6/in6_var.h>
+#endif
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <err.h>
+#include <netdb.h>
+#include <limits.h>
+
+#include <dhcp6.h>
+#include <config.h>
+#include <common.h>
+#include <timer.h>
+#include <auth.h>
+#include <base64.h>
+#include <control.h>
+#include <dhcp6_ctl.h>
+
+#define DUID_FILE LOCALDBDIR "/dhcp6s_duid"
+#define DHCP6S_CONF SYSCONFDIR "/dhcp6s.conf"
+#define DEFAULT_KEYFILE SYSCONFDIR "/dhcp6sctlkey"
+
+#define CTLSKEW 300
+
+typedef enum { DHCP6_BINDING_IA } dhcp6_bindingtype_t;
+
+struct dhcp6_binding {
+ TAILQ_ENTRY(dhcp6_binding) link;
+
+ dhcp6_bindingtype_t type;
+
+ /* identifier of the binding */
+ struct duid clientid;
+ /* additional identifiers for IA-based bindings */
+ int iatype;
+ u_int32_t iaid;
+
+ /*
+ * configuration information of this binding,
+ * which is type-dependent.
+ */
+ union {
+ struct dhcp6_list uv_list;
+ } val;
+#define val_list val.uv_list
+
+ u_int32_t duration;
+ time_t updatetime;
+ struct dhcp6_timer *timer;
+};
+static TAILQ_HEAD(, dhcp6_binding) dhcp6_binding_head;
+
+struct relayinfo {
+ TAILQ_ENTRY(relayinfo) link;
+
+ u_int hcnt; /* hop count */
+ struct in6_addr linkaddr; /* link address */
+ struct in6_addr peeraddr; /* peer address */
+ struct dhcp6_vbuf relay_ifid; /* Interface ID (if provided) */
+ struct dhcp6_vbuf relay_msg; /* relay message */
+};
+TAILQ_HEAD(relayinfolist, relayinfo);
+
+static int debug = 0;
+
+const dhcp6_mode_t dhcp6_mode = DHCP6_MODE_SERVER;
+char *device = NULL;
+int ifidx;
+int insock; /* inbound UDP port */
+int outsock; /* outbound UDP port */
+int ctlsock = -1; /* control TCP port */
+char *ctladdr = DEFAULT_SERVER_CONTROL_ADDR;
+char *ctlport = DEFAULT_SERVER_CONTROL_PORT;
+
+static const struct sockaddr_in6 *sa6_any_downstream, *sa6_any_relay;
+static struct msghdr rmh;
+static char rdatabuf[BUFSIZ];
+static int rmsgctllen;
+static char *conffile = DHCP6S_CONF;
+static char *rmsgctlbuf;
+static struct duid server_duid;
+static struct dhcp6_list arg_dnslist;
+static char *ctlkeyfile = DEFAULT_KEYFILE;
+static struct keyinfo *ctlkey = NULL;
+static int ctldigestlen;
+
+static inline int get_val32 __P((char **, int *, u_int32_t *));
+static inline int get_val __P((char **, int *, void *, size_t));
+
+static void usage __P((void));
+static void server6_init __P((void));
+static void server6_mainloop __P((void));
+static int server6_do_ctlcommand __P((char *, ssize_t));
+static void server6_reload __P((void));
+static void server6_stop __P((void));
+static void server6_recv __P((int));
+static void free_relayinfo __P((struct relayinfo *));
+static int process_relayforw __P((struct dhcp6 **, struct dhcp6opt **,
+ struct relayinfolist *, struct sockaddr *));
+static int set_statelessinfo __P((int, struct dhcp6_optinfo *));
+static int react_solicit __P((struct dhcp6_if *, struct dhcp6 *, ssize_t,
+ struct dhcp6_optinfo *, struct sockaddr *, int, struct relayinfolist *));
+static int react_request __P((struct dhcp6_if *, struct in6_pktinfo *,
+ struct dhcp6 *, ssize_t, struct dhcp6_optinfo *, struct sockaddr *, int,
+ struct relayinfolist *));
+static int react_renew __P((struct dhcp6_if *, struct in6_pktinfo *,
+ struct dhcp6 *, ssize_t, struct dhcp6_optinfo *, struct sockaddr *, int,
+ struct relayinfolist *));
+static int react_rebind __P((struct dhcp6_if *, struct dhcp6 *, ssize_t,
+ struct dhcp6_optinfo *, struct sockaddr *, int, struct relayinfolist *));
+static int react_release __P((struct dhcp6_if *, struct in6_pktinfo *,
+ struct dhcp6 *, ssize_t, struct dhcp6_optinfo *, struct sockaddr *, int,
+ struct relayinfolist *));
+static int react_informreq __P((struct dhcp6_if *, struct dhcp6 *, ssize_t,
+ struct dhcp6_optinfo *, struct sockaddr *, int, struct relayinfolist *));
+static int server6_send __P((int, struct dhcp6_if *, struct dhcp6 *,
+ struct dhcp6_optinfo *, struct sockaddr *, int, struct dhcp6_optinfo *,
+ struct relayinfolist *, struct host_conf *));
+static int make_ia_stcode __P((int, u_int32_t, u_int16_t,
+ struct dhcp6_list *));
+static int update_ia __P((int, struct dhcp6_listval *,
+ struct dhcp6_list *, struct dhcp6_optinfo *));
+static int release_binding_ia __P((struct dhcp6_listval *, struct dhcp6_list *,
+ struct dhcp6_optinfo *));
+static int make_ia __P((struct dhcp6_listval *, struct dhcp6_list *,
+ struct dhcp6_list *, struct host_conf *, int));
+static int make_match_ia __P((struct dhcp6_listval *, struct dhcp6_list *,
+ struct dhcp6_list *));
+static void calc_ia_timo __P((struct dhcp6_ia *, struct dhcp6_list *,
+ struct host_conf *));
+static void update_binding_duration __P((struct dhcp6_binding *));
+static struct dhcp6_binding *add_binding __P((struct duid *,
+ dhcp6_bindingtype_t, int, u_int32_t, void *));
+static struct dhcp6_binding *find_binding __P((struct duid *,
+ dhcp6_bindingtype_t, int, u_int32_t));
+static void update_binding __P((struct dhcp6_binding *));
+static void remove_binding __P((struct dhcp6_binding *));
+static void free_binding __P((struct dhcp6_binding *));
+static struct dhcp6_timer *binding_timo __P((void *));
+static struct dhcp6_listval *find_binding_ia __P((struct dhcp6_listval *,
+ struct dhcp6_binding *));
+static char *bindingstr __P((struct dhcp6_binding *));
+static int process_auth __P((struct dhcp6 *, ssize_t, struct host_conf *,
+ struct dhcp6_optinfo *, struct dhcp6_optinfo *));
+static inline char *clientstr __P((struct host_conf *, struct duid *));
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ int ch;
+ struct in6_addr a;
+ struct dhcp6_listval *dlv;
+ char *progname;
+
+ if ((progname = strrchr(*argv, '/')) == NULL)
+ progname = *argv;
+ else
+ progname++;
+
+ TAILQ_INIT(&arg_dnslist);
+ TAILQ_INIT(&dnslist);
+ TAILQ_INIT(&dnsnamelist);
+ TAILQ_INIT(&siplist);
+ TAILQ_INIT(&sipnamelist);
+ TAILQ_INIT(&ntplist);
+
+ srandom(time(NULL) & getpid());
+ while ((ch = getopt(argc, argv, "c:dDfk:n:p:")) != -1) {
+ switch (ch) {
+ case 'c':
+ conffile = optarg;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'D':
+ debug = 2;
+ break;
+ case 'f':
+ foreground++;
+ break;
+ case 'k':
+ ctlkeyfile = optarg;
+ break;
+ case 'n':
+ warnx("-n dnsserv option was obsoleted. "
+ "use configuration file.");
+ if (inet_pton(AF_INET6, optarg, &a) != 1) {
+ errx(1, "invalid DNS server %s", optarg);
+ /* NOTREACHED */
+ }
+ if ((dlv = malloc(sizeof *dlv)) == NULL) {
+ errx(1, "malloc failed for a DNS server");
+ /* NOTREACHED */
+ }
+ dlv->val_addr6 = a;
+ TAILQ_INSERT_TAIL(&arg_dnslist, dlv, link);
+ break;
+ case 'p':
+ ctlport = optarg;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ usage();
+ /* NOTREACHED */
+ }
+ device = argv[0];
+
+ if (foreground == 0)
+ openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON);
+
+ setloglevel(debug);
+
+ if (ifinit(device) == NULL)
+ exit(1);
+
+ if ((cfparse(conffile)) != 0) {
+ dprintf(LOG_ERR, FNAME, "failed to parse configuration file");
+ exit(1);
+ }
+
+ if (foreground == 0) {
+ if (daemon(0, 0) < 0)
+ err(1, "daemon");
+ }
+ /* prohibit a mixture of old and new style of DNS server config */
+ if (!TAILQ_EMPTY(&arg_dnslist)) {
+ if (!TAILQ_EMPTY(&dnslist)) {
+ dprintf(LOG_INFO, FNAME, "do not specify DNS servers "
+ "both by command line and by configuration file.");
+ exit(1);
+ }
+ dhcp6_move_list(&dnslist, &arg_dnslist);
+ TAILQ_INIT(&arg_dnslist);
+ }
+
+ server6_init();
+
+ server6_mainloop();
+ exit(0);
+}
+
+static void
+usage()
+{
+ fprintf(stderr,
+ "usage: dhcp6s [-c configfile] [-dDf] [-k ctlkeyfile] "
+ "[-p ctlport] intface\n");
+ exit(0);
+}
+
+/*------------------------------------------------------------*/
+
+void
+server6_init()
+{
+ struct addrinfo hints;
+ struct addrinfo *res, *res2;
+ int error;
+ int on = 1;
+ struct ipv6_mreq mreq6;
+ static struct iovec iov;
+ static struct sockaddr_in6 sa6_any_downstream_storage;
+ static struct sockaddr_in6 sa6_any_relay_storage;
+
+ TAILQ_INIT(&dhcp6_binding_head);
+
+ ifidx = if_nametoindex(device);
+ if (ifidx == 0) {
+ dprintf(LOG_ERR, FNAME, "invalid interface %s", device);
+ exit(1);
+ }
+
+ /* get our DUID */
+ if (get_duid(DUID_FILE, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to get a DUID");
+ exit(1);
+ }
+
+ if (dhcp6_ctl_authinit(ctlkeyfile, &ctlkey, &ctldigestlen) != 0) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to initialize control message authentication");
+ /* run the server anyway */
+ }
+
+ /* initialize send/receive buffer */
+ iov.iov_base = (caddr_t)rdatabuf;
+ iov.iov_len = sizeof(rdatabuf);
+ rmh.msg_iov = &iov;
+ rmh.msg_iovlen = 1;
+ rmsgctllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ if ((rmsgctlbuf = (char *)malloc(rmsgctllen)) == NULL) {
+ dprintf(LOG_ERR, FNAME, "memory allocation failed");
+ exit(1);
+ }
+
+ /* initialize socket */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_PASSIVE;
+ error = getaddrinfo(NULL, DH6PORT_UPSTREAM, &hints, &res);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ insock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (insock < 0) {
+ dprintf(LOG_ERR, FNAME, "socket(insock): %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (setsockopt(insock, SOL_SOCKET, SO_REUSEPORT, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME, "setsockopt(insock, SO_REUSEPORT): %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (setsockopt(insock, SOL_SOCKET, SO_REUSEADDR, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME, "setsockopt(insock, SO_REUSEADDR): %s",
+ strerror(errno));
+ exit(1);
+ }
+#ifdef IPV6_RECVPKTINFO
+ if (setsockopt(insock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(inbound, IPV6_RECVPKTINFO): %s",
+ strerror(errno));
+ exit(1);
+ }
+#else
+ if (setsockopt(insock, IPPROTO_IPV6, IPV6_PKTINFO, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(inbound, IPV6_PKTINFO): %s",
+ strerror(errno));
+ exit(1);
+ }
+#endif
+ if (setsockopt(insock, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(inbound, IPV6_V6ONLY): %s", strerror(errno));
+ exit(1);
+ }
+ if (bind(insock, res->ai_addr, res->ai_addrlen) < 0) {
+ dprintf(LOG_ERR, FNAME, "bind(insock): %s", strerror(errno));
+ exit(1);
+ }
+ freeaddrinfo(res);
+
+ hints.ai_flags = 0;
+ error = getaddrinfo(DH6ADDR_ALLAGENT, DH6PORT_UPSTREAM, &hints, &res2);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ memset(&mreq6, 0, sizeof(mreq6));
+ mreq6.ipv6mr_interface = ifidx;
+ memcpy(&mreq6.ipv6mr_multiaddr,
+ &((struct sockaddr_in6 *)res2->ai_addr)->sin6_addr,
+ sizeof(mreq6.ipv6mr_multiaddr));
+ if (setsockopt(insock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq6, sizeof(mreq6))) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(insock, IPV6_JOIN_GROUP): %s",
+ strerror(errno));
+ exit(1);
+ }
+ freeaddrinfo(res2);
+
+ hints.ai_flags = 0;
+ error = getaddrinfo(DH6ADDR_ALLSERVER, DH6PORT_UPSTREAM,
+ &hints, &res2);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ memset(&mreq6, 0, sizeof(mreq6));
+ mreq6.ipv6mr_interface = ifidx;
+ memcpy(&mreq6.ipv6mr_multiaddr,
+ &((struct sockaddr_in6 *)res2->ai_addr)->sin6_addr,
+ sizeof(mreq6.ipv6mr_multiaddr));
+ if (setsockopt(insock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq6, sizeof(mreq6))) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(insock, IPV6_JOIN_GROUP): %s",
+ strerror(errno));
+ exit(1);
+ }
+ freeaddrinfo(res2);
+
+ hints.ai_flags = 0;
+ error = getaddrinfo(NULL, DH6PORT_DOWNSTREAM, &hints, &res);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ outsock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (outsock < 0) {
+ dprintf(LOG_ERR, FNAME, "socket(outsock): %s",
+ strerror(errno));
+ exit(1);
+ }
+ /* set outgoing interface of multicast packets for DHCP reconfig */
+ if (setsockopt(outsock, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ &ifidx, sizeof(ifidx)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(outsock, IPV6_MULTICAST_IF): %s",
+ strerror(errno));
+ exit(1);
+ }
+#ifndef __linux__
+ /* make the socket write-only */
+ if (shutdown(outsock, 0)) {
+ dprintf(LOG_ERR, FNAME, "shutdown(outbound, 0): %s",
+ strerror(errno));
+ exit(1);
+ }
+#endif
+ freeaddrinfo(res);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ error = getaddrinfo("::", DH6PORT_DOWNSTREAM, &hints, &res);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ memcpy(&sa6_any_downstream_storage, res->ai_addr, res->ai_addrlen);
+ sa6_any_downstream =
+ (const struct sockaddr_in6*)&sa6_any_downstream_storage;
+ freeaddrinfo(res);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ error = getaddrinfo("::", DH6PORT_UPSTREAM, &hints, &res);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ memcpy(&sa6_any_relay_storage, res->ai_addr, res->ai_addrlen);
+ sa6_any_relay =
+ (const struct sockaddr_in6*)&sa6_any_relay_storage;
+ freeaddrinfo(res);
+
+ /* set up control socket */
+ if (ctlkey == NULL)
+ dprintf(LOG_NOTICE, FNAME, "skip opening control port");
+ else if (dhcp6_ctl_init(ctladdr, ctlport,
+ DHCP6CTL_DEF_COMMANDQUEUELEN, &ctlsock)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to initialize control channel");
+ exit(1);
+ }
+
+ return;
+}
+
+static void
+server6_mainloop()
+{
+ struct timeval *w;
+ int ret;
+ fd_set r;
+ int maxsock;
+
+
+ while (1) {
+ w = dhcp6_check_timer();
+
+ FD_ZERO(&r);
+ FD_SET(insock, &r);
+ maxsock = insock;
+ if (ctlsock >= 0) {
+ FD_SET(ctlsock, &r);
+ maxsock = (insock > ctlsock) ? insock : ctlsock;
+ (void)dhcp6_ctl_setreadfds(&r, &maxsock);
+ }
+
+ ret = select(maxsock + 1, &r, NULL, NULL, w);
+ switch (ret) {
+ case -1:
+ dprintf(LOG_ERR, FNAME, "select: %s",
+ strerror(errno));
+ exit(1);
+ /* NOTREACHED */
+ case 0: /* timeout */
+ break;
+ default:
+ break;
+ }
+
+ if (FD_ISSET(insock, &r))
+ server6_recv(insock);
+ if (ctlsock >= 0) {
+ if (FD_ISSET(ctlsock, &r)) {
+ (void)dhcp6_ctl_acceptcommand(ctlsock,
+ server6_do_ctlcommand);
+ }
+ (void)dhcp6_ctl_readcommand(&r);
+ }
+ }
+}
+
+static inline int
+get_val32(bpp, lenp, valp)
+ char **bpp;
+ int *lenp;
+ u_int32_t *valp;
+{
+ char *bp = *bpp;
+ int len = *lenp;
+ u_int32_t i32;
+
+ if (len < sizeof(*valp))
+ return (-1);
+
+ memcpy(&i32, bp, sizeof(i32));
+ *valp = ntohl(i32);
+
+ *bpp = bp + sizeof(*valp);
+ *lenp = len - sizeof(*valp);
+
+ return (0);
+}
+
+static inline int
+get_val(bpp, lenp, valp, vallen)
+ char **bpp;
+ int *lenp;
+ void *valp;
+ size_t vallen;
+{
+ char *bp = *bpp;
+ int len = *lenp;
+
+ if (len < vallen)
+ return (-1);
+
+ memcpy(valp, bp, vallen);
+
+ *bpp = bp + vallen;
+ *lenp = len - vallen;
+
+ return (0);
+}
+
+static int
+server6_do_ctlcommand(buf, len)
+ char *buf;
+ ssize_t len;
+{
+ struct dhcp6ctl *ctlhead;
+ struct dhcp6ctl_iaspec iaspec;
+ u_int16_t command, version;
+ u_int32_t p32, iaid, duidlen, ts, ts0;
+ struct duid duid;
+ struct dhcp6_binding *binding;
+ int commandlen;
+ char *bp;
+ time_t now;
+
+ ctlhead = (struct dhcp6ctl *)buf;
+
+ command = ntohs(ctlhead->command);
+ commandlen = (int)(ntohs(ctlhead->len));
+ version = ntohs(ctlhead->version);
+ if (len != sizeof(struct dhcp6ctl) + commandlen) {
+ dprintf(LOG_ERR, FNAME,
+ "assumption failure: command length mismatch");
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ /* replay protection and message authentication */
+ if ((now = time(NULL)) < 0) {
+ dprintf(LOG_ERR, FNAME, "failed to get current time: %s",
+ strerror(errno));
+ return (DHCP6CTL_R_FAILURE);
+ }
+ ts0 = (u_int32_t)now;
+ ts = ntohl(ctlhead->timestamp);
+ if (ts + CTLSKEW < ts0 || (ts - CTLSKEW) > ts0) {
+ dprintf(LOG_INFO, FNAME, "timestamp is out of range");
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ if (ctlkey == NULL) { /* should not happen!! */
+ dprintf(LOG_ERR, FNAME, "no secret key for control channel");
+ return (DHCP6CTL_R_FAILURE);
+ }
+ if (dhcp6_verify_mac(buf, len, DHCP6CTL_AUTHPROTO_UNDEF,
+ DHCP6CTL_AUTHALG_HMACMD5, sizeof(*ctlhead), ctlkey) != 0) {
+ dprintf(LOG_INFO, FNAME, "authentication failure");
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ bp = buf + sizeof(*ctlhead) + ctldigestlen;
+ commandlen -= ctldigestlen;
+
+ if (version > DHCP6CTL_VERSION) {
+ dprintf(LOG_INFO, FNAME, "unsupported version: %d", version);
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ switch (command) {
+ case DHCP6CTL_COMMAND_RELOAD:
+ if (commandlen != 0) {
+ dprintf(LOG_INFO, FNAME, "invalid command length "
+ "for reload: %d", commandlen);
+ return (DHCP6CTL_R_DONE);
+ }
+ server6_reload();
+ break;
+ case DHCP6CTL_COMMAND_STOP:
+ if (commandlen != 0) {
+ dprintf(LOG_INFO, FNAME, "invalid command length "
+ "for stop: %d", commandlen);
+ return (DHCP6CTL_R_DONE);
+ }
+ server6_stop();
+ break;
+ case DHCP6CTL_COMMAND_REMOVE:
+ if (get_val32(&bp, &commandlen, &p32))
+ return (DHCP6CTL_R_FAILURE);
+ if (p32 != DHCP6CTL_BINDING) {
+ dprintf(LOG_INFO, FNAME,
+ "unknown remove target: %ul", p32);
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ if (get_val32(&bp, &commandlen, &p32))
+ return (DHCP6CTL_R_FAILURE);
+ if (p32 != DHCP6CTL_BINDING_IA) {
+ dprintf(LOG_INFO, FNAME, "unknown binding type: %ul",
+ p32);
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ if (get_val(&bp, &commandlen, &iaspec, sizeof(iaspec)))
+ return (DHCP6CTL_R_FAILURE);
+ if (ntohl(iaspec.type) != DHCP6CTL_IA_PD &&
+ ntohl(iaspec.type) != DHCP6CTL_IA_NA) {
+ dprintf(LOG_INFO, FNAME, "unknown IA type: %ul",
+ ntohl(iaspec.type));
+ return (DHCP6CTL_R_FAILURE);
+ }
+ iaid = ntohl(iaspec.id);
+ duidlen = ntohl(iaspec.duidlen);
+
+ if (duidlen > commandlen) {
+ dprintf(LOG_INFO, FNAME, "DUID length mismatch");
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ duid.duid_len = (size_t)duidlen;
+ duid.duid_id = bp;
+
+ binding = find_binding(&duid, DHCP6_BINDING_IA,
+ DHCP6_LISTVAL_IAPD, iaid);
+ if (binding == NULL) {
+ binding = find_binding(&duid, DHCP6_BINDING_IA,
+ DHCP6_LISTVAL_IANA, iaid);
+ if (binding == NULL) {
+ dprintf(LOG_INFO, FNAME, "no such binding");
+ return (DHCP6CTL_R_FAILURE);
+ }
+ }
+ remove_binding(binding);
+
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME,
+ "unknown control command: %d (len=%d)",
+ (int)command, commandlen);
+ return (DHCP6CTL_R_FAILURE);
+ }
+
+ return (DHCP6CTL_R_DONE);
+}
+
+static void
+server6_reload()
+{
+ /* reload the configuration file */
+ if (cfparse(conffile) != 0) {
+ dprintf(LOG_WARNING, FNAME,
+ "failed to reload configuration file");
+ return;
+ }
+
+ dprintf(LOG_NOTICE, FNAME, "server reloaded");
+
+ return;
+}
+
+static void
+server6_stop()
+{
+ /* Right now, we simply stop running */
+
+ dprintf(LOG_NOTICE, FNAME, "exiting");
+
+ exit (0);
+}
+
+static void
+server6_recv(s)
+ int s;
+{
+ ssize_t len;
+ struct sockaddr_storage from;
+ int fromlen;
+ struct msghdr mhdr;
+ struct iovec iov;
+ char cmsgbuf[BUFSIZ];
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi = NULL;
+ struct dhcp6_if *ifp;
+ struct dhcp6 *dh6;
+ struct dhcp6_optinfo optinfo;
+ struct dhcp6opt *optend;
+ struct relayinfolist relayinfohead;
+ struct relayinfo *relayinfo;
+
+ TAILQ_INIT(&relayinfohead);
+
+ memset(&iov, 0, sizeof(iov));
+ memset(&mhdr, 0, sizeof(mhdr));
+
+ iov.iov_base = rdatabuf;
+ iov.iov_len = sizeof(rdatabuf);
+ mhdr.msg_name = &from;
+ mhdr.msg_namelen = sizeof(from);
+ mhdr.msg_iov = &iov;
+ mhdr.msg_iovlen = 1;
+ mhdr.msg_control = (caddr_t)cmsgbuf;
+ mhdr.msg_controllen = sizeof(cmsgbuf);
+
+ if ((len = recvmsg(insock, &mhdr, 0)) < 0) {
+ dprintf(LOG_ERR, FNAME, "recvmsg: %s", strerror(errno));
+ return;
+ }
+ fromlen = mhdr.msg_namelen;
+
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&mhdr, cm)) {
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
+ pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
+ }
+ }
+ if (pi == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to get packet info");
+ return;
+ }
+ /*
+ * DHCPv6 server may receive a DHCPv6 packet from a non-listening
+ * interface, when a DHCPv6 relay agent is running on that interface.
+ * This check prevents such reception.
+ */
+ if (pi->ipi6_ifindex != ifidx)
+ return;
+ if ((ifp = find_ifconfbyid((unsigned int)pi->ipi6_ifindex)) == NULL) {
+ dprintf(LOG_INFO, FNAME, "unexpected interface (%d)",
+ (unsigned int)pi->ipi6_ifindex);
+ return;
+ }
+
+ dh6 = (struct dhcp6 *)rdatabuf;
+
+ if (len < sizeof(*dh6)) {
+ dprintf(LOG_INFO, FNAME, "short packet (%d bytes)", len);
+ return;
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "received %s from %s",
+ dhcp6msgstr(dh6->dh6_msgtype),
+ addr2str((struct sockaddr *)&from));
+
+ /*
+ * A server MUST discard any Solicit, Confirm, Rebind or
+ * Information-request messages it receives with a unicast
+ * destination address.
+ * [RFC3315 Section 15.]
+ */
+ if (!IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) &&
+ (dh6->dh6_msgtype == DH6_SOLICIT ||
+ dh6->dh6_msgtype == DH6_CONFIRM ||
+ dh6->dh6_msgtype == DH6_REBIND ||
+ dh6->dh6_msgtype == DH6_INFORM_REQ)) {
+ dprintf(LOG_INFO, FNAME, "invalid unicast message");
+ return;
+ }
+
+ /*
+ * A server never receives a relay reply message. Since relay
+ * replay messages will annoy option parser below, we explicitly
+ * reject them here.
+ */
+ if (dh6->dh6_msgtype == DH6_RELAY_REPLY) {
+ dprintf(LOG_INFO, FNAME, "relay reply message from %s",
+ addr2str((struct sockaddr *)&from));
+ return;
+
+ }
+
+ optend = (struct dhcp6opt *)(rdatabuf + len);
+ if (dh6->dh6_msgtype == DH6_RELAY_FORW) {
+ if (process_relayforw(&dh6, &optend, &relayinfohead,
+ (struct sockaddr *)&from)) {
+ goto end;
+ }
+ /* dh6 and optend should have been updated. */
+ }
+
+ /*
+ * parse and validate options in the message
+ */
+ dhcp6_init_options(&optinfo);
+ if (dhcp6_get_options((struct dhcp6opt *)(dh6 + 1),
+ optend, &optinfo) < 0) {
+ dprintf(LOG_INFO, FNAME, "failed to parse options");
+ goto end;
+ }
+
+ switch (dh6->dh6_msgtype) {
+ case DH6_SOLICIT:
+ (void)react_solicit(ifp, dh6, len, &optinfo,
+ (struct sockaddr *)&from, fromlen, &relayinfohead);
+ break;
+ case DH6_REQUEST:
+ (void)react_request(ifp, pi, dh6, len, &optinfo,
+ (struct sockaddr *)&from, fromlen, &relayinfohead);
+ break;
+ case DH6_RENEW:
+ (void)react_renew(ifp, pi, dh6, len, &optinfo,
+ (struct sockaddr *)&from, fromlen, &relayinfohead);
+ break;
+ case DH6_REBIND:
+ (void)react_rebind(ifp, dh6, len, &optinfo,
+ (struct sockaddr *)&from, fromlen, &relayinfohead);
+ break;
+ case DH6_RELEASE:
+ (void)react_release(ifp, pi, dh6, len, &optinfo,
+ (struct sockaddr *)&from, fromlen, &relayinfohead);
+ break;
+ case DH6_INFORM_REQ:
+ (void)react_informreq(ifp, dh6, len, &optinfo,
+ (struct sockaddr *)&from, fromlen, &relayinfohead);
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME, "unknown or unsupported msgtype (%s)",
+ dhcp6msgstr(dh6->dh6_msgtype));
+ break;
+ }
+
+ dhcp6_clear_options(&optinfo);
+
+ end:
+ while ((relayinfo = TAILQ_FIRST(&relayinfohead)) != NULL) {
+ TAILQ_REMOVE(&relayinfohead, relayinfo, link);
+ free_relayinfo(relayinfo);
+ }
+
+ return;
+}
+
+static void
+free_relayinfo(relayinfo)
+ struct relayinfo *relayinfo;
+{
+ if (relayinfo->relay_ifid.dv_buf)
+ dhcp6_vbuf_free(&relayinfo->relay_ifid);
+
+ if (relayinfo->relay_msg.dv_buf)
+ dhcp6_vbuf_free(&relayinfo->relay_msg);
+
+ free(relayinfo);
+}
+
+static int
+process_relayforw(dh6p, optendp, relayinfohead, from)
+ struct dhcp6 **dh6p;
+ struct dhcp6opt **optendp;
+ struct relayinfolist *relayinfohead;
+ struct sockaddr *from;
+{
+ struct dhcp6_relay *dh6relay = (struct dhcp6_relay *)*dh6p;
+ struct dhcp6opt *optend = *optendp;
+ struct relayinfo *relayinfo;
+ struct dhcp6_optinfo optinfo;
+ int len;
+
+ again:
+ len = (void *)optend - (void *)dh6relay;
+ if (len < sizeof (*dh6relay)) {
+ dprintf(LOG_INFO, FNAME, "short relay message from %s",
+ addr2str(from));
+ return (-1);
+ }
+ dprintf(LOG_DEBUG, FNAME,
+ "dhcp6 relay: hop=%d, linkaddr=%s, peeraddr=%s",
+ dh6relay->dh6relay_hcnt,
+ in6addr2str(&dh6relay->dh6relay_linkaddr, 0),
+ in6addr2str(&dh6relay->dh6relay_peeraddr, 0));
+
+ /*
+ * parse and validate options in the relay forward message.
+ */
+ dhcp6_init_options(&optinfo);
+ if (dhcp6_get_options((struct dhcp6opt *)(dh6relay + 1),
+ optend, &optinfo) < 0) {
+ dprintf(LOG_INFO, FNAME, "failed to parse options");
+ return (-1);
+ }
+
+ /* A relay forward message must include a relay message option */
+ if (optinfo.relaymsg_msg == NULL) {
+ dprintf(LOG_INFO, FNAME, "relay forward from %s "
+ "without a relay message", addr2str(from));
+ return (-1);
+ }
+
+ /* relay message must contain a DHCPv6 message. */
+ len = optinfo.relaymsg_len;
+ if (len < sizeof (struct dhcp6)) {
+ dprintf(LOG_INFO, FNAME,
+ "short packet (%d bytes) in relay message", len);
+ return (-1);
+ }
+
+ if ((relayinfo = malloc(sizeof (*relayinfo))) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to allocate relay info");
+ return (-1);
+ }
+ memset(relayinfo, 0, sizeof (*relayinfo));
+
+ relayinfo->hcnt = dh6relay->dh6relay_hcnt;
+ memcpy(&relayinfo->linkaddr, &dh6relay->dh6relay_linkaddr,
+ sizeof (relayinfo->linkaddr));
+ memcpy(&relayinfo->peeraddr, &dh6relay->dh6relay_peeraddr,
+ sizeof (relayinfo->peeraddr));
+
+ if (dhcp6_vbuf_copy(&relayinfo->relay_msg, &optinfo.relay_msg))
+ goto fail;
+ if (optinfo.ifidopt_id &&
+ dhcp6_vbuf_copy(&relayinfo->relay_ifid, &optinfo.ifidopt)) {
+ goto fail;
+ }
+
+ TAILQ_INSERT_HEAD(relayinfohead, relayinfo, link);
+
+ dhcp6_clear_options(&optinfo);
+
+ optend = (struct dhcp6opt *)(relayinfo->relay_msg.dv_buf + len);
+ dh6relay = (struct dhcp6_relay *)relayinfo->relay_msg.dv_buf;
+
+ if (dh6relay->dh6relay_msgtype != DH6_RELAY_FORW) {
+ *dh6p = (struct dhcp6 *)dh6relay;
+ *optendp = optend;
+ return (0);
+ }
+
+ goto again;
+
+ fail:
+ free_relayinfo(relayinfo);
+ dhcp6_clear_options(&optinfo);
+
+ return (-1);
+}
+
+/*
+ * Set stateless configuration information to a option structure.
+ * It is the caller's responsibility to deal with error cases.
+ */
+static int
+set_statelessinfo(type, optinfo)
+ int type;
+ struct dhcp6_optinfo *optinfo;
+{
+ /* SIP server domain name */
+ if (dhcp6_copy_list(&optinfo->sipname_list, &sipnamelist)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to copy SIP server domain list");
+ return (-1);
+ }
+
+ /* SIP server */
+ if (dhcp6_copy_list(&optinfo->sip_list, &siplist)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy SIP servers");
+ return (-1);
+ }
+
+ /* DNS server */
+ if (dhcp6_copy_list(&optinfo->dns_list, &dnslist)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy DNS servers");
+ return (-1);
+ }
+
+ /* DNS search list */
+ if (dhcp6_copy_list(&optinfo->dnsname_list, &dnsnamelist)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy DNS search list");
+ return (-1);
+ }
+
+ /* NTP server */
+ if (dhcp6_copy_list(&optinfo->ntp_list, &ntplist)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy NTP servers");
+ return (-1);
+ }
+
+ /*
+ * Information refresh time. Only include in a response to
+ * an Information-request message.
+ */
+ if (type == DH6_INFORM_REQ &&
+ optrefreshtime != DH6OPT_REFRESHTIME_UNDEF) {
+ optinfo->refreshtime = (int64_t)optrefreshtime;
+ }
+
+ return (0);
+}
+
+static int
+react_solicit(ifp, dh6, len, optinfo, from, fromlen, relayinfohead)
+ struct dhcp6_if *ifp;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+{
+ struct dhcp6_optinfo roptinfo;
+ struct host_conf *client_conf;
+ int resptype, do_binding = 0, error;
+
+ /*
+ * Servers MUST discard any Solicit messages that do not include a
+ * Client Identifier option.
+ * [RFC3315 Section 15.2]
+ */
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no client ID option");
+ return (-1);
+ } else {
+ dprintf(LOG_DEBUG, FNAME, "client ID %s",
+ duidstr(&optinfo->clientID));
+ }
+
+ /* get per-host configuration for the client, if any. */
+ if ((client_conf = find_hostconf(&optinfo->clientID))) {
+ dprintf(LOG_DEBUG, FNAME, "found a host configuration for %s",
+ client_conf->name);
+ }
+
+ /*
+ * configure necessary options based on the options in solicit.
+ */
+ dhcp6_init_options(&roptinfo);
+
+ /* process authentication */
+ if (process_auth(dh6, len, client_conf, optinfo, &roptinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication "
+ "information for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ goto fail;
+ }
+
+ /* server identifier option */
+ if (duidcpy(&roptinfo.serverID, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto fail;
+ }
+
+ /* copy client information back */
+ if (duidcpy(&roptinfo.clientID, &optinfo->clientID)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto fail;
+ }
+
+ /* preference (if configured) */
+ if (ifp->server_pref != DH6OPT_PREF_UNDEF)
+ roptinfo.pref = ifp->server_pref;
+
+ /* add other configuration information */
+ if (set_statelessinfo(DH6_SOLICIT, &roptinfo)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to set other stateless information");
+ goto fail;
+ }
+
+ /*
+ * see if we have information for requested options, and if so,
+ * configure corresponding options.
+ */
+ if (optinfo->rapidcommit && (ifp->allow_flags & DHCIFF_RAPID_COMMIT))
+ do_binding = 1;
+
+ /*
+ * The delegating router MUST include an IA_PD option, identifying any
+ * prefix(es) that the delegating router will delegate to the
+ * requesting router. [RFC3633 Section 11.2]
+ */
+ if (!TAILQ_EMPTY(&optinfo->iapd_list)) {
+ int found = 0;
+ struct dhcp6_list conflist;
+ struct dhcp6_listval *iapd;
+
+ TAILQ_INIT(&conflist);
+
+ /* make a local copy of the configured prefixes */
+ if (client_conf &&
+ dhcp6_copy_list(&conflist, &client_conf->prefix_list)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make local data");
+ goto fail;
+ }
+
+ for (iapd = TAILQ_FIRST(&optinfo->iapd_list); iapd;
+ iapd = TAILQ_NEXT(iapd, link)) {
+ /*
+ * find an appropriate prefix for each IA_PD,
+ * removing the adopted prefixes from the list.
+ * (dhcp6s cannot create IAs without client config)
+ */
+ if (client_conf &&
+ make_ia(iapd, &conflist, &roptinfo.iapd_list,
+ client_conf, do_binding) > 0)
+ found = 1;
+ }
+
+ dhcp6_clear_list(&conflist);
+
+ if (!found) {
+ /*
+ * If the delegating router will not assign any
+ * prefixes to any IA_PDs in a subsequent Request from
+ * the requesting router, the delegating router MUST
+ * send an Advertise message to the requesting router
+ * that includes a Status Code option with code
+ * NoPrefixAvail.
+ * [dhcpv6-opt-prefix-delegation-01 Section 10.2]
+ */
+ u_int16_t stcode = DH6OPT_STCODE_NOPREFIXAVAIL;
+
+ if (dhcp6_add_listval(&roptinfo.stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, NULL) == NULL)
+ goto fail;
+ }
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->iana_list)) {
+ int found = 0;
+ struct dhcp6_list conflist;
+ struct dhcp6_listval *iana;
+
+ TAILQ_INIT(&conflist);
+
+ /* make a local copy of the configured addresses */
+ if (client_conf &&
+ dhcp6_copy_list(&conflist, &client_conf->addr_list)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make local data");
+ goto fail;
+ }
+
+ for (iana = TAILQ_FIRST(&optinfo->iana_list); iana;
+ iana = TAILQ_NEXT(iana, link)) {
+ /*
+ * find an appropriate address for each IA_NA,
+ * removing the adopted addresses from the list.
+ * (dhcp6s cannot create IAs without client config)
+ */
+ if (client_conf &&
+ make_ia(iana, &conflist, &roptinfo.iana_list,
+ client_conf, do_binding) > 0)
+ found = 1;
+ }
+
+ dhcp6_clear_list(&conflist);
+
+ if (!found) {
+ u_int16_t stcode = DH6OPT_STCODE_NOADDRSAVAIL;
+
+ if (dhcp6_add_listval(&roptinfo.stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, NULL) == NULL)
+ goto fail;
+ }
+ }
+
+ if (optinfo->rapidcommit && (ifp->allow_flags & DHCIFF_RAPID_COMMIT)) {
+ /*
+ * If the client has included a Rapid Commit option and the
+ * server has been configured to respond with committed address
+ * assignments and other resources, responds to the Solicit
+ * with a Reply message.
+ * [RFC3315 Section 17.2.1]
+ */
+ roptinfo.rapidcommit = 1;
+ resptype = DH6_REPLY;
+ } else
+ resptype = DH6_ADVERTISE;
+
+ error = server6_send(resptype, ifp, dh6, optinfo, from, fromlen,
+ &roptinfo, relayinfohead, client_conf);
+ dhcp6_clear_options(&roptinfo);
+ return (error);
+
+ fail:
+ dhcp6_clear_options(&roptinfo);
+ return (-1);
+}
+
+static int
+react_request(ifp, pi, dh6, len, optinfo, from, fromlen, relayinfohead)
+ struct dhcp6_if *ifp;
+ struct in6_pktinfo *pi;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+{
+ struct dhcp6_optinfo roptinfo;
+ struct host_conf *client_conf;
+
+ /* message validation according to Section 15.4 of RFC3315 */
+
+ /* the message must include a Server Identifier option */
+ if (optinfo->serverID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+ /* the contents of the Server Identifier option must match ours */
+ if (duidcmp(&optinfo->serverID, &server_duid)) {
+ dprintf(LOG_INFO, FNAME, "server ID mismatch");
+ return (-1);
+ }
+ /* the message must include a Client Identifier option */
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+
+ /*
+ * configure necessary options based on the options in request.
+ */
+ dhcp6_init_options(&roptinfo);
+
+ /* server identifier option */
+ if (duidcpy(&roptinfo.serverID, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto fail;
+ }
+ /* copy client information back */
+ if (duidcpy(&roptinfo.clientID, &optinfo->clientID)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto fail;
+ }
+
+ /* get per-host configuration for the client, if any. */
+ if ((client_conf = find_hostconf(&optinfo->clientID))) {
+ dprintf(LOG_DEBUG, FNAME,
+ "found a host configuration named %s", client_conf->name);
+ }
+
+ /* process authentication */
+ if (process_auth(dh6, len, client_conf, optinfo, &roptinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication "
+ "information for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ goto fail;
+ }
+
+ /*
+ * When the server receives a Request message via unicast from a
+ * client to which the server has not sent a unicast option, the server
+ * discards the Request message and responds with a Reply message
+ * containing a Status Code option with value UseMulticast, a Server
+ * Identifier option containing the server's DUID, the Client
+ * Identifier option from the client message and no other options.
+ * [RFC3315 18.2.1]
+ * (Our current implementation never sends a unicast option.)
+ * Note: a request message encapsulated in a relay server option can be
+ * unicasted.
+ */
+ if (!IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) &&
+ TAILQ_EMPTY(relayinfohead)) {
+ u_int16_t stcode = DH6OPT_STCODE_USEMULTICAST;
+
+ dprintf(LOG_INFO, FNAME, "unexpected unicast message from %s",
+ addr2str(from));
+ if (dhcp6_add_listval(&roptinfo.stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to add a status code");
+ goto fail;
+ }
+ server6_send(DH6_REPLY, ifp, dh6, optinfo, from,
+ fromlen, &roptinfo, relayinfohead, client_conf);
+ goto end;
+ }
+
+ /*
+ * See if we have to make a binding of some configuration information
+ * for the client.
+ */
+
+ /*
+ * When a delegating router receives a Request message from a
+ * requesting router that contains an IA_PD option, and the delegating
+ * router is authorized to delegate prefix(es) to the requesting
+ * router, the delegating router selects the prefix(es) to be delegated
+ * to the requesting router.
+ * [RFC3633 Section 12.2]
+ */
+ if (!TAILQ_EMPTY(&optinfo->iapd_list)) {
+ struct dhcp6_list conflist;
+ struct dhcp6_listval *iapd;
+
+ TAILQ_INIT(&conflist);
+
+ /* make a local copy of the configured prefixes */
+ if (client_conf &&
+ dhcp6_copy_list(&conflist, &client_conf->prefix_list)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make local data");
+ goto fail;
+ }
+
+ for (iapd = TAILQ_FIRST(&optinfo->iapd_list); iapd;
+ iapd = TAILQ_NEXT(iapd, link)) {
+ /*
+ * Find an appropriate prefix for each IA_PD,
+ * removing the adopted prefixes from the list.
+ * The prefixes will be bound to the client.
+ */
+ if (make_ia(iapd, &conflist, &roptinfo.iapd_list,
+ client_conf, 1) == 0) {
+ /*
+ * We could not find any prefixes for the IA.
+ * RFC3315 specifies to include NoAddrsAvail
+ * for the IA in the address configuration
+ * case (Section 18.2.1). We follow the same
+ * logic for prefix delegation as well.
+ */
+ if (make_ia_stcode(DHCP6_LISTVAL_IAPD,
+ iapd->val_ia.iaid,
+ DH6OPT_STCODE_NOPREFIXAVAIL,
+ &roptinfo.iapd_list)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make an option list");
+ dhcp6_clear_list(&conflist);
+ goto fail;
+ }
+ }
+ }
+
+ dhcp6_clear_list(&conflist);
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->iana_list)) {
+ struct dhcp6_list conflist;
+ struct dhcp6_listval *iana;
+
+ TAILQ_INIT(&conflist);
+
+ /* make a local copy of the configured prefixes */
+ if (client_conf &&
+ dhcp6_copy_list(&conflist, &client_conf->addr_list)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make local data");
+ goto fail;
+ }
+
+ for (iana = TAILQ_FIRST(&optinfo->iana_list); iana;
+ iana = TAILQ_NEXT(iana, link)) {
+ /*
+ * Find an appropriate address for each IA_NA,
+ * removing the adopted addresses from the list.
+ * The addresses will be bound to the client.
+ */
+ if (make_ia(iana, &conflist, &roptinfo.iana_list,
+ client_conf, 1) == 0) {
+ if (make_ia_stcode(DHCP6_LISTVAL_IANA,
+ iana->val_ia.iaid,
+ DH6OPT_STCODE_NOADDRSAVAIL,
+ &roptinfo.iana_list)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make an option list");
+ dhcp6_clear_list(&conflist);
+ goto fail;
+ }
+ }
+ }
+
+ dhcp6_clear_list(&conflist);
+ }
+
+ /*
+ * If the Request message contained an Option Request option, the
+ * server MUST include options in the Reply message for any options in
+ * the Option Request option the server is configured to return to the
+ * client.
+ * [RFC3315 18.2.1]
+ * Note: our current implementation always includes all information
+ * that we can provide. So we do not have to check the option request
+ * options.
+ */
+#if 0
+ for (opt = TAILQ_FIRST(&optinfo->reqopt_list); opt;
+ opt = TAILQ_NEXT(opt, link)) {
+ ;
+ }
+#endif
+
+ /*
+ * Add options to the Reply message for any other configuration
+ * information to be assigned to the client.
+ */
+ if (set_statelessinfo(DH6_REQUEST, &roptinfo)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to set other stateless information");
+ goto fail;
+ }
+
+ /* send a reply message. */
+ (void)server6_send(DH6_REPLY, ifp, dh6, optinfo, from, fromlen,
+ &roptinfo, relayinfohead, client_conf);
+
+ end:
+ dhcp6_clear_options(&roptinfo);
+ return (0);
+
+ fail:
+ dhcp6_clear_options(&roptinfo);
+ return (-1);
+}
+
+static int
+react_renew(ifp, pi, dh6, len, optinfo, from, fromlen, relayinfohead)
+ struct dhcp6_if *ifp;
+ struct in6_pktinfo *pi;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+{
+ struct dhcp6_optinfo roptinfo;
+ struct dhcp6_listval *ia;
+ struct host_conf *client_conf;
+
+ /* message validation according to Section 15.6 of RFC3315 */
+
+ /* the message must include a Server Identifier option */
+ if (optinfo->serverID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+ /* the contents of the Server Identifier option must match ours */
+ if (duidcmp(&optinfo->serverID, &server_duid)) {
+ dprintf(LOG_INFO, FNAME, "server ID mismatch");
+ return (-1);
+ }
+ /* the message must include a Client Identifier option */
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+
+ /*
+ * configure necessary options based on the options in request.
+ */
+ dhcp6_init_options(&roptinfo);
+
+ /* server identifier option */
+ if (duidcpy(&roptinfo.serverID, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto fail;
+ }
+ /* copy client information back */
+ if (duidcpy(&roptinfo.clientID, &optinfo->clientID)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto fail;
+ }
+
+ /* get per-host configuration for the client, if any. */
+ if ((client_conf = find_hostconf(&optinfo->clientID))) {
+ dprintf(LOG_DEBUG, FNAME,
+ "found a host configuration named %s", client_conf->name);
+ }
+
+ /* process authentication */
+ if (process_auth(dh6, len, client_conf, optinfo, &roptinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication "
+ "information for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ goto fail;
+ }
+
+ /*
+ * When the server receives a Renew message via unicast from a
+ * client to which the server has not sent a unicast option, the server
+ * discards the Request message and responds with a Reply message
+ * containing a status code option with value UseMulticast, a Server
+ * Identifier option containing the server's DUID, the Client
+ * Identifier option from the client message and no other options.
+ * [RFC3315 18.2.3]
+ * (Our current implementation never sends a unicast option.)
+ */
+ if (!IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) &&
+ TAILQ_EMPTY(relayinfohead)) {
+ u_int16_t stcode = DH6OPT_STCODE_USEMULTICAST;
+
+ dprintf(LOG_INFO, FNAME, "unexpected unicast message from %s",
+ addr2str(from));
+ if (dhcp6_add_listval(&roptinfo.stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to add a status code");
+ goto fail;
+ }
+ server6_send(DH6_REPLY, ifp, dh6, optinfo, from,
+ fromlen, &roptinfo, relayinfohead, client_conf);
+ goto end;
+ }
+
+ /*
+ * Locates the client's binding and verifies that the information
+ * from the client matches the information stored for that client.
+ */
+ for (ia = TAILQ_FIRST(&optinfo->iapd_list); ia;
+ ia = TAILQ_NEXT(ia, link)) {
+ if (update_ia(DH6_RENEW, ia, &roptinfo.iapd_list, optinfo))
+ goto fail;
+ }
+ for (ia = TAILQ_FIRST(&optinfo->iana_list); ia;
+ ia = TAILQ_NEXT(ia, link)) {
+ if (update_ia(DH6_RENEW, ia, &roptinfo.iana_list, optinfo))
+ goto fail;
+ }
+
+ /* add other configuration information */
+ if (set_statelessinfo(DH6_RENEW, &roptinfo)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to set other stateless information");
+ goto fail;
+ }
+
+ (void)server6_send(DH6_REPLY, ifp, dh6, optinfo, from, fromlen,
+ &roptinfo, relayinfohead, client_conf);
+
+ end:
+ dhcp6_clear_options(&roptinfo);
+ return (0);
+
+ fail:
+ dhcp6_clear_options(&roptinfo);
+ return (-1);
+}
+
+static int
+react_rebind(ifp, dh6, len, optinfo, from, fromlen, relayinfohead)
+ struct dhcp6_if *ifp;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+{
+ struct dhcp6_optinfo roptinfo;
+ struct dhcp6_listval *ia;
+ struct host_conf *client_conf;
+
+ /* message validation according to Section 15.7 of RFC3315 */
+
+ /* the message must include a Client Identifier option */
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+
+ /* the message must not include a server Identifier option */
+ if (optinfo->serverID.duid_len) {
+ dprintf(LOG_INFO, FNAME, "server ID option is included in "
+ "a rebind message");
+ return (-1);
+ }
+
+ /*
+ * configure necessary options based on the options in request.
+ */
+ dhcp6_init_options(&roptinfo);
+
+ /* server identifier option */
+ if (duidcpy(&roptinfo.serverID, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto fail;
+ }
+ /* copy client information back */
+ if (duidcpy(&roptinfo.clientID, &optinfo->clientID)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto fail;
+ }
+
+ /* get per-host configuration for the client, if any. */
+ if ((client_conf = find_hostconf(&optinfo->clientID))) {
+ dprintf(LOG_DEBUG, FNAME,
+ "found a host configuration named %s", client_conf->name);
+ }
+
+ /* process authentication */
+ if (process_auth(dh6, len, client_conf, optinfo, &roptinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication "
+ "information for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ goto fail;
+ }
+
+ /*
+ * Locates the client's binding and verifies that the information
+ * from the client matches the information stored for that client.
+ */
+ for (ia = TAILQ_FIRST(&optinfo->iapd_list); ia;
+ ia = TAILQ_NEXT(ia, link)) {
+ if (update_ia(DH6_REBIND, ia, &roptinfo.iapd_list, optinfo))
+ goto fail;
+ }
+ for (ia = TAILQ_FIRST(&optinfo->iana_list); ia;
+ ia = TAILQ_NEXT(ia, link)) {
+ if (update_ia(DH6_REBIND, ia, &roptinfo.iana_list, optinfo))
+ goto fail;
+ }
+
+ /*
+ * If the returned iapd_list is empty, we do not have an explicit
+ * knowledge about validity nor invalidity for any IA_PD information
+ * in the Rebind message. In this case, we should rather ignore the
+ * message than to send a Reply with empty information back to the
+ * client, which may annoy the recipient. However, if we have at least
+ * one useful information, either positive or negative, based on some
+ * explicit knowledge, we should reply with the responsible part.
+ */
+ if (TAILQ_EMPTY(&roptinfo.iapd_list)) {
+ dprintf(LOG_INFO, FNAME, "no useful information for a rebind");
+ goto fail; /* discard the rebind */
+ }
+
+ /* add other configuration information */
+ if (set_statelessinfo(DH6_REBIND, &roptinfo)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to set other stateless information");
+ goto fail;
+ }
+
+ (void)server6_send(DH6_REPLY, ifp, dh6, optinfo, from, fromlen,
+ &roptinfo, relayinfohead, client_conf);
+
+ dhcp6_clear_options(&roptinfo);
+ return (0);
+
+ fail:
+ dhcp6_clear_options(&roptinfo);
+ return (-1);
+}
+
+static int
+react_release(ifp, pi, dh6, len, optinfo, from, fromlen, relayinfohead)
+ struct dhcp6_if *ifp;
+ struct in6_pktinfo *pi;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+{
+ struct dhcp6_optinfo roptinfo;
+ struct dhcp6_listval *ia;
+ struct host_conf *client_conf;
+ u_int16_t stcode;
+
+ /* message validation according to Section 15.9 of RFC3315 */
+
+ /* the message must include a Server Identifier option */
+ if (optinfo->serverID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+ /* the contents of the Server Identifier option must match ours */
+ if (duidcmp(&optinfo->serverID, &server_duid)) {
+ dprintf(LOG_INFO, FNAME, "server ID mismatch");
+ return (-1);
+ }
+ /* the message must include a Client Identifier option */
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+
+ /*
+ * configure necessary options based on the options in request.
+ */
+ dhcp6_init_options(&roptinfo);
+
+ /* server identifier option */
+ if (duidcpy(&roptinfo.serverID, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto fail;
+ }
+ /* copy client information back */
+ if (duidcpy(&roptinfo.clientID, &optinfo->clientID)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto fail;
+ }
+
+ /* get per-host configuration for the client, if any. */
+ if ((client_conf = find_hostconf(&optinfo->clientID))) {
+ dprintf(LOG_DEBUG, FNAME,
+ "found a host configuration named %s", client_conf->name);
+ }
+
+ /* process authentication */
+ if (process_auth(dh6, len, client_conf, optinfo, &roptinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication "
+ "information for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ goto fail;
+ }
+
+ /*
+ * When the server receives a Release message via unicast from a
+ * client to which the server has not sent a unicast option, the server
+ * discards the Release message and responds with a Reply message
+ * containing a Status Code option with value UseMulticast, a Server
+ * Identifier option containing the server's DUID, the Client
+ * Identifier option from the client message and no other options.
+ * [RFC3315 18.2.6]
+ * (Our current implementation never sends a unicast option.)
+ */
+ if (!IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) &&
+ TAILQ_EMPTY(relayinfohead)) {
+ u_int16_t stcode = DH6OPT_STCODE_USEMULTICAST;
+
+ dprintf(LOG_INFO, FNAME, "unexpected unicast message from %s",
+ addr2str(from));
+ if (dhcp6_add_listval(&roptinfo.stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to add a status code");
+ goto fail;
+ }
+ server6_send(DH6_REPLY, ifp, dh6, optinfo, from,
+ fromlen, &roptinfo, relayinfohead, client_conf);
+ goto end;
+ }
+
+ /*
+ * Locates the client's binding and verifies that the information
+ * from the client matches the information stored for that client.
+ */
+ for (ia = TAILQ_FIRST(&optinfo->iapd_list); ia;
+ ia = TAILQ_NEXT(ia, link)) {
+ if (release_binding_ia(ia, &roptinfo.iapd_list, optinfo))
+ goto fail;
+ }
+ for (ia = TAILQ_FIRST(&optinfo->iana_list); ia;
+ ia = TAILQ_NEXT(ia, link)) {
+ if (release_binding_ia(ia, &roptinfo.iana_list, optinfo))
+ goto fail;
+ }
+
+ /*
+ * After all the addresses have been processed, the server generates a
+ * Reply message and includes a Status Code option with value Success.
+ * [RFC3315 Section 18.2.6]
+ */
+ stcode = DH6OPT_STCODE_SUCCESS;
+ if (dhcp6_add_listval(&roptinfo.stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, NULL) == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to add a status code");
+ goto fail;
+ }
+
+ (void)server6_send(DH6_REPLY, ifp, dh6, optinfo, from, fromlen,
+ &roptinfo, relayinfohead, client_conf);
+
+ end:
+ dhcp6_clear_options(&roptinfo);
+ return (0);
+
+ fail:
+ dhcp6_clear_options(&roptinfo);
+ return (-1);
+}
+
+static int
+react_informreq(ifp, dh6, len, optinfo, from, fromlen, relayinfohead)
+ struct dhcp6_if *ifp;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+{
+ struct dhcp6_optinfo roptinfo;
+ int error;
+
+ /*
+ * An IA option is not allowed to appear in an Information-request
+ * message. Such a message SHOULD be discarded.
+ * [RFC3315 Section 15]
+ */
+ if (!TAILQ_EMPTY(&optinfo->iapd_list)) {
+ dprintf(LOG_INFO, FNAME,
+ "information request contains an IA_PD option");
+ return (-1);
+ }
+ if (!TAILQ_EMPTY(&optinfo->iana_list)) {
+ dprintf(LOG_INFO, FNAME,
+ "information request contains an IA_NA option");
+ return (-1);
+ }
+
+ /* if a server identifier is included, it must match ours. */
+ if (optinfo->serverID.duid_len &&
+ duidcmp(&optinfo->serverID, &server_duid)) {
+ dprintf(LOG_INFO, FNAME, "server DUID mismatch");
+ return (-1);
+ }
+
+ /*
+ * configure necessary options based on the options in request.
+ */
+ dhcp6_init_options(&roptinfo);
+
+ /* server identifier option */
+ if (duidcpy(&roptinfo.serverID, &server_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto fail;
+ }
+
+ /* copy client information back (if provided) */
+ if (optinfo->clientID.duid_id &&
+ duidcpy(&roptinfo.clientID, &optinfo->clientID)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto fail;
+ }
+
+ /* set stateless information */
+ if (set_statelessinfo(DH6_INFORM_REQ, &roptinfo)) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to set other stateless information");
+ goto fail;
+ }
+
+ error = server6_send(DH6_REPLY, ifp, dh6, optinfo, from, fromlen,
+ &roptinfo, relayinfohead, NULL);
+
+ dhcp6_clear_options(&roptinfo);
+ return (error);
+
+ fail:
+ dhcp6_clear_options(&roptinfo);
+ return (-1);
+}
+
+static int
+update_ia(msgtype, iap, retlist, optinfo)
+ int msgtype;
+ struct dhcp6_listval *iap;
+ struct dhcp6_list *retlist;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct dhcp6_binding *binding;
+ struct host_conf *client_conf;
+
+ /* get per-host configuration for the client, if any. */
+ if ((client_conf = find_hostconf(&optinfo->clientID))) {
+ dprintf(LOG_DEBUG, FNAME,
+ "found a host configuration named %s", client_conf->name);
+ }
+
+ if ((binding = find_binding(&optinfo->clientID, DHCP6_BINDING_IA,
+ iap->type, iap->val_ia.iaid)) == NULL) {
+ /*
+ * Behavior in the case where the delegating router cannot
+ * find a binding for the requesting router's IA_PD as
+ * described in RFC3633 Section 12.2. It is derived from
+ * Sections 18.2.3 and 18.2.4 of RFC3315, and the two sets
+ * of behavior are identical.
+ */
+ dprintf(LOG_INFO, FNAME, "no binding found for %s",
+ duidstr(&optinfo->clientID));
+
+ switch (msgtype) {
+ case DH6_RENEW:
+ /*
+ * If the delegating router cannot find a binding for
+ * the requesting router's IA_PD the delegating router
+ * returns the IA_PD containing no prefixes with a
+ * Status Code option set to NoBinding in the Reply
+ * message.
+ */
+ if (make_ia_stcode(iap->type, iap->val_ia.iaid,
+ DH6OPT_STCODE_NOBINDING, retlist)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make an option list");
+ return (-1);
+ }
+ break;
+ case DH6_REBIND:
+ /*
+ * If it can be determined the prefixes are not
+ * appropriate from the delegating router's explicit
+ * configuration, it MAY send a Reply message to
+ * the requesting router containing the IA_PD with the
+ * lifetimes of the prefixes in the IA_PD set to zero.
+ *
+ * If unable to determine, the Rebind message is
+ * discarded.
+ *
+ * XXX: it is not very clear what the explicit
+ * configuration means. Thus, we always discard the
+ * message.
+ */
+ return (-1);
+ default: /* XXX: should be a bug */
+ dprintf(LOG_ERR, FNAME, "impossible message type %s",
+ dhcp6msgstr(msgtype));
+ return (-1);
+ }
+ } else { /* we found a binding */
+ struct dhcp6_list ialist;
+ struct dhcp6_listval *lv;
+ struct dhcp6_prefix prefix;
+ struct dhcp6_statefuladdr saddr;
+ struct dhcp6_ia ia;
+
+ TAILQ_INIT(&ialist);
+ update_binding(binding);
+
+ /* see if each information to be renewed is still valid. */
+ for (lv = TAILQ_FIRST(&iap->sublist); lv;
+ lv = TAILQ_NEXT(lv, link)) {
+ struct dhcp6_listval *blv;
+
+ switch (iap->type) {
+ case DHCP6_LISTVAL_IAPD:
+ if (lv->type != DHCP6_LISTVAL_PREFIX6)
+ continue;
+
+ prefix = lv->val_prefix6;
+ blv = dhcp6_find_listval(&binding->val_list,
+ DHCP6_LISTVAL_PREFIX6, &prefix, 0);
+ if (blv == NULL) {
+ dprintf(LOG_DEBUG, FNAME,
+ "%s/%d is not found in %s",
+ in6addr2str(&prefix.addr, 0),
+ prefix.plen, bindingstr(binding));
+ prefix.pltime = 0;
+ prefix.vltime = 0;
+ } else {
+ prefix.pltime =
+ blv->val_prefix6.pltime;
+ prefix.vltime =
+ blv->val_prefix6.vltime;
+ }
+
+ if (dhcp6_add_listval(&ialist,
+ DHCP6_LISTVAL_PREFIX6, &prefix, NULL)
+ == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to copy binding info");
+ dhcp6_clear_list(&ialist);
+ return (-1);
+ }
+ break;
+ case DHCP6_LISTVAL_IANA:
+ if (lv->type != DHCP6_LISTVAL_STATEFULADDR6)
+ continue;
+
+ saddr = lv->val_statefuladdr6;
+ blv = dhcp6_find_listval(&binding->val_list,
+ DHCP6_LISTVAL_STATEFULADDR6, &saddr, 0);
+ if (blv == NULL) {
+ dprintf(LOG_DEBUG, FNAME,
+ "%s is not found in %s",
+ in6addr2str(&saddr.addr, 0),
+ bindingstr(binding));
+ saddr.pltime = 0;
+ saddr.vltime = 0;
+ } else {
+ saddr.pltime =
+ blv->val_statefuladdr6.pltime;
+ saddr.vltime =
+ blv->val_statefuladdr6.pltime;
+ }
+
+ if (dhcp6_add_listval(&ialist,
+ DHCP6_LISTVAL_STATEFULADDR6, &saddr, NULL)
+ == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to copy binding info");
+ dhcp6_clear_list(&ialist);
+ return (-1);
+ }
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unsupported IA type");
+ return (-1); /* XXX */
+ }
+ }
+
+ memset(&ia, 0, sizeof(ia));
+ ia.iaid = binding->iaid;
+ /* determine appropriate T1 and T2 */
+ calc_ia_timo(&ia, &ialist, client_conf);
+
+ if (dhcp6_add_listval(retlist, iap->type,
+ &ia, &ialist) == NULL) {
+ dhcp6_clear_list(&ialist);
+ return (-1);
+ }
+ dhcp6_clear_list(&ialist);
+ }
+
+ return (0);
+}
+
+static int
+release_binding_ia(iap, retlist, optinfo)
+ struct dhcp6_listval *iap;
+ struct dhcp6_list *retlist;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct dhcp6_binding *binding;
+
+ if ((binding = find_binding(&optinfo->clientID, DHCP6_BINDING_IA,
+ iap->type, iap->val_ia.iaid)) == NULL) {
+ /*
+ * For each IA in the Release message for which the server has
+ * no binding information, the server adds an IA option using
+ * the IAID from the Release message and includes a Status Code
+ * option with the value NoBinding in the IA option.
+ */
+ if (make_ia_stcode(iap->type, iap->val_ia.iaid,
+ DH6OPT_STCODE_NOBINDING, retlist)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make an option list");
+ return (-1);
+ }
+ } else {
+ struct dhcp6_listval *lv, *lvia;
+
+ /*
+ * If the IAs in the message are in a binding for the client
+ * and the addresses in the IAs have been assigned by the
+ * server to those IAs, the server deletes the addresses from
+ * the IAs and makes the addresses available for assignment to
+ * other clients.
+ * [RFC3315 Section 18.2.6]
+ * RFC3633 is not very clear about the similar case for IA_PD,
+ * but we apply the same logic.
+ */
+ for (lv = TAILQ_FIRST(&iap->sublist); lv;
+ lv = TAILQ_NEXT(lv, link)) {
+ if ((lvia = find_binding_ia(lv, binding)) != NULL) {
+ switch (binding->iatype) {
+ case DHCP6_LISTVAL_IAPD:
+ dprintf(LOG_DEBUG, FNAME,
+ "bound prefix %s/%d "
+ "has been released",
+ in6addr2str(&lvia->val_prefix6.addr,
+ 0),
+ lvia->val_prefix6.plen);
+ break;
+ case DHCP6_LISTVAL_IANA:
+ dprintf(LOG_DEBUG, FNAME,
+ "bound address %s "
+ "has been released",
+ in6addr2str(&lvia->val_prefix6.addr,
+ 0));
+ break;
+ }
+
+ TAILQ_REMOVE(&binding->val_list, lvia, link);
+ dhcp6_clear_listval(lvia);
+ if (TAILQ_EMPTY(&binding->val_list)) {
+ /*
+ * if the binding has become empty,
+ * stop procedure.
+ */
+ remove_binding(binding);
+ return (0);
+ }
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+server6_send(type, ifp, origmsg, optinfo, from, fromlen,
+ roptinfo, relayinfohead, client_conf)
+ int type;
+ struct dhcp6_if *ifp;
+ struct dhcp6 *origmsg;
+ struct dhcp6_optinfo *optinfo, *roptinfo;
+ struct sockaddr *from;
+ int fromlen;
+ struct relayinfolist *relayinfohead;
+ struct host_conf *client_conf;
+{
+ char replybuf[BUFSIZ];
+ struct sockaddr_in6 dst;
+ int len, optlen;
+ int relayed = 0;
+ struct dhcp6 *dh6;
+ struct relayinfo *relayinfo;
+
+ if (sizeof(struct dhcp6) > sizeof(replybuf)) {
+ dprintf(LOG_ERR, FNAME, "buffer size assumption failed");
+ return (-1);
+ }
+
+ dh6 = (struct dhcp6 *)replybuf;
+ len = sizeof(*dh6);
+ memset(dh6, 0, sizeof(*dh6));
+ dh6->dh6_msgtypexid = origmsg->dh6_msgtypexid;
+ dh6->dh6_msgtype = (u_int8_t)type;
+
+ /* set options in the reply message */
+ if ((optlen = dhcp6_set_options(type, (struct dhcp6opt *)(dh6 + 1),
+ (struct dhcp6opt *)(replybuf + sizeof(replybuf)), roptinfo)) < 0) {
+ dprintf(LOG_INFO, FNAME, "failed to construct reply options");
+ return (-1);
+ }
+ len += optlen;
+
+ /* calculate MAC if necessary, and put it to the message */
+ switch (roptinfo->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ if (client_conf == NULL || client_conf->delayedkey == NULL) {
+ /* This case should have been caught earlier */
+ dprintf(LOG_ERR, FNAME, "authentication required "
+ "but not key provided");
+ break;
+ }
+ if (dhcp6_calc_mac((char *)dh6, len, roptinfo->authproto,
+ roptinfo->authalgorithm,
+ roptinfo->delayedauth_offset + sizeof(*dh6),
+ client_conf->delayedkey)) {
+ dprintf(LOG_WARNING, FNAME, "failed to calculate MAC");
+ return (-1);
+ }
+ break;
+ default:
+ break; /* do nothing */
+ }
+
+ /* construct a relay chain, if necessary */
+ for (relayinfo = TAILQ_FIRST(relayinfohead); relayinfo;
+ relayinfo = TAILQ_NEXT(relayinfo, link)) {
+ struct dhcp6_optinfo relayopt;
+ struct dhcp6_vbuf relaymsgbuf;
+ struct dhcp6_relay *dh6relay;
+
+ relayed = 1;
+ dhcp6_init_options(&relayopt);
+
+ relaymsgbuf.dv_len = len;
+ relaymsgbuf.dv_buf = replybuf;
+ if (dhcp6_vbuf_copy(&relayopt.relay_msg, &relaymsgbuf))
+ return (-1);
+ if (relayinfo->relay_ifid.dv_buf &&
+ dhcp6_vbuf_copy(&relayopt.ifidopt,
+ &relayinfo->relay_ifid)) {
+ dhcp6_vbuf_free(&relayopt.relay_msg);
+ return (-1);
+ }
+
+ /* we can safely reuse replybuf here */
+ dh6relay = (struct dhcp6_relay *)replybuf;
+ memset(dh6relay, 0, sizeof (*dh6relay));
+ dh6relay->dh6relay_msgtype = DH6_RELAY_REPLY;
+ dh6relay->dh6relay_hcnt = relayinfo->hcnt;
+ memcpy(&dh6relay->dh6relay_linkaddr, &relayinfo->linkaddr,
+ sizeof (dh6relay->dh6relay_linkaddr));
+ memcpy(&dh6relay->dh6relay_peeraddr, &relayinfo->peeraddr,
+ sizeof (dh6relay->dh6relay_peeraddr));
+
+ len = sizeof(*dh6relay);
+ if ((optlen = dhcp6_set_options(DH6_RELAY_REPLY,
+ (struct dhcp6opt *)(dh6relay + 1),
+ (struct dhcp6opt *)(replybuf + sizeof(replybuf)),
+ &relayopt)) < 0) {
+ dprintf(LOG_INFO, FNAME,
+ "failed to construct relay message");
+ dhcp6_clear_options(&relayopt);
+ return (-1);
+ }
+ len += optlen;
+
+ dhcp6_clear_options(&relayopt);
+ }
+
+ /* specify the destination and send the reply */
+ dst = relayed ? *sa6_any_relay : *sa6_any_downstream;
+ dst.sin6_addr = ((struct sockaddr_in6 *)from)->sin6_addr;
+ dst.sin6_scope_id = ((struct sockaddr_in6 *)from)->sin6_scope_id;
+ if (transmit_sa(outsock, (struct sockaddr *)&dst,
+ replybuf, len) != 0) {
+ dprintf(LOG_ERR, FNAME, "transmit %s to %s failed",
+ dhcp6msgstr(type), addr2str((struct sockaddr *)&dst));
+ return (-1);
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "transmit %s to %s",
+ dhcp6msgstr(type), addr2str((struct sockaddr *)&dst));
+
+ return (0);
+}
+
+static int
+make_ia_stcode(iatype, iaid, stcode, retlist)
+ int iatype;
+ u_int16_t stcode;
+ u_int32_t iaid;
+ struct dhcp6_list *retlist;
+{
+ struct dhcp6_list stcode_list;
+ struct dhcp6_ia ia_empty;
+
+ memset(&ia_empty, 0, sizeof(ia_empty));
+ ia_empty.iaid = iaid;
+
+ TAILQ_INIT(&stcode_list);
+ if (dhcp6_add_listval(&stcode_list, DHCP6_LISTVAL_STCODE,
+ &stcode, NULL) == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to make an option list");
+ return (-1);
+ }
+
+ if (dhcp6_add_listval(retlist, iatype,
+ &ia_empty, &stcode_list) == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to make an option list");
+ dhcp6_clear_list(&stcode_list);
+ return (-1);
+ }
+ dhcp6_clear_list(&stcode_list);
+
+ return (0);
+}
+
+static int
+make_ia(spec, conflist, retlist, client_conf, do_binding)
+ struct dhcp6_listval *spec;
+ struct dhcp6_list *conflist, *retlist;
+ struct host_conf *client_conf;
+ int do_binding;
+{
+ struct dhcp6_binding *binding;
+ struct dhcp6_list ialist;
+ struct dhcp6_listval *specia;
+ struct dhcp6_ia ia;
+ int found = 0;
+
+ /*
+ * If we happen to have a binding already, update the binding and
+ * return it. Perhaps the request is being retransmitted.
+ */
+ if ((binding = find_binding(&client_conf->duid, DHCP6_BINDING_IA,
+ spec->type, spec->val_ia.iaid)) != NULL) {
+ struct dhcp6_list *blist = &binding->val_list;
+ struct dhcp6_listval *bia, *v;
+
+ dprintf(LOG_DEBUG, FNAME, "we have a binding already: %s",
+ bindingstr(binding));
+
+ update_binding(binding);
+
+ memset(&ia, 0, sizeof(ia));
+ ia.iaid = spec->val_ia.iaid;
+ /* determine appropriate T1 and T2 */
+ calc_ia_timo(&ia, blist, client_conf);
+ if (dhcp6_add_listval(retlist, spec->type, &ia, blist)
+ == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to copy binding info");
+ return (0);
+ }
+
+ /* remove bound values from the configuration */
+ for (bia = TAILQ_FIRST(blist); bia;
+ bia = TAILQ_NEXT(bia, link)) {
+ if ((v = dhcp6_find_listval(conflist,
+ bia->type, &bia->uv, 0)) != NULL) {
+ found++;
+ TAILQ_REMOVE(conflist, v, link);
+ dhcp6_clear_listval(v);
+ }
+ }
+
+ return (found);
+ }
+
+ /*
+ * trivial case:
+ * if the configuration is empty, we cannot make any IA.
+ */
+ if (TAILQ_EMPTY(conflist))
+ return (0);
+
+ TAILQ_INIT(&ialist);
+
+ /* First, check if we can meet the client's requirement */
+ for (specia = TAILQ_FIRST(&spec->sublist); specia;
+ specia = TAILQ_NEXT(specia, link)) {
+ /* try to find an IA that matches the spec best. */
+ if (make_match_ia(specia, conflist, &ialist))
+ found++;
+ }
+ if (found == 0) {
+ struct dhcp6_listval *v;
+
+ /* use the first IA in the configuration list */
+ v = TAILQ_FIRST(conflist);
+ if (dhcp6_add_listval(&ialist, v->type, &v->uv, NULL)) {
+ found = 1;
+ TAILQ_REMOVE(conflist, v, link);
+ dhcp6_clear_listval(v);
+ }
+ }
+ if (found) {
+ memset(&ia, 0, sizeof(ia));
+ ia.iaid = spec->val_ia.iaid;
+ /* determine appropriate T1 and T2 */
+ calc_ia_timo(&ia, &ialist, client_conf);
+
+ /* make a binding for the set if necessary */
+ if (do_binding) {
+ if (add_binding(&client_conf->duid, DHCP6_BINDING_IA,
+ spec->type, spec->val_ia.iaid, &ialist) == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to make a binding");
+ found = 0;
+ }
+ }
+ if (found) {
+ /* make an IA for the set */
+ if (dhcp6_add_listval(retlist, spec->type,
+ &ia, &ialist) == NULL)
+ found = 0;
+ }
+ dhcp6_clear_list(&ialist);
+ }
+
+ return (found);
+}
+
+static int
+make_match_ia(spec, conflist, retlist)
+ struct dhcp6_listval *spec;
+ struct dhcp6_list *conflist, *retlist;
+{
+ struct dhcp6_listval *match;
+ int matched = 0;
+
+ /* do we have the exact value specified? */
+ match = dhcp6_find_listval(conflist, spec->type, &spec->uv, 0);
+
+ /* if not, make further search specific to the IA type. */
+ if (!match) {
+ switch (spec->type) {
+ case DHCP6_LISTVAL_PREFIX6:
+ match = dhcp6_find_listval(conflist, spec->type,
+ &spec->uv, MATCHLIST_PREFIXLEN);
+ break;
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ /* No "partial match" for addresses */
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unsupported IA type");
+ return (0); /* XXX */
+ }
+ }
+
+ /*
+ * if found, remove the matched entry from the configuration list
+ * and copy the value in the returned list.
+ */
+ if (match) {
+ if (dhcp6_add_listval(retlist, match->type,
+ &match->uv, NULL)) {
+ matched = 1;
+ TAILQ_REMOVE(conflist, match, link);
+ dhcp6_clear_listval(match);
+ }
+ }
+
+ return (matched);
+}
+
+static void
+calc_ia_timo(ia, ialist, client_conf)
+ struct dhcp6_ia *ia;
+ struct dhcp6_list *ialist; /* this should not be empty */
+ struct host_conf *client_conf; /* unused yet */
+{
+ struct dhcp6_listval *iav;
+ u_int32_t base = DHCP6_DURATITION_INFINITE;
+ int iatype;
+
+ iatype = TAILQ_FIRST(ialist)->type;
+ for (iav = TAILQ_FIRST(ialist); iav; iav = TAILQ_NEXT(iav, link)) {
+ if (iav->type != iatype) {
+ dprintf(LOG_ERR, FNAME,
+ "assumption failure: IA list is not consistent");
+ exit (1); /* XXX */
+ }
+ switch (iatype) {
+ case DHCP6_LISTVAL_PREFIX6:
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ if (base == DHCP6_DURATITION_INFINITE ||
+ iav->val_prefix6.pltime < base)
+ base = iav->val_prefix6.pltime;
+ break;
+ }
+ }
+
+ switch (iatype) {
+ case DHCP6_LISTVAL_PREFIX6:
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ /*
+ * Configure the timeout parameters as recommended in
+ * Section 22.4 of RFC3315 and Section 9 of RFC3633.
+ * We could also set the parameters to 0 if we let the client
+ * decide the renew timing (not implemented yet).
+ */
+ if (base == DHCP6_DURATITION_INFINITE) {
+ ia->t1 = DHCP6_DURATITION_INFINITE;
+ ia->t2 = DHCP6_DURATITION_INFINITE;
+ } else {
+ ia->t1 = base / 2;
+ ia->t2 = (base * 4) / 5;
+ }
+ break;
+ }
+}
+
+static void
+update_binding_duration(binding)
+ struct dhcp6_binding *binding;
+{
+ struct dhcp6_list *ia_list = &binding->val_list;
+ struct dhcp6_listval *iav;
+ int duration = DHCP6_DURATITION_INFINITE;
+ u_int32_t past, min_lifetime;
+ time_t now = time(NULL);
+
+ min_lifetime = 0;
+ past = (u_int32_t)(now >= binding->updatetime ?
+ now - binding->updatetime : 0);
+
+ switch (binding->type) {
+ case DHCP6_BINDING_IA:
+ /*
+ * Binding configuration is a list of IA parameters.
+ * Determine the minimum valid lifetime.
+ */
+ for (iav = TAILQ_FIRST(ia_list); iav;
+ iav = TAILQ_NEXT(iav, link)) {
+ u_int32_t lifetime;
+
+ switch (binding->iatype) {
+ case DHCP6_LISTVAL_IAPD:
+ lifetime = iav->val_prefix6.vltime;
+ break;
+ case DHCP6_LISTVAL_IANA:
+ lifetime = iav->val_statefuladdr6.vltime;
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unsupported IA type");
+ return; /* XXX */
+ }
+
+ if (min_lifetime == 0 ||
+ (lifetime != DHCP6_DURATITION_INFINITE &&
+ lifetime < min_lifetime))
+ min_lifetime = lifetime;
+ }
+
+ if (past < min_lifetime)
+ duration = min_lifetime - past;
+ else
+ duration = 0;
+
+ break;
+ default:
+ /* should be internal error. */
+ dprintf(LOG_ERR, FNAME, "unknown binding type (%d)",
+ binding->type);
+ return;
+ }
+
+ binding->duration = duration;
+}
+
+static struct dhcp6_binding *
+add_binding(clientid, btype, iatype, iaid, val0)
+ struct duid *clientid;
+ dhcp6_bindingtype_t btype;
+ int iatype;
+ u_int32_t iaid;
+ void *val0;
+{
+ struct dhcp6_binding *binding = NULL;
+ u_int32_t duration = DHCP6_DURATITION_INFINITE;
+
+ if ((binding = malloc(sizeof(*binding))) == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to allocate memory");
+ return (NULL);
+ }
+ memset(binding, 0, sizeof(*binding));
+ binding->type = btype;
+ if (duidcpy(&binding->clientid, clientid)) {
+ dprintf(LOG_NOTICE, FNAME, "failed to copy DUID");
+ goto fail;
+ }
+ binding->iatype = iatype;
+ binding->iaid = iaid;
+
+ /* construct configuration information for this binding */
+ switch (btype) {
+ case DHCP6_BINDING_IA:
+ TAILQ_INIT(&binding->val_list);
+ if (dhcp6_copy_list(&binding->val_list,
+ (struct dhcp6_list *)val0)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to copy binding data");
+ goto fail;
+ }
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unexpected binding type(%d)", btype);
+ goto fail;
+ }
+
+ /* calculate duration and start timer accordingly */
+ binding->updatetime = time(NULL);
+ update_binding_duration(binding);
+ if (binding->duration != DHCP6_DURATITION_INFINITE) {
+ struct timeval timo;
+
+ binding->timer = dhcp6_add_timer(binding_timo, binding);
+ if (binding->timer == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to add timer");
+ goto fail;
+ }
+ timo.tv_sec = (long)duration;
+ timo.tv_usec = 0;
+ dhcp6_set_timer(&timo, binding->timer);
+ }
+
+ TAILQ_INSERT_TAIL(&dhcp6_binding_head, binding, link);
+
+ dprintf(LOG_DEBUG, FNAME, "add a new binding %s", bindingstr(binding));
+
+ return (binding);
+
+ fail:
+ if (binding)
+ free_binding(binding);
+ return (NULL);
+}
+
+static struct dhcp6_binding *
+find_binding(clientid, btype, iatype, iaid)
+ struct duid *clientid;
+ dhcp6_bindingtype_t btype;
+ int iatype;
+ u_int32_t iaid;
+{
+ struct dhcp6_binding *bp;
+
+ for (bp = TAILQ_FIRST(&dhcp6_binding_head); bp;
+ bp = TAILQ_NEXT(bp, link)) {
+ if (bp->type != btype || duidcmp(&bp->clientid, clientid))
+ continue;
+
+ if (btype == DHCP6_BINDING_IA &&
+ (bp->iatype != iatype || bp->iaid != iaid))
+ continue;
+
+ return (bp);
+ }
+
+ return (NULL);
+}
+
+static void
+update_binding(binding)
+ struct dhcp6_binding *binding;
+{
+ struct timeval timo;
+
+ dprintf(LOG_DEBUG, FNAME, "update binding %s for %s",
+ bindingstr(binding), duidstr(&binding->clientid));
+
+ /* update timestamp and calculate new duration */
+ binding->updatetime = time(NULL);
+ update_binding_duration(binding);
+
+ /* if the lease duration is infinite, there's nothing to do. */
+ if (binding->duration == DHCP6_DURATITION_INFINITE)
+ return;
+
+ /* reset the timer with the duration */
+ timo.tv_sec = (long)binding->duration;
+ timo.tv_usec = 0;
+ dhcp6_set_timer(&timo, binding->timer);
+}
+
+static void
+remove_binding(binding)
+ struct dhcp6_binding *binding;
+{
+ dprintf(LOG_DEBUG, FNAME, "remove a binding %s",
+ bindingstr(binding));
+
+ if (binding->timer)
+ dhcp6_remove_timer(&binding->timer);
+
+ TAILQ_REMOVE(&dhcp6_binding_head, binding, link);
+
+ free_binding(binding);
+}
+
+static void
+free_binding(binding)
+ struct dhcp6_binding *binding;
+{
+ duidfree(&binding->clientid);
+
+ /* free configuration info in a type dependent manner. */
+ switch (binding->type) {
+ case DHCP6_BINDING_IA:
+ dhcp6_clear_list(&binding->val_list);
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unknown binding type %d",
+ binding->type);
+ break;
+ }
+
+ free(binding);
+}
+
+static struct dhcp6_timer *
+binding_timo(arg)
+ void *arg;
+{
+ struct dhcp6_binding *binding = (struct dhcp6_binding *)arg;
+ struct dhcp6_list *ia_list = &binding->val_list;
+ struct dhcp6_listval *iav, *iav_next;
+ time_t now = time(NULL);
+ u_int32_t past, lifetime;
+ struct timeval timo;
+
+ past = (u_int32_t)(now >= binding->updatetime ?
+ now - binding->updatetime : 0);
+
+ switch (binding->type) {
+ case DHCP6_BINDING_IA:
+ for (iav = TAILQ_FIRST(ia_list); iav; iav = iav_next) {
+ iav_next = TAILQ_NEXT(iav, link);
+
+ switch (binding->iatype) {
+ case DHCP6_LISTVAL_IAPD:
+ case DHCP6_LISTVAL_IANA:
+ lifetime = iav->val_prefix6.vltime;
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "internal error: "
+ "unknown binding type (%d)",
+ binding->iatype);
+ return (NULL); /* XXX */
+ }
+
+ if (lifetime != DHCP6_DURATITION_INFINITE &&
+ lifetime <= past) {
+ dprintf(LOG_DEBUG, FNAME, "bound prefix %s/%d"
+ " in %s has expired",
+ in6addr2str(&iav->val_prefix6.addr, 0),
+ iav->val_prefix6.plen,
+ bindingstr(binding));
+ TAILQ_REMOVE(ia_list, iav, link);
+ dhcp6_clear_listval(iav);
+ }
+ }
+
+ /* If all IA parameters have expired, remove the binding. */
+ if (TAILQ_EMPTY(ia_list)) {
+ remove_binding(binding);
+ return (NULL);
+ }
+
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unknown binding type %d",
+ binding->type);
+ return (NULL); /* XXX */
+ }
+
+ update_binding_duration(binding);
+
+ /* if the lease duration is infinite, there's nothing to do. */
+ if (binding->duration == DHCP6_DURATITION_INFINITE)
+ return (NULL);
+
+ /* reset the timer with the duration */
+ timo.tv_sec = (long)binding->duration;
+ timo.tv_usec = 0;
+ dhcp6_set_timer(&timo, binding->timer);
+
+ return (binding->timer);
+}
+
+static struct dhcp6_listval *
+find_binding_ia(key, binding)
+ struct dhcp6_listval *key;
+ struct dhcp6_binding *binding;
+{
+ struct dhcp6_list *ia_list = &binding->val_list;
+
+ switch (binding->type) {
+ case DHCP6_BINDING_IA:
+ return (dhcp6_find_listval(ia_list, key->type, &key->uv, 0));
+ default:
+ dprintf(LOG_ERR, FNAME, "unknown binding type %d",
+ binding->type);
+ return (NULL); /* XXX */
+ }
+}
+
+static char *
+bindingstr(binding)
+ struct dhcp6_binding *binding;
+{
+ static char strbuf[LINE_MAX]; /* XXX: thread unsafe */
+ char *iatype = NULL;
+
+ switch (binding->type) {
+ case DHCP6_BINDING_IA:
+ switch (binding->iatype) {
+ case DHCP6_LISTVAL_IAPD:
+ iatype = "PD";
+ break;
+ case DHCP6_LISTVAL_IANA:
+ iatype = "NA";
+ break;
+ }
+
+ snprintf(strbuf, sizeof(strbuf),
+ "[IA: duid=%s, type=%s, iaid=%lu, duration=%lu]",
+ duidstr(&binding->clientid), iatype, (u_long)binding->iaid,
+ (u_long)binding->duration);
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unexpected binding type(%d)",
+ binding->type);
+ return ("???");
+ }
+
+ return (strbuf);
+}
+
+static int
+process_auth(dh6, len, client_conf, optinfo, roptinfo)
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct host_conf *client_conf;
+ struct dhcp6_optinfo *optinfo, *roptinfo;
+{
+ u_int8_t msgtype = dh6->dh6_msgtype;
+ int authenticated = 0;
+ struct keyinfo *key;
+
+ /*
+ * if the client wanted DHCPv6 authentication, check if a secret
+ * key is available for the client.
+ */
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_UNDEF:
+ /*
+ * The client did not include authentication option. What if
+ * we had sent authentication information? The specification
+ * is not clear, but we should probably accept it, since the
+ * client MAY ignore the information in advertise messages.
+ */
+ return (0);
+ case DHCP6_AUTHPROTO_DELAYED:
+ if (optinfo->authalgorithm != DHCP6_AUTHALG_HMACMD5) {
+ dprintf(LOG_INFO, FNAME, "unknown authentication "
+ "algorithm (%d) required by %s",
+ optinfo->authalgorithm,
+ clientstr(client_conf, &optinfo->clientID));
+ break; /* give up with this authentication */
+ }
+
+ if (optinfo->authrdm != DHCP6_AUTHRDM_MONOCOUNTER) {
+ dprintf(LOG_INFO, FNAME,
+ "unknown RDM (%d) required by %s",
+ optinfo->authrdm,
+ clientstr(client_conf, &optinfo->clientID));
+ break; /* give up with this authentication */
+ }
+
+ /* see if we have a key for the client */
+ if (client_conf == NULL || client_conf->delayedkey == NULL) {
+ dprintf(LOG_INFO, FNAME, "client %s wanted "
+ "authentication, but no key found",
+ clientstr(client_conf, &optinfo->clientID));
+ break;
+ }
+ key = client_conf->delayedkey;
+ dprintf(LOG_DEBUG, FNAME, "found key %s for client %s",
+ key->name, clientstr(client_conf, &optinfo->clientID));
+
+ if (msgtype == DH6_SOLICIT) {
+ if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
+ /*
+ * A solicit message should not contain
+ * authentication information.
+ */
+ dprintf(LOG_INFO, FNAME,
+ "authentication information "
+ "provided in solicit from %s",
+ clientstr(client_conf,
+ &optinfo->clientID));
+ /* accept it anyway. (or discard?) */
+ }
+ } else {
+ /* replay protection */
+ if (!client_conf->saw_previous_rd) {
+ dprintf(LOG_WARNING, FNAME,
+ "previous RD value for %s is unknown "
+ "(accept it)", clientstr(client_conf,
+ &optinfo->clientID));
+ } else {
+ if (dhcp6_auth_replaycheck(optinfo->authrdm,
+ client_conf->previous_rd,
+ optinfo->authrd)) {
+ dprintf(LOG_INFO, FNAME,
+ "possible replay attack detected "
+ "for client %s",
+ clientstr(client_conf,
+ &optinfo->clientID));
+ break;
+ }
+ }
+
+ if ((optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
+ dprintf(LOG_INFO, FNAME,
+ "client %s did not provide authentication "
+ "information in %s",
+ clientstr(client_conf, &optinfo->clientID),
+ dhcp6msgstr(msgtype));
+ break;
+ }
+
+ /*
+ * The client MUST use the same key used by the server
+ * to generate the authentication information.
+ * [RFC3315 Section 21.4.4.3]
+ * The RFC does not say what the server should do if
+ * the client breaks this rule, but it should be
+ * natural to interpret this as authentication failure.
+ */
+ if (optinfo->delayedauth_keyid != key->keyid ||
+ optinfo->delayedauth_realmlen != key->realmlen ||
+ memcmp(optinfo->delayedauth_realmval, key->realm,
+ key->realmlen) != 0) {
+ dprintf(LOG_INFO, FNAME, "authentication key "
+ "mismatch with client %s",
+ clientstr(client_conf,
+ &optinfo->clientID));
+ break;
+ }
+
+ /* check for the key lifetime */
+ if (dhcp6_validate_key(key)) {
+ dprintf(LOG_INFO, FNAME, "key %s has expired",
+ key->name);
+ break;
+ }
+
+ /* validate MAC */
+ if (dhcp6_verify_mac((char *)dh6, len,
+ optinfo->authproto, optinfo->authalgorithm,
+ optinfo->delayedauth_offset + sizeof(*dh6), key)
+ == 0) {
+ dprintf(LOG_DEBUG, FNAME,
+ "message authentication validated for "
+ "client %s", clientstr(client_conf,
+ &optinfo->clientID));
+ } else {
+ dprintf(LOG_INFO, FNAME, "invalid message "
+ "authentication");
+ break;
+ }
+ }
+
+ roptinfo->authproto = optinfo->authproto;
+ roptinfo->authalgorithm = optinfo->authalgorithm;
+ roptinfo->authrdm = optinfo->authrdm;
+
+ if (get_rdvalue(roptinfo->authrdm, &roptinfo->authrd,
+ sizeof(roptinfo->authrd))) {
+ dprintf(LOG_ERR, FNAME, "failed to get a replay "
+ "detection value for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ break; /* XXX: try to recover? */
+ }
+
+ roptinfo->delayedauth_keyid = key->keyid;
+ roptinfo->delayedauth_realmlen = key->realmlen;
+ roptinfo->delayedauth_realmval =
+ malloc(roptinfo->delayedauth_realmlen);
+ if (roptinfo->delayedauth_realmval == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to allocate memory "
+ "for authentication realm for %s",
+ clientstr(client_conf, &optinfo->clientID));
+ break;
+ }
+ memcpy(roptinfo->delayedauth_realmval, key->realm,
+ roptinfo->delayedauth_realmlen);
+
+ authenticated = 1;
+
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME, "client %s wanted authentication "
+ "with unsupported protocol (%d)",
+ clientstr(client_conf, &optinfo->clientID),
+ optinfo->authproto);
+ return (-1); /* or simply ignore it? */
+ }
+
+ if (authenticated == 0) {
+ if (msgtype != DH6_SOLICIT) {
+ /*
+ * If the message fails to pass the validation test,
+ * the server MUST discard the message.
+ * [RFC3315 Section 21.4.5.2]
+ */
+ return (-1);
+ }
+ } else {
+ /* Message authenticated. Update RD counter. */
+ if (msgtype != DH6_SOLICIT && client_conf != NULL) {
+ client_conf->previous_rd = optinfo->authrd;
+ client_conf->saw_previous_rd = 1;
+ }
+ }
+
+ return (0);
+}
+
+static inline char *
+clientstr(conf, duid)
+ struct host_conf *conf;
+ struct duid *duid;
+{
+ if (conf != NULL)
+ return (conf->name);
+
+ return (duidstr(duid));
+}