aboutsummaryrefslogtreecommitdiff
path: root/common.c
diff options
context:
space:
mode:
authorSUZUKI, Shinsuke <suz@kame.net>2005-12-01 06:35:48 +0000
committerSUZUKI, Shinsuke <suz@kame.net>2005-12-01 06:35:48 +0000
commit48c68dfb9b77069425980bd155e331e64dc3c64e (patch)
tree0a824722c4c612d5390716f01242faa520bb7af1 /common.c
imported KAME-DHCPv6 snapshot at 20051201KAME_20051201
Diffstat (limited to 'common.c')
-rw-r--r--common.c3288
1 files changed, 3288 insertions, 0 deletions
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..46366f2
--- /dev/null
+++ b/common.c
@@ -0,0 +1,3288 @@
+/* $KAME: common.c,v 1.129 2005/09/16 11:30:13 suz Exp $ */
+/*
+ * Copyright (C) 1998 and 1999 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#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>
+#include <netinet/in.h>
+#ifdef __KAME__
+#include <net/if_types.h>
+#ifdef __FreeBSD__
+#include <net/if_var.h>
+#endif
+#include <net/if_dl.h>
+#endif
+#ifdef __linux__
+#include <linux/if_packet.h>
+#endif
+#include <net/if_arp.h>
+
+#ifdef __KAME__
+#include <netinet6/in6_var.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <err.h>
+#include <netdb.h>
+
+#ifdef HAVE_GETIFADDRS
+# ifdef HAVE_IFADDRS_H
+# define USE_GETIFADDRS
+# include <ifaddrs.h>
+# endif
+#endif
+
+#include <dhcp6.h>
+#include <config.h>
+#include <common.h>
+#include <timer.h>
+
+#ifdef __linux__
+/* from /usr/include/linux/ipv6.h */
+
+struct in6_ifreq {
+ struct in6_addr ifr6_addr;
+ u_int32_t ifr6_prefixlen;
+ unsigned int ifr6_ifindex;
+};
+#endif
+
+#define MAXDNAME 255
+
+int foreground;
+int debug_thresh;
+
+static int dhcp6_count_list __P((struct dhcp6_list *));
+static int in6_matchflags __P((struct sockaddr *, char *, int));
+static ssize_t dnsencode __P((const char *, char *, size_t));
+static char *dnsdecode __P((u_char **, u_char *, char *, size_t));
+static int copyout_option __P((char *, char *, struct dhcp6_listval *));
+static int copyin_option __P((int, struct dhcp6opt *, struct dhcp6opt *,
+ struct dhcp6_list *));
+static int copy_option __P((u_int16_t, u_int16_t, void *, struct dhcp6opt **,
+ struct dhcp6opt *, int *));
+static ssize_t gethwid __P((char *, int, const char *, u_int16_t *));
+static int get_delegated_prefixes __P((char *, char *,
+ struct dhcp6_optinfo *));
+static char *sprint_uint64 __P((char *, int, u_int64_t));
+static char *sprint_auth __P((struct dhcp6_optinfo *));
+
+int
+dhcp6_copy_list(dst, src)
+ struct dhcp6_list *dst, *src;
+{
+ struct dhcp6_listval *ent;
+
+ for (ent = TAILQ_FIRST(src); ent; ent = TAILQ_NEXT(ent, link)) {
+ if (dhcp6_add_listval(dst, ent->type,
+ &ent->uv, &ent->sublist) == NULL)
+ goto fail;
+ }
+
+ return (0);
+
+ fail:
+ dhcp6_clear_list(dst);
+ return (-1);
+}
+
+void
+dhcp6_move_list(dst, src)
+ struct dhcp6_list *dst, *src;
+{
+ struct dhcp6_listval *v;
+
+ while ((v = TAILQ_FIRST(src)) != NULL) {
+ TAILQ_REMOVE(src, v, link);
+ TAILQ_INSERT_TAIL(dst, v, link);
+ }
+}
+
+void
+dhcp6_clear_list(head)
+ struct dhcp6_list *head;
+{
+ struct dhcp6_listval *v;
+
+ while ((v = TAILQ_FIRST(head)) != NULL) {
+ TAILQ_REMOVE(head, v, link);
+ dhcp6_clear_listval(v);
+ }
+
+ return;
+}
+
+static int
+dhcp6_count_list(head)
+ struct dhcp6_list *head;
+{
+ struct dhcp6_listval *v;
+ int i;
+
+ for (i = 0, v = TAILQ_FIRST(head); v; v = TAILQ_NEXT(v, link))
+ i++;
+
+ return (i);
+}
+
+void
+dhcp6_clear_listval(lv)
+ struct dhcp6_listval *lv;
+{
+ dhcp6_clear_list(&lv->sublist);
+ switch (lv->type) {
+ case DHCP6_LISTVAL_VBUF:
+ dhcp6_vbuf_free(&lv->val_vbuf);
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ free(lv);
+}
+
+/*
+ * Note: this function only searches for the first entry that matches
+ * VAL. It also does not care about sublists.
+ */
+struct dhcp6_listval *
+dhcp6_find_listval(head, type, val, option)
+ struct dhcp6_list *head;
+ dhcp6_listval_type_t type;
+ void *val;
+ int option;
+{
+ struct dhcp6_listval *lv;
+
+ for (lv = TAILQ_FIRST(head); lv; lv = TAILQ_NEXT(lv, link)) {
+ if (lv->type != type)
+ continue;
+
+ switch(type) {
+ case DHCP6_LISTVAL_NUM:
+ if (lv->val_num == *(int *)val)
+ return (lv);
+ break;
+ case DHCP6_LISTVAL_STCODE:
+ if (lv->val_num16 == *(u_int16_t *)val)
+ return (lv);
+ break;
+ case DHCP6_LISTVAL_ADDR6:
+ if (IN6_ARE_ADDR_EQUAL(&lv->val_addr6,
+ (struct in6_addr *)val)) {
+ return (lv);
+ }
+ break;
+ case DHCP6_LISTVAL_PREFIX6:
+ if ((option & MATCHLIST_PREFIXLEN) &&
+ lv->val_prefix6.plen ==
+ ((struct dhcp6_prefix *)val)->plen) {
+ return (lv);
+ } else if (IN6_ARE_ADDR_EQUAL(&lv->val_prefix6.addr,
+ &((struct dhcp6_prefix *)val)->addr) &&
+ lv->val_prefix6.plen ==
+ ((struct dhcp6_prefix *)val)->plen) {
+ return (lv);
+ }
+ break;
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ if (IN6_ARE_ADDR_EQUAL(&lv->val_statefuladdr6.addr,
+ &((struct dhcp6_prefix *)val)->addr)) {
+ return (lv);
+ }
+ break;
+ case DHCP6_LISTVAL_IAPD:
+ case DHCP6_LISTVAL_IANA:
+ if (lv->val_ia.iaid ==
+ ((struct dhcp6_ia *)val)->iaid) {
+ return (lv);
+ }
+ break;
+ case DHCP6_LISTVAL_VBUF:
+ if (dhcp6_vbuf_cmp(&lv->val_vbuf,
+ (struct dhcp6_vbuf *)val) == 0) {
+ return (lv);
+ }
+ break;
+ }
+ }
+
+ return (NULL);
+}
+
+struct dhcp6_listval *
+dhcp6_add_listval(head, type, val, sublist)
+ struct dhcp6_list *head, *sublist;
+ dhcp6_listval_type_t type;
+ void *val;
+{
+ struct dhcp6_listval *lv = NULL;
+
+ if ((lv = malloc(sizeof(*lv))) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to allocate memory for list entry");
+ goto fail;
+ }
+ memset(lv, 0, sizeof(*lv));
+ lv->type = type;
+ TAILQ_INIT(&lv->sublist);
+
+ switch(type) {
+ case DHCP6_LISTVAL_NUM:
+ lv->val_num = *(int *)val;
+ break;
+ case DHCP6_LISTVAL_STCODE:
+ lv->val_num16 = *(u_int16_t *)val;
+ break;
+ case DHCP6_LISTVAL_ADDR6:
+ lv->val_addr6 = *(struct in6_addr *)val;
+ break;
+ case DHCP6_LISTVAL_PREFIX6:
+ lv->val_prefix6 = *(struct dhcp6_prefix *)val;
+ break;
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ lv->val_statefuladdr6 = *(struct dhcp6_statefuladdr *)val;
+ break;
+ case DHCP6_LISTVAL_IAPD:
+ case DHCP6_LISTVAL_IANA:
+ lv->val_ia = *(struct dhcp6_ia *)val;
+ break;
+ case DHCP6_LISTVAL_VBUF:
+ if (dhcp6_vbuf_copy(&lv->val_vbuf, (struct dhcp6_vbuf *)val))
+ goto fail;
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME,
+ "unexpected list value type (%d)", type);
+ goto fail;
+ }
+
+ if (sublist && dhcp6_copy_list(&lv->sublist, sublist))
+ goto fail;
+
+ TAILQ_INSERT_TAIL(head, lv, link);
+
+ return (lv);
+
+ fail:
+ if (lv)
+ free(lv);
+
+ return (NULL);
+}
+
+int
+dhcp6_vbuf_copy(dst, src)
+ struct dhcp6_vbuf *dst, *src;
+{
+ dst->dv_buf = malloc(src->dv_len);
+ if (dst->dv_buf == NULL)
+ return (-1);
+
+ dst->dv_len = src->dv_len;
+ memcpy(dst->dv_buf, src->dv_buf, dst->dv_len);
+
+ return (0);
+}
+
+void
+dhcp6_vbuf_free(vbuf)
+ struct dhcp6_vbuf *vbuf;
+{
+ free(vbuf->dv_buf);
+
+ vbuf->dv_len = 0;
+ vbuf->dv_buf = NULL;
+}
+
+int
+dhcp6_vbuf_cmp(vb1, vb2)
+ struct dhcp6_vbuf *vb1, *vb2;
+{
+ if (vb1->dv_len != vb2->dv_len)
+ return (vb1->dv_len - vb2->dv_len);
+
+ return (memcmp(vb1->dv_buf, vb2->dv_buf, vb1->dv_len));
+}
+
+struct dhcp6_event *
+dhcp6_create_event(ifp, state)
+ struct dhcp6_if *ifp;
+ int state;
+{
+ struct dhcp6_event *ev;
+
+ if ((ev = malloc(sizeof(*ev))) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to allocate memory for an event");
+ return (NULL);
+ }
+ memset(ev, 0, sizeof(*ev));
+ ev->ifp = ifp;
+ ev->state = state;
+ TAILQ_INIT(&ev->data_list);
+
+ return (ev);
+}
+
+void
+dhcp6_remove_event(ev)
+ struct dhcp6_event *ev;
+{
+ struct dhcp6_serverinfo *sp, *sp_next;
+
+ dprintf(LOG_DEBUG, FNAME, "removing an event on %s, state=%s",
+ ev->ifp->ifname, dhcp6_event_statestr(ev));
+
+ dhcp6_remove_evdata(ev);
+
+ duidfree(&ev->serverid);
+
+ if (ev->timer)
+ dhcp6_remove_timer(&ev->timer);
+ TAILQ_REMOVE(&ev->ifp->event_list, ev, link);
+
+ for (sp = ev->servers; sp; sp = sp_next) {
+ sp_next = sp->next;
+
+ dprintf(LOG_DEBUG, FNAME, "removing server (ID: %s)",
+ duidstr(&sp->optinfo.serverID));
+ dhcp6_clear_options(&sp->optinfo);
+ if (sp->authparam != NULL)
+ free(sp->authparam);
+ free(sp);
+ }
+
+ if (ev->authparam != NULL)
+ free(ev->authparam);
+
+ free(ev);
+}
+
+void
+dhcp6_remove_evdata(ev)
+ struct dhcp6_event *ev;
+{
+ struct dhcp6_eventdata *evd;
+
+ while ((evd = TAILQ_FIRST(&ev->data_list)) != NULL) {
+ TAILQ_REMOVE(&ev->data_list, evd, link);
+ if (evd->destructor)
+ (*evd->destructor)(evd);
+ free(evd);
+ }
+}
+
+struct authparam *
+new_authparam(proto, alg, rdm)
+ int proto, alg, rdm;
+{
+ struct authparam *authparam;
+
+ if ((authparam = malloc(sizeof(*authparam))) == NULL)
+ return (NULL);
+
+ memset(authparam, 0, sizeof(*authparam));
+
+ authparam->authproto = proto;
+ authparam->authalgorithm = alg;
+ authparam->authrdm = rdm;
+ authparam->key = NULL;
+ authparam->flags |= AUTHPARAM_FLAGS_NOPREVRD;
+ authparam->prevrd = 0;
+
+ return (authparam);
+}
+
+struct authparam *
+copy_authparam(authparam)
+ struct authparam *authparam;
+{
+ struct authparam *dst;
+
+ if ((dst = malloc(sizeof(*dst))) == NULL)
+ return (NULL);
+
+ memcpy(dst, authparam, sizeof(*dst));
+
+ return (dst);
+}
+
+/*
+ * Home-brew function of a 64-bit version of ntohl.
+ * XXX: is there any standard for this?
+ */
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static __inline u_int64_t
+ntohq(u_int64_t x)
+{
+ return (u_int64_t)ntohl((u_int32_t)(x >> 32)) |
+ (int64_t)ntohl((u_int32_t)(x & 0xffffffff)) << 32;
+}
+#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define ntohq(x) (x)
+#endif
+
+int
+dhcp6_auth_replaycheck(method, prev, current)
+ int method;
+ u_int64_t prev, current;
+{
+ char bufprev[] = "ffff ffff ffff ffff";
+ char bufcurrent[] = "ffff ffff ffff ffff";
+
+ if (method != DHCP6_AUTHRDM_MONOCOUNTER) {
+ dprintf(LOG_ERR, FNAME, "unsupported replay detection "
+ "method (%d)", method);
+ return (-1);
+ }
+
+ (void)sprint_uint64(bufprev, sizeof(bufprev), prev);
+ (void)sprint_uint64(bufcurrent, sizeof(bufcurrent), current);
+ dprintf(LOG_DEBUG, FNAME, "previous: %s, current: %s",
+ bufprev, bufcurrent);
+
+ prev = ntohq(prev);
+ current = ntohq(current);
+
+ /* we call the singular point guilty */
+ if (prev == (current ^ 8000000000000000ULL)) {
+ dprintf(LOG_INFO, FNAME, "detected a singular point");
+ return (1);
+ }
+
+ return (((int64_t)(current - prev) > 0) ? 0 : 1);
+}
+
+int
+getifaddr(addr, ifnam, prefix, plen, strong, ignoreflags)
+ struct in6_addr *addr;
+ char *ifnam;
+ struct in6_addr *prefix;
+ int plen;
+ int strong; /* if strong host model is required or not */
+ int ignoreflags;
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in6 sin6;
+ int error = -1;
+
+ if (getifaddrs(&ifap) != 0) {
+ dprintf(LOG_WARNING, FNAME,
+ "getifaddrs failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ int s1, s2;
+
+ if (strong && strcmp(ifnam, ifa->ifa_name) != 0)
+ continue;
+
+ /* in any case, ignore interfaces in different scope zones. */
+ if ((s1 = in6_addrscopebyif(prefix, ifnam)) < 0 ||
+ (s2 = in6_addrscopebyif(prefix, ifa->ifa_name)) < 0 ||
+ s1 != s2)
+ continue;
+
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+#ifndef __linux__
+ if (ifa->ifa_addr->sa_len > sizeof(sin6))
+ continue;
+#endif
+
+ if (in6_matchflags(ifa->ifa_addr, ifa->ifa_name, ignoreflags))
+ continue;
+
+ memcpy(&sin6, ifa->ifa_addr, sysdep_sa_len(ifa->ifa_addr));
+#ifdef __KAME__
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) {
+ sin6.sin6_addr.s6_addr[2] = 0;
+ sin6.sin6_addr.s6_addr[3] = 0;
+ }
+#endif
+ if (plen % 8 == 0) {
+ if (memcmp(&sin6.sin6_addr, prefix, plen / 8) != 0)
+ continue;
+ } else {
+ struct in6_addr a, m;
+ int i;
+
+ memcpy(&a, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
+ memset(&m, 0, sizeof(m));
+ memset(&m, 0xff, plen / 8);
+ m.s6_addr[plen / 8] = (0xff00 >> (plen % 8)) & 0xff;
+ for (i = 0; i < sizeof(a); i++)
+ a.s6_addr[i] &= m.s6_addr[i];
+
+ if (memcmp(&a, prefix, plen / 8) != 0 ||
+ a.s6_addr[plen / 8] !=
+ (prefix->s6_addr[plen / 8] & m.s6_addr[plen / 8]))
+ continue;
+ }
+ memcpy(addr, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
+#ifdef __KAME__
+ if (IN6_IS_ADDR_LINKLOCAL(addr))
+ addr->s6_addr[2] = addr->s6_addr[3] = 0;
+#endif
+ error = 0;
+ break;
+ }
+
+ freeifaddrs(ifap);
+ return (error);
+}
+
+int
+getifidfromaddr(addr, ifidp)
+ struct in6_addr *addr;
+ unsigned int *ifidp;
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in6 *sa6;
+ unsigned int ifid;
+ int retval = -1;
+
+ if (getifaddrs(&ifap) != 0) {
+ dprintf(LOG_WARNING, FNAME,
+ "getifaddrs failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+
+ sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+ if (IN6_ARE_ADDR_EQUAL(addr, &sa6->sin6_addr))
+ break;
+ }
+
+ if (ifa != NULL) {
+ if ((ifid = if_nametoindex(ifa->ifa_name)) == 0) {
+ dprintf(LOG_ERR, FNAME,
+ "if_nametoindex failed for %s", ifa->ifa_name);
+ goto end;
+ }
+ retval = 0;
+ *ifidp = ifid;
+ }
+
+ end:
+ freeifaddrs(ifap);
+ return (retval);
+}
+
+int
+in6_addrscopebyif(addr, ifnam)
+ struct in6_addr *addr;
+ char *ifnam;
+{
+ u_int ifindex;
+
+ if ((ifindex = if_nametoindex(ifnam)) == 0)
+ return (-1);
+
+ if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr))
+ return (ifindex);
+
+ if (IN6_IS_ADDR_SITELOCAL(addr) || IN6_IS_ADDR_MC_SITELOCAL(addr))
+ return (1); /* XXX */
+
+ if (IN6_IS_ADDR_MC_ORGLOCAL(addr))
+ return (1); /* XXX */
+
+ return (1); /* treat it as global */
+}
+
+int
+transmit_sa(s, sa, buf, len)
+ int s;
+ struct sockaddr *sa;
+ char *buf;
+ size_t len;
+{
+ int error;
+
+ error = sendto(s, buf, len, 0, sa, sysdep_sa_len(sa));
+
+ return (error != len) ? -1 : 0;
+}
+
+long
+random_between(x, y)
+ long x;
+ long y;
+{
+ long ratio;
+
+ ratio = 1 << 16;
+ while ((y - x) * ratio < (y - x))
+ ratio = ratio / 2;
+ return (x + ((y - x) * (ratio - 1) / random() & (ratio - 1)));
+}
+
+int
+prefix6_mask(in6, plen)
+ struct in6_addr *in6;
+ int plen;
+{
+ struct sockaddr_in6 mask6;
+ int i;
+
+ if (sa6_plen2mask(&mask6, plen))
+ return (-1);
+
+ for (i = 0; i < 16; i++)
+ in6->s6_addr[i] &= mask6.sin6_addr.s6_addr[i];
+
+ return (0);
+}
+
+int
+sa6_plen2mask(sa6, plen)
+ struct sockaddr_in6 *sa6;
+ int plen;
+{
+ u_char *cp;
+
+ if (plen < 0 || plen > 128)
+ return (-1);
+
+ memset(sa6, 0, sizeof(*sa6));
+ sa6->sin6_family = AF_INET6;
+#ifndef __linux__
+ sa6->sin6_len = sizeof(*sa6);
+#endif
+
+ for (cp = (u_char *)&sa6->sin6_addr; plen > 7; plen -= 8)
+ *cp++ = 0xff;
+ *cp = 0xff << (8 - plen);
+
+ return (0);
+}
+
+char *
+addr2str(sa)
+ struct sockaddr *sa;
+{
+ static char addrbuf[8][NI_MAXHOST];
+ static int round = 0;
+ char *cp;
+
+ round = (round + 1) & 7;
+ cp = addrbuf[round];
+
+ getnameinfo(sa, sysdep_sa_len(sa), cp, NI_MAXHOST,
+ NULL, 0, NI_NUMERICHOST);
+
+ return (cp);
+}
+
+char *
+in6addr2str(in6, scopeid)
+ struct in6_addr *in6;
+ int scopeid;
+{
+ struct sockaddr_in6 sa6;
+
+ memset(&sa6, 0, sizeof(sa6));
+ sa6.sin6_family = AF_INET6;
+#ifndef __linux__
+ sa6.sin6_len = sizeof(sa6);
+#endif
+ sa6.sin6_addr = *in6;
+ sa6.sin6_scope_id = scopeid;
+
+ return (addr2str((struct sockaddr *)&sa6));
+}
+
+/* return IPv6 address scope type. caller assumes that smaller is narrower. */
+int
+in6_scope(addr)
+ struct in6_addr *addr;
+{
+ int scope;
+
+ if (addr->s6_addr[0] == 0xfe) {
+ scope = addr->s6_addr[1] & 0xc0;
+
+ switch (scope) {
+ case 0x80:
+ return (2); /* link-local */
+ break;
+ case 0xc0:
+ return (5); /* site-local */
+ break;
+ default:
+ return (14); /* global: just in case */
+ break;
+ }
+ }
+
+ /* multicast scope. just return the scope field */
+ if (addr->s6_addr[0] == 0xff)
+ return (addr->s6_addr[1] & 0x0f);
+
+ if (bcmp(&in6addr_loopback, addr, sizeof(addr) - 1) == 0) {
+ if (addr->s6_addr[15] == 1) /* loopback */
+ return (1);
+ if (addr->s6_addr[15] == 0) /* unspecified */
+ return (0); /* XXX: good value? */
+ }
+
+ return (14); /* global */
+}
+
+static int
+in6_matchflags(addr, ifnam, flags)
+ struct sockaddr *addr;
+ char *ifnam;
+ int flags;
+{
+#ifdef __KAME__
+ int s;
+ struct in6_ifreq ifr6;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ warn("in6_matchflags: socket(DGRAM6)");
+ return (-1);
+ }
+ memset(&ifr6, 0, sizeof(ifr6));
+ strncpy(ifr6.ifr_name, ifnam, sizeof(ifr6.ifr_name));
+ ifr6.ifr_addr = *(struct sockaddr_in6 *)addr;
+
+ if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) < 0) {
+ warn("in6_matchflags: ioctl(SIOCGIFAFLAG_IN6, %s)",
+ addr2str(addr));
+ close(s);
+ return (-1);
+ }
+
+ close(s);
+
+ return (ifr6.ifr_ifru.ifru_flags6 & flags);
+#else
+ return (0);
+#endif
+}
+
+int
+get_duid(idfile, duid)
+ char *idfile;
+ struct duid *duid;
+{
+ FILE *fp = NULL;
+ u_int16_t len = 0, hwtype;
+ struct dhcp6opt_duid_type1 *dp; /* we only support the type1 DUID */
+ char tmpbuf[256]; /* DUID should be no more than 256 bytes */
+
+ if ((fp = fopen(idfile, "r")) == NULL && errno != ENOENT)
+ dprintf(LOG_NOTICE, FNAME, "failed to open DUID file: %s",
+ idfile);
+
+ if (fp) {
+ /* decode length */
+ if (fread(&len, sizeof(len), 1, fp) != 1) {
+ dprintf(LOG_ERR, FNAME, "DUID file corrupted");
+ goto fail;
+ }
+ } else {
+ int l;
+
+ if ((l = gethwid(tmpbuf, sizeof(tmpbuf), NULL, &hwtype)) < 0) {
+ dprintf(LOG_INFO, FNAME,
+ "failed to get a hardware address");
+ goto fail;
+ }
+ len = l + sizeof(struct dhcp6opt_duid_type1);
+ }
+
+ memset(duid, 0, sizeof(*duid));
+ duid->duid_len = len;
+ if ((duid->duid_id = (char *)malloc(len)) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to allocate memory");
+ goto fail;
+ }
+
+ /* copy (and fill) the ID */
+ if (fp) {
+ if (fread(duid->duid_id, len, 1, fp) != 1) {
+ dprintf(LOG_ERR, FNAME, "DUID file corrupted");
+ goto fail;
+ }
+
+ dprintf(LOG_DEBUG, FNAME,
+ "extracted an existing DUID from %s: %s",
+ idfile, duidstr(duid));
+ } else {
+ u_int64_t t64;
+
+ dp = (struct dhcp6opt_duid_type1 *)duid->duid_id;
+ dp->dh6_duid1_type = htons(1); /* type 1 */
+ dp->dh6_duid1_hwtype = htons(hwtype);
+ /* time is Jan 1, 2000 (UTC), modulo 2^32 */
+ t64 = (u_int64_t)(time(NULL) - 946684800);
+ dp->dh6_duid1_time = htonl((u_long)(t64 & 0xffffffff));
+ memcpy((void *)(dp + 1), tmpbuf, (len - sizeof(*dp)));
+
+ dprintf(LOG_DEBUG, FNAME, "generated a new DUID: %s",
+ duidstr(duid));
+ }
+
+ /* save the (new) ID to the file for next time */
+ if (!fp) {
+ if ((fp = fopen(idfile, "w+")) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to open DUID file for save");
+ goto fail;
+ }
+ if ((fwrite(&len, sizeof(len), 1, fp)) != 1) {
+ dprintf(LOG_ERR, FNAME, "failed to save DUID");
+ goto fail;
+ }
+ if ((fwrite(duid->duid_id, len, 1, fp)) != 1) {
+ dprintf(LOG_ERR, FNAME, "failed to save DUID");
+ goto fail;
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "saved generated DUID to %s",
+ idfile);
+ }
+
+ if (fp)
+ fclose(fp);
+ return (0);
+
+ fail:
+ if (fp)
+ fclose(fp);
+ if (duid->duid_id) {
+ free(duid->duid_id);
+ duid->duid_id = NULL; /* for safety */
+ }
+ return (-1);
+}
+
+static ssize_t
+gethwid(buf, len, ifname, hwtypep)
+ char *buf;
+ int len;
+ const char *ifname;
+ u_int16_t *hwtypep;
+{
+ struct ifaddrs *ifa, *ifap;
+#ifdef __KAME__
+ struct sockaddr_dl *sdl;
+#endif
+#ifdef __linux__
+ struct sockaddr_ll *sll;
+#endif
+ ssize_t l;
+
+ if (getifaddrs(&ifap) < 0)
+ return (-1);
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifname && strcmp(ifa->ifa_name, ifname) != 0)
+ continue;
+ if (ifa->ifa_addr == NULL)
+ continue;
+#ifdef __KAME__
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ if (len < 2 + sdl->sdl_alen)
+ goto fail;
+ /*
+ * translate interface type to hardware type based on
+ * http://www.iana.org/assignments/arp-parameters
+ */
+ switch(sdl->sdl_type) {
+ case IFT_ETHER:
+#ifdef IFT_IEEE80211
+ case IFT_IEEE80211:
+#endif
+ *hwtypep = ARPHRD_ETHER;
+ break;
+ default:
+ continue; /* XXX */
+ }
+ dprintf(LOG_DEBUG, FNAME, "found an interface %s for DUID",
+ ifa->ifa_name);
+ memcpy(buf, LLADDR(sdl), sdl->sdl_alen);
+ l = sdl->sdl_alen; /* sdl will soon be freed */
+#endif
+#ifdef __linux__
+ if (ifa->ifa_addr->sa_family != AF_PACKET)
+ continue;
+
+ sll = (struct sockaddr_ll *)ifa->ifa_addr;
+ if (sll->sll_hatype != ARPHRD_ETHER)
+ continue;
+ *hwtypep = ARPHRD_ETHER;
+ dprintf(LOG_DEBUG, FNAME, "found an interface %s for DUID",
+ ifa->ifa_name);
+ memcpy(buf, sll->sll_addr, sll->sll_halen);
+ l = sll->sll_halen; /* sll will soon be freed */
+#endif
+ freeifaddrs(ifap);
+ return (l);
+ }
+
+ fail:
+ freeifaddrs(ifap);
+ return (-1);
+}
+
+void
+dhcp6_init_options(optinfo)
+ struct dhcp6_optinfo *optinfo;
+{
+ memset(optinfo, 0, sizeof(*optinfo));
+
+ optinfo->pref = DH6OPT_PREF_UNDEF;
+ optinfo->elapsed_time = DH6OPT_ELAPSED_TIME_UNDEF;
+ optinfo->refreshtime = DH6OPT_REFRESHTIME_UNDEF;
+
+ TAILQ_INIT(&optinfo->iapd_list);
+ TAILQ_INIT(&optinfo->iana_list);
+ TAILQ_INIT(&optinfo->reqopt_list);
+ TAILQ_INIT(&optinfo->stcode_list);
+ TAILQ_INIT(&optinfo->sip_list);
+ TAILQ_INIT(&optinfo->sipname_list);
+ TAILQ_INIT(&optinfo->dns_list);
+ TAILQ_INIT(&optinfo->dnsname_list);
+ TAILQ_INIT(&optinfo->ntp_list);
+ TAILQ_INIT(&optinfo->prefix_list);
+
+ optinfo->authproto = DHCP6_AUTHPROTO_UNDEF;
+ optinfo->authalgorithm = DHCP6_AUTHALG_UNDEF;
+ optinfo->authrdm = DHCP6_AUTHRDM_UNDEF;
+}
+
+void
+dhcp6_clear_options(optinfo)
+ struct dhcp6_optinfo *optinfo;
+{
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ if (optinfo->delayedauth_realmval != NULL) {
+ free(optinfo->delayedauth_realmval);
+ }
+ break;
+ }
+
+ duidfree(&optinfo->clientID);
+ duidfree(&optinfo->serverID);
+
+ dhcp6_clear_list(&optinfo->iapd_list);
+ dhcp6_clear_list(&optinfo->iana_list);
+ dhcp6_clear_list(&optinfo->reqopt_list);
+ dhcp6_clear_list(&optinfo->stcode_list);
+ dhcp6_clear_list(&optinfo->sip_list);
+ dhcp6_clear_list(&optinfo->sipname_list);
+ dhcp6_clear_list(&optinfo->dns_list);
+ dhcp6_clear_list(&optinfo->dnsname_list);
+ dhcp6_clear_list(&optinfo->ntp_list);
+ dhcp6_clear_list(&optinfo->prefix_list);
+
+ if (optinfo->relaymsg_msg != NULL)
+ free(optinfo->relaymsg_msg);
+
+ if (optinfo->ifidopt_id != NULL)
+ free(optinfo->ifidopt_id);
+
+ dhcp6_init_options(optinfo);
+}
+
+int
+dhcp6_copy_options(dst, src)
+ struct dhcp6_optinfo *dst, *src;
+{
+ if (duidcpy(&dst->clientID, &src->clientID))
+ goto fail;
+ if (duidcpy(&dst->serverID, &src->serverID))
+ goto fail;
+ dst->rapidcommit = src->rapidcommit;
+
+ if (dhcp6_copy_list(&dst->iapd_list, &src->iapd_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->iana_list, &src->iana_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->reqopt_list, &src->reqopt_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->stcode_list, &src->stcode_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->sip_list, &src->sip_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->sipname_list, &src->sipname_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->dns_list, &src->dns_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->dnsname_list, &src->dnsname_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->ntp_list, &src->ntp_list))
+ goto fail;
+ if (dhcp6_copy_list(&dst->prefix_list, &src->prefix_list))
+ goto fail;
+ dst->elapsed_time = src->elapsed_time;
+ dst->refreshtime = src->refreshtime;
+ dst->pref = src->pref;
+
+ if (src->relaymsg_msg != NULL) {
+ if ((dst->relaymsg_msg = malloc(src->relaymsg_len)) == NULL)
+ goto fail;
+ dst->relaymsg_len = src->relaymsg_len;
+ memcpy(dst->relaymsg_msg, src->relaymsg_msg,
+ src->relaymsg_len);
+ }
+
+ if (src->ifidopt_id != NULL) {
+ if ((dst->ifidopt_id = malloc(src->ifidopt_len)) == NULL)
+ goto fail;
+ dst->ifidopt_len = src->ifidopt_len;
+ memcpy(dst->ifidopt_id, src->ifidopt_id, src->ifidopt_len);
+ }
+
+ dst->authflags = src->authflags;
+ dst->authproto = src->authproto;
+ dst->authalgorithm = src->authalgorithm;
+ dst->authrdm = src->authrdm;
+ dst->authrd = src->authrd;
+
+ switch (src->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ dst->delayedauth_keyid = src->delayedauth_keyid;
+ dst->delayedauth_offset = src->delayedauth_offset;
+ dst->delayedauth_realmlen = src->delayedauth_realmlen;
+ if (src->delayedauth_realmval != NULL) {
+ if ((dst->delayedauth_realmval =
+ malloc(src->delayedauth_realmlen)) == NULL) {
+ goto fail;
+ }
+ memcpy(dst->delayedauth_realmval,
+ src->delayedauth_realmval,
+ src->delayedauth_realmlen);
+ }
+ break;
+ case DHCP6_AUTHPROTO_RECONFIG:
+ dst->reconfigauth_type = src->reconfigauth_type;
+ dst->reconfigauth_offset = src->reconfigauth_offset;
+ memcpy(dst->reconfigauth_val, src->reconfigauth_val,
+ sizeof(dst->reconfigauth_val));
+ break;
+ }
+
+ return (0);
+
+ fail:
+ /* cleanup temporary resources */
+ dhcp6_clear_options(dst);
+ return (-1);
+}
+
+int
+dhcp6_get_options(p, ep, optinfo)
+ struct dhcp6opt *p, *ep;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct dhcp6opt *np, opth;
+ int i, opt, optlen, reqopts;
+ u_int16_t num;
+ char *bp, *cp, *val;
+ u_int16_t val16;
+ u_int32_t val32;
+ struct in6_addr valaddr;
+ struct dhcp6opt_ia optia;
+ struct dhcp6_ia ia;
+ struct dhcp6_list sublist;
+ int authinfolen;
+
+ bp = (char *)p;
+ for (; p + 1 <= ep; p = np) {
+ struct duid duid0;
+
+ /*
+ * get the option header. XXX: since there is no guarantee
+ * about the header alignment, we need to make a local copy.
+ */
+ memcpy(&opth, p, sizeof(opth));
+ optlen = ntohs(opth.dh6opt_len);
+ opt = ntohs(opth.dh6opt_type);
+
+ cp = (char *)(p + 1);
+ np = (struct dhcp6opt *)(cp + optlen);
+
+ dprintf(LOG_DEBUG, FNAME, "get DHCP option %s, len %d",
+ dhcp6optstr(opt), optlen);
+
+ /* option length field overrun */
+ if (np > ep) {
+ dprintf(LOG_INFO, FNAME, "malformed DHCP options");
+ return (-1);
+ }
+
+ switch (opt) {
+ case DH6OPT_CLIENTID:
+ if (optlen == 0)
+ goto malformed;
+ duid0.duid_len = optlen;
+ duid0.duid_id = cp;
+ dprintf(LOG_DEBUG, "",
+ " DUID: %s", duidstr(&duid0));
+ if (duidcpy(&optinfo->clientID, &duid0)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy DUID");
+ goto fail;
+ }
+ break;
+ case DH6OPT_SERVERID:
+ if (optlen == 0)
+ goto malformed;
+ duid0.duid_len = optlen;
+ duid0.duid_id = cp;
+ dprintf(LOG_DEBUG, "", " DUID: %s", duidstr(&duid0));
+ if (duidcpy(&optinfo->serverID, &duid0)) {
+ dprintf(LOG_ERR, FNAME, "failed to copy DUID");
+ goto fail;
+ }
+ break;
+ case DH6OPT_STATUS_CODE:
+ if (optlen < sizeof(u_int16_t))
+ goto malformed;
+ memcpy(&val16, cp, sizeof(val16));
+ num = ntohs(val16);
+ dprintf(LOG_DEBUG, "", " status code: %s",
+ dhcp6_stcodestr(num));
+
+ /* need to check duplication? */
+
+ if (dhcp6_add_listval(&optinfo->stcode_list,
+ DHCP6_LISTVAL_STCODE, &num, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to copy "
+ "status code");
+ goto fail;
+ }
+
+ break;
+ case DH6OPT_ORO:
+ if ((optlen % 2) != 0 || optlen == 0)
+ goto malformed;
+ reqopts = optlen / 2;
+ for (i = 0, val = cp; i < reqopts;
+ i++, val += sizeof(u_int16_t)) {
+ u_int16_t opttype;
+
+ memcpy(&opttype, val, sizeof(u_int16_t));
+ num = ntohs(opttype);
+
+ dprintf(LOG_DEBUG, "",
+ " requested option: %s",
+ dhcp6optstr(num));
+
+ if (dhcp6_find_listval(&optinfo->reqopt_list,
+ DHCP6_LISTVAL_NUM, &num, 0)) {
+ dprintf(LOG_INFO, FNAME, "duplicated "
+ "option type (%s)",
+ dhcp6optstr(opttype));
+ goto nextoption;
+ }
+
+ if (dhcp6_add_listval(&optinfo->reqopt_list,
+ DHCP6_LISTVAL_NUM, &num, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to copy requested option");
+ goto fail;
+ }
+ nextoption:
+ ;
+ }
+ break;
+ case DH6OPT_PREFERENCE:
+ if (optlen != 1)
+ goto malformed;
+ dprintf(LOG_DEBUG, "", " preference: %d",
+ (int)*(u_char *)cp);
+ if (optinfo->pref != DH6OPT_PREF_UNDEF) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated preference option");
+ } else
+ optinfo->pref = (int)*(u_char *)cp;
+ break;
+ case DH6OPT_ELAPSED_TIME:
+ if (optlen != 2)
+ goto malformed;
+ memcpy(&val16, cp, sizeof(val16));
+ val16 = ntohs(val16);
+ dprintf(LOG_DEBUG, "", " elapsed time: %lu",
+ (u_int32_t)val16);
+ if (optinfo->elapsed_time !=
+ DH6OPT_ELAPSED_TIME_UNDEF) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated elapsed time option");
+ } else
+ optinfo->elapsed_time = val16;
+ break;
+ case DH6OPT_RELAY_MSG:
+ if ((optinfo->relaymsg_msg = malloc(optlen)) == NULL)
+ goto fail;
+ memcpy(optinfo->relaymsg_msg, cp, optlen);
+ optinfo->relaymsg_len = optlen;
+ break;
+ case DH6OPT_AUTH:
+ if (optlen < sizeof(struct dhcp6opt_auth) - 4)
+ goto malformed;
+
+ /*
+ * Any DHCP message that includes more than one
+ * authentication option MUST be discarded.
+ * [RFC3315 Section 21.4.2]
+ */
+ if (optinfo->authproto != DHCP6_AUTHPROTO_UNDEF) {
+ dprintf(LOG_INFO, FNAME, "found more than one "
+ "authentication option");
+ goto fail;
+ }
+
+ optinfo->authproto = *cp++;
+ optinfo->authalgorithm = *cp++;
+ optinfo->authrdm = *cp++;
+ memcpy(&optinfo->authrd, cp, sizeof(optinfo->authrd));
+ cp += sizeof(optinfo->authrd);
+
+ dprintf(LOG_DEBUG, "", " %s", sprint_auth(optinfo));
+
+ authinfolen =
+ optlen - (sizeof(struct dhcp6opt_auth) - 4);
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ if (authinfolen == 0) {
+ optinfo->authflags |=
+ DHCP6OPT_AUTHFLAG_NOINFO;
+ break;
+ }
+ /* XXX: should we reject an empty realm? */
+ if (authinfolen <
+ sizeof(optinfo->delayedauth_keyid) + 16) {
+ goto malformed;
+ }
+
+ optinfo->delayedauth_realmlen = authinfolen -
+ (sizeof(optinfo->delayedauth_keyid) + 16);
+ optinfo->delayedauth_realmval =
+ malloc(optinfo->delayedauth_realmlen);
+ if (optinfo->delayedauth_realmval == NULL) {
+ dprintf(LOG_WARNING, FNAME, "failed "
+ "allocate memory for auth realm");
+ goto fail;
+ }
+ memcpy(optinfo->delayedauth_realmval, cp,
+ optinfo->delayedauth_realmlen);
+ cp += optinfo->delayedauth_realmlen;
+
+ memcpy(&optinfo->delayedauth_keyid, cp,
+ sizeof(optinfo->delayedauth_keyid));
+ optinfo->delayedauth_keyid =
+ ntohl(optinfo->delayedauth_keyid);
+ cp += sizeof(optinfo->delayedauth_keyid);
+
+ optinfo->delayedauth_offset = cp - bp;
+ cp += 16;
+
+ dprintf(LOG_DEBUG, "", " auth key ID: %x, "
+ "offset=%d, realmlen=%d",
+ optinfo->delayedauth_keyid,
+ optinfo->delayedauth_offset,
+ optinfo->delayedauth_realmlen);
+ break;
+#ifdef notyet
+ case DHCP6_AUTHPROTO_RECONFIG:
+ break;
+#endif
+ default:
+ dprintf(LOG_INFO, FNAME,
+ "unsupported authentication protocol: %d",
+ *cp);
+ goto fail;
+ }
+ break;
+ case DH6OPT_RAPID_COMMIT:
+ if (optlen != 0)
+ goto malformed;
+ optinfo->rapidcommit = 1;
+ break;
+ case DH6OPT_INTERFACE_ID:
+ if ((optinfo->ifidopt_id = malloc(optlen)) == NULL)
+ goto fail;
+ memcpy(optinfo->ifidopt_id, cp, optlen);
+ optinfo->ifidopt_len = optlen;
+ break;
+ case DH6OPT_SIP_SERVER_D:
+ val = cp;
+ while (val < cp + optlen) {
+ struct dhcp6_vbuf vb;
+ char name[MAXDNAME + 1];
+
+ if (dnsdecode((u_char **)(void *)&val,
+ (u_char *)(cp + optlen), name,
+ sizeof(name)) == NULL) {
+ dprintf(LOG_INFO, FNAME, "failed to "
+ "decode a SIP domain name");
+ goto malformed; /* or proceed? */
+ }
+
+ vb.dv_len = strlen(name) + 1;
+ vb.dv_buf = name;
+
+ if (dhcp6_add_listval(&optinfo->sipname_list,
+ DHCP6_LISTVAL_VBUF, &vb, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to "
+ "copy a SIP domain name");
+ goto fail;
+ }
+ }
+ break;
+ case DH6OPT_SIP_SERVER_A:
+ if (optlen % sizeof(struct in6_addr) || optlen == 0)
+ goto malformed;
+ for (val = cp; val < cp + optlen;
+ val += sizeof(struct in6_addr)) {
+ memcpy(&valaddr, val, sizeof(valaddr));
+ if (dhcp6_find_listval(&optinfo->sip_list,
+ DHCP6_LISTVAL_ADDR6, &valaddr, 0)) {
+ dprintf(LOG_INFO, FNAME, "duplicated "
+ "SIP server address (%s)",
+ in6addr2str(&valaddr, 0));
+ goto nextsip;
+ }
+
+ if (dhcp6_add_listval(&optinfo->sip_list,
+ DHCP6_LISTVAL_ADDR6, &valaddr, NULL)
+ == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to copy "
+ "SIP server address");
+ goto fail;
+ }
+ nextsip:
+ ;
+ }
+ break;
+ case DH6OPT_DNS:
+ if (optlen % sizeof(struct in6_addr) || optlen == 0)
+ goto malformed;
+ for (val = cp; val < cp + optlen;
+ val += sizeof(struct in6_addr)) {
+ memcpy(&valaddr, val, sizeof(valaddr));
+ if (dhcp6_find_listval(&optinfo->dns_list,
+ DHCP6_LISTVAL_ADDR6, &valaddr, 0)) {
+ dprintf(LOG_INFO, FNAME, "duplicated "
+ "DNS address (%s)",
+ in6addr2str(&valaddr, 0));
+ goto nextdns;
+ }
+
+ if (dhcp6_add_listval(&optinfo->dns_list,
+ DHCP6_LISTVAL_ADDR6, &valaddr, NULL)
+ == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to copy DNS address");
+ goto fail;
+ }
+ nextdns:
+ ;
+ }
+ break;
+ case DH6OPT_DNSNAME:
+ val = cp;
+ while (val < cp + optlen) {
+ struct dhcp6_vbuf vb;
+ char name[MAXDNAME + 1];
+
+ if (dnsdecode((u_char **)(void *)&val,
+ (u_char *)(cp + optlen), name,
+ sizeof(name)) == NULL) {
+ dprintf(LOG_INFO, FNAME, "failed to "
+ "decode a DNS name");
+ goto malformed; /* or proceed? */
+ }
+
+ vb.dv_len = strlen(name) + 1;
+ vb.dv_buf = name;
+
+ if (dhcp6_add_listval(&optinfo->dnsname_list,
+ DHCP6_LISTVAL_VBUF, &vb, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to "
+ "copy a DNS name");
+ goto fail;
+ }
+ }
+ break;
+#ifdef USE_DH6OPT_NTP
+ case DH6OPT_NTP:
+ if (optlen % sizeof(struct in6_addr) || optlen == 0)
+ goto malformed;
+ for (val = cp; val < cp + optlen;
+ val += sizeof(struct in6_addr)) {
+ memcpy(&valaddr, val, sizeof(valaddr));
+ if (dhcp6_find_listval(&optinfo->ntp_list,
+ DHCP6_LISTVAL_ADDR6, &valaddr, 0)) {
+ dprintf(LOG_INFO, FNAME, "duplicated "
+ "NTP server address (%s)",
+ in6addr2str(&valaddr, 0));
+ goto nextntp;
+ }
+
+ if (dhcp6_add_listval(&optinfo->ntp_list,
+ DHCP6_LISTVAL_ADDR6, &valaddr, NULL)
+ == NULL) {
+ dprintf(LOG_ERR, FNAME, "failed to "
+ "copy NTP server address");
+ goto fail;
+ }
+ nextntp:
+ ;
+ }
+ break;
+#endif
+ case DH6OPT_IA_PD:
+ if (optlen + sizeof(struct dhcp6opt) <
+ sizeof(optia))
+ goto malformed;
+ memcpy(&optia, p, sizeof(optia));
+ ia.iaid = ntohl(optia.dh6_ia_iaid);
+ ia.t1 = ntohl(optia.dh6_ia_t1);
+ ia.t2 = ntohl(optia.dh6_ia_t2);
+
+ dprintf(LOG_DEBUG, "",
+ " IA_PD: ID=%lu, T1=%lu, T2=%lu",
+ ia.iaid, ia.t1, ia.t2);
+
+ /* duplication check */
+ if (dhcp6_find_listval(&optinfo->iapd_list,
+ DHCP6_LISTVAL_IAPD, &ia, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated IA_PD %lu", ia.iaid);
+ break; /* ignore this IA_PD */
+ }
+
+ /* take care of sub-options */
+ TAILQ_INIT(&sublist);
+ if (copyin_option(opt,
+ (struct dhcp6opt *)((char *)p + sizeof(optia)),
+ (struct dhcp6opt *)(cp + optlen), &sublist)) {
+ goto fail;
+ }
+
+ /* link this option set */
+ if (dhcp6_add_listval(&optinfo->iapd_list,
+ DHCP6_LISTVAL_IAPD, &ia, &sublist) == NULL) {
+ dhcp6_clear_list(&sublist);
+ goto fail;
+ }
+ dhcp6_clear_list(&sublist);
+
+ break;
+ case DH6OPT_PREFIX_DELEGATION:
+ if (get_delegated_prefixes(cp, cp + optlen, optinfo))
+ goto fail;
+ break;
+#ifdef USE_DH6OPT_REFRESHTIME
+ case DH6OPT_REFRESHTIME:
+ if (optlen != 4)
+ goto malformed;
+ memcpy(&val32, cp, sizeof(val32));
+ val32 = ntohl(val32);
+ dprintf(LOG_DEBUG, "",
+ " information refresh time: %lu", val32);
+ if (val32 < DHCP6_IRT_MINIMUM) {
+ /*
+ * A client MUST use the refresh time
+ * IRT_MINIMUM if it receives the option with a
+ * value less than IRT_MINIMUM.
+ * [draft-ietf-dhc-lifetime-02.txt,
+ * Section 3.2]
+ */
+ dprintf(LOG_INFO, FNAME,
+ "refresh time is too small (%d), adjusted",
+ val32);
+ val32 = DHCP6_IRT_MINIMUM;
+ }
+ if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated refresh time option");
+ } else
+ optinfo->refreshtime = (int64_t)val32;
+ break;
+#else
+ val32 = val32; /* XXX deceive compiler */
+#endif
+ case DH6OPT_IA_NA:
+ if (optlen + sizeof(struct dhcp6opt) <
+ sizeof(optia))
+ goto malformed;
+ memcpy(&optia, p, sizeof(optia));
+ ia.iaid = ntohl(optia.dh6_ia_iaid);
+ ia.t1 = ntohl(optia.dh6_ia_t1);
+ ia.t2 = ntohl(optia.dh6_ia_t2);
+
+ dprintf(LOG_DEBUG, "",
+ " IA_NA: ID=%lu, T1=%lu, T2=%lu",
+ ia.iaid, ia.t1, ia.t2);
+
+ /* duplication check */
+ if (dhcp6_find_listval(&optinfo->iapd_list,
+ DHCP6_LISTVAL_IANA, &ia, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated IA_NA %lu", ia.iaid);
+ break; /* ignore this IA_NA */
+ }
+
+ /* take care of sub-options */
+ TAILQ_INIT(&sublist);
+ if (copyin_option(opt,
+ (struct dhcp6opt *)((char *)p + sizeof(optia)),
+ (struct dhcp6opt *)(cp + optlen), &sublist)) {
+ goto fail;
+ }
+
+ /* link this option set */
+ if (dhcp6_add_listval(&optinfo->iana_list,
+ DHCP6_LISTVAL_IANA, &ia, &sublist) == NULL) {
+ dhcp6_clear_list(&sublist);
+ goto fail;
+ }
+ dhcp6_clear_list(&sublist);
+
+ break;
+ default:
+ /* no option specific behavior */
+ dprintf(LOG_INFO, FNAME,
+ "unknown or unexpected DHCP6 option %s, len %d",
+ dhcp6optstr(opt), optlen);
+ break;
+ }
+ }
+
+ return (0);
+
+ malformed:
+ dprintf(LOG_INFO, FNAME, "malformed DHCP option: type %d, len %d",
+ opt, optlen);
+ fail:
+ dhcp6_clear_options(optinfo);
+ return (-1);
+}
+
+static char *
+dnsdecode(sp, ep, buf, bufsiz)
+ u_char **sp;
+ u_char *ep;
+ char *buf;
+ size_t bufsiz;
+{
+ int i, l;
+ u_char *cp;
+ char tmpbuf[MAXDNAME + 1];
+
+ cp = *sp;
+ *buf = '\0';
+ i = 0; /* XXX: appease gcc */
+
+ if (cp >= ep)
+ return (NULL);
+ while (cp < ep) {
+ i = *cp;
+ if (i == 0 || cp != *sp) {
+ if (strlcat((char *)buf, ".", bufsiz) >= bufsiz)
+ return (NULL); /* result overrun */
+ }
+ if (i == 0)
+ break;
+ cp++;
+
+ if (i > 0x3f)
+ return (NULL); /* invalid label */
+
+ if (i > ep - cp)
+ return (NULL); /* source overrun */
+ while (i-- > 0 && cp < ep) {
+ if (!isprint(*cp)) /* we don't accept non-printables */
+ return (NULL);
+ l = snprintf(tmpbuf, sizeof(tmpbuf), "%c" , *cp);
+ if (l >= sizeof(tmpbuf) || l < 0)
+ return (NULL);
+ if (strlcat(buf, tmpbuf, bufsiz) >= bufsiz)
+ return (NULL); /* result overrun */
+ cp++;
+ }
+ }
+ if (i != 0)
+ return (NULL); /* not terminated */
+ cp++;
+ *sp = cp;
+ return (buf);
+}
+
+static int
+copyin_option(type, p, ep, list)
+ int type;
+ struct dhcp6opt *p, *ep;
+ struct dhcp6_list *list;
+{
+ int opt, optlen;
+ char *cp;
+ struct dhcp6opt *np, opth;
+ struct dhcp6opt_stcode opt_stcode;
+ struct dhcp6opt_ia_pd_prefix opt_iapd_prefix;
+ struct dhcp6_prefix iapd_prefix;
+ struct dhcp6opt_ia_addr opt_ia_addr;
+ struct dhcp6_prefix ia_addr;
+ struct dhcp6_list sublist;
+
+ TAILQ_INIT(&sublist);
+
+ for (; p + 1 <= ep; p = np) {
+ memcpy(&opth, p, sizeof(opth));
+ optlen = ntohs(opth.dh6opt_len);
+ opt = ntohs(opth.dh6opt_type);
+
+ cp = (char *)(p + 1);
+ np = (struct dhcp6opt *)(cp + optlen);
+
+ dprintf(LOG_DEBUG, FNAME, "get DHCP option %s, len %d",
+ dhcp6optstr(opt), optlen);
+
+ if (np > ep) {
+ dprintf(LOG_INFO, FNAME, "malformed DHCP option");
+ goto fail;
+ }
+
+ switch (opt) {
+ case DH6OPT_IA_PD_PREFIX:
+ /* check option context */
+ if (type != DH6OPT_IA_PD) {
+ dprintf(LOG_INFO, FNAME,
+ "%s is an invalid position for %s",
+ dhcp6optstr(type), dhcp6optstr(opt));
+ goto fail;
+ }
+ /* check option length */
+ if (optlen + sizeof(opth) < sizeof(opt_iapd_prefix))
+ goto malformed;
+
+ /* copy and convert option values */
+ memcpy(&opt_iapd_prefix, p, sizeof(opt_iapd_prefix));
+ if (opt_iapd_prefix.dh6_iapd_prefix_prefix_len > 128) {
+ dprintf(LOG_INFO, FNAME,
+ "invalid prefix length (%d)",
+ opt_iapd_prefix.dh6_iapd_prefix_prefix_len);
+ goto malformed;
+ }
+ iapd_prefix.pltime = ntohl(opt_iapd_prefix.dh6_iapd_prefix_preferred_time);
+ iapd_prefix.vltime = ntohl(opt_iapd_prefix.dh6_iapd_prefix_valid_time);
+ iapd_prefix.plen =
+ opt_iapd_prefix.dh6_iapd_prefix_prefix_len;
+ memcpy(&iapd_prefix.addr,
+ &opt_iapd_prefix.dh6_iapd_prefix_prefix_addr,
+ sizeof(iapd_prefix.addr));
+ /* clear padding bits in the prefix address */
+ prefix6_mask(&iapd_prefix.addr, iapd_prefix.plen);
+
+ dprintf(LOG_DEBUG, FNAME, " IA_PD prefix: "
+ "%s/%d pltime=%lu vltime=%lu",
+ in6addr2str(&iapd_prefix.addr, 0),
+ iapd_prefix.plen,
+ iapd_prefix.pltime, iapd_prefix.vltime);
+
+ if (dhcp6_find_listval(list, DHCP6_LISTVAL_PREFIX6,
+ &iapd_prefix, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated IA_PD prefix "
+ "%s/%d pltime=%lu vltime=%lu",
+ in6addr2str(&iapd_prefix.addr, 0),
+ iapd_prefix.plen,
+ iapd_prefix.pltime, iapd_prefix.vltime);
+ goto nextoption;
+ }
+
+ /* take care of sub-options */
+ TAILQ_INIT(&sublist);
+ if (copyin_option(opt,
+ (struct dhcp6opt *)((char *)p +
+ sizeof(opt_iapd_prefix)), np, &sublist)) {
+ goto fail;
+ }
+
+ if (dhcp6_add_listval(list, DHCP6_LISTVAL_PREFIX6,
+ &iapd_prefix, &sublist) == NULL) {
+ dhcp6_clear_list(&sublist);
+ goto fail;
+ }
+ dhcp6_clear_list(&sublist);
+ break;
+ case DH6OPT_IAADDR:
+ /* check option context */
+ if (type != DH6OPT_IA_NA) {
+ dprintf(LOG_INFO, FNAME,
+ "%s is an invalid position for %s",
+ dhcp6optstr(type), dhcp6optstr(opt));
+ goto fail;
+ }
+ /* check option length */
+ if (optlen + sizeof(opth) < sizeof(opt_ia_addr))
+ goto malformed;
+
+ /* copy and convert option values */
+ memcpy(&opt_ia_addr, p, sizeof(opt_ia_addr));
+ ia_addr.pltime = ntohl(opt_ia_addr.dh6_ia_addr_preferred_time);
+ ia_addr.vltime = ntohl(opt_ia_addr.dh6_ia_addr_valid_time);
+ memcpy(&ia_addr.addr, &opt_ia_addr.dh6_ia_addr_addr,
+ sizeof(ia_addr.addr));
+
+ dprintf(LOG_DEBUG, FNAME, " IA_NA address: "
+ "%s pltime=%lu vltime=%lu",
+ in6addr2str(&ia_addr.addr, 0),
+ ia_addr.pltime, ia_addr.vltime);
+
+ if (dhcp6_find_listval(list,
+ DHCP6_LISTVAL_STATEFULADDR6, &ia_addr, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated IA_NA address"
+ "%s pltime=%lu vltime=%lu",
+ in6addr2str(&ia_addr.addr, 0),
+ ia_addr.pltime, ia_addr.vltime);
+ goto nextoption;
+ }
+
+ /* take care of sub-options */
+ TAILQ_INIT(&sublist);
+ if (copyin_option(opt,
+ (struct dhcp6opt *)((char *)p +
+ sizeof(opt_ia_addr)), np, &sublist)) {
+ goto fail;
+ }
+
+ if (dhcp6_add_listval(list, DHCP6_LISTVAL_STATEFULADDR6,
+ &ia_addr, &sublist) == NULL) {
+ dhcp6_clear_list(&sublist);
+ goto fail;
+ }
+ dhcp6_clear_list(&sublist);
+ break;
+ case DH6OPT_STATUS_CODE:
+ /* check option context */
+ if (type != DH6OPT_IA_PD &&
+ type != DH6OPT_IA_PD_PREFIX &&
+ type != DH6OPT_IA_NA &&
+ type != DH6OPT_IAADDR) {
+ dprintf(LOG_INFO, FNAME,
+ "%s is an invalid position for %s",
+ dhcp6optstr(type), dhcp6optstr(opt));
+ goto nextoption; /* or discard the message? */
+ }
+ /* check option length */
+ if (optlen + sizeof(opth) < sizeof(opt_stcode))
+ goto malformed;
+
+ /* copy and convert option values */
+ memcpy(&opt_stcode, p, sizeof(opt_stcode));
+ opt_stcode.dh6_stcode_code =
+ ntohs(opt_stcode.dh6_stcode_code);
+
+ dprintf(LOG_DEBUG, "", " status code: %s",
+ dhcp6_stcodestr(opt_stcode.dh6_stcode_code));
+
+ /* duplication check */
+ if (dhcp6_find_listval(list, DHCP6_LISTVAL_STCODE,
+ &opt_stcode.dh6_stcode_code, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated status code (%d)",
+ opt_stcode.dh6_stcode_code);
+ goto nextoption;
+ }
+
+ /* copy-in the code value */
+ if (dhcp6_add_listval(list, DHCP6_LISTVAL_STCODE,
+ &opt_stcode.dh6_stcode_code, NULL) == NULL)
+ goto fail;
+
+ break;
+ }
+ nextoption:
+ ;
+ }
+
+ return (0);
+
+ malformed:
+ dprintf(LOG_INFO, "", " malformed DHCP option: type %d", opt);
+
+ fail:
+ dhcp6_clear_list(&sublist);
+ return (-1);
+}
+
+static int
+get_delegated_prefixes(p, ep, optinfo)
+ char *p, *ep;
+ struct dhcp6_optinfo *optinfo;
+{
+ char *np, *cp;
+ struct dhcp6opt opth;
+ struct dhcp6opt_prefix_info pi;
+ struct dhcp6_prefix prefix;
+ int optlen, opt;
+
+ for (; p + sizeof(struct dhcp6opt) <= ep; p = np) {
+ /* XXX: alignment issue */
+ memcpy(&opth, p, sizeof(opth));
+ optlen = ntohs(opth.dh6opt_len);
+ opt = ntohs(opth.dh6opt_type);
+
+ cp = p + sizeof(opth);
+ np = cp + optlen;
+ dprintf(LOG_DEBUG, "", " prefix delegation option: %s, "
+ "len %d", dhcp6optstr(opt), optlen);
+
+ if (np > ep) {
+ dprintf(LOG_INFO, FNAME, "malformed DHCP options");
+ return (-1);
+ }
+
+ switch(opt) {
+ case DH6OPT_PREFIX_INFORMATION:
+ if (optlen != sizeof(pi) - 4)
+ goto malformed;
+
+ memcpy(&pi, p, sizeof(pi));
+
+ if (pi.dh6_pi_plen > 128) {
+ dprintf(LOG_INFO, FNAME,
+ "invalid prefix length (%d)",
+ pi.dh6_pi_plen);
+ goto malformed;
+ }
+
+ /* clear padding bits in the prefix address */
+ prefix6_mask(&pi.dh6_pi_paddr, pi.dh6_pi_plen);
+
+ /* copy the information into internal format */
+ memset(&prefix, 0, sizeof(prefix));
+ prefix.addr = pi.dh6_pi_paddr;
+ prefix.plen = pi.dh6_pi_plen;
+ /* XXX */
+ prefix.vltime = ntohl(pi.dh6_pi_duration);
+ prefix.pltime = ntohl(pi.dh6_pi_duration);
+
+ if (prefix.vltime != DHCP6_DURATITION_INFINITE) {
+ dprintf(LOG_DEBUG, "",
+ " prefix information: "
+ "%s/%d duration %lu",
+ in6addr2str(&prefix.addr, 0),
+ prefix.plen, prefix.vltime);
+ } else {
+ dprintf(LOG_DEBUG, "",
+ " prefix information: "
+ "%s/%d duration infinity",
+ in6addr2str(&prefix.addr, 0),
+ prefix.plen);
+ }
+
+ if (dhcp6_find_listval(&optinfo->prefix_list,
+ DHCP6_LISTVAL_PREFIX6, &prefix, 0)) {
+ dprintf(LOG_INFO, FNAME,
+ "duplicated prefix (%s/%d)",
+ in6addr2str(&prefix.addr, 0),
+ prefix.plen);
+ goto nextoption;
+ }
+
+ if (dhcp6_add_listval(&optinfo->prefix_list,
+ DHCP6_LISTVAL_PREFIX6, &prefix, NULL) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to copy a prefix");
+ goto fail;
+ }
+ }
+
+ nextoption:
+ ;
+ }
+
+ return (0);
+
+ malformed:
+ dprintf(LOG_INFO,
+ "", " malformed prefix delegation option: type %d, len %d",
+ opt, optlen);
+ fail:
+ return (-1);
+}
+
+static char *
+sprint_uint64(buf, buflen, i64)
+ char *buf;
+ int buflen;
+ u_int64_t i64;
+{
+ u_int16_t rd0, rd1, rd2, rd3;
+
+ rd0 = ntohs(*(u_int16_t *)(void *)&i64);
+ rd1 = ntohs(*((u_int16_t *)(void *)(&i64 + 1)));
+ rd2 = ntohs(*((u_int16_t *)(void *)(&i64 + 2)));
+ rd3 = ntohs(*((u_int16_t *)(void *)(&i64 + 3)));
+
+ snprintf(buf, buflen, "%04x %04x %04x %04x", rd0, rd1, rd2, rd3);
+
+ return (buf);
+}
+
+static char *
+sprint_auth(optinfo)
+ struct dhcp6_optinfo *optinfo;
+{
+ static char ret[1024]; /* XXX: thread unsafe */
+ char *proto, proto0[] = "unknown(255)";
+ char *alg, alg0[] = "unknown(255)";
+ char *rdm, rdm0[] = "unknown(255)";
+ char rd[] = "ffff ffff ffff ffff";
+
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ proto = "delayed";
+ break;
+ case DHCP6_AUTHPROTO_RECONFIG:
+ proto = "reconfig";
+ break;
+ default:
+ snprintf(proto0, sizeof(proto0), "unknown(%d)",
+ optinfo->authproto & 0xff);
+ proto = proto0;
+ break;
+ }
+
+ switch (optinfo->authalgorithm) {
+ case DHCP6_AUTHALG_HMACMD5:
+ alg = "HMAC-MD5";
+ break;
+ default:
+ snprintf(alg0, sizeof(alg0), "unknown(%d)",
+ optinfo->authalgorithm & 0xff);
+ alg = alg0;
+ break;
+ }
+
+ switch (optinfo->authrdm) {
+ case DHCP6_AUTHRDM_MONOCOUNTER:
+ rdm = "mono counter";
+ break;
+ default:
+ snprintf(rdm0, sizeof(rdm0), "unknown(%d)", optinfo->authrdm);
+ rdm = rdm0;
+ }
+
+ (void)sprint_uint64(rd, sizeof(rd), optinfo->authrd);
+
+ snprintf(ret, sizeof(ret), "proto: %s, alg: %s, RDM: %s, RD: %s",
+ proto, alg, rdm, rd);
+
+ return (ret);
+}
+
+static int
+copy_option(type, len, val, optp, ep, totallenp)
+ u_int16_t type, len;
+ void *val;
+ struct dhcp6opt **optp, *ep;
+ int *totallenp;
+{
+ struct dhcp6opt *opt = *optp, opth;
+
+ if ((void *)ep - (void *)optp < len + sizeof(struct dhcp6opt)) {
+ dprintf(LOG_INFO, FNAME,
+ "option buffer short for %s", dhcp6optstr(type));
+ return (-1);
+ }
+ opth.dh6opt_type = htons(type);
+ opth.dh6opt_len = htons(len);
+ memcpy(opt, &opth, sizeof(opth));
+ if (len != 0)
+ memcpy(opt + 1, val, len);
+
+ *optp = (struct dhcp6opt *)((char *)(opt + 1) + len);
+ *totallenp += sizeof(struct dhcp6opt) + len;
+ dprintf(LOG_DEBUG, FNAME, "set %s (len %d)", dhcp6optstr(type), len);
+
+ return (0);
+}
+
+int
+dhcp6_set_options(type, optbp, optep, optinfo)
+ int type;
+ struct dhcp6opt *optbp, *optep;
+ struct dhcp6_optinfo *optinfo;
+{
+ struct dhcp6opt *p = optbp;
+ struct dhcp6_listval *stcode, *op, *d;
+ int len = 0, optlen;
+ char *tmpbuf = NULL;
+
+ if (optinfo->clientID.duid_len) {
+ if (copy_option(DH6OPT_CLIENTID, optinfo->clientID.duid_len,
+ optinfo->clientID.duid_id, &p, optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ if (optinfo->serverID.duid_len) {
+ if (copy_option(DH6OPT_SERVERID, optinfo->serverID.duid_len,
+ optinfo->serverID.duid_id, &p, optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ for (op = TAILQ_FIRST(&optinfo->iana_list); op;
+ op = TAILQ_NEXT(op, link)) {
+ int optlen;
+
+ tmpbuf = NULL;
+ if ((optlen = copyout_option(NULL, NULL, op)) < 0) {
+ dprintf(LOG_INFO, FNAME,
+ "failed to count option length");
+ goto fail;
+ }
+ if ((void *)optep - (void *)p < optlen) {
+ dprintf(LOG_INFO, FNAME, "short buffer");
+ goto fail;
+ }
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "memory allocation failed for IA_NA options");
+ goto fail;
+ }
+ if (copyout_option(tmpbuf, tmpbuf + optlen, op) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to construct an IA_NA option");
+ goto fail;
+ }
+ memcpy(p, tmpbuf, optlen);
+ free(tmpbuf);
+ p = (struct dhcp6opt *)((char *)p + optlen);
+ len += optlen;
+ }
+
+ if (optinfo->rapidcommit) {
+ if (copy_option(DH6OPT_RAPID_COMMIT, 0, NULL, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ if (optinfo->pref != DH6OPT_PREF_UNDEF) {
+ u_int8_t p8 = (u_int8_t)optinfo->pref;
+
+ if (copy_option(DH6OPT_PREFERENCE, sizeof(p8), &p8, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ if (optinfo->elapsed_time != DH6OPT_ELAPSED_TIME_UNDEF) {
+ u_int16_t p16 = (u_int16_t)optinfo->elapsed_time;
+
+ p16 = htons(p16);
+ if (copy_option(DH6OPT_ELAPSED_TIME, sizeof(p16), &p16, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ for (stcode = TAILQ_FIRST(&optinfo->stcode_list); stcode;
+ stcode = TAILQ_NEXT(stcode, link)) {
+ u_int16_t code;
+
+ code = htons(stcode->val_num);
+ if (copy_option(DH6OPT_STATUS_CODE, sizeof(code), &code, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->reqopt_list)) {
+ struct dhcp6_listval *opt;
+ u_int16_t *valp;
+ int buflen;
+
+ tmpbuf = NULL;
+ buflen = dhcp6_count_list(&optinfo->reqopt_list) *
+ sizeof(u_int16_t);
+ if ((tmpbuf = malloc(buflen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for options");
+ goto fail;
+ }
+ optlen = 0;
+ valp = (u_int16_t *)tmpbuf;
+ for (opt = TAILQ_FIRST(&optinfo->reqopt_list); opt;
+ opt = TAILQ_NEXT(opt, link)) {
+ /*
+ * Information request option can only be specified
+ * in information-request messages.
+ * [draft-ietf-dhc-lifetime-02.txt, Section 3.2]
+ */
+ if (opt->val_num == DH6OPT_REFRESHTIME &&
+ type != DH6_INFORM_REQ) {
+ dprintf(LOG_DEBUG, FNAME,
+ "refresh time option is not requested "
+ "for %s", dhcp6msgstr(type));
+ }
+
+ *valp = htons((u_int16_t)opt->val_num);
+ valp++;
+ optlen += sizeof(u_int16_t);
+ }
+ if (optlen > 0 &&
+ copy_option(DH6OPT_ORO, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+ }
+
+ optlen = 0;
+ for (d = TAILQ_FIRST(&optinfo->sipname_list); d;
+ d = TAILQ_NEXT(d, link)) {
+ optlen += (d->val_vbuf.dv_len + 1);
+ }
+ if (optlen) {
+ char name[MAXDNAME], *cp, *ep;
+ tmpbuf = NULL;
+
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for "
+ "SIP server domain options");
+ goto fail;
+ }
+ cp = tmpbuf;
+ ep = cp + optlen;
+ for (d = TAILQ_FIRST(&optinfo->sipname_list); d;
+ d = TAILQ_NEXT(d, link)) {
+ int nlen;
+
+ nlen = dnsencode((const char *)d->val_vbuf.dv_buf,
+ name, sizeof (name));
+ if (nlen < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to encode a SIP server "
+ "domain name");
+ goto fail;
+ }
+ if (ep - cp < nlen) {
+ dprintf(LOG_ERR, FNAME,
+ "buffer length for SIP server "
+ "domain name is too short");
+ goto fail;
+ }
+ memcpy(cp, name, nlen);
+ cp += nlen;
+ }
+ if (copy_option(DH6OPT_SIP_SERVER_D, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+ }
+ if (!TAILQ_EMPTY(&optinfo->sip_list)) {
+ struct in6_addr *in6;
+
+ tmpbuf = NULL;
+ optlen = dhcp6_count_list(&optinfo->sip_list) *
+ sizeof(struct in6_addr);
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for SIP server options");
+ goto fail;
+ }
+ in6 = (struct in6_addr *)tmpbuf;
+ for (d = TAILQ_FIRST(&optinfo->sip_list); d;
+ d = TAILQ_NEXT(d, link), in6++) {
+ memcpy(in6, &d->val_addr6, sizeof(*in6));
+ }
+ if (copy_option(DH6OPT_SIP_SERVER_A, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->dns_list)) {
+ struct in6_addr *in6;
+
+ tmpbuf = NULL;
+ optlen = dhcp6_count_list(&optinfo->dns_list) *
+ sizeof(struct in6_addr);
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for DNS options");
+ goto fail;
+ }
+ in6 = (struct in6_addr *)tmpbuf;
+ for (d = TAILQ_FIRST(&optinfo->dns_list); d;
+ d = TAILQ_NEXT(d, link), in6++) {
+ memcpy(in6, &d->val_addr6, sizeof(*in6));
+ }
+ if (copy_option(DH6OPT_DNS, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+ }
+
+ optlen = 0;
+ for (d = TAILQ_FIRST(&optinfo->dnsname_list); d;
+ d = TAILQ_NEXT(d, link)) {
+ optlen += (d->val_vbuf.dv_len + 1);
+ }
+ if (optlen) {
+ char name[MAXDNAME], *cp, *ep;
+ tmpbuf = NULL;
+
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for DNS name options");
+ goto fail;
+ }
+ cp = tmpbuf;
+ ep = cp + optlen;
+ for (d = TAILQ_FIRST(&optinfo->dnsname_list); d;
+ d = TAILQ_NEXT(d, link)) {
+ int nlen;
+
+ nlen = dnsencode((const char *)d->val_vbuf.dv_buf,
+ name, sizeof (name));
+ if (nlen < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to encode a DNS name");
+ goto fail;
+ }
+ if (ep - cp < nlen) {
+ dprintf(LOG_ERR, FNAME,
+ "buffer length for DNS name is too short");
+ goto fail;
+ }
+ memcpy(cp, name, nlen);
+ cp += nlen;
+ }
+ if (copy_option(DH6OPT_DNSNAME, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+ }
+
+#ifdef USE_DH6OPT_NTP
+ if (!TAILQ_EMPTY(&optinfo->ntp_list)) {
+ struct in6_addr *in6;
+
+ tmpbuf = NULL;
+ optlen = dhcp6_count_list(&optinfo->ntp_list) *
+ sizeof(struct in6_addr);
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for NTP options");
+ goto fail;
+ }
+ in6 = (struct in6_addr *)tmpbuf;
+ for (d = TAILQ_FIRST(&optinfo->ntp_list); d;
+ d = TAILQ_NEXT(d, link), in6++) {
+ memcpy(in6, &d->val_addr6, sizeof(*in6));
+ }
+ if (copy_option(DH6OPT_NTP, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+ }
+#endif
+
+ for (op = TAILQ_FIRST(&optinfo->iapd_list); op;
+ op = TAILQ_NEXT(op, link)) {
+ int optlen;
+
+ tmpbuf = NULL;
+ if ((optlen = copyout_option(NULL, NULL, op)) < 0) {
+ dprintf(LOG_INFO, FNAME,
+ "failed to count option length");
+ goto fail;
+ }
+ if ((void *)optep - (void *)p < optlen) {
+ dprintf(LOG_INFO, FNAME, "short buffer");
+ goto fail;
+ }
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_NOTICE, FNAME,
+ "memory allocation failed for IA_PD options");
+ goto fail;
+ }
+ if (copyout_option(tmpbuf, tmpbuf + optlen, op) < 0) {
+ dprintf(LOG_ERR, FNAME,
+ "failed to construct an IA_PD option");
+ goto fail;
+ }
+ memcpy(p, tmpbuf, optlen);
+ free(tmpbuf);
+ p = (struct dhcp6opt *)((char *)p + optlen);
+ len += optlen;
+ }
+
+ if (!TAILQ_EMPTY(&optinfo->prefix_list)) {
+ char *tp;
+ struct dhcp6_listval *dp;
+ struct dhcp6opt_prefix_info pi;
+
+ tmpbuf = NULL;
+ optlen = dhcp6_count_list(&optinfo->prefix_list) *
+ sizeof(struct dhcp6opt_prefix_info);
+ if ((tmpbuf = malloc(optlen)) == NULL) {
+ dprintf(LOG_ERR, FNAME,
+ "memory allocation failed for options");
+ goto fail;
+ }
+ for (dp = TAILQ_FIRST(&optinfo->prefix_list), tp = tmpbuf; dp;
+ dp = TAILQ_NEXT(dp, link), tp += sizeof(pi)) {
+ /*
+ * XXX: We need a temporary structure due to alignment
+ * issue.
+ */
+ memset(&pi, 0, sizeof(pi));
+ pi.dh6_pi_type = htons(DH6OPT_PREFIX_INFORMATION);
+ pi.dh6_pi_len = htons(sizeof(pi) - 4);
+ pi.dh6_pi_duration = htonl(dp->val_prefix6.vltime);
+ pi.dh6_pi_plen = dp->val_prefix6.plen;
+ memcpy(&pi.dh6_pi_paddr, &dp->val_prefix6.addr,
+ sizeof(struct in6_addr));
+ memcpy(tp, &pi, sizeof(pi));
+ }
+ if (copy_option(DH6OPT_PREFIX_DELEGATION, optlen, tmpbuf, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ free(tmpbuf);
+
+ }
+
+ if (optinfo->relaymsg_len) {
+ if (copy_option(DH6OPT_RELAY_MSG, optinfo->relaymsg_len,
+ optinfo->relaymsg_msg, &p, optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ if (optinfo->ifidopt_id) {
+ if (copy_option(DH6OPT_INTERFACE_ID, optinfo->ifidopt_len,
+ optinfo->ifidopt_id, &p, optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+#ifdef USE_DH6OPT_REFRESHTIME
+ if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) {
+ u_int32_t p32 = (u_int32_t)optinfo->refreshtime;
+
+ p32 = htonl(p32);
+ if (copy_option(DH6OPT_REFRESHTIME, sizeof(p32), &p32, &p,
+ optep, &len) != 0) {
+ goto fail;
+ }
+ }
+#endif
+
+ if (optinfo->authproto != DHCP6_AUTHPROTO_UNDEF) {
+ struct dhcp6opt_auth *auth;
+ int authlen;
+ char *authinfo;
+
+ authlen = sizeof(*auth);
+ if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ /* Realm + key ID + HMAC-MD5 */
+ authlen += optinfo->delayedauth_realmlen +
+ sizeof(optinfo->delayedauth_keyid) + 16;
+ break;
+#ifdef notyet
+ case DHCP6_AUTHPROTO_RECONFIG:
+ /* type + key-or-HAMC */
+ authlen += 17;
+ break;
+#endif
+ default:
+ dprintf(LOG_ERR, FNAME,
+ "unexpected authentication protocol");
+ goto fail;
+ }
+ }
+ if ((auth = malloc(authlen)) == NULL) {
+ dprintf(LOG_WARNING, FNAME, "failed to allocate "
+ "memory for authentication information");
+ goto fail;
+ }
+
+ memset(auth, 0, authlen);
+ /* copy_option will take care of type and len later */
+ auth->dh6_auth_proto = (u_int8_t)optinfo->authproto;
+ auth->dh6_auth_alg = (u_int8_t)optinfo->authalgorithm;
+ auth->dh6_auth_rdm = (u_int8_t)optinfo->authrdm;
+ memcpy(auth->dh6_auth_rdinfo, &optinfo->authrd,
+ sizeof(auth->dh6_auth_rdinfo));
+
+ if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
+ u_int32_t p32;
+
+ switch (optinfo->authproto) {
+ case DHCP6_AUTHPROTO_DELAYED:
+ authinfo = (char *)(auth + 1);
+
+ /* copy realm */
+ memcpy(authinfo, optinfo->delayedauth_realmval,
+ optinfo->delayedauth_realmlen);
+ authinfo += optinfo->delayedauth_realmlen;
+
+ /* copy key ID (need memcpy for alignment) */
+ p32 = htonl(optinfo->delayedauth_keyid);
+ memcpy(authinfo, &p32, sizeof(p32));
+
+ /*
+ * Set the offset so that the caller can
+ * calculate the HMAC.
+ */
+ optinfo->delayedauth_offset =
+ ((char *)p - (char *)optbp) + authlen - 16;
+
+ dprintf(LOG_DEBUG, FNAME,
+ "key ID %x, offset %d",
+ optinfo->delayedauth_keyid,
+ optinfo->delayedauth_offset);
+ break;
+#ifdef notyet
+ case DHCP6_AUTHPROTO_RECONFIG:
+#endif
+ default:
+ dprintf(LOG_ERR, FNAME,
+ "unexpected authentication protocol");
+ goto fail;
+ }
+ }
+
+ if (copy_option(DH6OPT_AUTH, authlen - 4,
+ &auth->dh6_auth_proto, &p, optep, &len) != 0) {
+ goto fail;
+ }
+ }
+
+ return (len);
+
+ fail:
+ if (tmpbuf)
+ free(tmpbuf);
+ return (-1);
+}
+#undef COPY_OPTION
+
+static ssize_t
+dnsencode(name, buf, buflen)
+ const char *name;
+ char *buf;
+ size_t buflen;
+{
+ char *cp, *ep;
+ const char *p, *q;
+ int i;
+ int namelen = strlen(name);
+
+ cp = buf;
+ ep = cp + buflen;
+
+ /* if not certain about my name, return an empty buffer */
+ if (namelen == 0)
+ return (0);
+
+ p = name;
+ while (cp < ep && p < name + namelen) {
+ i = 0;
+ for (q = p; q < name + namelen && *q && *q != '.'; q++)
+ i++;
+ /* result does not fit into buf */
+ if (cp + i + 1 >= ep)
+ goto fail;
+ /*
+ * DNS label length restriction, RFC1035 page 8.
+ * "i == 0" case is included here to avoid returning
+ * 0-length label on "foo..bar".
+ */
+ if (i <= 0 || i >= 64)
+ goto fail;
+ *cp++ = i;
+ if (!isalpha(p[0]) || !isalnum(p[i - 1]))
+ goto fail;
+ while (i > 0) {
+ if (!isalnum(*p) && *p != '-')
+ goto fail;
+ if (isupper(*p))
+ *cp++ = tolower(*p++);
+ else
+ *cp++ = *p++;
+ i--;
+ }
+ p = q;
+ if (p < name + namelen && *p == '.')
+ p++;
+ }
+ /* termination */
+ if (cp + 1 >= ep)
+ goto fail;
+ *cp++ = '\0';
+ return (cp - buf);
+
+ fail:
+ return (-1);
+}
+
+/*
+ * Construct a DHCPv6 option along with sub-options in the wire format.
+ * If the packet buffer is NULL, just calculate the length of the option
+ * (and sub-options) so that the caller can allocate a buffer to store the
+ * option(s).
+ * This function basically assumes that the caller prepares enough buffer to
+ * store all the options. However, it also takes the buffer end and checks
+ * the possibility of overrun for safety.
+ */
+static int
+copyout_option(p, ep, optval)
+ char *p, *ep;
+ struct dhcp6_listval *optval;
+{
+ struct dhcp6opt *opt;
+ struct dhcp6opt_stcode stcodeopt;
+ struct dhcp6opt_ia ia;
+ struct dhcp6opt_ia_pd_prefix pd_prefix;
+ struct dhcp6opt_ia_addr ia_addr;
+ char *subp;
+ struct dhcp6_listval *subov;
+ int optlen, headlen, sublen, opttype;
+
+ /* check invariant for safety */
+ if (p && ep <= p)
+ return (-1);
+
+ /* first, detect the length of the option head */
+ switch(optval->type) {
+ case DHCP6_LISTVAL_IAPD:
+ memset(&ia, 0, sizeof(ia));
+ headlen = sizeof(ia);
+ opttype = DH6OPT_IA_PD;
+ opt = (struct dhcp6opt *)(void *)&ia;
+ break;
+ case DHCP6_LISTVAL_IANA:
+ memset(&ia, 0, sizeof(ia));
+ headlen = sizeof(ia);
+ opttype = DH6OPT_IA_NA;
+ opt = (struct dhcp6opt *)(void *)&ia;
+ break;
+ case DHCP6_LISTVAL_ADDR6:
+ memset(&pd_prefix, 0, sizeof(pd_prefix));
+ headlen = sizeof(pd_prefix);
+ opttype = DH6OPT_IA_PD_PREFIX;
+ opt = (struct dhcp6opt *)&pd_prefix;
+ break;
+ case DHCP6_LISTVAL_PREFIX6:
+ memset(&pd_prefix, 0, sizeof(pd_prefix));
+ headlen = sizeof(pd_prefix);
+ opttype = DH6OPT_IA_PD_PREFIX;
+ opt = (struct dhcp6opt *)&pd_prefix;
+ break;
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ memset(&ia_addr, 0, sizeof(ia_addr));
+ headlen = sizeof(ia_addr);
+ opttype = DH6OPT_IAADDR;
+ opt = (struct dhcp6opt *)&ia_addr;
+ break;
+ case DHCP6_LISTVAL_STCODE:
+ memset(&stcodeopt, 0, sizeof(stcodeopt));
+ headlen = sizeof(stcodeopt);
+ opttype = DH6OPT_STATUS_CODE;
+ opt = (struct dhcp6opt *)(void *)&stcodeopt;
+ break;
+ default:
+ /*
+ * we encounter an unknown option. this should be an internal
+ * error.
+ */
+ dprintf(LOG_ERR, FNAME, "unknown option: code %d",
+ optval->type);
+ return (-1);
+ }
+
+ /* then, calculate the length of and/or fill in the sub-options */
+ subp = NULL;
+ sublen = 0;
+ if (p)
+ subp = p + headlen;
+ for (subov = TAILQ_FIRST(&optval->sublist); subov;
+ subov = TAILQ_NEXT(subov, link)) {
+ int s;
+
+ if ((s = copyout_option(subp, ep, subov)) < 0)
+ return (-1);
+ if (p)
+ subp += s;
+ sublen += s;
+ }
+
+ /* finally, deal with the head part again */
+ optlen = headlen + sublen;
+ if (!p)
+ return(optlen);
+
+ dprintf(LOG_DEBUG, FNAME, "set %s", dhcp6optstr(opttype));
+ if (ep - p < headlen) /* check it just in case */
+ return (-1);
+
+ /* fill in the common part */
+ opt->dh6opt_type = htons(opttype);
+ opt->dh6opt_len = htons(optlen - sizeof(struct dhcp6opt));
+
+ /* fill in type specific fields */
+ switch(optval->type) {
+ case DHCP6_LISTVAL_IAPD:
+ ia.dh6_ia_iaid = htonl(optval->val_ia.iaid);
+ ia.dh6_ia_t1 = htonl(optval->val_ia.t1);
+ ia.dh6_ia_t2 = htonl(optval->val_ia.t2);
+ break;
+ case DHCP6_LISTVAL_IANA:
+ ia.dh6_ia_iaid = htonl(optval->val_ia.iaid);
+ ia.dh6_ia_t1 = htonl(optval->val_ia.t1);
+ ia.dh6_ia_t2 = htonl(optval->val_ia.t2);
+ break;
+ case DHCP6_LISTVAL_PREFIX6:
+ pd_prefix.dh6_iapd_prefix_preferred_time =
+ htonl(optval->val_prefix6.pltime);
+ pd_prefix.dh6_iapd_prefix_valid_time =
+ htonl(optval->val_prefix6.vltime);
+ pd_prefix.dh6_iapd_prefix_prefix_len =
+ optval->val_prefix6.plen;
+ /* XXX: prefix_addr is badly aligned, so we need memcpy */
+ memcpy(&pd_prefix.dh6_iapd_prefix_prefix_addr,
+ &optval->val_prefix6.addr, sizeof(struct in6_addr));
+ break;
+ case DHCP6_LISTVAL_STATEFULADDR6:
+ ia_addr.dh6_ia_addr_preferred_time =
+ htonl(optval->val_statefuladdr6.pltime);
+ ia_addr.dh6_ia_addr_valid_time =
+ htonl(optval->val_statefuladdr6.vltime);
+ ia_addr.dh6_ia_addr_addr = optval->val_statefuladdr6.addr;
+ break;
+ case DHCP6_LISTVAL_STCODE:
+ stcodeopt.dh6_stcode_code = htons(optval->val_num16);
+ break;
+ default:
+ /*
+ * XXX: this case should be rejected at the beginning of this
+ * function.
+ */
+ return (-1);
+ }
+
+ /* copyout the data (p must be non NULL at this point) */
+ memcpy(p, opt, headlen);
+ return (optlen);
+}
+
+void
+dhcp6_set_timeoparam(ev)
+ struct dhcp6_event *ev;
+{
+ ev->retrans = 0;
+ ev->init_retrans = 0;
+ ev->max_retrans_cnt = 0;
+ ev->max_retrans_dur = 0;
+ ev->max_retrans_time = 0;
+
+ switch(ev->state) {
+ case DHCP6S_SOLICIT:
+ ev->init_retrans = SOL_TIMEOUT;
+ ev->max_retrans_time = SOL_MAX_RT;
+ break;
+ case DHCP6S_INFOREQ:
+ ev->init_retrans = INF_TIMEOUT;
+ ev->max_retrans_time = INF_MAX_RT;
+ break;
+ case DHCP6S_REQUEST:
+ ev->init_retrans = REQ_TIMEOUT;
+ ev->max_retrans_time = REQ_MAX_RT;
+ ev->max_retrans_cnt = REQ_MAX_RC;
+ break;
+ case DHCP6S_RENEW:
+ ev->init_retrans = REN_TIMEOUT;
+ ev->max_retrans_time = REN_MAX_RT;
+ break;
+ case DHCP6S_REBIND:
+ ev->init_retrans = REB_TIMEOUT;
+ ev->max_retrans_time = REB_MAX_RT;
+ break;
+ case DHCP6S_RELEASE:
+ ev->init_retrans = REL_TIMEOUT;
+ ev->max_retrans_cnt = REL_MAX_RC;
+ break;
+ default:
+ dprintf(LOG_ERR, FNAME, "unexpected event state %d on %s",
+ ev->state, ev->ifp->ifname);
+ exit(1);
+ }
+}
+
+void
+dhcp6_reset_timer(ev)
+ struct dhcp6_event *ev;
+{
+ double n, r;
+ char *statestr;
+ struct timeval interval;
+
+ switch(ev->state) {
+ case DHCP6S_INIT:
+ /*
+ * The first Solicit message from the client on the interface
+ * MUST be delayed by a random amount of time between
+ * 0 and SOL_MAX_DELAY.
+ * [RFC3315 17.1.2]
+ * XXX: a random delay is also necessary before the first
+ * information-request message. Fortunately, the parameters
+ * and the algorithm for these two cases are the same.
+ * [RFC3315 18.1.5]
+ */
+ ev->retrans = (random() % (SOL_MAX_DELAY));
+ break;
+ default:
+ if (ev->state == DHCP6S_SOLICIT && ev->timeouts == 0) {
+ /*
+ * The first RT MUST be selected to be strictly
+ * greater than IRT by choosing RAND to be strictly
+ * greater than 0.
+ * [RFC3315 17.1.2]
+ */
+ r = (double)((random() % 1000) + 1) / 10000;
+ n = ev->init_retrans + r * ev->init_retrans;
+ } else {
+ r = (double)((random() % 2000) - 1000) / 10000;
+
+ if (ev->timeouts == 0) {
+ n = ev->init_retrans + r * ev->init_retrans;
+ } else
+ n = 2 * ev->retrans + r * ev->retrans;
+ }
+ if (ev->max_retrans_time && n > ev->max_retrans_time)
+ n = ev->max_retrans_time + r * ev->max_retrans_time;
+ ev->retrans = (long)n;
+ break;
+ }
+
+ interval.tv_sec = (ev->retrans * 1000) / 1000000;
+ interval.tv_usec = (ev->retrans * 1000) % 1000000;
+ dhcp6_set_timer(&interval, ev->timer);
+
+ statestr = dhcp6_event_statestr(ev);
+
+ dprintf(LOG_DEBUG, FNAME, "reset a timer on %s, "
+ "state=%s, timeo=%d, retrans=%d",
+ ev->ifp->ifname, statestr, ev->timeouts, ev->retrans);
+}
+
+int
+duidcpy(dd, ds)
+ struct duid *dd, *ds;
+{
+ dd->duid_len = ds->duid_len;
+ if ((dd->duid_id = malloc(dd->duid_len)) == NULL) {
+ dprintf(LOG_ERR, FNAME, "memory allocation failed");
+ return (-1);
+ }
+ memcpy(dd->duid_id, ds->duid_id, dd->duid_len);
+
+ return (0);
+}
+
+int
+duidcmp(d1, d2)
+ struct duid *d1, *d2;
+{
+ if (d1->duid_len == d2->duid_len) {
+ return (memcmp(d1->duid_id, d2->duid_id, d1->duid_len));
+ } else
+ return (-1);
+}
+
+void
+duidfree(duid)
+ struct duid *duid;
+{
+ if (duid->duid_id)
+ free(duid->duid_id);
+ duid->duid_id = NULL;
+ duid->duid_len = 0;
+}
+
+/*
+ * Provide an NTP-format timestamp as a replay detection counter
+ * as mentioned in RFC3315.
+ */
+#define JAN_1970 2208988800UL /* 1970 - 1900 in seconds */
+int
+get_rdvalue(rdm, rdvalue, rdsize)
+ int rdm;
+ void *rdvalue;
+ size_t rdsize;
+{
+#if defined(HAVE_CLOCK_GETTIME)
+ struct timespec tp;
+ double nsec;
+#else
+ struct timeval tv;
+#endif
+ u_int32_t u32, l32;
+
+ if (rdm != DHCP6_AUTHRDM_MONOCOUNTER) {
+ dprintf(LOG_INFO, FNAME, "unsupported RDM (%d)", rdm);
+ return (-1);
+ }
+ if (rdsize != sizeof(u_int64_t)) {
+ dprintf(LOG_INFO, FNAME, "unsupported RD size (%d)", rdsize);
+ return (-1);
+ }
+
+#if defined(HAVE_CLOCK_GETTIME)
+ if (clock_gettime(CLOCK_REALTIME, &tp)) {
+ dprintf(LOG_WARNING, FNAME, "clock_gettime failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ u32 = (u_int32_t)tp.tv_sec;
+ u32 += JAN_1970;
+
+ nsec = (double)tp.tv_nsec / 1e9 * 0x100000000ULL;
+ /* nsec should be smaller than 2^32 */
+ l32 = (u_int32_t)nsec;
+#else
+ if (gettimeofday(&tv, NULL) != 0) {
+ dprintf(LOG_WARNING, FNAME, "gettimeofday failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+ u32 = (u_int32_t)tv.tv_sec;
+ u32 += JAN_1970;
+ l32 = (u_int32_t)tv.tv_usec;
+#endif /* HAVE_CLOCK_GETTIME */
+
+ u32 = htonl(u32);
+ l32 = htonl(l32);
+
+ memcpy(rdvalue, &u32, sizeof(u32));
+ memcpy((char *)rdvalue + sizeof(u32), &l32, sizeof(l32));
+
+ return (0);
+}
+
+char *
+dhcp6optstr(type)
+ int type;
+{
+ static char genstr[sizeof("opt_65535") + 1]; /* XXX thread unsafe */
+
+ if (type > 65535)
+ return ("INVALID option");
+
+ switch(type) {
+ case DH6OPT_CLIENTID:
+ return ("client ID");
+ case DH6OPT_SERVERID:
+ return ("server ID");
+ case DH6OPT_IA_NA:
+ return ("identity association");
+ case DH6OPT_IA_TA:
+ return ("IA for temporary");
+ case DH6OPT_IAADDR:
+ return ("IA address");
+ case DH6OPT_ORO:
+ return ("option request");
+ case DH6OPT_PREFERENCE:
+ return ("preference");
+ case DH6OPT_ELAPSED_TIME:
+ return ("elapsed time");
+ case DH6OPT_RELAY_MSG:
+ return ("relay message");
+ case DH6OPT_AUTH:
+ return ("authentication");
+ case DH6OPT_UNICAST:
+ return ("server unicast");
+ case DH6OPT_STATUS_CODE:
+ return ("status code");
+ case DH6OPT_RAPID_COMMIT:
+ return ("rapid commit");
+ case DH6OPT_USER_CLASS:
+ return ("user class");
+ case DH6OPT_VENDOR_CLASS:
+ return ("vendor class");
+ case DH6OPT_VENDOR_OPTS:
+ return ("vendor specific info");
+ case DH6OPT_INTERFACE_ID:
+ return ("interface ID");
+ case DH6OPT_RECONF_MSG:
+ return ("reconfigure message");
+ case DH6OPT_SIP_SERVER_D:
+ return ("SIP server domain name ");
+ case DH6OPT_SIP_SERVER_A:
+ return ("SIP server address");
+ case DH6OPT_DNS:
+ return ("DNS");
+ case DH6OPT_DNSNAME:
+ return ("domain search list");
+#ifdef USE_DH6OPT_NTP
+ case DH6OPT_NTP:
+ return ("NTP server");
+#endif
+ case DH6OPT_PREFIX_DELEGATION:
+ return ("prefix delegation");
+ case DH6OPT_PREFIX_INFORMATION:
+ return ("prefix information");
+ case DH6OPT_IA_PD:
+ return ("IA_PD");
+ case DH6OPT_IA_PD_PREFIX:
+ return ("IA_PD prefix");
+#ifdef USE_DH6OPT_REFRESHTIME
+ case DH6OPT_REFRESHTIME:
+ return ("information refresh time");
+#endif
+ default:
+ snprintf(genstr, sizeof(genstr), "opt_%d", type);
+ return (genstr);
+ }
+}
+
+char *
+dhcp6msgstr(type)
+ int type;
+{
+ static char genstr[sizeof("msg255") + 1]; /* XXX thread unsafe */
+
+ if (type > 255)
+ return ("INVALID msg");
+
+ switch(type) {
+ case DH6_SOLICIT:
+ return ("solicit");
+ case DH6_ADVERTISE:
+ return ("advertise");
+ case DH6_REQUEST:
+ return ("request");
+ case DH6_CONFIRM:
+ return ("confirm");
+ case DH6_RENEW:
+ return ("renew");
+ case DH6_REBIND:
+ return ("rebind");
+ case DH6_REPLY:
+ return ("reply");
+ case DH6_RELEASE:
+ return ("release");
+ case DH6_DECLINE:
+ return ("decline");
+ case DH6_RECONFIGURE:
+ return ("reconfigure");
+ case DH6_INFORM_REQ:
+ return ("information request");
+ case DH6_RELAY_FORW:
+ return ("relay-forward");
+ case DH6_RELAY_REPLY:
+ return ("relay-reply");
+ default:
+ snprintf(genstr, sizeof(genstr), "msg%d", type);
+ return (genstr);
+ }
+}
+
+char *
+dhcp6_stcodestr(code)
+ u_int16_t code;
+{
+ static char genstr[sizeof("code255") + 1]; /* XXX thread unsafe */
+
+ if (code > 255)
+ return ("INVALID code");
+
+ switch(code) {
+ case DH6OPT_STCODE_SUCCESS:
+ return ("success");
+ case DH6OPT_STCODE_UNSPECFAIL:
+ return ("unspec failure");
+ case DH6OPT_STCODE_NOADDRSAVAIL:
+ return ("no addresses");
+ case DH6OPT_STCODE_NOBINDING:
+ return ("no binding");
+ case DH6OPT_STCODE_NOTONLINK:
+ return ("not on-link");
+ case DH6OPT_STCODE_USEMULTICAST:
+ return ("use multicast");
+ case DH6OPT_STCODE_NOPREFIXAVAIL:
+ return ("no prefixes");
+ default:
+ snprintf(genstr, sizeof(genstr), "code%d", code);
+ return (genstr);
+ }
+}
+
+char *
+duidstr(duid)
+ struct duid *duid;
+{
+ int i, n;
+ char *cp, *ep;
+ static char duidstr[sizeof("xx:") * 128 + sizeof("...")];
+
+ cp = duidstr;
+ ep = duidstr + sizeof(duidstr);
+ for (i = 0; i < duid->duid_len && i <= 128; i++) {
+ n = snprintf(cp, ep - cp, "%s%02x", i == 0 ? "" : ":",
+ duid->duid_id[i] & 0xff);
+ if (n < 0)
+ return NULL;
+ cp += n;
+ }
+ if (i < duid->duid_len)
+ snprintf(cp, ep - cp, "%s", "...");
+
+ return (duidstr);
+}
+
+char *dhcp6_event_statestr(ev)
+ struct dhcp6_event *ev;
+{
+ switch(ev->state) {
+ case DHCP6S_INIT:
+ return ("INIT");
+ case DHCP6S_SOLICIT:
+ return ("SOLICIT");
+ case DHCP6S_INFOREQ:
+ return ("INFOREQ");
+ case DHCP6S_REQUEST:
+ return ("REQUEST");
+ case DHCP6S_RENEW:
+ return ("RENEW");
+ case DHCP6S_REBIND:
+ return ("REBIND");
+ case DHCP6S_RELEASE:
+ return ("RELEASE");
+ case DHCP6S_IDLE:
+ return ("IDLE");
+ default:
+ return ("???"); /* XXX */
+ }
+}
+
+void
+setloglevel(debuglevel)
+ int debuglevel;
+{
+ if (foreground) {
+ switch(debuglevel) {
+ case 0:
+ debug_thresh = LOG_ERR;
+ break;
+ case 1:
+ debug_thresh = LOG_INFO;
+ break;
+ default:
+ debug_thresh = LOG_DEBUG;
+ break;
+ }
+ } else {
+ switch(debuglevel) {
+ case 0:
+ setlogmask(LOG_UPTO(LOG_ERR));
+ break;
+ case 1:
+ setlogmask(LOG_UPTO(LOG_INFO));
+ break;
+ }
+ }
+}
+
+void
+dprintf(int level, const char *fname, const char *fmt, ...)
+{
+ va_list ap;
+ char logbuf[LINE_MAX];
+ int printfname = 1;
+
+ va_start(ap, fmt);
+ vsnprintf(logbuf, sizeof(logbuf), fmt, ap);
+
+ if (*fname == '\0')
+ printfname = 0;
+
+ if (foreground && debug_thresh >= level) {
+ time_t now;
+ struct tm *tm_now;
+ const char *month[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+
+ if ((now = time(NULL)) < 0)
+ exit(1); /* XXX */
+ tm_now = localtime(&now);
+ fprintf(stderr, "%3s/%02d/%04d %02d:%02d:%02d: %s%s%s\n",
+ month[tm_now->tm_mon], tm_now->tm_mday,
+ tm_now->tm_year + 1900,
+ tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
+ fname, printfname ? ": " : "",
+ logbuf);
+ } else
+ syslog(level, "%s%s%s", fname, printfname ? ": " : "", logbuf);
+}
+
+int
+ifaddrconf(cmd, ifname, addr, plen, pltime, vltime)
+ ifaddrconf_cmd_t cmd;
+ char *ifname;
+ struct sockaddr_in6 *addr;
+ int plen;
+ int pltime;
+ int vltime;
+{
+#ifdef __KAME__
+ struct in6_aliasreq req;
+#endif
+#ifdef __linux__
+ struct in6_ifreq req;
+ struct ifreq ifr;
+#endif
+ unsigned long ioctl_cmd;
+ char *cmdstr;
+ int s; /* XXX overhead */
+
+ switch(cmd) {
+ case IFADDRCONF_ADD:
+ cmdstr = "add";
+#ifdef __KAME__
+ ioctl_cmd = SIOCAIFADDR_IN6;
+#endif
+#ifdef __linux__
+ ioctl_cmd = SIOCSIFADDR;
+#endif
+ break;
+ case IFADDRCONF_REMOVE:
+ cmdstr = "remove";
+#ifdef __KAME__
+ ioctl_cmd = SIOCDIFADDR_IN6;
+#endif
+#ifdef __linux__
+ ioctl_cmd = SIOCDIFADDR;
+#endif
+ break;
+ default:
+ return (-1);
+ }
+
+ if ((s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+ dprintf(LOG_ERR, FNAME, "can't open a temporary socket: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ memset(&req, 0, sizeof(req));
+#ifdef __KAME__
+ req.ifra_addr = *addr;
+ memcpy(req.ifra_name, ifname, sizeof(req.ifra_name));
+ (void)sa6_plen2mask(&req.ifra_prefixmask, plen);
+ /* XXX: should lifetimes be calculated based on the lease duration? */
+ req.ifra_lifetime.ia6t_vltime = vltime;
+ req.ifra_lifetime.ia6t_pltime = pltime;
+#endif
+#ifdef __linux__
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
+ if (ioctl(s, SIOGIFINDEX, &ifr) < 0) {
+ dprintf(LOG_NOTICE, FNAME, "failed to get the index of %s: %s",
+ ifname, strerror(errno));
+ close(s);
+ return (-1);
+ }
+ memcpy(&req.ifr6_addr, &addr->sin6_addr, sizeof(struct in6_addr));
+ req.ifr6_prefixlen = plen;
+ req.ifr6_ifindex = ifr.ifr_ifindex;
+#endif
+
+ if (ioctl(s, ioctl_cmd, &req)) {
+ dprintf(LOG_NOTICE, FNAME, "failed to %s an address on %s: %s",
+ cmdstr, ifname, strerror(errno));
+ close(s);
+ return (-1);
+ }
+
+ dprintf(LOG_DEBUG, FNAME, "%s an address %s/%d on %s", cmdstr,
+ addr2str((struct sockaddr *)addr), plen, ifname);
+
+ close(s);
+ return (0);
+}