summaryrefslogtreecommitdiffstats
path: root/dhcp6c.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dhcp6c.c2148
-rw-r--r--dhcp6c.conf.5670
-rw-r--r--dhcp6c.conf.sample24
3 files changed, 2842 insertions, 0 deletions
diff --git a/dhcp6c.c b/dhcp6c.c
new file mode 100644
index 0000000..1caaaa5
--- /dev/null
+++ b/dhcp6c.c
@@ -0,0 +1,2148 @@
+/* $KAME: dhcp6c.c,v 1.164 2006/01/10 02:46:09 jinmei 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 sig_atomic_t sig_flags = 0;
+#define SIGF_TERM 0x1
+#define SIGF_HUP 0x2
+
+const dhcp6_mode_t dhcp6_mode = DHCP6_MODE_CLIENT;
+
+int sock; /* inbound/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 *));
+static struct dhcp6_timer *client6_expire_refreshtime __P((void *));
+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);
+ }
+ sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (sock < 0) {
+ dprintf(LOG_ERR, FNAME, "socket");
+ exit(1);
+ }
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ &on, sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(SO_REUSEPORT): %s", strerror(errno));
+ exit(1);
+ }
+#ifdef IPV6_RECVPKTINFO
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(IPV6_RECVPKTINFO): %s",
+ strerror(errno));
+ exit(1);
+ }
+#else
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(IPV6_PKTINFO): %s",
+ strerror(errno));
+ exit(1);
+ }
+#endif
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on,
+ sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "setsockopt(sock, IPV6_MULTICAST_LOOP): %s",
+ strerror(errno));
+ exit(1);
+ }
+#ifdef IPV6_V6ONLY
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on)) < 0) {
+ dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_V6ONLY): %s",
+ strerror(errno));
+ exit(1);
+ }
+#endif
+
+ /*
+ * According RFC3315 2.2, only the incoming port should be bound to UDP
+ * port 546. However, to have an interoperability with some servers,
+ * the outgoing port is also bound to the DH6PORT_DOWNSTREAM.
+ */
+ if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
+ dprintf(LOG_ERR, FNAME, "bind: %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(sock, &r);
+ maxsock = sock;
+ if (ctlsock >= 0) {
+ FD_SET(ctlsock, &r);
+ maxsock = (sock > ctlsock) ? sock : 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(sock, &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;
+
+ memset(ifname, 0, sizeof(ifname));
+
+ 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);
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "%s interface %s",
+ command == DHCP6CTL_COMMAND_START ? "start" : "stop", ifname);
+
+ 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);
+}
+
+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);
+}
+
+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;
+{
+
+ 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(sock, 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(sock, &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 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);
+ }
+
+ /*
+ * 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)");
+ }
+
+ /* 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;
+}
diff --git a/dhcp6c.conf.5 b/dhcp6c.conf.5
new file mode 100644
index 0000000..611ab4f
--- /dev/null
+++ b/dhcp6c.conf.5
@@ -0,0 +1,670 @@
+.\" $KAME: dhcp6c.conf.5,v 1.30 2005/05/03 06:54:26 jinmei Exp $
+.\"
+.\" Copyright (C) 2002 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.
+.\"
+.Dd July 29, 2004
+.Dt DHCP6C.CONF 5
+.Os KAME
+.\"
+.Sh NAME
+.Nm dhcp6c.conf
+.Nd DHCPv6 client configuration file
+.\"
+.Sh SYNOPSIS
+.Pa /usr/local/etc/dhcp6c.conf
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+file contains configuration information for KAME's DHCPv6 client,
+.Nm dhcp6c .
+The configuration file consists of a sequence of statements terminated
+by a semi-colon (`;').
+Statements are composed of tokens separated by white space,
+which can be any combination of blanks,
+tabs and newlines.
+In some cases a set of statements is combined with a pair of brackets,
+which is regarded as a single token.
+Lines beginning with
+.Ql #
+are comments.
+.Sh Interface specification
+There are some statements that may or have to specify interface.
+Interfaces are specified in the form of "name unit", such as
+.Ar fxp0
+and
+.Ar gif1.
+.\"
+.Sh DHCPv6 options
+Some configuration statements take the description of a DHCPv6 option
+as an argument.
+The followings are the format and description of available DHCPv6
+options.
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic domain-name-servers
+.Xc
+means a Domain Name Server option.
+.It Xo
+.Ic domain-name
+.Xc
+means a domain name option.
+.It Xo
+.Ic ntp-servers
+.Xc
+means an NTP server option.
+As of this writing, the option type for this option is not officially
+assigned.
+.Nm dhcp6c
+will reject this option unless it is explicitly built to accept the option.
+.It Xo
+.Ic sip-server-address
+.Xc
+means a SIP Server address option.
+.It Xo
+.Ic sip-server-domain-name
+.Xc
+means a SIP server domain name option.
+.It Xo
+.Ic nis-server-address
+.Xc
+means a NIS Server address option.
+.It Xo
+.Ic nis-domain-name
+.Xc
+means a NIS domain name option.
+.It Xo
+.Ic nisp-server-address
+.Xc
+means a NIS+ Server address option.
+.It Xo
+.Ic nisp-domain-name
+.Xc
+means a NIS+ domain name option.
+.It Xo
+.Ic bcmcs-server-address
+.Xc
+means a BCMCS Server address option.
+.It Xo
+.Ic bcmcs-server-domain-name
+.Xc
+means a BCMCS server domain name option.
+.It Ic ia-pd Ar ID
+means an IA_PD
+.Pq Identity Association for Prefix Delegation
+option.
+.Ar ID
+is a decimal number of the IAID
+.Pq see below about identity associations .
+.It Ic ia-na Ar ID
+means an IA_PD
+.Pq Identity Association for Non-temporary Addresses
+option.
+.Ar ID
+is a decimal number of the IAID
+.Pq see below about identity associations .
+.It Ic rapid-commit
+means a rapid-commit option.
+.It Ic authentication Ar authname
+means an authentication option.
+.Ar authname
+is a string specifying parameters of the authentication protocol.
+An
+.Ic authentication
+statement for
+.Ar authname
+must be provided.
+.El
+.\"
+.Sh Interface statement
+An interface statement specifies configuration parameters on the
+interface.
+The generic format of an interface statement is as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic interface Ar interface
+{
+.Ar substatements
+};
+.Xc
+The followings are possible
+.Ar substatements
+in an interface statement.
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic send Ar send-options
+;
+.Xc
+This statement specifies DHCPv6 options to be sent to the server(s).
+Some options can only appear in particular messages according to the
+specification,
+in which case the appearance of the options is limited to be compliant
+with the specification.
+.Pp
+.Ar send-options
+is a comma-separated list of options,
+each of which should be specified as described above.
+Multiple
+.Ic send
+statements can also be specified,
+in which case all the specified options will be sent.
+.Pp
+When
+.Ic rapid-commit
+is specified,
+.Nm dhcp6c
+will include a rapid-commit option in solicit messages and wait for
+an immediate reply instead of advertisements.
+.Pp
+When
+.Ic ia-pd
+is specified,
+.Nm dhcp6c
+will initiate prefix delegation as a requesting router by
+including an IA_PD option with the specified
+.Ar ID
+in solicit messages.
+.Pp
+When
+.Ic ia-na
+is specified,
+.Nm dhcp6c
+will initiate stateful address assignment by
+including an IA_NA option with the specified
+.Ar ID
+in solicit messages.
+.Pp
+In either case, a corresponding identity association statement
+must exist with the same
+.Ar ID .
+.It Ic request Ar request-options ;
+This statement specifies DHCPv6 options to be included in an
+option-request option.
+.Ar request-options
+is a comma-separated list of options,
+which can consist of the following options.
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic domain-name-servers
+.Xc
+requests a list of Domain Name Server addresses.
+.It Xo
+.Ic domain-name
+.Xc
+requests a DNS search path.
+.It Xo
+.Ic ntp-servers
+.Xc
+requests a list of NTP server addresses.
+As of this writing, the option type for this option is not officially
+assigned.
+.Nm dhcp6c
+will reject this option unless it is explicitly built to accept the option.
+.It Xo
+.Ic sip-server-address
+.Xc
+requests a list of SIP server addresses.
+.It Xo
+.Ic sip-domain-name
+.Xc
+requests a SIP server domain name.
+.It Xo
+.Ic nis-server-address
+.Xc
+requests a list of NIS server addresses.
+.It Xo
+.Ic nis-domain-name
+.Xc
+requests a NIS domain name.
+.It Xo
+.Ic nisp-server-address
+.Xc
+requests a list of NIS+ server addresses.
+.It Xo
+.Ic nisp-domain-name
+.Xc
+requests a NIS+ domain name.
+.It Xo
+.Ic bcmcs-server-address
+.Xc
+requests a list of BCMCS server addresses.
+.It Xo
+.Ic bcmcs-domain-name
+.Xc
+requests a BCMCS domain name.
+.It Xo
+.Ic refreshtime
+.Xc
+means an information refresh time option.
+This can only be specified when sent with information-request
+messages;
+.Nm dhcp6c
+will ignore this option for other messages.
+.El
+Multiple
+.Ic request
+statements can also be specified,
+in which case all the specified options will be requested.
+.It Ic information-only ;
+This statement specifies
+.Nm dhcp6c
+to only exchange informational configuration parameters with servers.
+A list of DNS server addresses is an example of such parameters.
+This statement is useful when the client does not need stateful
+configuration parameters such as IPv6 addresses or prefixes.
+.It Ic script Ar \(dqscript-name\(dq ;
+This statement specifies a path to script invoked by
+.Nm dhcp6c
+on a certain condition including when the daemon receives a reply
+message.
+.Ar script-name
+must be the absolute path from root to the script file, be a regular
+file, and be created by the same owner who runs the daemon.
+.El
+.El
+.\"
+.Sh Identity association statement
+Identity association
+.Pq IA
+is a key notion of DHCPv6.
+An IA is uniquely identified in a client by a pair of IA type and
+IA identifier
+.Pq IAID .
+An IA is associated with configuration information dependent on the IA type.
+.Pp
+An identity association statement defines a single IA with some
+client-side configuration parameters.
+Its format is as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic id-assoc Ar type Op Ar ID
+{
+.Ar substatements
+};
+.Xc
+.Ar type
+is a string for the type of this IA.
+The current implementation supports
+.Ql Ic na
+(non-temporary address allocation)
+.Ql Ic pd
+(prefix delegation) for the IA type.
+.Ar ID
+is a decimal number of IAID.
+If omitted, the value 0 will be used by default.
+.Ar substatements
+is a sequence of statements that specifies configuration parameters
+for this IA.
+Each statement may or may not be specific to the type of IA.
+.Pp
+The followings are possible
+.Ar substatements
+for an IA of type
+.Ic na .
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic address Ar ipv6-address pltime Op Ar vltime ;
+.Xc
+specifies an address and related parameters that the client wants to be
+allocated.
+Multiple addresses can be specified, each of which is described as a
+separate
+.Ic address
+substatement.
+.Nm dhcp6c
+will include all the addresses
+.Pq and related parameters
+in Solicit messages,
+as an IA_NA prefix option encapsulated in the corresponding IA_NA
+option.
+Note, however, that the server may or may not respect the specified
+prefix parameters.
+For parameters of the
+.Ic address
+substatement,
+see
+.Xr dhcp6s.conf 5 .
+.El
+.Pp
+The followings are possible
+.Ar substatements
+for an IA of type
+.Ic pd .
+.Bl -tag -width Ds -compact
+.It Xo
+.Ar prefix_interface_statement
+.Xc
+specifies the client's local configuration of how delegated prefixes
+should be used
+.Pq see below .
+.It Ic prefix Ar ipv6-prefix pltime Op Ar vltime ;
+specifies a prefix and related parameters that the client wants to be
+delegated.
+Multiple prefixes can be specified, each of which is described as a
+separate
+.Ic prefix
+substatement.
+.Nm dhcp6c
+will include all the prefixes
+.Pq and related parameters
+in Solicit messages,
+as an IA_PD prefix option encapsulated in the corresponding IA_PD
+option.
+Note, however, that the server may or may not respect the specified
+prefix parameters.
+For parameters of the
+.Ic prefix
+substatement,
+see
+.Xr dhcp6s.conf 5 .
+.El
+.El
+.\"
+.Sh Prefix interface statement
+A prefix interface statement specifies configuration parameters of
+prefixes on local interfaces that are derived from delegated prefixes.
+A prefix interface statement can only appear as a substatement of
+an identity association statement with the type
+.Ic pd .
+The generic format of an interface statement is as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic prefix-interface Ar interface
+{
+.Ar substatements
+};
+.Xc
+When an IPv6 prefix is delegated from a DHCPv6 server,
+.Nm dhcp6c
+will assign a prefix on the
+.Ar interface
+unless the interface receives the DHCPv6 message that contains the prefix
+with the delegated prefix and the parameters provided in
+.Ar substatements .
+Possible substatements are as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic sla-id Ar ID
+;
+.Xc
+This statement specifies the identifier value of the site-level aggregator
+.Pq SLA
+on the interface.
+.Ar ID
+must be a decimal integer which fits in the length of SLA IDs
+.Pq see below .
+For example,
+if
+.Ar ID
+is 1 and the client is delegated an IPv6 prefix 2001:db8:ffff::/48,
+.Nm dhcp6c
+will combine the two values into a single IPv6 prefix,
+2001:db8:ffff:1::/64,
+and will configure the prefix on the specified
+.Ar interface .
+.It Xo
+.Ic sla-len Ar length
+;
+.Xc
+This statement specifies the length of the SLA ID in bits.
+.Ar length
+must be a decimal number between 0 and 128.
+If the length is not specified by this statement,
+the default value 16 will be used.
+.El
+.El
+.\"
+.Sh Authentication statement
+An authentication statement defines a set of authentication parameters
+used in DHCPv6 exchanges with the server(s).
+The format of an authentication statement is as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic authentication Ar authname
+{
+.Ar substatements
+};
+.Xc
+.Ar authname
+is a string which is unique among all authentication statements in the
+configuration file.
+It will specify a particular set of authentication parameters when
+.Ic authentication
+option is specified in the
+.Ic interface
+statement.
+Possible substatements of the
+.Ic authentication
+statement are as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic protocol Ar authprotocol
+;
+.Xc
+specifies the authentication protocol.
+Currently, the only available protocol as
+.Ar authprotocol
+is
+.Ic delayed ,
+which means the DHCPv6 delayed authentication protocol.
+.It Xo
+.Ic algorithm Ar authalgorithm
+;
+.Xc
+specifies the algorithm for this authentication.
+Currently, the only available algorithm is HMAC-MD5,
+which can be specified as one of the followings:
+.Ic hmac-md5 ,
+.Ic HMAC-MD5 ,
+.Ic hmacmd5 ,
+or
+.Ic HMACMD5 .
+This substatement can be omitted.
+In this case,
+HMAC-MD5 will be used as the algorithm.
+.It Xo
+.Ic rdm Ar replay-detection-method
+;
+.Xc
+specifies the replay protection method for this authentication.
+Currently, the only available method is
+.Ic monocounter ,
+which means the use of a monotonically increasing counter.
+If this method is specified,
+.Ic dhcp6c
+will use an NTP-format timestamp when it authenticates the message.
+This substatement can be omitted,
+in which case
+.Ic monocounter
+will be used as the method.
+.El
+.El
+.\"
+.Sh Keyinfo statement
+A keyinfo statement defines a secret key shared with the server(s)
+to authenticate DHCPv6 messages.
+The format of a keyinfo statement is as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic keyinfo Ar keyname
+{
+.Ar substatements
+};
+.Xc
+.Ar keyname
+is an arbitrary string.
+It does not affect client's behavior but is provided for readability
+of log messages.
+Possible substatements of the
+.Ic keyinfo
+statement are as follows:
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic realm Ar \(dqrealmname\(dq
+;
+.Xc
+specifies the DHCP realm.
+.Ar realmname
+is an arbitrary string,
+but is typically expected to be a domain name like \(dqkame.net\(dq .
+.It Xo
+.Ic keyid Ar ID
+;
+.Xc
+specifies the key identifier,
+.Ar ID ,
+as a decimal number.
+A secret key is uniquely identified within the client by the DHCP
+realm and the key identifier.
+.It Xo
+.Ic secret Ar \(dqsecret-value\(dq
+;
+.Xc
+specifies the shared secret of this key.
+.Ar \(dqsecret-value\(dq
+is a base-64 encoded string of the secret.
+.It Xo
+.Ic expire Ar \(dqexpiration-time\(dq
+;
+.Xc
+specifies the expiration time of this key.
+.Ar \(dqexpiration-time\(dq
+should be formatted in one of the followings:
+.Ar yyyy-mm-dd HH:MM ,
+.Ar mm-dd HH:MM ,
+or
+.Ar HH:MM ,
+where
+.Ar yyyy
+is the year with century (e.g., 2004),
+.Ar mm
+is the month,
+.Ar dd
+is the day of the month,
+.Ar HH
+is the hour of 24-hour clock,
+and
+.Ar MM
+is the minute,
+each of which is given as a decimal number.
+Additionally,
+a special keyword
+.Ic forever
+can be specified as
+.Ar expiration-time ,
+which means the key has an infinite lifetime and never expires.
+This substatement can be omitted,
+in which case
+.Ic forever
+will be used by default.
+.El
+.El
+.\"
+.Sh Examples
+The followings are a sample configuration to be delegated an IPv6
+prefix from an upstream service provider.
+With this configuration
+.Nm dhcp6c
+will send solicit messages containing an IA_PD option,
+with an IAID 0,
+on to an upstream PPP link,
+.Ar ppp0 .
+After receiving some prefixes from a server,
+.Nm dhcp6c
+will then configure derived IPv6 prefixes with the SLA ID 1 on a
+local ethernet interface,
+.Ar ne0 .
+Note that the IAID for the
+.Ic id-assoc
+statement is 0 according to the default.
+.Bd -literal -offset
+interface ppp0 {
+ send ia-pd 0;
+};
+
+id-assoc pd {
+ prefix-interface ne0 {
+ sla-id 1;
+ };
+};
+.Ed
+.Pp
+If a shared secret should be configured in both the client and the
+server for DHCPv6 authentication,
+it would be specified in the configuration file as follows:
+.Bd -literal -offset
+keyinfo kame-key {
+ realm "kame.net";
+ keyid 1;
+ secret "5pvW2g48OHPvkYMJSw0vZA==";
+};
+.Ed
+.Pp
+One easy way of generating a new secret in the base64 format is to
+execute the
+.Xr openssl 1
+command (when available) as follows,
+.Bd -literal -offset
+% openssl rand -base64 16
+.Ed
+.Pp
+and copy the output to the
+.Nm dhcp6c.conf
+file.
+.Pp
+To include an authentication option for DHCPv6 authentication,
+the
+.Ic interface
+statement should be modified and an
+.Ic authentication
+statement should be added as follows:
+.Bd -literal -offset
+interface ppp0 {
+ send ia-pd 0;
+ send authentication kame;
+};
+
+authentication kame {
+ protocol delayed;
+};
+.Ed
+.Pp
+.Bd -literal -offset
+interface fxp0 {
+ send ia-na 0;
+};
+.Ed
+.Sh SEE ALSO
+.Xr dhcp6s.conf 5
+.Xr dhcp6c 8
+.\"
+.Sh HISTORY
+The
+.Nm
+configuration file first appeared in the WIDE/KAME IPv6 protocol
+stack kit.
diff --git a/dhcp6c.conf.sample b/dhcp6c.conf.sample
new file mode 100644
index 0000000..3aeede1
--- /dev/null
+++ b/dhcp6c.conf.sample
@@ -0,0 +1,24 @@
+# The followings are a sample configuration for requiring the "stateless"
+# DHCPv6 service.
+interface ne0 {
+ information-only;
+};
+
+
+# The followings are a sample configuration to be delegated an IPv6 prefix
+# from an upstream service provider. With this configuration dhcp6c will
+# send solicit messages containing an IA_PD option, with an IAID 0, on to
+# an upstream PPP link, ppp0. After receiving some prefixes from a server,
+# dhcp6c will then configure derived IPv6 prefixes with the SLA ID 1 on a
+# local ethernet interface, ne0. Note that the IAID for the id-assoc
+# statement is 0 according to the default.
+
+interface ppp0 {
+ send ia-pd 0;
+};
+
+id-assoc pd {
+ prefix-interface ne0 {
+ sla-id 1;
+ };
+};