aboutsummaryrefslogtreecommitdiff
path: root/dhcp6c.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 /dhcp6c.c
imported KAME-DHCPv6 snapshot at 20051201KAME_20051201
Diffstat (limited to 'dhcp6c.c')
-rw-r--r--dhcp6c.c2202
1 files changed, 2202 insertions, 0 deletions
diff --git a/dhcp6c.c b/dhcp6c.c
new file mode 100644
index 0000000..c776021
--- /dev/null
+++ b/dhcp6c.c
@@ -0,0 +1,2202 @@
+/* $KAME: dhcp6c.c,v 1.163 2005/09/16 11:30:14 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/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <limits.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 <net/if.h>
+#ifdef __FreeBSD__
+#include <net/if_var.h>
+#endif
+
+#include <netinet/in.h>
+#ifdef __KAME__
+#include <net/if_dl.h>
+#include <netinet6/in6_var.h>
+#endif
+
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <err.h>
+#include <ifaddrs.h>
+
+#include <dhcp6.h>
+#include <config.h>
+#include <common.h>
+#include <timer.h>
+#include <dhcp6c.h>
+#include <control.h>
+#include <dhcp6_ctl.h>
+#include <dhcp6c_ia.h>
+#include <prefixconf.h>
+#include <auth.h>
+
+static int debug = 0;
+static int exit_ok = 0;
+static u_long sig_flags = 0;
+#define SIGF_TERM 0x1
+#define SIGF_HUP 0x2
+
+const dhcp6_mode_t dhcp6_mode = DHCP6_MODE_CLIENT;
+
+int insock; /* inbound udp port */
+int outsock; /* outbound udp port */
+int rtsock; /* routing socket */
+int ctlsock = -1; /* control TCP port */
+char *ctladdr = DEFAULT_CLIENT_CONTROL_ADDR;
+char *ctlport = DEFAULT_CLIENT_CONTROL_PORT;
+
+#define DEFAULT_KEYFILE SYSCONFDIR "/dhcp6cctlkey"
+#define CTLSKEW 300
+
+static char *conffile = DHCP6C_CONF;
+
+static const struct sockaddr_in6 *sa6_allagent;
+static struct duid client_duid;
+static char *pid_file = DHCP6C_PIDFILE;
+
+static char *ctlkeyfile = DEFAULT_KEYFILE;
+static struct keyinfo *ctlkey = NULL;
+static int ctldigestlen;
+
+static int infreq_mode = 0;
+
+static inline int get_val32 __P((char **, int *, u_int32_t *));
+static inline int get_ifname __P((char **, int *, char *, int));
+
+static void usage __P((void));
+static void client6_init __P((void));
+static void client6_startall __P((int));
+static void free_resources __P((struct dhcp6_if *));
+static void client6_mainloop __P((void));
+static int client6_do_ctlcommand __P((char *, ssize_t));
+static void client6_reload __P((void));
+static int client6_ifctl __P((char *ifname, u_int16_t));
+static void check_exit __P((void));
+static void process_signals __P((void));
+static struct dhcp6_serverinfo *find_server __P((struct dhcp6_event *,
+ struct duid *));
+static struct dhcp6_serverinfo *select_server __P((struct dhcp6_event *));
+static void client6_recv __P((void));
+static int client6_recvadvert __P((struct dhcp6_if *, struct dhcp6 *,
+ ssize_t, struct dhcp6_optinfo *));
+static int client6_recvreply __P((struct dhcp6_if *, struct dhcp6 *,
+ ssize_t, struct dhcp6_optinfo *));
+static void client6_signal __P((int));
+static struct dhcp6_event *find_event_withid __P((struct dhcp6_if *,
+ u_int32_t));
+static int construct_confdata __P((struct dhcp6_if *, struct dhcp6_event *));
+static int construct_reqdata __P((struct dhcp6_if *, struct dhcp6_optinfo *,
+ struct dhcp6_event *));
+static void destruct_iadata __P((struct dhcp6_eventdata *));
+static void tv_sub __P((struct timeval *, struct timeval *, struct timeval *));
+#ifdef USE_DH6OPT_REFRESHTIME
+static struct dhcp6_timer *client6_expire_refreshtime __P((void *));
+#endif
+static int process_auth __P((struct authparam *, struct dhcp6 *dh6, ssize_t,
+ struct dhcp6_optinfo *));
+static int set_auth __P((struct dhcp6_event *, struct dhcp6_optinfo *));
+
+struct dhcp6_timer *client6_timo __P((void *));
+int client6_start __P((struct dhcp6_if *));
+static void info_printf __P((const char *, ...));
+
+extern int client6_script __P((char *, int, struct dhcp6_optinfo *));
+
+#define MAX_ELAPSED_TIME 0xffff
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ int ch, pid;
+ char *progname;
+ FILE *pidfp;
+ struct dhcp6_if *ifp;
+
+#ifndef HAVE_ARC4RANDOM
+ srandom(time(NULL) & getpid());
+#endif
+
+ if ((progname = strrchr(*argv, '/')) == NULL)
+ progname = *argv;
+ else
+ progname++;
+
+ while ((ch = getopt(argc, argv, "c:dDfik:p:")) != -1) {
+ switch (ch) {
+ case 'c':
+ conffile = optarg;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'D':
+ debug = 2;
+ break;
+ case 'f':
+ foreground++;
+ break;
+ case 'i':
+ infreq_mode = 1;
+ break;
+ case 'k':
+ ctlkeyfile = optarg;
+ break;
+ case 'p':
+ pid_file = optarg;
+ break;
+ default:
+ usage();
+ exit(0);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ usage();
+ exit(0);
+ }
+
+ if (foreground == 0)
+ openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON);
+
+ setloglevel(debug);
+
+ client6_init();
+ while (argc-- > 0) {
+ if ((ifp = ifinit(argv[0])) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to initialize %s",
+ argv[0]);
+ exit(1);
+ }
+ argv++;
+ }
+
+ if (infreq_mode == 0 && (cfparse(conffile)) != 0) {
+ dprintf(LOG_ERR, FNAME, "failed to parse configuration file");
+ exit(1);
+ }
+
+ if (foreground == 0 && infreq_mode == 0) {
+ if (daemon(0, 0) < 0)
+ err(1, "daemon");
+ }
+
+ /* dump current PID */
+ pid = getpid();
+ if ((pidfp = fopen(pid_file, "w")) != NULL) {
+ fprintf(pidfp, "%d\n", pid);
+ fclose(pidfp);
+ }
+
+ client6_startall(0);
+ client6_mainloop();
+ exit(0);
+}
+
+static void
+usage()
+{
+
+ fprintf(stderr, "usage: dhcp6c [-c configfile] [-dDfi] "
+ "[-p pid-file] interface [interfaces...]\n");
+}
+
+/*------------------------------------------------------------*/
+
+void
+client6_init()
+{
+ struct addrinfo hints, *res;
+ static struct sockaddr_in6 sa6_allagent_storage;
+ int error, on = 1;
+
+ /* get our DUID */
+ if (get_duid(DUID_FILE, &client_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 initialize control message authentication");
+ /* run the server anyway */
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_PASSIVE;
+ error = getaddrinfo(NULL, DH6PORT_DOWNSTREAM, &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(inbound)");
+ exit(1);
+ }
+ if (setsockopt(insock, SOL_SOCKET, SO_REUSEPORT,
+ &on, sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(inbound, SO_REUSEPORT): %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(inbound): %s", strerror(errno));
+ exit(1);
+ }
+ freeaddrinfo(res);
+
+ hints.ai_flags = 0;
+ error = getaddrinfo(NULL, DH6PORT_UPSTREAM, &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(outbound): %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (setsockopt(outsock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(outsock, IPV6_MULTICAST_LOOP): %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (setsockopt(outsock, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(outbound, IPV6_V6ONLY): %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);
+
+ /*
+ * bind the well-known incoming port to the outgoing socket
+ * for interoperability with some servers.
+ */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_PASSIVE;
+ error = getaddrinfo(NULL, DH6PORT_DOWNSTREAM, &hints, &res);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ if (setsockopt(outsock, SOL_SOCKET, SO_REUSEPORT,
+ &on, sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(outbound, SO_REUSEPORT): %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (bind(outsock, res->ai_addr, res->ai_addrlen) < 0) {
+ dprintf(LOG_ERR, FNAME, "bind(outbound): %s",
+ strerror(errno));
+ exit(1);
+ }
+ freeaddrinfo(res);
+
+ /* open a routing socket to watch the routing table */
+ if ((rtsock = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) {
+ dprintf(LOG_ERR, FNAME, "open a routing socket: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ error = getaddrinfo(DH6ADDR_ALLAGENT, DH6PORT_UPSTREAM, &hints, &res);
+ if (error) {
+ dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
+ gai_strerror(error));
+ exit(1);
+ }
+ memcpy(&sa6_allagent_storage, res->ai_addr, res->ai_addrlen);
+ sa6_allagent = (const struct sockaddr_in6 *)&sa6_allagent_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);
+ }
+
+ if (signal(SIGHUP, client6_signal) == SIG_ERR) {
+ dprintf(LOG_WARNING, FNAME, "failed to set signal: %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (signal(SIGTERM, client6_signal) == SIG_ERR) {
+ dprintf(LOG_WARNING, FNAME, "failed to set signal: %s",
+ strerror(errno));
+ exit(1);
+ }
+}
+
+int
+client6_start(ifp)
+ struct dhcp6_if *ifp;
+{
+ struct dhcp6_event *ev;
+
+ /* make sure that the interface does not have a timer */
+ if (ifp->timer != NULL) {
+ dprintf(LOG_DEBUG, FNAME,
+ "removed existing timer on %s", ifp->ifname);
+ dhcp6_remove_timer(&ifp->timer);
+ }
+
+ /* create an event for the initial delay */
+ if ((ev = dhcp6_create_event(ifp, DHCP6S_INIT)) == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to create an event");
+ return (-1);
+ }
+ TAILQ_INSERT_TAIL(&ifp->event_list, ev, link);
+
+ if ((ev->authparam = new_authparam(ifp->authproto,
+ ifp->authalgorithm, ifp->authrdm)) == NULL) {
+ dprintf(LOG_WARNING, FNAME, "failed to allocate "
+ "authentication parameters");
+ dhcp6_remove_event(ev);
+ return (-1);
+ }
+
+ if ((ev->timer = dhcp6_add_timer(client6_timo, ev)) == NULL) {
+ dprintf(LOG_NOTICE, FNAME, "failed to add a timer for %s",
+ ifp->ifname);
+ dhcp6_remove_event(ev);
+ return (-1);
+ }
+ dhcp6_reset_timer(ev);
+
+ return (0);
+}
+
+static void
+client6_startall(isrestart)
+ int isrestart;
+{
+ struct dhcp6_if *ifp;
+
+ for (ifp = dhcp6_if; ifp; ifp = ifp->next) {
+ if (isrestart &&ifreset(ifp)) {
+ dprintf(LOG_NOTICE, FNAME, "failed to reset %s",
+ ifp->ifname);
+ continue; /* XXX: try to recover? */
+ }
+ if (client6_start(ifp))
+ exit(1); /* initialization failure. we give up. */
+ }
+}
+
+static void
+free_resources(freeifp)
+ struct dhcp6_if *freeifp;
+{
+ struct dhcp6_if *ifp;
+
+ for (ifp = dhcp6_if; ifp; ifp = ifp->next) {
+ struct dhcp6_event *ev, *ev_next;
+
+ if (freeifp != NULL && freeifp != ifp)
+ continue;
+
+ /* release all IAs as well as send RELEASE message(s) */
+ release_all_ia(ifp);
+
+ /*
+ * Cancel all outstanding events for each interface except
+ * ones being released.
+ */
+ for (ev = TAILQ_FIRST(&ifp->event_list); ev; ev = ev_next) {
+ ev_next = TAILQ_NEXT(ev, link);
+
+ if (ev->state == DHCP6S_RELEASE)
+ continue; /* keep it for now */
+
+ dhcp6_remove_event(ev);
+ }
+ }
+}
+
+static void
+check_exit()
+{
+ struct dhcp6_if *ifp;
+
+ if (!exit_ok)
+ return;
+
+ for (ifp = dhcp6_if; ifp; ifp = ifp->next) {
+ /*
+ * Check if we have an outstanding event. If we do, we cannot
+ * exit for now.
+ */
+ if (!TAILQ_EMPTY(&ifp->event_list))
+ return;
+ }
+
+ /* We have no existing event. Do exit. */
+ dprintf(LOG_INFO, FNAME, "exiting");
+
+ exit(0);
+}
+
+static void
+process_signals()
+{
+ if ((sig_flags & SIGF_TERM)) {
+ exit_ok = 1;
+ free_resources(NULL);
+ unlink(pid_file);
+ check_exit();
+ }
+ if ((sig_flags & SIGF_HUP)) {
+ dprintf(LOG_INFO, FNAME, "restarting");
+ free_resources(NULL);
+ client6_startall(1);
+ }
+
+ sig_flags = 0;
+}
+
+static void
+client6_mainloop()
+{
+ struct timeval *w;
+ int ret, maxsock;
+ fd_set r;
+
+ while(1) {
+ if (sig_flags)
+ process_signals();
+
+ 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:
+ if (errno != EINTR) {
+ dprintf(LOG_ERR, FNAME, "select: %s",
+ strerror(errno));
+ exit(1);
+ }
+ continue;
+ case 0: /* timeout */
+ break; /* dhcp6_check_timer() will treat the case */
+ default:
+ break;
+ }
+ if (FD_ISSET(insock, &r))
+ client6_recv();
+ if (ctlsock >= 0) {
+ if (FD_ISSET(ctlsock, &r)) {
+ (void)dhcp6_ctl_acceptcommand(ctlsock,
+ client6_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_ifname(bpp, lenp, ifbuf, ifbuflen)
+ char **bpp;
+ int *lenp;
+ char *ifbuf;
+ int ifbuflen;
+{
+ char *bp = *bpp;
+ int len = *lenp, ifnamelen;
+ u_int32_t i32;
+
+ if (get_val32(bpp, lenp, &i32))
+ return (-1);
+ ifnamelen = (int)i32;
+
+ if (*lenp < ifnamelen || ifnamelen > ifbuflen)
+ return (-1);
+
+ memset(ifbuf, 0, sizeof(ifbuf));
+ memcpy(ifbuf, *bpp, ifnamelen);
+ if (ifbuf[ifbuflen - 1] != '\0')
+ return (-1); /* not null terminated */
+
+ *bpp = bp + sizeof(i32) + ifnamelen;
+ *lenp = len - (sizeof(i32) + ifnamelen);
+
+ return (0);
+}
+
+static int
+client6_do_ctlcommand(buf, len)
+ char *buf;
+ ssize_t len;
+{
+ struct dhcp6ctl *ctlhead;
+ u_int16_t command, version;
+ u_int32_t p32, ts, ts0;
+ int commandlen;
+ char *bp;
+ char ifname[IFNAMSIZ];
+ 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);
+ }
+ client6_reload();
+ break;
+ case DHCP6CTL_COMMAND_START:
+ if (get_val32(&bp, &commandlen, &p32))
+ return (DHCP6CTL_R_FAILURE);
+ switch (p32) {
+ case DHCP6CTL_INTERFACE:
+ if (get_ifname(&bp, &commandlen, ifname,
+ sizeof(ifname))) {
+ return (DHCP6CTL_R_FAILURE);
+ }
+ if (client6_ifctl(ifname, DHCP6CTL_COMMAND_START))
+ return (DHCP6CTL_R_FAILURE);
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME,
+ "unknown start target: %ul", p32);
+ return (DHCP6CTL_R_FAILURE);
+ }
+ break;
+ case DHCP6CTL_COMMAND_STOP:
+ if (commandlen == 0) {
+ exit_ok = 1;
+ free_resources(NULL);
+ unlink(pid_file);
+ check_exit();
+ } else {
+ if (get_val32(&bp, &commandlen, &p32))
+ return (DHCP6CTL_R_FAILURE);
+
+ switch (p32) {
+ case DHCP6CTL_INTERFACE:
+ if (get_ifname(&bp, &commandlen, ifname,
+ sizeof(ifname))) {
+ return (DHCP6CTL_R_FAILURE);
+ }
+ if (client6_ifctl(ifname,
+ DHCP6CTL_COMMAND_STOP)) {
+ return (DHCP6CTL_R_FAILURE);
+ }
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME,
+ "unknown start target: %ul", p32);
+ return (DHCP6CTL_R_FAILURE);
+ }
+ }
+ 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
+client6_reload()
+{
+ /* reload the configuration file */
+ if (cfparse(conffile) != 0) {
+ dprintf(LOG_WARNING, FNAME,
+ "failed to reload configuration file");
+ return;
+ }
+
+ dprintf(LOG_NOTICE, FNAME, "client reloaded");
+
+ return;
+}
+
+static int
+client6_ifctl(ifname, command)
+ char *ifname;
+ u_int16_t command;
+{
+ struct dhcp6_if *ifp;
+
+ if ((ifp = find_ifconfbyname(ifname)) == NULL) {
+ dprintf(LOG_INFO, FNAME,
+ "failed to find interface configuration for %s",
+ ifname);
+ return (-1);
+ }
+
+ switch(command) {
+ case DHCP6CTL_COMMAND_START:
+ free_resources(ifp);
+ if (client6_start(ifp)) {
+ dprintf(LOG_NOTICE, FNAME, "failed to restart %s",
+ ifname);
+ return (-1);
+ }
+ break;
+ case DHCP6CTL_COMMAND_STOP:
+ free_resources(ifp);
+ if (ifp->timer != NULL) {
+ dprintf(LOG_DEBUG, FNAME,
+ "removed existing timer on %s", ifp->ifname);
+ dhcp6_remove_timer(&ifp->timer);
+ }
+ break;
+ default: /* impossible case, should be a bug */
+ dprintf(LOG_ERR, FNAME, "unknown command: %d", (int)command);
+ break;
+ }
+
+ return (0);
+}
+
+#ifdef USE_DH6OPT_REFRESHTIME
+static struct dhcp6_timer *
+client6_expire_refreshtime(arg)
+ void *arg;
+{
+ struct dhcp6_if *ifp = arg;
+
+ dprintf(LOG_DEBUG, FNAME,
+ "information refresh time on %s expired", ifp->ifname);
+
+ dhcp6_remove_timer(&ifp->timer);
+ client6_start(ifp);
+
+ return (NULL);
+}
+#endif
+
+struct dhcp6_timer *
+client6_timo(arg)
+ void *arg;
+{
+ struct dhcp6_event *ev = (struct dhcp6_event *)arg;
+ struct dhcp6_if *ifp;
+ int state = ev->state;
+
+ ifp = ev->ifp;
+ ev->timeouts++;
+
+ /*
+ * Unless MRC is zero, the message exchange fails once the client has
+ * transmitted the message MRC times.
+ * [RFC3315 14.]
+ */
+ if (ev->max_retrans_cnt && ev->timeouts >= ev->max_retrans_cnt) {
+ dprintf(LOG_INFO, FNAME, "no responses were received");
+ dhcp6_remove_event(ev);
+
+ if (state == DHCP6S_RELEASE)
+ check_exit();
+
+ return (NULL);
+ }
+
+ switch(ev->state) {
+ case DHCP6S_INIT:
+ ev->timeouts = 0; /* indicate to generate a new XID. */
+ if ((ifp->send_flags & DHCIFF_INFO_ONLY) || infreq_mode)
+ ev->state = DHCP6S_INFOREQ;
+ else {
+ ev->state = DHCP6S_SOLICIT;
+ if (construct_confdata(ifp, ev)) {
+ dprintf(LOG_ERR, FNAME, "can't send solicit");
+ exit(1); /* XXX */
+ }
+ }
+ dhcp6_set_timeoparam(ev); /* XXX */
+ /* fall through */
+ case DHCP6S_REQUEST:
+ case DHCP6S_RELEASE:
+ case DHCP6S_INFOREQ:
+ client6_send(ev);
+ break;
+ case DHCP6S_RENEW:
+ case DHCP6S_REBIND:
+ if (!TAILQ_EMPTY(&ev->data_list))
+ client6_send(ev);
+ else {
+ dprintf(LOG_INFO, FNAME,
+ "all information to be updated was canceled");
+ dhcp6_remove_event(ev);
+ return (NULL);
+ }
+ break;
+ case DHCP6S_SOLICIT:
+ if (ev->servers) {
+ /*
+ * Send a Request to the best server.
+ * Note that when we set Rapid-commit in Solicit,
+ * but a direct Reply has been delayed (very much),
+ * the transition to DHCP6S_REQUEST (and the change of
+ * transaction ID) will invalidate the reply even if it
+ * ever arrives.
+ */
+ ev->current_server = select_server(ev);
+ if (ev->current_server == NULL) {
+ /* this should not happen! */
+ dprintf(LOG_NOTICE, FNAME,
+ "can't find a server");
+ exit(1); /* XXX */
+ }
+ if (duidcpy(&ev->serverid,
+ &ev->current_server->optinfo.serverID)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "can't copy server ID");
+ return (NULL); /* XXX: better recovery? */
+ }
+ ev->timeouts = 0;
+ ev->state = DHCP6S_REQUEST;
+ dhcp6_set_timeoparam(ev);
+
+ if (ev->authparam != NULL)
+ free(ev->authparam);
+ ev->authparam = ev->current_server->authparam;
+ ev->current_server->authparam = NULL;
+
+ if (construct_reqdata(ifp,
+ &ev->current_server->optinfo, ev)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to construct request data");
+ break;
+ }
+ }
+ client6_send(ev);
+ break;
+ }
+
+ dhcp6_reset_timer(ev);
+
+ return (ev->timer);
+}
+
+static int
+construct_confdata(ifp, ev)
+ struct dhcp6_if *ifp;
+ struct dhcp6_event *ev;
+{
+ struct ia_conf *iac;
+ struct dhcp6_eventdata *evd = NULL;
+ struct dhcp6_list *ial = NULL, pl;
+ struct dhcp6_ia iaparam;
+
+ TAILQ_INIT(&pl); /* for safety */
+
+ for (iac = TAILQ_FIRST(&ifp->iaconf_list); iac;
+ iac = TAILQ_NEXT(iac, link)) {
+ /* ignore IA config currently used */
+ if (!TAILQ_EMPTY(&iac->iadata))
+ continue;
+
+ evd = NULL;
+ if ((evd = malloc(sizeof(*evd))) == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to create a new event data");
+ goto fail;
+ }
+ memset(evd, 0, sizeof(evd));
+
+ memset(&iaparam, 0, sizeof(iaparam));
+ iaparam.iaid = iac->iaid;
+ switch (iac->type) {
+ case IATYPE_PD:
+ ial = NULL;
+ if ((ial = malloc(sizeof(*ial))) == NULL)
+ goto fail;
+ TAILQ_INIT(ial);
+
+ TAILQ_INIT(&pl);
+ dhcp6_copy_list(&pl,
+ &((struct iapd_conf *)iac)->iapd_prefix_list);
+ if (dhcp6_add_listval(ial, DHCP6_LISTVAL_IAPD,
+ &iaparam, &pl) == NULL) {
+ goto fail;
+ }
+ dhcp6_clear_list(&pl);
+
+ evd->type = DHCP6_EVDATA_IAPD;
+ evd->data = ial;
+ evd->event = ev;
+ evd->destructor = destruct_iadata;
+ TAILQ_INSERT_TAIL(&ev->data_list, evd, link);
+ break;
+ case IATYPE_NA:
+ ial = NULL;
+ if ((ial = malloc(sizeof(*ial))) == NULL)
+ goto fail;
+ TAILQ_INIT(ial);
+
+ TAILQ_INIT(&pl);
+ dhcp6_copy_list(&pl,
+ &((struct iana_conf *)iac)->iana_address_list);
+ if (dhcp6_add_listval(ial, DHCP6_LISTVAL_IANA,
+ &iaparam, &pl) == NULL) {
+ goto fail;
+ }
+ dhcp6_clear_list(&pl);
+
+ evd->type = DHCP6_EVDATA_IANA;
+ evd->data = ial;
+ evd->event = ev;
+ evd->destructor = destruct_iadata;
+ TAILQ_INSERT_TAIL(&ev->data_list, evd, link);
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "internal error");
+ exit(1);
+ }
+ }
+
+ return (0);
+
+ fail:
+ if (evd)
+ free(evd);
+ if (ial)
+ free(ial);
+ dhcp6_remove_event(ev); /* XXX */
+
+ return (-1);
+}
+
+static int
+construct_reqdata(ifp, optinfo, ev)
+ struct dhcp6_if *ifp;
+ struct dhcp6_optinfo *optinfo;
+ struct dhcp6_event *ev;
+{
+ struct ia_conf *iac;
+ struct dhcp6_eventdata *evd = NULL;
+ struct dhcp6_list *ial = NULL;
+ struct dhcp6_ia iaparam;
+
+ /* discard previous event data */
+ dhcp6_remove_evdata(ev);
+
+ if (optinfo == NULL)
+ return (0);
+
+ for (iac = TAILQ_FIRST(&ifp->iaconf_list); iac;
+ iac = TAILQ_NEXT(iac, link)) {
+ struct dhcp6_listval *v;
+
+ /* ignore IA config currently used */
+ if (!TAILQ_EMPTY(&iac->iadata))
+ continue;
+
+ memset(&iaparam, 0, sizeof(iaparam));
+ iaparam.iaid = iac->iaid;
+
+ ial = NULL;
+ evd = NULL;
+
+ switch (iac->type) {
+ case IATYPE_PD:
+ if ((v = dhcp6_find_listval(&optinfo->iapd_list,
+ DHCP6_LISTVAL_IAPD, &iaparam, 0)) == NULL)
+ continue;
+
+ if ((ial = malloc(sizeof(*ial))) == NULL)
+ goto fail;
+
+ TAILQ_INIT(ial);
+ if (dhcp6_add_listval(ial, DHCP6_LISTVAL_IAPD,
+ &iaparam, &v->sublist) == NULL) {
+ goto fail;
+ }
+
+ if ((evd = malloc(sizeof(*evd))) == NULL)
+ goto fail;
+ memset(evd, 0, sizeof(*evd));
+ evd->type = DHCP6_EVDATA_IAPD;
+ evd->data = ial;
+ evd->event = ev;
+ evd->destructor = destruct_iadata;
+ TAILQ_INSERT_TAIL(&ev->data_list, evd, link);
+ break;
+ case IATYPE_NA:
+ if ((v = dhcp6_find_listval(&optinfo->iana_list,
+ DHCP6_LISTVAL_IANA, &iaparam, 0)) == NULL)
+ continue;
+
+ if ((ial = malloc(sizeof(*ial))) == NULL)
+ goto fail;
+
+ TAILQ_INIT(ial);
+ if (dhcp6_add_listval(ial, DHCP6_LISTVAL_IANA,
+ &iaparam, &v->sublist) == NULL) {
+ goto fail;
+ }
+
+ if ((evd = malloc(sizeof(*evd))) == NULL)
+ goto fail;
+ memset(evd, 0, sizeof(*evd));
+ evd->type = DHCP6_EVDATA_IANA;
+ evd->data = ial;
+ evd->event = ev;
+ evd->destructor = destruct_iadata;
+ TAILQ_INSERT_TAIL(&ev->data_list, evd, link);
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "internal error");
+ exit(1);
+ }
+ }
+
+ return (0);
+
+ fail:
+ if (evd)
+ free(evd);
+ if (ial)
+ free(ial);
+ dhcp6_remove_event(ev); /* XXX */
+
+ return (-1);
+}
+
+static void
+destruct_iadata(evd)
+ struct dhcp6_eventdata *evd;
+{
+ struct dhcp6_list *ial;
+
+ if (evd->type != DHCP6_EVDATA_IAPD && evd->type != DHCP6_EVDATA_IANA) {
+ dprintf(LOG_ERR, FNAME, "assumption failure %d", evd->type);
+ exit(1);
+ }
+
+ ial = (struct dhcp6_list *)evd->data;
+ dhcp6_clear_list(ial);
+ free(ial);
+}
+
+static struct dhcp6_serverinfo *
+select_server(ev)
+ struct dhcp6_event *ev;
+{
+ struct dhcp6_serverinfo *s;
+
+ /*
+ * pick the best server according to RFC3315 Section 17.1.3.
+ * XXX: we currently just choose the one that is active and has the
+ * highest preference.
+ */
+ for (s = ev->servers; s; s = s->next) {
+ if (s->active) {
+ dprintf(LOG_DEBUG, FNAME, "picked a server (ID: %s)",
+ duidstr(&s->optinfo.serverID));
+ return (s);
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+client6_signal(sig)
+ int sig;
+{
+
+ dprintf(LOG_INFO, FNAME, "received a signal (%d)", sig);
+
+ switch (sig) {
+ case SIGTERM:
+ sig_flags |= SIGF_TERM;
+ break;
+ case SIGHUP:
+ sig_flags |= SIGF_HUP;
+ break;
+ }
+}
+
+void
+client6_send(ev)
+ struct dhcp6_event *ev;
+{
+ struct dhcp6_if *ifp;
+ char buf[BUFSIZ];
+ struct sockaddr_in6 dst;
+ struct dhcp6 *dh6;
+ struct dhcp6_optinfo optinfo;
+ ssize_t optlen, len;
+ struct dhcp6_eventdata *evd;
+
+ ifp = ev->ifp;
+
+ dh6 = (struct dhcp6 *)buf;
+ memset(dh6, 0, sizeof(*dh6));
+
+ switch(ev->state) {
+ case DHCP6S_SOLICIT:
+ dh6->dh6_msgtype = DH6_SOLICIT;
+ break;
+ case DHCP6S_REQUEST:
+ dh6->dh6_msgtype = DH6_REQUEST;
+ break;
+ case DHCP6S_RENEW:
+ dh6->dh6_msgtype = DH6_RENEW;
+ break;
+ case DHCP6S_REBIND:
+ dh6->dh6_msgtype = DH6_REBIND;
+ break;
+ case DHCP6S_RELEASE:
+ dh6->dh6_msgtype = DH6_RELEASE;
+ break;
+ case DHCP6S_INFOREQ:
+ dh6->dh6_msgtype = DH6_INFORM_REQ;
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unexpected state");
+ exit(1); /* XXX */
+ }
+
+ if (ev->timeouts == 0) {
+ /*
+ * A client SHOULD generate a random number that cannot easily
+ * be guessed or predicted to use as the transaction ID for
+ * each new message it sends.
+ *
+ * A client MUST leave the transaction-ID unchanged in
+ * retransmissions of a message. [RFC3315 15.1]
+ */
+#ifdef HAVE_ARC4RANDOM
+ ev->xid = arc4random() & DH6_XIDMASK;
+#else
+ ev->xid = random() & DH6_XIDMASK;
+#endif
+ dprintf(LOG_DEBUG, FNAME, "a new XID (%x) is generated",
+ ev->xid);
+ }
+ dh6->dh6_xid &= ~ntohl(DH6_XIDMASK);
+ dh6->dh6_xid |= htonl(ev->xid);
+ len = sizeof(*dh6);
+
+ /*
+ * construct options
+ */
+ dhcp6_init_options(&optinfo);
+
+ /* server ID */
+ switch (ev->state) {
+ case DHCP6S_REQUEST:
+ case DHCP6S_RENEW:
+ case DHCP6S_RELEASE:
+ if (duidcpy(&optinfo.serverID, &ev->serverid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy server ID");
+ goto end;
+ }
+ break;
+ }
+
+ /* client ID */
+ if (duidcpy(&optinfo.clientID, &client_duid)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy client ID");
+ goto end;
+ }
+
+ /* rapid commit (in Solicit only) */
+ if (ev->state == DHCP6S_SOLICIT &&
+ (ifp->send_flags & DHCIFF_RAPID_COMMIT)) {
+ optinfo.rapidcommit = 1;
+ }
+
+ /* elapsed time */
+ if (ev->timeouts == 0) {
+ gettimeofday(&ev->tv_start, NULL);
+ optinfo.elapsed_time = 0;
+ } else {
+ struct timeval now, tv_diff;
+ long et;
+
+ gettimeofday(&now, NULL);
+ tv_sub(&now, &ev->tv_start, &tv_diff);
+
+ /*
+ * The client uses the value 0xffff to represent any elapsed
+ * time values greater than the largest time value that can be
+ * represented in the Elapsed Time option.
+ * [RFC3315 22.9.]
+ */
+ if (tv_diff.tv_sec >= (MAX_ELAPSED_TIME / 100) + 1) {
+ /*
+ * Perhaps we are nervous too much, but without this
+ * additional check, we would see an overflow in 248
+ * days (of no responses).
+ */
+ et = MAX_ELAPSED_TIME;
+ } else {
+ et = tv_diff.tv_sec * 100 + tv_diff.tv_usec / 10000;
+ if (et >= MAX_ELAPSED_TIME)
+ et = MAX_ELAPSED_TIME;
+ }
+ optinfo.elapsed_time = (int32_t)et;
+ }
+
+ /* option request options */
+ if (ev->state != DHCP6S_RELEASE &&
+ dhcp6_copy_list(&optinfo.reqopt_list, &ifp->reqopt_list)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy requested options");
+ goto end;
+ }
+
+ /* configuration information specified as event data */
+ for (evd = TAILQ_FIRST(&ev->data_list); evd;
+ evd = TAILQ_NEXT(evd, link)) {
+ switch(evd->type) {
+ case DHCP6_EVDATA_IAPD:
+ if (dhcp6_copy_list(&optinfo.iapd_list,
+ (struct dhcp6_list *)evd->data)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to add an IAPD");
+ goto end;
+ }
+ break;
+ case DHCP6_EVDATA_IANA:
+ if (dhcp6_copy_list(&optinfo.iana_list,
+ (struct dhcp6_list *)evd->data)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to add an IAPD");
+ goto end;
+ }
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unexpected event data (%d)",
+ evd->type);
+ exit(1);
+ }
+ }
+
+ /* authentication information */
+ if (set_auth(ev, &optinfo)) {
+ dprintf(LOG_INFO, FNAME,
+ "failed to set authentication option");
+ goto end;
+ }
+
+ /* set options in the message */
+ if ((optlen = dhcp6_set_options(dh6->dh6_msgtype,
+ (struct dhcp6opt *)(dh6 + 1),
+ (struct dhcp6opt *)(buf + sizeof(buf)), &optinfo)) < 0) {
+ dprintf(LOG_INFO, FNAME, "failed to construct options");
+ goto end;
+ }
+ len += optlen;
+
+ /* calculate MAC if necessary, and put it to the message */
+ if (ev->authparam != NULL) {
+ switch (ev->authparam->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ if (ev->authparam->key == NULL)
+ break;
+
+ if (dhcp6_calc_mac((char *)dh6, len,
+ optinfo.authproto, optinfo.authalgorithm,
+ optinfo.delayedauth_offset + sizeof(*dh6),
+ ev->authparam->key)) {
+ dprintf(LOG_WARNING, FNAME,
+ "failed to calculate MAC");
+ goto end;
+ }
+ break;
+ default:
+ break; /* do nothing */
+ }
+ }
+
+ /*
+ * Unless otherwise specified in this document or in a document that
+ * describes how IPv6 is carried over a specific type of link (for link
+ * types that do not support multicast), a client sends DHCP messages
+ * to the All_DHCP_Relay_Agents_and_Servers.
+ * [RFC3315 Section 13.]
+ */
+ dst = *sa6_allagent;
+ dst.sin6_scope_id = ifp->linkid;
+
+ if (sendto(outsock, buf, len, 0, (struct sockaddr *)&dst,
+ sysdep_sa_len((struct sockaddr *)&dst)) == -1) {
+ dprintf(LOG_ERR, FNAME,
+ "transmit failed: %s", strerror(errno));
+ goto end;
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "send %s to %s",
+ dhcp6msgstr(dh6->dh6_msgtype), addr2str((struct sockaddr *)&dst));
+
+ end:
+ dhcp6_clear_options(&optinfo);
+ return;
+}
+
+/* result will be a - b */
+static void
+tv_sub(a, b, result)
+ struct timeval *a, *b, *result;
+{
+ if (a->tv_sec < b->tv_sec ||
+ (a->tv_sec == b->tv_sec && a->tv_usec < b->tv_usec)) {
+ result->tv_sec = 0;
+ result->tv_usec = 0;
+
+ return;
+ }
+
+ result->tv_sec = a->tv_sec - b->tv_sec;
+ if (a->tv_usec < b->tv_usec) {
+ result->tv_usec = a->tv_usec + 1000000 - b->tv_usec;
+ result->tv_sec -= 1;
+ } else
+ result->tv_usec = a->tv_usec - b->tv_usec;
+
+ return;
+}
+
+static void
+client6_recv()
+{
+ char rbuf[BUFSIZ], cmsgbuf[BUFSIZ];
+ struct msghdr mhdr;
+ struct iovec iov;
+ struct sockaddr_storage from;
+ struct dhcp6_if *ifp;
+ struct dhcp6opt *p, *ep;
+ struct dhcp6_optinfo optinfo;
+ ssize_t len;
+ struct dhcp6 *dh6;
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi = NULL;
+
+ memset(&iov, 0, sizeof(iov));
+ memset(&mhdr, 0, sizeof(mhdr));
+
+ iov.iov_base = (caddr_t)rbuf;
+ iov.iov_len = sizeof(rbuf);
+ mhdr.msg_name = (caddr_t)&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;
+ }
+
+ /* detect receiving interface */
+ 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;
+ }
+
+ if ((ifp = find_ifconfbyid((unsigned int)pi->ipi6_ifindex)) == NULL) {
+ dprintf(LOG_INFO, FNAME, "unexpected interface (%d)",
+ (unsigned int)pi->ipi6_ifindex);
+ return;
+ }
+
+ if (len < sizeof(*dh6)) {
+ dprintf(LOG_INFO, FNAME, "short packet (%d bytes)", len);
+ return;
+ }
+
+ dh6 = (struct dhcp6 *)rbuf;
+
+ dprintf(LOG_DEBUG, FNAME, "receive %s from %s on %s",
+ dhcp6msgstr(dh6->dh6_msgtype),
+ addr2str((struct sockaddr *)&from), ifp->ifname);
+
+ /* get options */
+ dhcp6_init_options(&optinfo);
+ p = (struct dhcp6opt *)(dh6 + 1);
+ ep = (struct dhcp6opt *)((char *)dh6 + len);
+ if (dhcp6_get_options(p, ep, &optinfo) < 0) {
+ dprintf(LOG_INFO, FNAME, "failed to parse options");
+ return;
+ }
+
+ switch(dh6->dh6_msgtype) {
+ case DH6_ADVERTISE:
+ (void)client6_recvadvert(ifp, dh6, len, &optinfo);
+ break;
+ case DH6_REPLY:
+ (void)client6_recvreply(ifp, dh6, len, &optinfo);
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME, "received an unexpected message (%s) "
+ "from %s", dhcp6msgstr(dh6->dh6_msgtype),
+ addr2str((struct sockaddr *)&from));
+ break;
+ }
+
+ dhcp6_clear_options(&optinfo);
+ return;
+}
+
+static int
+client6_recvadvert(ifp, dh6, len, optinfo)
+ struct dhcp6_if *ifp;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct dhcp6_serverinfo *newserver, **sp;
+ struct dhcp6_event *ev;
+ struct dhcp6_eventdata *evd;
+ struct authparam *authparam = NULL, authparam0;
+
+ /* find the corresponding event based on the received xid */
+ ev = find_event_withid(ifp, ntohl(dh6->dh6_xid) & DH6_XIDMASK);
+ if (ev == NULL) {
+ dprintf(LOG_INFO, FNAME, "XID mismatch");
+ return (-1);
+ }
+
+ /* packet validation based on Section 15.3 of RFC3315. */
+ if (optinfo->serverID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ } else {
+ dprintf(LOG_DEBUG, FNAME, "server ID: %s, pref=%d",
+ duidstr(&optinfo->serverID),
+ optinfo->pref);
+ }
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no client ID option");
+ return (-1);
+ }
+ if (duidcmp(&optinfo->clientID, &client_duid)) {
+ dprintf(LOG_INFO, FNAME, "client DUID mismatch");
+ return (-1);
+ }
+
+ /* validate authentication */
+ authparam0 = *ev->authparam;
+ if (process_auth(&authparam0, dh6, len, optinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication");
+ return (-1);
+ }
+
+ /*
+ * The requesting router MUST ignore any Advertise message that
+ * includes a Status Code option containing the value NoPrefixAvail
+ * [RFC3633 Section 11.1].
+ * Likewise, the client MUST ignore any Advertise message that includes
+ * a Status Code option containing the value NoAddrsAvail.
+ * [RFC3315 Section 17.1.3].
+ * We only apply this when we are going to request an address or
+ * a prefix.
+ */
+ for (evd = TAILQ_FIRST(&ev->data_list); evd;
+ evd = TAILQ_NEXT(evd, link)) {
+ u_int16_t stcode;
+ char *stcodestr;
+
+ switch (evd->type) {
+ case DHCP6_EVDATA_IAPD:
+ stcode = DH6OPT_STCODE_NOPREFIXAVAIL;
+ stcodestr = "NoPrefixAvail";
+ break;
+ case DHCP6_EVDATA_IANA:
+ stcode = DH6OPT_STCODE_NOADDRSAVAIL;
+ stcodestr = "NoAddrsAvail";
+ break;
+ default:
+ continue;
+ }
+ if (dhcp6_find_listval(&optinfo->stcode_list,
+ DHCP6_LISTVAL_STCODE, &stcode, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "advertise contains %s status", stcodestr);
+ return (-1);
+ }
+ }
+
+ if (ev->state != DHCP6S_SOLICIT ||
+ (ifp->send_flags & DHCIFF_RAPID_COMMIT) || infreq_mode) {
+ /*
+ * We expected a reply message, but do actually receive an
+ * Advertise message. The server should be configured not to
+ * allow the Rapid Commit option.
+ * We process the message as if we expected the Advertise.
+ * [RFC3315 Section 17.1.4]
+ */
+ dprintf(LOG_INFO, FNAME, "unexpected advertise");
+ /* proceed anyway */
+ }
+
+ /* ignore the server if it is known */
+ if (find_server(ev, &optinfo->serverID)) {
+ dprintf(LOG_INFO, FNAME, "duplicated server (ID: %s)",
+ duidstr(&optinfo->serverID));
+ return (-1);
+ }
+
+ /* keep the server */
+ if ((newserver = malloc(sizeof(*newserver))) == NULL) {
+ dprintf(LOG_WARNING, FNAME,
+ "memory allocation failed for server");
+ return (-1);
+ }
+ memset(newserver, 0, sizeof(*newserver));
+
+ /* remember authentication parameters */
+ newserver->authparam = ev->authparam;
+ newserver->authparam->flags = authparam0.flags;
+ newserver->authparam->prevrd = authparam0.prevrd;
+ newserver->authparam->key = authparam0.key;
+
+ /* allocate new authentication parameter for the soliciting event */
+ if ((authparam = new_authparam(ev->authparam->authproto,
+ ev->authparam->authalgorithm, ev->authparam->authrdm)) == NULL) {
+ dprintf(LOG_WARNING, FNAME, "memory allocation failed "
+ "for authentication parameters");
+ free(newserver);
+ return (-1);
+ }
+ ev->authparam = authparam;
+
+ /* copy options */
+ dhcp6_init_options(&newserver->optinfo);
+ if (dhcp6_copy_options(&newserver->optinfo, optinfo)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy options");
+ if (newserver->authparam != NULL)
+ free(newserver->authparam);
+ free(newserver);
+ return (-1);
+ }
+ if (optinfo->pref != DH6OPT_PREF_UNDEF)
+ newserver->pref = optinfo->pref;
+ newserver->active = 1;
+ for (sp = &ev->servers; *sp; sp = &(*sp)->next) {
+ if ((*sp)->pref != DH6OPT_PREF_MAX &&
+ (*sp)->pref < newserver->pref) {
+ break;
+ }
+ }
+ newserver->next = *sp;
+ *sp = newserver;
+
+ if (newserver->pref == DH6OPT_PREF_MAX) {
+ /*
+ * If the client receives an Advertise message that includes a
+ * Preference option with a preference value of 255, the client
+ * immediately begins a client-initiated message exchange.
+ * [RFC3315 Section 17.1.2]
+ */
+ ev->current_server = newserver;
+ if (duidcpy(&ev->serverid,
+ &ev->current_server->optinfo.serverID)) {
+ dprintf(LOG_NOTICE, FNAME, "can't copy server ID");
+ return (-1); /* XXX: better recovery? */
+ }
+ if (construct_reqdata(ifp, &ev->current_server->optinfo, ev)) {
+ dprintf(LOG_NOTICE, FNAME,
+ "failed to construct request data");
+ return (-1); /* XXX */
+ }
+
+ ev->timeouts = 0;
+ ev->state = DHCP6S_REQUEST;
+
+ free(ev->authparam);
+ ev->authparam = newserver->authparam;
+ newserver->authparam = NULL;
+
+ client6_send(ev);
+
+ dhcp6_set_timeoparam(ev);
+ dhcp6_reset_timer(ev);
+ } else if (ev->servers->next == NULL) {
+ struct timeval *rest, elapsed, tv_rt, tv_irt, timo;
+
+ /*
+ * If this is the first advertise, adjust the timer so that
+ * the client can collect other servers until IRT elapses.
+ * XXX: we did not want to do such "low level" timer
+ * calculation here.
+ */
+ rest = dhcp6_timer_rest(ev->timer);
+ tv_rt.tv_sec = (ev->retrans * 1000) / 1000000;
+ tv_rt.tv_usec = (ev->retrans * 1000) % 1000000;
+ tv_irt.tv_sec = (ev->init_retrans * 1000) / 1000000;
+ tv_irt.tv_usec = (ev->init_retrans * 1000) % 1000000;
+ timeval_sub(&tv_rt, rest, &elapsed);
+ if (TIMEVAL_LEQ(elapsed, tv_irt))
+ timeval_sub(&tv_irt, &elapsed, &timo);
+ else
+ timo.tv_sec = timo.tv_usec = 0;
+
+ dprintf(LOG_DEBUG, FNAME, "reset timer for %s to %d.%06d",
+ ifp->ifname, (int)timo.tv_sec, (int)timo.tv_usec);
+
+ dhcp6_set_timer(&timo, ev->timer);
+ }
+
+ return (0);
+}
+
+static struct dhcp6_serverinfo *
+find_server(ev, duid)
+ struct dhcp6_event *ev;
+ struct duid *duid;
+{
+ struct dhcp6_serverinfo *s;
+
+ for (s = ev->servers; s; s = s->next) {
+ if (duidcmp(&s->optinfo.serverID, duid) == 0)
+ return (s);
+ }
+
+ return (NULL);
+}
+
+static int
+client6_recvreply(ifp, dh6, len, optinfo)
+ struct dhcp6_if *ifp;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct dhcp6_listval *lv;
+ struct dhcp6_event *ev;
+ int state;
+
+ /* find the corresponding event based on the received xid */
+ ev = find_event_withid(ifp, ntohl(dh6->dh6_xid) & DH6_XIDMASK);
+ if (ev == NULL) {
+ dprintf(LOG_INFO, FNAME, "XID mismatch");
+ return (-1);
+ }
+
+ state = ev->state;
+ if (state != DHCP6S_INFOREQ &&
+ state != DHCP6S_REQUEST &&
+ state != DHCP6S_RENEW &&
+ state != DHCP6S_REBIND &&
+ state != DHCP6S_RELEASE &&
+ (state != DHCP6S_SOLICIT ||
+ !(ifp->send_flags & DHCIFF_RAPID_COMMIT))) {
+ dprintf(LOG_INFO, FNAME, "unexpected reply");
+ return (-1);
+ }
+
+ /* A Reply message must contain a Server ID option */
+ if (optinfo->serverID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no server ID option");
+ return (-1);
+ }
+
+ /*
+ * DUID in the Client ID option (which must be contained for our
+ * client implementation) must match ours.
+ */
+ if (optinfo->clientID.duid_len == 0) {
+ dprintf(LOG_INFO, FNAME, "no client ID option");
+ return (-1);
+ }
+ if (duidcmp(&optinfo->clientID, &client_duid)) {
+ dprintf(LOG_INFO, FNAME, "client DUID mismatch");
+ return (-1);
+ }
+
+ /* validate authentication */
+ if (process_auth(ev->authparam, dh6, len, optinfo)) {
+ dprintf(LOG_INFO, FNAME, "failed to process authentication");
+ return (-1);
+ }
+
+ /*
+ * If the client included a Rapid Commit option in the Solicit message,
+ * the client discards any Reply messages it receives that do not
+ * include a Rapid Commit option.
+ * (should we keep the server otherwise?)
+ * [RFC3315 Section 17.1.4]
+ */
+ if (state == DHCP6S_SOLICIT &&
+ (ifp->send_flags & DHCIFF_RAPID_COMMIT) &&
+ !optinfo->rapidcommit) {
+ dprintf(LOG_INFO, FNAME, "no rapid commit");
+ return (-1);
+ }
+
+ /*
+ * The client MAY choose to report any status code or message from the
+ * status code option in the Reply message.
+ * [RFC3315 Section 18.1.8]
+ */
+ for (lv = TAILQ_FIRST(&optinfo->stcode_list); lv;
+ lv = TAILQ_NEXT(lv, link)) {
+ dprintf(LOG_INFO, FNAME, "status code: %s",
+ dhcp6_stcodestr(lv->val_num16));
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->dns_list)) {
+ struct dhcp6_listval *d;
+ int i = 0;
+
+ for (d = TAILQ_FIRST(&optinfo->dns_list); d;
+ d = TAILQ_NEXT(d, link), i++) {
+ info_printf("nameserver[%d] %s",
+ i, in6addr2str(&d->val_addr6, 0));
+ }
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->dnsname_list)) {
+ struct dhcp6_listval *d;
+ int i = 0;
+
+ for (d = TAILQ_FIRST(&optinfo->dnsname_list); d;
+ d = TAILQ_NEXT(d, link), i++) {
+ info_printf("Domain search list[%d] %s",
+ i, d->val_vbuf.dv_buf);
+ }
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->ntp_list)) {
+ struct dhcp6_listval *d;
+ int i = 0;
+
+ for (d = TAILQ_FIRST(&optinfo->ntp_list); d;
+ d = TAILQ_NEXT(d, link), i++) {
+ info_printf("NTP server[%d] %s",
+ i, in6addr2str(&d->val_addr6, 0));
+ }
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->sip_list)) {
+ struct dhcp6_listval *d;
+ int i = 0;
+
+ for (d = TAILQ_FIRST(&optinfo->sip_list); d;
+ d = TAILQ_NEXT(d, link), i++) {
+ info_printf("SIP server address[%d] %s",
+ i, in6addr2str(&d->val_addr6, 0));
+ }
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->sipname_list)) {
+ struct dhcp6_listval *d;
+ int i = 0;
+
+ for (d = TAILQ_FIRST(&optinfo->sipname_list); d;
+ d = TAILQ_NEXT(d, link), i++) {
+ info_printf("SIP server domain name[%d] %s",
+ i, d->val_vbuf.dv_buf);
+ }
+ }
+
+ /*
+ * Call the configuration script, if specified, to handle various
+ * configuration parameters.
+ */
+ if (ifp->scriptpath != NULL && strlen(ifp->scriptpath) != 0) {
+ dprintf(LOG_DEBUG, FNAME, "executes %s", ifp->scriptpath);
+ client6_script(ifp->scriptpath, state, optinfo);
+ }
+
+#ifdef USE_DH6OPT_REFRESHTIME
+ /*
+ * Set refresh timer for configuration information specified in
+ * information-request. If the timer value is specified by the server
+ * in an information refresh time option, use it; use the protocol
+ * default otherwise.
+ */
+ if (state == DHCP6S_INFOREQ) {
+ int64_t refreshtime = DHCP6_IRT_DEFAULT;
+
+ if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF)
+ refreshtime = optinfo->refreshtime;
+
+ ifp->timer = dhcp6_add_timer(client6_expire_refreshtime, ifp);
+ if (ifp->timer == NULL) {
+ dprintf(LOG_WARNING, FNAME,
+ "failed to add timer for refresh time");
+ } else {
+ struct timeval tv;
+
+ tv.tv_sec = (long)refreshtime;
+ tv.tv_usec = 0;
+
+ if (tv.tv_sec < 0) {
+ /*
+ * XXX: tv_sec can overflow for an
+ * unsigned 32bit value.
+ */
+ dprintf(LOG_WARNING, FNAME,
+ "refresh time is too large: %lu",
+ (u_int32_t)refreshtime);
+ tv.tv_sec = 0x7fffffff; /* XXX */
+ }
+
+ dhcp6_set_timer(&tv, ifp->timer);
+ }
+ } else if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) {
+ /*
+ * draft-ietf-dhc-lifetime-02 clarifies that refresh time
+ * is only used for information-request and reply exchanges.
+ */
+ dprintf(LOG_INFO, FNAME,
+ "unexpected information refresh time option (ignored)");
+ }
+#endif /* USE_DH6OPT_REFRESHTIME */
+
+ /* update stateful configuration information */
+ if (state != DHCP6S_RELEASE) {
+ update_ia(IATYPE_PD, &optinfo->iapd_list, ifp,
+ &optinfo->serverID, ev->authparam);
+ update_ia(IATYPE_NA, &optinfo->iana_list, ifp,
+ &optinfo->serverID, ev->authparam);
+ }
+
+ dhcp6_remove_event(ev);
+
+ if (state == DHCP6S_RELEASE) {
+ /*
+ * When the client receives a valid Reply message in response
+ * to a Release message, the client considers the Release event
+ * completed, regardless of the Status Code option(s) returned
+ * by the server.
+ * [RFC3315 Section 18.1.8]
+ */
+ check_exit();
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "got an expected reply, sleeping.");
+
+ if (infreq_mode) {
+ exit_ok = 1;
+ free_resources(NULL);
+ unlink(pid_file);
+ check_exit();
+ }
+ return (0);
+}
+
+static struct dhcp6_event *
+find_event_withid(ifp, xid)
+ struct dhcp6_if *ifp;
+ u_int32_t xid;
+{
+ struct dhcp6_event *ev;
+
+ for (ev = TAILQ_FIRST(&ifp->event_list); ev;
+ ev = TAILQ_NEXT(ev, link)) {
+ if (ev->xid == xid)
+ return (ev);
+ }
+
+ return (NULL);
+}
+
+static int
+process_auth(authparam, dh6, len, optinfo)
+ struct authparam *authparam;
+ struct dhcp6 *dh6;
+ ssize_t len;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct keyinfo *key = NULL;
+ int authenticated = 0;
+
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_UNDEF:
+ /* server did not provide authentication option */
+ break;
+ case DHCP6_AUTHPROTO_DELAYED:
+ if ((optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
+ dprintf(LOG_INFO, FNAME, "server did not include "
+ "authentication information");
+ break;
+ }
+
+ if (optinfo->authalgorithm != DHCP6_AUTHALG_HMACMD5) {
+ dprintf(LOG_INFO, FNAME, "unknown authentication "
+ "algorithm (%d)", optinfo->authalgorithm);
+ break;
+ }
+
+ if (optinfo->authrdm != DHCP6_AUTHRDM_MONOCOUNTER) {
+ dprintf(LOG_INFO, FNAME,"unknown RDM (%d)",
+ optinfo->authrdm);
+ break;
+ }
+
+ /*
+ * Replay protection. If we do not know the previous RD value,
+ * we accept the message anyway (XXX).
+ */
+ if ((authparam->flags & AUTHPARAM_FLAGS_NOPREVRD)) {
+ dprintf(LOG_WARNING, FNAME, "previous RD value is "
+ "unknown (accept it)");
+ } else {
+ if (dhcp6_auth_replaycheck(optinfo->authrdm,
+ authparam->prevrd, optinfo->authrd)) {
+ dprintf(LOG_INFO, FNAME,
+ "possible replay attack detected");
+ break;
+ }
+ }
+
+ /* identify the secret key */
+ if ((key = authparam->key) != NULL) {
+ /*
+ * If we already know a key, its identification should
+ * match that contained in the received option.
+ * (from Section 21.4.5.1 of RFC3315)
+ */
+ 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");
+ break;
+ }
+ } else {
+ key = find_key(optinfo->delayedauth_realmval,
+ optinfo->delayedauth_realmlen,
+ optinfo->delayedauth_keyid);
+ if (key == NULL) {
+ dprintf(LOG_INFO, FNAME, "failed to find key "
+ "provided by the server (ID: %x)",
+ optinfo->delayedauth_keyid);
+ break;
+ } else {
+ dprintf(LOG_DEBUG, FNAME, "found key for "
+ "authentication: %s", key->name);
+ }
+ authparam->key = key;
+ }
+
+ /* 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");
+ authenticated = 1;
+ } else {
+ dprintf(LOG_INFO, FNAME, "invalid message "
+ "authentication");
+ }
+
+ break;
+ default:
+ dprintf(LOG_INFO, FNAME, "server sent unsupported "
+ "authentication protocol (%d)", optinfo->authproto);
+ break;
+ }
+
+ if (authenticated == 0) {
+ if (authparam->authproto != DHCP6_AUTHPROTO_UNDEF) {
+ dprintf(LOG_INFO, FNAME, "message not authenticated "
+ "while authentication required");
+
+ /*
+ * Right now, we simply discard unauthenticated
+ * messages.
+ */
+ return (-1);
+ }
+ } else {
+ /* if authenticated, update the "previous" RD value */
+ authparam->prevrd = optinfo->authrd;
+ authparam->flags &= ~AUTHPARAM_FLAGS_NOPREVRD;
+ }
+
+ return (0);
+}
+
+static int
+set_auth(ev, optinfo)
+ struct dhcp6_event *ev;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct authparam *authparam = ev->authparam;
+
+ if (authparam == NULL)
+ return (0);
+
+ optinfo->authproto = authparam->authproto;
+ optinfo->authalgorithm = authparam->authalgorithm;
+ optinfo->authrdm = authparam->authrdm;
+
+ switch (authparam->authproto) {
+ case DHCP6_AUTHPROTO_UNDEF: /* we simply do not need authentication */
+ return (0);
+ case DHCP6_AUTHPROTO_DELAYED:
+ if (ev->state == DHCP6S_INFOREQ) {
+ /*
+ * In the current implementation, delayed
+ * authentication for Information-request and Reply
+ * exchanges doesn't work. Specification is also
+ * unclear on this usage.
+ */
+ dprintf(LOG_WARNING, FNAME, "delayed authentication "
+ "cannot be used for Information-request yet");
+ return (-1);
+ }
+
+ if (ev->state == DHCP6S_SOLICIT) {
+ optinfo->authflags |= DHCP6OPT_AUTHFLAG_NOINFO;
+ return (0); /* no auth information is needed */
+ }
+
+ if (authparam->key == NULL) {
+ dprintf(LOG_INFO, FNAME,
+ "no authentication key for %s",
+ dhcp6_event_statestr(ev));
+ return (-1);
+ }
+
+ if (dhcp6_validate_key(authparam->key)) {
+ dprintf(LOG_INFO, FNAME, "key %s is invalid",
+ authparam->key->name);
+ return (-1);
+ }
+
+ if (get_rdvalue(optinfo->authrdm, &optinfo->authrd,
+ sizeof(optinfo->authrd))) {
+ dprintf(LOG_ERR, FNAME, "failed to get a replay "
+ "detection value");
+ return (-1);
+ }
+
+ optinfo->delayedauth_keyid = authparam->key->keyid;
+ optinfo->delayedauth_realmlen = authparam->key->realmlen;
+ optinfo->delayedauth_realmval =
+ malloc(optinfo->delayedauth_realmlen);
+ if (optinfo->delayedauth_realmval == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to allocate memory "
+ "for authentication realm");
+ return (-1);
+ }
+ memcpy(optinfo->delayedauth_realmval, authparam->key->realm,
+ optinfo->delayedauth_realmlen);
+
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unsupported authentication protocol "
+ "%d", authparam->authproto);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static void
+info_printf(const char *fmt, ...)
+{
+ va_list ap;
+ char logbuf[LINE_MAX];
+
+ va_start(ap, fmt);
+ vsnprintf(logbuf, sizeof(logbuf), fmt, ap);
+
+ dprintf(LOG_DEBUG, FNAME, "%s", logbuf);
+ if (infreq_mode)
+ printf("%s\n", logbuf);
+
+ return;
+}