/* $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 #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #ifdef __FreeBSD__ #include #endif #include #ifdef __KAME__ #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; int fd; #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: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; case 'P': profile = optarg; break; default: usage(); exit(0); } } argc -= optind; argv += optind; if (argc == 0) { usage(); exit(0); } if (foreground == 0) { for (fd = 3; fd < 1024; fd++) close(fd); openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON); } setloglevel(debug); client6_init(); while (argc-- > 0) { if ((ifp = ifinit(argv[0])) == NULL) { debug_printf(LOG_ERR, FNAME, "failed to initialize %s", argv[0]); exit(1); } argv++; } if (infreq_mode == 0 && (cfparse(conffile)) != 0) { debug_printf(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)) { debug_printf(LOG_ERR, FNAME, "failed to get a DUID"); exit(1); } if (dhcp6_ctl_authinit(ctlkeyfile, &ctlkey, &ctldigestlen) != 0) { debug_printf(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) { debug_printf(LOG_ERR, FNAME, "getaddrinfo: %s", gai_strerror(error)); exit(1); } sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) { debug_printf(LOG_ERR, FNAME, "socket"); exit(1); } if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { debug_printf(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) { debug_printf(LOG_ERR, FNAME, "setsockopt(IPV6_RECVPKTINFO): %s", strerror(errno)); exit(1); } #else if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &on, sizeof(on)) < 0) { debug_printf(LOG_ERR, FNAME, "setsockopt(IPV6_PKTINFO): %s", strerror(errno)); exit(1); } #endif if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on)) < 0) { debug_printf(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) { debug_printf(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) { debug_printf(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) { debug_printf(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) { debug_printf(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) debug_printf(LOG_NOTICE, FNAME, "skip opening control port"); else if (dhcp6_ctl_init(ctladdr, ctlport, DHCP6CTL_DEF_COMMANDQUEUELEN, &ctlsock)) { debug_printf(LOG_ERR, FNAME, "failed to initialize control channel"); exit(1); } if (signal(SIGHUP, client6_signal) == SIG_ERR) { debug_printf(LOG_WARNING, FNAME, "failed to set signal: %s", strerror(errno)); exit(1); } if (signal(SIGTERM, client6_signal) == SIG_ERR) { debug_printf(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) { debug_printf(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) { debug_printf(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) { debug_printf(LOG_WARNING, FNAME, "failed to allocate " "authentication parameters"); dhcp6_remove_event(ev); return (-1); } if ((ev->timer = dhcp6_add_timer(client6_timo, ev)) == NULL) { debug_printf(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)) { debug_printf(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. */ debug_printf(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)) { debug_printf(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) { debug_printf(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) { debug_printf(LOG_ERR, FNAME, "assumption failure: command length mismatch"); return (DHCP6CTL_R_FAILURE); } /* replay protection and message authentication */ if ((now = time(NULL)) < 0) { debug_printf(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) { debug_printf(LOG_INFO, FNAME, "timestamp is out of range"); return (DHCP6CTL_R_FAILURE); } if (ctlkey == NULL) { /* should not happen!! */ debug_printf(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) { debug_printf(LOG_INFO, FNAME, "authentication failure"); return (DHCP6CTL_R_FAILURE); } bp = buf + sizeof(*ctlhead) + ctldigestlen; commandlen -= ctldigestlen; if (version > DHCP6CTL_VERSION) { debug_printf(LOG_INFO, FNAME, "unsupported version: %d", version); return (DHCP6CTL_R_FAILURE); } switch (command) { case DHCP6CTL_COMMAND_RELOAD: if (commandlen != 0) { debug_printf(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: debug_printf(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: debug_printf(LOG_INFO, FNAME, "unknown start target: %ul", p32); return (DHCP6CTL_R_FAILURE); } } break; default: debug_printf(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) { debug_printf(LOG_WARNING, FNAME, "failed to reload configuration file"); return; } debug_printf(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) { debug_printf(LOG_INFO, FNAME, "failed to find interface configuration for %s", ifname); return (-1); } debug_printf(LOG_DEBUG, FNAME, "%s interface %s", command == DHCP6CTL_COMMAND_START ? "start" : "stop", ifname); switch(command) { case DHCP6CTL_COMMAND_START: /* * The ifid might have changed, so reset it before releasing the * lease. */ if (ifreset(ifp)) { dprintf(LOG_NOTICE, FNAME, "failed to reset %s", ifname); return (-1); } free_resources(ifp); if (client6_start(ifp)) { debug_printf(LOG_NOTICE, FNAME, "failed to restart %s", ifname); return (-1); } break; case DHCP6CTL_COMMAND_STOP: free_resources(ifp); if (ifp->timer != NULL) { debug_printf(LOG_DEBUG, FNAME, "removed existing timer on %s", ifp->ifname); dhcp6_remove_timer(&ifp->timer); } break; default: /* impossible case, should be a bug */ debug_printf(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; debug_printf(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) { debug_printf(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)) { debug_printf(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 { debug_printf(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! */ debug_printf(LOG_NOTICE, FNAME, "can't find a server"); exit(1); /* XXX */ } if (duidcpy(&ev->serverid, &ev->current_server->optinfo.serverID)) { debug_printf(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)) { debug_printf(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) { debug_printf(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: debug_printf(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: debug_printf(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) { debug_printf(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) { debug_printf(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: debug_printf(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 debug_printf(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)) { debug_printf(LOG_ERR, FNAME, "failed to copy server ID"); goto end; } break; } /* client ID */ if (duidcpy(&optinfo.clientID, &client_duid)) { debug_printf(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)) { debug_printf(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)) { debug_printf(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)) { debug_printf(LOG_NOTICE, FNAME, "failed to add an IAPD"); goto end; } break; default: debug_printf(LOG_ERR, FNAME, "unexpected event data (%d)", evd->type); exit(1); } } /* authentication information */ if (set_auth(ev, &optinfo)) { debug_printf(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) { debug_printf(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)) { debug_printf(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) { debug_printf(LOG_ERR, FNAME, "transmit failed: %s", strerror(errno)); goto end; } debug_printf(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) { debug_printf(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) { debug_printf(LOG_NOTICE, FNAME, "failed to get packet info"); return; } if ((ifp = find_ifconfbyid((unsigned int)pi->ipi6_ifindex)) == NULL) { debug_printf(LOG_INFO, FNAME, "unexpected interface (%d)", (unsigned int)pi->ipi6_ifindex); return; } if (len < sizeof(*dh6)) { debug_printf(LOG_INFO, FNAME, "short packet (%d bytes)", len); return; } dh6 = (struct dhcp6 *)rbuf; debug_printf(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) { debug_printf(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: debug_printf(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) { debug_printf(LOG_INFO, FNAME, "XID mismatch"); return (-1); } /* packet validation based on Section 15.3 of RFC3315. */ if (optinfo->serverID.duid_len == 0) { debug_printf(LOG_INFO, FNAME, "no server ID option"); return (-1); } else { debug_printf(LOG_DEBUG, FNAME, "server ID: %s, pref=%d", duidstr(&optinfo->serverID), optinfo->pref); } if (optinfo->clientID.duid_len == 0) { debug_printf(LOG_INFO, FNAME, "no client ID option"); return (-1); } if (duidcmp(&optinfo->clientID, &client_duid)) { debug_printf(LOG_INFO, FNAME, "client DUID mismatch"); return (-1); } /* validate authentication */ authparam0 = *ev->authparam; if (process_auth(&authparam0, dh6, len, optinfo)) { debug_printf(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)) { debug_printf(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] */ debug_printf(LOG_INFO, FNAME, "unexpected advertise"); /* proceed anyway */ } /* ignore the server if it is known */ if (find_server(ev, &optinfo->serverID)) { debug_printf(LOG_INFO, FNAME, "duplicated server (ID: %s)", duidstr(&optinfo->serverID)); return (-1); } /* keep the server */ if ((newserver = malloc(sizeof(*newserver))) == NULL) { debug_printf(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) { debug_printf(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)) { debug_printf(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)) { debug_printf(LOG_NOTICE, FNAME, "can't copy server ID"); return (-1); /* XXX: better recovery? */ } if (construct_reqdata(ifp, &ev->current_server->optinfo, ev)) { debug_printf(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; debug_printf(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) { debug_printf(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))) { debug_printf(LOG_INFO, FNAME, "unexpected reply"); return (-1); } /* A Reply message must contain a Server ID option */ if (optinfo->serverID.duid_len == 0) { debug_printf(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) { debug_printf(LOG_INFO, FNAME, "no client ID option"); return (-1); } if (duidcmp(&optinfo->clientID, &client_duid)) { debug_printf(LOG_INFO, FNAME, "client DUID mismatch"); return (-1); } /* validate authentication */ if (process_auth(ev->authparam, dh6, len, optinfo)) { debug_printf(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) { debug_printf(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)) { debug_printf(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) { debug_printf(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) { debug_printf(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. */ debug_printf(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. */ debug_printf(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(); } debug_printf(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)) { debug_printf(LOG_INFO, FNAME, "server did not include " "authentication information"); break; } if (optinfo->authalgorithm != DHCP6_AUTHALG_HMACMD5) { debug_printf(LOG_INFO, FNAME, "unknown authentication " "algorithm (%d)", optinfo->authalgorithm); break; } if (optinfo->authrdm != DHCP6_AUTHRDM_MONOCOUNTER) { debug_printf(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)) { debug_printf(LOG_WARNING, FNAME, "previous RD value is " "unknown (accept it)"); } else { if (dhcp6_auth_replaycheck(optinfo->authrdm, authparam->prevrd, optinfo->authrd)) { debug_printf(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) { debug_printf(LOG_INFO, FNAME, "authentication key mismatch"); break; } } else { key = find_key(optinfo->delayedauth_realmval, optinfo->delayedauth_realmlen, optinfo->delayedauth_keyid); if (key == NULL) { debug_printf(LOG_INFO, FNAME, "failed to find key " "provided by the server (ID: %x)", optinfo->delayedauth_keyid); break; } else { debug_printf(LOG_DEBUG, FNAME, "found key for " "authentication: %s", key->name); } authparam->key = key; } /* check for the key lifetime */ if (dhcp6_validate_key(key)) { debug_printf(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) { debug_printf(LOG_DEBUG, FNAME, "message authentication " "validated"); authenticated = 1; } else { debug_printf(LOG_INFO, FNAME, "invalid message " "authentication"); } break; default: debug_printf(LOG_INFO, FNAME, "server sent unsupported " "authentication protocol (%d)", optinfo->authproto); break; } if (authenticated == 0) { if (authparam->authproto != DHCP6_AUTHPROTO_UNDEF) { debug_printf(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. */ debug_printf(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) { debug_printf(LOG_INFO, FNAME, "no authentication key for %s", dhcp6_event_statestr(ev)); return (-1); } if (dhcp6_validate_key(authparam->key)) { debug_printf(LOG_INFO, FNAME, "key %s is invalid", authparam->key->name); return (-1); } if (get_rdvalue(optinfo->authrdm, &optinfo->authrd, sizeof(optinfo->authrd))) { debug_printf(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) { debug_printf(LOG_ERR, FNAME, "failed to allocate memory " "for authentication realm"); return (-1); } memcpy(optinfo->delayedauth_realmval, authparam->key->realm, optinfo->delayedauth_realmlen); break; default: debug_printf(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); debug_printf(LOG_DEBUG, FNAME, "%s", logbuf); if (infreq_mode) printf("%s\n", logbuf); return; }