The port could be improved by adding support for pselect(2).
Other than that, this port has a few MINIX-specific changes:
- we undefine IN_IFF_ flags to stop dhcpcd from thinking that we have
operating system support for link-local IPv4 address management;
- we work around one crash bug that seems triggered by using dhcpcd
on some but not all interfaces;
- we add "noalias" to the default dhcpcd.conf(5) configuration file.
Change-Id: I8a81c2c2af353c5ce08335673b1ab2d4b39178da
./etc/devmand/scripts/singlechar minix-base
./etc/devmand/usb_hub.cfg minix-base
./etc/devmand/usb_storage.cfg minix-base
+./etc/dhcpcd.conf minix-base
./etc/fonts minix-base xorg
./etc/fonts/conf.avail minix-base xorg
./etc/fonts/conf.d minix-base xorg
./etc/rc.d/SERVERS minix-base
./etc/rc.d/blacklistd minix-base
./etc/rc.d/bootconf.sh minix-base
+./etc/rc.d/dhcpcd minix-base
./etc/rc.d/fsck minix-base
./etc/rc.d/ftpd minix-base
./etc/rc.d/inetd minix-base
./home/bin/.profile minix-base obsolete
./lib minix-base
./libexec minix-base
+./libexec/dhcpcd-hooks minix-base
+./libexec/dhcpcd-hooks/01-test minix-base
+./libexec/dhcpcd-hooks/02-dump minix-base
+./libexec/dhcpcd-hooks/10-wpa_supplicant minix-base
+./libexec/dhcpcd-hooks/15-timezone minix-base
+./libexec/dhcpcd-hooks/20-resolv.conf minix-base
+./libexec/dhcpcd-hooks/29-lookup-hostname minix-base
+./libexec/dhcpcd-hooks/30-hostname minix-base
+./libexec/dhcpcd-hooks/50-ntp.conf minix-base
+./libexec/dhcpcd-run-hooks minix-base
./libexec/resolvconf minix-base
./libexec/resolvconf/dnsmasq minix-base
./libexec/resolvconf/libc minix-base
./sbin/blacklistctl minix-base
./sbin/blacklistd minix-base
./sbin/chown minix-base
+./sbin/dhcpcd minix-base
./sbin/fsck minix-base
./sbin/fsck_ext2fs minix-base
./sbin/fsck_mfs minix-base
./usr/libdata/debug/sbin/blacklistctl.debug minix-debug debug
./usr/libdata/debug/sbin/blacklistd.debug minix-debug debug
./usr/libdata/debug/sbin/chown.debug minix-debug debug
+./usr/libdata/debug/sbin/dhcpcd.debug minix-debug debug
./usr/libdata/debug/sbin/fsck.debug minix-debug debug
./usr/libdata/debug/sbin/fsck_ext2fs.debug minix-debug debug
./usr/libdata/debug/sbin/fsck_mfs.debug minix-debug debug
./usr/man/man5/cpio.5 minix-man
./usr/man/man5/crontab.5 minix-man
./usr/man/man5/dhcp.conf.5 minix-man obsolete
+./usr/man/man5/dhcpcd.conf.5 minix-man
./usr/man/man5/dir.5 minix-man obsolete
./usr/man/man5/editrc.5 minix-man
./usr/man/man5/ethers.5 minix-man obsolete
./usr/man/man8/cron.8 minix-man
./usr/man/man8/dev_mkdb.8 minix-man
./usr/man/man8/devsize.8 minix-man
+./usr/man/man8/dhcpcd-run-hooks.8 minix-man
+./usr/man/man8/dhcpcd.8 minix-man
./usr/man/man8/dhcpd.8 minix-man obsolete
./usr/man/man8/diskctl.8 minix-man
./usr/man/man8/fbdctl.8 minix-man
.for subdir in . defaults mtree rc.d root skel
${MAKEDIRTARGET} ${subdir} configinstall
.endfor
+ ${MAKEDIRTARGET} ${NETBSDSRCDIR}/external/bsd/dhcpcd/sbin/dhcpcd configinstall
${_MKMSG_INSTALL} ${DESTDIR}/usr/lib/fonts
${INSTALL_DIR} ${DESTDIR}/usr/lib/fonts
${INSTALL_FILE} -m ${BINMODE} -o ${BINOWN} -g ${BINGRP} ${NETBSDSRCDIR}/etc/fonts/*.fnt ${DESTDIR}/usr/lib/fonts/
./home
./lib
./libexec
+./libexec/dhcpcd-hooks
./libexec/resolvconf
./mnt
./proc
\
bootconf.sh \
\
- \
+ dhcpcd \
fsck ftpd \
\
\
--- /dev/null
+#!/bin/sh
+
+# PROVIDE: dhcpcd
+# REQUIRE: network mountcritlocal
+# BEFORE: NETWORKING
+
+$_rc_subr_loaded . /etc/rc.subr
+
+name=dhcpcd
+rcvar=$name
+command=/sbin/$name
+extra_commands="reload"
+
+load_rc_config $name
+
+# If the last argument to dhcpcd is a valid interface and the prior argument
+# is not then dhcpcd will start on one interface only and create a pidfile
+# based on the interface name. See PR bin/43490.
+if [ -n "$flags" ]; then
+ myflags=$flags
+else
+ eval myflags=\$${name}_flags
+fi
+ifname="${myflags##* }"
+myflags="${myflags%% $ifname}"
+last_flag="${myflags##* }"
+if /sbin/ifconfig "$ifname" >/dev/null 2>&1 &&
+ ! /sbin/ifconfig "$last_flag" >/dev/null 2>&1
+then
+ pidfile=/var/run/$name-"$ifname".pid
+else
+ pidfile=/var/run/$name.pid
+fi
+unset myflags ifname last_flag
+
+run_rc_command "$1"
.include <bsd.own.mk>
#MINIX:
-SUBDIR= byacc \
+SUBDIR= byacc dhcpcd \
fetch file flex less \
libarchive libevent mdocml \
openresolv tmux top
--- /dev/null
+# $NetBSD: Makefile,v 1.1 2008/07/27 19:31:03 joerg Exp $
+
+SUBDIR= sbin
+
+.include <bsd.subdir.mk>
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: arp.c,v 1.14 2015/07/09 10:15:34 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/socket.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE 5
+#include "config.h"
+#include "arp.h"
+#include "if.h"
+#include "ipv4.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+
+#define ARP_LEN \
+ (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN))
+
+static ssize_t
+arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip)
+{
+ uint8_t arp_buffer[ARP_LEN];
+ struct arphdr ar;
+ size_t len;
+ uint8_t *p;
+
+ ar.ar_hrd = htons(ifp->family);
+ ar.ar_pro = htons(ETHERTYPE_IP);
+ ar.ar_hln = ifp->hwlen;
+ ar.ar_pln = sizeof(sip);
+ ar.ar_op = htons(ARPOP_REQUEST);
+
+ p = arp_buffer;
+ len = 0;
+
+#define CHECK(fun, b, l) \
+ do { \
+ if (len + (l) > sizeof(arp_buffer)) \
+ goto eexit; \
+ fun(p, (b), (l)); \
+ p += (l); \
+ len += (l); \
+ } while (/* CONSTCOND */ 0)
+#define APPEND(b, l) CHECK(memcpy, b, l)
+#define ZERO(l) CHECK(memset, 0, l)
+
+ APPEND(&ar, sizeof(ar));
+ APPEND(ifp->hwaddr, ifp->hwlen);
+ APPEND(&sip, sizeof(sip));
+ ZERO(ifp->hwlen);
+ APPEND(&tip, sizeof(tip));
+ return if_sendrawpacket(ifp, ETHERTYPE_ARP, arp_buffer, len);
+
+eexit:
+ errno = ENOBUFS;
+ return -1;
+}
+
+void
+arp_report_conflicted(const struct arp_state *astate, const struct arp_msg *amsg)
+{
+
+ if (amsg != NULL) {
+ char buf[HWADDR_LEN * 3];
+
+ logger(astate->iface->ctx, LOG_ERR,
+ "%s: hardware address %s claims %s",
+ astate->iface->name,
+ hwaddr_ntoa(amsg->sha, astate->iface->hwlen,
+ buf, sizeof(buf)),
+ inet_ntoa(astate->failed));
+ } else
+ logger(astate->iface->ctx, LOG_ERR,
+ "%s: DAD detected %s",
+ astate->iface->name, inet_ntoa(astate->failed));
+}
+
+static void
+arp_packet(void *arg)
+{
+ struct interface *ifp = arg;
+ const struct interface *ifn;
+ uint8_t arp_buffer[ARP_LEN];
+ struct arphdr ar;
+ struct arp_msg arm;
+ ssize_t bytes;
+ struct iarp_state *state;
+ struct arp_state *astate, *astaten;
+ unsigned char *hw_s, *hw_t;
+ int flags;
+
+ state = ARP_STATE(ifp);
+ flags = 0;
+ while (!(flags & RAW_EOF)) {
+ bytes = if_readrawpacket(ifp, ETHERTYPE_ARP,
+ arp_buffer, sizeof(arp_buffer), &flags);
+ if (bytes == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: arp if_readrawpacket: %m", ifp->name);
+ arp_close(ifp);
+ return;
+ }
+ /* We must have a full ARP header */
+ if ((size_t)bytes < sizeof(ar))
+ continue;
+ memcpy(&ar, arp_buffer, sizeof(ar));
+ /* Families must match */
+ if (ar.ar_hrd != htons(ifp->family))
+ continue;
+ /* Protocol must be IP. */
+ if (ar.ar_pro != htons(ETHERTYPE_IP))
+ continue;
+ if (ar.ar_pln != sizeof(arm.sip.s_addr))
+ continue;
+ /* Only these types are recognised */
+ if (ar.ar_op != htons(ARPOP_REPLY) &&
+ ar.ar_op != htons(ARPOP_REQUEST))
+ continue;
+
+ /* Get pointers to the hardware addreses */
+ hw_s = arp_buffer + sizeof(ar);
+ hw_t = hw_s + ar.ar_hln + ar.ar_pln;
+ /* Ensure we got all the data */
+ if ((hw_t + ar.ar_hln + ar.ar_pln) - arp_buffer > bytes)
+ continue;
+ /* Ignore messages from ourself */
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ar.ar_hln == ifn->hwlen &&
+ memcmp(hw_s, ifn->hwaddr, ifn->hwlen) == 0)
+ break;
+ }
+ if (ifn)
+ continue;
+ /* Copy out the HW and IP addresses */
+ memcpy(&arm.sha, hw_s, ar.ar_hln);
+ memcpy(&arm.sip.s_addr, hw_s + ar.ar_hln, ar.ar_pln);
+ memcpy(&arm.tha, hw_t, ar.ar_hln);
+ memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln);
+
+ /* Run the conflicts */
+ TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) {
+ if (astate->conflicted_cb)
+ astate->conflicted_cb(astate, &arm);
+ }
+ }
+}
+
+static void
+arp_open(struct interface *ifp)
+{
+ struct iarp_state *state;
+
+ state = ARP_STATE(ifp);
+ if (state->fd == -1) {
+ state->fd = if_openrawsocket(ifp, ETHERTYPE_ARP);
+ if (state->fd == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m",
+ __func__, ifp->name);
+ return;
+ }
+ eloop_event_add(ifp->ctx->eloop, state->fd,
+ arp_packet, ifp, NULL, NULL);
+ }
+}
+
+static void
+arp_announced(void *arg)
+{
+ struct arp_state *astate = arg;
+
+ if (astate->announced_cb) {
+ astate->announced_cb(astate);
+ return;
+ }
+
+ /* Nothing more to do, so free us */
+ arp_free(astate);
+}
+
+static void
+arp_announce1(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct interface *ifp = astate->iface;
+
+ if (++astate->claims < ANNOUNCE_NUM)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ARP announcing %s (%d of %d), "
+ "next in %d.0 seconds",
+ ifp->name, inet_ntoa(astate->addr),
+ astate->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
+ else
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ARP announcing %s (%d of %d)",
+ ifp->name, inet_ntoa(astate->addr),
+ astate->claims, ANNOUNCE_NUM);
+ if (arp_request(ifp, astate->addr.s_addr, astate->addr.s_addr) == -1)
+ logger(ifp->ctx, LOG_ERR, "send_arp: %m");
+ eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT,
+ astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced,
+ astate);
+}
+
+void
+arp_announce(struct arp_state *astate)
+{
+
+ arp_open(astate->iface);
+ astate->claims = 0;
+ arp_announce1(astate);
+}
+
+static void
+arp_probed(void *arg)
+{
+ struct arp_state *astate = arg;
+
+ astate->probed_cb(astate);
+}
+
+static void
+arp_probe1(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct interface *ifp = astate->iface;
+ struct timespec tv;
+
+ if (++astate->probes < PROBE_NUM) {
+ tv.tv_sec = PROBE_MIN;
+ tv.tv_nsec = (suseconds_t)arc4random_uniform(
+ (PROBE_MAX - PROBE_MIN) * NSEC_PER_SEC);
+ timespecnorm(&tv);
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe1, astate);
+ } else {
+ tv.tv_sec = ANNOUNCE_WAIT;
+ tv.tv_nsec = 0;
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probed, astate);
+ }
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ARP probing %s (%d of %d), next in %0.1f seconds",
+ ifp->name, inet_ntoa(astate->addr),
+ astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM,
+ timespec_to_double(&tv));
+ if (arp_request(ifp, 0, astate->addr.s_addr) == -1)
+ logger(ifp->ctx, LOG_ERR, "send_arp: %m");
+}
+
+void
+arp_probe(struct arp_state *astate)
+{
+
+ arp_open(astate->iface);
+ astate->probes = 0;
+ logger(astate->iface->ctx, LOG_DEBUG, "%s: probing for %s",
+ astate->iface->name, inet_ntoa(astate->addr));
+ arp_probe1(astate);
+}
+
+struct arp_state *
+arp_find(struct interface *ifp, const struct in_addr *addr)
+{
+ struct iarp_state *state;
+ struct arp_state *astate;
+
+ if ((state = ARP_STATE(ifp)) == NULL)
+ goto out;
+ TAILQ_FOREACH(astate, &state->arp_states, next) {
+ if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp)
+ return astate;
+ }
+out:
+ errno = ESRCH;
+ return NULL;
+}
+
+struct arp_state *
+arp_new(struct interface *ifp, const struct in_addr *addr)
+{
+ struct iarp_state *state;
+ struct arp_state *astate;
+
+ if ((state = ARP_STATE(ifp)) == NULL) {
+ ifp->if_data[IF_DATA_ARP] = malloc(sizeof(*state));
+ state = ARP_STATE(ifp);
+ if (state == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ state->fd = -1;
+ TAILQ_INIT(&state->arp_states);
+ } else {
+ if (addr && (astate = arp_find(ifp, addr)))
+ return astate;
+ }
+
+ if ((astate = calloc(1, sizeof(*astate))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__);
+ return NULL;
+ }
+ astate->iface = ifp;
+ if (addr)
+ astate->addr = *addr;
+ state = ARP_STATE(ifp);
+ TAILQ_INSERT_TAIL(&state->arp_states, astate, next);
+ return astate;
+}
+
+void
+arp_cancel(struct arp_state *astate)
+{
+
+ eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate);
+}
+
+void
+arp_free(struct arp_state *astate)
+{
+
+ if (astate != NULL) {
+ struct interface *ifp;
+ struct iarp_state *state;
+
+ ifp = astate->iface;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, astate);
+ state = ARP_STATE(ifp);
+ TAILQ_REMOVE(&state->arp_states, astate, next);
+ if (astate->free_cb)
+ astate->free_cb(astate);
+ free(astate);
+
+ /* If there are no more ARP states, close the socket. */
+ if (state->fd != -1 &&
+ TAILQ_FIRST(&state->arp_states) == NULL)
+ {
+ eloop_event_delete(ifp->ctx->eloop, state->fd);
+ close(state->fd);
+ free(state);
+ ifp->if_data[IF_DATA_ARP] = NULL;
+ }
+ }
+}
+
+void
+arp_free_but(struct arp_state *astate)
+{
+ struct iarp_state *state;
+ struct arp_state *p, *n;
+
+ state = ARP_STATE(astate->iface);
+ TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) {
+ if (p != astate)
+ arp_free(p);
+ }
+}
+
+void
+arp_close(struct interface *ifp)
+{
+ struct iarp_state *state;
+ struct arp_state *astate;
+
+ /* Freeing the last state will also free the main state,
+ * so test for both. */
+ for (;;) {
+ if ((state = ARP_STATE(ifp)) == NULL ||
+ (astate = TAILQ_FIRST(&state->arp_states)) == NULL)
+ break;
+ arp_free(astate);
+ }
+}
+
+void
+arp_handleifa(int cmd, struct interface *ifp, const struct in_addr *addr,
+ int flags)
+{
+#ifdef IN_IFF_DUPLICATED
+ struct iarp_state *state;
+ struct arp_state *astate, *asn;
+
+ if (cmd != RTM_NEWADDR || (state = ARP_STATE(ifp)) == NULL)
+ return;
+
+ TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, asn) {
+ if (astate->addr.s_addr == addr->s_addr) {
+ if (flags & IN_IFF_DUPLICATED) {
+ if (astate->conflicted_cb)
+ astate->conflicted_cb(astate, NULL);
+ } else if (!(flags & IN_IFF_NOTUSEABLE)) {
+ if (astate->probed_cb)
+ astate->probed_cb(astate);
+ }
+ }
+ }
+#else
+ UNUSED(cmd);
+ UNUSED(ifp);
+ UNUSED(addr);
+ UNUSED(flags);
+#endif
+}
--- /dev/null
+/* $NetBSD: arp.h,v 1.11 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef ARP_H
+#define ARP_H
+
+/* ARP timings from RFC5227 */
+#define PROBE_WAIT 1
+#define PROBE_NUM 3
+#define PROBE_MIN 1
+#define PROBE_MAX 2
+#define ANNOUNCE_WAIT 2
+#define ANNOUNCE_NUM 2
+#define ANNOUNCE_INTERVAL 2
+#define MAX_CONFLICTS 10
+#define RATE_LIMIT_INTERVAL 60
+#define DEFEND_INTERVAL 10
+
+#include "dhcpcd.h"
+
+struct arp_msg {
+ uint16_t op;
+ unsigned char sha[HWADDR_LEN];
+ struct in_addr sip;
+ unsigned char tha[HWADDR_LEN];
+ struct in_addr tip;
+};
+
+struct arp_state {
+ TAILQ_ENTRY(arp_state) next;
+ struct interface *iface;
+
+ void (*probed_cb)(struct arp_state *);
+ void (*announced_cb)(struct arp_state *);
+ void (*conflicted_cb)(struct arp_state *, const struct arp_msg *);
+ void (*free_cb)(struct arp_state *);
+
+ struct in_addr addr;
+ int probes;
+ int claims;
+ struct in_addr failed;
+};
+TAILQ_HEAD(arp_statehead, arp_state);
+
+struct iarp_state {
+ int fd;
+ struct arp_statehead arp_states;
+};
+
+#define ARP_STATE(ifp) \
+ ((struct iarp_state *)(ifp)->if_data[IF_DATA_ARP])
+#define ARP_CSTATE(ifp) \
+ ((const struct iarp_state *)(ifp)->if_data[IF_DATA_ARP])
+
+#ifdef INET
+void arp_report_conflicted(const struct arp_state *, const struct arp_msg *);
+void arp_announce(struct arp_state *);
+void arp_probe(struct arp_state *);
+struct arp_state *arp_new(struct interface *, const struct in_addr *);
+void arp_cancel(struct arp_state *);
+void arp_free(struct arp_state *);
+void arp_free_but(struct arp_state *);
+struct arp_state *arp_find(struct interface *, const struct in_addr *);
+void arp_close(struct interface *);
+
+void arp_handleifa(int, struct interface *, const struct in_addr *, int);
+#else
+#define arp_close(a) {}
+#endif
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: auth.c,v 1.10 2015/07/09 10:15:34 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/file.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "auth.h"
+#include "crypt/crypt.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "dhcpcd.h"
+
+#ifdef __sun
+#define htonll
+#define ntohll
+#endif
+
+#ifndef htonll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static inline uint64_t
+htonll(uint64_t x)
+{
+
+ return (uint64_t)htonl((uint32_t)(x >> 32)) |
+ (uint64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
+}
+#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define htonll(x) (x)
+#endif
+#endif /* htonll */
+
+#ifndef ntohll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static inline uint64_t
+ntohll(uint64_t x)
+{
+
+ return (uint64_t)ntohl((uint32_t)(x >> 32)) |
+ (uint64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
+}
+#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define ntohll(x) (x)
+#endif
+#endif /* ntohll */
+
+#define HMAC_LENGTH 16
+
+void
+dhcp_auth_reset(struct authstate *state)
+{
+
+ state->replay = 0;
+ if (state->token) {
+ free(state->token->key);
+ free(state->token->realm);
+ free(state->token);
+ state->token = NULL;
+ }
+ if (state->reconf) {
+ free(state->reconf->key);
+ free(state->reconf->realm);
+ free(state->reconf);
+ state->reconf = NULL;
+ }
+}
+
+/*
+ * Authenticate a DHCP message.
+ * m and mlen refer to the whole message.
+ * t is the DHCP type, pass it 4 or 6.
+ * data and dlen refer to the authentication option within the message.
+ */
+const struct token *
+dhcp_auth_validate(struct authstate *state, const struct auth *auth,
+ const uint8_t *m, size_t mlen, int mp, int mt,
+ const uint8_t *data, size_t dlen)
+{
+ uint8_t protocol, algorithm, rdm, *mm, type;
+ uint64_t replay;
+ uint32_t secretid;
+ const uint8_t *d, *realm;
+ size_t realm_len;
+ const struct token *t;
+ time_t now;
+ uint8_t hmac[HMAC_LENGTH];
+
+ if (dlen < 3 + sizeof(replay)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
+ if (data < m || data > m + mlen || data + dlen > m + mlen) {
+ errno = ERANGE;
+ return NULL;
+ }
+
+ d = data;
+ protocol = *d++;
+ algorithm = *d++;
+ rdm = *d++;
+ if (!(auth->options & DHCPCD_AUTH_SEND)) {
+ /* If we didn't send any authorisation, it can only be a
+ * reconfigure key */
+ if (protocol != AUTH_PROTO_RECONFKEY) {
+ errno = EINVAL;
+ return NULL;
+ }
+ } else if (protocol != auth->protocol ||
+ algorithm != auth->algorithm ||
+ rdm != auth->rdm)
+ {
+ /* As we don't require authentication, we should still
+ * accept a reconfigure key */
+ if (protocol != AUTH_PROTO_RECONFKEY ||
+ auth->options & DHCPCD_AUTH_REQUIRE)
+ {
+ errno = EPERM;
+ return NULL;
+ }
+ }
+ dlen -= 3;
+
+ memcpy(&replay, d, sizeof(replay));
+ replay = ntohll(replay);
+ if (state->token) {
+ if (state->replay == (replay ^ 0x8000000000000000ULL)) {
+ /* We don't know if the singular point is increasing
+ * or decreasing. */
+ errno = EPERM;
+ return NULL;
+ }
+ if ((uint64_t)(replay - state->replay) <= 0) {
+ /* Replay attack detected */
+ errno = EPERM;
+ return NULL;
+ }
+ }
+ d+= sizeof(replay);
+ dlen -= sizeof(replay);
+
+ realm = NULL;
+ realm_len = 0;
+
+ /* Extract realm and secret.
+ * Rest of data is MAC. */
+ switch (protocol) {
+ case AUTH_PROTO_TOKEN:
+ secretid = 0;
+ break;
+ case AUTH_PROTO_DELAYED:
+ if (dlen < sizeof(secretid) + sizeof(hmac)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ memcpy(&secretid, d, sizeof(secretid));
+ d += sizeof(secretid);
+ dlen -= sizeof(secretid);
+ break;
+ case AUTH_PROTO_DELAYEDREALM:
+ if (dlen < sizeof(secretid) + sizeof(hmac)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
+ if (realm_len) {
+ realm = d;
+ d += realm_len;
+ dlen -= realm_len;
+ }
+ memcpy(&secretid, d, sizeof(secretid));
+ d += sizeof(secretid);
+ dlen -= sizeof(secretid);
+ break;
+ case AUTH_PROTO_RECONFKEY:
+ if (dlen != 1 + 16) {
+ errno = EINVAL;
+ return NULL;
+ }
+ type = *d++;
+ dlen--;
+ switch (type) {
+ case 1:
+ if ((mp == 4 && mt == DHCP_ACK) ||
+ (mp == 6 && mt == DHCP6_REPLY))
+ {
+ if (state->reconf == NULL) {
+ state->reconf =
+ malloc(sizeof(*state->reconf));
+ if (state->reconf == NULL)
+ return NULL;
+ state->reconf->key = malloc(16);
+ if (state->reconf->key == NULL) {
+ free(state->reconf);
+ state->reconf = NULL;
+ return NULL;
+ }
+ state->reconf->secretid = 0;
+ state->reconf->expire = 0;
+ state->reconf->realm = NULL;
+ state->reconf->realm_len = 0;
+ state->reconf->key_len = 16;
+ }
+ memcpy(state->reconf->key, d, 16);
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (state->reconf == NULL)
+ errno = ENOENT;
+ /* Free the old token so we log acceptance */
+ if (state->token) {
+ free(state->token);
+ state->token = NULL;
+ }
+ /* Nothing to validate, just accepting the key */
+ return state->reconf;
+ case 2:
+ if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
+ (mp == 6 && mt == DHCP6_RECONFIGURE)))
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (state->reconf == NULL) {
+ errno = ENOENT;
+ return NULL;
+ }
+ t = state->reconf;
+ goto gottoken;
+ default:
+ errno = EINVAL;
+ return NULL;
+ }
+ default:
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ /* Find a token for the realm and secret */
+ secretid = ntohl(secretid);
+ TAILQ_FOREACH(t, &auth->tokens, next) {
+ if (t->secretid == secretid &&
+ t->realm_len == realm_len &&
+ (t->realm_len == 0 ||
+ memcmp(t->realm, realm, t->realm_len) == 0))
+ break;
+ }
+ if (t == NULL) {
+ errno = ESRCH;
+ return NULL;
+ }
+ if (t->expire) {
+ if (time(&now) == -1)
+ return NULL;
+ if (t->expire < now) {
+ errno = EFAULT;
+ return NULL;
+ }
+ }
+
+gottoken:
+ /* First message from the server */
+ if (state->token &&
+ (state->token->secretid != t->secretid ||
+ state->token->realm_len != t->realm_len ||
+ memcmp(state->token->realm, t->realm, t->realm_len)))
+ {
+ errno = EPERM;
+ return NULL;
+ }
+
+ /* Special case as no hashing needs to be done. */
+ if (protocol == AUTH_PROTO_TOKEN) {
+ if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
+ errno = EPERM;
+ return NULL;
+ }
+ goto finish;
+ }
+
+ /* Make a duplicate of the message, but zero out the MAC part */
+ mm = malloc(mlen);
+ if (mm == NULL)
+ return NULL;
+ memcpy(mm, m, mlen);
+ memset(mm + (d - m), 0, dlen);
+
+ /* RFC3318, section 5.2 - zero giaddr and hops */
+ if (mp == 4) {
+ *(mm + offsetof(struct dhcp_message, hwopcount)) = '\0';
+ memset(mm + offsetof(struct dhcp_message, giaddr), 0, 4);
+ }
+
+ memset(hmac, 0, sizeof(hmac));
+ switch (algorithm) {
+ case AUTH_ALG_HMAC_MD5:
+ hmac_md5(mm, mlen, t->key, t->key_len, hmac);
+ break;
+ default:
+ errno = ENOSYS;
+ free(mm);
+ return NULL;
+ }
+
+ free(mm);
+ if (memcmp(d, &hmac, dlen)) {
+ errno = EPERM;
+ return NULL;
+ }
+
+finish:
+ /* If we got here then authentication passed */
+ state->replay = replay;
+ if (state->token == NULL) {
+ /* We cannot just save a pointer because a reconfigure will
+ * recreate the token list. So we duplicate it. */
+ state->token = malloc(sizeof(*state->token));
+ if (state->token) {
+ state->token->secretid = t->secretid;
+ state->token->key = malloc(t->key_len);
+ if (state->token->key) {
+ state->token->key_len = t->key_len;
+ memcpy(state->token->key, t->key, t->key_len);
+ } else {
+ free(state->token);
+ state->token = NULL;
+ return NULL;
+ }
+ if (t->realm_len) {
+ state->token->realm = malloc(t->realm_len);
+ if (state->token->realm) {
+ state->token->realm_len = t->realm_len;
+ memcpy(state->token->realm, t->realm,
+ t->realm_len);
+ } else {
+ free(state->token->key);
+ free(state->token);
+ state->token = NULL;
+ return NULL;
+ }
+ } else {
+ state->token->realm = NULL;
+ state->token->realm_len = 0;
+ }
+ }
+ /* If we cannot save the token, we must invalidate */
+ if (state->token == NULL)
+ return NULL;
+ }
+
+ return t;
+}
+
+static uint64_t
+get_next_rdm_monotonic_counter(struct auth *auth)
+{
+ FILE *fp;
+ uint64_t rdm;
+#ifdef LOCK_EX
+ int flocked;
+#endif
+
+ fp = fopen(RDM_MONOFILE, "r+");
+ if (fp == NULL) {
+ if (errno != ENOENT)
+ return ++auth->last_replay; /* report error? */
+ fp = fopen(RDM_MONOFILE, "w");
+ if (fp == NULL)
+ return ++auth->last_replay; /* report error? */
+#ifdef LOCK_EX
+ flocked = flock(fileno(fp), LOCK_EX);
+#endif
+ rdm = 0;
+ } else {
+#ifdef LOCK_EX
+ flocked = flock(fileno(fp), LOCK_EX);
+#endif
+ if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1)
+ rdm = 0; /* truncated? report error? */
+ }
+
+ rdm++;
+ if (fseek(fp, 0, SEEK_SET) == -1 ||
+ ftruncate(fileno(fp), 0) == -1 ||
+ fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 ||
+ fflush(fp) == EOF)
+ {
+ if (!auth->last_replay_set) {
+ auth->last_replay = rdm;
+ auth->last_replay_set = 1;
+ } else
+ rdm = ++auth->last_replay;
+ /* report error? */
+ }
+#ifdef LOCK_EX
+ if (flocked == 0)
+ flock(fileno(fp), LOCK_UN);
+#endif
+ fclose(fp);
+ return rdm;
+}
+
+#define JAN_1970 2208988800U /* 1970 - 1900 in seconds */
+static uint64_t
+get_next_rdm_monotonic_clock(struct auth *auth)
+{
+ struct timespec ts;
+ uint32_t pack[2];
+ double frac;
+ uint64_t rdm;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ return ++auth->last_replay; /* report error? */
+ pack[0] = htonl((uint32_t)ts.tv_sec + JAN_1970);
+ frac = ((double)ts.tv_nsec / 1e9 * 0x100000000ULL);
+ pack[1] = htonl((uint32_t)frac);
+
+ memcpy(&rdm, &pack, sizeof(rdm));
+ return rdm;
+}
+
+static uint64_t
+get_next_rdm_monotonic(struct auth *auth)
+{
+
+ if (auth->options & DHCPCD_AUTH_RDM_COUNTER)
+ return get_next_rdm_monotonic_counter(auth);
+ return get_next_rdm_monotonic_clock(auth);
+}
+
+/*
+ * Encode a DHCP message.
+ * Either we know which token to use from the server response
+ * or we are using a basic configuration token.
+ * token is the token to encrypt with.
+ * m and mlen refer to the whole message.
+ * mp is the DHCP type, pass it 4 or 6.
+ * mt is the DHCP message type.
+ * data and dlen refer to the authentication option within the message.
+ */
+ssize_t
+dhcp_auth_encode(struct auth *auth, const struct token *t,
+ uint8_t *m, size_t mlen, int mp, int mt,
+ uint8_t *data, size_t dlen)
+{
+ uint64_t rdm;
+ uint8_t hmac[HMAC_LENGTH];
+ time_t now;
+ uint8_t hops, *p, info;
+ uint32_t giaddr, secretid;
+
+ if (auth->protocol == 0 && t == NULL) {
+ TAILQ_FOREACH(t, &auth->tokens, next) {
+ if (t->secretid == 0 &&
+ t->realm_len == 0)
+ break;
+ }
+ if (t == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (t->expire) {
+ if (time(&now) == -1)
+ return -1;
+ if (t->expire < now) {
+ errno = EPERM;
+ return -1;
+ }
+ }
+ }
+
+ switch(auth->protocol) {
+ case AUTH_PROTO_TOKEN:
+ case AUTH_PROTO_DELAYED:
+ case AUTH_PROTO_DELAYEDREALM:
+ /* We don't ever send a reconf key */
+ break;
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ switch(auth->algorithm) {
+ case AUTH_ALG_HMAC_MD5:
+ break;
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ switch(auth->rdm) {
+ case AUTH_RDM_MONOTONIC:
+ break;
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ /* DISCOVER or INFORM messages don't write auth info */
+ if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
+ (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
+ info = 0;
+ else
+ info = 1;
+
+ /* Work out the auth area size.
+ * We only need to do this for DISCOVER messages */
+ if (data == NULL) {
+ dlen = 1 + 1 + 1 + 8;
+ switch(auth->protocol) {
+ case AUTH_PROTO_TOKEN:
+ dlen += t->key_len;
+ break;
+ case AUTH_PROTO_DELAYEDREALM:
+ if (info && t)
+ dlen += t->realm_len;
+ /* FALLTHROUGH */
+ case AUTH_PROTO_DELAYED:
+ if (info && t)
+ dlen += sizeof(t->secretid) + sizeof(hmac);
+ break;
+ }
+ return (ssize_t)dlen;
+ }
+
+ if (dlen < 1 + 1 + 1 + 8) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
+ if (data < m || data > m + mlen || data + dlen > m + mlen) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* Write out our option */
+ *data++ = auth->protocol;
+ *data++ = auth->algorithm;
+ *data++ = auth->rdm;
+ switch (auth->rdm) {
+ case AUTH_RDM_MONOTONIC:
+ rdm = get_next_rdm_monotonic(auth);
+ break;
+ default:
+ /* This block appeases gcc, clang doesn't need it */
+ rdm = get_next_rdm_monotonic(auth);
+ break;
+ }
+ rdm = htonll(rdm);
+ memcpy(data, &rdm, 8);
+ data += 8;
+ dlen -= 1 + 1 + 1 + 8;
+
+ /* Special case as no hashing needs to be done. */
+ if (auth->protocol == AUTH_PROTO_TOKEN) {
+ /* Should be impossible, but still */
+ if (t == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (dlen < t->key_len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(data, t->key, t->key_len);
+ return (ssize_t)(dlen - t->key_len);
+ }
+
+ /* DISCOVER or INFORM messages don't write auth info */
+ if (!info)
+ return (ssize_t)dlen;
+
+ /* Loading a saved lease without an authentication option */
+ if (t == NULL)
+ return 0;
+
+ /* Write out the Realm */
+ if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
+ if (dlen < t->realm_len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(data, t->realm, t->realm_len);
+ data += t->realm_len;
+ dlen -= t->realm_len;
+ }
+
+ /* Write out the SecretID */
+ if (auth->protocol == AUTH_PROTO_DELAYED ||
+ auth->protocol == AUTH_PROTO_DELAYEDREALM)
+ {
+ if (dlen < sizeof(t->secretid)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ secretid = htonl(t->secretid);
+ memcpy(data, &secretid, sizeof(secretid));
+ data += sizeof(secretid);
+ dlen -= sizeof(secretid);
+ }
+
+ /* Zero what's left, the MAC */
+ memset(data, 0, dlen);
+
+ /* RFC3318, section 5.2 - zero giaddr and hops */
+ if (mp == 4) {
+ p = m + offsetof(struct dhcp_message, hwopcount);
+ hops = *p;
+ *p = '\0';
+ p = m + offsetof(struct dhcp_message, giaddr);
+ memcpy(&giaddr, p, sizeof(giaddr));
+ memset(p, 0, sizeof(giaddr));
+ } else {
+ /* appease GCC again */
+ hops = 0;
+ giaddr = 0;
+ }
+
+ /* Create our hash and write it out */
+ switch(auth->algorithm) {
+ case AUTH_ALG_HMAC_MD5:
+ hmac_md5(m, mlen, t->key, t->key_len, hmac);
+ memcpy(data, hmac, sizeof(hmac));
+ break;
+ }
+
+ /* RFC3318, section 5.2 - restore giaddr and hops */
+ if (mp == 4) {
+ p = m + offsetof(struct dhcp_message, hwopcount);
+ *p = hops;
+ p = m + offsetof(struct dhcp_message, giaddr);
+ memcpy(p, &giaddr, sizeof(giaddr));
+ }
+
+ /* Done! */
+ return (int)(dlen - sizeof(hmac)); /* should be zero */
+}
--- /dev/null
+/* $NetBSD: auth.h,v 1.9 2015/05/16 23:31:32 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef AUTH_H
+#define AUTH_H
+
+#include "config.h"
+
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h>
+#endif
+
+#define DHCPCD_AUTH_SEND (1 << 0)
+#define DHCPCD_AUTH_REQUIRE (1 << 1)
+#define DHCPCD_AUTH_RDM_COUNTER (1 << 2)
+
+#define DHCPCD_AUTH_SENDREQUIRE (DHCPCD_AUTH_SEND | DHCPCD_AUTH_REQUIRE)
+
+#define AUTH_PROTO_TOKEN 0
+#define AUTH_PROTO_DELAYED 1
+#define AUTH_PROTO_DELAYEDREALM 2
+#define AUTH_PROTO_RECONFKEY 3
+
+#define AUTH_ALG_HMAC_MD5 1
+
+#define AUTH_RDM_MONOTONIC 0
+
+struct token {
+ TAILQ_ENTRY(token) next;
+ uint32_t secretid;
+ size_t realm_len;
+ unsigned char *realm;
+ size_t key_len;
+ unsigned char *key;
+ time_t expire;
+};
+
+TAILQ_HEAD(token_head, token);
+
+struct auth {
+ int options;
+ uint8_t protocol;
+ uint8_t algorithm;
+ uint8_t rdm;
+ uint64_t last_replay;
+ uint8_t last_replay_set;
+ struct token_head tokens;
+};
+
+struct authstate {
+ uint64_t replay;
+ struct token *token;
+ struct token *reconf;
+};
+
+void dhcp_auth_reset(struct authstate *);
+
+const struct token * dhcp_auth_validate(struct authstate *,
+ const struct auth *,
+ const uint8_t *, size_t, int, int,
+ const uint8_t *, size_t);
+
+ssize_t dhcp_auth_encode(struct auth *, const struct token *,
+ uint8_t *, size_t, int, int,
+ uint8_t *, size_t);
+#endif
--- /dev/null
+/* $NetBSD: bpf-filter.h,v 1.9 2014/11/07 20:51:02 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2008 Roy Marples <roy@marples.name>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef BPF_ETHCOOK
+# define BPF_ETHCOOK 0
+#endif
+#ifndef BPF_WHOLEPACKET
+# define BPF_WHOLEPACKET ~0U
+#endif
+static const struct bpf_insn arp_bpf_filter [] = {
+#ifndef BPF_SKIPTYPE
+ /* Make sure this is an ARP packet... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3),
+#endif
+ /* Make sure this is an ARP REQUEST... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),
+ /* or ARP REPLY... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1),
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET),
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define arp_bpf_filter_len sizeof(arp_bpf_filter) / sizeof(arp_bpf_filter[0])
+
+
+/* dhcp_bpf_filter taken from bpf.c in dhcp-3.1.0
+ *
+ * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * http://www.isc.org/
+ */
+
+static const struct bpf_insn dhcp_bpf_filter [] = {
+#ifndef BPF_SKIPTYPE
+ /* Make sure this is an IP packet... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+#endif
+ /* Make sure it's a UDP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+ /* Make sure this isn't a fragment... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+ /* Get the IP header length... */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 + BPF_ETHCOOK),
+ /* Make sure it's to the right port... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1),
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET),
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define dhcp_bpf_filter_len sizeof(dhcp_bpf_filter) / sizeof(dhcp_bpf_filter[0])
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: common.c,v 1.14 2015/07/09 10:15:34 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#ifdef BSD
+# include <paths.h>
+#endif
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "dhcpcd.h"
+#include "if-options.h"
+
+#ifndef _PATH_DEVNULL
+# define _PATH_DEVNULL "/dev/null"
+#endif
+
+const char *
+get_hostname(char *buf, size_t buflen, int short_hostname)
+{
+ char *p;
+
+ if (gethostname(buf, buflen) != 0)
+ return NULL;
+ buf[buflen - 1] = '\0';
+ if (strcmp(buf, "(none)") == 0 ||
+ strcmp(buf, "localhost") == 0 ||
+ strncmp(buf, "localhost.", strlen("localhost.")) == 0 ||
+ buf[0] == '.')
+ return NULL;
+
+ if (short_hostname) {
+ p = strchr(buf, '.');
+ if (p)
+ *p = '\0';
+ }
+
+ return buf;
+}
+
+#if USE_LOGFILE
+void
+logger_open(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->logfile) {
+ int f = O_CREAT | O_APPEND | O_TRUNC;
+
+#ifdef O_CLOEXEC
+ f |= O_CLOEXEC;
+#endif
+ ctx->log_fd = open(ctx->logfile, O_WRONLY | f, 0644);
+ if (ctx->log_fd == -1)
+ warn("open: %s", ctx->logfile);
+#ifndef O_CLOEXEC
+ else {
+ if (fcntl(ctx->log_fd, F_GETFD, &f) == -1 ||
+ fcntl(ctx->log_fd, F_SETFD, f | FD_CLOEXEC) == -1)
+ warn("fcntl: %s", ctx->logfile);
+ }
+#endif
+ } else
+ openlog(PACKAGE, LOG_PID, LOG_DAEMON);
+}
+
+void
+logger_close(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->log_fd != -1) {
+ close(ctx->log_fd);
+ ctx->log_fd = -1;
+ }
+ closelog();
+}
+
+void
+logger(struct dhcpcd_ctx *ctx, int pri, const char *fmt, ...)
+{
+ va_list va;
+ int serrno;
+#ifndef HAVE_PRINTF_M
+ char fmt_cpy[1024];
+#endif
+
+ if (pri >= LOG_DEBUG && ctx && !(ctx->options & DHCPCD_DEBUG))
+ return;
+
+ serrno = errno;
+ va_start(va, fmt);
+
+#ifndef HAVE_PRINTF_M
+ /* Print strerrno(errno) in place of %m */
+ if (ctx == NULL || !(ctx->options & DHCPCD_QUIET) || ctx->log_fd != -1)
+ {
+ const char *p;
+ char *fp = fmt_cpy, *serr = NULL;
+ size_t fmt_left = sizeof(fmt_cpy) - 1, fmt_wrote;
+
+ for (p = fmt; *p != '\0'; p++) {
+ if (p[0] == '%' && p[1] == '%') {
+ if (fmt_left < 2)
+ break;
+ *fp++ = '%';
+ *fp++ = '%';
+ fmt_left -= 2;
+ p++;
+ } else if (p[0] == '%' && p[1] == 'm') {
+ if (serr == NULL)
+ serr = strerror(serrno);
+ fmt_wrote = strlcpy(fp, serr, fmt_left);
+ if (fmt_wrote > fmt_left)
+ break;
+ fp += fmt_wrote;
+ fmt_left -= fmt_wrote;
+ p++;
+ } else {
+ *fp++ = *p;
+ --fmt_left;
+ }
+ if (fmt_left == 0)
+ break;
+ }
+ *fp++ = '\0';
+ fmt = fmt_cpy;
+ }
+
+#endif
+
+ if (ctx == NULL || !(ctx->options & DHCPCD_QUIET)) {
+ va_list vac;
+
+ va_copy(vac, va);
+ vfprintf(pri <= LOG_ERR ? stderr : stdout, fmt, vac);
+ fputc('\n', pri <= LOG_ERR ? stderr : stdout);
+ va_end(vac);
+ }
+
+#ifdef HAVE_PRINTF_M
+ errno = serrno;
+#endif
+ if (ctx && ctx->log_fd != -1) {
+ struct timeval tv;
+ char buf[32];
+
+ /* Write the time, syslog style. month day time - */
+ if (gettimeofday(&tv, NULL) != -1) {
+ time_t now;
+ struct tm tmnow;
+
+ tzset();
+ now = tv.tv_sec;
+ localtime_r(&now, &tmnow);
+ strftime(buf, sizeof(buf), "%b %d %T ", &tmnow);
+ dprintf(ctx->log_fd, "%s", buf);
+ }
+
+ vdprintf(ctx->log_fd, fmt, va);
+ dprintf(ctx->log_fd, "\n");
+ } else
+ vsyslog(pri, fmt, va);
+ va_end(va);
+}
+#endif
+
+ssize_t
+setvar(struct dhcpcd_ctx *ctx,
+ char **e, const char *prefix, const char *var, const char *value)
+{
+ size_t len = strlen(var) + strlen(value) + 3;
+
+ if (prefix)
+ len += strlen(prefix) + 1;
+ *e = malloc(len);
+ if (*e == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ if (prefix)
+ snprintf(*e, len, "%s_%s=%s", prefix, var, value);
+ else
+ snprintf(*e, len, "%s=%s", var, value);
+ return (ssize_t)len;
+}
+
+ssize_t
+setvard(struct dhcpcd_ctx *ctx,
+ char **e, const char *prefix, const char *var, size_t value)
+{
+
+ char buffer[32];
+
+ snprintf(buffer, sizeof(buffer), "%zu", value);
+ return setvar(ctx, e, prefix, var, buffer);
+}
+
+ssize_t
+addvar(struct dhcpcd_ctx *ctx,
+ char ***e, const char *prefix, const char *var, const char *value)
+{
+ ssize_t len;
+
+ len = setvar(ctx, *e, prefix, var, value);
+ if (len != -1)
+ (*e)++;
+ return (ssize_t)len;
+}
+
+ssize_t
+addvard(struct dhcpcd_ctx *ctx,
+ char ***e, const char *prefix, const char *var, size_t value)
+{
+ char buffer[32];
+
+ snprintf(buffer, sizeof(buffer), "%zu", value);
+ return addvar(ctx, e, prefix, var, buffer);
+}
+
+char *
+hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen, char *buf, size_t buflen)
+{
+ char *p;
+ size_t i;
+
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ if (hwlen * 3 > buflen) {
+ errno = ENOBUFS;
+ return 0;
+ }
+
+ p = buf;
+ for (i = 0; i < hwlen; i++) {
+ if (i > 0)
+ *p ++= ':';
+ p += snprintf(p, 3, "%.2x", hwaddr[i]);
+ }
+ *p ++= '\0';
+ return buf;
+}
+
+size_t
+hwaddr_aton(unsigned char *buffer, const char *addr)
+{
+ char c[3];
+ const char *p = addr;
+ unsigned char *bp = buffer;
+ size_t len = 0;
+
+ c[2] = '\0';
+ while (*p) {
+ c[0] = *p++;
+ c[1] = *p++;
+ /* Ensure that digits are hex */
+ if (isxdigit((unsigned char)c[0]) == 0 ||
+ isxdigit((unsigned char)c[1]) == 0)
+ {
+ errno = EINVAL;
+ return 0;
+ }
+ /* We should have at least two entries 00:01 */
+ if (len == 0 && *p == '\0') {
+ errno = EINVAL;
+ return 0;
+ }
+ /* Ensure that next data is EOL or a seperator with data */
+ if (!(*p == '\0' || (*p == ':' && *(p + 1) != '\0'))) {
+ errno = EINVAL;
+ return 0;
+ }
+ if (*p)
+ p++;
+ if (bp)
+ *bp++ = (unsigned char)strtol(c, NULL, 16);
+ len++;
+ }
+ return len;
+}
--- /dev/null
+/* $NetBSD: common.h,v 1.10 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <syslog.h>
+
+#include "config.h"
+#include "defs.h"
+#include "dhcpcd.h"
+
+#ifndef HOSTNAME_MAX_LEN
+#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b))
+#define MAX(a,b) ((/*CONSTCOND*/(a)>(b))?(a):(b))
+#endif
+
+#define UNCONST(a) ((void *)(unsigned long)(const void *)(a))
+#define STRINGIFY(a) #a
+#define TOSTRING(a) STRINGIFY(a)
+#define UNUSED(a) (void)(a)
+
+#define USEC_PER_SEC 1000000L
+#define USEC_PER_NSEC 1000L
+#define NSEC_PER_SEC 1000000000L
+#define NSEC_PER_MSEC 1000000L
+#define MSEC_PER_SEC 1000L
+#define CSEC_PER_SEC 100L
+#define NSEC_PER_CSEC 10000000L
+
+/* Some systems don't define timespec macros */
+#ifndef timespecclear
+#define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L)
+#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec)
+#define timespeccmp(tsp, usp, cmp) \
+ (((tsp)->tv_sec == (usp)->tv_sec) ? \
+ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
+ ((tsp)->tv_sec cmp (usp)->tv_sec))
+#define timespecadd(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (/* CONSTCOND */ 0)
+#define timespecsub(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (/* CONSTCOND */ 0)
+#endif
+
+#define timespec_to_double(tv) \
+ ((double)(tv)->tv_sec + (double)((tv)->tv_nsec) / 1000000000.0)
+#define timespecnorm(tv) do { \
+ while ((tv)->tv_nsec >= NSEC_PER_SEC) { \
+ (tv)->tv_sec++; \
+ (tv)->tv_nsec -= NSEC_PER_SEC; \
+ } \
+} while (0 /* CONSTCOND */);
+#define ts_to_ms(ms, tv) do { \
+ ms = (tv)->tv_sec * MSEC_PER_SEC; \
+ ms += (tv)->tv_nsec / NSEC_PER_MSEC; \
+} while (0 /* CONSTCOND */);
+#define ms_to_ts(tv, ms) do { \
+ (tv)->tv_sec = ms / MSEC_PER_SEC; \
+ (tv)->tv_nsec = (suseconds_t)(ms - ((tv)->tv_sec * MSEC_PER_SEC)) \
+ * NSEC_PER_MSEC; \
+} while (0 /* CONSTCOND */);
+
+#ifndef TIMEVAL_TO_TIMESPEC
+#define TIMEVAL_TO_TIMESPEC(tv, ts) do { \
+ (ts)->tv_sec = (tv)->tv_sec; \
+ (ts)->tv_nsec = (tv)->tv_usec * USEC_PER_NSEC; \
+} while (0 /* CONSTCOND */)
+#endif
+
+#if __GNUC__ > 2 || defined(__INTEL_COMPILER)
+# ifndef __dead
+# define __dead __attribute__((__noreturn__))
+# endif
+# ifndef __packed
+# define __packed __attribute__((__packed__))
+# endif
+# ifndef __printflike
+# define __printflike(a, b) __attribute__((format(printf, a, b)))
+# endif
+# ifndef __unused
+# define __unused __attribute__((__unused__))
+# endif
+#else
+# ifndef __dead
+# define __dead
+# endif
+# ifndef __packed
+# define __packed
+# endif
+# ifndef __printflike
+# define __printflike
+# endif
+# ifndef __unused
+# define __unused
+# endif
+#endif
+
+#ifndef __arraycount
+#define __arraycount(__x) (sizeof(__x) / sizeof(__x[0]))
+#endif
+
+/* We don't really need this as our supported systems define __restrict
+ * automatically for us, but it is here for completeness. */
+#ifndef __restrict
+# if defined(__lint__)
+# define __restrict
+# elif __STDC_VERSION__ >= 199901L
+# define __restrict restrict
+# elif !(2 < __GNUC__ || (2 == __GNU_C && 95 <= __GNUC_VERSION__))
+# define __restrict
+# endif
+#endif
+
+void get_line_free(void);
+const char *get_hostname(char *, size_t, int);
+extern int clock_monotonic;
+int get_monotonic(struct timespec *);
+
+/* We could shave a few k off the binary size by just using the
+ * syslog(3) interface.
+ * However, this results in a ugly output on the command line
+ * and relies on syslogd(8) starting before dhcpcd which is not
+ * always the case. */
+#ifndef USE_LOGFILE
+# define USE_LOGFILE 1
+#endif
+#if USE_LOGFILE
+void logger_open(struct dhcpcd_ctx *);
+#define logger_mask(ctx, lvl) setlogmask((lvl))
+__printflike(3, 4) void logger(struct dhcpcd_ctx *, int, const char *, ...);
+void logger_close(struct dhcpcd_ctx *);
+#else
+#define logger_open(ctx) openlog(PACKAGE, LOG_PERROR | LOG_PID, LOG_DAEMON)
+#define logger_mask(ctx, lvl) setlogmask((lvl))
+#define logger(ctx, pri, fmt, ...) \
+ do { \
+ UNUSED((ctx)); \
+ syslog((pri), (fmt), ##__VA_ARGS__); \
+ } while (0 /*CONSTCOND */)
+#define logger_close(ctx) closelog()
+#endif
+
+ssize_t setvar(struct dhcpcd_ctx *,
+ char **, const char *, const char *, const char *);
+ssize_t setvard(struct dhcpcd_ctx *,
+ char **, const char *, const char *, size_t);
+ssize_t addvar(struct dhcpcd_ctx *,
+ char ***, const char *, const char *, const char *);
+ssize_t addvard(struct dhcpcd_ctx *,
+ char ***, const char *, const char *, size_t);
+
+char *hwaddr_ntoa(const unsigned char *, size_t, char *, size_t);
+size_t hwaddr_aton(unsigned char *, const char *);
+#endif
--- /dev/null
+/* $NetBSD: config.h,v 1.9 2015/05/16 23:31:32 roy Exp $ */
+
+/* netbsd */
+#define SYSCONFDIR "/etc"
+#define SBINDIR "/sbin"
+#define LIBDIR "/lib"
+#define LIBEXECDIR "/libexec"
+#define DBDIR "/var/db"
+#define RUNDIR "/var/run"
+#define HAVE_SYS_QUEUE_H
+#define HAVE_SPAWN_H
+#if !defined(__minix)
+#define HAVE_KQUEUE
+#define HAVE_KQUEUE1
+#endif /* !defined(__minix) */
+#define HAVE_MD5_H
+#define SHA2_H <sha2.h>
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: control.c,v 1.10 2015/08/21 10:39:00 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "control.h"
+#include "eloop.h"
+#include "if.h"
+
+#ifndef SUN_LEN
+#define SUN_LEN(su) \
+ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
+
+static void
+control_queue_purge(struct dhcpcd_ctx *ctx, char *data)
+{
+ int found;
+ struct fd_list *fp;
+ struct fd_data *fpd;
+
+ /* If no other fd queue has the same data, free it */
+ found = 0;
+ TAILQ_FOREACH(fp, &ctx->control_fds, next) {
+ TAILQ_FOREACH(fpd, &fp->queue, next) {
+ if (fpd->data == data) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found)
+ free(data);
+}
+
+static void
+control_queue_free(struct fd_list *fd)
+{
+ struct fd_data *fdp;
+
+ while ((fdp = TAILQ_FIRST(&fd->queue))) {
+ TAILQ_REMOVE(&fd->queue, fdp, next);
+ if (fdp->freeit)
+ control_queue_purge(fd->ctx, fdp->data);
+ free(fdp);
+ }
+ while ((fdp = TAILQ_FIRST(&fd->free_queue))) {
+ TAILQ_REMOVE(&fd->free_queue, fdp, next);
+ free(fdp);
+ }
+}
+
+static void
+control_delete(struct fd_list *fd)
+{
+
+ TAILQ_REMOVE(&fd->ctx->control_fds, fd, next);
+ eloop_event_delete(fd->ctx->eloop, fd->fd);
+ close(fd->fd);
+ control_queue_free(fd);
+ free(fd);
+}
+
+static void
+control_handle_data(void *arg)
+{
+ struct fd_list *fd = arg;
+ char buffer[1024], *e, *p, *argvp[255], **ap, *a;
+ ssize_t bytes;
+ size_t len;
+ int argc;
+
+ bytes = read(fd->fd, buffer, sizeof(buffer) - 1);
+ if (bytes == -1 || bytes == 0) {
+ /* Control was closed or there was an error.
+ * Remove it from our list. */
+ control_delete(fd);
+ return;
+ }
+ buffer[bytes] = '\0';
+ p = buffer;
+ e = buffer + bytes;
+
+ /* Each command is \n terminated
+ * Each argument is NULL separated */
+ while (p < e) {
+ argc = 0;
+ ap = argvp;
+ while (p < e) {
+ argc++;
+ if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) {
+ errno = ENOBUFS;
+ return;
+ }
+ a = *ap++ = p;
+ len = strlen(p);
+ p += len + 1;
+ if (len && a[len - 1] == '\n') {
+ a[len - 1] = '\0';
+ break;
+ }
+ }
+ *ap = NULL;
+ if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) {
+ logger(fd->ctx, LOG_ERR,
+ "%s: dhcpcd_handleargs: %m", __func__);
+ if (errno != EINTR && errno != EAGAIN) {
+ control_delete(fd);
+ return;
+ }
+ }
+ }
+}
+
+static void
+control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags)
+{
+ struct sockaddr_un run;
+ socklen_t len;
+ struct fd_list *l;
+ int fd, flags;
+
+ len = sizeof(run);
+ if ((fd = accept(lfd, (struct sockaddr *)&run, &len)) == -1)
+ return;
+ if ((flags = fcntl(fd, F_GETFD, 0)) == -1 ||
+ fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ {
+ close(fd);
+ return;
+ }
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1 ||
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+ {
+ close(fd);
+ return;
+ }
+ l = malloc(sizeof(*l));
+ if (l) {
+ l->ctx = ctx;
+ l->fd = fd;
+ l->flags = fd_flags;
+ TAILQ_INIT(&l->queue);
+ TAILQ_INIT(&l->free_queue);
+ TAILQ_INSERT_TAIL(&ctx->control_fds, l, next);
+ eloop_event_add(ctx->eloop, l->fd,
+ control_handle_data, l, NULL, NULL);
+ } else
+ close(fd);
+}
+
+static void
+control_handle(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ control_handle1(ctx, ctx->control_fd, 0);
+}
+
+static void
+control_handle_unpriv(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV);
+}
+
+static int
+make_sock(struct sockaddr_un *sa, const char *ifname, int unpriv)
+{
+ int fd;
+
+ if ((fd = xsocket(AF_UNIX, SOCK_STREAM, 0, O_NONBLOCK|O_CLOEXEC)) == -1)
+ return -1;
+ memset(sa, 0, sizeof(*sa));
+ sa->sun_family = AF_UNIX;
+ if (unpriv)
+ strlcpy(sa->sun_path, UNPRIVSOCKET, sizeof(sa->sun_path));
+ else {
+ snprintf(sa->sun_path, sizeof(sa->sun_path), CONTROLSOCKET,
+ ifname ? "-" : "", ifname ? ifname : "");
+ }
+ return fd;
+}
+
+#define S_PRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
+#define S_UNPRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+static int
+control_start1(struct dhcpcd_ctx *ctx, const char *ifname, mode_t fmode)
+{
+ struct sockaddr_un sa;
+ int fd;
+ socklen_t len;
+
+ if ((fd = make_sock(&sa, ifname, (fmode & S_UNPRIV) == S_UNPRIV)) == -1)
+ return -1;
+ len = (socklen_t)SUN_LEN(&sa);
+ unlink(sa.sun_path);
+ if (bind(fd, (struct sockaddr *)&sa, len) == -1 ||
+ chmod(sa.sun_path, fmode) == -1 ||
+ (ctx->control_group &&
+ chown(sa.sun_path, geteuid(), ctx->control_group) == -1) ||
+ listen(fd, sizeof(ctx->control_fds)) == -1)
+ {
+ close(fd);
+ unlink(sa.sun_path);
+ return -1;
+ }
+
+ if ((fmode & S_UNPRIV) != S_UNPRIV)
+ strlcpy(ctx->control_sock, sa.sun_path,
+ sizeof(ctx->control_sock));
+ return fd;
+}
+
+int
+control_start(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ int fd;
+
+ if ((fd = control_start1(ctx, ifname, S_PRIV)) == -1)
+ return -1;
+
+ ctx->control_fd = fd;
+ eloop_event_add(ctx->eloop, fd, control_handle, ctx, NULL, NULL);
+
+ if (ifname == NULL && (fd = control_start1(ctx, NULL, S_UNPRIV)) != -1){
+ /* We must be in master mode, so create an unpriviledged socket
+ * to allow normal users to learn the status of dhcpcd. */
+ ctx->control_unpriv_fd = fd;
+ eloop_event_add(ctx->eloop, fd, control_handle_unpriv,
+ ctx, NULL, NULL);
+ }
+ return ctx->control_fd;
+}
+
+int
+control_stop(struct dhcpcd_ctx *ctx)
+{
+ int retval = 0;
+ struct fd_list *l;
+
+ if (ctx->options & DHCPCD_FORKED)
+ goto freeit;
+
+ if (ctx->control_fd == -1)
+ return 0;
+ eloop_event_delete(ctx->eloop, ctx->control_fd);
+ close(ctx->control_fd);
+ ctx->control_fd = -1;
+ if (unlink(ctx->control_sock) == -1)
+ retval = -1;
+
+ if (ctx->control_unpriv_fd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->control_unpriv_fd);
+ close(ctx->control_unpriv_fd);
+ ctx->control_unpriv_fd = -1;
+ if (unlink(UNPRIVSOCKET) == -1)
+ retval = -1;
+ }
+
+freeit:
+ while ((l = TAILQ_FIRST(&ctx->control_fds))) {
+ TAILQ_REMOVE(&ctx->control_fds, l, next);
+ eloop_event_delete(ctx->eloop, l->fd);
+ close(l->fd);
+ control_queue_free(l);
+ free(l);
+ }
+
+ return retval;
+}
+
+int
+control_open(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ struct sockaddr_un sa;
+ socklen_t len;
+
+ if ((ctx->control_fd = make_sock(&sa, ifname, 0)) == -1)
+ return -1;
+ len = (socklen_t)SUN_LEN(&sa);
+ if (connect(ctx->control_fd, (struct sockaddr *)&sa, len) == -1) {
+ close(ctx->control_fd);
+ ctx->control_fd = -1;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+control_send(struct dhcpcd_ctx *ctx, int argc, char * const *argv)
+{
+ char buffer[1024];
+ int i;
+ size_t len, l;
+
+ if (argc > 255) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ len = 0;
+ for (i = 0; i < argc; i++) {
+ l = strlen(argv[i]) + 1;
+ if (len + l > sizeof(buffer)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(buffer + len, argv[i], l);
+ len += l;
+ }
+ return write(ctx->control_fd, buffer, len);
+}
+
+static void
+control_writeone(void *arg)
+{
+ struct fd_list *fd;
+ struct iovec iov[2];
+ struct fd_data *data;
+
+ fd = arg;
+ data = TAILQ_FIRST(&fd->queue);
+ iov[0].iov_base = &data->data_len;
+ iov[0].iov_len = sizeof(size_t);
+ iov[1].iov_base = data->data;
+ iov[1].iov_len = data->data_len;
+ if (writev(fd->fd, iov, 2) == -1) {
+ logger(fd->ctx, LOG_ERR,
+ "%s: writev fd %d: %m", __func__, fd->fd);
+ if (errno != EINTR && errno != EAGAIN)
+ control_delete(fd);
+ return;
+ }
+
+ TAILQ_REMOVE(&fd->queue, data, next);
+ if (data->freeit)
+ control_queue_purge(fd->ctx, data->data);
+ data->data = NULL; /* safety */
+ data->data_len = 0;
+ TAILQ_INSERT_TAIL(&fd->free_queue, data, next);
+
+ if (TAILQ_FIRST(&fd->queue) == NULL)
+ eloop_event_remove_writecb(fd->ctx->eloop, fd->fd);
+}
+
+int
+control_queue(struct fd_list *fd, char *data, size_t data_len, uint8_t fit)
+{
+ struct fd_data *d;
+ size_t n;
+
+ d = TAILQ_FIRST(&fd->free_queue);
+ if (d) {
+ TAILQ_REMOVE(&fd->free_queue, d, next);
+ } else {
+ n = 0;
+ TAILQ_FOREACH(d, &fd->queue, next) {
+ if (++n == CONTROL_QUEUE_MAX) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ }
+ d = malloc(sizeof(*d));
+ if (d == NULL)
+ return -1;
+ }
+ d->data = data;
+ d->data_len = data_len;
+ d->freeit = fit;
+ TAILQ_INSERT_TAIL(&fd->queue, d, next);
+ eloop_event_add(fd->ctx->eloop, fd->fd,
+ NULL, NULL, control_writeone, fd);
+ return 0;
+}
+
+void
+control_close(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->control_fd != -1) {
+ close(ctx->control_fd);
+ ctx->control_fd = -1;
+ }
+}
--- /dev/null
+/* $NetBSD: control.h,v 1.7 2015/01/30 09:47:05 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef CONTROL_H
+#define CONTROL_H
+
+#include "dhcpcd.h"
+
+/* Limit queue size per fd */
+#define CONTROL_QUEUE_MAX 100
+
+struct fd_data {
+ TAILQ_ENTRY(fd_data) next;
+ char *data;
+ size_t data_len;
+ uint8_t freeit;
+};
+TAILQ_HEAD(fd_data_head, fd_data);
+
+struct fd_list {
+ TAILQ_ENTRY(fd_list) next;
+ struct dhcpcd_ctx *ctx;
+ int fd;
+ unsigned int flags;
+ struct fd_data_head queue;
+ struct fd_data_head free_queue;
+};
+TAILQ_HEAD(fd_list_head, fd_list);
+
+#define FD_LISTEN (1<<0)
+#define FD_UNPRIV (1<<1)
+
+int control_start(struct dhcpcd_ctx *, const char *);
+int control_stop(struct dhcpcd_ctx *);
+int control_open(struct dhcpcd_ctx *, const char *);
+ssize_t control_send(struct dhcpcd_ctx *, int, char * const *);
+int control_queue(struct fd_list *fd, char *data, size_t data_len, uint8_t fit);
+void control_close(struct dhcpcd_ctx *ctx);
+
+#endif
--- /dev/null
+/* $NetBSD: crypt.h,v 1.6 2015/01/30 09:47:05 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef CRYPT_H
+#define CRYPT_H
+
+void hmac_md5(const uint8_t *, size_t, const uint8_t *, size_t, uint8_t *);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: hmac_md5.c,v 1.7 2015/03/27 11:33:47 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <inttypes.h>
+#include <string.h>
+
+#include "crypt.h"
+
+#include "../config.h"
+#ifdef HAVE_MD5_H
+# ifndef DEPGEN
+# include <md5.h>
+# endif
+#else
+# include "md5.h"
+#endif
+
+#define HMAC_PAD_LEN 64
+#define IPAD 0x36
+#define OPAD 0x5C
+
+/* hmac_md5 as per RFC3118 */
+void
+hmac_md5(const uint8_t *text, size_t text_len,
+ const uint8_t *key, size_t key_len,
+ uint8_t *digest)
+{
+ uint8_t k_ipad[HMAC_PAD_LEN], k_opad[HMAC_PAD_LEN];
+ uint8_t tk[MD5_DIGEST_LENGTH];
+ int i;
+ MD5_CTX context;
+
+ /* Ensure key is no bigger than HMAC_PAD_LEN */
+ if (key_len > HMAC_PAD_LEN) {
+ MD5Init(&context);
+ MD5Update(&context, key, (unsigned int)key_len);
+ MD5Final(tk, &context);
+ key = tk;
+ key_len = MD5_DIGEST_LENGTH;
+ }
+
+ /* store key in pads */
+ memcpy(k_ipad, key, key_len);
+ memcpy(k_opad, key, key_len);
+ memset(k_ipad + key_len, 0, sizeof(k_ipad) - key_len);
+ memset(k_opad + key_len, 0, sizeof(k_opad) - key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i = 0; i < HMAC_PAD_LEN; i++) {
+ k_ipad[i] ^= IPAD;
+ k_opad[i] ^= OPAD;
+ }
+
+ /* inner MD5 */
+ MD5Init(&context);
+ MD5Update(&context, k_ipad, HMAC_PAD_LEN);
+ MD5Update(&context, text, (unsigned int)text_len);
+ MD5Final(digest, &context);
+
+ /* outer MD5 */
+ MD5Init(&context);
+ MD5Update(&context, k_opad, HMAC_PAD_LEN);
+ MD5Update(&context, digest, MD5_DIGEST_LENGTH);
+ MD5Final(digest, &context);
+}
--- /dev/null
+/* $NetBSD: defs.h,v 1.21 2015/09/04 12:25:01 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#define PACKAGE "dhcpcd"
+#define VERSION "6.9.3"
+
+#ifndef CONFIG
+# define CONFIG SYSCONFDIR "/" PACKAGE ".conf"
+#endif
+#ifndef SCRIPT
+# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks"
+#endif
+#ifndef DEVDIR
+# define DEVDIR LIBDIR "/" PACKAGE "/dev"
+#endif
+#ifndef DUID
+# define DUID SYSCONFDIR "/" PACKAGE ".duid"
+#endif
+#ifndef SECRET
+# define SECRET SYSCONFDIR "/" PACKAGE ".secret"
+#endif
+#ifndef LEASEFILE
+# define LEASEFILE DBDIR "/" PACKAGE "-%s%s.lease"
+#endif
+#ifndef LEASEFILE6
+# define LEASEFILE6 LEASEFILE "6"
+#endif
+#ifndef PIDFILE
+# define PIDFILE RUNDIR "/" PACKAGE "%s%s%s.pid"
+#endif
+#ifndef CONTROLSOCKET
+# define CONTROLSOCKET RUNDIR "/" PACKAGE "%s%s.sock"
+#endif
+#ifndef UNPRIVSOCKET
+# define UNPRIVSOCKET RUNDIR "/" PACKAGE ".unpriv.sock"
+#endif
+#ifndef RDM_MONOFILE
+# define RDM_MONOFILE DBDIR "/" PACKAGE "-rdm.monotonic"
+#endif
+
+#ifndef NO_SIGNALS
+# define USE_SIGNALS
+#endif
+#ifndef USE_SIGNALS
+# ifndef THERE_IS_NO_FORK
+# define THERE_IS_NO_FORK
+# endif
+#endif
+
+#endif
--- /dev/null
+/* $NetBSD: dev.h,v 1.7 2015/01/30 09:47:05 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef DEV_H
+#define DEV_H
+
+// dev plugin setup
+struct dev {
+ const char *name;
+ int (*initialized)(const char *);
+ int (*listening)(void);
+ int (*handle_device)(void *);
+ int (*start)(void);
+ void (*stop)(void);
+};
+
+struct dev_dhcpcd {
+ int (*handle_interface)(void *, int, const char *);
+};
+
+int dev_init(struct dev *, const struct dev_dhcpcd *);
+
+// hooks for dhcpcd
+#ifdef PLUGIN_DEV
+#include "dhcpcd.h"
+int dev_initialized(struct dhcpcd_ctx *, const char *);
+int dev_listening(struct dhcpcd_ctx *);
+int dev_start(struct dhcpcd_ctx *);
+void dev_stop(struct dhcpcd_ctx *);
+#else
+#define dev_initialized(a, b) (1)
+#define dev_listening(a) (0)
+#define dev_start(a) {}
+#define dev_stop(a) {}
+#endif
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: dhcp-common.c,v 1.10 2015/07/09 10:15:34 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/utsname.h>
+
+#include <arpa/nameser.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include "common.h"
+#include "dhcp-common.h"
+#include "dhcp.h"
+#include "if.h"
+#include "ipv6.h"
+
+/* Support very old arpa/nameser.h as found in OpenBSD */
+#ifndef NS_MAXDNAME
+#define NS_MAXCDNAME MAXCDNAME
+#define NS_MAXDNAME MAXDNAME
+#define NS_MAXLABEL MAXLABEL
+#endif
+
+void
+dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols)
+{
+
+ while (cols < 40) {
+ putchar(' ');
+ cols++;
+ }
+ putchar('\t');
+ if (opt->type & EMBED)
+ printf(" embed");
+ if (opt->type & ENCAP)
+ printf(" encap");
+ if (opt->type & INDEX)
+ printf(" index");
+ if (opt->type & ARRAY)
+ printf(" array");
+ if (opt->type & UINT8)
+ printf(" byte");
+ else if (opt->type & UINT16)
+ printf(" uint16");
+ else if (opt->type & SINT16)
+ printf(" sint16");
+ else if (opt->type & UINT32)
+ printf(" uint32");
+ else if (opt->type & SINT32)
+ printf(" sint32");
+ else if (opt->type & ADDRIPV4)
+ printf(" ipaddress");
+ else if (opt->type & ADDRIPV6)
+ printf(" ip6address");
+ else if (opt->type & FLAG)
+ printf(" flag");
+ else if (opt->type & BITFLAG)
+ printf(" bitflags");
+ else if (opt->type & RFC1035)
+ printf(" domain");
+ else if (opt->type & DOMAIN)
+ printf(" dname");
+ else if (opt->type & ASCII)
+ printf(" ascii");
+ else if (opt->type & RAW)
+ printf(" raw");
+ else if (opt->type & BINHEX)
+ printf(" binhex");
+ else if (opt->type & STRING)
+ printf(" string");
+ if (opt->type & RFC3361)
+ printf(" rfc3361");
+ if (opt->type & RFC3442)
+ printf(" rfc3442");
+ if (opt->type & RFC5969)
+ printf(" rfc5969");
+ if (opt->type & REQUEST)
+ printf(" request");
+ if (opt->type & NOREQ)
+ printf(" norequest");
+ putchar('\n');
+}
+
+struct dhcp_opt *
+vivso_find(uint32_t iana_en, const void *arg)
+{
+ const struct interface *ifp;
+ size_t i;
+ struct dhcp_opt *opt;
+
+ ifp = arg;
+ for (i = 0, opt = ifp->options->vivso_override;
+ i < ifp->options->vivso_override_len;
+ i++, opt++)
+ if (opt->option == iana_en)
+ return opt;
+ for (i = 0, opt = ifp->ctx->vivso;
+ i < ifp->ctx->vivso_len;
+ i++, opt++)
+ if (opt->option == iana_en)
+ return opt;
+ return NULL;
+}
+
+ssize_t
+dhcp_vendor(char *str, size_t len)
+{
+ struct utsname utn;
+ char *p;
+ int l;
+
+ if (uname(&utn) != 0)
+ return (ssize_t)snprintf(str, len, "%s-%s",
+ PACKAGE, VERSION);
+ p = str;
+ l = snprintf(p, len,
+ "%s-%s:%s-%s:%s", PACKAGE, VERSION,
+ utn.sysname, utn.release, utn.machine);
+ if (l == -1 || (size_t)(l + 1) > len)
+ return -1;
+ p += l;
+ len -= (size_t)l;
+ l = if_machinearch(p, len);
+ if (l == -1 || (size_t)(l + 1) > len)
+ return -1;
+ p += l;
+ return p - str;
+}
+
+int
+make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len,
+ const struct dhcp_opt *odopts, size_t odopts_len,
+ uint8_t *mask, const char *opts, int add)
+{
+ char *token, *o, *p;
+ const struct dhcp_opt *opt;
+ int match, e;
+ unsigned int n;
+ size_t i;
+
+ if (opts == NULL)
+ return -1;
+ o = p = strdup(opts);
+ while ((token = strsep(&p, ", "))) {
+ if (*token == '\0')
+ continue;
+ match = 0;
+ for (i = 0, opt = odopts; i < odopts_len; i++, opt++) {
+ if (strcmp(opt->var, token) == 0)
+ match = 1;
+ else {
+ n = (unsigned int)strtou(token, NULL, 0,
+ 0, UINT_MAX, &e);
+ if (e == 0 && opt->option == n)
+ match = 1;
+ }
+ if (match)
+ break;
+ }
+ if (match == 0) {
+ for (i = 0, opt = dopts; i < dopts_len; i++, opt++) {
+ if (strcmp(opt->var, token) == 0)
+ match = 1;
+ else {
+ n = (unsigned int)strtou(token, NULL, 0,
+ 0, UINT_MAX, &e);
+ if (e == 0 && opt->option == n)
+ match = 1;
+ }
+ if (match)
+ break;
+ }
+ }
+ if (!match || !opt->option) {
+ free(o);
+ errno = ENOENT;
+ return -1;
+ }
+ if (add == 2 && !(opt->type & ADDRIPV4)) {
+ free(o);
+ errno = EINVAL;
+ return -1;
+ }
+ if (add == 1 || add == 2)
+ add_option_mask(mask, opt->option);
+ else
+ del_option_mask(mask, opt->option);
+ }
+ free(o);
+ return 0;
+}
+
+size_t
+encode_rfc1035(const char *src, uint8_t *dst)
+{
+ uint8_t *p;
+ uint8_t *lp;
+ size_t len;
+ uint8_t has_dot;
+
+ if (src == NULL || *src == '\0')
+ return 0;
+
+ if (dst) {
+ p = dst;
+ lp = p++;
+ }
+ /* Silence bogus GCC warnings */
+ else
+ p = lp = NULL;
+
+ len = 1;
+ has_dot = 0;
+ for (; *src; src++) {
+ if (*src == '\0')
+ break;
+ if (*src == '.') {
+ /* Skip the trailing . */
+ if (src[1] == '\0')
+ break;
+ has_dot = 1;
+ if (dst) {
+ *lp = (uint8_t)(p - lp - 1);
+ if (*lp == '\0')
+ return len;
+ lp = p++;
+ }
+ } else if (dst)
+ *p++ = (uint8_t)*src;
+ len++;
+ }
+
+ if (dst) {
+ *lp = (uint8_t)(p - lp - 1);
+ if (has_dot)
+ *p++ = '\0';
+ }
+
+ if (has_dot)
+ len++;
+
+ return len;
+}
+
+/* Decode an RFC1035 DNS search order option into a space
+ * separated string. Returns length of string (including
+ * terminating zero) or zero on error. out may be NULL
+ * to just determine output length. */
+ssize_t
+decode_rfc1035(char *out, size_t len, const uint8_t *p, size_t pl)
+{
+ const char *start;
+ size_t start_len, l, count;
+ const uint8_t *r, *q = p, *e;
+ int hops;
+ uint8_t ltype;
+
+ if (pl > NS_MAXCDNAME) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ count = 0;
+ start = out;
+ start_len = len;
+ q = p;
+ e = p + pl;
+ while (q < e) {
+ r = NULL;
+ hops = 0;
+ /* Check we are inside our length again in-case
+ * the name isn't fully qualified (ie, not terminated) */
+ while (q < e && (l = (size_t)*q++)) {
+ ltype = l & 0xc0;
+ if (ltype == 0x80 || ltype == 0x40) {
+ /* Currently reserved for future use as noted
+ * in RFC1035 4.1.4 as the 10 and 01
+ * combinations. */
+ errno = ENOTSUP;
+ return -1;
+ }
+ else if (ltype == 0xc0) { /* pointer */
+ if (q == e) {
+ errno = ERANGE;
+ return -1;
+ }
+ l = (l & 0x3f) << 8;
+ l |= *q++;
+ /* save source of first jump. */
+ if (!r)
+ r = q;
+ hops++;
+ if (hops > 255) {
+ errno = ERANGE;
+ return -1;
+ }
+ q = p + l;
+ if (q >= e) {
+ errno = ERANGE;
+ return -1;
+ }
+ } else {
+ /* straightforward name segment, add with '.' */
+ if (q + l > e) {
+ errno = ERANGE;
+ return -1;
+ }
+ count += l + 1;
+ if (out) {
+ if (l + 1 > len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ if (l + 1 > NS_MAXLABEL) {
+ errno = EINVAL;
+ return -1;
+ }
+ memcpy(out, q, l);
+ out += l;
+ *out++ = '.';
+ len -= l;
+ len--;
+ }
+ q += l;
+ }
+ }
+ /* change last dot to space */
+ if (out && out != start)
+ *(out - 1) = ' ';
+ if (r)
+ q = r;
+ }
+
+ /* change last space to zero terminator */
+ if (out) {
+ if (out != start)
+ *(out - 1) = '\0';
+ else if (start_len > 0)
+ *out = '\0';
+ }
+
+ if (count)
+ /* Don't count the trailing NUL */
+ count--;
+ if (count > NS_MAXDNAME) {
+ errno = E2BIG;
+ return -1;
+ }
+ return (ssize_t)count;
+}
+
+/* Check for a valid domain name as per RFC1123 with the exception of
+ * allowing - and _ (but not at start or end) as they seem to be widely used. */
+static int
+valid_domainname(char *lbl, int type)
+{
+ char *slbl, *lst;
+ unsigned char c;
+ int start, len, errset;
+
+ if (lbl == NULL || *lbl == '\0') {
+ errno = EINVAL;
+ return 0;
+ }
+
+ slbl = lbl;
+ lst = NULL;
+ start = 1;
+ len = errset = 0;
+ for (;;) {
+ c = (unsigned char)*lbl++;
+ if (c == '\0')
+ return 1;
+ if (c == ' ') {
+ if (lbl - 1 == slbl) /* No space at start */
+ break;
+ if (!(type & ARRAY))
+ break;
+ /* Skip to the next label */
+ if (!start) {
+ start = 1;
+ lst = lbl - 1;
+ }
+ if (len)
+ len = 0;
+ continue;
+ }
+ if (c == '.') {
+ if (*lbl == '.')
+ break;
+ len = 0;
+ continue;
+ }
+ if (((c == '-' || c == '_') &&
+ !start && *lbl != ' ' && *lbl != '\0') ||
+ isalnum(c))
+ {
+ if (++len > NS_MAXLABEL) {
+ errno = ERANGE;
+ errset = 1;
+ break;
+ }
+ } else
+ break;
+ if (start)
+ start = 0;
+ }
+
+ if (!errset)
+ errno = EINVAL;
+ if (lst) {
+ /* At least one valid domain, return it */
+ *lst = '\0';
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Prints a chunk of data to a string.
+ * PS_SHELL goes as it is these days, it's upto the target to validate it.
+ * PS_SAFE has all non ascii and non printables changes to escaped octal.
+ */
+static const char hexchrs[] = "0123456789abcdef";
+ssize_t
+print_string(char *dst, size_t len, int type, const uint8_t *data, size_t dl)
+{
+ char *odst;
+ uint8_t c;
+ const uint8_t *e;
+ size_t bytes;
+
+ odst = dst;
+ bytes = 0;
+ e = data + dl;
+
+ while (data < e) {
+ c = *data++;
+ if (type & BINHEX) {
+ if (dst) {
+ if (len == 0 || len == 1) {
+ errno = ENOSPC;
+ return -1;
+ }
+ *dst++ = hexchrs[(c & 0xF0) >> 4];
+ *dst++ = hexchrs[(c & 0x0F)];
+ len -= 2;
+ }
+ bytes += 2;
+ continue;
+ }
+ if (type & ASCII && (!isascii(c))) {
+ errno = EINVAL;
+ break;
+ }
+ if (!(type & (ASCII | RAW | ESCSTRING | ESCFILE)) /* plain */ &&
+ (!isascii(c) && !isprint(c)))
+ {
+ errno = EINVAL;
+ break;
+ }
+ if ((type & (ESCSTRING | ESCFILE) &&
+ (c == '\\' || !isascii(c) || !isprint(c))) ||
+ (type & ESCFILE && (c == '/' || c == ' ')))
+ {
+ errno = EINVAL;
+ if (c == '\\') {
+ if (dst) {
+ if (len == 0 || len == 1) {
+ errno = ENOSPC;
+ return -1;
+ }
+ *dst++ = '\\'; *dst++ = '\\';
+ len -= 2;
+ }
+ bytes += 2;
+ continue;
+ }
+ if (dst) {
+ if (len < 5) {
+ errno = ENOSPC;
+ return -1;
+ }
+ *dst++ = '\\';
+ *dst++ = (char)(((c >> 6) & 03) + '0');
+ *dst++ = (char)(((c >> 3) & 07) + '0');
+ *dst++ = (char)(( c & 07) + '0');
+ len -= 4;
+ }
+ bytes += 4;
+ } else {
+ if (dst) {
+ if (len == 0) {
+ errno = ENOSPC;
+ return -1;
+ }
+ *dst++ = (char)c;
+ len--;
+ }
+ bytes++;
+ }
+ }
+
+ /* NULL */
+ if (dst) {
+ if (len == 0) {
+ errno = ENOSPC;
+ return -1;
+ }
+ *dst = '\0';
+
+ /* Now we've printed it, validate the domain */
+ if (type & DOMAIN && !valid_domainname(odst, type)) {
+ *odst = '\0';
+ return 1;
+ }
+
+ }
+
+ return (ssize_t)bytes;
+}
+
+#define ADDRSZ 4
+#define ADDR6SZ 16
+static ssize_t
+dhcp_optlen(const struct dhcp_opt *opt, size_t dl)
+{
+ size_t sz;
+
+ if (opt->type == 0 ||
+ opt->type & (STRING | BINHEX | RFC3442 | RFC5969))
+ {
+ if (opt->len) {
+ if ((size_t)opt->len > dl)
+ return -1;
+ return (ssize_t)opt->len;
+ }
+ return (ssize_t)dl;
+ }
+
+ if ((opt->type & (ADDRIPV4 | ARRAY)) == (ADDRIPV4 | ARRAY)) {
+ if (dl < ADDRSZ)
+ return -1;
+ return (ssize_t)(dl - (dl % ADDRSZ));
+ }
+
+ if ((opt->type & (ADDRIPV6 | ARRAY)) == (ADDRIPV6 | ARRAY)) {
+ if (dl < ADDR6SZ)
+ return -1;
+ return (ssize_t)(dl - (dl % ADDR6SZ));
+ }
+
+ if (opt->type & (UINT32 | ADDRIPV4))
+ sz = sizeof(uint32_t);
+ else if (opt->type & UINT16)
+ sz = sizeof(uint16_t);
+ else if (opt->type & (UINT8 | BITFLAG))
+ sz = sizeof(uint8_t);
+ else if (opt->type & ADDRIPV6)
+ sz = ADDR6SZ;
+ else
+ /* If we don't know the size, assume it's valid */
+ return (ssize_t)dl;
+ return dl < sz ? -1 : (ssize_t)sz;
+}
+
+static ssize_t
+print_option(char *s, size_t len, const struct dhcp_opt *opt,
+ const uint8_t *data, size_t dl, const char *ifname)
+{
+ const uint8_t *e, *t;
+ uint16_t u16;
+ int16_t s16;
+ uint32_t u32;
+ int32_t s32;
+ struct in_addr addr;
+ ssize_t bytes = 0, sl;
+ size_t l;
+ char *tmp;
+
+#ifndef INET6
+ UNUSED(ifname);
+#endif
+
+ if (opt->type & RFC1035) {
+ sl = decode_rfc1035(NULL, 0, data, dl);
+ if (sl == 0 || sl == -1)
+ return sl;
+ l = (size_t)sl + 1;
+ tmp = malloc(l);
+ if (tmp == NULL)
+ return -1;
+ decode_rfc1035(tmp, l, data, dl);
+ sl = print_string(s, len, opt->type, (uint8_t *)tmp, l - 1);
+ free(tmp);
+ return sl;
+ }
+
+#ifdef INET
+ if (opt->type & RFC3361) {
+ if ((tmp = decode_rfc3361(data, dl)) == NULL)
+ return -1;
+ l = strlen(tmp);
+ sl = print_string(s, len, opt->type, (uint8_t *)tmp, l);
+ free(tmp);
+ return sl;
+ }
+
+ if (opt->type & RFC3442)
+ return decode_rfc3442(s, len, data, dl);
+
+ if (opt->type & RFC5969)
+ return decode_rfc5969(s, len, data, dl);
+#endif
+
+ if (opt->type & STRING)
+ return print_string(s, len, opt->type, data, dl);
+
+ if (opt->type & FLAG) {
+ if (s) {
+ *s++ = '1';
+ *s = '\0';
+ }
+ return 1;
+ }
+
+ if (opt->type & BITFLAG) {
+ /* bitflags are a string, MSB first, such as ABCDEFGH
+ * where A is 10000000, B is 01000000, etc. */
+ bytes = 0;
+ for (l = 0, sl = sizeof(opt->bitflags) - 1;
+ l < sizeof(opt->bitflags);
+ l++, sl--)
+ {
+ /* Don't print NULL or 0 flags */
+ if (opt->bitflags[l] != '\0' &&
+ opt->bitflags[l] != '0' &&
+ *data & (1 << sl))
+ {
+ if (s)
+ *s++ = opt->bitflags[l];
+ bytes++;
+ }
+ }
+ if (s)
+ *s = '\0';
+ return bytes;
+ }
+
+ if (!s) {
+ if (opt->type & UINT8)
+ l = 3;
+ else if (opt->type & UINT16) {
+ l = 5;
+ dl /= 2;
+ } else if (opt->type & SINT16) {
+ l = 6;
+ dl /= 2;
+ } else if (opt->type & UINT32) {
+ l = 10;
+ dl /= 4;
+ } else if (opt->type & SINT32) {
+ l = 11;
+ dl /= 4;
+ } else if (opt->type & ADDRIPV4) {
+ l = 16;
+ dl /= 4;
+ }
+#ifdef INET6
+ else if (opt->type & ADDRIPV6) {
+ e = data + dl;
+ l = 0;
+ while (data < e) {
+ if (l)
+ l++; /* space */
+ sl = ipv6_printaddr(NULL, 0, data, ifname);
+ if (sl != -1)
+ l += (size_t)sl;
+ data += 16;
+ }
+ return (ssize_t)l;
+ }
+#endif
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+ return (ssize_t)(l * dl);
+ }
+
+ t = data;
+ e = data + dl;
+ while (data < e) {
+ if (data != t) {
+ *s++ = ' ';
+ bytes++;
+ len--;
+ }
+ if (opt->type & UINT8) {
+ sl = snprintf(s, len, "%u", *data);
+ data++;
+ } else if (opt->type & UINT16) {
+ memcpy(&u16, data, sizeof(u16));
+ u16 = ntohs(u16);
+ sl = snprintf(s, len, "%u", u16);
+ data += sizeof(u16);
+ } else if (opt->type & SINT16) {
+ memcpy(&u16, data, sizeof(u16));
+ s16 = (int16_t)ntohs(u16);
+ sl = snprintf(s, len, "%d", s16);
+ data += sizeof(u16);
+ } else if (opt->type & UINT32) {
+ memcpy(&u32, data, sizeof(u32));
+ u32 = ntohl(u32);
+ sl = snprintf(s, len, "%u", u32);
+ data += sizeof(u32);
+ } else if (opt->type & SINT32) {
+ memcpy(&u32, data, sizeof(u32));
+ s32 = (int32_t)ntohl(u32);
+ sl = snprintf(s, len, "%d", s32);
+ data += sizeof(u32);
+ } else if (opt->type & ADDRIPV4) {
+ memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
+ sl = snprintf(s, len, "%s", inet_ntoa(addr));
+ data += sizeof(addr.s_addr);
+ }
+#ifdef INET6
+ else if (opt->type & ADDRIPV6) {
+ ssize_t r;
+
+ r = ipv6_printaddr(s, len, data, ifname);
+ if (r != -1)
+ sl = r;
+ else
+ sl = 0;
+ data += 16;
+ }
+#endif
+ else
+ sl = 0;
+ len -= (size_t)sl;
+ bytes += sl;
+ s += sl;
+ }
+
+ return bytes;
+}
+
+int
+dhcp_set_leasefile(char *leasefile, size_t len, int family,
+ const struct interface *ifp)
+{
+ char ssid[len];
+
+ if (ifp->name[0] == '\0') {
+ strlcpy(leasefile, ifp->ctx->pidfile, len);
+ return 0;
+ }
+
+ switch (family) {
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ifp->wireless) {
+ ssid[0] = '-';
+ print_string(ssid + 1, sizeof(ssid) - 1,
+ ESCFILE,
+ (const uint8_t *)ifp->ssid, ifp->ssid_len);
+ } else
+ ssid[0] = '\0';
+ return snprintf(leasefile, len,
+ family == AF_INET ? LEASEFILE : LEASEFILE6,
+ ifp->name, ssid);
+}
+
+static size_t
+dhcp_envoption1(struct dhcpcd_ctx *ctx, char **env, const char *prefix,
+ const struct dhcp_opt *opt, int vname, const uint8_t *od, size_t ol,
+ const char *ifname)
+{
+ ssize_t len;
+ size_t e;
+ char *v, *val;
+
+ if (opt->len && opt->len < ol)
+ ol = opt->len;
+ len = print_option(NULL, 0, opt, od, ol, ifname);
+ if (len < 0)
+ return 0;
+ if (vname)
+ e = strlen(opt->var) + 1;
+ else
+ e = 0;
+ if (prefix)
+ e += strlen(prefix);
+ e += (size_t)len + 2;
+ if (env == NULL)
+ return e;
+ v = val = *env = malloc(e);
+ if (v == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return 0;
+ }
+ if (vname)
+ v += snprintf(val, e, "%s_%s=", prefix, opt->var);
+ else
+ v += snprintf(val, e, "%s=", prefix);
+ if (len != 0)
+ print_option(v, (size_t)len + 1, opt, od, ol, ifname);
+ return e;
+}
+
+size_t
+dhcp_envoption(struct dhcpcd_ctx *ctx, char **env, const char *prefix,
+ const char *ifname, struct dhcp_opt *opt,
+ const uint8_t *(*dgetopt)(struct dhcpcd_ctx *,
+ size_t *, unsigned int *, size_t *,
+ const uint8_t *, size_t, struct dhcp_opt **),
+ const uint8_t *od, size_t ol)
+{
+ size_t e, i, n, eos, eol;
+ ssize_t eo;
+ unsigned int eoc;
+ const uint8_t *eod;
+ int ov;
+ struct dhcp_opt *eopt, *oopt;
+ char *pfx;
+
+ /* If no embedded or encapsulated options, it's easy */
+ if (opt->embopts_len == 0 && opt->encopts_len == 0) {
+ if (!(opt->type & RESERVED) &&
+ dhcp_envoption1(ctx, env == NULL ? NULL : &env[0],
+ prefix, opt, 1, od, ol, ifname))
+ return 1;
+ return 0;
+ }
+
+ /* Create a new prefix based on the option */
+ if (env) {
+ if (opt->type & INDEX) {
+ if (opt->index > 999) {
+ errno = ENOBUFS;
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return 0;
+ }
+ }
+ e = strlen(prefix) + strlen(opt->var) + 2 +
+ (opt->type & INDEX ? 3 : 0);
+ pfx = malloc(e);
+ if (pfx == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return 0;
+ }
+ if (opt->type & INDEX)
+ snprintf(pfx, e, "%s_%s%d", prefix,
+ opt->var, ++opt->index);
+ else
+ snprintf(pfx, e, "%s_%s", prefix, opt->var);
+ } else
+ pfx = NULL;
+
+ /* Embedded options are always processed first as that
+ * is a fixed layout */
+ n = 0;
+ for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) {
+ eo = dhcp_optlen(eopt, ol);
+ if (eo == -1) {
+ if (env == NULL)
+ logger(ctx, LOG_ERR,
+ "%s: %s: malformed embedded option %d:%d",
+ ifname, __func__, opt->option,
+ eopt->option);
+ goto out;
+ }
+ if (eo == 0) {
+ /* An option was expected, but there is no data
+ * data for it.
+ * This may not be an error as some options like
+ * DHCP FQDN in RFC4702 have a string as the last
+ * option which is optional.
+ * FIXME: Add an flag to the options to indicate
+ * wether this is allowable or not. */
+ if (env == NULL &&
+ (ol != 0 || i + 1 < opt->embopts_len))
+ logger(ctx, LOG_ERR,
+ "%s: %s: malformed embedded option %d:%d",
+ ifname, __func__, opt->option,
+ eopt->option);
+ goto out;
+ }
+ /* Use the option prefix if the embedded option
+ * name is different.
+ * This avoids new_fqdn_fqdn which would be silly. */
+ if (!(eopt->type & RESERVED)) {
+ ov = strcmp(opt->var, eopt->var);
+ if (dhcp_envoption1(ctx, env == NULL ? NULL : &env[n],
+ pfx, eopt, ov, od, (size_t)eo, ifname))
+ n++;
+ }
+ od += (size_t)eo;
+ ol -= (size_t)eo;
+ }
+
+ /* Enumerate our encapsulated options */
+ if (opt->encopts_len && ol > 0) {
+ /* Zero any option indexes
+ * We assume that referenced encapsulated options are NEVER
+ * recursive as the index order could break. */
+ for (i = 0, eopt = opt->encopts;
+ i < opt->encopts_len;
+ i++, eopt++)
+ {
+ eoc = opt->option;
+ if (eopt->type & OPTION) {
+ dgetopt(ctx, NULL, &eoc, NULL, NULL, 0, &oopt);
+ if (oopt)
+ oopt->index = 0;
+ }
+ }
+
+ while ((eod = dgetopt(ctx, &eos, &eoc, &eol, od, ol, &oopt))) {
+ for (i = 0, eopt = opt->encopts;
+ i < opt->encopts_len;
+ i++, eopt++)
+ {
+ if (eopt->option == eoc) {
+ if (eopt->type & OPTION) {
+ if (oopt == NULL)
+ /* Report error? */
+ continue;
+ }
+ n += dhcp_envoption(ctx,
+ env == NULL ? NULL : &env[n], pfx,
+ ifname,
+ eopt->type & OPTION ? oopt : eopt,
+ dgetopt, eod, eol);
+ break;
+ }
+ }
+ od += eos + eol;
+ ol -= eos + eol;
+ }
+ }
+
+out:
+ if (env)
+ free(pfx);
+
+ /* Return number of options found */
+ return n;
+}
+
+void
+dhcp_zero_index(struct dhcp_opt *opt)
+{
+ size_t i;
+ struct dhcp_opt *o;
+
+ opt->index = 0;
+ for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++)
+ dhcp_zero_index(o);
+ for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++)
+ dhcp_zero_index(o);
+}
--- /dev/null
+/* $NetBSD: dhcp-common.h,v 1.10 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef DHCPCOMMON_H
+#define DHCPCOMMON_H
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <stdint.h>
+
+#include "common.h"
+#include "dhcpcd.h"
+
+/* Max MTU - defines dhcp option length */
+#define MTU_MAX 1500
+#define MTU_MIN 576
+
+#define REQUEST (1 << 0)
+#define UINT8 (1 << 1)
+#define UINT16 (1 << 2)
+#define SINT16 (1 << 3)
+#define UINT32 (1 << 4)
+#define SINT32 (1 << 5)
+#define ADDRIPV4 (1 << 6)
+#define STRING (1 << 7)
+#define ARRAY (1 << 8)
+#define RFC3361 (1 << 9)
+#define RFC1035 (1 << 10)
+#define RFC3442 (1 << 11)
+#define RFC5969 (1 << 12)
+#define ADDRIPV6 (1 << 13)
+#define BINHEX (1 << 14)
+#define FLAG (1 << 15)
+#define NOREQ (1 << 16)
+#define EMBED (1 << 17)
+#define ENCAP (1 << 18)
+#define INDEX (1 << 19)
+#define OPTION (1 << 20)
+#define DOMAIN (1 << 21)
+#define ASCII (1 << 22)
+#define RAW (1 << 23)
+#define ESCSTRING (1 << 24)
+#define ESCFILE (1 << 25)
+#define BITFLAG (1 << 26)
+#define RESERVED (1 << 27)
+
+struct dhcp_opt {
+ uint32_t option; /* Also used for IANA Enterpise Number */
+ int type;
+ size_t len;
+ char *var;
+
+ int index; /* Index counter for many instances of the same option */
+ char bitflags[8];
+
+ /* Embedded options.
+ * The option code is irrelevant here. */
+ struct dhcp_opt *embopts;
+ size_t embopts_len;
+
+ /* Encapsulated options */
+ struct dhcp_opt *encopts;
+ size_t encopts_len;
+};
+
+struct dhcp_opt *vivso_find(uint32_t, const void *);
+
+ssize_t dhcp_vendor(char *, size_t);
+
+void dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols);
+#define add_option_mask(var, val) \
+ ((var)[(val) >> 3] = (uint8_t)((var)[(val) >> 3] | 1 << ((val) & 7)))
+#define del_option_mask(var, val) \
+ ((var)[(val) >> 3] = (uint8_t)((var)[(val) >> 3] & ~(1 << ((val) & 7))))
+#define has_option_mask(var, val) \
+ ((var)[(val) >> 3] & (uint8_t)(1 << ((val) & 7)))
+int make_option_mask(const struct dhcp_opt *, size_t,
+ const struct dhcp_opt *, size_t,
+ uint8_t *, const char *, int);
+
+size_t encode_rfc1035(const char *src, uint8_t *dst);
+ssize_t decode_rfc1035(char *, size_t, const uint8_t *, size_t);
+ssize_t print_string(char *, size_t, int, const uint8_t *, size_t);
+int dhcp_set_leasefile(char *, size_t, int, const struct interface *);
+
+size_t dhcp_envoption(struct dhcpcd_ctx *,
+ char **, const char *, const char *, struct dhcp_opt *,
+ const uint8_t *(*dgetopt)(struct dhcpcd_ctx *,
+ size_t *, unsigned int *, size_t *,
+ const uint8_t *, size_t, struct dhcp_opt **),
+ const uint8_t *od, size_t ol);
+void dhcp_zero_index(struct dhcp_opt *);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: dhcp.c,v 1.35 2015/09/04 12:25:01 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/if_ether.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */
+#include <netinet/udp.h>
+#undef __FAVOR_BSD
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE 2
+#include "config.h"
+#include "arp.h"
+#include "common.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
+#include "dhcp-common.h"
+#include "duid.h"
+#include "eloop.h"
+#include "if.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "script.h"
+
+#define DAD "Duplicate address detected"
+#define DHCP_MIN_LEASE 20
+
+#define IPV4A ADDRIPV4 | ARRAY
+#define IPV4R ADDRIPV4 | REQUEST
+
+/* We should define a maximum for the NAK exponential backoff */
+#define NAKOFF_MAX 60
+
+/* Wait N nanoseconds between sending a RELEASE and dropping the address.
+ * This gives the kernel enough time to actually send it. */
+#define RELEASE_DELAY_S 0
+#define RELEASE_DELAY_NS 10000000
+
+#ifndef IPDEFTTL
+#define IPDEFTTL 64 /* RFC1340 */
+#endif
+
+struct dhcp_op {
+ uint8_t value;
+ const char *name;
+};
+
+static const struct dhcp_op dhcp_ops[] = {
+ { DHCP_DISCOVER, "DISCOVER" },
+ { DHCP_OFFER, "OFFER" },
+ { DHCP_REQUEST, "REQUEST" },
+ { DHCP_DECLINE, "DECLINE" },
+ { DHCP_ACK, "ACK" },
+ { DHCP_NAK, "NAK" },
+ { DHCP_RELEASE, "RELEASE" },
+ { DHCP_INFORM, "INFORM" },
+ { DHCP_FORCERENEW, "DHCP_FORCERENEW"},
+ { 0, NULL }
+};
+
+static const char * const dhcp_params[] = {
+ "ip_address",
+ "subnet_cidr",
+ "network_number",
+ "filename",
+ "server_name",
+ NULL
+};
+
+struct udp_dhcp_packet
+{
+ struct ip ip;
+ struct udphdr udp;
+ struct dhcp_message dhcp;
+};
+
+static const size_t udp_dhcp_len = sizeof(struct udp_dhcp_packet);
+
+static int dhcp_open(struct interface *ifp);
+
+void
+dhcp_printoptions(const struct dhcpcd_ctx *ctx,
+ const struct dhcp_opt *opts, size_t opts_len)
+{
+ const char * const *p;
+ size_t i, j;
+ const struct dhcp_opt *opt, *opt2;
+ int cols;
+
+ for (p = dhcp_params; *p; p++)
+ printf(" %s\n", *p);
+
+ for (i = 0, opt = ctx->dhcp_opts; i < ctx->dhcp_opts_len; i++, opt++) {
+ for (j = 0, opt2 = opts; j < opts_len; j++, opt2++)
+ if (opt->option == opt2->option)
+ break;
+ if (j == opts_len) {
+ cols = printf("%03d %s", opt->option, opt->var);
+ dhcp_print_option_encoding(opt, cols);
+ }
+ }
+ for (i = 0, opt = opts; i < opts_len; i++, opt++) {
+ cols = printf("%03d %s", opt->option, opt->var);
+ dhcp_print_option_encoding(opt, cols);
+ }
+}
+
+#define get_option_raw(ctx, dhcp, opt) get_option(ctx, dhcp, opt, NULL)
+static const uint8_t *
+get_option(struct dhcpcd_ctx *ctx,
+ const struct dhcp_message *dhcp, unsigned int opt, size_t *len)
+{
+ const uint8_t *p = dhcp->options;
+ const uint8_t *e = p + sizeof(dhcp->options);
+ uint8_t l, ol = 0;
+ uint8_t o = 0;
+ uint8_t overl = 0;
+ uint8_t *bp = NULL;
+ const uint8_t *op = NULL;
+ size_t bl = 0;
+
+ /* Check we have the magic cookie */
+ if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ while (p < e) {
+ o = *p++;
+ switch (o) {
+ case DHO_PAD:
+ /* No length to read */
+ continue;
+ case DHO_END:
+ if (overl & 1) {
+ /* bit 1 set means parse boot file */
+ overl = (uint8_t)(overl & ~1);
+ p = dhcp->bootfile;
+ e = p + sizeof(dhcp->bootfile);
+ } else if (overl & 2) {
+ /* bit 2 set means parse server name */
+ overl = (uint8_t)(overl & ~2);
+ p = dhcp->servername;
+ e = p + sizeof(dhcp->servername);
+ } else
+ goto exit;
+ /* No length to read */
+ continue;
+ }
+
+ /* Check we can read the length */
+ if (p == e) {
+ errno = EINVAL;
+ return NULL;
+ }
+ l = *p++;
+
+ if (o == DHO_OPTIONSOVERLOADED) {
+ /* Ensure we only get this option once by setting
+ * the last bit as well as the value.
+ * This is valid because only the first two bits
+ * actually mean anything in RFC2132 Section 9.3 */
+ if (l == 1 && !overl)
+ overl = 0x80 | p[0];
+ }
+
+ if (o == opt) {
+ if (op) {
+ if (!ctx->opt_buffer) {
+ ctx->opt_buffer =
+ malloc(DHCP_OPTION_LEN +
+ BOOTFILE_LEN + SERVERNAME_LEN);
+ if (ctx->opt_buffer == NULL)
+ return NULL;
+ }
+ if (!bp)
+ bp = ctx->opt_buffer;
+ memcpy(bp, op, ol);
+ bp += ol;
+ }
+ ol = l;
+ if (p + ol >= e) {
+ errno = EINVAL;
+ return NULL;
+ }
+ op = p;
+ bl += ol;
+ }
+ p += l;
+ }
+
+exit:
+ if (len)
+ *len = bl;
+ if (bp) {
+ memcpy(bp, op, ol);
+ return (const uint8_t *)ctx->opt_buffer;
+ }
+ if (op)
+ return op;
+ errno = ENOENT;
+ return NULL;
+}
+
+int
+get_option_addr(struct dhcpcd_ctx *ctx,
+ struct in_addr *a, const struct dhcp_message *dhcp,
+ uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+
+ p = get_option(ctx, dhcp, option, &len);
+ if (!p || len < (ssize_t)sizeof(a->s_addr))
+ return -1;
+ memcpy(&a->s_addr, p, sizeof(a->s_addr));
+ return 0;
+}
+
+static int
+get_option_uint32(struct dhcpcd_ctx *ctx,
+ uint32_t *i, const struct dhcp_message *dhcp, uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+ uint32_t d;
+
+ p = get_option(ctx, dhcp, option, &len);
+ if (!p || len < (ssize_t)sizeof(d))
+ return -1;
+ memcpy(&d, p, sizeof(d));
+ if (i)
+ *i = ntohl(d);
+ return 0;
+}
+
+static int
+get_option_uint16(struct dhcpcd_ctx *ctx,
+ uint16_t *i, const struct dhcp_message *dhcp, uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+ uint16_t d;
+
+ p = get_option(ctx, dhcp, option, &len);
+ if (!p || len < (ssize_t)sizeof(d))
+ return -1;
+ memcpy(&d, p, sizeof(d));
+ if (i)
+ *i = ntohs(d);
+ return 0;
+}
+
+static int
+get_option_uint8(struct dhcpcd_ctx *ctx,
+ uint8_t *i, const struct dhcp_message *dhcp, uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+
+ p = get_option(ctx, dhcp, option, &len);
+ if (!p || len < (ssize_t)sizeof(*p))
+ return -1;
+ if (i)
+ *i = *(p);
+ return 0;
+}
+
+ssize_t
+decode_rfc3442(char *out, size_t len, const uint8_t *p, size_t pl)
+{
+ const uint8_t *e;
+ size_t bytes = 0, ocets;
+ int b;
+ uint8_t cidr;
+ struct in_addr addr;
+ char *o = out;
+
+ /* Minimum is 5 -first is CIDR and a router length of 4 */
+ if (pl < 5) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ e = p + pl;
+ while (p < e) {
+ cidr = *p++;
+ if (cidr > 32) {
+ errno = EINVAL;
+ return -1;
+ }
+ ocets = (size_t)(cidr + 7) / NBBY;
+ if (p + 4 + ocets > e) {
+ errno = ERANGE;
+ return -1;
+ }
+ if (!out) {
+ p += 4 + ocets;
+ bytes += ((4 * 4) * 2) + 4;
+ continue;
+ }
+ if ((((4 * 4) * 2) + 4) > len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ if (o != out) {
+ *o++ = ' ';
+ len--;
+ }
+ /* If we have ocets then we have a destination and netmask */
+ if (ocets > 0) {
+ addr.s_addr = 0;
+ memcpy(&addr.s_addr, p, ocets);
+ b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr);
+ p += ocets;
+ } else
+ b = snprintf(o, len, "0.0.0.0/0");
+ o += b;
+ len -= (size_t)b;
+
+ /* Finally, snag the router */
+ memcpy(&addr.s_addr, p, 4);
+ p += 4;
+ b = snprintf(o, len, " %s", inet_ntoa(addr));
+ o += b;
+ len -= (size_t)b;
+ }
+
+ if (out)
+ return o - out;
+ return (ssize_t)bytes;
+}
+
+static struct rt_head *
+decode_rfc3442_rt(struct dhcpcd_ctx *ctx, const uint8_t *data, size_t dl)
+{
+ const uint8_t *p = data;
+ const uint8_t *e;
+ uint8_t cidr;
+ size_t ocets;
+ struct rt_head *routes;
+ struct rt *rt = NULL;
+
+ /* Minimum is 5 -first is CIDR and a router length of 4 */
+ if (dl < 5)
+ return NULL;
+
+ routes = malloc(sizeof(*routes));
+ TAILQ_INIT(routes);
+ e = p + dl;
+ while (p < e) {
+ cidr = *p++;
+ if (cidr > 32) {
+ ipv4_freeroutes(routes);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ ocets = (size_t)(cidr + 7) / NBBY;
+ if (p + 4 + ocets > e) {
+ ipv4_freeroutes(routes);
+ errno = ERANGE;
+ return NULL;
+ }
+
+ rt = calloc(1, sizeof(*rt));
+ if (rt == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(routes);
+ return NULL;
+ }
+ TAILQ_INSERT_TAIL(routes, rt, next);
+
+ /* If we have ocets then we have a destination and netmask */
+ if (ocets > 0) {
+ memcpy(&rt->dest.s_addr, p, ocets);
+ p += ocets;
+ rt->net.s_addr = htonl(~0U << (32 - cidr));
+ }
+
+ /* Finally, snag the router */
+ memcpy(&rt->gate.s_addr, p, 4);
+ p += 4;
+ }
+ return routes;
+}
+
+char *
+decode_rfc3361(const uint8_t *data, size_t dl)
+{
+ uint8_t enc;
+ size_t l;
+ ssize_t r;
+ char *sip = NULL;
+ struct in_addr addr;
+ char *p;
+
+ if (dl < 2) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ enc = *data++;
+ dl--;
+ switch (enc) {
+ case 0:
+ if ((r = decode_rfc1035(NULL, 0, data, dl)) > 0) {
+ l = (size_t)r;
+ sip = malloc(l);
+ if (sip == NULL)
+ return 0;
+ decode_rfc1035(sip, l, data, dl);
+ }
+ break;
+ case 1:
+ if (dl == 0 || dl % 4 != 0) {
+ errno = EINVAL;
+ break;
+ }
+ addr.s_addr = INADDR_BROADCAST;
+ l = ((dl / sizeof(addr.s_addr)) * ((4 * 4) + 1)) + 1;
+ sip = p = malloc(l);
+ if (sip == NULL)
+ return 0;
+ while (dl != 0) {
+ memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
+ data += sizeof(addr.s_addr);
+ p += snprintf(p, l - (size_t)(p - sip),
+ "%s ", inet_ntoa(addr));
+ dl -= sizeof(addr.s_addr);
+ }
+ *--p = '\0';
+ break;
+ default:
+ errno = EINVAL;
+ return 0;
+ }
+
+ return sip;
+}
+
+/* Decode an RFC5969 6rd order option into a space
+ * separated string. Returns length of string (including
+ * terminating zero) or zero on error. */
+ssize_t
+decode_rfc5969(char *out, size_t len, const uint8_t *p, size_t pl)
+{
+ uint8_t ipv4masklen, ipv6prefixlen;
+ uint8_t ipv6prefix[16];
+ uint8_t br[4];
+ int i;
+ ssize_t b, bytes = 0;
+
+ if (pl < 22) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ ipv4masklen = *p++;
+ pl--;
+ ipv6prefixlen = *p++;
+ pl--;
+
+ for (i = 0; i < 16; i++) {
+ ipv6prefix[i] = *p++;
+ pl--;
+ }
+ if (out) {
+ b= snprintf(out, len,
+ "%d %d "
+ "%02x%02x:%02x%02x:"
+ "%02x%02x:%02x%02x:"
+ "%02x%02x:%02x%02x:"
+ "%02x%02x:%02x%02x",
+ ipv4masklen, ipv6prefixlen,
+ ipv6prefix[0], ipv6prefix[1], ipv6prefix[2], ipv6prefix[3],
+ ipv6prefix[4], ipv6prefix[5], ipv6prefix[6], ipv6prefix[7],
+ ipv6prefix[8], ipv6prefix[9], ipv6prefix[10],ipv6prefix[11],
+ ipv6prefix[12],ipv6prefix[13],ipv6prefix[14], ipv6prefix[15]
+ );
+
+ len -= (size_t)b;
+ out += b;
+ bytes += b;
+ } else {
+ bytes += 16 * 2 + 8 + 2 + 1 + 2;
+ }
+
+ while (pl >= 4) {
+ br[0] = *p++;
+ br[1] = *p++;
+ br[2] = *p++;
+ br[3] = *p++;
+ pl -= 4;
+
+ if (out) {
+ b= snprintf(out, len, " %d.%d.%d.%d",
+ br[0], br[1], br[2], br[3]);
+ len -= (size_t)b;
+ out += b;
+ bytes += b;
+ } else {
+ bytes += (4 * 4);
+ }
+ }
+
+ return bytes;
+}
+
+static char *
+get_option_string(struct dhcpcd_ctx *ctx,
+ const struct dhcp_message *dhcp, uint8_t option)
+{
+ size_t len;
+ const uint8_t *p;
+ char *s;
+
+ p = get_option(ctx, dhcp, option, &len);
+ if (!p || len == 0 || *p == '\0')
+ return NULL;
+
+ s = malloc(sizeof(char) * (len + 1));
+ if (s) {
+ memcpy(s, p, len);
+ s[len] = '\0';
+ }
+ return s;
+}
+
+/* This calculates the netmask that we should use for static routes.
+ * This IS different from the calculation used to calculate the netmask
+ * for an interface address. */
+static uint32_t
+route_netmask(uint32_t ip_in)
+{
+ /* used to be unsigned long - check if error */
+ uint32_t p = ntohl(ip_in);
+ uint32_t t;
+
+ if (IN_CLASSA(p))
+ t = ~IN_CLASSA_NET;
+ else {
+ if (IN_CLASSB(p))
+ t = ~IN_CLASSB_NET;
+ else {
+ if (IN_CLASSC(p))
+ t = ~IN_CLASSC_NET;
+ else
+ t = 0;
+ }
+ }
+
+ while (t & p)
+ t >>= 1;
+
+ return (htonl(~t));
+}
+
+/* We need to obey routing options.
+ * If we have a CSR then we only use that.
+ * Otherwise we add static routes and then routers. */
+static struct rt_head *
+get_option_routes(struct interface *ifp, const struct dhcp_message *dhcp)
+{
+ struct if_options *ifo = ifp->options;
+ const uint8_t *p;
+ const uint8_t *e;
+ struct rt_head *routes = NULL;
+ struct rt *route = NULL;
+ size_t len;
+ const char *csr = "";
+
+ /* If we have CSR's then we MUST use these only */
+ if (!has_option_mask(ifo->nomask, DHO_CSR))
+ p = get_option(ifp->ctx, dhcp, DHO_CSR, &len);
+ else
+ p = NULL;
+ /* Check for crappy MS option */
+ if (!p && !has_option_mask(ifo->nomask, DHO_MSCSR)) {
+ p = get_option(ifp->ctx, dhcp, DHO_MSCSR, &len);
+ if (p)
+ csr = "MS ";
+ }
+ if (p) {
+ routes = decode_rfc3442_rt(ifp->ctx, p, len);
+ if (routes) {
+ const struct dhcp_state *state;
+
+ state = D_CSTATE(ifp);
+ if (!(ifo->options & DHCPCD_CSR_WARNED) &&
+ !(state->added & STATE_FAKE))
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: using %sClassless Static Routes",
+ ifp->name, csr);
+ ifo->options |= DHCPCD_CSR_WARNED;
+ }
+ return routes;
+ }
+ }
+
+ /* OK, get our static routes first. */
+ routes = malloc(sizeof(*routes));
+ if (routes == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ TAILQ_INIT(routes);
+ if (!has_option_mask(ifo->nomask, DHO_STATICROUTE))
+ p = get_option(ifp->ctx, dhcp, DHO_STATICROUTE, &len);
+ else
+ p = NULL;
+ /* RFC 2131 Section 5.8 states length MUST be in multiples of 8 */
+ if (p && len % 8 == 0) {
+ e = p + len;
+ while (p < e) {
+ if ((route = calloc(1, sizeof(*route))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(routes);
+ return NULL;
+ }
+ memcpy(&route->dest.s_addr, p, 4);
+ p += 4;
+ memcpy(&route->gate.s_addr, p, 4);
+ p += 4;
+ /* RFC 2131 Section 5.8 states default route is
+ * illegal */
+ if (route->dest.s_addr == htonl(INADDR_ANY)) {
+ errno = EINVAL;
+ free(route);
+ continue;
+ }
+ /* A host route is normally set by having the
+ * gateway match the destination or assigned address */
+ if (route->gate.s_addr == route->dest.s_addr ||
+ route->gate.s_addr == dhcp->yiaddr)
+ {
+ route->gate.s_addr = htonl(INADDR_ANY);
+ route->net.s_addr = htonl(INADDR_BROADCAST);
+ }
+ route->net.s_addr = route_netmask(route->dest.s_addr);
+ TAILQ_INSERT_TAIL(routes, route, next);
+ }
+ }
+
+ /* Now grab our routers */
+ if (!has_option_mask(ifo->nomask, DHO_ROUTER))
+ p = get_option(ifp->ctx, dhcp, DHO_ROUTER, &len);
+ else
+ p = NULL;
+ if (p) {
+ e = p + len;
+ while (p < e) {
+ if ((route = calloc(1, sizeof(*route))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(routes);
+ return NULL;
+ }
+ memcpy(&route->gate.s_addr, p, 4);
+ p += 4;
+ TAILQ_INSERT_TAIL(routes, route, next);
+ }
+ }
+
+ return routes;
+}
+
+uint16_t
+dhcp_get_mtu(const struct interface *ifp)
+{
+ const struct dhcp_message *dhcp;
+ uint16_t mtu;
+
+ if (ifp->options->mtu)
+ return (uint16_t)ifp->options->mtu;
+ mtu = 0; /* bogus gcc warning */
+ if ((dhcp = D_CSTATE(ifp)->new) == NULL ||
+ has_option_mask(ifp->options->nomask, DHO_MTU) ||
+ get_option_uint16(ifp->ctx, &mtu, dhcp, DHO_MTU) == -1)
+ return 0;
+ return mtu;
+}
+
+/* Grab our routers from the DHCP message and apply any MTU value
+ * the message contains */
+struct rt_head *
+dhcp_get_routes(struct interface *ifp)
+{
+ struct rt_head *routes;
+ uint16_t mtu;
+ const struct dhcp_message *dhcp;
+
+ dhcp = D_CSTATE(ifp)->new;
+ routes = get_option_routes(ifp, dhcp);
+ if ((mtu = dhcp_get_mtu(ifp)) != 0) {
+ struct rt *rt;
+
+ TAILQ_FOREACH(rt, routes, next) {
+ rt->mtu = mtu;
+ }
+ }
+ return routes;
+}
+
+#define PUTADDR(_type, _val) \
+ { \
+ *p++ = _type; \
+ *p++ = 4; \
+ memcpy(p, &_val.s_addr, 4); \
+ p += 4; \
+ }
+
+int
+dhcp_message_add_addr(struct dhcp_message *dhcp,
+ uint8_t type, struct in_addr addr)
+{
+ uint8_t *p;
+ size_t len;
+
+ p = dhcp->options;
+ while (*p != DHO_END) {
+ p++;
+ p += *p + 1;
+ }
+
+ len = (size_t)(p - (uint8_t *)dhcp);
+ if (len + 6 > sizeof(*dhcp)) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ PUTADDR(type, addr);
+ *p = DHO_END;
+ return 0;
+}
+
+ssize_t
+make_message(struct dhcp_message **message,
+ const struct interface *ifp,
+ uint8_t type)
+{
+ struct dhcp_message *dhcp;
+ uint8_t *m, *lp, *p, *auth;
+ uint8_t *n_params = NULL, auth_len;
+ uint32_t ul;
+ uint16_t sz;
+ size_t len, i;
+ const struct dhcp_opt *opt;
+ struct if_options *ifo = ifp->options;
+ const struct dhcp_state *state = D_CSTATE(ifp);
+ const struct dhcp_lease *lease = &state->lease;
+ char hbuf[HOSTNAME_MAX_LEN + 1];
+ const char *hostname;
+ const struct vivco *vivco;
+
+ dhcp = calloc(1, sizeof (*dhcp));
+ if (dhcp == NULL)
+ return -1;
+ m = (uint8_t *)dhcp;
+ p = dhcp->options;
+
+ if ((type == DHCP_INFORM || type == DHCP_RELEASE ||
+ (type == DHCP_REQUEST &&
+ state->net.s_addr == lease->net.s_addr &&
+ (state->new == NULL ||
+ state->new->cookie == htonl(MAGIC_COOKIE)))))
+ {
+ dhcp->ciaddr = state->addr.s_addr;
+ /* In-case we haven't actually configured the address yet */
+ if (type == DHCP_INFORM && state->addr.s_addr == 0)
+ dhcp->ciaddr = lease->addr.s_addr;
+ }
+
+ dhcp->op = DHCP_BOOTREQUEST;
+ dhcp->hwtype = (uint8_t)ifp->family;
+ switch (ifp->family) {
+ case ARPHRD_ETHER:
+ case ARPHRD_IEEE802:
+ dhcp->hwlen = (uint8_t)ifp->hwlen;
+ memcpy(&dhcp->chaddr, &ifp->hwaddr, ifp->hwlen);
+ break;
+ }
+
+ if (ifo->options & DHCPCD_BROADCAST &&
+ dhcp->ciaddr == 0 &&
+ type != DHCP_DECLINE &&
+ type != DHCP_RELEASE)
+ dhcp->flags = htons(BROADCAST_FLAG);
+
+ if (type != DHCP_DECLINE && type != DHCP_RELEASE) {
+ struct timespec tv;
+
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ timespecsub(&tv, &state->started, &tv);
+ if (tv.tv_sec < 0 || tv.tv_sec > (time_t)UINT16_MAX)
+ dhcp->secs = htons((uint16_t)UINT16_MAX);
+ else
+ dhcp->secs = htons((uint16_t)tv.tv_sec);
+ }
+ dhcp->xid = htonl(state->xid);
+ dhcp->cookie = htonl(MAGIC_COOKIE);
+
+ if (!(ifo->options & DHCPCD_BOOTP)) {
+ *p++ = DHO_MESSAGETYPE;
+ *p++ = 1;
+ *p++ = type;
+ }
+
+ if (state->clientid) {
+ *p++ = DHO_CLIENTID;
+ memcpy(p, state->clientid, (size_t)state->clientid[0] + 1);
+ p += state->clientid[0] + 1;
+ }
+
+ if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) {
+ if (type == DHCP_DECLINE ||
+ (type == DHCP_REQUEST &&
+ lease->addr.s_addr != state->addr.s_addr))
+ {
+ PUTADDR(DHO_IPADDRESS, lease->addr);
+ if (lease->server.s_addr)
+ PUTADDR(DHO_SERVERID, lease->server);
+ }
+
+ if (type == DHCP_RELEASE) {
+ if (lease->server.s_addr)
+ PUTADDR(DHO_SERVERID, lease->server);
+ }
+ }
+
+ if (type == DHCP_DECLINE) {
+ *p++ = DHO_MESSAGE;
+ len = strlen(DAD);
+ *p++ = (uint8_t)len;
+ memcpy(p, DAD, len);
+ p += len;
+ }
+
+ if (type == DHCP_DISCOVER &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+ has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT))
+ {
+ /* RFC 4039 Section 3 */
+ *p++ = DHO_RAPIDCOMMIT;
+ *p++ = 0;
+ }
+
+ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST)
+ PUTADDR(DHO_IPADDRESS, ifo->req_addr);
+
+ /* RFC 2563 Auto Configure */
+ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL) {
+ *p++ = DHO_AUTOCONFIGURE;
+ *p++ = 1;
+ *p++ = 1;
+ }
+
+ if (type == DHCP_DISCOVER ||
+ type == DHCP_INFORM ||
+ type == DHCP_REQUEST)
+ {
+ if (!(ifo->options & DHCPCD_BOOTP)) {
+ int mtu;
+
+ if ((mtu = if_getmtu(ifp)) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: if_getmtu: %m", ifp->name);
+ else if (mtu < MTU_MIN) {
+ if (if_setmtu(ifp, MTU_MIN) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: if_setmtu: %m", ifp->name);
+ mtu = MTU_MIN;
+ } else if (mtu > MTU_MAX) {
+ /* Even though our MTU could be greater than
+ * MTU_MAX (1500) dhcpcd does not presently
+ * handle DHCP packets any bigger. */
+ mtu = MTU_MAX;
+ }
+ if (mtu != -1) {
+ *p++ = DHO_MAXMESSAGESIZE;
+ *p++ = 2;
+ sz = htons((uint16_t)mtu);
+ memcpy(p, &sz, 2);
+ p += 2;
+ }
+ }
+
+ if (ifo->userclass[0]) {
+ *p++ = DHO_USERCLASS;
+ memcpy(p, ifo->userclass,
+ (size_t)ifo->userclass[0] + 1);
+ p += ifo->userclass[0] + 1;
+ }
+
+ if (ifo->vendorclassid[0]) {
+ *p++ = DHO_VENDORCLASSID;
+ memcpy(p, ifo->vendorclassid,
+ (size_t)ifo->vendorclassid[0] + 1);
+ p += ifo->vendorclassid[0] + 1;
+ }
+
+ if (type != DHCP_INFORM) {
+ if (ifo->leasetime != 0) {
+ *p++ = DHO_LEASETIME;
+ *p++ = 4;
+ ul = htonl(ifo->leasetime);
+ memcpy(p, &ul, 4);
+ p += 4;
+ }
+ }
+
+ if (ifo->hostname[0] == '\0')
+ hostname = get_hostname(hbuf, sizeof(hbuf),
+ ifo->options & DHCPCD_HOSTNAME_SHORT ? 1 : 0);
+ else
+ hostname = ifo->hostname;
+
+ /*
+ * RFC4702 3.1 States that if we send the Client FQDN option
+ * then we MUST NOT also send the Host Name option.
+ * Technically we could, but that is not RFC conformant and
+ * also seems to break some DHCP server implemetations such as
+ * Windows. On the other hand, ISC dhcpd is just as non RFC
+ * conformant by not accepting a partially qualified FQDN.
+ */
+ if (ifo->fqdn != FQDN_DISABLE) {
+ /* IETF DHC-FQDN option (81), RFC4702 */
+ *p++ = DHO_FQDN;
+ lp = p;
+ *p++ = 3;
+ /*
+ * Flags: 0000NEOS
+ * S: 1 => Client requests Server to update
+ * a RR in DNS as well as PTR
+ * O: 1 => Server indicates to client that
+ * DNS has been updated
+ * E: 1 => Name data is DNS format
+ * N: 1 => Client requests Server to not
+ * update DNS
+ */
+ if (hostname)
+ *p++ = (uint8_t)((ifo->fqdn & 0x09) | 0x04);
+ else
+ *p++ = (FQDN_NONE & 0x09) | 0x04;
+ *p++ = 0; /* from server for PTR RR */
+ *p++ = 0; /* from server for A RR if S=1 */
+ if (hostname) {
+ i = encode_rfc1035(hostname, p);
+ *lp = (uint8_t)(*lp + i);
+ p += i;
+ }
+ } else if (ifo->options & DHCPCD_HOSTNAME && hostname) {
+ *p++ = DHO_HOSTNAME;
+ len = strlen(hostname);
+ *p++ = (uint8_t)len;
+ memcpy(p, hostname, len);
+ p += len;
+ }
+
+ /* vendor is already encoded correctly, so just add it */
+ if (ifo->vendor[0]) {
+ *p++ = DHO_VENDOR;
+ memcpy(p, ifo->vendor, (size_t)ifo->vendor[0] + 1);
+ p += ifo->vendor[0] + 1;
+ }
+
+ if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
+ DHCPCD_AUTH_SENDREQUIRE)
+ {
+ /* We support HMAC-MD5 */
+ *p++ = DHO_FORCERENEW_NONCE;
+ *p++ = 1;
+ *p++ = AUTH_ALG_HMAC_MD5;
+ }
+
+ if (ifo->vivco_len) {
+ *p++ = DHO_VIVCO;
+ lp = p++;
+ *lp = sizeof(ul);
+ ul = htonl(ifo->vivco_en);
+ memcpy(p, &ul, sizeof(ul));
+ p += sizeof(ul);
+ for (i = 0, vivco = ifo->vivco;
+ i < ifo->vivco_len;
+ i++, vivco++)
+ {
+ len = (size_t)(p - m) + vivco->len + 1;
+ if (len > sizeof(*dhcp))
+ goto toobig;
+ if (vivco->len + 2 + *lp > 255) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: VIVCO option too big",
+ ifp->name);
+ free(dhcp);
+ return -1;
+ }
+ *p++ = (uint8_t)vivco->len;
+ memcpy(p, vivco->data, vivco->len);
+ p += vivco->len;
+ *lp = (uint8_t)(*lp + vivco->len + 1);
+ }
+ }
+
+ len = (size_t)((p - m) + 3);
+ if (len > sizeof(*dhcp))
+ goto toobig;
+ *p++ = DHO_PARAMETERREQUESTLIST;
+ n_params = p;
+ *p++ = 0;
+ for (i = 0, opt = ifp->ctx->dhcp_opts;
+ i < ifp->ctx->dhcp_opts_len;
+ i++, opt++)
+ {
+ if (!(opt->type & REQUEST ||
+ has_option_mask(ifo->requestmask, opt->option)))
+ continue;
+ if (opt->type & NOREQ)
+ continue;
+ if (type == DHCP_INFORM &&
+ (opt->option == DHO_RENEWALTIME ||
+ opt->option == DHO_REBINDTIME))
+ continue;
+ len = (size_t)((p - m) + 2);
+ if (len > sizeof(*dhcp))
+ goto toobig;
+ *p++ = (uint8_t)opt->option;
+ }
+ for (i = 0, opt = ifo->dhcp_override;
+ i < ifo->dhcp_override_len;
+ i++, opt++)
+ {
+ /* Check if added above */
+ for (lp = n_params + 1; lp < p; lp++)
+ if (*lp == (uint8_t)opt->option)
+ break;
+ if (lp < p)
+ continue;
+ if (!(opt->type & REQUEST ||
+ has_option_mask(ifo->requestmask, opt->option)))
+ continue;
+ if (opt->type & NOREQ)
+ continue;
+ if (type == DHCP_INFORM &&
+ (opt->option == DHO_RENEWALTIME ||
+ opt->option == DHO_REBINDTIME))
+ continue;
+ len = (size_t)((p - m) + 2);
+ if (len > sizeof(*dhcp))
+ goto toobig;
+ *p++ = (uint8_t)opt->option;
+ }
+ *n_params = (uint8_t)(p - n_params - 1);
+ }
+
+ /* silence GCC */
+ auth_len = 0;
+ auth = NULL;
+
+ if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ ssize_t alen = dhcp_auth_encode(&ifo->auth,
+ state->auth.token,
+ NULL, 0, 4, type, NULL, 0);
+ if (alen != -1 && alen > UINT8_MAX) {
+ errno = ERANGE;
+ alen = -1;
+ }
+ if (alen == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp_auth_encode: %m", ifp->name);
+ else if (alen != 0) {
+ auth_len = (uint8_t)alen;
+ len = (size_t)((p + alen) - m);
+ if (len > sizeof(*dhcp))
+ goto toobig;
+ *p++ = DHO_AUTHENTICATION;
+ *p++ = auth_len;
+ auth = p;
+ p += auth_len;
+ }
+ }
+
+ *p++ = DHO_END;
+
+ /* Pad out to the BOOTP minimum message length.
+ * Some DHCP servers incorrectly require this. */
+ while (p - m < BOOTP_MESSAGE_LENTH_MIN)
+ *p++ = DHO_PAD;
+
+ len = (size_t)(p - m);
+ if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0)
+ dhcp_auth_encode(&ifo->auth, state->auth.token,
+ m, len, 4, type, auth, auth_len);
+
+ *message = dhcp;
+ return (ssize_t)len;
+
+toobig:
+ logger(ifp->ctx, LOG_ERR, "%s: DHCP messge too big", ifp->name);
+ free(dhcp);
+ return -1;
+}
+
+static ssize_t
+write_lease(const struct interface *ifp, const struct dhcp_message *dhcp)
+{
+ int fd;
+ size_t len;
+ ssize_t bytes;
+ const uint8_t *e, *p;
+ uint8_t l;
+ uint8_t o = 0;
+ const struct dhcp_state *state = D_CSTATE(ifp);
+
+ /* We don't write BOOTP leases */
+ if (IS_BOOTP(ifp, dhcp)) {
+ unlink(state->leasefile);
+ return 0;
+ }
+
+ logger(ifp->ctx, LOG_DEBUG, "%s: writing lease `%s'",
+ ifp->name, state->leasefile);
+
+ fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd == -1)
+ return -1;
+
+ /* Only write as much as we need */
+ p = dhcp->options;
+ e = p + sizeof(dhcp->options);
+ len = sizeof(*dhcp);
+ while (p < e) {
+ o = *p;
+ if (o == DHO_END) {
+ len = (size_t)(p - (const uint8_t *)dhcp);
+ break;
+ }
+ p++;
+ if (o != DHO_PAD) {
+ l = *p++;
+ p += l;
+ }
+ }
+ bytes = write(fd, dhcp, len);
+ close(fd);
+ return bytes;
+}
+
+static struct dhcp_message *
+read_lease(struct interface *ifp)
+{
+ int fd;
+ struct dhcp_message *dhcp;
+ struct dhcp_state *state = D_STATE(ifp);
+ ssize_t bytes;
+ const uint8_t *auth;
+ uint8_t type;
+ size_t auth_len;
+
+ fd = open(state->leasefile, O_RDONLY);
+ if (fd == -1) {
+ if (errno != ENOENT)
+ logger(ifp->ctx, LOG_ERR, "%s: open `%s': %m",
+ ifp->name, state->leasefile);
+ return NULL;
+ }
+ logger(ifp->ctx, LOG_DEBUG, "%s: reading lease `%s'",
+ ifp->name, state->leasefile);
+ dhcp = calloc(1, sizeof(*dhcp));
+ if (dhcp == NULL) {
+ close(fd);
+ return NULL;
+ }
+ bytes = read(fd, dhcp, sizeof(*dhcp));
+ close(fd);
+ if (bytes < 0) {
+ free(dhcp);
+ return NULL;
+ }
+
+ /* We may have found a BOOTP server */
+ if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1)
+ type = 0;
+
+ /* Authenticate the message */
+ auth = get_option(ifp->ctx, dhcp, DHO_AUTHENTICATION, &auth_len);
+ if (auth) {
+ if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+ (uint8_t *)dhcp, sizeof(*dhcp), 4, type,
+ auth, auth_len) == NULL)
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: dhcp_auth_validate: %m", ifp->name);
+ free(dhcp);
+ return NULL;
+ }
+ if (state->auth.token)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: accepted reconfigure key", ifp->name);
+ } else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) ==
+ DHCPCD_AUTH_SENDREQUIRE)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: authentication now required", ifp->name);
+ free(dhcp);
+ return NULL;
+ }
+
+ return dhcp;
+}
+
+static const struct dhcp_opt *
+dhcp_getoverride(const struct if_options *ifo, unsigned int o)
+{
+ size_t i;
+ const struct dhcp_opt *opt;
+
+ for (i = 0, opt = ifo->dhcp_override;
+ i < ifo->dhcp_override_len;
+ i++, opt++)
+ {
+ if (opt->option == o)
+ return opt;
+ }
+ return NULL;
+}
+
+static const uint8_t *
+dhcp_getoption(struct dhcpcd_ctx *ctx,
+ size_t *os, unsigned int *code, size_t *len,
+ const uint8_t *od, size_t ol, struct dhcp_opt **oopt)
+{
+ size_t i;
+ struct dhcp_opt *opt;
+
+ if (od) {
+ if (ol < 2) {
+ errno = EINVAL;
+ return NULL;
+ }
+ *os = 2; /* code + len */
+ *code = (unsigned int)*od++;
+ *len = (size_t)*od++;
+ if (*len > ol) {
+ errno = EINVAL;
+ return NULL;
+ }
+ }
+
+ for (i = 0, opt = ctx->dhcp_opts; i < ctx->dhcp_opts_len; i++, opt++) {
+ if (opt->option == *code) {
+ *oopt = opt;
+ break;
+ }
+ }
+
+ return od;
+}
+
+ssize_t
+dhcp_env(char **env, const char *prefix, const struct dhcp_message *dhcp,
+ const struct interface *ifp)
+{
+ const struct if_options *ifo;
+ const uint8_t *p;
+ struct in_addr addr;
+ struct in_addr net;
+ struct in_addr brd;
+ struct dhcp_opt *opt, *vo;
+ size_t e, i, pl;
+ char **ep;
+ char cidr[4], safe[(BOOTFILE_LEN * 4) + 1];
+ uint8_t overl = 0;
+ uint32_t en;
+
+ e = 0;
+ ifo = ifp->options;
+ get_option_uint8(ifp->ctx, &overl, dhcp, DHO_OPTIONSOVERLOADED);
+
+ if (env == NULL) {
+ if (dhcp->yiaddr || dhcp->ciaddr)
+ e += 5;
+ if (*dhcp->bootfile && !(overl & 1))
+ e++;
+ if (*dhcp->servername && !(overl & 2))
+ e++;
+ for (i = 0, opt = ifp->ctx->dhcp_opts;
+ i < ifp->ctx->dhcp_opts_len;
+ i++, opt++)
+ {
+ if (has_option_mask(ifo->nomask, opt->option))
+ continue;
+ if (dhcp_getoverride(ifo, opt->option))
+ continue;
+ p = get_option(ifp->ctx, dhcp, opt->option, &pl);
+ if (!p)
+ continue;
+ e += dhcp_envoption(ifp->ctx, NULL, NULL, ifp->name,
+ opt, dhcp_getoption, p, pl);
+ }
+ for (i = 0, opt = ifo->dhcp_override;
+ i < ifo->dhcp_override_len;
+ i++, opt++)
+ {
+ if (has_option_mask(ifo->nomask, opt->option))
+ continue;
+ p = get_option(ifp->ctx, dhcp, opt->option, &pl);
+ if (!p)
+ continue;
+ e += dhcp_envoption(ifp->ctx, NULL, NULL, ifp->name,
+ opt, dhcp_getoption, p, pl);
+ }
+ return (ssize_t)e;
+ }
+
+ ep = env;
+ if (dhcp->yiaddr || dhcp->ciaddr) {
+ /* Set some useful variables that we derive from the DHCP
+ * message but are not necessarily in the options */
+ addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr;
+ addvar(ifp->ctx, &ep, prefix, "ip_address", inet_ntoa(addr));
+ if (get_option_addr(ifp->ctx, &net,
+ dhcp, DHO_SUBNETMASK) == -1)
+ {
+ net.s_addr = ipv4_getnetmask(addr.s_addr);
+ addvar(ifp->ctx, &ep, prefix,
+ "subnet_mask", inet_ntoa(net));
+ }
+ snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net));
+ addvar(ifp->ctx, &ep, prefix, "subnet_cidr", cidr);
+ if (get_option_addr(ifp->ctx, &brd,
+ dhcp, DHO_BROADCAST) == -1)
+ {
+ brd.s_addr = addr.s_addr | ~net.s_addr;
+ addvar(ifp->ctx, &ep, prefix,
+ "broadcast_address", inet_ntoa(brd));
+ }
+ addr.s_addr = dhcp->yiaddr & net.s_addr;
+ addvar(ifp->ctx, &ep, prefix,
+ "network_number", inet_ntoa(addr));
+ }
+
+ if (*dhcp->bootfile && !(overl & 1)) {
+ print_string(safe, sizeof(safe), STRING,
+ dhcp->bootfile, sizeof(dhcp->bootfile));
+ addvar(ifp->ctx, &ep, prefix, "filename", safe);
+ }
+ if (*dhcp->servername && !(overl & 2)) {
+ print_string(safe, sizeof(safe), STRING | DOMAIN,
+ dhcp->servername, sizeof(dhcp->servername));
+ addvar(ifp->ctx, &ep, prefix, "server_name", safe);
+ }
+
+ /* Zero our indexes */
+ if (env) {
+ for (i = 0, opt = ifp->ctx->dhcp_opts;
+ i < ifp->ctx->dhcp_opts_len;
+ i++, opt++)
+ dhcp_zero_index(opt);
+ for (i = 0, opt = ifp->options->dhcp_override;
+ i < ifp->options->dhcp_override_len;
+ i++, opt++)
+ dhcp_zero_index(opt);
+ for (i = 0, opt = ifp->ctx->vivso;
+ i < ifp->ctx->vivso_len;
+ i++, opt++)
+ dhcp_zero_index(opt);
+ }
+
+ for (i = 0, opt = ifp->ctx->dhcp_opts;
+ i < ifp->ctx->dhcp_opts_len;
+ i++, opt++)
+ {
+ if (has_option_mask(ifo->nomask, opt->option))
+ continue;
+ if (dhcp_getoverride(ifo, opt->option))
+ continue;
+ if ((p = get_option(ifp->ctx, dhcp, opt->option, &pl))) {
+ ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name,
+ opt, dhcp_getoption, p, pl);
+ if (opt->option == DHO_VIVSO &&
+ pl > (int)sizeof(uint32_t))
+ {
+ memcpy(&en, p, sizeof(en));
+ en = ntohl(en);
+ vo = vivso_find(en, ifp);
+ if (vo) {
+ /* Skip over en + total size */
+ p += sizeof(en) + 1;
+ pl -= sizeof(en) + 1;
+ ep += dhcp_envoption(ifp->ctx,
+ ep, prefix, ifp->name,
+ vo, dhcp_getoption, p, pl);
+ }
+ }
+ }
+ }
+
+ for (i = 0, opt = ifo->dhcp_override;
+ i < ifo->dhcp_override_len;
+ i++, opt++)
+ {
+ if (has_option_mask(ifo->nomask, opt->option))
+ continue;
+ if ((p = get_option(ifp->ctx, dhcp, opt->option, &pl)))
+ ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name,
+ opt, dhcp_getoption, p, pl);
+ }
+
+ return ep - env;
+}
+
+static void
+get_lease(struct dhcpcd_ctx *ctx,
+ struct dhcp_lease *lease, const struct dhcp_message *dhcp)
+{
+
+ lease->cookie = dhcp->cookie;
+ /* BOOTP does not set yiaddr for replies when ciaddr is set. */
+ if (dhcp->yiaddr)
+ lease->addr.s_addr = dhcp->yiaddr;
+ else
+ lease->addr.s_addr = dhcp->ciaddr;
+ if (get_option_addr(ctx, &lease->net, dhcp, DHO_SUBNETMASK) == -1)
+ lease->net.s_addr = ipv4_getnetmask(lease->addr.s_addr);
+ if (get_option_addr(ctx, &lease->brd, dhcp, DHO_BROADCAST) == -1)
+ lease->brd.s_addr = lease->addr.s_addr | ~lease->net.s_addr;
+ if (get_option_uint32(ctx, &lease->leasetime, dhcp, DHO_LEASETIME) != 0)
+ lease->leasetime = ~0U; /* Default to infinite lease */
+ if (get_option_uint32(ctx, &lease->renewaltime,
+ dhcp, DHO_RENEWALTIME) != 0)
+ lease->renewaltime = 0;
+ if (get_option_uint32(ctx, &lease->rebindtime,
+ dhcp, DHO_REBINDTIME) != 0)
+ lease->rebindtime = 0;
+ if (get_option_addr(ctx, &lease->server, dhcp, DHO_SERVERID) != 0)
+ lease->server.s_addr = INADDR_ANY;
+}
+
+static const char *
+get_dhcp_op(uint8_t type)
+{
+ const struct dhcp_op *d;
+
+ for (d = dhcp_ops; d->name; d++)
+ if (d->value == type)
+ return d->name;
+ return NULL;
+}
+
+static void
+dhcp_fallback(void *arg)
+{
+ struct interface *iface;
+
+ iface = (struct interface *)arg;
+ dhcpcd_selectprofile(iface, iface->options->fallback);
+ dhcpcd_startinterface(iface);
+}
+
+uint32_t
+dhcp_xid(const struct interface *ifp)
+{
+ uint32_t xid;
+
+ if (ifp->options->options & DHCPCD_XID_HWADDR &&
+ ifp->hwlen >= sizeof(xid))
+ /* The lower bits are probably more unique on the network */
+ memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid),
+ sizeof(xid));
+ else
+ xid = arc4random();
+
+ return xid;
+}
+
+void
+dhcp_close(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+
+ if (state == NULL)
+ return;
+
+ if (state->raw_fd != -1) {
+ eloop_event_delete(ifp->ctx->eloop, state->raw_fd);
+ close(state->raw_fd);
+ state->raw_fd = -1;
+ }
+
+ state->interval = 0;
+}
+
+static int
+dhcp_openudp(struct interface *ifp)
+{
+ int s;
+ struct sockaddr_in sin;
+ int n;
+ struct dhcp_state *state;
+#ifdef SO_BINDTODEVICE
+ struct ifreq ifr;
+ char *p;
+#endif
+
+ if ((s = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP, O_CLOEXEC)) == -1)
+ return -1;
+
+ n = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1)
+ goto eexit;
+#ifdef SO_BINDTODEVICE
+ if (ifp) {
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ /* We can only bind to the real device */
+ p = strchr(ifr.ifr_name, ':');
+ if (p)
+ *p = '\0';
+ if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr,
+ sizeof(ifr)) == -1)
+ goto eexit;
+ }
+#endif
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(DHCP_CLIENT_PORT);
+ if (ifp) {
+ state = D_STATE(ifp);
+ sin.sin_addr.s_addr = state->addr.s_addr;
+ } else
+ state = NULL; /* appease gcc */
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1)
+ goto eexit;
+
+ return s;
+
+eexit:
+ close(s);
+ return -1;
+}
+
+static uint16_t
+checksum(const void *data, unsigned int len)
+{
+ const uint8_t *addr = data;
+ uint32_t sum = 0;
+
+ while (len > 1) {
+ sum += (uint32_t)(addr[0] * 256 + addr[1]);
+ addr += 2;
+ len -= 2;
+ }
+
+ if (len == 1)
+ sum += (uint32_t)(*addr * 256);
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ return (uint16_t)~htons((uint16_t)sum);
+}
+
+static struct udp_dhcp_packet *
+dhcp_makeudppacket(size_t *sz, const uint8_t *data, size_t length,
+ struct in_addr source, struct in_addr dest)
+{
+ struct udp_dhcp_packet *udpp;
+ struct ip *ip;
+ struct udphdr *udp;
+
+ udpp = calloc(1, sizeof(*udpp));
+ if (udpp == NULL)
+ return NULL;
+ ip = &udpp->ip;
+ udp = &udpp->udp;
+
+ /* OK, this is important :)
+ * We copy the data to our packet and then create a small part of the
+ * ip structure and an invalid ip_len (basically udp length).
+ * We then fill the udp structure and put the checksum
+ * of the whole packet into the udp checksum.
+ * Finally we complete the ip structure and ip checksum.
+ * If we don't do the ordering like so then the udp checksum will be
+ * broken, so find another way of doing it! */
+
+ memcpy(&udpp->dhcp, data, length);
+
+ ip->ip_p = IPPROTO_UDP;
+ ip->ip_src.s_addr = source.s_addr;
+ if (dest.s_addr == 0)
+ ip->ip_dst.s_addr = INADDR_BROADCAST;
+ else
+ ip->ip_dst.s_addr = dest.s_addr;
+
+ udp->uh_sport = htons(DHCP_CLIENT_PORT);
+ udp->uh_dport = htons(DHCP_SERVER_PORT);
+ udp->uh_ulen = htons((uint16_t)(sizeof(*udp) + length));
+ ip->ip_len = udp->uh_ulen;
+ udp->uh_sum = checksum(udpp, sizeof(*udpp));
+
+ ip->ip_v = IPVERSION;
+ ip->ip_hl = sizeof(*ip) >> 2;
+ ip->ip_id = (uint16_t)arc4random_uniform(UINT16_MAX);
+ ip->ip_ttl = IPDEFTTL;
+ ip->ip_len = htons((uint16_t)(sizeof(*ip) + sizeof(*udp) + length));
+ ip->ip_sum = checksum(ip, sizeof(*ip));
+
+ *sz = sizeof(*ip) + sizeof(*udp) + length;
+ return udpp;
+}
+
+static void
+send_message(struct interface *ifp, uint8_t type,
+ void (*callback)(void *))
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct if_options *ifo = ifp->options;
+ struct dhcp_message *dhcp;
+ struct udp_dhcp_packet *udp;
+ size_t len;
+ ssize_t r;
+ struct in_addr from, to;
+ in_addr_t a = INADDR_ANY;
+ struct timespec tv;
+ int s;
+#ifdef IN_IFF_NOTUSEABLE
+ struct ipv4_addr *ia;
+#endif
+
+ s = -1;
+ if (!callback) {
+ /* No carrier? Don't bother sending the packet. */
+ if (ifp->carrier == LINK_DOWN)
+ return;
+ logger(ifp->ctx, LOG_DEBUG, "%s: sending %s with xid 0x%x",
+ ifp->name,
+ ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type),
+ state->xid);
+ } else {
+ if (state->interval == 0)
+ state->interval = 4;
+ else {
+ state->interval *= 2;
+ if (state->interval > 64)
+ state->interval = 64;
+ }
+ tv.tv_sec = state->interval + DHCP_RAND_MIN;
+ tv.tv_nsec = (suseconds_t)arc4random_uniform(
+ (DHCP_RAND_MAX - DHCP_RAND_MIN) * NSEC_PER_SEC);
+ timespecnorm(&tv);
+ /* No carrier? Don't bother sending the packet.
+ * However, we do need to advance the timeout. */
+ if (ifp->carrier == LINK_DOWN)
+ goto fail;
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: sending %s (xid 0x%x), next in %0.1f seconds",
+ ifp->name,
+ ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type),
+ state->xid,
+ timespec_to_double(&tv));
+ }
+
+ if (dhcp_open(ifp) == -1)
+ return;
+
+ if (state->added && !(state->added & STATE_FAKE) &&
+ state->addr.s_addr != INADDR_ANY &&
+ state->new != NULL &&
+#ifdef IN_IFF_NOTUSEABLE
+ ((ia = ipv4_iffindaddr(ifp, &state->addr, NULL)) &&
+ !(ia->addr_flags & IN_IFF_NOTUSEABLE)) &&
+#endif
+ (state->lease.server.s_addr ||
+ ifp->options->options & DHCPCD_INFORM) &&
+ !IS_BOOTP(ifp, state->new))
+ {
+ s = dhcp_openudp(ifp);
+ if (s == -1) {
+ if (errno != EADDRINUSE)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp_openudp: %m", ifp->name);
+ /* We cannot renew */
+ a = state->addr.s_addr;
+ state->addr.s_addr = INADDR_ANY;
+ }
+ }
+
+ r = make_message(&dhcp, ifp, type);
+ if (a != INADDR_ANY)
+ state->addr.s_addr = a;
+ if (r == -1)
+ goto fail;
+ len = (size_t)r;
+ from.s_addr = dhcp->ciaddr;
+ if (s != -1 && from.s_addr != INADDR_ANY)
+ to.s_addr = state->lease.server.s_addr;
+ else
+ to.s_addr = INADDR_ANY;
+ if (to.s_addr && to.s_addr != INADDR_BROADCAST) {
+ struct sockaddr_in sin;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = to.s_addr;
+ sin.sin_port = htons(DHCP_SERVER_PORT);
+ r = sendto(s, (uint8_t *)dhcp, len, 0,
+ (struct sockaddr *)&sin, sizeof(sin));
+ if (r == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp_sendpacket: %m", ifp->name);
+ } else {
+ size_t ulen;
+
+ r = 0;
+ udp = dhcp_makeudppacket(&ulen, (uint8_t *)dhcp, len, from, to);
+ if (udp == NULL) {
+ logger(ifp->ctx, LOG_ERR, "dhcp_makeudppacket: %m");
+ } else {
+ r = if_sendrawpacket(ifp, ETHERTYPE_IP,
+ (uint8_t *)udp, ulen);
+ free(udp);
+ }
+ /* If we failed to send a raw packet this normally means
+ * we don't have the ability to work beneath the IP layer
+ * for this interface.
+ * As such we remove it from consideration without actually
+ * stopping the interface. */
+ if (r == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: if_sendrawpacket: %m", ifp->name);
+ switch(errno) {
+ case ENETDOWN:
+ case ENETRESET:
+ case ENETUNREACH:
+ break;
+ default:
+ if (!(ifp->ctx->options & DHCPCD_TEST))
+ dhcp_drop(ifp, "FAIL");
+ dhcp_free(ifp);
+ eloop_timeout_delete(ifp->ctx->eloop,
+ NULL, ifp);
+ callback = NULL;
+ }
+ }
+ }
+ free(dhcp);
+
+fail:
+ if (s != -1)
+ close(s);
+
+ /* Even if we fail to send a packet we should continue as we are
+ * as our failure timeouts will change out codepath when needed. */
+ if (callback)
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, callback, ifp);
+}
+
+static void
+send_inform(void *arg)
+{
+
+ send_message((struct interface *)arg, DHCP_INFORM, send_inform);
+}
+
+static void
+send_discover(void *arg)
+{
+
+ send_message((struct interface *)arg, DHCP_DISCOVER, send_discover);
+}
+
+static void
+send_request(void *arg)
+{
+
+ send_message((struct interface *)arg, DHCP_REQUEST, send_request);
+}
+
+static void
+send_renew(void *arg)
+{
+
+ send_message((struct interface *)arg, DHCP_REQUEST, send_renew);
+}
+
+static void
+send_rebind(void *arg)
+{
+
+ send_message((struct interface *)arg, DHCP_REQUEST, send_rebind);
+}
+
+void
+dhcp_discover(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct if_options *ifo = ifp->options;
+
+ state->state = DHS_DISCOVER;
+ state->xid = dhcp_xid(ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ if (ifo->fallback)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, dhcp_fallback, ifp);
+ else if (ifo->options & DHCPCD_IPV4LL)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, ipv4ll_start, ifp);
+ if (ifo->options & DHCPCD_REQUEST)
+ logger(ifp->ctx, LOG_INFO,
+ "%s: soliciting a DHCP lease (requesting %s)",
+ ifp->name, inet_ntoa(ifo->req_addr));
+ else
+ logger(ifp->ctx, LOG_INFO,
+ "%s: soliciting a %s lease",
+ ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : "DHCP");
+ send_discover(ifp);
+}
+
+static void
+dhcp_request(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+
+ state->state = DHS_REQUEST;
+ send_request(ifp);
+}
+
+static void
+dhcp_expire(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+
+ logger(ifp->ctx, LOG_ERR, "%s: DHCP lease expired", ifp->name);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ dhcp_drop(ifp, "EXPIRE");
+ unlink(state->leasefile);
+ state->interval = 0;
+ dhcp_discover(ifp);
+}
+
+static void
+dhcp_decline(struct interface *ifp)
+{
+
+ send_message(ifp, DHCP_DECLINE, NULL);
+}
+
+static void
+dhcp_renew(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct dhcp_lease *lease = &state->lease;
+
+ logger(ifp->ctx, LOG_DEBUG, "%s: renewing lease of %s",
+ ifp->name, inet_ntoa(lease->addr));
+ logger(ifp->ctx, LOG_DEBUG, "%s: rebind in %"PRIu32" seconds,"
+ " expire in %"PRIu32" seconds",
+ ifp->name, lease->rebindtime - lease->renewaltime,
+ lease->leasetime - lease->renewaltime);
+ state->state = DHS_RENEW;
+ state->xid = dhcp_xid(ifp);
+ send_renew(ifp);
+}
+
+static void
+dhcp_arp_announced(struct arp_state *astate)
+{
+
+ arp_close(astate->iface);
+}
+
+static void
+dhcp_rebind(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct dhcp_lease *lease = &state->lease;
+
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: failed to renew DHCP, rebinding", ifp->name);
+ logger(ifp->ctx, LOG_DEBUG, "%s: expire in %"PRIu32" seconds",
+ ifp->name, lease->leasetime - lease->rebindtime);
+ state->state = DHS_REBIND;
+ eloop_timeout_delete(ifp->ctx->eloop, send_renew, ifp);
+ state->lease.server.s_addr = 0;
+ ifp->options->options &= ~(DHCPCD_CSR_WARNED |
+ DHCPCD_ROUTER_HOST_ROUTE_WARNED);
+ send_rebind(ifp);
+}
+
+static void
+dhcp_arp_probed(struct arp_state *astate)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+
+ state = D_STATE(astate->iface);
+ ifo = astate->iface->options;
+ if (state->arping_index < ifo->arping_len) {
+ /* We didn't find a profile for this
+ * address or hwaddr, so move to the next
+ * arping profile */
+ if (++state->arping_index < ifo->arping_len) {
+ astate->addr.s_addr =
+ ifo->arping[state->arping_index - 1];
+ arp_probe(astate);
+ }
+ dhcpcd_startinterface(astate->iface);
+ return;
+ }
+
+ logger(astate->iface->ctx, LOG_DEBUG, "%s: DAD completed for %s",
+ astate->iface->name, inet_ntoa(astate->addr));
+ dhcp_bind(astate->iface);
+ arp_announce(astate);
+
+ /* Stop IPv4LL now we have a working DHCP address */
+ ipv4ll_drop(astate->iface);
+}
+
+static void
+dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct interface *ifp;
+ struct dhcp_state *state;
+ struct if_options *ifo;
+
+ ifp = astate->iface;
+ ifo = ifp->options;
+ state = D_STATE(ifp);
+ if (state->arping_index &&
+ state->arping_index <= ifo->arping_len &&
+ amsg &&
+ (amsg->sip.s_addr == ifo->arping[state->arping_index - 1] ||
+ (amsg->sip.s_addr == 0 &&
+ amsg->tip.s_addr == ifo->arping[state->arping_index - 1])))
+ {
+ char buf[HWADDR_LEN * 3];
+
+ astate->failed.s_addr = ifo->arping[state->arping_index - 1];
+ arp_report_conflicted(astate, amsg);
+ hwaddr_ntoa(amsg->sha, ifp->hwlen, buf, sizeof(buf));
+ if (dhcpcd_selectprofile(ifp, buf) == -1 &&
+ dhcpcd_selectprofile(ifp,
+ inet_ntoa(astate->failed)) == -1)
+ {
+ /* We didn't find a profile for this
+ * address or hwaddr, so move to the next
+ * arping profile */
+ dhcp_arp_probed(astate);
+ return;
+ }
+ dhcp_close(ifp);
+ arp_free(astate);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ dhcpcd_startinterface(ifp);
+ }
+
+ /* RFC 2131 3.1.5, Client-server interaction
+ * NULL amsg means IN_IFF_DUPLICATED */
+ if (amsg == NULL || (state->offer &&
+ (amsg->sip.s_addr == state->offer->yiaddr ||
+ (amsg->sip.s_addr == 0 &&
+ amsg->tip.s_addr == state->offer->yiaddr))))
+ {
+#ifdef IN_IFF_DUPLICATED
+ struct ipv4_addr *ia;
+#endif
+
+ if (amsg)
+ astate->failed.s_addr = state->offer->yiaddr;
+ else
+ astate->failed = astate->addr;
+ arp_report_conflicted(astate, amsg);
+ unlink(state->leasefile);
+ if (!(ifp->options->options & DHCPCD_STATIC) &&
+ !state->lease.frominfo)
+ dhcp_decline(ifp);
+#ifdef IN_IFF_DUPLICATED
+ ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
+ if (ia)
+ ipv4_deladdr(ifp, &ia->addr, &ia->net, 1);
+#endif
+ arp_free(astate);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ DHCP_RAND_MAX, dhcp_discover, ifp);
+ }
+}
+
+void
+dhcp_bind(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct if_options *ifo = ifp->options;
+ struct dhcp_lease *lease = &state->lease;
+
+ state->reason = NULL;
+ free(state->old);
+ state->old = state->new;
+ state->new = state->offer;
+ state->offer = NULL;
+ get_lease(ifp->ctx, lease, state->new);
+ if (ifo->options & DHCPCD_STATIC) {
+ logger(ifp->ctx, LOG_INFO, "%s: using static address %s/%d",
+ ifp->name, inet_ntoa(lease->addr),
+ inet_ntocidr(lease->net));
+ lease->leasetime = ~0U;
+ state->reason = "STATIC";
+ } else if (ifo->options & DHCPCD_INFORM) {
+ if (ifo->req_addr.s_addr != 0)
+ lease->addr.s_addr = ifo->req_addr.s_addr;
+ else
+ lease->addr.s_addr = state->addr.s_addr;
+ logger(ifp->ctx, LOG_INFO, "%s: received approval for %s",
+ ifp->name, inet_ntoa(lease->addr));
+ lease->leasetime = ~0U;
+ state->reason = "INFORM";
+ } else {
+ if (lease->frominfo)
+ state->reason = "TIMEOUT";
+ if (lease->leasetime == ~0U) {
+ lease->renewaltime =
+ lease->rebindtime =
+ lease->leasetime;
+ logger(ifp->ctx, LOG_INFO, "%s: leased %s for infinity",
+ ifp->name, inet_ntoa(lease->addr));
+ } else {
+ if (lease->leasetime < DHCP_MIN_LEASE) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: minimum lease is %d seconds",
+ ifp->name, DHCP_MIN_LEASE);
+ lease->leasetime = DHCP_MIN_LEASE;
+ }
+ if (lease->rebindtime == 0)
+ lease->rebindtime =
+ (uint32_t)(lease->leasetime * T2);
+ else if (lease->rebindtime >= lease->leasetime) {
+ lease->rebindtime =
+ (uint32_t)(lease->leasetime * T2);
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: rebind time greater than lease "
+ "time, forcing to %"PRIu32" seconds",
+ ifp->name, lease->rebindtime);
+ }
+ if (lease->renewaltime == 0)
+ lease->renewaltime =
+ (uint32_t)(lease->leasetime * T1);
+ else if (lease->renewaltime > lease->rebindtime) {
+ lease->renewaltime =
+ (uint32_t)(lease->leasetime * T1);
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: renewal time greater than rebind "
+ "time, forcing to %"PRIu32" seconds",
+ ifp->name, lease->renewaltime);
+ }
+ logger(ifp->ctx,
+ lease->addr.s_addr == state->addr.s_addr &&
+ !(state->added & STATE_FAKE) ?
+ LOG_DEBUG : LOG_INFO,
+ "%s: leased %s for %"PRIu32" seconds", ifp->name,
+ inet_ntoa(lease->addr), lease->leasetime);
+ }
+ }
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ state->reason = "TEST";
+ script_runreason(ifp, state->reason);
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+ if (state->reason == NULL) {
+ if (state->old && !(state->added & STATE_FAKE)) {
+ if (state->old->yiaddr == state->new->yiaddr &&
+ lease->server.s_addr)
+ state->reason = "RENEW";
+ else
+ state->reason = "REBIND";
+ } else if (state->state == DHS_REBOOT)
+ state->reason = "REBOOT";
+ else
+ state->reason = "BOUND";
+ }
+ if (lease->leasetime == ~0U)
+ lease->renewaltime = lease->rebindtime = lease->leasetime;
+ else {
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ (time_t)lease->renewaltime, dhcp_renew, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ (time_t)lease->rebindtime, dhcp_rebind, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ (time_t)lease->leasetime, dhcp_expire, ifp);
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: renew in %"PRIu32" seconds, rebind in %"PRIu32
+ " seconds",
+ ifp->name, lease->renewaltime, lease->rebindtime);
+ }
+ state->state = DHS_BOUND;
+ if (!state->lease.frominfo &&
+ !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))
+ if (write_lease(ifp, state->new) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: write_lease: %m", __func__);
+
+ ipv4_applyaddr(ifp);
+}
+
+static void
+dhcp_timeout(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+
+ dhcp_bind(ifp);
+ state->interval = 0;
+ dhcp_discover(ifp);
+}
+
+struct dhcp_message *
+dhcp_message_new(const struct in_addr *addr, const struct in_addr *mask)
+{
+ struct dhcp_message *dhcp;
+ uint8_t *p;
+
+ dhcp = calloc(1, sizeof(*dhcp));
+ if (dhcp == NULL)
+ return NULL;
+ dhcp->yiaddr = addr->s_addr;
+ dhcp->cookie = htonl(MAGIC_COOKIE);
+ p = dhcp->options;
+ if (mask && mask->s_addr != INADDR_ANY) {
+ *p++ = DHO_SUBNETMASK;
+ *p++ = sizeof(mask->s_addr);
+ memcpy(p, &mask->s_addr, sizeof(mask->s_addr));
+ p+= sizeof(mask->s_addr);
+ }
+ *p++ = DHO_END;
+ return dhcp;
+}
+
+static void
+dhcp_arp_bind(struct interface *ifp)
+{
+ const struct dhcp_state *state;
+ struct in_addr addr;
+ struct ipv4_addr *ia;
+ struct arp_state *astate;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+ state = D_CSTATE(ifp);
+ addr.s_addr = state->offer->yiaddr;
+ /* If the interface already has the address configured
+ * then we can't ARP for duplicate detection. */
+ ia = ipv4_findaddr(ifp->ctx, &addr);
+
+#ifdef IN_IFF_TENTATIVE
+ if (ia == NULL || ia->addr_flags & IN_IFF_NOTUSEABLE) {
+ if ((astate = arp_new(ifp, &addr)) != NULL) {
+ astate->probed_cb = dhcp_arp_probed;
+ astate->conflicted_cb = dhcp_arp_conflicted;
+ astate->announced_cb = dhcp_arp_announced;
+ }
+ if (ia == NULL) {
+ struct dhcp_lease l;
+
+ get_lease(ifp->ctx, &l, state->offer);
+ /* Add the address now, let the kernel handle DAD. */
+ ipv4_addaddr(ifp, &l.addr, &l.net, &l.brd);
+ } else
+ logger(ifp->ctx, LOG_INFO, "%s: waiting for DAD on %s",
+ ifp->name, inet_ntoa(addr));
+ return;
+ }
+#else
+ if (ifp->options->options & DHCPCD_ARP && ia == NULL) {
+ struct dhcp_lease l;
+
+ get_lease(ifp->ctx, &l, state->offer);
+ logger(ifp->ctx, LOG_INFO, "%s: probing static address %s/%d",
+ ifp->name, inet_ntoa(l.addr), inet_ntocidr(l.net));
+ if ((astate = arp_new(ifp, &addr)) != NULL) {
+ astate->probed_cb = dhcp_arp_probed;
+ astate->conflicted_cb = dhcp_arp_conflicted;
+ astate->announced_cb = dhcp_arp_announced;
+ /* We need to handle DAD. */
+ arp_probe(astate);
+ }
+ return;
+ }
+#endif
+
+ dhcp_bind(ifp);
+}
+
+static void
+dhcp_static(struct interface *ifp)
+{
+ struct if_options *ifo;
+ struct dhcp_state *state;
+ struct ipv4_addr *ia;
+
+ state = D_STATE(ifp);
+ ifo = ifp->options;
+
+ ia = NULL;
+ if (ifo->req_addr.s_addr == INADDR_ANY &&
+ (ia = ipv4_iffindaddr(ifp, NULL, NULL)) == NULL)
+ {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: waiting for 3rd party to "
+ "configure IP address",
+ ifp->name);
+ state->reason = "3RDPARTY";
+ script_runreason(ifp, state->reason);
+ return;
+ }
+
+ state->offer = dhcp_message_new(ia ? &ia->addr : &ifo->req_addr,
+ ia ? &ia->net : &ifo->req_mask);
+ if (state->offer)
+ dhcp_arp_bind(ifp);
+}
+
+void
+dhcp_inform(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+ struct ipv4_addr *ap;
+
+ state = D_STATE(ifp);
+ ifo = ifp->options;
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ state->addr.s_addr = ifo->req_addr.s_addr;
+ state->net.s_addr = ifo->req_mask.s_addr;
+ } else {
+ if (ifo->req_addr.s_addr == INADDR_ANY) {
+ state = D_STATE(ifp);
+ ap = ipv4_iffindaddr(ifp, NULL, NULL);
+ if (ap == NULL) {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: waiting for 3rd party to "
+ "configure IP address",
+ ifp->name);
+ state->reason = "3RDPARTY";
+ script_runreason(ifp, state->reason);
+ return;
+ }
+ state->offer =
+ dhcp_message_new(&ap->addr, &ap->net);
+ } else
+ state->offer =
+ dhcp_message_new(&ifo->req_addr, &ifo->req_mask);
+ if (state->offer) {
+ ifo->options |= DHCPCD_STATIC;
+ dhcp_bind(ifp);
+ ifo->options &= ~DHCPCD_STATIC;
+ }
+ }
+
+ state->state = DHS_INFORM;
+ state->xid = dhcp_xid(ifp);
+ send_inform(ifp);
+}
+
+void
+dhcp_reboot_newopts(struct interface *ifp, unsigned long long oldopts)
+{
+ struct if_options *ifo;
+ struct dhcp_state *state = D_STATE(ifp);
+
+ if (state == NULL)
+ return;
+ ifo = ifp->options;
+ if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) &&
+ state->addr.s_addr != ifo->req_addr.s_addr) ||
+ (oldopts & (DHCPCD_INFORM | DHCPCD_STATIC) &&
+ !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))))
+ {
+ dhcp_drop(ifp, "EXPIRE");
+ }
+}
+
+static void
+dhcp_reboot(struct interface *ifp)
+{
+ struct if_options *ifo;
+ struct dhcp_state *state = D_STATE(ifp);
+
+ if (state == NULL)
+ return;
+ ifo = ifp->options;
+ state->state = DHS_REBOOT;
+ state->interval = 0;
+
+ if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: waiting for carrier", ifp->name);
+ return;
+ }
+ if (ifo->options & DHCPCD_STATIC) {
+ dhcp_static(ifp);
+ return;
+ }
+ if (ifo->options & DHCPCD_INFORM) {
+ logger(ifp->ctx, LOG_INFO, "%s: informing address of %s",
+ ifp->name, inet_ntoa(state->lease.addr));
+ dhcp_inform(ifp);
+ return;
+ }
+ if (ifo->reboot == 0 || state->offer == NULL) {
+ dhcp_discover(ifp);
+ return;
+ }
+ if (state->offer->cookie == 0)
+ return;
+
+ logger(ifp->ctx, LOG_INFO, "%s: rebinding lease of %s",
+ ifp->name, inet_ntoa(state->lease.addr));
+ state->xid = dhcp_xid(ifp);
+ state->lease.server.s_addr = 0;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+ /* Need to add this before dhcp_expire and friends. */
+ if (!ifo->fallback && ifo->options & DHCPCD_IPV4LL)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, ipv4ll_start, ifp);
+
+ if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, dhcp_timeout, ifp);
+ else if (!(ifo->options & DHCPCD_INFORM))
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, dhcp_expire, ifp);
+
+ /* Don't bother ARP checking as the server could NAK us first.
+ * Don't call dhcp_request as that would change the state */
+ send_request(ifp);
+}
+
+void
+dhcp_drop(struct interface *ifp, const char *reason)
+{
+ struct dhcp_state *state;
+#ifdef RELEASE_SLOW
+ struct timespec ts;
+#endif
+
+ state = D_STATE(ifp);
+ /* dhcp_start may just have been called and we don't yet have a state
+ * but we do have a timeout, so punt it. */
+ if (state == NULL) {
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ return;
+ }
+
+ if (ifp->options->options & DHCPCD_RELEASE) {
+ /* Failure to send the release may cause this function to
+ * re-enter so guard by setting the state. */
+ if (state->state == DHS_RELEASE)
+ return;
+ state->state = DHS_RELEASE;
+
+ unlink(state->leasefile);
+ if (ifp->carrier != LINK_DOWN &&
+ state->new != NULL &&
+ state->lease.server.s_addr != INADDR_ANY)
+ {
+ logger(ifp->ctx, LOG_INFO, "%s: releasing lease of %s",
+ ifp->name, inet_ntoa(state->lease.addr));
+ state->xid = dhcp_xid(ifp);
+ send_message(ifp, DHCP_RELEASE, NULL);
+#ifdef RELEASE_SLOW
+ /* Give the packet a chance to go */
+ ts.tv_sec = RELEASE_DELAY_S;
+ ts.tv_nsec = RELEASE_DELAY_NS;
+ nanosleep(&ts, NULL);
+#endif
+ }
+ }
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ dhcp_auth_reset(&state->auth);
+ dhcp_close(ifp);
+
+ free(state->offer);
+ state->offer = NULL;
+ free(state->old);
+ state->old = state->new;
+ state->new = NULL;
+ state->reason = reason;
+ ipv4_applyaddr(ifp);
+ free(state->old);
+ state->old = NULL;
+ state->lease.addr.s_addr = 0;
+ ifp->options->options &= ~(DHCPCD_CSR_WARNED |
+ DHCPCD_ROUTER_HOST_ROUTE_WARNED);
+}
+
+static void
+log_dhcp1(int lvl, const char *msg,
+ const struct interface *ifp, const struct dhcp_message *dhcp,
+ const struct in_addr *from, int ad)
+{
+ const char *tfrom;
+ char *a, sname[sizeof(dhcp->servername) * 4];
+ struct in_addr addr;
+ int r;
+
+ if (strcmp(msg, "NAK:") == 0) {
+ a = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE);
+ if (a) {
+ char *tmp;
+ size_t al, tmpl;
+
+ al = strlen(a);
+ tmpl = (al * 4) + 1;
+ tmp = malloc(tmpl);
+ if (tmp == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ free(a);
+ return;
+ }
+ print_string(tmp, tmpl, STRING, (uint8_t *)a, al);
+ free(a);
+ a = tmp;
+ }
+ } else if (ad && dhcp->yiaddr != 0) {
+ addr.s_addr = dhcp->yiaddr;
+ a = strdup(inet_ntoa(addr));
+ if (a == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ } else
+ a = NULL;
+
+ tfrom = "from";
+ r = get_option_addr(ifp->ctx, &addr, dhcp, DHO_SERVERID);
+ if (dhcp->servername[0] && r == 0) {
+ print_string(sname, sizeof(sname), STRING,
+ dhcp->servername, strlen((const char *)dhcp->servername));
+ if (a == NULL)
+ logger(ifp->ctx, lvl, "%s: %s %s %s `%s'",
+ ifp->name, msg, tfrom, inet_ntoa(addr), sname);
+ else
+ logger(ifp->ctx, lvl, "%s: %s %s %s %s `%s'",
+ ifp->name, msg, a, tfrom, inet_ntoa(addr), sname);
+ } else {
+ if (r != 0) {
+ tfrom = "via";
+ addr = *from;
+ }
+ if (a == NULL)
+ logger(ifp->ctx, lvl, "%s: %s %s %s",
+ ifp->name, msg, tfrom, inet_ntoa(addr));
+ else
+ logger(ifp->ctx, lvl, "%s: %s %s %s %s",
+ ifp->name, msg, a, tfrom, inet_ntoa(addr));
+ }
+ free(a);
+}
+
+static void
+log_dhcp(int lvl, const char *msg,
+ const struct interface *ifp, const struct dhcp_message *dhcp,
+ const struct in_addr *from)
+{
+
+ log_dhcp1(lvl, msg, ifp, dhcp, from, 1);
+}
+
+static int
+blacklisted_ip(const struct if_options *ifo, in_addr_t addr)
+{
+ size_t i;
+
+ for (i = 0; i < ifo->blacklist_len; i += 2)
+ if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1]))
+ return 1;
+ return 0;
+}
+
+static int
+whitelisted_ip(const struct if_options *ifo, in_addr_t addr)
+{
+ size_t i;
+
+ if (ifo->whitelist_len == 0)
+ return -1;
+ for (i = 0; i < ifo->whitelist_len; i += 2)
+ if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1]))
+ return 1;
+ return 0;
+}
+
+static void
+dhcp_handledhcp(struct interface *ifp, struct dhcp_message **dhcpp,
+ const struct in_addr *from)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct if_options *ifo = ifp->options;
+ struct dhcp_message *dhcp = *dhcpp;
+ struct dhcp_lease *lease = &state->lease;
+ uint8_t type, tmp;
+ const uint8_t *auth;
+ struct in_addr addr;
+ unsigned int i;
+ size_t auth_len;
+ char *msg;
+#ifdef IN_IFF_DUPLICATED
+ struct ipv4_addr *ia;
+#endif
+
+ /* We may have found a BOOTP server */
+ if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1)
+ type = 0;
+ else if (ifo->options & DHCPCD_BOOTP) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ignoring DHCP reply (excpecting BOOTP)",
+ ifp->name);
+ return;
+ }
+
+ /* Authenticate the message */
+ auth = get_option(ifp->ctx, dhcp, DHO_AUTHENTICATION, &auth_len);
+ if (auth) {
+ if (dhcp_auth_validate(&state->auth, &ifo->auth,
+ (uint8_t *)*dhcpp, sizeof(**dhcpp), 4, type,
+ auth, auth_len) == NULL)
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: dhcp_auth_validate: %m", ifp->name);
+ log_dhcp1(LOG_ERR, "authentication failed",
+ ifp, dhcp, from, 0);
+ return;
+ }
+ if (state->auth.token)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: accepted reconfigure key", ifp->name);
+ } else if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+ log_dhcp1(LOG_ERR, "no authentication",
+ ifp, dhcp, from, 0);
+ return;
+ }
+ log_dhcp1(LOG_WARNING, "no authentication",
+ ifp, dhcp, from, 0);
+ }
+
+ /* RFC 3203 */
+ if (type == DHCP_FORCERENEW) {
+ if (from->s_addr == INADDR_ANY ||
+ from->s_addr == INADDR_BROADCAST)
+ {
+ log_dhcp(LOG_ERR, "discarding Force Renew",
+ ifp, dhcp, from);
+ return;
+ }
+ if (auth == NULL) {
+ log_dhcp(LOG_ERR, "unauthenticated Force Renew",
+ ifp, dhcp, from);
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE)
+ return;
+ }
+ if (state->state != DHS_BOUND && state->state != DHS_INFORM) {
+ log_dhcp(LOG_DEBUG, "not bound, ignoring Force Renew",
+ ifp, dhcp, from);
+ return;
+ }
+ log_dhcp(LOG_ERR, "Force Renew from", ifp, dhcp, from);
+ /* The rebind and expire timings are still the same, we just
+ * enter the renew state early */
+ if (state->state == DHS_BOUND) {
+ eloop_timeout_delete(ifp->ctx->eloop,
+ dhcp_renew, ifp);
+ dhcp_renew(ifp);
+ } else {
+ eloop_timeout_delete(ifp->ctx->eloop,
+ send_inform, ifp);
+ dhcp_inform(ifp);
+ }
+ return;
+ }
+
+ if (state->state == DHS_BOUND) {
+ /* Before we supported FORCERENEW we closed off the raw
+ * port so we effectively ignored all messages.
+ * As such we'll not log by default here. */
+ //log_dhcp(LOG_DEBUG, "bound, ignoring", iface, dhcp, from);
+ return;
+ }
+
+ /* Ensure it's the right transaction */
+ if (state->xid != ntohl(dhcp->xid)) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: wrong xid 0x%x (expecting 0x%x) from %s",
+ ifp->name, ntohl(dhcp->xid), state->xid,
+ inet_ntoa(*from));
+ return;
+ }
+ /* reset the message counter */
+ state->interval = 0;
+
+ /* Ensure that no reject options are present */
+ for (i = 1; i < 255; i++) {
+ if (has_option_mask(ifo->rejectmask, i) &&
+ get_option_uint8(ifp->ctx, &tmp, dhcp, (uint8_t)i) == 0)
+ {
+ log_dhcp(LOG_WARNING, "reject DHCP", ifp, dhcp, from);
+ return;
+ }
+ }
+
+ if (type == DHCP_NAK) {
+ /* For NAK, only check if we require the ServerID */
+ if (has_option_mask(ifo->requiremask, DHO_SERVERID) &&
+ get_option_addr(ifp->ctx, &addr, dhcp, DHO_SERVERID) == -1)
+ {
+ log_dhcp(LOG_WARNING, "reject NAK", ifp, dhcp, from);
+ return;
+ }
+
+ /* We should restart on a NAK */
+ log_dhcp(LOG_WARNING, "NAK:", ifp, dhcp, from);
+ if ((msg = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE))) {
+ logger(ifp->ctx, LOG_WARNING, "%s: message: %s",
+ ifp->name, msg);
+ free(msg);
+ }
+ if (state->state == DHS_INFORM) /* INFORM should not be NAKed */
+ return;
+ if (!(ifp->ctx->options & DHCPCD_TEST)) {
+ dhcp_drop(ifp, "NAK");
+ unlink(state->leasefile);
+ }
+
+ /* If we constantly get NAKS then we should slowly back off */
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ state->nakoff, dhcp_discover, ifp);
+ if (state->nakoff == 0)
+ state->nakoff = 1;
+ else {
+ state->nakoff *= 2;
+ if (state->nakoff > NAKOFF_MAX)
+ state->nakoff = NAKOFF_MAX;
+ }
+ return;
+ }
+
+ /* Ensure that all required options are present */
+ for (i = 1; i < 255; i++) {
+ if (has_option_mask(ifo->requiremask, i) &&
+ get_option_uint8(ifp->ctx, &tmp, dhcp, (uint8_t)i) != 0)
+ {
+ /* If we are BOOTP, then ignore the need for serverid.
+ * To ignore BOOTP, require dhcp_message_type.
+ * However, nothing really stops BOOTP from providing
+ * DHCP style options as well so the above isn't
+ * always true. */
+ if (type == 0 && i == DHO_SERVERID)
+ continue;
+ log_dhcp(LOG_WARNING, "reject DHCP", ifp, dhcp, from);
+ return;
+ }
+ }
+
+ /* DHCP Auto-Configure, RFC 2563 */
+ if (type == DHCP_OFFER && dhcp->yiaddr == 0) {
+ log_dhcp(LOG_WARNING, "no address given", ifp, dhcp, from);
+ if ((msg = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE))) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: message: %s", ifp->name, msg);
+ free(msg);
+ }
+ if (state->state == DHS_DISCOVER &&
+ get_option_uint8(ifp->ctx, &tmp, dhcp,
+ DHO_AUTOCONFIGURE) == 0)
+ {
+ switch (tmp) {
+ case 0:
+ log_dhcp(LOG_WARNING, "IPv4LL disabled from",
+ ifp, dhcp, from);
+ ipv4ll_drop(ifp);
+ arp_close(ifp);
+ break;
+ case 1:
+ log_dhcp(LOG_WARNING, "IPv4LL enabled from",
+ ifp, dhcp, from);
+ ipv4ll_start(ifp);
+ break;
+ default:
+ logger(ifp->ctx, LOG_ERR,
+ "%s: unknown auto configuration option %d",
+ ifp->name, tmp);
+ break;
+ }
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ DHCP_MAX, dhcp_discover, ifp);
+ }
+ return;
+ }
+
+ /* Ensure that the address offered is valid */
+ if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) &&
+ (dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) &&
+ (dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST))
+ {
+ log_dhcp(LOG_WARNING, "reject invalid address",
+ ifp, dhcp, from);
+ return;
+ }
+
+#ifdef IN_IFF_DUPLICATED
+ ia = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ if (ia && ia->addr_flags & IN_IFF_DUPLICATED) {
+ log_dhcp(LOG_WARNING, "declined duplicate address",
+ ifp, dhcp, from);
+ if (type)
+ dhcp_decline(ifp);
+ ipv4_deladdr(ifp, &ia->addr, &ia->net, 0);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ DHCP_RAND_MAX, dhcp_discover, ifp);
+ return;
+ }
+#endif
+
+ if ((type == 0 || type == DHCP_OFFER) && state->state == DHS_DISCOVER) {
+ lease->frominfo = 0;
+ lease->addr.s_addr = dhcp->yiaddr;
+ lease->cookie = dhcp->cookie;
+ if (type == 0 ||
+ get_option_addr(ifp->ctx,
+ &lease->server, dhcp, DHO_SERVERID) != 0)
+ lease->server.s_addr = INADDR_ANY;
+ log_dhcp(LOG_INFO, "offered", ifp, dhcp, from);
+ free(state->offer);
+ state->offer = dhcp;
+ *dhcpp = NULL;
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ free(state->old);
+ state->old = state->new;
+ state->new = state->offer;
+ state->offer = NULL;
+ state->reason = "TEST";
+ script_runreason(ifp, state->reason);
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+ eloop_timeout_delete(ifp->ctx->eloop, send_discover, ifp);
+ /* We don't request BOOTP addresses */
+ if (type) {
+ /* We used to ARP check here, but that seems to be in
+ * violation of RFC2131 where it only describes
+ * DECLINE after REQUEST.
+ * It also seems that some MS DHCP servers actually
+ * ignore DECLINE if no REQUEST, ie we decline a
+ * DISCOVER. */
+ dhcp_request(ifp);
+ return;
+ }
+ }
+
+ if (type) {
+ if (type == DHCP_OFFER) {
+ log_dhcp(LOG_WARNING, "ignoring offer of",
+ ifp, dhcp, from);
+ return;
+ }
+
+ /* We should only be dealing with acks */
+ if (type != DHCP_ACK) {
+ log_dhcp(LOG_ERR, "not ACK or OFFER",
+ ifp, dhcp, from);
+ return;
+ }
+
+ if (!(ifo->options & DHCPCD_INFORM))
+ log_dhcp(LOG_DEBUG, "acknowledged", ifp, dhcp, from);
+ else
+ ifo->options &= ~DHCPCD_STATIC;
+ }
+
+ /* No NAK, so reset the backoff
+ * We don't reset on an OFFER message because the server could
+ * potentially NAK the REQUEST. */
+ state->nakoff = 0;
+
+ /* BOOTP could have already assigned this above, so check we still
+ * have a pointer. */
+ if (*dhcpp) {
+ free(state->offer);
+ state->offer = dhcp;
+ *dhcpp = NULL;
+ }
+
+ lease->frominfo = 0;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+ dhcp_arp_bind(ifp);
+}
+
+static size_t
+get_udp_data(const uint8_t **data, const uint8_t *udp)
+{
+ struct udp_dhcp_packet p;
+
+ memcpy(&p, udp, sizeof(p));
+ *data = udp + offsetof(struct udp_dhcp_packet, dhcp);
+ return ntohs(p.ip.ip_len) - sizeof(p.ip) - sizeof(p.udp);
+}
+
+static int
+valid_udp_packet(const uint8_t *data, size_t data_len, struct in_addr *from,
+ int noudpcsum)
+{
+ struct udp_dhcp_packet p;
+ uint16_t bytes, udpsum;
+
+ if (data_len < sizeof(p.ip)) {
+ if (from)
+ from->s_addr = INADDR_ANY;
+ errno = EINVAL;
+ return -1;
+ }
+ memcpy(&p, data, MIN(data_len, sizeof(p)));
+ if (from)
+ from->s_addr = p.ip.ip_src.s_addr;
+ if (data_len > sizeof(p)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (checksum(&p.ip, sizeof(p.ip)) != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ bytes = ntohs(p.ip.ip_len);
+ if (data_len < bytes) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (noudpcsum == 0) {
+ udpsum = p.udp.uh_sum;
+ p.udp.uh_sum = 0;
+ p.ip.ip_hl = 0;
+ p.ip.ip_v = 0;
+ p.ip.ip_tos = 0;
+ p.ip.ip_len = p.udp.uh_ulen;
+ p.ip.ip_id = 0;
+ p.ip.ip_off = 0;
+ p.ip.ip_ttl = 0;
+ p.ip.ip_sum = 0;
+ if (udpsum && checksum(&p, bytes) != udpsum) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+dhcp_handlepacket(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_message *dhcp = NULL;
+ const uint8_t *pp;
+ size_t bytes;
+ struct in_addr from;
+ int i, flags;
+ const struct dhcp_state *state = D_CSTATE(ifp);
+
+ /* Need this API due to BPF */
+ flags = 0;
+ while (!(flags & RAW_EOF)) {
+ bytes = (size_t)if_readrawpacket(ifp, ETHERTYPE_IP,
+ ifp->ctx->packet, udp_dhcp_len, &flags);
+ if ((ssize_t)bytes == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp if_readrawpacket: %m", ifp->name);
+ dhcp_close(ifp);
+ arp_close(ifp);
+ break;
+ }
+ if (valid_udp_packet(ifp->ctx->packet, bytes,
+ &from, flags & RAW_PARTIALCSUM) == -1)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: invalid UDP packet from %s",
+ ifp->name, inet_ntoa(from));
+ continue;
+ }
+ i = whitelisted_ip(ifp->options, from.s_addr);
+ if (i == 0) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: non whitelisted DHCP packet from %s",
+ ifp->name, inet_ntoa(from));
+ continue;
+ } else if (i != 1 &&
+ blacklisted_ip(ifp->options, from.s_addr) == 1)
+ {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: blacklisted DHCP packet from %s",
+ ifp->name, inet_ntoa(from));
+ continue;
+ }
+ if (ifp->flags & IFF_POINTOPOINT &&
+ state->dst.s_addr != from.s_addr)
+ {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: server %s is not destination",
+ ifp->name, inet_ntoa(from));
+ }
+ bytes = get_udp_data(&pp, ifp->ctx->packet);
+ if (bytes > sizeof(*dhcp)) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: packet greater than DHCP size from %s",
+ ifp->name, inet_ntoa(from));
+ continue;
+ }
+ if (dhcp == NULL) {
+ dhcp = calloc(1, sizeof(*dhcp));
+ if (dhcp == NULL) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: calloc: %m", __func__);
+ break;
+ }
+ }
+ memcpy(dhcp, pp, bytes);
+ if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
+ logger(ifp->ctx, LOG_DEBUG, "%s: bogus cookie from %s",
+ ifp->name, inet_ntoa(from));
+ continue;
+ }
+ /* Ensure packet is for us */
+ if (ifp->hwlen <= sizeof(dhcp->chaddr) &&
+ memcmp(dhcp->chaddr, ifp->hwaddr, ifp->hwlen))
+ {
+ char buf[sizeof(dhcp->chaddr) * 3];
+
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: xid 0x%x is for hwaddr %s",
+ ifp->name, ntohl(dhcp->xid),
+ hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr),
+ buf, sizeof(buf)));
+ continue;
+ }
+ dhcp_handledhcp(ifp, &dhcp, &from);
+ if (state->raw_fd == -1)
+ break;
+ }
+ free(dhcp);
+}
+
+static void
+dhcp_handleudp(void *arg)
+{
+ struct dhcpcd_ctx *ctx;
+ uint8_t buffer[sizeof(struct dhcp_message)];
+
+ ctx = arg;
+
+ /* Just read what's in the UDP fd and discard it as we always read
+ * from the raw fd */
+ if (read(ctx->udp_fd, buffer, sizeof(buffer)) == -1) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ eloop_event_delete(ctx->eloop, ctx->udp_fd);
+ close(ctx->udp_fd);
+ ctx->udp_fd = -1;
+ }
+}
+
+static int
+dhcp_open(struct interface *ifp)
+{
+ struct dhcp_state *state;
+
+ if (ifp->ctx->packet == NULL) {
+ ifp->ctx->packet = malloc(udp_dhcp_len);
+ if (ifp->ctx->packet == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ }
+
+ state = D_STATE(ifp);
+ if (state->raw_fd == -1) {
+ state->raw_fd = if_openrawsocket(ifp, ETHERTYPE_IP);
+ if (state->raw_fd == -1) {
+ if (errno == ENOENT) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s not found", if_pfname);
+ /* May as well disable IPv4 entirely at
+ * this point as we really need it. */
+ ifp->options->options &= ~DHCPCD_IPV4;
+ } else
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m",
+ __func__, ifp->name);
+ return -1;
+ }
+ eloop_event_add(ifp->ctx->eloop,
+ state->raw_fd, dhcp_handlepacket, ifp, NULL, NULL);
+ }
+ return 0;
+}
+
+int
+dhcp_dump(struct interface *ifp)
+{
+ struct dhcp_state *state;
+
+ ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state));
+ if (state == NULL)
+ goto eexit;
+ state->raw_fd = -1;
+ dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
+ AF_INET, ifp);
+ state->new = read_lease(ifp);
+ if (state->new == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m",
+ *ifp->name ? ifp->name : state->leasefile, __func__);
+ return -1;
+ }
+ state->reason = "DUMP";
+ return script_runreason(ifp, state->reason);
+
+eexit:
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+}
+
+void
+dhcp_free(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct dhcpcd_ctx *ctx;
+
+ dhcp_close(ifp);
+ arp_close(ifp);
+ if (state) {
+ free(state->old);
+ free(state->new);
+ free(state->offer);
+ free(state->clientid);
+ free(state);
+ ifp->if_data[IF_DATA_DHCP] = NULL;
+ }
+
+ ctx = ifp->ctx;
+ /* If we don't have any more DHCP enabled interfaces,
+ * close the global socket and release resources */
+ if (ctx->ifaces) {
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (D_STATE(ifp))
+ break;
+ }
+ }
+ if (ifp == NULL) {
+ if (ctx->udp_fd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->udp_fd);
+ close(ctx->udp_fd);
+ ctx->udp_fd = -1;
+ }
+
+ free(ctx->packet);
+ free(ctx->opt_buffer);
+ ctx->packet = NULL;
+ ctx->opt_buffer = NULL;
+ }
+}
+
+static int
+dhcp_init(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ const struct if_options *ifo;
+ uint8_t len;
+ char buf[(sizeof(ifo->clientid) - 1) * 3];
+
+ state = D_STATE(ifp);
+ if (state == NULL) {
+ ifp->if_data[IF_DATA_DHCP] = calloc(1, sizeof(*state));
+ state = D_STATE(ifp);
+ if (state == NULL)
+ return -1;
+ /* 0 is a valid fd, so init to -1 */
+ state->raw_fd = -1;
+
+ /* Now is a good time to find IPv4 routes */
+ if_initrt(ifp);
+ }
+
+ state->state = DHS_INIT;
+ state->reason = "PREINIT";
+ state->nakoff = 0;
+ dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
+ AF_INET, ifp);
+
+ ifo = ifp->options;
+ /* We need to drop the leasefile so that dhcp_start
+ * doesn't load it. */
+ if (ifo->options & DHCPCD_REQUEST)
+ unlink(state->leasefile);
+
+ free(state->clientid);
+ state->clientid = NULL;
+
+ if (*ifo->clientid) {
+ state->clientid = malloc((size_t)(ifo->clientid[0] + 1));
+ if (state->clientid == NULL)
+ goto eexit;
+ memcpy(state->clientid, ifo->clientid,
+ (size_t)(ifo->clientid[0]) + 1);
+ } else if (ifo->options & DHCPCD_CLIENTID) {
+ if (ifo->options & DHCPCD_DUID) {
+ state->clientid = malloc(ifp->ctx->duid_len + 6);
+ if (state->clientid == NULL)
+ goto eexit;
+ state->clientid[0] =(uint8_t)(ifp->ctx->duid_len + 5);
+ state->clientid[1] = 255; /* RFC 4361 */
+ memcpy(state->clientid + 2, ifo->iaid, 4);
+ memcpy(state->clientid + 6, ifp->ctx->duid,
+ ifp->ctx->duid_len);
+ } else {
+ len = (uint8_t)(ifp->hwlen + 1);
+ state->clientid = malloc((size_t)len + 1);
+ if (state->clientid == NULL)
+ goto eexit;
+ state->clientid[0] = len;
+ state->clientid[1] = (uint8_t)ifp->family;
+ memcpy(state->clientid + 2, ifp->hwaddr,
+ ifp->hwlen);
+ }
+ }
+
+ if (ifo->options & DHCPCD_DUID)
+ /* Don't bother logging as DUID and IAID are reported
+ * at device start. */
+ return 0;
+
+ if (ifo->options & DHCPCD_CLIENTID)
+ logger(ifp->ctx, LOG_DEBUG, "%s: using ClientID %s", ifp->name,
+ hwaddr_ntoa(state->clientid + 1, state->clientid[0],
+ buf, sizeof(buf)));
+ else if (ifp->hwlen)
+ logger(ifp->ctx, LOG_DEBUG, "%s: using hwaddr %s", ifp->name,
+ hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf)));
+ return 0;
+
+eexit:
+ logger(ifp->ctx, LOG_ERR, "%s: error making ClientID: %m", __func__);
+ return -1;
+}
+
+static void
+dhcp_start1(void *arg)
+{
+ struct interface *ifp = arg;
+ struct if_options *ifo = ifp->options;
+ struct dhcp_state *state;
+ struct stat st;
+ uint32_t l;
+ int nolease;
+
+ if (!(ifo->options & DHCPCD_IPV4))
+ return;
+
+ /* Listen on *.*.*.*:bootpc so that the kernel never sends an
+ * ICMP port unreachable message back to the DHCP server */
+ if (ifp->ctx->udp_fd == -1) {
+ ifp->ctx->udp_fd = dhcp_openudp(NULL);
+ if (ifp->ctx->udp_fd == -1) {
+ /* Don't log an error if some other process
+ * is handling this. */
+ if (errno != EADDRINUSE)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp_openudp: %m", __func__);
+ } else
+ eloop_event_add(ifp->ctx->eloop,
+ ifp->ctx->udp_fd, dhcp_handleudp,
+ ifp->ctx, NULL, NULL);
+ }
+
+ if (dhcp_init(ifp) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: dhcp_init: %m", ifp->name);
+ return;
+ }
+
+ state = D_STATE(ifp);
+ clock_gettime(CLOCK_MONOTONIC, &state->started);
+ free(state->offer);
+ state->offer = NULL;
+
+ if (state->arping_index < ifo->arping_len) {
+ struct arp_state *astate;
+
+ astate = arp_new(ifp, NULL);
+ if (astate) {
+ astate->probed_cb = dhcp_arp_probed;
+ astate->conflicted_cb = dhcp_arp_conflicted;
+ dhcp_arp_probed(astate);
+ }
+ return;
+ }
+
+ if (ifo->options & DHCPCD_STATIC) {
+ dhcp_static(ifp);
+ return;
+ }
+
+ if (ifo->options & DHCPCD_DHCP && dhcp_open(ifp) == -1)
+ return;
+
+ if (ifo->options & DHCPCD_INFORM) {
+ dhcp_inform(ifp);
+ return;
+ }
+ if (ifp->hwlen == 0 && ifo->clientid[0] == '\0') {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: needs a clientid to configure", ifp->name);
+ dhcp_drop(ifp, "FAIL");
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ return;
+ }
+ /* We don't want to read the old lease if we NAK an old test */
+ nolease = state->offer && ifp->ctx->options & DHCPCD_TEST;
+ if (!nolease && ifo->options & DHCPCD_DHCP) {
+ state->offer = read_lease(ifp);
+ /* Check the saved lease matches the type we want */
+ if (state->offer) {
+#ifdef IN_IFF_DUPLICATED
+ struct in_addr addr;
+ struct ipv4_addr *ia;
+
+ addr.s_addr = state->offer->yiaddr;
+ ia = ipv4_iffindaddr(ifp, &addr, NULL);
+#endif
+
+ if ((IS_BOOTP(ifp, state->offer) &&
+ !(ifo->options & DHCPCD_BOOTP)) ||
+#ifdef IN_IFF_DUPLICATED
+ (ia && ia->addr_flags & IN_IFF_DUPLICATED) ||
+#endif
+ (!IS_BOOTP(ifp, state->offer) &&
+ ifo->options & DHCPCD_BOOTP))
+ {
+ free(state->offer);
+ state->offer = NULL;
+ }
+ }
+ }
+ if (state->offer) {
+ get_lease(ifp->ctx, &state->lease, state->offer);
+ state->lease.frominfo = 1;
+ if (state->new == NULL &&
+ ipv4_iffindaddr(ifp, &state->lease.addr, &state->lease.net))
+ {
+ /* We still have the IP address from the last lease.
+ * Fake add the address and routes from it so the lease
+ * can be cleaned up. */
+ state->new = malloc(sizeof(*state->new));
+ if (state->new) {
+ memcpy(state->new, state->offer,
+ sizeof(*state->new));
+ state->addr = state->lease.addr;
+ state->net = state->lease.net;
+ state->added |= STATE_ADDED | STATE_FAKE;
+ ipv4_buildroutes(ifp->ctx);
+ } else
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ }
+ if (state->offer->cookie == 0) {
+ if (state->offer->yiaddr == state->addr.s_addr) {
+ free(state->offer);
+ state->offer = NULL;
+ }
+ } else if (state->lease.leasetime != ~0U &&
+ stat(state->leasefile, &st) == 0)
+ {
+ time_t now;
+
+ /* Offset lease times and check expiry */
+ now = time(NULL);
+ if (now == -1 ||
+ (time_t)state->lease.leasetime < now - st.st_mtime)
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: discarding expired lease", ifp->name);
+ free(state->offer);
+ state->offer = NULL;
+ state->lease.addr.s_addr = 0;
+ /* Technically we should discard the lease
+ * as it's expired, just as DHCPv6 addresses
+ * would be by the kernel.
+ * However, this may violate POLA so
+ * we currently leave it be.
+ * If we get a totally different lease from
+ * the DHCP server we'll drop it anyway, as
+ * we will on any other event which would
+ * trigger a lease drop.
+ * This should only happen if dhcpcd stops
+ * running and the lease expires before
+ * dhcpcd starts again. */
+#if 0
+ if (state->new)
+ dhcp_drop(ifp, "EXPIRE");
+#endif
+ } else {
+ l = (uint32_t)(now - st.st_mtime);
+ state->lease.leasetime -= l;
+ state->lease.renewaltime -= l;
+ state->lease.rebindtime -= l;
+ }
+ }
+ }
+
+ if (!(ifo->options & DHCPCD_DHCP)) {
+ if (ifo->options & DHCPCD_IPV4LL)
+ ipv4ll_start(ifp);
+ return;
+ }
+
+ if (state->offer == NULL || state->offer->cookie == 0)
+ dhcp_discover(ifp);
+ else
+ dhcp_reboot(ifp);
+}
+
+void
+dhcp_start(struct interface *ifp)
+{
+ struct timespec tv;
+
+ if (!(ifp->options->options & DHCPCD_IPV4))
+ return;
+
+ /* No point in delaying a static configuration */
+ if (ifp->options->options & DHCPCD_STATIC ||
+ !(ifp->options->options & DHCPCD_INITIAL_DELAY))
+ {
+ dhcp_start1(ifp);
+ return;
+ }
+
+ tv.tv_sec = DHCP_MIN_DELAY;
+ tv.tv_nsec = (suseconds_t)arc4random_uniform(
+ (DHCP_MAX_DELAY - DHCP_MIN_DELAY) * NSEC_PER_SEC);
+ timespecnorm(&tv);
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: delaying IPv4 for %0.1f seconds",
+ ifp->name, timespec_to_double(&tv));
+
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcp_start1, ifp);
+}
+
+void
+dhcp_abort(struct interface *ifp)
+{
+
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp_start1, ifp);
+}
+
+void
+dhcp_handleifa(int cmd, struct interface *ifp,
+ const struct in_addr *addr,
+ const struct in_addr *net,
+ const struct in_addr *dst,
+ __unused int flags)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+ uint8_t i;
+
+ state = D_STATE(ifp);
+ if (state == NULL)
+ return;
+
+ if (cmd == RTM_DELADDR) {
+ if (state->addr.s_addr == addr->s_addr &&
+ state->net.s_addr == net->s_addr)
+ {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: removing IP address %s/%d",
+ ifp->name, inet_ntoa(state->addr),
+ inet_ntocidr(state->net));
+ dhcp_drop(ifp, "EXPIRE");
+ }
+ return;
+ }
+
+ if (cmd != RTM_NEWADDR)
+ return;
+
+ ifo = ifp->options;
+ if (ifo->options & DHCPCD_INFORM) {
+ if (state->state != DHS_INFORM)
+ dhcp_inform(ifp);
+ return;
+ }
+
+ if (!(ifo->options & DHCPCD_STATIC))
+ return;
+ if (ifo->req_addr.s_addr != INADDR_ANY)
+ return;
+
+ free(state->old);
+ state->old = state->new;
+ state->new = dhcp_message_new(addr, net);
+ if (state->new == NULL)
+ return;
+ state->dst.s_addr = dst ? dst->s_addr : INADDR_ANY;
+ if (dst) {
+ for (i = 1; i < 255; i++)
+ if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i))
+ dhcp_message_add_addr(state->new, i, *dst);
+ }
+ state->reason = "STATIC";
+ ipv4_buildroutes(ifp->ctx);
+ script_runreason(ifp, state->reason);
+ if (ifo->options & DHCPCD_INFORM) {
+ state->state = DHS_INFORM;
+ state->xid = dhcp_xid(ifp);
+ state->lease.server.s_addr = dst ? dst->s_addr : INADDR_ANY;
+ state->addr = *addr;
+ state->net = *net;
+ dhcp_inform(ifp);
+ }
+}
--- /dev/null
+/* $NetBSD: dhcp.h,v 1.11 2015/08/21 10:39:00 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <limits.h>
+#include <stdint.h>
+
+#include "arp.h"
+#include "auth.h"
+#include "dhcp-common.h"
+
+/* UDP port numbers for DHCP */
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+#define MAGIC_COOKIE 0x63825363
+#define BROADCAST_FLAG 0x8000
+
+/* DHCP message OP code */
+#define DHCP_BOOTREQUEST 1
+#define DHCP_BOOTREPLY 2
+
+/* DHCP message type */
+#define DHCP_DISCOVER 1
+#define DHCP_OFFER 2
+#define DHCP_REQUEST 3
+#define DHCP_DECLINE 4
+#define DHCP_ACK 5
+#define DHCP_NAK 6
+#define DHCP_RELEASE 7
+#define DHCP_INFORM 8
+#define DHCP_FORCERENEW 9
+
+/* Constants taken from RFC 2131. */
+#define T1 0.5
+#define T2 0.875
+#define DHCP_BASE 4
+#define DHCP_MAX 64
+#define DHCP_RAND_MIN -1
+#define DHCP_RAND_MAX 1
+
+#ifdef RFC2131_STRICT
+/* Be strictly conformant for section 4.1.1 */
+# define DHCP_MIN_DELAY 1
+# define DHCP_MAX_DELAY 10
+#else
+/* or mirror the more modern IPv6RS and DHCPv6 delays */
+# define DHCP_MIN_DELAY 0
+# define DHCP_MAX_DELAY 1
+#endif
+
+/* DHCP options */
+enum DHO {
+ DHO_PAD = 0,
+ DHO_SUBNETMASK = 1,
+ DHO_ROUTER = 3,
+ DHO_DNSSERVER = 6,
+ DHO_HOSTNAME = 12,
+ DHO_DNSDOMAIN = 15,
+ DHO_MTU = 26,
+ DHO_BROADCAST = 28,
+ DHO_STATICROUTE = 33,
+ DHO_NISDOMAIN = 40,
+ DHO_NISSERVER = 41,
+ DHO_NTPSERVER = 42,
+ DHO_VENDOR = 43,
+ DHO_IPADDRESS = 50,
+ DHO_LEASETIME = 51,
+ DHO_OPTIONSOVERLOADED = 52,
+ DHO_MESSAGETYPE = 53,
+ DHO_SERVERID = 54,
+ DHO_PARAMETERREQUESTLIST = 55,
+ DHO_MESSAGE = 56,
+ DHO_MAXMESSAGESIZE = 57,
+ DHO_RENEWALTIME = 58,
+ DHO_REBINDTIME = 59,
+ DHO_VENDORCLASSID = 60,
+ DHO_CLIENTID = 61,
+ DHO_USERCLASS = 77, /* RFC 3004 */
+ DHO_RAPIDCOMMIT = 80, /* RFC 4039 */
+ DHO_FQDN = 81,
+ DHO_AUTHENTICATION = 90, /* RFC 3118 */
+ DHO_AUTOCONFIGURE = 116, /* RFC 2563 */
+ DHO_DNSSEARCH = 119, /* RFC 3397 */
+ DHO_CSR = 121, /* RFC 3442 */
+ DHO_VIVCO = 124, /* RFC 3925 */
+ DHO_VIVSO = 125, /* RFC 3925 */
+ DHO_FORCERENEW_NONCE = 145, /* RFC 6704 */
+ DHO_SIXRD = 212, /* RFC 5969 */
+ DHO_MSCSR = 249, /* MS code for RFC 3442 */
+ DHO_END = 255
+};
+
+/* FQDN values - lsnybble used in flags
+ * hsnybble to create order
+ * and to allow 0x00 to mean disable
+ */
+enum FQDN {
+ FQDN_DISABLE = 0x00,
+ FQDN_NONE = 0x18,
+ FQDN_PTR = 0x20,
+ FQDN_BOTH = 0x31
+};
+
+/* Sizes for DHCP options */
+#define DHCP_CHADDR_LEN 16
+#define SERVERNAME_LEN 64
+#define BOOTFILE_LEN 128
+#define DHCP_UDP_LEN (14 + 20 + 8)
+#define DHCP_FIXED_LEN (DHCP_UDP_LEN + 226)
+#define DHCP_OPTION_LEN (MTU_MAX - DHCP_FIXED_LEN)
+
+/* Some crappy DHCP servers require the BOOTP minimum length */
+#define BOOTP_MESSAGE_LENTH_MIN 300
+
+/* Don't import common.h as that defines __unused which causes problems
+ * on some Linux systems which define it as part of a structure */
+#if __GNUC__ > 2 || defined(__INTEL_COMPILER)
+# ifndef __packed
+# define __packed __attribute__((__packed__))
+# endif
+#else
+# ifndef __packed
+# define __packed
+# endif
+#endif
+
+struct dhcp_message {
+ uint8_t op; /* message type */
+ uint8_t hwtype; /* hardware address type */
+ uint8_t hwlen; /* hardware address length */
+ uint8_t hwopcount; /* should be zero in client message */
+ uint32_t xid; /* transaction id */
+ uint16_t secs; /* elapsed time in sec. from boot */
+ uint16_t flags;
+ uint32_t ciaddr; /* (previously allocated) client IP */
+ uint32_t yiaddr; /* 'your' client IP address */
+ uint32_t siaddr; /* should be zero in client's messages */
+ uint32_t giaddr; /* should be zero in client's messages */
+ uint8_t chaddr[DHCP_CHADDR_LEN]; /* client's hardware address */
+ uint8_t servername[SERVERNAME_LEN]; /* server host name */
+ uint8_t bootfile[BOOTFILE_LEN]; /* boot file name */
+ uint32_t cookie;
+ uint8_t options[DHCP_OPTION_LEN]; /* message options - cookie */
+} __packed;
+
+struct dhcp_lease {
+ struct in_addr addr;
+ struct in_addr net;
+ struct in_addr brd;
+ uint32_t leasetime;
+ uint32_t renewaltime;
+ uint32_t rebindtime;
+ struct in_addr server;
+ uint8_t frominfo;
+ uint32_t cookie;
+};
+
+enum DHS {
+ DHS_INIT,
+ DHS_DISCOVER,
+ DHS_REQUEST,
+ DHS_BOUND,
+ DHS_RENEW,
+ DHS_REBIND,
+ DHS_REBOOT,
+ DHS_INFORM,
+ DHS_RENEW_REQUESTED,
+ DHS_RELEASE
+};
+
+struct dhcp_state {
+ enum DHS state;
+ struct dhcp_message *sent;
+ struct dhcp_message *offer;
+ struct dhcp_message *new;
+ struct dhcp_message *old;
+ struct dhcp_lease lease;
+ const char *reason;
+ time_t interval;
+ time_t nakoff;
+ uint32_t xid;
+ int socket;
+
+ int raw_fd;
+ struct in_addr addr;
+ struct in_addr net;
+ struct in_addr dst;
+ uint8_t added;
+
+ char leasefile[sizeof(LEASEFILE) + IF_NAMESIZE + (IF_SSIDSIZE * 4)];
+ struct timespec started;
+ unsigned char *clientid;
+ struct authstate auth;
+ size_t arping_index;
+};
+
+#define D_STATE(ifp) \
+ ((struct dhcp_state *)(ifp)->if_data[IF_DATA_DHCP])
+#define D_CSTATE(ifp) \
+ ((const struct dhcp_state *)(ifp)->if_data[IF_DATA_DHCP])
+#define D_STATE_RUNNING(ifp) \
+ (D_CSTATE((ifp)) && D_CSTATE((ifp))->new && D_CSTATE((ifp))->reason)
+
+#include "dhcpcd.h"
+#include "if-options.h"
+
+#ifdef INET
+char *decode_rfc3361(const uint8_t *, size_t);
+ssize_t decode_rfc3442(char *, size_t, const uint8_t *p, size_t);
+ssize_t decode_rfc5969(char *, size_t, const uint8_t *p, size_t);
+
+void dhcp_printoptions(const struct dhcpcd_ctx *,
+ const struct dhcp_opt *, size_t);
+int get_option_addr(struct dhcpcd_ctx *,struct in_addr *,
+ const struct dhcp_message *, uint8_t);
+#define IS_BOOTP(i, m) ((m) != NULL && \
+ get_option_uint8((i)->ctx, NULL, (m), DHO_MESSAGETYPE) == -1)
+uint16_t dhcp_get_mtu(const struct interface *);
+struct rt_head *dhcp_get_routes(struct interface *);
+ssize_t dhcp_env(char **, const char *, const struct dhcp_message *,
+ const struct interface *);
+
+uint32_t dhcp_xid(const struct interface *);
+struct dhcp_message *dhcp_message_new(const struct in_addr *addr,
+ const struct in_addr *mask);
+int dhcp_message_add_addr(struct dhcp_message *, uint8_t, struct in_addr);
+ssize_t make_message(struct dhcp_message **, const struct interface *,
+ uint8_t);
+int valid_dhcp_packet(unsigned char *);
+
+void dhcp_handleifa(int, struct interface *,
+ const struct in_addr *, const struct in_addr *, const struct in_addr *,
+ int);
+
+void dhcp_drop(struct interface *, const char *);
+void dhcp_start(struct interface *);
+void dhcp_abort(struct interface *);
+void dhcp_discover(void *);
+void dhcp_inform(struct interface *);
+void dhcp_bind(struct interface *);
+void dhcp_reboot_newopts(struct interface *, unsigned long long);
+void dhcp_close(struct interface *);
+void dhcp_free(struct interface *);
+int dhcp_dump(struct interface *);
+#else
+#define dhcp_drop(a, b) {}
+#define dhcp_start(a) {}
+#define dhcp_abort(a) {}
+#define dhcp_reboot(a, b) (b = b)
+#define dhcp_reboot_newopts(a, b) (b = b)
+#define dhcp_close(a) {}
+#define dhcp_free(a) {}
+#define dhcp_dump(a) (-1)
+#endif
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: dhcp6.c,v 1.15 2015/08/21 10:39:00 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+/* TODO: We should decline dupliate addresses detected */
+
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define ELOOP_QUEUE 4
+#include "config.h"
+#include "common.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "duid.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv6nd.h"
+#include "script.h"
+
+#ifndef __UNCONST
+#define __UNCONST(a) ((void *)(unsigned long)(const void *)(a))
+#endif
+
+/* DHCPCD Project has been assigned an IANA PEN of 40712 */
+#define DHCPCD_IANA_PEN 40712
+
+/* Unsure if I want this */
+//#define VENDOR_SPLIT
+
+/* Support older systems with different defines */
+#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)
+#define IPV6_RECVPKTINFO IPV6_PKTINFO
+#endif
+
+struct dhcp6_op {
+ uint16_t type;
+ const char *name;
+};
+
+static const struct dhcp6_op dhcp6_ops[] = {
+ { DHCP6_SOLICIT, "SOLICIT6" },
+ { DHCP6_ADVERTISE, "ADVERTISE6" },
+ { DHCP6_REQUEST, "REQUEST6" },
+ { DHCP6_REPLY, "REPLY6" },
+ { DHCP6_RENEW, "RENEW6" },
+ { DHCP6_REBIND, "REBIND6" },
+ { DHCP6_CONFIRM, "CONFIRM6" },
+ { DHCP6_INFORMATION_REQ, "INFORM6" },
+ { DHCP6_RELEASE, "RELEASE6" },
+ { DHCP6_RECONFIGURE, "RECONFIURE6" },
+ { 0, NULL }
+};
+
+struct dhcp_compat {
+ uint8_t dhcp_opt;
+ uint16_t dhcp6_opt;
+};
+
+const struct dhcp_compat dhcp_compats[] = {
+ { DHO_DNSSERVER, D6_OPTION_DNS_SERVERS },
+ { DHO_HOSTNAME, D6_OPTION_FQDN },
+ { DHO_DNSDOMAIN, D6_OPTION_FQDN },
+ { DHO_NISSERVER, D6_OPTION_NIS_SERVERS },
+ { DHO_NTPSERVER, D6_OPTION_SNTP_SERVERS },
+ { DHO_RAPIDCOMMIT, D6_OPTION_RAPID_COMMIT },
+ { DHO_FQDN, D6_OPTION_FQDN },
+ { DHO_VIVCO, D6_OPTION_VENDOR_CLASS },
+ { DHO_VIVSO, D6_OPTION_VENDOR_OPTS },
+ { DHO_DNSSEARCH, D6_OPTION_DOMAIN_LIST },
+ { 0, 0 }
+};
+
+static const char * const dhcp6_statuses[] = {
+ "Success",
+ "Unspecified Failure",
+ "No Addresses Available",
+ "No Binding",
+ "Not On Link",
+ "Use Multicast"
+};
+
+struct dhcp6_ia_addr {
+ struct in6_addr addr;
+ uint32_t pltime;
+ uint32_t vltime;
+} __packed;
+
+struct dhcp6_pd_addr {
+ uint32_t pltime;
+ uint32_t vltime;
+ uint8_t prefix_len;
+ struct in6_addr prefix;
+} __packed;
+
+void
+dhcp6_printoptions(const struct dhcpcd_ctx *ctx,
+ const struct dhcp_opt *opts, size_t opts_len)
+{
+ size_t i, j;
+ const struct dhcp_opt *opt, *opt2;
+ int cols;
+
+ for (i = 0, opt = ctx->dhcp6_opts;
+ i < ctx->dhcp6_opts_len; i++, opt++)
+ {
+ for (j = 0, opt2 = opts; j < opts_len; j++, opt2++)
+ if (opt2->option == opt->option)
+ break;
+ if (j == opts_len) {
+ cols = printf("%05d %s", opt->option, opt->var);
+ dhcp_print_option_encoding(opt, cols);
+ }
+ }
+ for (i = 0, opt = opts; i < opts_len; i++, opt++) {
+ cols = printf("%05d %s", opt->option, opt->var);
+ dhcp_print_option_encoding(opt, cols);
+ }
+}
+
+static size_t
+dhcp6_makevendor(struct dhcp6_option *o, const struct interface *ifp)
+{
+ const struct if_options *ifo;
+ size_t len, i;
+ uint8_t *p;
+ uint16_t u16;
+ uint32_t u32;
+ ssize_t vlen;
+ const struct vivco *vivco;
+ char vendor[VENDORCLASSID_MAX_LEN];
+
+ ifo = ifp->options;
+ len = sizeof(uint32_t); /* IANA PEN */
+ if (ifo->vivco_en) {
+ for (i = 0, vivco = ifo->vivco;
+ i < ifo->vivco_len;
+ i++, vivco++)
+ len += sizeof(uint16_t) + vivco->len;
+ vlen = 0; /* silence bogus gcc warning */
+ } else {
+ vlen = dhcp_vendor(vendor, sizeof(vendor));
+ if (vlen == -1)
+ vlen = 0;
+ else
+ len += sizeof(uint16_t) + (size_t)vlen;
+ }
+
+ if (len > UINT16_MAX) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: DHCPv6 Vendor Class too big", ifp->name);
+ return 0;
+ }
+
+ if (o) {
+ o->code = htons(D6_OPTION_VENDOR_CLASS);
+ o->len = htons((uint16_t)len);
+ p = D6_OPTION_DATA(o);
+ u32 = htonl(ifo->vivco_en ? ifo->vivco_en : DHCPCD_IANA_PEN);
+ memcpy(p, &u32, sizeof(u32));
+ p += sizeof(u32);
+ if (ifo->vivco_en) {
+ for (i = 0, vivco = ifo->vivco;
+ i < ifo->vivco_len;
+ i++, vivco++)
+ {
+ u16 = htons((uint16_t)vivco->len);
+ memcpy(p, &u16, sizeof(u16));
+ p += sizeof(u16);
+ memcpy(p, vivco->data, vivco->len);
+ p += vivco->len;
+ }
+ } else if (vlen) {
+ u16 = htons((uint16_t)vlen);
+ memcpy(p, &u16, sizeof(u16));
+ p += sizeof(u16);
+ memcpy(p, vendor, (size_t)vlen);
+ }
+ }
+
+ return len;
+}
+
+static const struct dhcp6_option *
+dhcp6_findoption(uint16_t code, const uint8_t *d, size_t len)
+{
+ const struct dhcp6_option *o;
+ size_t ol;
+
+ code = htons(code);
+ for (o = (const struct dhcp6_option *)d;
+ len >= sizeof(*o);
+ o = D6_CNEXT_OPTION(o))
+ {
+ ol = sizeof(*o) + ntohs(o->len);
+ if (ol > len) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (o->code == code)
+ return o;
+ len -= ol;
+ }
+
+ errno = ESRCH;
+ return NULL;
+}
+
+static const uint8_t *
+dhcp6_getoption(struct dhcpcd_ctx *ctx,
+ size_t *os, unsigned int *code, size_t *len,
+ const uint8_t *od, size_t ol, struct dhcp_opt **oopt)
+{
+ const struct dhcp6_option *o;
+ size_t i;
+ struct dhcp_opt *opt;
+
+ if (od) {
+ *os = sizeof(*o);
+ if (ol < *os) {
+ errno = EINVAL;
+ return NULL;
+ }
+ o = (const struct dhcp6_option *)od;
+ *len = ntohs(o->len);
+ if (*len > ol) {
+ errno = EINVAL;
+ return NULL;
+ }
+ *code = ntohs(o->code);
+ } else
+ o = NULL;
+
+ for (i = 0, opt = ctx->dhcp6_opts;
+ i < ctx->dhcp6_opts_len; i++, opt++)
+ {
+ if (opt->option == *code) {
+ *oopt = opt;
+ break;
+ }
+ }
+
+ if (o)
+ return D6_COPTION_DATA(o);
+ return NULL;
+}
+
+static const struct dhcp6_option *
+dhcp6_getmoption(uint16_t code, const struct dhcp6_message *m, size_t len)
+{
+
+ if (len < sizeof(*m)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ len -= sizeof(*m);
+ return dhcp6_findoption(code,
+ (const uint8_t *)D6_CFIRST_OPTION(m), len);
+}
+
+static int
+dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, size_t len)
+{
+ struct dhcp6_state *state;
+ const struct dhcp6_option *co;
+ struct dhcp6_option *o;
+ struct timespec tv;
+ time_t hsec;
+ uint16_t u16;
+
+ co = dhcp6_getmoption(D6_OPTION_ELAPSED, m, len);
+ if (co == NULL)
+ return -1;
+
+ o = __UNCONST(co);
+ state = D6_STATE(ifp);
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ if (state->RTC == 0) {
+ /* An RTC of zero means we're the first message
+ * out of the door, so the elapsed time is zero. */
+ state->started = tv;
+ hsec = 0;
+ } else {
+ timespecsub(&tv, &state->started, &tv);
+ /* Elapsed time is measured in centiseconds.
+ * We need to be sure it will not potentially overflow. */
+ if (tv.tv_sec >= (UINT16_MAX / CSEC_PER_SEC) + 1)
+ hsec = UINT16_MAX;
+ else {
+ hsec = (tv.tv_sec * CSEC_PER_SEC) +
+ (tv.tv_nsec / NSEC_PER_CSEC);
+ if (hsec > UINT16_MAX)
+ hsec = UINT16_MAX;
+ }
+ }
+ u16 = htons((uint16_t)hsec);
+ memcpy(D6_OPTION_DATA(o), &u16, sizeof(u16));
+ return 0;
+}
+
+static void
+dhcp6_newxid(const struct interface *ifp, struct dhcp6_message *m)
+{
+ uint32_t xid;
+
+ if (ifp->options->options & DHCPCD_XID_HWADDR &&
+ ifp->hwlen >= sizeof(xid))
+ /* The lower bits are probably more unique on the network */
+ memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid),
+ sizeof(xid));
+ else
+ xid = arc4random();
+
+ m->xid[0] = (xid >> 16) & 0xff;
+ m->xid[1] = (xid >> 8) & 0xff;
+ m->xid[2] = xid & 0xff;
+}
+
+static const struct if_sla *
+dhcp6_findselfsla(struct interface *ifp, const uint8_t *iaid)
+{
+ size_t i, j;
+
+ for (i = 0; i < ifp->options->ia_len; i++) {
+ if (iaid == NULL ||
+ memcmp(&ifp->options->ia[i].iaid, iaid,
+ sizeof(ifp->options->ia[i].iaid)) == 0)
+ {
+ for (j = 0; j < ifp->options->ia[i].sla_len; j++) {
+ if (strcmp(ifp->options->ia[i].sla[j].ifname,
+ ifp->name) == 0)
+ return &ifp->options->ia[i].sla[j];
+ }
+ }
+ }
+ return NULL;
+}
+
+
+#ifndef ffs32
+static int
+ffs32(uint32_t n)
+{
+ int v;
+
+ if (!n)
+ return 0;
+
+ v = 1;
+ if ((n & 0x0000FFFFU) == 0) {
+ n >>= 16;
+ v += 16;
+ }
+ if ((n & 0x000000FFU) == 0) {
+ n >>= 8;
+ v += 8;
+ }
+ if ((n & 0x0000000FU) == 0) {
+ n >>= 4;
+ v += 4;
+ }
+ if ((n & 0x00000003U) == 0) {
+ n >>= 2;
+ v += 2;
+ }
+ if ((n & 0x00000001U) == 0)
+ v += 1;
+
+ return v;
+}
+#endif
+
+static int
+dhcp6_delegateaddr(struct in6_addr *addr, struct interface *ifp,
+ const struct ipv6_addr *prefix, const struct if_sla *sla, struct if_ia *ia)
+{
+ struct dhcp6_state *state;
+ struct if_sla asla;
+ char sabuf[INET6_ADDRSTRLEN];
+ const char *sa;
+
+ state = D6_STATE(ifp);
+ if (state == NULL) {
+ ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state));
+ state = D6_STATE(ifp);
+ if (state == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+
+ TAILQ_INIT(&state->addrs);
+ state->state = DH6S_DELEGATED;
+ state->reason = "DELEGATED6";
+ }
+
+ if (sla == NULL || sla->sla_set == 0) {
+ asla.sla = ifp->index;
+ asla.prefix_len = 0;
+ sla = &asla;
+ } else if (sla->prefix_len == 0) {
+ asla.sla = sla->sla;
+ if (asla.sla == 0)
+ asla.prefix_len = prefix->prefix_len;
+ else
+ asla.prefix_len = 0;
+ sla = &asla;
+ }
+ if (sla->prefix_len == 0) {
+ uint32_t sla_max;
+ int bits;
+
+ if (ia->sla_max == 0) {
+ const struct interface *ifi;
+
+ sla_max = 0;
+ TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) {
+ if (ifi != ifp && ifi->index > sla_max)
+ sla_max = ifi->index;
+ }
+ } else
+ sla_max = ia->sla_max;
+
+ bits = ffs32(sla_max);
+
+ if (prefix->prefix_len + bits > UINT8_MAX)
+ asla.prefix_len = UINT8_MAX;
+ else {
+ asla.prefix_len = (uint8_t)(prefix->prefix_len + bits);
+
+ /* Make a 64 prefix by default, as this maks SLAAC
+ * possible. Otherwise round up to the nearest octet. */
+ if (asla.prefix_len <= 64)
+ asla.prefix_len = 64;
+ else
+ asla.prefix_len = (uint8_t)ROUNDUP8(asla.prefix_len);
+
+ }
+
+#define BIT(n) (1l << (n))
+#define BIT_MASK(len) (BIT(len) - 1)
+ if (ia->sla_max == 0)
+ /* Work out the real sla_max from our bits used */
+ ia->sla_max = (uint32_t)BIT_MASK(asla.prefix_len -
+ prefix->prefix_len);
+ }
+
+ if (ipv6_userprefix(&prefix->prefix, prefix->prefix_len,
+ sla->sla, addr, sla->prefix_len) == -1)
+ {
+ sa = inet_ntop(AF_INET6, &prefix->prefix,
+ sabuf, sizeof(sabuf));
+ logger(ifp->ctx, LOG_ERR,
+ "%s: invalid prefix %s/%d + %d/%d: %m",
+ ifp->name, sa, prefix->prefix_len,
+ sla->sla, sla->prefix_len);
+ return -1;
+ }
+
+ if (prefix->prefix_exclude_len &&
+ IN6_ARE_ADDR_EQUAL(addr, &prefix->prefix_exclude))
+ {
+ sa = inet_ntop(AF_INET6, &prefix->prefix_exclude,
+ sabuf, sizeof(sabuf));
+ logger(ifp->ctx, LOG_ERR,
+ "%s: cannot delegate excluded prefix %s/%d",
+ ifp->name, sa, prefix->prefix_exclude_len);
+ return -1;
+ }
+
+ return sla->prefix_len;
+}
+
+int
+dhcp6_has_public_addr(const struct interface *ifp)
+{
+ const struct dhcp6_state *state = D6_CSTATE(ifp);
+ const struct ipv6_addr *ia;
+
+ if (state == NULL)
+ return 0;
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ipv6_publicaddr(ia))
+ return 1;
+ }
+ return 0;
+}
+
+static int
+dhcp6_makemessage(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ struct dhcp6_message *m;
+ struct dhcp6_option *o, *so, *eo;
+ const struct dhcp6_option *si, *unicast;
+ size_t l, n, len, ml;
+ uint8_t u8, type;
+ uint16_t u16, n_options, auth_len;
+ struct if_options *ifo;
+ const struct dhcp_opt *opt, *opt2;
+ uint8_t IA, *p;
+ const uint8_t *pp;
+ uint32_t u32;
+ const struct ipv6_addr *ap;
+ char hbuf[HOSTNAME_MAX_LEN + 1];
+ const char *hostname;
+ int fqdn;
+ struct dhcp6_ia_addr *iap;
+ struct dhcp6_pd_addr *pdp;
+
+ state = D6_STATE(ifp);
+ if (state->send) {
+ free(state->send);
+ state->send = NULL;
+ }
+
+ ifo = ifp->options;
+ fqdn = ifo->fqdn;
+
+ if (fqdn == FQDN_DISABLE && ifo->options & DHCPCD_HOSTNAME) {
+ /* We're sending the DHCPv4 hostname option, so send FQDN as
+ * DHCPv6 has no FQDN option and DHCPv4 must not send
+ * hostname and FQDN according to RFC4702 */
+ fqdn = FQDN_BOTH;
+ }
+ if (fqdn != FQDN_DISABLE) {
+ if (ifo->hostname[0] == '\0')
+ hostname = get_hostname(hbuf, sizeof(hbuf),
+ ifo->options & DHCPCD_HOSTNAME_SHORT ? 1 : 0);
+ else
+ hostname = ifo->hostname;
+ } else
+ hostname = NULL; /* appearse gcc */
+
+ /* Work out option size first */
+ n_options = 0;
+ len = 0;
+ si = NULL;
+ if (state->state != DH6S_RELEASE) {
+ for (l = 0, opt = ifp->ctx->dhcp6_opts;
+ l < ifp->ctx->dhcp6_opts_len;
+ l++, opt++)
+ {
+ for (n = 0, opt2 = ifo->dhcp6_override;
+ n < ifo->dhcp6_override_len;
+ n++, opt2++)
+ {
+ if (opt->option == opt2->option)
+ break;
+ }
+ if (n < ifo->dhcp6_override_len)
+ continue;
+ if (!(opt->type & NOREQ) &&
+ (opt->type & REQUEST ||
+ has_option_mask(ifo->requestmask6, opt->option)))
+ {
+ n_options++;
+ len += sizeof(u16);
+ }
+ }
+ for (l = 0, opt = ifo->dhcp6_override;
+ l < ifo->dhcp6_override_len;
+ l++, opt++)
+ {
+ if (!(opt->type & NOREQ) &&
+ (opt->type & REQUEST ||
+ has_option_mask(ifo->requestmask6, opt->option)))
+ {
+ n_options++;
+ len += sizeof(u16);
+ }
+ }
+ if (dhcp6_findselfsla(ifp, NULL)) {
+ n_options++;
+ len += sizeof(u16);
+ }
+ if (len)
+ len += sizeof(*o);
+
+ if (fqdn != FQDN_DISABLE)
+ len += sizeof(*o) + 1 + encode_rfc1035(hostname, NULL);
+
+ if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
+ DHCPCD_AUTH_SENDREQUIRE)
+ len += sizeof(*o); /* Reconfigure Accept */
+ }
+
+ len += sizeof(*state->send);
+ len += sizeof(*o) + ifp->ctx->duid_len;
+ len += sizeof(*o) + sizeof(uint16_t); /* elapsed */
+ len += sizeof(*o) + dhcp6_makevendor(NULL, ifp);
+
+ /* IA */
+ m = NULL;
+ ml = 0;
+ switch(state->state) {
+ case DH6S_REQUEST:
+ m = state->recv;
+ ml = state->recv_len;
+ /* FALLTHROUGH */
+ case DH6S_RELEASE:
+ /* FALLTHROUGH */
+ case DH6S_RENEW:
+ if (m == NULL) {
+ m = state->new;
+ ml = state->new_len;
+ }
+ si = dhcp6_getmoption(D6_OPTION_SERVERID, m, ml);
+ if (si == NULL) {
+ errno = ESRCH;
+ return -1;
+ }
+ len += sizeof(*si) + ntohs(si->len);
+ /* FALLTHROUGH */
+ case DH6S_REBIND:
+ /* FALLTHROUGH */
+ case DH6S_CONFIRM:
+ /* FALLTHROUGH */
+ case DH6S_DISCOVER:
+ if (m == NULL) {
+ m = state->new;
+ ml = state->new_len;
+ }
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->prefix_vltime == 0 &&
+ !(ap->flags & IPV6_AF_REQUEST))
+ continue;
+ if (ap->ia_type == D6_OPTION_IA_PD) {
+ len += sizeof(*o) + sizeof(u8) +
+ sizeof(u32) + sizeof(u32) +
+ sizeof(ap->prefix);
+ if (ap->prefix_exclude_len)
+ len += sizeof(*o) + 1 +
+ (uint8_t)((ap->prefix_exclude_len -
+ ap->prefix_len - 1) / NBBY) + 1;
+ } else
+ len += sizeof(*o) + sizeof(ap->addr) +
+ sizeof(u32) + sizeof(u32);
+ }
+ /* FALLTHROUGH */
+ case DH6S_INIT:
+ len += ifo->ia_len * (sizeof(*o) + (sizeof(u32) * 3));
+ IA = 1;
+ break;
+ default:
+ IA = 0;
+ }
+
+ if (state->state == DH6S_DISCOVER &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+ has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT))
+ len += sizeof(*o);
+
+ if (m == NULL) {
+ m = state->new;
+ ml = state->new_len;
+ }
+ unicast = NULL;
+ /* Depending on state, get the unicast address */
+ switch(state->state) {
+ break;
+ case DH6S_INIT: /* FALLTHROUGH */
+ case DH6S_DISCOVER:
+ type = DHCP6_SOLICIT;
+ break;
+ case DH6S_REQUEST:
+ type = DHCP6_REQUEST;
+ unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
+ break;
+ case DH6S_CONFIRM:
+ type = DHCP6_CONFIRM;
+ break;
+ case DH6S_REBIND:
+ type = DHCP6_REBIND;
+ break;
+ case DH6S_RENEW:
+ type = DHCP6_RENEW;
+ unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
+ break;
+ case DH6S_INFORM:
+ type = DHCP6_INFORMATION_REQ;
+ break;
+ case DH6S_RELEASE:
+ type = DHCP6_RELEASE;
+ unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ auth_len = 0;
+ if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ ssize_t alen = dhcp_auth_encode(&ifo->auth,
+ state->auth.token, NULL, 0, 6, type, NULL, 0);
+ if (alen != -1 && alen > UINT16_MAX) {
+ errno = ERANGE;
+ alen = -1;
+ }
+ if (alen == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp_auth_encode: %m", ifp->name);
+ else if (alen != 0) {
+ auth_len = (uint16_t)alen;
+ len += sizeof(*o) + auth_len;
+ }
+ }
+
+ state->send = malloc(len);
+ if (state->send == NULL)
+ return -1;
+
+ state->send_len = len;
+ state->send->type = type;
+
+ /* If we found a unicast option, copy it to our state for sending */
+ if (unicast && ntohs(unicast->len) == sizeof(state->unicast))
+ memcpy(&state->unicast, D6_COPTION_DATA(unicast),
+ sizeof(state->unicast));
+ else
+ state->unicast = in6addr_any;
+
+ dhcp6_newxid(ifp, state->send);
+
+ o = D6_FIRST_OPTION(state->send);
+ o->code = htons(D6_OPTION_CLIENTID);
+ o->len = htons((uint16_t)ifp->ctx->duid_len);
+ memcpy(D6_OPTION_DATA(o), ifp->ctx->duid, ifp->ctx->duid_len);
+
+ if (si) {
+ o = D6_NEXT_OPTION(o);
+ memcpy(o, si, sizeof(*si) + ntohs(si->len));
+ }
+
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(D6_OPTION_ELAPSED);
+ o->len = htons(sizeof(uint16_t));
+ p = D6_OPTION_DATA(o);
+ memset(p, 0, sizeof(uint16_t));
+
+ o = D6_NEXT_OPTION(o);
+ dhcp6_makevendor(o, ifp);
+
+ if (state->state == DH6S_DISCOVER &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+ has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT))
+ {
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(D6_OPTION_RAPID_COMMIT);
+ o->len = 0;
+ }
+
+ for (l = 0; IA && l < ifo->ia_len; l++) {
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(ifo->ia[l].ia_type);
+ o->len = htons(sizeof(u32) + sizeof(u32) + sizeof(u32));
+ p = D6_OPTION_DATA(o);
+ memcpy(p, ifo->ia[l].iaid, sizeof(u32));
+ p += sizeof(u32);
+ memset(p, 0, sizeof(u32) + sizeof(u32));
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->prefix_vltime == 0 &&
+ !(ap->flags & IPV6_AF_REQUEST))
+ continue;
+ if (memcmp(ifo->ia[l].iaid, ap->iaid, sizeof(u32)))
+ continue;
+ so = D6_NEXT_OPTION(o);
+ if (ap->ia_type == D6_OPTION_IA_PD) {
+ so->code = htons(D6_OPTION_IAPREFIX);
+ so->len = htons(sizeof(ap->prefix) +
+ sizeof(u32) + sizeof(u32) + sizeof(u8));
+ pdp = (struct dhcp6_pd_addr *)
+ D6_OPTION_DATA(so);
+ pdp->pltime = htonl(ap->prefix_pltime);
+ pdp->vltime = htonl(ap->prefix_vltime);
+ pdp->prefix_len = ap->prefix_len;
+ pdp->prefix = ap->prefix;
+
+ /* RFC6603 Section 4.2 */
+ if (ap->prefix_exclude_len) {
+ n = (size_t)((ap->prefix_exclude_len -
+ ap->prefix_len - 1) / NBBY) + 1;
+ eo = D6_NEXT_OPTION(so);
+ eo->code = htons(D6_OPTION_PD_EXCLUDE);
+ eo->len = (uint16_t)(n + 1);
+ p = D6_OPTION_DATA(eo);
+ *p++ = (uint8_t)ap->prefix_exclude_len;
+ pp = ap->prefix_exclude.s6_addr;
+ pp += (size_t)((ap->prefix_len - 1) / NBBY)
+ + (n - 1);
+ u8 = ap->prefix_len % NBBY;
+ if (u8)
+ n--;
+ while (n-- > 0)
+ *p++ = *pp--;
+ if (u8)
+ *p = (uint8_t)(*pp << u8);
+ u16 = (uint16_t)(ntohs(so->len) +
+ sizeof(*eo) + eo->len);
+ so->len = htons(u16);
+ eo->len = htons(eo->len);
+ }
+
+ u16 = (uint16_t)(ntohs(o->len) + sizeof(*so)
+ + ntohs(so->len));
+ o->len = htons(u16);
+ } else {
+ so->code = htons(D6_OPTION_IA_ADDR);
+ so->len = sizeof(ap->addr) +
+ sizeof(u32) + sizeof(u32);
+ iap = (struct dhcp6_ia_addr *)
+ D6_OPTION_DATA(so);
+ iap->addr = ap->addr;
+ iap->pltime = htonl(ap->prefix_pltime);
+ iap->vltime = htonl(ap->prefix_vltime);
+ u16 = (uint16_t)(ntohs(o->len) + sizeof(*so)
+ + so->len);
+ so->len = htons(so->len);
+ o->len = htons(u16);
+ }
+ }
+ }
+
+ if (state->send->type != DHCP6_RELEASE) {
+ if (fqdn != FQDN_DISABLE) {
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(D6_OPTION_FQDN);
+ p = D6_OPTION_DATA(o);
+ switch (fqdn) {
+ case FQDN_BOTH:
+ *p = D6_FQDN_BOTH;
+ break;
+ case FQDN_PTR:
+ *p = D6_FQDN_PTR;
+ break;
+ default:
+ *p = D6_FQDN_NONE;
+ break;
+ }
+ l = encode_rfc1035(hostname, p + 1);
+ if (l == 0)
+ *p = D6_FQDN_NONE;
+ o->len = htons((uint16_t)(l + 1));
+ }
+
+ if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
+ DHCPCD_AUTH_SENDREQUIRE)
+ {
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(D6_OPTION_RECONF_ACCEPT);
+ o->len = 0;
+ }
+
+ if (n_options) {
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(D6_OPTION_ORO);
+ o->len = 0;
+ p = D6_OPTION_DATA(o);
+ for (l = 0, opt = ifp->ctx->dhcp6_opts;
+ l < ifp->ctx->dhcp6_opts_len;
+ l++, opt++)
+ {
+ for (n = 0, opt2 = ifo->dhcp6_override;
+ n < ifo->dhcp6_override_len;
+ n++, opt2++)
+ {
+ if (opt->option == opt2->option)
+ break;
+ }
+ if (n < ifo->dhcp6_override_len)
+ continue;
+ if (!(opt->type & NOREQ) &&
+ (opt->type & REQUEST ||
+ has_option_mask(ifo->requestmask6,
+ opt->option)))
+ {
+ u16 = htons((uint16_t)opt->option);
+ memcpy(p, &u16, sizeof(u16));
+ p += sizeof(u16);
+ o->len = (uint16_t)(o->len + sizeof(u16));
+ }
+ }
+ for (l = 0, opt = ifo->dhcp6_override;
+ l < ifo->dhcp6_override_len;
+ l++, opt++)
+ {
+ if (!(opt->type & NOREQ) &&
+ (opt->type & REQUEST ||
+ has_option_mask(ifo->requestmask6,
+ opt->option)))
+ {
+ u16 = htons((uint16_t)opt->option);
+ memcpy(p, &u16, sizeof(u16));
+ p += sizeof(u16);
+ o->len = (uint16_t)(o->len + sizeof(u16));
+ }
+ }
+ if (dhcp6_findselfsla(ifp, NULL)) {
+ u16 = htons(D6_OPTION_PD_EXCLUDE);
+ memcpy(p, &u16, sizeof(u16));
+ o->len = (uint16_t)(o->len + sizeof(u16));
+ }
+ o->len = htons(o->len);
+ }
+ }
+
+ /* This has to be the last option */
+ if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) {
+ o = D6_NEXT_OPTION(o);
+ o->code = htons(D6_OPTION_AUTH);
+ o->len = htons((uint16_t)auth_len);
+ /* data will be filled at send message time */
+ }
+
+ return 0;
+}
+
+static const char *
+dhcp6_get_op(uint16_t type)
+{
+ const struct dhcp6_op *d;
+
+ for (d = dhcp6_ops; d->name; d++)
+ if (d->type == type)
+ return d->name;
+ return NULL;
+}
+
+static void
+dhcp6_freedrop_addrs(struct interface *ifp, int drop,
+ const struct interface *ifd)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ if (state) {
+ ipv6_freedrop_addrs(&state->addrs, drop, ifd);
+ if (drop)
+ ipv6_buildroutes(ifp->ctx);
+ }
+}
+
+static void dhcp6_delete_delegates(struct interface *ifp)
+{
+ struct interface *ifp0;
+
+ if (ifp->ctx->ifaces) {
+ TAILQ_FOREACH(ifp0, ifp->ctx->ifaces, next) {
+ if (ifp0 != ifp)
+ dhcp6_freedrop_addrs(ifp0, 1, ifp);
+ }
+ }
+}
+
+static ssize_t
+dhcp6_update_auth(struct interface *ifp, struct dhcp6_message *m, size_t len)
+{
+ struct dhcp6_state *state;
+ const struct dhcp6_option *co;
+ struct dhcp6_option *o;
+
+ co = dhcp6_getmoption(D6_OPTION_AUTH, m, len);
+ if (co == NULL)
+ return -1;
+
+ o = __UNCONST(co);
+ state = D6_STATE(ifp);
+
+ return dhcp_auth_encode(&ifp->options->auth, state->auth.token,
+ (uint8_t *)state->send, state->send_len,
+ 6, state->send->type,
+ D6_OPTION_DATA(o), ntohs(o->len));
+}
+
+static int
+dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *))
+{
+ struct dhcp6_state *state;
+ struct ipv6_ctx *ctx;
+ struct sockaddr_in6 dst;
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi;
+ struct timespec RTprev;
+ double rnd;
+ time_t ms;
+ uint8_t neg;
+ const char *broad_uni;
+ const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT;
+
+ if (!callback && ifp->carrier == LINK_DOWN)
+ return 0;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sin6_family = AF_INET6;
+ dst.sin6_port = htons(DHCP6_SERVER_PORT);
+#ifdef SIN6_LEN
+ dst.sin6_len = sizeof(dst);
+#endif
+
+ state = D6_STATE(ifp);
+ /* We need to ensure we have sufficient scope to unicast the address */
+ /* XXX FIXME: We should check any added addresses we have like from
+ * a Router Advertisement */
+ if (IN6_IS_ADDR_UNSPECIFIED(&state->unicast) ||
+ (state->state == DH6S_REQUEST &&
+ (!IN6_IS_ADDR_LINKLOCAL(&state->unicast) || !ipv6_linklocal(ifp))))
+ {
+ dst.sin6_addr = alldhcp;
+ broad_uni = "broadcasting";
+ } else {
+ dst.sin6_addr = state->unicast;
+ broad_uni = "unicasting";
+ }
+
+ if (!callback)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: %s %s with xid 0x%02x%02x%02x",
+ ifp->name,
+ broad_uni,
+ dhcp6_get_op(state->send->type),
+ state->send->xid[0],
+ state->send->xid[1],
+ state->send->xid[2]);
+ else {
+ if (state->IMD &&
+ !(ifp->options->options & DHCPCD_INITIAL_DELAY))
+ state->IMD = 0;
+ if (state->IMD) {
+ /* Some buggy PPP servers close the link too early
+ * after sending an invalid status in their reply
+ * which means this host won't see it.
+ * 1 second grace seems to be the sweet spot. */
+ if (ifp->flags & IFF_POINTOPOINT)
+ state->RT.tv_sec = 1;
+ else
+ state->RT.tv_sec = 0;
+ state->RT.tv_nsec = (suseconds_t)arc4random_uniform(
+ (uint32_t)(state->IMD * NSEC_PER_SEC));
+ timespecnorm(&state->RT);
+ broad_uni = "delaying";
+ goto logsend;
+ }
+ if (state->RTC == 0) {
+ RTprev.tv_sec = state->IRT;
+ RTprev.tv_nsec = 0;
+ state->RT.tv_sec = RTprev.tv_sec;
+ state->RT.tv_nsec = 0;
+ } else {
+ RTprev = state->RT;
+ timespecadd(&state->RT, &state->RT, &state->RT);
+ }
+
+ rnd = DHCP6_RAND_MIN;
+ rnd += (suseconds_t)arc4random_uniform(
+ DHCP6_RAND_MAX - DHCP6_RAND_MIN);
+ rnd /= MSEC_PER_SEC;
+ neg = (rnd < 0.0);
+ if (neg)
+ rnd = -rnd;
+ ts_to_ms(ms, &RTprev);
+ ms = (time_t)((double)ms * rnd);
+ ms_to_ts(&RTprev, ms);
+ if (neg)
+ timespecsub(&state->RT, &RTprev, &state->RT);
+ else
+ timespecadd(&state->RT, &RTprev, &state->RT);
+
+ if (state->MRT != 0 && state->RT.tv_sec > state->MRT) {
+ RTprev.tv_sec = state->MRT;
+ RTprev.tv_nsec = 0;
+ state->RT.tv_sec = state->MRT;
+ state->RT.tv_nsec = 0;
+ ts_to_ms(ms, &RTprev);
+ ms = (time_t)((double)ms * rnd);
+ ms_to_ts(&RTprev, ms);
+ if (neg)
+ timespecsub(&state->RT, &RTprev, &state->RT);
+ else
+ timespecadd(&state->RT, &RTprev, &state->RT);
+ }
+
+logsend:
+ if (ifp->carrier != LINK_DOWN)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: %s %s (xid 0x%02x%02x%02x),"
+ " next in %0.1f seconds",
+ ifp->name,
+ broad_uni,
+ dhcp6_get_op(state->send->type),
+ state->send->xid[0],
+ state->send->xid[1],
+ state->send->xid[2],
+ timespec_to_double(&state->RT));
+
+ /* This sometimes happens when we delegate to this interface
+ * AND run DHCPv6 on it normally. */
+ assert(timespec_to_double(&state->RT) != 0);
+
+ /* Wait the initial delay */
+ if (state->IMD != 0) {
+ state->IMD = 0;
+ eloop_timeout_add_tv(ifp->ctx->eloop,
+ &state->RT, callback, ifp);
+ return 0;
+ }
+ }
+
+ if (ifp->carrier == LINK_DOWN)
+ return 0;
+
+ /* Update the elapsed time */
+ dhcp6_updateelapsed(ifp, state->send, state->send_len);
+ if (ifp->options->auth.options & DHCPCD_AUTH_SEND &&
+ dhcp6_update_auth(ifp, state->send, state->send_len) == -1)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_updateauth: %m", ifp->name);
+ if (errno != ESRCH)
+ return -1;
+ }
+
+ ctx = ifp->ctx->ipv6;
+ dst.sin6_scope_id = ifp->index;
+ ctx->sndhdr.msg_name = (void *)&dst;
+ ctx->sndhdr.msg_iov[0].iov_base = state->send;
+ ctx->sndhdr.msg_iov[0].iov_len = state->send_len;
+
+ /* Set the outbound interface */
+ cm = CMSG_FIRSTHDR(&ctx->sndhdr);
+ if (cm == NULL) /* unlikely */
+ return -1;
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memset(&pi, 0, sizeof(pi));
+ pi.ipi6_ifindex = ifp->index;
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+ if (sendmsg(ctx->dhcp_fd, &ctx->sndhdr, 0) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: %s: sendmsg: %m", ifp->name, __func__);
+ ifp->options->options &= ~DHCPCD_IPV6;
+ dhcp6_drop(ifp, "EXPIRE6");
+ return -1;
+ }
+
+ state->RTC++;
+ if (callback) {
+ if (state->MRC == 0 || state->RTC < state->MRC)
+ eloop_timeout_add_tv(ifp->ctx->eloop,
+ &state->RT, callback, ifp);
+ else if (state->MRC != 0 && state->MRCcallback)
+ eloop_timeout_add_tv(ifp->ctx->eloop,
+ &state->RT, state->MRCcallback, ifp);
+ else
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: sent %d times with no reply",
+ ifp->name, state->RTC);
+ }
+ return 0;
+}
+
+static void
+dhcp6_sendinform(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_sendinform);
+}
+
+static void
+dhcp6_senddiscover(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_senddiscover);
+}
+
+static void
+dhcp6_sendrequest(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_sendrequest);
+}
+
+static void
+dhcp6_sendrebind(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_sendrebind);
+}
+
+static void
+dhcp6_sendrenew(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_sendrenew);
+}
+
+static void
+dhcp6_sendconfirm(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_sendconfirm);
+}
+
+static void
+dhcp6_sendrelease(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_sendrelease);
+}
+
+static void
+dhcp6_startrenew(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+
+ ifp = arg;
+ state = D6_STATE(ifp);
+ state->state = DH6S_RENEW;
+ state->RTC = 0;
+ state->IRT = REN_TIMEOUT;
+ state->MRT = REN_MAX_RT;
+ state->MRC = 0;
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ else
+ dhcp6_sendrenew(ifp);
+}
+
+int
+dhcp6_dadcompleted(const struct interface *ifp)
+{
+ const struct dhcp6_state *state;
+ const struct ipv6_addr *ap;
+
+ state = D6_CSTATE(ifp);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->flags & IPV6_AF_ADDED &&
+ !(ap->flags & IPV6_AF_DADCOMPLETED))
+ return 0;
+ }
+ return 1;
+}
+
+static void
+dhcp6_dadcallback(void *arg)
+{
+ struct ipv6_addr *ap = arg;
+ struct interface *ifp;
+ struct dhcp6_state *state;
+ int wascompleted, valid;
+
+ wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED);
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ if (ap->flags & IPV6_AF_DUPLICATED)
+ /* XXX FIXME
+ * We should decline the address */
+ logger(ap->iface->ctx, LOG_WARNING, "%s: DAD detected %s",
+ ap->iface->name, ap->saddr);
+
+ if (!wascompleted) {
+ ifp = ap->iface;
+ state = D6_STATE(ifp);
+ if (state->state == DH6S_BOUND ||
+ state->state == DH6S_DELEGATED)
+ {
+ struct ipv6_addr *ap2;
+
+ valid = (ap->delegating_iface == NULL);
+ TAILQ_FOREACH(ap2, &state->addrs, next) {
+ if (ap2->flags & IPV6_AF_ADDED &&
+ !(ap2->flags & IPV6_AF_DADCOMPLETED))
+ {
+ wascompleted = 1;
+ break;
+ }
+ }
+ if (!wascompleted) {
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: DHCPv6 DAD completed", ifp->name);
+ script_runreason(ifp,
+ ap->delegating_iface ?
+ "DELEGATED6" : state->reason);
+ if (valid)
+ dhcpcd_daemonise(ifp->ctx);
+ }
+ }
+ }
+}
+
+static void
+dhcp6_addrequestedaddrs(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ size_t i;
+ struct if_ia *ia;
+ struct ipv6_addr *a;
+ char iabuf[INET6_ADDRSTRLEN];
+ const char *iap;
+
+ state = D6_STATE(ifp);
+ /* Add any requested prefixes / addresses */
+ for (i = 0; i < ifp->options->ia_len; i++) {
+ ia = &ifp->options->ia[i];
+ if (!((ia->ia_type == D6_OPTION_IA_PD && ia->prefix_len) ||
+ !IN6_IS_ADDR_UNSPECIFIED(&ia->addr)))
+ continue;
+ a = calloc(1, sizeof(*a));
+ if (a == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ a->flags = IPV6_AF_REQUEST;
+ a->iface = ifp;
+ a->dadcallback = dhcp6_dadcallback;
+ memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid));
+ a->ia_type = ia->ia_type;
+ //a->prefix_pltime = 0;
+ //a->prefix_vltime = 0;
+
+ if (ia->ia_type == D6_OPTION_IA_PD) {
+ memcpy(&a->prefix, &ia->addr, sizeof(a->addr));
+ a->prefix_len = ia->prefix_len;
+ iap = inet_ntop(AF_INET6, &a->prefix,
+ iabuf, sizeof(iabuf));
+ } else {
+ memcpy(&a->addr, &ia->addr, sizeof(a->addr));
+ /*
+ * RFC 5942 Section 5
+ * We cannot assume any prefix length, nor tie the
+ * address to an existing one as it could expire
+ * before the address.
+ * As such we just give it a 128 prefix.
+ */
+ a->prefix_len = 128;
+ ipv6_makeprefix(&a->prefix, &a->addr, a->prefix_len);
+ iap = inet_ntop(AF_INET6, &a->addr,
+ iabuf, sizeof(iabuf));
+ }
+ snprintf(a->saddr, sizeof(a->saddr),
+ "%s/%d", iap, a->prefix_len);
+ TAILQ_INSERT_TAIL(&state->addrs, a, next);
+ }
+}
+
+static void
+dhcp6_startdiscover(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+
+ ifp = arg;
+ dhcp6_delete_delegates(ifp);
+ logger(ifp->ctx, LOG_INFO, "%s: soliciting a DHCPv6 lease", ifp->name);
+ state = D6_STATE(ifp);
+ state->state = DH6S_DISCOVER;
+ state->RTC = 0;
+ state->IMD = SOL_MAX_DELAY;
+ state->IRT = SOL_TIMEOUT;
+ state->MRT = state->sol_max_rt;
+ state->MRC = 0;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ free(state->new);
+ state->new = NULL;
+ state->new_len = 0;
+
+ dhcp6_freedrop_addrs(ifp, 0, NULL);
+ unlink(state->leasefile);
+
+ dhcp6_addrequestedaddrs(ifp);
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ else
+ dhcp6_senddiscover(ifp);
+}
+
+static void
+dhcp6_failconfirm(void *arg)
+{
+ struct interface *ifp;
+
+ ifp = arg;
+ logger(ifp->ctx, LOG_ERR,
+ "%s: failed to confirm prior address", ifp->name);
+ /* Section 18.1.2 says that we SHOULD use the last known
+ * IP address(s) and lifetimes if we didn't get a reply.
+ * I disagree with this. */
+ dhcp6_startdiscover(ifp);
+}
+
+static void
+dhcp6_failrequest(void *arg)
+{
+ struct interface *ifp;
+
+ ifp = arg;
+ logger(ifp->ctx, LOG_ERR, "%s: failed to request address", ifp->name);
+ /* Section 18.1.1 says that client local policy dictates
+ * what happens if a REQUEST fails.
+ * Of the possible scenarios listed, moving back to the
+ * DISCOVER phase makes more sense for us. */
+ dhcp6_startdiscover(ifp);
+}
+
+static void
+dhcp6_failrebind(void *arg)
+{
+ struct interface *ifp;
+
+ ifp = arg;
+ logger(ifp->ctx, LOG_ERR,
+ "%s: failed to rebind prior delegation", ifp->name);
+ dhcp6_delete_delegates(ifp);
+ /* Section 18.1.2 says that we SHOULD use the last known
+ * IP address(s) and lifetimes if we didn't get a reply.
+ * I disagree with this. */
+ dhcp6_startdiscover(ifp);
+}
+
+
+static int
+dhcp6_hasprefixdelegation(struct interface *ifp)
+{
+ size_t i;
+ uint16_t t;
+
+ t = 0;
+ for (i = 0; i < ifp->options->ia_len; i++) {
+ if (t && t != ifp->options->ia[i].ia_type) {
+ if (t == D6_OPTION_IA_PD ||
+ ifp->options->ia[i].ia_type == D6_OPTION_IA_PD)
+ return 2;
+ }
+ t = ifp->options->ia[i].ia_type;
+ }
+ return t == D6_OPTION_IA_PD ? 1 : 0;
+}
+
+static void
+dhcp6_startrebind(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+ int pd;
+
+ ifp = arg;
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp);
+ state = D6_STATE(ifp);
+ if (state->state == DH6S_RENEW)
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: failed to renew DHCPv6, rebinding", ifp->name);
+ else
+ logger(ifp->ctx, LOG_INFO,
+ "%s: rebinding prior DHCPv6 lease", ifp->name);
+ state->state = DH6S_REBIND;
+ state->RTC = 0;
+ state->MRC = 0;
+
+ /* RFC 3633 section 12.1 */
+ pd = dhcp6_hasprefixdelegation(ifp);
+ if (pd) {
+ state->IMD = CNF_MAX_DELAY;
+ state->IRT = CNF_TIMEOUT;
+ state->MRT = CNF_MAX_RT;
+ } else {
+ state->IRT = REB_TIMEOUT;
+ state->MRT = REB_MAX_RT;
+ }
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ else
+ dhcp6_sendrebind(ifp);
+
+ /* RFC 3633 section 12.1 */
+ if (pd)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ CNF_MAX_RD, dhcp6_failrebind, ifp);
+}
+
+
+static void
+dhcp6_startrequest(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp);
+ state = D6_STATE(ifp);
+ state->state = DH6S_REQUEST;
+ state->RTC = 0;
+ state->IRT = REQ_TIMEOUT;
+ state->MRT = REQ_MAX_RT;
+ state->MRC = REQ_MAX_RC;
+ state->MRCcallback = dhcp6_failrequest;
+
+ if (dhcp6_makemessage(ifp) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ return;
+ }
+
+ dhcp6_sendrequest(ifp);
+}
+
+static void
+dhcp6_startconfirm(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ state->state = DH6S_CONFIRM;
+ state->RTC = 0;
+ state->IMD = CNF_MAX_DELAY;
+ state->IRT = CNF_TIMEOUT;
+ state->MRT = CNF_MAX_RT;
+ state->MRC = 0;
+
+ logger(ifp->ctx, LOG_INFO,
+ "%s: confirming prior DHCPv6 lease", ifp->name);
+ if (dhcp6_makemessage(ifp) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ return;
+ }
+ dhcp6_sendconfirm(ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ CNF_MAX_RD, dhcp6_failconfirm, ifp);
+}
+
+static void
+dhcp6_startinform(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+
+ ifp = arg;
+ state = D6_STATE(ifp);
+ if (state->new == NULL || ifp->options->options & DHCPCD_DEBUG)
+ logger(ifp->ctx, LOG_INFO,
+ "%s: requesting DHCPv6 information", ifp->name);
+ state->state = DH6S_INFORM;
+ state->RTC = 0;
+ state->IMD = INF_MAX_DELAY;
+ state->IRT = INF_TIMEOUT;
+ state->MRT = state->inf_max_rt;
+ state->MRC = 0;
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ else
+ dhcp6_sendinform(ifp);
+}
+
+static void
+dhcp6_startexpire(void *arg)
+{
+ struct interface *ifp;
+
+ ifp = arg;
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp);
+
+ logger(ifp->ctx, LOG_ERR, "%s: DHCPv6 lease expired", ifp->name);
+ dhcp6_freedrop_addrs(ifp, 1, NULL);
+ dhcp6_delete_delegates(ifp);
+ script_runreason(ifp, "EXPIRE6");
+ if (ipv6nd_hasradhcp(ifp) || dhcp6_hasprefixdelegation(ifp))
+ dhcp6_startdiscover(ifp);
+ else
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: no advertising IPv6 router wants DHCP", ifp->name);
+}
+
+static void
+dhcp6_finishrelease(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+
+ ifp = (struct interface *)arg;
+ state = D6_STATE(ifp);
+ state->state = DH6S_RELEASED;
+ dhcp6_drop(ifp, "RELEASE6");
+}
+
+static void
+dhcp6_startrelease(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ if (state->state != DH6S_BOUND)
+ return;
+
+ state->state = DH6S_RELEASE;
+ state->RTC = 0;
+ state->IRT = REL_TIMEOUT;
+ state->MRT = 0;
+ /* MRC of REL_MAX_RC is optional in RFC 3315 18.1.6 */
+#if 0
+ state->MRC = REL_MAX_RC;
+ state->MRCcallback = dhcp6_finishrelease;
+#else
+ state->MRC = 0;
+ state->MRCcallback = NULL;
+#endif
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_makemessage: %m", ifp->name);
+ else {
+ dhcp6_sendrelease(ifp);
+ dhcp6_finishrelease(ifp);
+ }
+}
+
+static int
+dhcp6_checkstatusok(const struct interface *ifp,
+ const struct dhcp6_message *m, const uint8_t *p, size_t len)
+{
+ const struct dhcp6_option *o;
+ uint16_t code;
+ char *status;
+
+ if (p)
+ o = dhcp6_findoption(D6_OPTION_STATUS_CODE, p, len);
+ else
+ o = dhcp6_getmoption(D6_OPTION_STATUS_CODE, m, len);
+ if (o == NULL) {
+ //logger(ifp->ctx, LOG_DEBUG, "%s: no status", ifp->name);
+ return 0;
+ }
+
+ len = ntohs(o->len);
+ if (len < sizeof(code)) {
+ logger(ifp->ctx, LOG_ERR, "%s: status truncated", ifp->name);
+ return -1;
+ }
+
+ p = D6_COPTION_DATA(o);
+ memcpy(&code, p, sizeof(code));
+ code = ntohs(code);
+ if (code == D6_STATUS_OK)
+ return 1;
+
+ len -= sizeof(code);
+
+ if (len == 0) {
+ if (code < sizeof(dhcp6_statuses) / sizeof(char *)) {
+ p = (const uint8_t *)dhcp6_statuses[code];
+ len = strlen((const char *)p);
+ } else
+ p = NULL;
+ } else
+ p += sizeof(code);
+
+ status = malloc(len + 1);
+ if (status == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ if (p)
+ memcpy(status, p, len);
+ status[len] = '\0';
+ logger(ifp->ctx, LOG_ERR, "%s: DHCPv6 REPLY: %s", ifp->name, status);
+ free(status);
+ return -1;
+}
+
+const struct ipv6_addr *
+dhcp6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr,
+ short flags)
+{
+ const struct dhcp6_state *state;
+ const struct ipv6_addr *ap;
+
+ if ((state = D6_STATE(ifp)) != NULL) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ipv6_findaddrmatch(ap, addr, flags))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+struct ipv6_addr *
+dhcp6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr,
+ short flags)
+{
+ struct interface *ifp;
+ struct ipv6_addr *ap;
+ struct dhcp6_state *state;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if ((state = D6_STATE(ifp)) != NULL) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ipv6_findaddrmatch(ap, addr, flags))
+ return ap;
+ }
+ }
+ }
+ return NULL;
+}
+
+static int
+dhcp6_findna(struct interface *ifp, uint16_t ot, const uint8_t *iaid,
+ const uint8_t *d, size_t l, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ const struct dhcp6_option *o;
+ struct ipv6_addr *a;
+ char iabuf[INET6_ADDRSTRLEN];
+ const char *ia;
+ int i;
+ uint32_t u32;
+ size_t off;
+ const struct dhcp6_ia_addr *iap;
+
+ i = 0;
+ state = D6_STATE(ifp);
+ while ((o = dhcp6_findoption(D6_OPTION_IA_ADDR, d, l))) {
+ off = (size_t)((const uint8_t *)o - d);
+ l -= off;
+ d += off;
+ u32 = ntohs(o->len);
+ l -= sizeof(*o) + u32;
+ d += sizeof(*o) + u32;
+ if (u32 < 24) {
+ errno = EINVAL;
+ logger(ifp->ctx, LOG_ERR,
+ "%s: IA Address option truncated", ifp->name);
+ continue;
+ }
+ iap = (const struct dhcp6_ia_addr *)D6_COPTION_DATA(o);
+ TAILQ_FOREACH(a, &state->addrs, next) {
+ if (ipv6_findaddrmatch(a, &iap->addr, 0))
+ break;
+ }
+ if (a == NULL) {
+ a = calloc(1, sizeof(*a));
+ if (a == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ break;
+ }
+ a->iface = ifp;
+ a->flags = IPV6_AF_NEW | IPV6_AF_ONLINK;
+ a->dadcallback = dhcp6_dadcallback;
+ a->ia_type = ot;
+ memcpy(a->iaid, iaid, sizeof(a->iaid));
+ a->addr = iap->addr;
+ a->created = *acquired;
+
+ /*
+ * RFC 5942 Section 5
+ * We cannot assume any prefix length, nor tie the
+ * address to an existing one as it could expire
+ * before the address.
+ * As such we just give it a 128 prefix.
+ */
+ a->prefix_len = 128;
+ ipv6_makeprefix(&a->prefix, &a->addr, a->prefix_len);
+ ia = inet_ntop(AF_INET6, &a->addr,
+ iabuf, sizeof(iabuf));
+ snprintf(a->saddr, sizeof(a->saddr),
+ "%s/%d", ia, a->prefix_len);
+
+ TAILQ_INSERT_TAIL(&state->addrs, a, next);
+ } else {
+ if (!(a->flags & IPV6_AF_ONLINK))
+ a->flags |= IPV6_AF_ONLINK | IPV6_AF_NEW;
+ a->flags &= ~IPV6_AF_STALE;
+ }
+ a->acquired = *acquired;
+ a->prefix_pltime = ntohl(iap->pltime);
+ u32 = ntohl(iap->vltime);
+ if (a->prefix_vltime != u32) {
+ a->flags |= IPV6_AF_NEW;
+ a->prefix_vltime = u32;
+ }
+ if (a->prefix_pltime && a->prefix_pltime < state->lowpl)
+ state->lowpl = a->prefix_pltime;
+ if (a->prefix_vltime && a->prefix_vltime > state->expire)
+ state->expire = a->prefix_vltime;
+ i++;
+ }
+ return i;
+}
+
+static int
+dhcp6_findpd(struct interface *ifp, const uint8_t *iaid,
+ const uint8_t *d, size_t l, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ const struct dhcp6_option *o, *ex;
+ const uint8_t *p, *op;
+ struct ipv6_addr *a;
+ char iabuf[INET6_ADDRSTRLEN];
+ const char *ia;
+ int i;
+ uint8_t u8, *pw;
+ size_t off;
+ uint16_t ol;
+ const struct dhcp6_pd_addr *pdp;
+
+ i = 0;
+ state = D6_STATE(ifp);
+ while ((o = dhcp6_findoption(D6_OPTION_IAPREFIX, d, l))) {
+ off = (size_t)((const uint8_t *)o - d);
+ l -= off;
+ d += off;
+ ol = ntohs(o->len);
+ l -= sizeof(*o) + ol;
+ d += sizeof(*o) + ol;
+ if (ol < sizeof(*pdp)) {
+ errno = EINVAL;
+ logger(ifp->ctx, LOG_ERR,
+ "%s: IA Prefix option truncated", ifp->name);
+ continue;
+ }
+
+ pdp = (const struct dhcp6_pd_addr *)D6_COPTION_DATA(o);
+ TAILQ_FOREACH(a, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&a->prefix, &pdp->prefix))
+ break;
+ }
+ if (a == NULL) {
+ a = calloc(1, sizeof(*a));
+ if (a == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ break;
+ }
+ a->iface = ifp;
+ a->flags = IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX;
+ a->created = *acquired;
+ a->dadcallback = dhcp6_dadcallback;
+ a->ia_type = D6_OPTION_IA_PD;
+ memcpy(a->iaid, iaid, sizeof(a->iaid));
+ a->prefix = pdp->prefix;
+ a->prefix_len = pdp->prefix_len;
+ ia = inet_ntop(AF_INET6, &a->prefix,
+ iabuf, sizeof(iabuf));
+ snprintf(a->saddr, sizeof(a->saddr),
+ "%s/%d", ia, a->prefix_len);
+ TAILQ_INSERT_TAIL(&state->addrs, a, next);
+ } else {
+ if (!(a->flags & IPV6_AF_DELEGATEDPFX))
+ a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX;
+ a->flags &= ~(IPV6_AF_STALE | IPV6_AF_REQUEST);
+ if (a->prefix_vltime != ntohl(pdp->vltime))
+ a->flags |= IPV6_AF_NEW;
+ }
+
+ a->acquired = *acquired;
+ a->prefix_pltime = ntohl(pdp->pltime);
+ a->prefix_vltime = ntohl(pdp->vltime);
+
+ if (a->prefix_pltime && a->prefix_pltime < state->lowpl)
+ state->lowpl = a->prefix_pltime;
+ if (a->prefix_vltime && a->prefix_vltime > state->expire)
+ state->expire = a->prefix_vltime;
+ i++;
+
+ p = D6_COPTION_DATA(o) + sizeof(pdp);
+ ol = (uint16_t)(ol - sizeof(pdp));
+ ex = dhcp6_findoption(D6_OPTION_PD_EXCLUDE, p, ol);
+ a->prefix_exclude_len = 0;
+ memset(&a->prefix_exclude, 0, sizeof(a->prefix_exclude));
+#if 0
+ if (ex == NULL) {
+ struct dhcp6_option *w;
+ uint8_t *wp;
+
+ w = calloc(1, 128);
+ w->len = htons(2);
+ wp = D6_OPTION_DATA(w);
+ *wp++ = 64;
+ *wp++ = 0x78;
+ ex = w;
+ }
+#endif
+ if (ex == NULL)
+ continue;
+ ol = ntohs(ex->len);
+ if (ol < 2) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: truncated PD Exclude", ifp->name);
+ continue;
+ }
+ op = D6_COPTION_DATA(ex);
+ a->prefix_exclude_len = *op++;
+ ol--;
+ if (((a->prefix_exclude_len - a->prefix_len - 1) / NBBY) + 1
+ != ol)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: PD Exclude length mismatch", ifp->name);
+ a->prefix_exclude_len = 0;
+ continue;
+ }
+ u8 = a->prefix_len % NBBY;
+ memcpy(&a->prefix_exclude, &a->prefix,
+ sizeof(a->prefix_exclude));
+ if (u8)
+ ol--;
+ pw = a->prefix_exclude.s6_addr +
+ (a->prefix_exclude_len / NBBY) - 1;
+ while (ol-- > 0)
+ *pw-- = *op++;
+ if (u8)
+ *pw = (uint8_t)(*pw | (*op >> u8));
+ }
+ return i;
+}
+
+static int
+dhcp6_findia(struct interface *ifp, const struct dhcp6_message *m, size_t l,
+ const char *sfrom, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ const struct if_options *ifo;
+ const struct dhcp6_option *o;
+ const uint8_t *p;
+ int i, e;
+ size_t j;
+ uint32_t u32, renew, rebind;
+ uint16_t code, ol;
+ uint8_t iaid[4];
+ char buf[sizeof(iaid) * 3];
+ struct ipv6_addr *ap, *nap;
+
+ if (l < sizeof(*m)) {
+ /* Should be impossible with guards at packet in
+ * and reading leases */
+ errno = EINVAL;
+ return -1;
+ }
+
+ ifo = ifp->options;
+ i = e = 0;
+ state = D6_STATE(ifp);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ ap->flags |= IPV6_AF_STALE;
+ }
+ l -= sizeof(*m);
+ for (o = D6_CFIRST_OPTION(m); l > sizeof(*o); o = D6_CNEXT_OPTION(o)) {
+ ol = ntohs(o->len);
+ if (sizeof(*o) + ol > l) {
+ errno = EINVAL;
+ logger(ifp->ctx, LOG_ERR,
+ "%s: option overflow", ifp->name);
+ break;
+ }
+ l -= sizeof(*o) + ol;
+
+ code = ntohs(o->code);
+ switch(code) {
+ case D6_OPTION_IA_TA:
+ u32 = 4;
+ break;
+ case D6_OPTION_IA_NA:
+ case D6_OPTION_IA_PD:
+ u32 = 12;
+ break;
+ default:
+ continue;
+ }
+ if (ol < u32) {
+ errno = EINVAL;
+ logger(ifp->ctx, LOG_ERR,
+ "%s: IA option truncated", ifp->name);
+ continue;
+ }
+
+ p = D6_COPTION_DATA(o);
+ memcpy(iaid, p, sizeof(iaid));
+ p += sizeof(iaid);
+ ol = (uint16_t)(ol - sizeof(iaid));
+
+ for (j = 0; j < ifo->ia_len; j++) {
+ if (memcmp(&ifo->ia[j].iaid, iaid, sizeof(iaid)) == 0)
+ break;
+ }
+ if (j == ifo->ia_len &&
+ !(ifo->ia_len == 0 && ifp->ctx->options & DHCPCD_DUMPLEASE))
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ignoring unrequested IAID %s",
+ ifp->name,
+ hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf)));
+ continue;
+ }
+ if ( j < ifo->ia_len && ifo->ia[j].ia_type != code) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: IAID %s: option type mismatch",
+ ifp->name,
+ hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf)));
+ continue;
+ }
+
+ if (code != D6_OPTION_IA_TA) {
+ memcpy(&u32, p, sizeof(u32));
+ renew = ntohl(u32);
+ p += sizeof(u32);
+ ol = (uint16_t)(ol - sizeof(u32));
+ memcpy(&u32, p, sizeof(u32));
+ rebind = ntohl(u32);
+ p += sizeof(u32);
+ ol = (uint16_t)(ol - sizeof(u32));
+ } else
+ renew = rebind = 0; /* appease gcc */
+ if (dhcp6_checkstatusok(ifp, NULL, p, ol) == -1) {
+ e = 1;
+ continue;
+ }
+ if (code == D6_OPTION_IA_PD) {
+ if (dhcp6_findpd(ifp, iaid, p, ol, acquired) == 0) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: %s: DHCPv6 REPLY missing Prefix",
+ ifp->name, sfrom);
+ continue;
+ }
+ } else {
+ if (dhcp6_findna(ifp, code, iaid, p, ol, acquired) == 0)
+ {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: %s: DHCPv6 REPLY missing IA Address",
+ ifp->name, sfrom);
+ continue;
+ }
+ }
+ if (code != D6_OPTION_IA_TA) {
+ if (renew > rebind && rebind > 0) {
+ if (sfrom)
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: T1 (%d) > T2 (%d) from %s",
+ ifp->name, renew, rebind, sfrom);
+ renew = 0;
+ rebind = 0;
+ }
+ if (renew != 0 &&
+ (renew < state->renew || state->renew == 0))
+ state->renew = renew;
+ if (rebind != 0 &&
+ (rebind < state->rebind || state->rebind == 0))
+ state->rebind = rebind;
+ }
+ i++;
+ }
+ TAILQ_FOREACH_SAFE(ap, &state->addrs, next, nap) {
+ if (ap->flags & IPV6_AF_STALE) {
+ eloop_q_timeout_delete(ifp->ctx->eloop, 0, NULL, ap);
+ if (ap->flags & IPV6_AF_REQUEST) {
+ ap->prefix_vltime = ap->prefix_pltime = 0;
+ } else {
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ free(ap);
+ }
+ }
+ }
+ if (i == 0 && e)
+ return -1;
+ return i;
+}
+
+static int
+dhcp6_validatelease(struct interface *ifp,
+ const struct dhcp6_message *m, size_t len,
+ const char *sfrom, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ int nia;
+ struct timespec aq;
+
+ if (len <= sizeof(*m)) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: DHCPv6 lease truncated", ifp->name);
+ return -1;
+ }
+
+ state = D6_STATE(ifp);
+ if (dhcp6_checkstatusok(ifp, m, NULL, len) == -1)
+ return -1;
+
+ state->renew = state->rebind = state->expire = 0;
+ state->lowpl = ND6_INFINITE_LIFETIME;
+ if (!acquired) {
+ clock_gettime(CLOCK_MONOTONIC, &aq);
+ acquired = &aq;
+ }
+ nia = dhcp6_findia(ifp, m, len, sfrom, acquired);
+ if (nia == 0) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: no useable IA found in lease", ifp->name);
+ return -1;
+ }
+ return nia;
+}
+
+static ssize_t
+dhcp6_writelease(const struct interface *ifp)
+{
+ const struct dhcp6_state *state;
+ int fd;
+ ssize_t bytes;
+
+ state = D6_CSTATE(ifp);
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: writing lease `%s'", ifp->name, state->leasefile);
+
+ fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: dhcp6_writelease: %m", ifp->name);
+ return -1;
+ }
+ bytes = write(fd, state->new, state->new_len);
+ close(fd);
+ return bytes;
+}
+
+static int
+dhcp6_readlease(struct interface *ifp, int validate)
+{
+ struct dhcp6_state *state;
+ struct stat st;
+ int fd;
+ ssize_t bytes;
+ const struct dhcp6_option *o;
+ struct timespec acquired;
+ time_t now;
+ int retval;
+
+ state = D6_STATE(ifp);
+ if (stat(state->leasefile, &st) == -1)
+ return -1;
+ logger(ifp->ctx, LOG_DEBUG, "%s: reading lease `%s'",
+ ifp->name, state->leasefile);
+ if (st.st_size > UINT32_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+ if ((fd = open(state->leasefile, O_RDONLY)) == -1)
+ return -1;
+ if ((state->new = malloc((size_t)st.st_size)) == NULL)
+ return -1;
+ retval = -1;
+ state->new_len = (size_t)st.st_size;
+ bytes = read(fd, state->new, state->new_len);
+ close(fd);
+ if (bytes != (ssize_t)state->new_len)
+ goto ex;
+
+ /* If not validating IA's and if they have expired,
+ * skip to the auth check. */
+ if (!validate) {
+ fd = 0;
+ goto auth;
+ }
+
+ if ((now = time(NULL)) == -1)
+ goto ex;
+
+ clock_gettime(CLOCK_MONOTONIC, &acquired);
+ acquired.tv_sec -= now - st.st_mtime;
+
+ /* Check to see if the lease is still valid */
+ fd = dhcp6_validatelease(ifp, state->new, state->new_len, NULL,
+ &acquired);
+ if (fd == -1)
+ goto ex;
+
+ if (!(ifp->ctx->options & DHCPCD_DUMPLEASE) &&
+ state->expire != ND6_INFINITE_LIFETIME)
+ {
+ if ((time_t)state->expire < now - st.st_mtime) {
+ logger(ifp->ctx,
+ LOG_DEBUG,"%s: discarding expired lease",
+ ifp->name);
+ retval = 0;
+ goto ex;
+ }
+ }
+
+auth:
+
+ retval = 0;
+ /* Authenticate the message */
+ o = dhcp6_getmoption(D6_OPTION_AUTH, state->new, state->new_len);
+ if (o) {
+ if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+ (uint8_t *)state->new, state->new_len, 6, state->new->type,
+ D6_COPTION_DATA(o), ntohs(o->len)) == NULL)
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: dhcp_auth_validate: %m", ifp->name);
+ logger(ifp->ctx, LOG_ERR,
+ "%s: authentication failed", ifp->name);
+ goto ex;
+ }
+ if (state->auth.token)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: accepted reconfigure key", ifp->name);
+ } else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) ==
+ DHCPCD_AUTH_SENDREQUIRE)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: authentication now required", ifp->name);
+ goto ex;
+ }
+
+ return fd;
+
+ex:
+ dhcp6_freedrop_addrs(ifp, 0, NULL);
+ free(state->new);
+ state->new = NULL;
+ state->new_len = 0;
+ if (!(ifp->ctx->options & DHCPCD_DUMPLEASE))
+ unlink(state->leasefile);
+ return retval;
+}
+
+static void
+dhcp6_startinit(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ int r;
+ uint8_t has_ta, has_non_ta;
+ size_t i;
+
+ state = D6_STATE(ifp);
+ state->state = DH6S_INIT;
+ state->expire = ND6_INFINITE_LIFETIME;
+ state->lowpl = ND6_INFINITE_LIFETIME;
+
+ dhcp6_addrequestedaddrs(ifp);
+ has_ta = has_non_ta = 0;
+ for (i = 0; i < ifp->options->ia_len; i++) {
+ switch (ifp->options->ia[i].ia_type) {
+ case D6_OPTION_IA_TA:
+ has_ta = 1;
+ break;
+ default:
+ has_non_ta = 1;
+ }
+ }
+
+ if (!(ifp->ctx->options & DHCPCD_TEST) &&
+ !(has_ta && !has_non_ta) &&
+ ifp->options->reboot != 0)
+ {
+ r = dhcp6_readlease(ifp, 1);
+ if (r == -1) {
+ if (errno != ENOENT)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_readlease: %s: %m",
+ ifp->name, state->leasefile);
+ } else if (r != 0) {
+ /* RFC 3633 section 12.1 */
+ if (dhcp6_hasprefixdelegation(ifp))
+ dhcp6_startrebind(ifp);
+ else
+ dhcp6_startconfirm(ifp);
+ return;
+ }
+ }
+ dhcp6_startdiscover(ifp);
+}
+
+static struct ipv6_addr *
+dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix,
+ const struct if_sla *sla, struct if_ia *ia, struct interface *ifs)
+{
+ struct dhcp6_state *state;
+ struct in6_addr addr;
+ struct ipv6_addr *a, *ap, *apn;
+ char sabuf[INET6_ADDRSTRLEN];
+ const char *sa;
+ int pfxlen;
+
+ /* RFC6603 Section 4.2 */
+ if (strcmp(ifp->name, ifs->name) == 0) {
+ if (prefix->prefix_exclude_len == 0) {
+ /* Don't spam the log automatically */
+ if (sla)
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: DHCPv6 server does not support "
+ "OPTION_PD_EXCLUDE",
+ ifp->name);
+ return NULL;
+ }
+ pfxlen = prefix->prefix_exclude_len;
+ memcpy(&addr, &prefix->prefix_exclude, sizeof(addr));
+ } else if ((pfxlen = dhcp6_delegateaddr(&addr, ifp, prefix,
+ sla, ia)) == -1)
+ return NULL;
+
+
+ a = calloc(1, sizeof(*a));
+ if (a == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ a->iface = ifp;
+ a->flags = IPV6_AF_NEW | IPV6_AF_ONLINK;
+ a->dadcallback = dhcp6_dadcallback;
+ a->delegating_iface = ifs;
+ memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid));
+ a->created = a->acquired = prefix->acquired;
+ a->prefix_pltime = prefix->prefix_pltime;
+ a->prefix_vltime = prefix->prefix_vltime;
+ a->prefix = addr;
+ a->prefix_len = (uint8_t)pfxlen;
+
+ /* Wang a 1 at the end as the prefix could be >64
+ * making SLAAC impossible. */
+ a->addr = a->prefix;
+ a->addr.s6_addr[sizeof(a->addr.s6_addr) - 1] =
+ (uint8_t)(a->addr.s6_addr[sizeof(a->addr.s6_addr) - 1] + 1);
+
+ state = D6_STATE(ifp);
+ /* Remove any exiting address */
+ TAILQ_FOREACH_SAFE(ap, &state->addrs, next, apn) {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, &a->addr)) {
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ /* Keep our flags */
+ a->flags |= ap->flags;
+ a->flags &= ~IPV6_AF_NEW;
+ a->created = ap->created;
+ ipv6_freeaddr(ap);
+ }
+ }
+
+ sa = inet_ntop(AF_INET6, &a->addr, sabuf, sizeof(sabuf));
+ snprintf(a->saddr, sizeof(a->saddr), "%s/%d", sa, a->prefix_len);
+ TAILQ_INSERT_TAIL(&state->addrs, a, next);
+ return a;
+}
+
+static void
+dhcp6_script_try_run(struct interface *ifp, int delegated)
+{
+ struct dhcp6_state *state;
+ struct ipv6_addr *ap;
+ int completed;
+
+ state = D6_STATE(ifp);
+ completed = 1;
+ /* If all addresses have completed DAD run the script */
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (!(ap->flags & IPV6_AF_ADDED))
+ continue;
+ if (ap->flags & IPV6_AF_ONLINK) {
+ if (!(ap->flags & IPV6_AF_DADCOMPLETED) &&
+ ipv6_iffindaddr(ap->iface, &ap->addr))
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0 &&
+ ((delegated && ap->delegating_iface) ||
+ (!delegated && !ap->delegating_iface)))
+ {
+ completed = 0;
+ break;
+ }
+ }
+ }
+ if (completed) {
+ script_runreason(ifp, delegated ? "DELEGATED6" : state->reason);
+ if (!delegated)
+ dhcpcd_daemonise(ifp->ctx);
+ } else
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: waiting for DHCPv6 DAD to complete", ifp->name);
+}
+
+static void
+dhcp6_delegate_prefix(struct interface *ifp)
+{
+ struct if_options *ifo;
+ struct dhcp6_state *state, *ifd_state;
+ struct ipv6_addr *ap;
+ size_t i, j, k;
+ struct if_ia *ia;
+ struct if_sla *sla;
+ struct interface *ifd;
+ uint8_t carrier_warned, abrt;
+
+ ifo = ifp->options;
+ state = D6_STATE(ifp);
+
+ /* Try to load configured interfaces for delegation that do not exist */
+ for (i = 0; i < ifo->ia_len; i++) {
+ ia = &ifo->ia[i];
+ for (j = 0; j < ia->sla_len; j++) {
+ sla = &ia->sla[j];
+ for (k = 0; k < i; j++)
+ if (strcmp(sla->ifname, ia->sla[j].ifname) == 0)
+ break;
+ if (j >= i &&
+ if_find(ifp->ctx->ifaces, sla->ifname) == NULL)
+ {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: loading for delegation", sla->ifname);
+ if (dhcpcd_handleinterface(ifp->ctx, 2,
+ sla->ifname) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: interface does not exist"
+ " for delegation",
+ sla->ifname);
+ }
+ }
+ }
+
+ TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) {
+ k = 0;
+ carrier_warned = abrt = 0;
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
+ continue;
+ if (ap->flags & IPV6_AF_NEW) {
+ ap->flags &= ~IPV6_AF_NEW;
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: delegated prefix %s",
+ ifp->name, ap->saddr);
+ }
+ for (i = 0; i < ifo->ia_len; i++) {
+ ia = &ifo->ia[i];
+ if (memcmp(ia->iaid, ap->iaid,
+ sizeof(ia->iaid)))
+ continue;
+ if (ia->sla_len == 0) {
+ /* no SLA configured, so lets
+ * automate it */
+ if (ifd->carrier != LINK_UP) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: has no carrier, cannot"
+ " delegate addresses",
+ ifd->name);
+ carrier_warned = 1;
+ break;
+ }
+ if (dhcp6_ifdelegateaddr(ifd, ap,
+ NULL, ia, ifp))
+ k++;
+ }
+ for (j = 0; j < ia->sla_len; j++) {
+ sla = &ia->sla[j];
+ if (sla->sla_set && sla->sla == 0)
+ ap->flags |=
+ IPV6_AF_DELEGATEDZERO;
+ if (strcmp(ifd->name, sla->ifname))
+ continue;
+ if (ifd->carrier != LINK_UP) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: has no carrier, cannot"
+ " delegate addresses",
+ ifd->name);
+ carrier_warned = 1;
+ break;
+ }
+ if (dhcp6_ifdelegateaddr(ifd, ap,
+ sla, ia, ifp))
+ k++;
+ }
+ if (carrier_warned ||abrt)
+ break;
+ }
+ if (carrier_warned || abrt)
+ break;
+ }
+ if (k && !carrier_warned) {
+ ifd_state = D6_STATE(ifd);
+ ipv6_addaddrs(&ifd_state->addrs);
+ if_initrt6(ifd);
+ dhcp6_script_try_run(ifd, 1);
+ }
+ }
+}
+
+static void
+dhcp6_find_delegates1(void *arg)
+{
+
+ dhcp6_find_delegates(arg);
+}
+
+size_t
+dhcp6_find_delegates(struct interface *ifp)
+{
+ struct if_options *ifo;
+ struct dhcp6_state *state;
+ struct ipv6_addr *ap;
+ size_t i, j, k;
+ struct if_ia *ia;
+ struct if_sla *sla;
+ struct interface *ifd;
+
+ k = 0;
+ TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) {
+ ifo = ifd->options;
+ state = D6_STATE(ifd);
+ if (state == NULL || state->state != DH6S_BOUND)
+ continue;
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
+ continue;
+ for (i = 0; i < ifo->ia_len; i++) {
+ ia = &ifo->ia[i];
+ if (memcmp(ia->iaid, ap->iaid,
+ sizeof(ia->iaid)))
+ continue;
+ for (j = 0; j < ia->sla_len; j++) {
+ sla = &ia->sla[j];
+ if (strcmp(ifp->name, sla->ifname))
+ continue;
+ if (ipv6_linklocal(ifp) == NULL) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: delaying adding"
+ " delegated addresses for"
+ " LL address",
+ ifp->name);
+ ipv6_addlinklocalcallback(ifp,
+ dhcp6_find_delegates1, ifp);
+ return 1;
+ }
+ if (dhcp6_ifdelegateaddr(ifp, ap,
+ sla, ia, ifd))
+ k++;
+ }
+ }
+ }
+ }
+
+ if (k) {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: adding delegated prefixes", ifp->name);
+ state = D6_STATE(ifp);
+ state->state = DH6S_DELEGATED;
+ ipv6_addaddrs(&state->addrs);
+ if_initrt6(ifp);
+ ipv6_buildroutes(ifp->ctx);
+ dhcp6_script_try_run(ifp, 1);
+ }
+ return k;
+}
+
+/* ARGSUSED */
+static void
+dhcp6_handledata(void *arg)
+{
+ struct dhcpcd_ctx *dctx;
+ struct ipv6_ctx *ctx;
+ size_t i, len;
+ ssize_t bytes;
+ struct cmsghdr *cm;
+ struct in6_pktinfo pkt;
+ struct interface *ifp;
+ const char *op;
+ struct dhcp6_message *r;
+ struct dhcp6_state *state;
+ const struct dhcp6_option *o, *auth;
+ const struct dhcp_opt *opt;
+ const struct if_options *ifo;
+ struct ipv6_addr *ap;
+ uint8_t has_new;
+ int error;
+ uint32_t u32;
+
+ dctx = arg;
+ ctx = dctx->ipv6;
+ ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ bytes = recvmsg(ctx->dhcp_fd, &ctx->rcvhdr, 0);
+ if (bytes == -1) {
+ logger(dctx, LOG_ERR, "%s: recvmsg: %m", __func__);
+ close(ctx->dhcp_fd);
+ eloop_event_delete(dctx->eloop, ctx->dhcp_fd);
+ ctx->dhcp_fd = -1;
+ return;
+ }
+ len = (size_t)bytes;
+ ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr,
+ ctx->ntopbuf, sizeof(ctx->ntopbuf));
+ if (len < sizeof(struct dhcp6_message)) {
+ logger(dctx, LOG_ERR,
+ "DHCPv6 packet too short from %s", ctx->sfrom);
+ return;
+ }
+
+ pkt.ipi6_ifindex = 0;
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&ctx->rcvhdr);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&ctx->rcvhdr, cm))
+ {
+ if (cm->cmsg_level != IPPROTO_IPV6)
+ continue;
+ switch(cm->cmsg_type) {
+ case IPV6_PKTINFO:
+ if (cm->cmsg_len == CMSG_LEN(sizeof(pkt)))
+ memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt));
+ break;
+ }
+ }
+ if (pkt.ipi6_ifindex == 0) {
+ logger(dctx, LOG_ERR,
+ "DHCPv6 reply did not contain index from %s", ctx->sfrom);
+ return;
+ }
+
+ TAILQ_FOREACH(ifp, dctx->ifaces, next) {
+ if (ifp->index == (unsigned int)pkt.ipi6_ifindex)
+ break;
+ }
+ if (ifp == NULL) {
+ logger(dctx, LOG_DEBUG,
+ "DHCPv6 reply for unexpected interface from %s",
+ ctx->sfrom);
+ return;
+ }
+
+ state = D6_STATE(ifp);
+ if (state == NULL || state->send == NULL) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: DHCPv6 reply received but not running", ifp->name);
+ return;
+ }
+
+ r = (struct dhcp6_message *)ctx->rcvhdr.msg_iov[0].iov_base;
+ /* We're already bound and this message is for another machine */
+ /* XXX DELEGATED? */
+ if (r->type != DHCP6_RECONFIGURE &&
+ (state->state == DH6S_BOUND || state->state == DH6S_INFORMED))
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: DHCPv6 reply received but already bound", ifp->name);
+ return;
+ }
+
+ if (r->type != DHCP6_RECONFIGURE &&
+ (r->xid[0] != state->send->xid[0] ||
+ r->xid[1] != state->send->xid[1] ||
+ r->xid[2] != state->send->xid[2]))
+ {
+ logger(dctx, LOG_DEBUG,
+ "%s: wrong xid 0x%02x%02x%02x"
+ " (expecting 0x%02x%02x%02x) from %s",
+ ifp->name,
+ r->xid[0], r->xid[1], r->xid[2],
+ state->send->xid[0], state->send->xid[1],
+ state->send->xid[2],
+ ctx->sfrom);
+ return;
+ }
+
+ if (dhcp6_getmoption(D6_OPTION_SERVERID, r, len) == NULL) {
+ logger(ifp->ctx, LOG_DEBUG, "%s: no DHCPv6 server ID from %s",
+ ifp->name, ctx->sfrom);
+ return;
+ }
+
+ o = dhcp6_getmoption(D6_OPTION_CLIENTID, r, len);
+ if (o == NULL || ntohs(o->len) != dctx->duid_len ||
+ memcmp(D6_COPTION_DATA(o), dctx->duid, dctx->duid_len) != 0)
+ {
+ logger(ifp->ctx, LOG_DEBUG, "%s: incorrect client ID from %s",
+ ifp->name, ctx->sfrom);
+ return;
+ }
+
+ ifo = ifp->options;
+ for (i = 0, opt = dctx->dhcp6_opts;
+ i < dctx->dhcp6_opts_len;
+ i++, opt++)
+ {
+ if (has_option_mask(ifo->requiremask6, opt->option) &&
+ dhcp6_getmoption((uint16_t)opt->option, r, len) == NULL)
+ {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: reject DHCPv6 (no option %s) from %s",
+ ifp->name, opt->var, ctx->sfrom);
+ return;
+ }
+ if (has_option_mask(ifo->rejectmask6, opt->option) &&
+ dhcp6_getmoption((uint16_t)opt->option, r, len))
+ {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: reject DHCPv6 (option %s) from %s",
+ ifp->name, opt->var, ctx->sfrom);
+ return;
+ }
+ }
+
+ /* Authenticate the message */
+ auth = dhcp6_getmoption(D6_OPTION_AUTH, r, len);
+ if (auth) {
+ if (dhcp_auth_validate(&state->auth, &ifo->auth,
+ (uint8_t *)r, len, 6, r->type,
+ D6_COPTION_DATA(auth), ntohs(auth->len)) == NULL)
+ {
+ logger(ifp->ctx, LOG_DEBUG, "dhcp_auth_validate: %m");
+ logger(ifp->ctx, LOG_ERR,
+ "%s: authentication failed from %s",
+ ifp->name, ctx->sfrom);
+ return;
+ }
+ if (state->auth.token)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: accepted reconfigure key", ifp->name);
+ } else if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: no authentication from %s",
+ ifp->name, ctx->sfrom);
+ return;
+ }
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: no authentication from %s", ifp->name, ctx->sfrom);
+ }
+
+ op = dhcp6_get_op(r->type);
+ switch(r->type) {
+ case DHCP6_REPLY:
+ switch(state->state) {
+ case DH6S_INFORM:
+ if (dhcp6_checkstatusok(ifp, r, NULL, len) == -1)
+ return;
+ /* RFC4242 */
+ o = dhcp6_getmoption(D6_OPTION_INFO_REFRESH_TIME,
+ r, len);
+ if (o == NULL || ntohs(o->len) != sizeof(u32))
+ state->renew = IRT_DEFAULT;
+ else {
+ memcpy(&u32, D6_COPTION_DATA(o), sizeof(u32));
+ state->renew = ntohl(u32);
+ if (state->renew < IRT_MINIMUM)
+ state->renew = IRT_MINIMUM;
+ }
+ break;
+ case DH6S_CONFIRM:
+ error = dhcp6_checkstatusok(ifp, r, NULL, len);
+ /* If we got an OK status the chances are that we
+ * didn't get the IA's returned, so preserve them
+ * from our saved response */
+ if (error == 1)
+ goto recv;
+ if (error == -1 ||
+ dhcp6_validatelease(ifp, r, len,
+ ctx->sfrom, NULL) == -1)
+ {
+ dhcp6_startdiscover(ifp);
+ return;
+ }
+ break;
+ case DH6S_DISCOVER:
+ if (has_option_mask(ifo->requestmask6,
+ D6_OPTION_RAPID_COMMIT) &&
+ dhcp6_getmoption(D6_OPTION_RAPID_COMMIT, r, len))
+ state->state = DH6S_REQUEST;
+ else
+ op = NULL;
+ case DH6S_REQUEST: /* FALLTHROUGH */
+ case DH6S_RENEW: /* FALLTHROUGH */
+ case DH6S_REBIND:
+ if (dhcp6_validatelease(ifp, r, len,
+ ctx->sfrom, NULL) == -1)
+ {
+ /* PD doesn't use CONFIRM, so REBIND could
+ * throw up an invalid prefix if we
+ * changed link */
+ if (dhcp6_hasprefixdelegation(ifp))
+ dhcp6_startdiscover(ifp);
+ return;
+ }
+ break;
+ default:
+ op = NULL;
+ }
+ break;
+ case DHCP6_ADVERTISE:
+ if (state->state != DH6S_DISCOVER) {
+ op = NULL;
+ break;
+ }
+ /* RFC7083 */
+ o = dhcp6_getmoption(D6_OPTION_SOL_MAX_RT, r, len);
+ if (o && ntohs(o->len) >= sizeof(u32)) {
+ memcpy(&u32, D6_COPTION_DATA(o), sizeof(u32));
+ u32 = ntohl(u32);
+ if (u32 >= 60 && u32 <= 86400) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: SOL_MAX_RT %llu -> %d", ifp->name,
+ (unsigned long long)state->sol_max_rt, u32);
+ state->sol_max_rt = (time_t)u32;
+ } else
+ logger(ifp->ctx, LOG_ERR,
+ "%s: invalid SOL_MAX_RT %d",
+ ifp->name, u32);
+ }
+ o = dhcp6_getmoption(D6_OPTION_INF_MAX_RT, r, len);
+ if (o && ntohs(o->len) >= sizeof(u32)) {
+ memcpy(&u32, D6_COPTION_DATA(o), sizeof(u32));
+ u32 = ntohl(u32);
+ if (u32 >= 60 && u32 <= 86400) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: INF_MAX_RT %llu -> %d",
+ ifp->name,
+ (unsigned long long)state->inf_max_rt, u32);
+ state->inf_max_rt = (time_t)u32;
+ } else
+ logger(ifp->ctx, LOG_ERR,
+ "%s: invalid INF_MAX_RT %d",
+ ifp->name, u32);
+ }
+ if (dhcp6_validatelease(ifp, r, len, ctx->sfrom, NULL) == -1)
+ return;
+ break;
+ case DHCP6_RECONFIGURE:
+ if (auth == NULL) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: unauthenticated %s from %s",
+ ifp->name, op, ctx->sfrom);
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE)
+ return;
+ }
+ logger(ifp->ctx, LOG_INFO, "%s: %s from %s",
+ ifp->name, op, ctx->sfrom);
+ o = dhcp6_getmoption(D6_OPTION_RECONF_MSG, r, len);
+ if (o == NULL) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: missing Reconfigure Message option",
+ ifp->name);
+ return;
+ }
+ if (ntohs(o->len) != 1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: missing Reconfigure Message type", ifp->name);
+ return;
+ }
+ switch(*D6_COPTION_DATA(o)) {
+ case DHCP6_RENEW:
+ if (state->state != DH6S_BOUND) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: not bound, ignoring %s",
+ ifp->name, op);
+ return;
+ }
+ eloop_timeout_delete(ifp->ctx->eloop,
+ dhcp6_startrenew, ifp);
+ dhcp6_startrenew(ifp);
+ break;
+ case DHCP6_INFORMATION_REQ:
+ if (state->state != DH6S_INFORMED) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: not informed, ignoring %s",
+ ifp->name, op);
+ return;
+ }
+ eloop_timeout_delete(ifp->ctx->eloop,
+ dhcp6_sendinform, ifp);
+ dhcp6_startinform(ifp);
+ break;
+ default:
+ logger(ifp->ctx, LOG_ERR,
+ "%s: unsupported %s type %d",
+ ifp->name, op, *D6_COPTION_DATA(o));
+ break;
+ }
+ return;
+ default:
+ logger(ifp->ctx, LOG_ERR, "%s: invalid DHCP6 type %s (%d)",
+ ifp->name, op, r->type);
+ return;
+ }
+ if (op == NULL) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: invalid state for DHCP6 type %s (%d)",
+ ifp->name, op, r->type);
+ return;
+ }
+
+ if (state->recv_len < (size_t)len) {
+ free(state->recv);
+ state->recv = malloc(len);
+ if (state->recv == NULL) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: malloc recv: %m", ifp->name);
+ return;
+ }
+ }
+ memcpy(state->recv, r, len);
+ state->recv_len = len;
+
+ switch(r->type) {
+ case DHCP6_ADVERTISE:
+ if (state->state == DH6S_REQUEST) /* rapid commit */
+ break;
+ ap = TAILQ_FIRST(&state->addrs);
+ logger(ifp->ctx, LOG_INFO, "%s: ADV %s from %s",
+ ifp->name, ap->saddr, ctx->sfrom);
+ if (ifp->ctx->options & DHCPCD_TEST)
+ break;
+ dhcp6_startrequest(ifp);
+ return;
+ }
+
+recv:
+ has_new = 0;
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->flags & IPV6_AF_NEW) {
+ has_new = 1;
+ break;
+ }
+ }
+ logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG,
+ "%s: %s received from %s", ifp->name, op, ctx->sfrom);
+
+ state->reason = NULL;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ switch(state->state) {
+ case DH6S_INFORM:
+ state->rebind = 0;
+ state->expire = ND6_INFINITE_LIFETIME;
+ state->lowpl = ND6_INFINITE_LIFETIME;
+ state->reason = "INFORM6";
+ break;
+ case DH6S_REQUEST:
+ if (state->reason == NULL)
+ state->reason = "BOUND6";
+ /* FALLTHROUGH */
+ case DH6S_RENEW:
+ if (state->reason == NULL)
+ state->reason = "RENEW6";
+ /* FALLTHROUGH */
+ case DH6S_REBIND:
+ if (state->reason == NULL)
+ state->reason = "REBIND6";
+ /* FALLTHROUGH */
+ case DH6S_CONFIRM:
+ if (state->reason == NULL)
+ state->reason = "REBOOT6";
+ if (state->renew == 0) {
+ if (state->expire == ND6_INFINITE_LIFETIME)
+ state->renew = ND6_INFINITE_LIFETIME;
+ else if (state->lowpl != ND6_INFINITE_LIFETIME)
+ state->renew = (uint32_t)(state->lowpl * 0.5);
+ }
+ if (state->rebind == 0) {
+ if (state->expire == ND6_INFINITE_LIFETIME)
+ state->rebind = ND6_INFINITE_LIFETIME;
+ else if (state->lowpl != ND6_INFINITE_LIFETIME)
+ state->rebind = (uint32_t)(state->lowpl * 0.8);
+ }
+ break;
+ default:
+ state->reason = "UNKNOWN6";
+ break;
+ }
+
+ if (state->state != DH6S_CONFIRM) {
+ free(state->old);
+ state->old = state->new;
+ state->old_len = state->new_len;
+ state->new = state->recv;
+ state->new_len = state->recv_len;
+ state->recv = NULL;
+ state->recv_len = 0;
+ }
+
+ if (ifp->ctx->options & DHCPCD_TEST)
+ script_runreason(ifp, "TEST");
+ else {
+ if (state->state == DH6S_INFORM)
+ state->state = DH6S_INFORMED;
+ else
+ state->state = DH6S_BOUND;
+ if (state->renew && state->renew != ND6_INFINITE_LIFETIME)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ (time_t)state->renew,
+ state->state == DH6S_INFORMED ?
+ dhcp6_startinform : dhcp6_startrenew, ifp);
+ if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ (time_t)state->rebind, dhcp6_startrebind, ifp);
+ if (state->expire != ND6_INFINITE_LIFETIME)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ (time_t)state->expire, dhcp6_startexpire, ifp);
+
+ ipv6nd_runignoredra(ifp);
+ ipv6_addaddrs(&state->addrs);
+ dhcp6_delegate_prefix(ifp);
+
+ if (state->state == DH6S_INFORMED)
+ logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG,
+ "%s: refresh in %"PRIu32" seconds",
+ ifp->name, state->renew);
+ else if (state->renew || state->rebind)
+ logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG,
+ "%s: renew in %"PRIu32" seconds,"
+ " rebind in %"PRIu32" seconds",
+ ifp->name, state->renew, state->rebind);
+ else if (state->expire == 0)
+ logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG,
+ "%s: will expire", ifp->name);
+ if_initrt6(ifp);
+ ipv6_buildroutes(ifp->ctx);
+ dhcp6_writelease(ifp);
+ dhcp6_script_try_run(ifp, 0);
+ }
+
+ if (ifp->ctx->options & DHCPCD_TEST ||
+ (ifp->options->options & DHCPCD_INFORM &&
+ !(ifp->ctx->options & DHCPCD_MASTER)))
+ {
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ }
+}
+
+static int
+dhcp6_open(struct dhcpcd_ctx *dctx)
+{
+ struct ipv6_ctx *ctx;
+ struct sockaddr_in6 sa;
+ int n;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin6_family = AF_INET6;
+ sa.sin6_port = htons(DHCP6_CLIENT_PORT);
+#ifdef BSD
+ sa.sin6_len = sizeof(sa);
+#endif
+
+ ctx = dctx->ipv6;
+ ctx->dhcp_fd = xsocket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ O_NONBLOCK|O_CLOEXEC);
+ if (ctx->dhcp_fd == -1)
+ return -1;
+
+ n = 1;
+ if (setsockopt(ctx->dhcp_fd, SOL_SOCKET, SO_REUSEADDR,
+ &n, sizeof(n)) == -1)
+ goto errexit;
+
+ n = 1;
+ if (setsockopt(ctx->dhcp_fd, SOL_SOCKET, SO_BROADCAST,
+ &n, sizeof(n)) == -1)
+ goto errexit;
+
+#ifdef SO_REUSEPORT
+ n = 1;
+ if (setsockopt(ctx->dhcp_fd, SOL_SOCKET, SO_REUSEPORT,
+ &n, sizeof(n)) == -1)
+ logger(dctx, LOG_WARNING, "setsockopt: SO_REUSEPORT: %m");
+#endif
+
+ if (bind(ctx->dhcp_fd, (struct sockaddr *)&sa, sizeof(sa)) == -1)
+ goto errexit;
+
+ n = 1;
+ if (setsockopt(ctx->dhcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &n, sizeof(n)) == -1)
+ goto errexit;
+
+ eloop_event_add(dctx->eloop, ctx->dhcp_fd,
+ dhcp6_handledata, dctx, NULL, NULL);
+ return 0;
+
+errexit:
+ close(ctx->dhcp_fd);
+ ctx->dhcp_fd = -1;
+ return -1;
+}
+
+static void
+dhcp6_start1(void *arg)
+{
+ struct interface *ifp = arg;
+ struct if_options *ifo = ifp->options;
+ struct dhcp6_state *state;
+ size_t i;
+ const struct dhcp_compat *dhc;
+
+ state = D6_STATE(ifp);
+ /* If no DHCPv6 options are configured,
+ match configured DHCPv4 options to DHCPv6 equivalents. */
+ for (i = 0; i < sizeof(ifo->requestmask6); i++) {
+ if (ifo->requestmask6[i] != '\0')
+ break;
+ }
+ if (i == sizeof(ifo->requestmask6)) {
+ for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) {
+ if (has_option_mask(ifo->requestmask, dhc->dhcp_opt))
+ add_option_mask(ifo->requestmask6,
+ dhc->dhcp6_opt);
+ }
+ if (ifo->fqdn != FQDN_DISABLE ||
+ ifo->options & DHCPCD_HOSTNAME)
+ add_option_mask(ifo->requestmask6, D6_OPTION_FQDN);
+ }
+
+ /* Rapid commit won't work with Prefix Delegation Exclusion */
+ if (dhcp6_findselfsla(ifp, NULL))
+ del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT);
+
+ if (state->state == DH6S_INFORM) {
+ add_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
+ dhcp6_startinform(ifp);
+ } else {
+ del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
+ dhcp6_startinit(ifp);
+ }
+}
+
+int
+dhcp6_start(struct interface *ifp, enum DH6S init_state)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ if (state) {
+ if (state->state == DH6S_INFORMED &&
+ init_state == DH6S_INFORM)
+ {
+ dhcp6_startinform(ifp);
+ return 0;
+ }
+ if (init_state == DH6S_INIT &&
+ ifp->options->options & DHCPCD_DHCP6 &&
+ (state->state == DH6S_INFORM ||
+ state->state == DH6S_INFORMED ||
+ state->state == DH6S_DELEGATED))
+ {
+ /* Change from stateless to stateful */
+ goto gogogo;
+ }
+ /* We're already running DHCP6 */
+ /* XXX: What if the managed flag vanishes from all RA? */
+ return 0;
+ }
+
+ if (!(ifp->options->options & DHCPCD_DHCP6))
+ return 0;
+
+ if (ifp->ctx->ipv6->dhcp_fd == -1 && dhcp6_open(ifp->ctx) == -1)
+ return -1;
+
+ ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state));
+ state = D6_STATE(ifp);
+ if (state == NULL)
+ return -1;
+
+ state->sol_max_rt = SOL_MAX_RT;
+ state->inf_max_rt = INF_MAX_RT;
+ TAILQ_INIT(&state->addrs);
+
+gogogo:
+ state->state = init_state;
+ dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
+ AF_INET6, ifp);
+ if (ipv6_linklocal(ifp) == NULL) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: delaying DHCPv6 soliciation for LL address",
+ ifp->name);
+ ipv6_addlinklocalcallback(ifp, dhcp6_start1, ifp);
+ return 0;
+ }
+
+ dhcp6_start1(ifp);
+ return 0;
+}
+
+void
+dhcp6_reboot(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ if (state) {
+ switch (state->state) {
+ case DH6S_BOUND:
+ dhcp6_startrebind(ifp);
+ break;
+ case DH6S_INFORMED:
+ dhcp6_startinform(ifp);
+ break;
+ default:
+ dhcp6_startdiscover(ifp);
+ break;
+ }
+ }
+}
+
+static void
+dhcp6_freedrop(struct interface *ifp, int drop, const char *reason)
+{
+ struct dhcp6_state *state;
+ struct dhcpcd_ctx *ctx;
+ unsigned long long options;
+ int dropdele;
+
+ /*
+ * As the interface is going away from dhcpcd we need to
+ * remove the delegated addresses, otherwise we lose track
+ * of which interface is delegating as we remeber it by pointer.
+ * So if we need to change this behaviour, we need to change
+ * how we remember which interface delegated.
+ *
+ * XXX The below is no longer true due to the change of the
+ * default IAID, but do PPP links have stable ethernet
+ * addresses?
+ *
+ * To make it more interesting, on some OS's with PPP links
+ * there is no guarantee the delegating interface will have
+ * the same name or index so think very hard before changing
+ * this.
+ */
+ if (ifp->options)
+ options = ifp->options->options;
+ else
+ options = 0;
+ dropdele = (options & (DHCPCD_STOPPING | DHCPCD_RELEASE) &&
+ (options & DHCPCD_NODROP) != DHCPCD_NODROP);
+
+ if (ifp->ctx->eloop)
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+ if (dropdele)
+ dhcp6_delete_delegates(ifp);
+
+ state = D6_STATE(ifp);
+ if (state) {
+ /* Failure to send the release may cause this function to
+ * re-enter */
+ if (state->state == DH6S_RELEASE) {
+ dhcp6_finishrelease(ifp);
+ return;
+ }
+
+ if (drop && options & DHCPCD_RELEASE) {
+ if (ifp->carrier == LINK_UP &&
+ state->state != DH6S_RELEASED)
+ {
+ dhcp6_startrelease(ifp);
+ return;
+ }
+ unlink(state->leasefile);
+ }
+ dhcp6_freedrop_addrs(ifp, drop, NULL);
+ free(state->old);
+ state->old = state->new;
+ state->old_len = state->new_len;
+ state->new = NULL;
+ state->new_len = 0;
+ if (drop && state->old &&
+ (options & DHCPCD_NODROP) != DHCPCD_NODROP)
+ {
+ if (reason == NULL)
+ reason = "STOP6";
+ script_runreason(ifp, reason);
+ }
+ free(state->old);
+ free(state->send);
+ free(state->recv);
+ free(state);
+ ifp->if_data[IF_DATA_DHCP6] = NULL;
+ }
+
+ /* If we don't have any more DHCP6 enabled interfaces,
+ * close the global socket and release resources */
+ ctx = ifp->ctx;
+ if (ctx->ifaces) {
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (D6_STATE(ifp))
+ break;
+ }
+ }
+ if (ifp == NULL && ctx->ipv6) {
+ if (ctx->ipv6->dhcp_fd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->ipv6->dhcp_fd);
+ close(ctx->ipv6->dhcp_fd);
+ ctx->ipv6->dhcp_fd = -1;
+ }
+ }
+}
+
+void
+dhcp6_drop(struct interface *ifp, const char *reason)
+{
+
+ dhcp6_freedrop(ifp, 1, reason);
+}
+
+void
+dhcp6_free(struct interface *ifp)
+{
+
+ dhcp6_freedrop(ifp, 0, NULL);
+}
+
+void
+dhcp6_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname,
+ const struct in6_addr *addr, int flags)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+
+ if (ctx->ifaces == NULL)
+ return;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ state = D6_STATE(ifp);
+ if (state == NULL || strcmp(ifp->name, ifname))
+ continue;
+ ipv6_handleifa_addrs(cmd, &state->addrs, addr, flags);
+ }
+
+}
+
+ssize_t
+dhcp6_env(char **env, const char *prefix, const struct interface *ifp,
+ const struct dhcp6_message *m, size_t len)
+{
+ const struct if_options *ifo;
+ struct dhcp_opt *opt, *vo;
+ const struct dhcp6_option *o;
+ size_t i, n;
+ uint16_t ol, oc;
+ char *pfx;
+ uint32_t en;
+ const struct dhcpcd_ctx *ctx;
+ const struct dhcp6_state *state;
+ const struct ipv6_addr *ap;
+ char *v, *val;
+
+ n = 0;
+ if (m == NULL)
+ goto delegated;
+
+ if (len < sizeof(*m)) {
+ /* Should be impossible with guards at packet in
+ * and reading leases */
+ errno = EINVAL;
+ return -1;
+ }
+
+ ifo = ifp->options;
+ ctx = ifp->ctx;
+
+ /* Zero our indexes */
+ if (env) {
+ for (i = 0, opt = ctx->dhcp6_opts;
+ i < ctx->dhcp6_opts_len;
+ i++, opt++)
+ dhcp_zero_index(opt);
+ for (i = 0, opt = ifp->options->dhcp6_override;
+ i < ifp->options->dhcp6_override_len;
+ i++, opt++)
+ dhcp_zero_index(opt);
+ for (i = 0, opt = ctx->vivso;
+ i < ctx->vivso_len;
+ i++, opt++)
+ dhcp_zero_index(opt);
+ i = strlen(prefix) + strlen("_dhcp6") + 1;
+ pfx = malloc(i);
+ if (pfx == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ snprintf(pfx, i, "%s_dhcp6", prefix);
+ } else
+ pfx = NULL;
+
+ /* Unlike DHCP, DHCPv6 options *may* occur more than once.
+ * There is also no provision for option concatenation unlike DHCP. */
+ for (o = D6_CFIRST_OPTION(m);
+ len > (ssize_t)sizeof(*o);
+ o = D6_CNEXT_OPTION(o))
+ {
+ ol = ntohs(o->len);
+ if (sizeof(*o) + ol > len) {
+ errno = EINVAL;
+ break;
+ }
+ len -= sizeof(*o) + ol;
+ oc = ntohs(o->code);
+ if (has_option_mask(ifo->nomask6, oc))
+ continue;
+ for (i = 0, opt = ifo->dhcp6_override;
+ i < ifo->dhcp6_override_len;
+ i++, opt++)
+ if (opt->option == oc)
+ break;
+ if (i == ifo->dhcp6_override_len &&
+ oc == D6_OPTION_VENDOR_OPTS &&
+ ol > sizeof(en))
+ {
+ memcpy(&en, D6_COPTION_DATA(o), sizeof(en));
+ en = ntohl(en);
+ vo = vivso_find(en, ifp);
+ } else
+ vo = NULL;
+ if (i == ifo->dhcp6_override_len) {
+ for (i = 0, opt = ctx->dhcp6_opts;
+ i < ctx->dhcp6_opts_len;
+ i++, opt++)
+ if (opt->option == oc)
+ break;
+ if (i == ctx->dhcp6_opts_len)
+ opt = NULL;
+ }
+ if (opt) {
+ n += dhcp_envoption(ifp->ctx,
+ env == NULL ? NULL : &env[n],
+ pfx, ifp->name,
+ opt, dhcp6_getoption, D6_COPTION_DATA(o), ol);
+ }
+ if (vo) {
+ n += dhcp_envoption(ifp->ctx,
+ env == NULL ? NULL : &env[n],
+ pfx, ifp->name,
+ vo, dhcp6_getoption,
+ D6_COPTION_DATA(o) + sizeof(en),
+ ol - sizeof(en));
+ }
+ }
+ free(pfx);
+
+delegated:
+ /* Needed for Delegated Prefixes */
+ state = D6_CSTATE(ifp);
+ i = 0;
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->delegating_iface) {
+ i += strlen(ap->saddr) + 1;
+ }
+ }
+ if (env && i) {
+ i += strlen(prefix) + strlen("_delegated_dhcp6_prefix=");
+ v = val = env[n] = malloc(i);
+ if (v == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ v += snprintf(val, i, "%s_delegated_dhcp6_prefix=", prefix);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->delegating_iface) {
+ /* Can't use stpcpy(3) due to "security" */
+ const char *sap = ap->saddr;
+
+ do
+ *v++ = *sap;
+ while (*++sap != '\0');
+ *v++ = ' ';
+ }
+ }
+ *--v = '\0';
+ }
+ if (i)
+ n++;
+
+ return (ssize_t)n;
+}
+
+int
+dhcp6_dump(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ ifp->if_data[IF_DATA_DHCP6] = state = calloc(1, sizeof(*state));
+ if (state == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ TAILQ_INIT(&state->addrs);
+ dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
+ AF_INET6, ifp);
+ if (dhcp6_readlease(ifp, 0) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m",
+ *ifp->name ? ifp->name : state->leasefile, __func__);
+ return -1;
+ }
+ state->reason = "DUMP6";
+ return script_runreason(ifp, state->reason);
+}
--- /dev/null
+/* $NetBSD: dhcp6.h,v 1.10 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef DHCP6_H
+#define DHCP6_H
+
+#include "dhcpcd.h"
+
+#define IN6ADDR_LINKLOCAL_ALLDHCP_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 }}}
+
+/* UDP port numbers for DHCP */
+#define DHCP6_CLIENT_PORT 546
+#define DHCP6_SERVER_PORT 547
+
+/* DHCP message type */
+#define DHCP6_SOLICIT 1
+#define DHCP6_ADVERTISE 2
+#define DHCP6_REQUEST 3
+#define DHCP6_CONFIRM 4
+#define DHCP6_RENEW 5
+#define DHCP6_REBIND 6
+#define DHCP6_REPLY 7
+#define DHCP6_RELEASE 8
+#define DHCP6_DECLINE 9
+#define DHCP6_RECONFIGURE 10
+#define DHCP6_INFORMATION_REQ 11
+#define DHCP6_RELAY_FLOW 12
+#define DHCP6_RELAY_REPL 13
+#define DHCP6_RECONFIGURE_REQ 18
+#define DHCP6_RECONFIGURE_REPLY 19
+
+#define D6_OPTION_CLIENTID 1
+#define D6_OPTION_SERVERID 2
+#define D6_OPTION_IA_NA 3
+#define D6_OPTION_IA_TA 4
+#define D6_OPTION_ORO 6
+#define D6_OPTION_IA_ADDR 5
+#define D6_OPTION_PREFERENCE 7
+#define D6_OPTION_ELAPSED 8
+#define D6_OPTION_AUTH 11
+#define D6_OPTION_UNICAST 12
+#define D6_OPTION_STATUS_CODE 13
+#define D6_OPTION_RAPID_COMMIT 14
+#define D6_OPTION_VENDOR_CLASS 16
+#define D6_OPTION_VENDOR_OPTS 17
+#define D6_OPTION_INTERFACE_ID 18
+#define D6_OPTION_RECONF_MSG 19
+#define D6_OPTION_RECONF_ACCEPT 20
+#define D6_OPTION_SIP_SERVERS_NAME 21
+#define D6_OPTION_SIP_SERVERS_ADDRESS 22
+#define D6_OPTION_DNS_SERVERS 23
+#define D6_OPTION_DOMAIN_LIST 24
+#define D6_OPTION_IA_PD 25
+#define D6_OPTION_IAPREFIX 26
+#define D6_OPTION_NIS_SERVERS 27
+#define D6_OPTION_NISP_SERVERS 28
+#define D6_OPTION_NIS_DOMAIN_NAME 29
+#define D6_OPTION_NISP_DOMAIN_NAME 30
+#define D6_OPTION_SNTP_SERVERS 31
+#define D6_OPTION_INFO_REFRESH_TIME 32
+#define D6_OPTION_BCMS_SERVER_D 33
+#define D6_OPTION_BCMS_SERVER_A 34
+#define D6_OPTION_FQDN 39
+#define D6_OPTION_POSIX_TIMEZONE 41
+#define D6_OPTION_TZDB_TIMEZONE 42
+#define D6_OPTION_PD_EXCLUDE 67
+#define D6_OPTION_SOL_MAX_RT 82
+#define D6_OPTION_INF_MAX_RT 83
+
+#define D6_FQDN_PTR 0x00
+#define D6_FQDN_BOTH 0x01
+#define D6_FQDN_NONE 0x04
+
+#include "dhcp.h"
+#include "ipv6.h"
+
+struct dhcp6_message {
+ uint8_t type;
+ uint8_t xid[3];
+ /* followed by options */
+} __packed;
+
+struct dhcp6_option {
+ uint16_t code;
+ uint16_t len;
+ /* followed by data */
+} __packed;
+
+#define D6_STATUS_OK 0
+#define D6_STATUS_FAIL 1
+#define D6_STATUS_NOADDR 2
+#define D6_STATUS_NOBINDING 3
+#define D6_STATUS_NOTONLINK 4
+#define D6_STATUS_USEMULTICAST 5
+
+#define SOL_MAX_DELAY 1
+#define SOL_TIMEOUT 1
+#define SOL_MAX_RT 3600 /* RFC7083 */
+#define REQ_TIMEOUT 1
+#define REQ_MAX_RT 30
+#define REQ_MAX_RC 10
+#define CNF_MAX_DELAY 1
+#define CNF_TIMEOUT 1
+#define CNF_MAX_RT 4
+#define CNF_MAX_RD 10
+#define REN_TIMEOUT 10
+#define REN_MAX_RT 600
+#define REB_TIMEOUT 10
+#define REB_MAX_RT 600
+#define INF_MAX_DELAY 1
+#define INF_TIMEOUT 1
+#define INF_MAX_RT 3600 /* RFC7083 */
+#define REL_TIMEOUT 1
+#define REL_MAX_RC 5
+#define DEC_TIMEOUT 1
+#define DEC_MAX_RC 5
+#define REC_TIMEOUT 2
+#define REC_MAX_RC 8
+#define HOP_COUNT_LIMIT 32
+
+/* RFC4242 3.1 */
+#define IRT_DEFAULT 86400
+#define IRT_MINIMUM 600
+
+#define DHCP6_RAND_MIN -100
+#define DHCP6_RAND_MAX 100
+
+enum DH6S {
+ DH6S_INIT,
+ DH6S_DISCOVER,
+ DH6S_REQUEST,
+ DH6S_BOUND,
+ DH6S_RENEW,
+ DH6S_REBIND,
+ DH6S_CONFIRM,
+ DH6S_INFORM,
+ DH6S_INFORMED,
+ DH6S_RENEW_REQUESTED,
+ DH6S_PROBE,
+ DH6S_DELEGATED,
+ DH6S_RELEASE,
+ DH6S_RELEASED
+};
+
+struct dhcp6_state {
+ enum DH6S state;
+ struct timespec started;
+
+ /* Message retransmission timings */
+ struct timespec RT;
+ unsigned int IMD;
+ unsigned int RTC;
+ time_t IRT;
+ unsigned int MRC;
+ time_t MRT;
+ void (*MRCcallback)(void *);
+ time_t sol_max_rt;
+ time_t inf_max_rt;
+
+ struct dhcp6_message *send;
+ size_t send_len;
+ struct dhcp6_message *recv;
+ size_t recv_len;
+ struct dhcp6_message *new;
+ size_t new_len;
+ struct dhcp6_message *old;
+ size_t old_len;
+
+ uint32_t renew;
+ uint32_t rebind;
+ uint32_t expire;
+ struct in6_addr unicast;
+ struct ipv6_addrhead addrs;
+ uint32_t lowpl;
+ /* The +3 is for the possible .pd extension for prefix delegation */
+ char leasefile[sizeof(LEASEFILE6) + IF_NAMESIZE + (IF_SSIDSIZE * 4) +3];
+ const char *reason;
+
+ struct authstate auth;
+};
+
+#define D6_STATE(ifp) \
+ ((struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6])
+#define D6_CSTATE(ifp) \
+ ((const struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6])
+#define D6_STATE_RUNNING(ifp) \
+ (D6_CSTATE((ifp)) && \
+ D6_CSTATE((ifp))->reason && dhcp6_dadcompleted((ifp)))
+
+#define D6_FIRST_OPTION(m) \
+ ((struct dhcp6_option *) \
+ ((uint8_t *)(m) + sizeof(struct dhcp6_message)))
+#define D6_NEXT_OPTION(o) \
+ ((struct dhcp6_option *) \
+ (((uint8_t *)o) + sizeof(struct dhcp6_option) + ntohs((o)->len)))
+#define D6_OPTION_DATA(o) \
+ ((uint8_t *)(o) + sizeof(struct dhcp6_option))
+#define D6_CFIRST_OPTION(m) \
+ ((const struct dhcp6_option *) \
+ ((const uint8_t *)(m) + sizeof(struct dhcp6_message)))
+#define D6_CNEXT_OPTION(o) \
+ ((const struct dhcp6_option *) \
+ (((const uint8_t *)o) + sizeof(struct dhcp6_option) + ntohs((o)->len)))
+#define D6_COPTION_DATA(o) \
+ ((const uint8_t *)(o) + sizeof(struct dhcp6_option))
+
+#ifdef INET6
+void dhcp6_printoptions(const struct dhcpcd_ctx *,
+ const struct dhcp_opt *, size_t);
+const struct ipv6_addr *dhcp6_iffindaddr(const struct interface *ifp,
+ const struct in6_addr *addr, short flags);
+struct ipv6_addr *dhcp6_findaddr(struct dhcpcd_ctx *, const struct in6_addr *,
+ short);
+size_t dhcp6_find_delegates(struct interface *);
+int dhcp6_has_public_addr(const struct interface *);
+int dhcp6_start(struct interface *, enum DH6S);
+void dhcp6_reboot(struct interface *);
+ssize_t dhcp6_env(char **, const char *, const struct interface *,
+ const struct dhcp6_message *, size_t);
+void dhcp6_free(struct interface *);
+void dhcp6_handleifa(struct dhcpcd_ctx *, int, const char *,
+ const struct in6_addr *addr, int);
+int dhcp6_dadcompleted(const struct interface *);
+void dhcp6_drop(struct interface *, const char *);
+int dhcp6_dump(struct interface *);
+#else
+#define dhcp6_find_delegates(a) {}
+#define dhcp6_start(a, b) (0)
+#define dhcp6_reboot(a) {}
+#define dhcp6_env(a, b, c, d, e) {}
+#define dhcp6_free(a) {}
+#define dhcp6_dadcompleted(a) (0)
+#define dhcp6_drop(a, b) {}
+#define dhcp6_dump(a) (-1)
+#endif
+
+#endif
--- /dev/null
+# $NetBSD: dhcpcd-definitions.conf,v 1.9 2015/07/09 10:15:34 roy Exp $
+
+# Copyright (c) 2006-2015 Roy Marples
+# All rights reserved
+
+# DHCP option definitions for dhcpcd(8)
+# These are used to translate DHCP options into shell variables
+# for use in dhcpcd-run-hooks(8)
+# See dhcpcd.conf(5) for details
+
+##############################################################################
+# DHCP RFC2132 options unless otheriwse stated
+define 1 request ipaddress subnet_mask
+# RFC3442 states that the CSR has to come before all other routes
+# For completeness we also specify static routes then routers
+define 121 rfc3442 classless_static_routes
+# Option 249 is an IANA assigned private number used by Windows DHCP servers
+# to provide the exact same information as option 121, classless static routes
+define 249 rfc3442 ms_classless_static_routes
+define 33 request array ipaddress static_routes
+define 3 request array ipaddress routers
+define 2 uint32 time_offset
+define 4 array ipaddress time_servers
+define 5 array ipaddress ien116_name_servers
+define 6 array ipaddress domain_name_servers
+define 7 array ipaddress log_servers
+define 8 array ipaddress cookie_servers
+define 9 array ipaddress lpr_servers
+define 10 array ipaddress impress_servers
+define 11 array ipaddress resource_location_servers
+define 12 dname host_name
+define 13 uint16 boot_size
+define 14 string merit_dump
+# Technically domain_name is not an array, but many servers expect clients
+# to treat it as one.
+define 15 array dname domain_name
+define 16 ipaddress swap_server
+define 17 string root_path
+define 18 string extensions_path
+define 19 byte ip_forwarding
+define 20 byte non_local_source_routing
+define 21 array ipaddress policy_filter
+define 22 int16 max_dgram_reassembly
+define 23 uint16 default_ip_ttl
+define 24 uint32 path_mtu_aging_timeout
+define 25 array uint16 path_mtu_plateau_table
+define 26 uint16 interface_mtu
+define 27 byte all_subnets_local
+define 28 request ipaddress broadcast_address
+define 29 byte perform_mask_discovery
+define 30 byte mask_supplier
+define 31 byte router_discovery
+define 32 ipaddress router_solicitation_address
+define 34 byte trailer_encapsulation
+define 35 uint32 arp_cache_timeout
+define 36 uint16 ieee802_3_encapsulation
+define 37 byte default_tcp_ttl
+define 38 uint32 tcp_keepalive_interval
+define 39 byte tcp_keepalive_garbage
+define 40 string nis_domain
+define 41 array ipaddress nis_servers
+define 42 array ipaddress ntp_servers
+define 43 binhex vendor_encapsulated_options
+define 44 array ipaddress netbios_name_servers
+define 45 ipaddress netbios_dd_server
+define 46 byte netbios_node_type
+define 47 string netbios_scope
+define 48 array ipaddress font_servers
+define 49 array ipaddress x_display_manager
+define 50 ipaddress dhcp_requested_address
+define 51 request uint32 dhcp_lease_time
+define 52 byte dhcp_option_overload
+define 53 byte dhcp_message_type
+define 54 ipaddress dhcp_server_identifier
+define 55 array byte dhcp_parameter_request_list
+define 56 string dhcp_message
+define 57 uint16 dhcp_max_message_size
+define 58 request uint32 dhcp_renewal_time
+define 59 request uint32 dhcp_rebinding_time
+define 60 string vendor_class_identifier
+define 61 binhex dhcp_client_identifier
+define 64 string nisplus_domain
+define 65 array ipaddress nisplus_servers
+define 66 dname tftp_server_name
+define 67 string bootfile_name
+define 68 array ipaddress mobile_ip_home_agent
+define 69 array ipaddress smtp_server
+define 70 array ipaddress pop_server
+define 71 array ipaddress nntp_server
+define 72 array ipaddress www_server
+define 73 array ipaddress finger_server
+define 74 array ipaddress irc_server
+define 75 array ipaddress streettalk_server
+define 76 array ipaddress streettalk_directory_assistance_server
+
+# DHCP User Class, RFC3004
+define 77 binhex user_class
+
+# DHCP SLP Directory Agent, RFC2610
+define 78 embed slp_agent
+embed byte mandatory
+embed array ipaddress address
+define 79 embed slp_service
+embed byte mandatory
+embed ascii scope_list
+
+# DHCP Rapid Commit, RFC4039
+define 80 norequest flag rapid_commit
+
+# DHCP Fully Qualified Domain Name, RFC4702
+define 81 embed fqdn
+embed bitflags=0000NEOS flags
+embed byte rcode1
+embed byte rcode2
+# dhcpcd always sets the E bit which means the fqdn itself is always
+# RFC1035 encoded.
+# The server MUST use the encoding as specified by the client as noted
+# in RFC4702 Section 2.1.
+embed domain fqdn
+
+# Option 82 is for Relay Agents and DHCP servers
+
+# Options 83 ad 84 are unused, RFC3679
+
+# DHCP Novell Directory Services, RFC2241
+define 85 array ipaddress nds_servers
+define 86 raw nds_tree_name
+define 87 raw nds_context
+
+# DHCP Broadcast and Multicast Control Server, RFC4280
+define 88 array domain bcms_controller_names
+define 89 array ipaddress bcms_controller_address
+
+# DHCP Authentication, RFC3118
+define 90 embed auth
+embed byte protocol
+embed byte algorithm
+embed byte rdm
+embed binhex:8 replay
+embed binhex information
+
+# DHCP Leasequery, RFC4388
+define 91 uint32 client_last_transaction_time
+define 92 array ipaddress associated_ip
+
+# DHCP Options for Intel Preboot eXecution Environent (PXE), RFC4578
+# Options 93, 94 and 97 are used but of no use to dhcpcd
+
+# Option 95 used by Apple but never published RFC3679
+# Option 96 is unused, RFC3679
+
+# DHCP The Open Group's User Authentication Protocol, RFC2485
+define 98 string uap_servers
+
+# DHCP Civic Addresses Configuration Information, RFC4776
+define 99 encap geoconf_civic
+embed byte what
+embed uint16 country_code
+# The rest of this option is not supported
+
+# DHCP Timezone, RFC4883
+define 100 string posix_timezone
+define 101 string tzdb_timezone
+
+# Options 102-115 are unused, RFC3679
+
+# DHCP Auto-Configuration, RFC2563
+define 116 byte auto_configure
+
+# DHCP Name Service Search, RFC2937
+define 117 array uint16 name_service_search
+
+# DHCP Subnet Selection, RFC3011
+define 118 ipaddress subnet_selection
+
+# DHCP Domain Search, RFC3397
+define 119 array domain domain_search
+
+# DHCP Session Initiated Protocol Servers, RFC3361
+define 120 rfc3361 sip_server
+
+# Option 121 is defined at the top of this file
+
+# DHCP CableLabs Client, RFC3495
+define 122 encap tsp
+encap 1 ipaddress dhcp_server
+encap 2 ipaddress dhcp_secondary_server
+encap 3 rfc3361 provisioning_server
+encap 4 embed as_req_as_rep_backoff
+embed uint32 nominal
+embed uint32 maximum
+embed uint32 retry
+encap 5 embed ap_req_ap_rep_backoff
+embed uint32 nominal
+embed uint32 maximum
+embed uint32 retry
+encap 6 domain kerberos_realm
+encap 7 byte ticket_granting_server_utilization
+encap 8 byte provisioning_timer
+
+# DHCP Coordinate LCI, RFC6225
+# We have no means of expressing 6 bit lengths
+define 123 binhex geoconf
+
+# DHCP Vendor-Identifying Vendor Options, RFC3925
+define 124 binhex vivco
+define 125 embed vivso
+embed uint32 enterprise_number
+# Vendor options are shared between DHCP/DHCPv6
+# Their code is matched to the enterprise number defined above
+# see the end of this file for an example
+
+# Options 126 and 127 are unused, RFC3679
+
+# DHCP Options for Intel Preboot eXecution Environent (PXE), RFC4578
+# Options 128-135 are used but of no use to dhcpcd
+
+# DHCP PANA Authentication Agent, RFC5192
+define 136 array ipaddress pana_agent
+
+# DHCP Lost Server, RFC5223
+define 137 domain lost_server
+
+# DHCP CAPWAP, RFC5417
+define 138 array ipaddress capwap_ac
+
+# DHCP Mobility Services, RFC5678
+define 139 encap mos_ip
+encap 1 array ipaddress is
+encap 2 array ipaddress cs
+encap 3 array ipaddress es
+define 140 encap mos_domain
+encap 1 domain is
+encap 2 domain cs
+encap 3 domain es
+
+# DHCP SIP UA, RFC6011
+define 141 array domain sip_ua_cs_list
+
+# DHCP ANDSF, RFC6153
+define 142 array ipaddress andsf
+define 143 array ip6address andsf6
+
+# DHCP Coordinate LCI, RFC6225
+# We have no means of expressing 6 bit lengths
+define 144 binhex geoloc
+
+# DHCP FORCERENEW Nonce Capability, RFC6704
+define 145 array byte forcerenew_nonce_capable
+
+# DHCP RDNSS Selection for MIF Nodes, RFC6731
+define 146 embed rdnss_selection
+embed byte prf
+embed ipaddress primary
+embed ipaddress secondary
+embed array domain domains
+
+# Options 147, 148 and 149 are unused, RFC3942
+
+# DHCP TFTP Server Address, RFC5859
+define 150 array ipaddress tftp_servers
+
+# Options 151-157 are used for Lease Query, RFC6926 and not for dhcpcd
+# Options 158-174 are unused, RFC3942
+# Options 175-177 are tentativel assigned for Etherboot
+# Options 178-207 are unused, RFC3942
+
+# DHCP PXELINUX, RFC5071
+define 208 binhex pxelinux_magic
+define 209 string config_file
+define 210 string path_prefix
+define 211 uint32 reboot_time
+
+# DHCP IPv6 Rapid Deployment on IPv4 Infrastructures, RFC5969
+define 212 rfc5969 sixrd
+
+# DHCP Access Network Domain Name, RFC5986
+define 213 domain access_domain
+
+# Options 214-219 are unused, RFC3942
+
+# DHCP Subnet Allocation, RFC6656
+# Option 220 looks specific to Cisco hardware.
+
+# DHCP Virtual Subnet Selection, RFC6607
+define 221 encap vss
+encap 0 string nvt
+encap 1 binhex vpn_id
+encap 255 flag global
+
+# Options 222 and 223 are unused, RFC3942
+
+# Options 224-254 are reserved for Private Use
+# However, an expired RFC for Web Proxy Auto Discovery Protocol does define
+# Option 252 which is commonly used by major browsers.
+# Apparently the code was assigned by agreement of the DHC working group chair.
+define 252 string wpad_url
+
+# Option 255 End
+
+##############################################################################
+# ND6 options, RFC4861
+definend 1 binhex source_address
+definend 2 binhex target_address
+
+definend 3 index embed prefix_information
+embed byte length
+embed bitflags=LA flags
+embed uint32 vltime
+embed uint32 pltime
+embed uint32 reserved
+embed array ip6address prefix
+
+# option 4 is only for Redirect messages
+
+definend 5 embed mtu
+embed uint16 reserved
+embed uint32 mtu
+
+# ND6 options, RFC6101
+definend 25 index embed rdnss
+embed uint16 reserved
+embed uint32 lifetime
+embed array ip6address servers
+
+definend 31 index embed dnssl
+embed uint16 reserved
+embed uint32 lifetime
+embed domain search
+
+##############################################################################
+# DHCPv6 options, RFC3315
+define6 1 binhex client_id
+define6 2 binhex server_id
+
+define6 3 norequest index embed ia_na
+embed binhex:4 iaid
+embed uint32 t1
+embed uint32 t2
+encap 5 option
+encap 13 option
+
+define6 4 norequest index embed ia_ta
+embed uint32 iaid
+encap 5 option
+encap 13 option
+
+define6 5 norequest index embed ia_addr
+embed ip6address ia_addr
+embed uint32 pltime
+embed uint32 vltime
+encap 13 option
+
+define6 6 array uint16 option_request
+define6 7 byte preference
+define6 8 uint16 elased_time
+define6 9 binhex dhcp_relay_msg
+
+# Option 10 is unused
+
+define6 11 embed auth
+embed byte protocol
+embed byte algorithm
+embed byte rdm
+embed binhex:8 replay
+embed binhex information
+
+define6 12 ip6address unicast
+
+define6 13 norequest embed status_code
+embed uint16 status_code
+embed string message
+
+define6 14 norequest flag rapid_commit
+define6 15 binhex user_class
+
+define6 16 binhex vivco
+define6 17 embed vivso
+embed uint32 enterprise_number
+# Vendor options are shared between DHCP/DHCPv6
+# Their code is matched to the enterprise number defined above
+# See the end of this file for an example
+
+define6 18 binhex interface_id
+define6 19 byte reconfigure_msg
+define6 20 flag reconfigure_accept
+
+# DHCPv6 Session Initiation Protocol Options, RFC3319
+define6 21 array domain sip_servers_names
+define6 22 array ip6address sip_servers_addresses
+
+# DHCPv6 DNS Configuration Options, RFC3646
+define6 23 array ip6address name_servers
+define6 24 array domain domain_search
+
+# DHCPv6 Prefix Options, RFC6603
+define6 25 norequest index embed ia_pd
+embed binhex:4 iaid
+embed uint32 t1
+embed uint32 t2
+encap 26 option
+define6 26 index embed prefix
+embed uint32 pltime
+embed uint32 vltime
+embed byte length
+embed ip6address prefix
+encap 13 option
+encap 67 option
+
+# DHCPv6 Network Information Service Options, RFC3898
+define6 27 array ip6address nis_servers
+define6 28 array ip6address nisp_servers
+define6 29 string nis_domain_name
+define6 30 string nisp_domain_name
+
+# DHCPv6 Simple Network Time Protocol Servers Option, RFC4075
+define6 31 array ip6address sntp_servers
+
+# DHCPv6 Information Refresh Time, RFC4242
+define6 32 uint32 info_refresh_time
+
+# DHCPv6 Broadcast and Multicast Control Server, RFC4280
+define6 33 array domain bcms_server_d
+define6 34 array ip6address bcms_server_a
+
+# DHCP Civic Addresses Configuration Information, RFC4776
+define6 36 encap geoconf_civic
+embed byte what
+embed uint16 country_code
+# The rest of this option is not supported
+
+# DHCP Relay Agent Remote-ID, RFC4649
+define6 37 embed remote_id
+embed uint32 enterprise_number
+embed binhex remote_id
+
+# DHCP Relay Agent Subscriber-ID, RFC4580
+define6 38 binhex subscriber_id
+
+# DHCPv6 Fully Qualified Domain Name, RFC4704
+define6 39 embed fqdn
+embed bitflags=00000NOS flags
+embed domain fqdn
+
+# DHCPv6 PANA Authentication Agnet, RC5192
+define6 40 array ip6address pana_agent
+
+# DHCPv6 Timezone options, RFC4883
+define6 41 string posix_timezone
+define6 42 string tzdb_timezone
+
+# DHCPv6 Relay Agent Echo Request
+define6 43 array uint16 ero
+
+# Options 44-48 are used for Lease Query, RFC5007 and not for dhcpcd
+
+# DHCPv6 Home Info Discovery in MIPv6, RFC6610
+define6 49 domain mip6_hnidf
+define6 50 encap mip6_vdinf
+encap 71 option
+encap 72 option
+encap 73 option
+
+# DHCPv6 Lost Server, RFC5223
+define6 51 domain lost_server
+
+# DHCPv6 CAPWAP, RFC5417
+define6 52 array ip6address capwap_ac
+
+# DHCPv6 Relay-ID, RFC5460
+define6 53 binhex relay_id
+
+# DHCP Mobility Services, RFC5678
+define6 54 encap mos_ip
+encap 1 array ip6address is
+encap 2 array ip6address cs
+encap 3 array ip6address es
+define6 55 encap mos_domain
+encap 1 domain is
+encap 2 domain cs
+encap 3 domain es
+
+# DHCPv6 Network Time Protocol Server, RFC5908
+define6 56 encap ntp_server
+encap 1 ip6address addr
+encap 2 ip6address mcast_addr
+encap 3 ip6address fqdn
+
+# DHCPv6 LIS Discovery, RFC5986
+define6 57 domain access_domain
+
+# DHCPv6 SIP UA, RFC6011
+define6 58 array domain sip_ua_cs_list
+
+# DHCPv6 Network Boot, RFC5970
+define6 59 string bootfile_url
+# We presently cannot decode bootfile_param
+define6 60 binhex bootfile_param
+define6 61 array uint16 architecture_types
+define6 62 embed nii
+embed byte type
+embed byte major
+embed byte minor
+
+# DHCPv6 Coordinate LCI, RFC6225
+# We have no means of expressing 6 bit lengths
+define6 63 binhex geoloc
+
+# DHCPv6 AFTR-Name, RFC6334
+define6 64 domain aftr_name
+
+# DHCPv6 Prefix Exclude Option, RFC6603
+define6 67 embed pd_exclude
+embed byte prefix_len
+embed binhex subnetID
+
+# DHCPv6 Home Info Discovery in MIPv6, RFC6610
+define6 69 encap mip6_idinf
+encap 71 option
+encap 72 option
+encap 73 option
+define6 70 encap mip6_udinf
+encap 71 option
+encap 72 option
+encap 73 option
+define6 71 embed mip6_hnp
+embed byte prefix_len
+embed ip6address prefix
+define6 72 ip6address mip6_haa
+define6 73 domain mip6_haf
+
+# DHCPv6 RDNSS Selection for MIF Nodes, RFC6731
+define6 74 embed rdnss_selection
+embed ip6address server
+embed byte prf
+embed array domain domains
+
+# DHCPv6 Kerberos, RFC6784
+define6 75 string krb_principal_name
+define6 76 string krb_realm_name
+define6 78 embed krb_kdc
+embed uint16 priority
+embed uint16 weight
+embed byte transport_type
+embed uint16 port
+embed ip6address address
+embed string realm_name
+
+# DHCPv6 Client Link-Layer Address, RFC6939
+# Section 7 states that clients MUST ignore the option 79
+
+# DHCPv6 Relay-Triggered Reconfiguraion, RFC6977
+define6 80 ip6address link_address
+
+# DHCPv6 Radius, RFC7037
+# Section 7 states that clients MUST ignore the option 81
+
+# DHCPv6 SOL_MAX_RT, RFC7083
+define6 82 request uint32 sol_max_rt
+define6 83 request uint32 inf_max_rt
+
+# DHCPv6 Address Selection Policy
+# Currently not supported
+
+# Options 86-65535 are unasssinged
+
+##############################################################################
+# Vendor-Identifying Vendor Options
+# An example:
+#vendopt 12345 encap frobozzco
+#encap 1 string maze_location
+#encap 2 byte grue_probability
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: dhcpcd-embedded.c,v 1.10 2015/07/09 10:15:34 roy Exp $");
+
+/*
+ * DO NOT EDIT!
+ * Automatically generated from dhcpcd-embedded.conf
+ * Ths allows us to simply generate DHCP structure without any C programming.
+ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <unistd.h>
+
+const char * const dhcpcd_embedded_conf[] = {
+"define 1 request ipaddress subnet_mask",
+"define 121 rfc3442 classless_static_routes",
+"define 249 rfc3442 ms_classless_static_routes",
+"define 33 request array ipaddress static_routes",
+"define 3 request array ipaddress routers",
+"define 2 uint32 time_offset",
+"define 4 array ipaddress time_servers",
+"define 5 array ipaddress ien116_name_servers",
+"define 6 array ipaddress domain_name_servers",
+"define 7 array ipaddress log_servers",
+"define 8 array ipaddress cookie_servers",
+"define 9 array ipaddress lpr_servers",
+"define 10 array ipaddress impress_servers",
+"define 11 array ipaddress resource_location_servers",
+"define 12 dname host_name",
+"define 13 uint16 boot_size",
+"define 14 string merit_dump",
+"define 15 array dname domain_name",
+"define 16 ipaddress swap_server",
+"define 17 string root_path",
+"define 18 string extensions_path",
+"define 19 byte ip_forwarding",
+"define 20 byte non_local_source_routing",
+"define 21 array ipaddress policy_filter",
+"define 22 int16 max_dgram_reassembly",
+"define 23 uint16 default_ip_ttl",
+"define 24 uint32 path_mtu_aging_timeout",
+"define 25 array uint16 path_mtu_plateau_table",
+"define 26 uint16 interface_mtu",
+"define 27 byte all_subnets_local",
+"define 28 request ipaddress broadcast_address",
+"define 29 byte perform_mask_discovery",
+"define 30 byte mask_supplier",
+"define 31 byte router_discovery",
+"define 32 ipaddress router_solicitation_address",
+"define 34 byte trailer_encapsulation",
+"define 35 uint32 arp_cache_timeout",
+"define 36 uint16 ieee802_3_encapsulation",
+"define 37 byte default_tcp_ttl",
+"define 38 uint32 tcp_keepalive_interval",
+"define 39 byte tcp_keepalive_garbage",
+"define 40 string nis_domain",
+"define 41 array ipaddress nis_servers",
+"define 42 array ipaddress ntp_servers",
+"define 43 binhex vendor_encapsulated_options",
+"define 44 array ipaddress netbios_name_servers",
+"define 45 ipaddress netbios_dd_server",
+"define 46 byte netbios_node_type",
+"define 47 string netbios_scope",
+"define 48 array ipaddress font_servers",
+"define 49 array ipaddress x_display_manager",
+"define 50 ipaddress dhcp_requested_address",
+"define 51 request uint32 dhcp_lease_time",
+"define 52 byte dhcp_option_overload",
+"define 53 byte dhcp_message_type",
+"define 54 ipaddress dhcp_server_identifier",
+"define 55 array byte dhcp_parameter_request_list",
+"define 56 string dhcp_message",
+"define 57 uint16 dhcp_max_message_size",
+"define 58 request uint32 dhcp_renewal_time",
+"define 59 request uint32 dhcp_rebinding_time",
+"define 60 string vendor_class_identifier",
+"define 61 binhex dhcp_client_identifier",
+"define 64 string nisplus_domain",
+"define 65 array ipaddress nisplus_servers",
+"define 66 dname tftp_server_name",
+"define 67 string bootfile_name",
+"define 68 array ipaddress mobile_ip_home_agent",
+"define 69 array ipaddress smtp_server",
+"define 70 array ipaddress pop_server",
+"define 71 array ipaddress nntp_server",
+"define 72 array ipaddress www_server",
+"define 73 array ipaddress finger_server",
+"define 74 array ipaddress irc_server",
+"define 75 array ipaddress streettalk_server",
+"define 76 array ipaddress streettalk_directory_assistance_server",
+"define 77 binhex user_class",
+"define 78 embed slp_agent",
+"embed byte mandatory",
+"embed array ipaddress address",
+"define 79 embed slp_service",
+"embed byte mandatory",
+"embed ascii scope_list",
+"define 80 norequest flag rapid_commit",
+"define 81 embed fqdn",
+"embed bitflags=0000NEOS flags",
+"embed byte rcode1",
+"embed byte rcode2",
+"embed domain fqdn",
+"define 85 array ipaddress nds_servers",
+"define 86 raw nds_tree_name",
+"define 87 raw nds_context",
+"define 88 array domain bcms_controller_names",
+"define 89 array ipaddress bcms_controller_address",
+"define 90 embed auth",
+"embed byte protocol",
+"embed byte algorithm",
+"embed byte rdm",
+"embed binhex:8 replay",
+"embed binhex information",
+"define 91 uint32 client_last_transaction_time",
+"define 92 array ipaddress associated_ip",
+"define 98 string uap_servers",
+"define 99 encap geoconf_civic",
+"embed byte what",
+"embed uint16 country_code",
+"define 100 string posix_timezone",
+"define 101 string tzdb_timezone",
+"define 116 byte auto_configure",
+"define 117 array uint16 name_service_search",
+"define 118 ipaddress subnet_selection",
+"define 119 array domain domain_search",
+"define 120 rfc3361 sip_server",
+"define 122 encap tsp",
+"encap 1 ipaddress dhcp_server",
+"encap 2 ipaddress dhcp_secondary_server",
+"encap 3 rfc3361 provisioning_server",
+"encap 4 embed as_req_as_rep_backoff",
+"embed uint32 nominal",
+"embed uint32 maximum",
+"embed uint32 retry",
+"encap 5 embed ap_req_ap_rep_backoff",
+"embed uint32 nominal",
+"embed uint32 maximum",
+"embed uint32 retry",
+"encap 6 domain kerberos_realm",
+"encap 7 byte ticket_granting_server_utilization",
+"encap 8 byte provisioning_timer",
+"define 123 binhex geoconf",
+"define 124 binhex vivco",
+"define 125 embed vivso",
+"embed uint32 enterprise_number",
+"define 136 array ipaddress pana_agent",
+"define 137 domain lost_server",
+"define 138 array ipaddress capwap_ac",
+"define 139 encap mos_ip",
+"encap 1 array ipaddress is",
+"encap 2 array ipaddress cs",
+"encap 3 array ipaddress es",
+"define 140 encap mos_domain",
+"encap 1 domain is",
+"encap 2 domain cs",
+"encap 3 domain es",
+"define 141 array domain sip_ua_cs_list",
+"define 142 array ipaddress andsf",
+"define 143 array ip6address andsf6",
+"define 144 binhex geoloc",
+"define 145 array byte forcerenew_nonce_capable",
+"define 146 embed rdnss_selection",
+"embed byte prf",
+"embed ipaddress primary",
+"embed ipaddress secondary",
+"embed array domain domains",
+"define 150 array ipaddress tftp_servers",
+"define 208 binhex pxelinux_magic",
+"define 209 string config_file",
+"define 210 string path_prefix",
+"define 211 uint32 reboot_time",
+"define 212 rfc5969 sixrd",
+"define 213 domain access_domain",
+"define 221 encap vss",
+"encap 0 string nvt",
+"encap 1 binhex vpn_id",
+"encap 255 flag global",
+"define 252 string wpad_url",
+"definend 1 binhex source_address",
+"definend 2 binhex target_address",
+"definend 3 index embed prefix_information",
+"embed byte length",
+"embed bitflags=LA flags",
+"embed uint32 vltime",
+"embed uint32 pltime",
+"embed uint32 reserved",
+"embed array ip6address prefix",
+"definend 5 embed mtu",
+"embed uint16 reserved",
+"embed uint32 mtu",
+"definend 25 index embed rdnss",
+"embed uint16 reserved",
+"embed uint32 lifetime",
+"embed array ip6address servers",
+"definend 31 index embed dnssl",
+"embed uint16 reserved",
+"embed uint32 lifetime",
+"embed domain search",
+"define6 1 binhex client_id",
+"define6 2 binhex server_id",
+"define6 3 norequest index embed ia_na",
+"embed binhex:4 iaid",
+"embed uint32 t1",
+"embed uint32 t2",
+"encap 5 option",
+"encap 13 option",
+"define6 4 norequest index embed ia_ta",
+"embed uint32 iaid",
+"encap 5 option",
+"encap 13 option",
+"define6 5 norequest index embed ia_addr",
+"embed ip6address ia_addr",
+"embed uint32 pltime",
+"embed uint32 vltime",
+"encap 13 option",
+"define6 6 array uint16 option_request",
+"define6 7 byte preference",
+"define6 8 uint16 elased_time",
+"define6 9 binhex dhcp_relay_msg",
+"define6 11 embed auth",
+"embed byte protocol",
+"embed byte algorithm",
+"embed byte rdm",
+"embed binhex:8 replay",
+"embed binhex information",
+"define6 12 ip6address unicast",
+"define6 13 norequest embed status_code",
+"embed uint16 status_code",
+"embed string message",
+"define6 14 norequest flag rapid_commit",
+"define6 15 binhex user_class",
+"define6 16 binhex vivco",
+"define6 17 embed vivso",
+"embed uint32 enterprise_number",
+"define6 18 binhex interface_id",
+"define6 19 byte reconfigure_msg",
+"define6 20 flag reconfigure_accept",
+"define6 21 array domain sip_servers_names",
+"define6 22 array ip6address sip_servers_addresses",
+"define6 23 array ip6address name_servers",
+"define6 24 array domain domain_search",
+"define6 25 norequest index embed ia_pd",
+"embed binhex:4 iaid",
+"embed uint32 t1",
+"embed uint32 t2",
+"encap 26 option",
+"define6 26 index embed prefix",
+"embed uint32 pltime",
+"embed uint32 vltime",
+"embed byte length",
+"embed ip6address prefix",
+"encap 13 option",
+"encap 67 option",
+"define6 27 array ip6address nis_servers",
+"define6 28 array ip6address nisp_servers",
+"define6 29 string nis_domain_name",
+"define6 30 string nisp_domain_name",
+"define6 31 array ip6address sntp_servers",
+"define6 32 uint32 info_refresh_time",
+"define6 33 array domain bcms_server_d",
+"define6 34 array ip6address bcms_server_a",
+"define6 36 encap geoconf_civic",
+"embed byte what",
+"embed uint16 country_code",
+"define6 37 embed remote_id",
+"embed uint32 enterprise_number",
+"embed binhex remote_id",
+"define6 38 binhex subscriber_id",
+"define6 39 embed fqdn",
+"embed bitflags=00000NOS flags",
+"embed domain fqdn",
+"define6 40 array ip6address pana_agent",
+"define6 41 string posix_timezone",
+"define6 42 string tzdb_timezone",
+"define6 43 array uint16 ero",
+"define6 49 domain mip6_hnidf",
+"define6 50 encap mip6_vdinf",
+"encap 71 option",
+"encap 72 option",
+"encap 73 option",
+"define6 51 domain lost_server",
+"define6 52 array ip6address capwap_ac",
+"define6 53 binhex relay_id",
+"define6 54 encap mos_ip",
+"encap 1 array ip6address is",
+"encap 2 array ip6address cs",
+"encap 3 array ip6address es",
+"define6 55 encap mos_domain",
+"encap 1 domain is",
+"encap 2 domain cs",
+"encap 3 domain es",
+"define6 56 encap ntp_server",
+"encap 1 ip6address addr",
+"encap 2 ip6address mcast_addr",
+"encap 3 ip6address fqdn",
+"define6 57 domain access_domain",
+"define6 58 array domain sip_ua_cs_list",
+"define6 59 string bootfile_url",
+"define6 60 binhex bootfile_param",
+"define6 61 array uint16 architecture_types",
+"define6 62 embed nii",
+"embed byte type",
+"embed byte major",
+"embed byte minor",
+"define6 63 binhex geoloc",
+"define6 64 domain aftr_name",
+"define6 67 embed pd_exclude",
+"embed byte prefix_len",
+"embed binhex subnetID",
+"define6 69 encap mip6_idinf",
+"encap 71 option",
+"encap 72 option",
+"encap 73 option",
+"define6 70 encap mip6_udinf",
+"encap 71 option",
+"encap 72 option",
+"encap 73 option",
+"define6 71 embed mip6_hnp",
+"embed byte prefix_len",
+"embed ip6address prefix",
+"define6 72 ip6address mip6_haa",
+"define6 73 domain mip6_haf",
+"define6 74 embed rdnss_selection",
+"embed ip6address server",
+"embed byte prf",
+"embed array domain domains",
+"define6 75 string krb_principal_name",
+"define6 76 string krb_realm_name",
+"define6 78 embed krb_kdc",
+"embed uint16 priority",
+"embed uint16 weight",
+"embed byte transport_type",
+"embed uint16 port",
+"embed ip6address address",
+"embed string realm_name",
+"define6 80 ip6address link_address",
+"define6 82 request uint32 sol_max_rt",
+"define6 83 request uint32 inf_max_rt",
+NULL
+};
--- /dev/null
+/*
+ * DO NOT EDIT!
+ * Automatically generated from dhcpcd-embedded.conf
+ * Ths allows us to simply generate DHCP structure without any C programming.
+ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <unistd.h>
+
+const char * const dhcpcd_embedded_conf[] = {
--- /dev/null
+/* $NetBSD: dhcpcd-embedded.h,v 1.9 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#define INITDEFINES 122
+#define INITDEFINENDS 6
+#define INITDEFINE6S 68
+
+extern const char * const dhcpcd_embedded_conf[];
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#define INITDEFINES @INITDEFINES@
+#define INITDEFINENDS @INITDEFINENDS@
+#define INITDEFINE6S @INITDEFINE6S@
+
+extern const char * const dhcpcd_embedded_conf[];
--- /dev/null
+# $NetBSD: 01-test,v 1.8 2015/07/09 10:15:34 roy Exp $
+
+# Echo the interface flags, reason and message options
+
+if [ "$reason" = "TEST" ]; then
+ set | grep "^\(interface\|pid\|reason\|profile\|skip_hooks\)=" | sort
+ set | grep "^if\(carrier\|flags\|mtu\|wireless\|ssid\)=" | sort
+ set | grep "^\(new_\|old_\|nd[0-9]*_\)" | sort
+ exit 0
+fi
--- /dev/null
+# $NetBSD: 02-dump,v 1.6 2014/11/07 20:51:03 roy Exp $
+
+# Just echo our DHCP options we have
+
+case "$reason" in
+DUMP|DUMP6)
+ set | sed -ne 's/^new_//p' | sort
+ exit 0
+ ;;
+esac
--- /dev/null
+# $NetBSD: 10-wpa_supplicant,v 1.6 2014/11/07 20:51:03 roy Exp $
+
+# Start, reconfigure and stop wpa_supplicant per wireless interface.
+# This is needed because wpa_supplicant lacks hotplugging of any kind
+# and the user should not be expected to have to wire it into their system
+# if the base system doesn't do this itself.
+
+if [ -z "$wpa_supplicant_conf" ]; then
+ for x in \
+ /etc/wpa_supplicant/wpa_supplicant-"$interface".conf \
+ /etc/wpa_supplicant/wpa_supplicant.conf \
+ /etc/wpa_supplicant-"$interface".conf \
+ /etc/wpa_supplicant.conf \
+ ; do
+ if [ -s "$x" ]; then
+ wpa_supplicant_conf="$x"
+ break
+ fi
+ done
+fi
+: ${wpa_supplicant_conf:=/etc/wpa_supplicant.conf}
+
+wpa_supplicant_ctrldir()
+{
+ local dir
+
+ dir=$(key_get_value "[[:space:]]*ctrl_interface=" \
+ "$wpa_supplicant_conf")
+ dir=$(trim "$dir")
+ case "$dir" in
+ DIR=*)
+ dir=${dir##DIR=}
+ dir=${dir%%[[:space:]]GROUP=*}
+ dir=$(trim "$dir")
+ ;;
+ esac
+ printf %s "$dir"
+}
+
+wpa_supplicant_start()
+{
+ local dir err errn
+
+ # If the carrier is up, don't bother checking anything
+ [ "$ifcarrier" = "up" ] && return 0
+
+ # Pre flight checks
+ if [ ! -s "$wpa_supplicant_conf" ]; then
+ syslog warn \
+ "$wpa_supplicant_conf does not exist"
+ syslog warn "not interacting with wpa_supplicant(8)"
+ return 1
+ fi
+ dir=$(wpa_supplicant_ctrldir)
+ if [ -z "$dir" ]; then
+ syslog warn \
+ "ctrl_interface not defined in $wpa_supplicant_conf"
+ syslog warn "not interacting with wpa_supplicant(8)"
+ return 1
+ fi
+
+ wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1 && return 0
+ syslog info "starting wpa_supplicant"
+ driver=${wpa_supplicant_driver:+-D}$wpa_supplicant_driver
+ err=$(wpa_supplicant -B -c"$wpa_supplicant_conf" -i"$interface" \
+ "$driver" 2>&1)
+ errn=$?
+ if [ $errn != 0 ]; then
+ syslog err "failed to start wpa_supplicant"
+ syslog err "$err"
+ fi
+ return $errn
+}
+
+wpa_supplicant_reconfigure()
+{
+ local dir err errn
+
+ dir=$(wpa_supplicant_ctrldir)
+ [ -z "$dir" ] && return 1
+ if ! wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1; then
+ wpa_supplicant_start
+ return $?
+ fi
+ syslog info "reconfiguring wpa_supplicant"
+ err=$(wpa_cli -p "$dir" -i "$interface" reconfigure 2>&1)
+ errn=$?
+ if [ $errn != 0 ]; then
+ syslog err "failed to reconfigure wpa_supplicant"
+ syslog err "$err"
+ fi
+ return $errn
+}
+
+wpa_supplicant_stop()
+{
+ local dir err errn
+
+ dir=$(wpa_supplicant_ctrldir)
+ [ -z "$dir" ] && return 1
+ wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1 || return 0
+ syslog info "stopping wpa_supplicant"
+ err=$(wpa_cli -i"$interface" terminate 2>&1)
+ errn=$?
+ if [ $errn != 0 ]; then
+ syslog err "failed to start wpa_supplicant"
+ syslog err "$err"
+ fi
+ return $errn
+}
+
+if [ "$ifwireless" = "1" ] && \
+ type wpa_supplicant >/dev/null 2>&1 && \
+ type wpa_cli >/dev/null 2>&1
+then
+ case "$reason" in
+ PREINIT) wpa_supplicant_start;;
+ RECONFIGURE) wpa_supplicant_reconfigure;;
+ DEPARTED) wpa_supplicant_stop;;
+ esac
+fi
--- /dev/null
+# $NetBSD: 15-timezone,v 1.6 2014/11/07 20:51:03 roy Exp $
+
+# Configure timezone
+
+: ${localtime:=/etc/localtime}
+
+set_zoneinfo()
+{
+ local zoneinfo_dir= zone_file=
+
+ [ -z "$new_tzdb_timezone" ] && return 0
+
+ for d in \
+ /usr/share/zoneinfo \
+ /usr/lib/zoneinfo \
+ /var/share/zoneinfo \
+ /var/zoneinfo \
+ ; do
+ if [ -d "$d" ]; then
+ zoneinfo_dir="$d"
+ break
+ fi
+ done
+
+ if [ -z "$zoneinfo_dir" ]; then
+ syslog warning "timezone directory not found"
+ return 1
+ fi
+
+ zone_file="$zoneinfo_dir/$new_tzdb_timezone"
+ if [ ! -e "$zone_file" ]; then
+ syslog warning "no timezone definition for $new_tzdb_timezone"
+ return 1
+ fi
+
+ if copy_file "$zone_file" "$localtime"; then
+ syslog info "timezone changed to $new_tzdb_timezone"
+ fi
+}
+
+# For ease of use, map DHCP6 names onto our DHCP4 names
+case "$reason" in
+BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6)
+ new_tzdb_timezone="$new_dhcp6_tzdb_timezone"
+ ;;
+esac
+
+if $if_up; then
+ set_zoneinfo
+fi
--- /dev/null
+# $NetBSD: 20-resolv.conf,v 1.8 2015/08/21 10:39:00 roy Exp $
+
+# Generate /etc/resolv.conf
+# Support resolvconf(8) if available
+# We can merge other dhcpcd resolv.conf files into one like resolvconf,
+# but resolvconf is preferred as other applications like VPN clients
+# can readily hook into it.
+# Also, resolvconf can configure local nameservers such as bind
+# or dnsmasq. This is important as the libc resolver isn't that powerful.
+
+resolv_conf_dir="$state_dir/resolv.conf"
+NL="
+"
+
+build_resolv_conf()
+{
+ local cf="$state_dir/resolv.conf.$ifname"
+ local interfaces= header= search= srvs= servers= x=
+
+ # Build a list of interfaces
+ interfaces=$(list_interfaces "$resolv_conf_dir")
+
+ # Build the resolv.conf
+ if [ -n "$interfaces" ]; then
+ # Build the header
+ for x in ${interfaces}; do
+ header="$header${header:+, }$x"
+ done
+
+ # Build the search list
+ domain=$(cd "$resolv_conf_dir"; \
+ key_get_value "domain " ${interfaces})
+ search=$(cd "$resolv_conf_dir"; \
+ key_get_value "search " ${interfaces})
+ set -- ${domain}
+ domain="$1"
+ [ -n "$2" ] && search="$search $*"
+ [ -n "$search" ] && search="$(uniqify $search)"
+ [ "$domain" = "$search" ] && search=
+ [ -n "$domain" ] && domain="domain $domain$NL"
+ [ -n "$search" ] && search="search $search$NL"
+
+ # Build the nameserver list
+ srvs=$(cd "$resolv_conf_dir"; \
+ key_get_value "nameserver " ${interfaces})
+ for x in $(uniqify ${srvs}); do
+ servers="${servers}nameserver $x$NL"
+ done
+ fi
+ header="$signature_base${header:+ $from }$header"
+
+ # Assemble resolv.conf using our head and tail files
+ [ -f "$cf" ] && rm -f "$cf"
+ [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir"
+ echo "$header" > "$cf"
+ if [ -f /etc/resolv.conf.head ]; then
+ cat /etc/resolv.conf.head >> "$cf"
+ else
+ echo "# /etc/resolv.conf.head can replace this line" >> "$cf"
+ fi
+ printf %s "$domain$search$servers" >> "$cf"
+ if [ -f /etc/resolv.conf.tail ]; then
+ cat /etc/resolv.conf.tail >> "$cf"
+ else
+ echo "# /etc/resolv.conf.tail can replace this line" >> "$cf"
+ fi
+ if change_file /etc/resolv.conf "$cf"; then
+ chmod 644 /etc/resolv.conf
+ fi
+ rm -f "$cf"
+}
+
+# Extract any ND DNS options from the RA
+# For now, we ignore the lifetime of the DNS options unless they
+# are absent or zero.
+# In this case they are removed from consideration.
+# See draft-gont-6man-slaac-dns-config-issues-01 for issues
+# regarding DNS option lifetime in ND messages.
+eval_nd_dns()
+{
+
+ eval ltime=\$nd${i}_rdnss${j}_lifetime
+ if [ -z "$ltime" -o "$ltime" = 0 ]; then
+ rdnss=
+ else
+ eval rdnss=\$nd${i}_rdnss${j}_servers
+ fi
+ eval ltime=\$nd${i}_dnssl${j}_lifetime
+ if [ -z "$ltime" -o "$ltime" = 0 ]; then
+ dnssl=
+ else
+ eval dnssl=\$nd${i}_dnssl${j}_search
+ fi
+
+ [ -z "$rdnss" -a -z "$dnssl" ] && return 1
+
+ new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss"
+ new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl"
+ j=$(($j + 1))
+ return 0
+}
+
+add_resolv_conf()
+{
+ local x= conf="$signature$NL" warn=true
+ local i j ltime rdnss dnssl new_rdnss new_dnssl
+
+ # Loop to extract the ND DNS options using our indexed shell values
+ i=1
+ j=1
+ while true; do
+ while true; do
+ eval_nd_dns || break
+ done
+ i=$(($i + 1))
+ j=1
+ eval_nd_dns || break
+ done
+ new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$new_rdnss"
+ new_domain_search="$new_domain_search${new_domain_search:+ }$new_dnssl"
+
+ # Derive a new domain from our various hostname options
+ if [ -z "$new_domain_name" ]; then
+ if [ "$new_dhcp6_fqdn" != "${new_dhcp6_fqdn#*.}" ]; then
+ new_domain_name="${new_dhcp6_fqdn#*.}"
+ elif [ "$new_fqdn" != "${new_fqdn#*.}" ]; then
+ new_domain_name="${new_fqdn#*.}"
+ elif [ "$new_host_name" != "${new_host_name#*.}" ]; then
+ new_domain_name="${new_host_name#*.}"
+ fi
+ fi
+
+ # If we don't have any configuration, remove it
+ if [ -z "$new_domain_name_servers" -a \
+ -z "$new_domain_name" -a \
+ -z "$new_domain_search" ]; then
+ remove_resolv_conf
+ return $?
+ fi
+
+ if [ -n "$new_domain_name" ]; then
+ set -- $new_domain_name
+ if valid_domainname "$1"; then
+ conf="${conf}domain $1$NL"
+ else
+ syslog err "Invalid domain name: $1"
+ fi
+ # If there is no search this, make this one
+ if [ -z "$new_domain_search" ]; then
+ new_domain_search="$new_domain_name"
+ [ "$new_domain_name" = "$1" ] && warn=true
+ fi
+ fi
+ if [ -n "$new_domain_search" ]; then
+ if valid_domainname_list $new_domain_search; then
+ conf="${conf}search $new_domain_search$NL"
+ elif ! $warn; then
+ syslog err "Invalid domain name in list:" \
+ "$new_domain_search"
+ fi
+ fi
+ for x in ${new_domain_name_servers}; do
+ conf="${conf}nameserver $x$NL"
+ done
+ if type resolvconf >/dev/null 2>&1; then
+ [ -n "$ifmetric" ] && export IF_METRIC="$ifmetric"
+ printf %s "$conf" | resolvconf -a "$ifname"
+ return $?
+ fi
+
+ if [ -e "$resolv_conf_dir/$ifname" ]; then
+ rm -f "$resolv_conf_dir/$ifname"
+ fi
+ [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir"
+ printf %s "$conf" > "$resolv_conf_dir/$ifname"
+ build_resolv_conf
+}
+
+remove_resolv_conf()
+{
+ if type resolvconf >/dev/null 2>&1; then
+ resolvconf -d "$ifname" -f
+ else
+ if [ -e "$resolv_conf_dir/$ifname" ]; then
+ rm -f "$resolv_conf_dir/$ifname"
+ fi
+ build_resolv_conf
+ fi
+}
+
+# For ease of use, map DHCP6 names onto our DHCP4 names
+case "$reason" in
+BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6)
+ new_domain_name_servers="$new_dhcp6_name_servers"
+ new_domain_search="$new_dhcp6_domain_search"
+ ;;
+esac
+
+if $if_up || [ "$reason" = ROUTERADVERT ]; then
+ add_resolv_conf
+elif $if_down; then
+ remove_resolv_conf
+fi
--- /dev/null
+# $NetBSD: 29-lookup-hostname,v 1.7 2014/12/09 20:21:05 roy Exp $
+
+# Lookup the hostname in DNS if not set
+
+lookup_hostname()
+{
+ [ -z "$new_ip_address" ] && return 1
+ local h=
+ # Silly ISC programs love to send error text to stdout
+ if type dig >/dev/null 2>&1; then
+ h=$(dig +short -x $new_ip_address)
+ if [ $? = 0 ]; then
+ echo "$h" | sed 's/\.$//'
+ return 0
+ fi
+ elif type host >/dev/null 2>&1; then
+ h=$(host $new_ip_address)
+ if [ $? = 0 ]; then
+ echo "$h" \
+ | sed 's/.* domain name pointer \(.*\)./\1/'
+ return 0
+ fi
+ elif type getent >/dev/null 2>&1; then
+ h=$(getent hosts $new_ip_address)
+ if [ $? = 0 ]; then
+ echo "$h" | sed 's/[^ ]* *\([^ ]*\).*/\1/'
+ return 0
+ fi
+ fi
+ return 1
+}
+
+set_hostname()
+{
+ if [ -z "$new_host_name" -a -z "$new_fqdn_name" ]; then
+ export new_host_name="$(lookup_hostname)"
+ fi
+}
+
+if $if_up; then
+ set_hostname
+fi
--- /dev/null
+# $NetBSD: 30-hostname,v 1.7 2015/08/21 10:39:00 roy Exp $
+
+# Set the hostname from DHCP data if required
+
+# A hostname can either be a short hostname or a FQDN.
+# hostname_fqdn=true
+# hostname_fqdn=false
+# hostname_fqdn=server
+
+# A value of server means just what the server says, don't manipulate it.
+# This could lead to an inconsistent hostname on a DHCPv4 and DHCPv6 network
+# where the DHCPv4 hostname is short and the DHCPv6 has an FQDN.
+# DHCPv6 has no hostname option.
+# RFC4702 section 3.1 says FQDN should be prefered over hostname.
+#
+# As such, the default is hostname_fqdn=true so that a consistent hostname
+# is always assigned.
+: ${hostname_fqdn:=true}
+
+# Some systems don't have hostname(1)
+_hostname()
+{
+ local name=
+
+ if [ -z "$1" ]; then
+ if type hostname >/dev/null 2>&1; then
+ hostname
+ elif [ -r /proc/sys/kernel/hostname ]; then
+ read name </proc/sys/kernel/hostname && echo "$name"
+ elif sysctl kern.hostname >/dev/null 2>&1; then
+ sysctl -n kern.hostname
+ elif sysctl kernel.hostname >/dev/null 2>&1; then
+ sysctl -n kernel.hostname
+ else
+ return 1
+ fi
+ return $?
+ fi
+
+ # Always prefer hostname(1) if we have it
+ if type hostname >/dev/null 2>&1; then
+ hostname "$1"
+ elif [ -w /proc/sys/kernel/hostname ]; then
+ echo "$1" >/proc/sys/kernel/hostname
+ elif sysctl kern.hostname >/dev/null 2>&1; then
+ sysctl -w "kern.hostname=$1"
+ elif sysctl kernel.hostname >/dev/null 2>&1; then
+ sysctl -w "kernel.hostname=$1"
+ else
+ # We know this will fail, but it will now fail
+ # with an error to stdout
+ hostname "$1"
+ fi
+}
+
+need_hostname()
+{
+ local hostname hfqdn=false hshort=false
+
+ case "$force_hostname" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;;
+ esac
+
+ hostname="$(_hostname)"
+ case "$hostname" in
+ ""|"(none)"|localhost|localhost.localdomain) return 0;;
+ esac
+
+ case "$hostname_fqdn" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;;
+ [Ss][Ee][Rr][Vv][Ee][Rr]) ;;
+ *) hshort=true;;
+ esac
+
+ if [ -n "$old_fqdn" ]; then
+ if ${hfqdn} || ! ${hsort}; then
+ [ "$hostname" = "$old_fqdn" ]
+ else
+ [ "$hostname" = "${old_fqdn%%.*}" ]
+ fi
+ elif [ -n "$old_host_name" ]; then
+ if ${hfqdn}; then
+ if [ -n "$old_domain_name" -a \
+ "$old_host_name" = "${old_host_name#*.}" ]
+ then
+ [ "$hostname" = \
+ "$old_host_name.$old_domain_name" ]
+ else
+ [ "$hostname" = "$old_host_name" ]
+ fi
+ elif ${hshort}; then
+ [ "$hostname" = "${old_host_name%%.*}" ]
+ else
+ [ "$hostname" = "$old_host_name" ]
+ fi
+ else
+ # No old hostname
+ false
+ fi
+}
+
+try_hostname()
+{
+
+ if valid_domainname "$1"; then
+ _hostname "$1"
+ else
+ syslog err "Invalid hostname: $1"
+ fi
+}
+
+set_hostname()
+{
+ local hfqdn=false hshort=false
+
+ need_hostname || return
+
+ case "$hostname_fqdn" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;;
+ "") ;;
+ *) hshort=true;;
+ esac
+
+ if [ -n "$new_fqdn" ]; then
+ if ${hfqdn} || ! ${hshort}; then
+ try_hostname "$new_fqdn"
+ else
+ try_hostname "${new_fqdn%%.*}"
+ fi
+ elif [ -n "$new_host_name" ]; then
+ if ${hfqdn}; then
+ if [ -n "$new_domain_name" -a \
+ "$new_host_name" = "${new_host_name#*.}" ]
+ then
+ try_hostname "$new_host_name.$new_domain_name"
+ else
+ try_hostname "$new_host_name"
+ fi
+ elif ${hshort}; then
+ try_hostname "${new_host_name%%.*}"
+ else
+ try_hostname "$new_host_name"
+ fi
+ fi
+}
+
+# For ease of use, map DHCP6 names onto our DHCP4 names
+case "$reason" in
+BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6)
+ new_fqdn="$new_dhcp6_fqdn"
+ old_fqdn="$old_dhcp6_fqdn"
+ ;;
+esac
+
+if $if_up; then
+ set_hostname
+fi
--- /dev/null
+# $NetBSD: 50-ntp.conf,v 1.6 2014/11/07 20:51:03 roy Exp $
+
+# Sample dhcpcd hook script for ntp
+# Like our resolv.conf hook script, we store a database of ntp.conf files
+# and merge into /etc/ntp.conf
+
+# You can set the env var NTP_CONF to another file like this
+# dhcpcd -e NTP_CONF=/usr/pkg/etc/ntpd.conf
+# or by adding this to /etc/dhcpcd.enter-hook
+# NTP_CONF=/usr/pkg/etc/ntpd.conf
+# to use OpenNTPD instead of the default NTP.
+
+if type invoke-rc.d >/dev/null 2>&1; then
+ # Debian has a seperate file for DHCP config to avoid stamping on
+ # the master.
+ [ -e /var/lib/ntp ] || mkdir /var/lib/ntp
+ : ${ntp_service:=ntp}
+ : ${NTP_DHCP_CONF:=/var/lib/ntp/ntp.conf.dhcp}
+fi
+
+: ${ntp_service:=ntpd}
+: ${ntp_restart_cmd:=service_condcommand $ntp_service restart}
+ntp_conf_dir="$state_dir/ntp.conf"
+
+# If we have installed OpenNTPD but not NTP then prefer it
+# XXX If both exist then update both?
+if [ -z "$NTP_CONF" -a -e /etc/ntpd.conf -a ! -e /etc/ntp.conf ]; then
+ : ${NTP_CONF:=/etc/ntpd.conf}
+else
+ : ${NTP_CONF:=/etc/ntp.conf}
+fi
+
+ntp_conf=${NTP_CONF}
+NL="
+"
+
+build_ntp_conf()
+{
+ local cf="$state_dir/ntp.conf.$ifname"
+ local interfaces= header= srvs= servers= x=
+
+ # Build a list of interfaces
+ interfaces=$(list_interfaces "$ntp_conf_dir")
+
+ if [ -n "$interfaces" ]; then
+ # Build the header
+ for x in ${interfaces}; do
+ header="$header${header:+, }$x"
+ done
+
+ # Build a server list
+ srvs=$(cd "$ntp_conf_dir";
+ key_get_value "server " $interfaces)
+ if [ -n "$srvs" ]; then
+ for x in $(uniqify $srvs); do
+ servers="${servers}server $x$NL"
+ done
+ fi
+ fi
+
+ # Merge our config into ntp.conf
+ [ -e "$cf" ] && rm -f "$cf"
+ [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir"
+
+ if [ -n "$NTP_DHCP_CONF" ]; then
+ [ -e "$ntp_conf" ] && cp "$ntp_conf" "$cf"
+ ntp_conf="$NTP_DHCP_CONF"
+ elif [ -e "$ntp_conf" ]; then
+ remove_markers "$signature_base" "$signature_base_end" \
+ "$ntp_conf" > "$cf"
+ fi
+
+ if [ -n "$servers" ]; then
+ echo "$signature_base${header:+ $from }$header" >> "$cf"
+ printf %s "$servers" >> "$cf"
+ echo "$signature_base_end${header:+ $from }$header" >> "$cf"
+ else
+ [ -e "$ntp_conf" -a -e "$cf" ] || return
+ fi
+
+ # If we changed anything, restart ntpd
+ if change_file "$ntp_conf" "$cf"; then
+ [ -n "$ntp_restart_cmd" ] && eval $ntp_restart_cmd
+ fi
+}
+
+add_ntp_conf()
+{
+ local cf="$ntp_conf_dir/$ifname" x=
+
+ [ -e "$cf" ] && rm "$cf"
+ [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir"
+ if [ -n "$new_ntp_servers" ]; then
+ for x in $new_ntp_servers; do
+ echo "server $x" >> "$cf"
+ done
+ fi
+ build_ntp_conf
+}
+
+remove_ntp_conf()
+{
+ if [ -e "$ntp_conf_dir/$ifname" ]; then
+ rm "$ntp_conf_dir/$ifname"
+ fi
+ build_ntp_conf
+}
+
+# For ease of use, map DHCP6 names onto our DHCP4 names
+case "$reason" in
+BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6)
+ new_ntp_servers="$new_dhcp6_sntp_servers"
+;;
+esac
+
+if $if_up; then
+ add_ntp_conf
+elif $if_down; then
+ remove_ntp_conf
+fi
--- /dev/null
+# $NetBSD: 50-ypbind,v 1.7 2015/03/26 10:26:37 roy Exp $
+
+# Sample dhcpcd hook for ypbind
+# This script is only suitable for the BSD versions.
+
+: ${ypbind_restart_cmd:=service_command ypbind restart}
+: ${ypbind_stop_cmd:=service_condcommand ypbind stop}
+ypbind_dir="$state_dir/ypbind"
+: ${ypdomain_dir:=/var/yp}
+: ${ypdomain_suffix:=.ypservers}
+
+
+best_domain()
+{
+ local i=
+
+ for i in "$ypbind_dir/$interface_order".*; do
+ if [ -f "$i" ]; then
+ cat "$i"
+ return 0
+ fi
+ done
+ return 1
+}
+
+make_yp_binding()
+{
+ [ -d "$ypbind_dir" ] || mkdir -p "$ypbind_dir"
+ echo "$new_nis_domain" >"$ypbind_dir/$ifname"
+
+ if [ -z "$ypdomain_dir" ]; then
+ false
+ else
+ local cf="$ypdomain_dir/$new_nis_domain$ypdomain_suffix"
+ if [ -n "$new_nis_servers" ]; then
+ local ncf="$cf.$ifname" x=
+ rm -f "$ncf"
+ for x in $new_nis_servers; do
+ echo "$x" >>"$ncf"
+ done
+ change_file "$cf" "$ncf"
+ else
+ [ -e "$cf" ] && rm "$cf"
+ fi
+ fi
+
+ local nd="$(best_domain)"
+ if [ $? = 0 -a "$nd" != "$(domainname)" ]; then
+ domainname "$nd"
+ if [ -n "$ypbind_restart_cmd" ]; then
+ eval $ypbind_restart_cmd
+ fi
+ fi
+}
+
+restore_yp_binding()
+{
+
+ rm -f "$ypbind_dir/$ifname"
+ local nd="$(best_domain)"
+ # We need to stop ypbind if there is no best domain
+ # otherwise it will just stall as we cannot set domainname
+ # to blank :/
+ if [ -z "$nd" ]; then
+ if [ -n "$ypbind_stop_cmd" ]; then
+ eval $ypbind_stop_cmd
+ fi
+ elif [ "$nd" != "$(domainname)" ]; then
+ domainname "$nd"
+ if [ -n "$ypbind_restart_cmd" ]; then
+ eval $ypbind_restart_cmd
+ fi
+ fi
+}
+
+if [ "$reason" = PREINIT ]; then
+ rm -f "$ypbind_dir/$interface".*
+elif $if_up || $if_down; then
+ if [ -n "$new_nis_domain" ]; then
+ if valid_domainname "$new_nis_domain"; then
+ make_yp_binding
+ else
+ syslog err "Invalid NIS domain name: $new_nis_domain"
+ fi
+ elif [ -n "$old_nis_domain" ]; then
+ restore_yp_binding
+ fi
+fi
--- /dev/null
+.\" $NetBSD: dhcpcd-run-hooks.8.in,v 1.15 2015/07/09 10:15:34 roy Exp $
+.\" Copyright (c) 2006-2015 Roy Marples
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd June 29, 2015
+.Dt DHCPCD-RUN-HOOKS 8
+.Os
+.Sh NAME
+.Nm dhcpcd-run-hooks
+.Nd DHCP client configuration script
+.Sh DESCRIPTION
+.Nm
+is used by
+.Xr dhcpcd 8
+to run any system and user defined hook scripts.
+System hook scripts are found in
+.Pa @HOOKDIR@
+and the user defined hooks are
+.Pa @SYSCONFDIR@/dhcpcd.enter-hook .
+and
+.Pa @SYSCONFDIR@/dhcpcd.exit-hook .
+The default install supplies hook scripts for configuring
+.Pa /etc/resolv.conf
+and the hostname.
+Your distribution may have included other hook scripts to say configure
+ntp or ypbind.
+A test hook is also supplied that simply echos the dhcp variables to the
+console from DISCOVER message.
+.Pp
+Each time
+.Nm
+is invoked,
+.Ev $interface
+is set to the interface that
+.Nm dhcpcd
+is run on and
+.Ev $reason
+is to the reason why
+.Nm
+was invoked.
+DHCP information to be configured is held in variables starting with the word
+new_ and old DHCP information to be removed is held in variables starting with
+the word old_.
+.Nm dhcpcd
+can display the full list of variables it knows how about by using the
+.Fl V , -variables
+argument.
+.Pp
+Here's a list of reasons why
+.Nm
+could be invoked:
+.Bl -tag -width EXPIREXXXEXPIRE6
+.It Dv PREINIT
+dhcpcd is starting up and any pre-initialisation should be done.
+.It Dv CARRIER
+dhcpcd has detected the carrier is up.
+This is generally just a notification and no action need be taken.
+.It Dv NOCARRIER
+dhcpcd lost the carrier.
+The cable may have been unplugged or association to the wireless point lost.
+.It Dv INFORM | Dv INFORM6
+dhcpcd informed a DHCP server about it's address and obtained other
+configuration details.
+.It Dv BOUND | Dv BOUND6
+dhcpcd obtained a new lease from a DHCP server.
+.It Dv RENEW | Dv RENEW6
+dhcpcd renewed it's lease.
+.It Dv REBIND | Dv REBIND6
+dhcpcd has rebound to a new DHCP server.
+.It Dv REBOOT | Dv REBOOT6
+dhcpcd successfully requested a lease from a DHCP server.
+.It Dv DELEGATED6
+dhcpcd assigned a delegated prefix to the interface.
+.It Dv IPV4LL
+dhcpcd obtaind an IPV4LL address, or one was removed.
+.It Dv STATIC
+dhcpcd has been configured with a static configuration which has not been
+obtained from a DHCP server.
+.It Dv 3RDPARTY
+dhcpcd is monitoring the interface for a 3rd party to give it an IP address.
+.It Dv TIMEOUT
+dhcpcd failed to contact any DHCP servers but was able to use an old lease.
+.It Dv EXPIRE | EXPIRE6
+dhcpcd's lease or state expired and it failed to obtain a new one.
+.It Dv NAK
+dhcpcd received a NAK from the DHCP server.
+This should be treated as EXPIRE.
+.It Dv RECONFIGURE
+dhcpcd has been instructed to reconfigure an interface.
+.It Dv ROUTERADVERT
+dhcpcd has received an IPv6 Router Advertisment, or one has expired.
+.It Dv STOP | Dv STOP6
+dhcpcd stopped running on the interface.
+.It Dv STOPPED
+dhcpcd has stopped entirely.
+.It Dv DEPARTED
+The interface has been removed.
+.It Dv FAIL
+dhcpcd failed to operate on the interface.
+This normally happens when dhcpcd does not support the raw interface, which
+means it cannot work as a DHCP or ZeroConf client.
+Static configuration and DHCP INFORM is still allowed.
+.It Dv DUMP
+dhcpcd has been asked to dump the last lease for the interface.
+.It Dv TEST
+dhcpcd received an OFFER from a DHCP server but will not configure the
+interface.
+This is primarily used to test the variables are filled correctly for the
+script to process them.
+.El
+.Sh ENVIRONMENT
+.Nm dhcpcd
+will clear the environment variables aside from
+.Ev $PATH
+and
+.Ev $RC_SVCNAME .
+The following variables will then be set, along with any protocol supplied
+ones.
+.Bl -tag -width xnew_delegated_dhcp6_prefix
+.It Ev $interface
+the name of the interface.
+.It Ev $reason
+as described above.
+.It Ev $pid
+the pid of
+.Nm dhcpcd .
+.It Ev $ifcarrier
+the link status of
+.Ev $interface :
+.Dv unknown ,
+.Dv up
+or
+.Dv down .
+.It Ev $ifmetric
+.Ev $interface
+preference, lower is better.
+.It Ev $ifwireless
+.Dv 1 if
+.Ev $interface
+is wireless, otherwise
+.Dv 0 .
+.It Ev $ifflags
+.Ev $interface
+flags.
+.It Ev $ifmtu
+.Ev $interface
+MTU.
+.It Ev $ifssid
+the name of the SSID the
+.Ev interface
+is connected to.
+.It Ev $interface_order
+A list of interfaces, in order of preference.
+.It Ev $if_up
+.Dv true
+if the
+.Ev interface
+is up, otherwise
+.Dv false .
+.It Ev $if_down
+.Dv true
+if the
+.Ev interface
+is down, otherwise
+.Dv false .
+.It Ev $af_waiting
+Address family waiting for, as defined in
+.Xr dhcpcd.conf 5 .
+.It Ev $profile
+the name of the profile selected from
+.Xr dhcpcd.conf 5 .
+.It Ev $new_delegated_dhcp6_prefix
+space separated list of delegated prefixes.
+.El
+.Sh FILES
+When
+.Nm
+runs, it loads
+.Pa @SYSCONFDIR@/dhcpcd.enter-hook
+and any scripts found in
+.Pa @HOOKDIR@
+in a lexical order and then finally
+.Pa @SYSCONFDIR@/dhcpcd.exit-hook
+.Sh SEE ALSO
+.Xr dhcpcd 8
+.Sh AUTHORS
+.An Roy Marples Aq Mt roy@marples.name
+.Sh BUGS
+Please report them to
+.Lk http://roy.marples.name/projects/dhcpcd
+.Sh SECURITY CONSIDERATIONS
+.Nm dhcpcd
+will validate the content of each option against its encoding.
+For string, ascii, raw or binhex encoding it's up to the user to validate it
+for the intended purpose.
+.Pp
+When used in a shell script, each variable must be quoted correctly.
--- /dev/null
+#!/bin/sh
+# $NetBSD: dhcpcd-run-hooks.in,v 1.11 2015/08/21 10:39:00 roy Exp $
+
+# dhcpcd client configuration script
+
+# Handy variables and functions for our hooks to use
+case "$reason" in
+ ROUTERADVERT)
+ ifsuffix=".ra";;
+ INFORM6|BOUND6|RENEW6|REBIND6|REBOOT6|EXPIRE6|RELEASE6|STOP6)
+ ifsuffix=".dhcp6";;
+ IPV4LL)
+ ifsuffix=".ipv4ll";;
+ *)
+ ifsuffix=".dhcp";;
+esac
+ifname="$interface$ifsuffix"
+
+from=from
+signature_base="# Generated by dhcpcd"
+signature="$signature_base $from $ifname"
+signature_base_end="# End of dhcpcd"
+signature_end="$signature_base_end $from $ifname"
+state_dir=@RUNDIR@/dhcpcd
+_detected_init=false
+
+: ${if_up:=false}
+: ${if_down:=false}
+: ${syslog_debug:=false}
+
+# Ensure that all arguments are unique
+uniqify()
+{
+ local result= i=
+ for i do
+ case " $result " in
+ *" $i "*);;
+ *) result="$result $i";;
+ esac
+ done
+ echo "${result# *}"
+}
+
+# List interface config files in a directory.
+# If dhcpcd is running as a single instance then it will have a list of
+# interfaces in the preferred order.
+# Otherwise we just use what we have.
+list_interfaces()
+{
+ local i= x= ifaces=
+ for i in $interface_order; do
+ for x in "$1"/$i.*; do
+ [ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}"
+ done
+ done
+ for x in "$1"/*; do
+ [ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}"
+ done
+ uniqify $ifaces
+}
+
+# Trim function
+trim()
+{
+ local var="$*"
+
+ var=${var#"${var%%[![:space:]]*}"}
+ var=${var%"${var##*[![:space:]]}"}
+ if [ -z "$var" ]; then
+ # So it seems our shell doesn't support wctype(3) patterns
+ # Fall back to sed
+ var=$(echo "$*" | sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ fi
+ printf %s "$var"
+}
+
+# We normally use sed to extract values using a key from a list of files
+# but sed may not always be available at the time.
+key_get_value()
+{
+ local key="$1" value= x= line=
+
+ shift
+ if type sed >/dev/null 2>&1; then
+ sed -n "s/^$key//p" $@
+ else
+ for x do
+ while read line; do
+ case "$line" in
+ "$key"*) echo "${line##$key}";;
+ esac
+ done < "$x"
+ done
+ fi
+}
+
+# We normally use sed to remove markers from a configuration file
+# but sed may not always be available at the time.
+remove_markers()
+{
+ local m1="$1" m2="$2" x= line= in_marker=0
+
+ shift; shift
+ if type sed >/dev/null 2>&1; then
+ sed "/^$m1/,/^$m2/d" $@
+ else
+ for x do
+ while read line; do
+ case "$line" in
+ "$m1"*) in_marker=1;;
+ "$m2"*) in_marker=0;;
+ *) [ $in_marker = 0 ] && echo "$line";;
+ esac
+ done < "$x"
+ done
+ fi
+}
+
+# Compare two files.
+comp_file()
+{
+
+ [ -e "$1" -a -e "$2" ] || return 1
+
+ if type cmp >/dev/null 2>&1; then
+ cmp -s "$1" "$2"
+ elif type diff >/dev/null 2>&1; then
+ diff -q "$1" "$2" >/dev/null
+ else
+ # Hopefully we're only working on small text files ...
+ [ "$(cat "$1")" = "$(cat "$2")" ]
+ fi
+}
+
+# Compare two files.
+# If different, replace first with second otherwise remove second.
+change_file()
+{
+
+ if [ -e "$1" ]; then
+ if comp_file "$1" "$2"; then
+ rm -f "$2"
+ return 1
+ fi
+ fi
+ cat "$2" > "$1"
+ rm -f "$2"
+ return 0
+}
+
+# Compare two files.
+# If different, copy or link depending on target type
+copy_file()
+{
+
+ if [ -h "$2" ]; then
+ [ "$(readlink "$2")" = "$1" ] && return 1
+ ln -sf "$1" "$2"
+ else
+ comp_file "$1" "$2" && return 1
+ cat "$1" >"$2"
+ fi
+}
+
+# Save a config file
+save_conf()
+{
+
+ if [ -f "$1" ]; then
+ rm -f "$1-pre.$interface"
+ cat "$1" > "$1-pre.$interface"
+ fi
+}
+
+# Restore a config file
+restore_conf()
+{
+
+ [ -f "$1-pre.$interface" ] || return 1
+ cat "$1-pre.$interface" > "$1"
+ rm -f "$1-pre.$interface"
+}
+
+# Write a syslog entry
+syslog()
+{
+ local lvl="$1"
+
+ if [ "$lvl" = debug ]; then
+ ${syslog_debug} || return 0
+ fi
+ [ -n "$lvl" ] && shift
+ [ -n "$*" ] || return 0
+ case "$lvl" in
+ err|error) echo "$interface: $*" >&2;;
+ *) echo "$interface: $*";;
+ esac
+ if type logger >/dev/null 2>&1; then
+ logger -i -p daemon."$lvl" -t dhcpcd-run-hooks "$interface: $*"
+ fi
+}
+
+# Check for a valid domain name as per RFC1123 with the exception of
+# allowing - and _ as they seem to be widely used.
+valid_domainname()
+{
+ local name="$1" label
+
+ [ -z "$name" -o ${#name} -gt 255 ] && return 1
+
+ while [ -n "$name" ]; do
+ label="${name%%.*}"
+ [ -z "$label" -o ${#label} -gt 63 ] && return 1
+ case "$label" in
+ -*|_*|*-|*_) return 1;;
+ # some sh require - as the first or last character in the class
+ # when matching it
+ *[![:alnum:]_-]*) return 1;;
+ esac
+ [ "$name" = "${name#*.}" ] && break
+ name="${name#*.}"
+ done
+ return 0
+}
+
+valid_domainname_list()
+{
+ local name
+
+ for name do
+ valid_domainname "$name" || return $?
+ done
+ return 0
+}
+
+# Check for a valid path
+valid_path()
+{
+
+ case "$@" in
+ *[![:alnum:]#%+-_:\.,@~\\/\[\]=\ ]*) return 1;;
+ esac
+ return 0
+}
+
+# With the advent of alternative init systems, it's possible to have
+# more than one installed. So we need to try and guess what one we're
+# using unless overriden by configure.
+detect_init()
+{
+ _service_exists="@SERVICEEXISTS@"
+ _service_cmd="@SERVICECMD@"
+ _service_status="@SERVICESTATUS@"
+
+ [ -n "$_service_cmd" ] && return 0
+
+ if ${_detected_init}; then
+ [ -n "$_service_cmd" ]
+ return $?
+ fi
+
+ # Detect the running init system.
+ # As systemd and OpenRC can be installed on top of legacy init
+ # systems we try to detect them first.
+ _service_status=
+ if [ -x /bin/systemctl -a -S /run/systemd/private ]; then
+ _service_exists="/bin/systemctl --quiet is-enabled \$1.service"
+ _service_status="/bin/systemctl --quiet is-active \$1.service"
+ _service_cmd="/bin/systemctl \$2 \$1.service"
+ elif [ -x /usr/bin/systemctl -a -S /run/systemd/private ]; then
+ _service_exists="/usr/bin/systemctl --quiet is-enabled \$1.service"
+ _service_status="/usr/bin/systemctl --quiet is-active \$1.service"
+ _service_cmd="/usr/bin/systemctl \$2 \$1.service"
+ elif [ -x /sbin/rc-service -a \
+ -s /libexec/rc/init.d/softlevel -o -s /run/openrc/softlevel ]
+ then
+ _service_exists="/sbin/rc-service -e \$1"
+ _service_cmd="/sbin/rc-service \$1 -- -D \$2"
+ elif [ -x /usr/sbin/invoke-rc.d ]; then
+ _service_exists="/usr/sbin/invoke-rc.d --query --quiet \$1 start >/dev/null 2>&1 || [ \$? = 104 ]"
+ _service_cmd="/usr/sbin/invoke-rc.d \$1 \$2"
+ elif [ -x /sbin/service ]; then
+ _service_exists="/sbin/service \$1 >/dev/null 2>&1"
+ _service_cmd="/sbin/service \$1 \$2"
+ elif [ -x /bin/sv ]; then
+ _service_exists="/bin/sv status \1 >/dev/null 2>&1"
+ _service_cmd="/bin/sv \$1 \$2"
+ elif [ -x /usr/bin/sv ]; then
+ _service_exists="/usr/bin/sv status \1 >/dev/null 2>&1"
+ _service_cmd="/usr/bin/sv \$1 \$2"
+ elif [ -e /etc/slackware-version -a -d /etc/rc.d ]; then
+ _service_exists="[ -x /etc/rc.d/rc.\$1 ]"
+ _service_cmd="/etc/rc.d/rc.\$1 \$2"
+ _service_status="/etc/rc.d/rc.\$1 status 1>/dev/null 2>&1"
+ else
+ for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
+ if [ -d $x ]; then
+ _service_exists="[ -x $x/\$1 ]"
+ _service_cmd="$x/\$1 \$2"
+ break
+ fi
+ done
+ if [ -e /etc/arch-release ]; then
+ _service_status="[ -e /var/run/daemons/\$1 ]"
+ elif [ "$x" = "/etc/rc.d" -a -e /etc/rc.d/rc.subr ]; then
+ _service_status="$x/\$1 check 1>/dev/null 2>&1"
+ fi
+ fi
+
+ _detected_init=true
+ if [ -z "$_service_cmd" ]; then
+ syslog err "could not detect a useable init system"
+ return 1
+ fi
+ return 0
+}
+
+# Check a system service exists
+service_exists()
+{
+
+ if [ -z "$_service_exists" ]; then
+ detect_init || return 1
+ fi
+ eval $_service_exists
+}
+
+# Send a command to a system service
+service_cmd()
+{
+
+ if [ -z "$_service_cmd" ]; then
+ detect_init || return 1
+ fi
+ eval $_service_cmd
+}
+
+# Send a command to a system service if it is running
+service_status()
+{
+
+ if [ -z "$_service_cmd" ]; then
+ detect_init || return 1
+ fi
+ if [ -n "$_service_status" ]; then
+ eval $_service_status
+ else
+ service_command $1 status >/dev/null 2>&1
+ fi
+}
+
+# Handy macros for our hooks
+service_command()
+{
+
+ service_exists $1 && service_cmd $1 $2
+}
+service_condcommand()
+{
+
+ service_exists $1 && service_status $1 && service_cmd $1 $2
+}
+
+# We source each script into this one so that scripts run earlier can
+# remove variables from the environment so later scripts don't see them.
+# Thus, the user can create their dhcpcd.enter/exit-hook script to configure
+# /etc/resolv.conf how they want and stop the system scripts ever updating it.
+for hook in \
+ @SYSCONFDIR@/dhcpcd.enter-hook \
+ @HOOKDIR@/* \
+ @SYSCONFDIR@/dhcpcd.exit-hook
+do
+ for skip in $skip_hooks; do
+ case "$hook" in
+ */*~) continue 2;;
+ */"$skip") continue 2;;
+ */[0-9][0-9]"-$skip") continue 2;;
+ */[0-9][0-9]"-$skip.sh") continue 2;;
+ esac
+ done
+ if [ -f "$hook" ]; then
+ . "$hook"
+ fi
+done
--- /dev/null
+.\" $NetBSD: dhcpcd.8.in,v 1.44 2015/08/21 10:39:00 roy Exp $
+.\" Copyright (c) 2006-2015 Roy Marples
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 21, 2015
+.Dt DHCPCD 8
+.Os
+.Sh NAME
+.Nm dhcpcd
+.Nd a DHCP client
+.Sh SYNOPSIS
+.Nm
+.Op Fl 46ABbDdEGgHJKLMpqTV
+.Op Fl C , Fl Fl nohook Ar hook
+.Op Fl c , Fl Fl script Ar script
+.Op Fl e , Fl Fl env Ar value
+.Op Fl F , Fl Fl fqdn Ar FQDN
+.Op Fl f , Fl Fl config Ar file
+.Op Fl h , Fl Fl hostname Ar hostname
+.Op Fl I , Fl Fl clientid Ar clientid
+.Op Fl i , Fl Fl vendorclassid Ar vendorclassid
+.Op Fl j , Fl Fl logfile Ar logfile
+.Op Fl l , Fl Fl leasetime Ar seconds
+.Op Fl m , Fl Fl metric Ar metric
+.Op Fl O , Fl Fl nooption Ar option
+.Op Fl o , Fl Fl option Ar option
+.Op Fl Q , Fl Fl require Ar option
+.Op Fl r , Fl Fl request Ar address
+.Op Fl S , Fl Fl static Ar value
+.Op Fl s , Fl Fl inform Ar address Ns Op Ar /cidr
+.Op Fl t , Fl Fl timeout Ar seconds
+.Op Fl u , Fl Fl userclass Ar class
+.Op Fl v , Fl Fl vendor Ar code , Ar value
+.Op Fl W , Fl Fl whitelist Ar address Ns Op Ar /cidr
+.Op Fl w
+.Op Fl Fl waitip Op 4 | 6
+.Op Fl y , Fl Fl reboot Ar seconds
+.Op Fl X , Fl Fl blacklist Ar address Ns Op Ar /cidr
+.Op Fl Z , Fl Fl denyinterfaces Ar pattern
+.Op Fl z , Fl Fl allowinterfaces Ar pattern
+.Op interface
+.Op ...
+.Nm
+.Fl n , Fl Fl rebind
+.Op interface
+.Nm
+.Fl k , Fl Fl release
+.Op interface
+.Nm
+.Fl U, Fl Fl dumplease
+.Ar interface
+.Nm
+.Fl Fl version
+.Nm
+.Fl x , Fl Fl exit
+.Op interface
+.Sh DESCRIPTION
+.Nm
+is an implementation of the DHCP client specified in
+.Li RFC 2131 .
+.Nm
+gets the host information
+.Po
+IP address, routes, etc
+.Pc
+from a DHCP server and configures the network
+.Ar interface
+of the
+machine on which it is running.
+.Nm
+then runs the configuration script which writes DNS information to
+.Xr resolvconf 8 ,
+if available, otherwise directly to
+.Pa /etc/resolv.conf .
+If the hostname is currently blank, (null) or localhost, or
+.Va force_hostname
+is YES or TRUE or 1 then
+.Nm
+sets the hostname to the one supplied by the DHCP server.
+.Nm
+then daemonises and waits for the lease renewal time to lapse.
+It will then attempt to renew its lease and reconfigure if the new lease
+changes when the lease beings to expire or the DHCP server sends message
+to renew early.
+.Pp
+If any interface reports a working carrier then
+.Nm
+will try and obtain a lease before forking to the background,
+otherwise it will fork right away.
+This behaviour can be modified with the
+.Fl b , Fl Fl background
+and
+.Fl w , Fl Fl waitip
+options.
+.Pp
+.Nm
+is also an implementation of the BOOTP client specified in
+.Li RFC 951 .
+.Pp
+.Nm
+is also an implementation of the IPv6 Router Solicitor as specified in
+.Li RFC 4861
+and
+.Li RFC 6106 .
+.Pp
+.Nm
+is also an implementation of the IPv6 Privacy Extensions to AutoConf as
+specified in
+.Li RFC 4941 .
+This feature needs to be enabled in the kernel and
+.Nm
+will start using it.
+.Pp
+.Nm
+is also an implemenation of the DHCPv6 client as specified in
+.Li RFC 3315 .
+By default,
+.Nm
+only starts DHCPv6 when instructed to do so by an IPV6 Router Advertisement.
+If no Identity Association is configured,
+then a Non-temporary Address is requested.
+.Ss Local Link configuration
+If
+.Nm
+failed to obtain a lease, it probes for a valid IPv4LL address
+.Po
+aka ZeroConf, aka APIPA
+.Pc .
+Once obtained it restarts the process of looking for a DHCP server to get a
+proper address.
+.Pp
+When using IPv4LL,
+.Nm
+nearly always succeeds and returns an exit code of 0.
+In the rare case it fails, it normally means that there is a reverse ARP proxy
+installed which always defeats IPv4LL probing.
+To disable this behaviour, you can use the
+.Fl L , Fl Fl noipv4ll
+option.
+.Ss Multiple interfaces
+If a list of interfaces are given on the command line, then
+.Nm
+only works with those interfaces, otherwise
+.Nm
+discovers available Ethernet interfaces that can be configured.
+When
+.Nm
+is operating on more than one interface,
+it is called Master mode. and this behaviour can be forced with the
+.Fl M , Fl Fl master
+option so that an individual interface can start
+.Nm
+but only one instance is running.
+The
+.Nm dhcpcd-ui
+project expects dhcpcd to be running this way.
+.Pp
+If a single interface is given then
+.Nm
+only works for that interface and runs as a separate instance.
+The
+.Fl w , Fl Fl waitip
+option is enabled in this instance to maintain compatibility with older
+versions.
+.Pp
+Interfaces are preferred by carrier, DHCP lease/IPv4LL and then lowest metric.
+For systems that support route metrics, each route will be tagged with the
+metric, otherwise
+.Nm
+changes the routes to use the interface with the same route and the lowest
+metric.
+See options below for controlling which interfaces we allow and deny through
+the use of patterns.
+.Ss Hooking into events
+.Nm
+runs
+.Pa @SCRIPT@ ,
+or the script specified by the
+.Fl c , Fl Fl script
+option.
+This script runs each script found in
+.Pa @HOOKDIR@
+in a lexical order.
+The default installation supplies the scripts
+.Pa 01-test ,
+.Pa 02-dump ,
+.Pa 10-wpa_supplicant ,
+.Pa 15-timezone ,
+.Pa 20-resolv.conf
+and
+.Pa 30-hostname .
+You can disable each script by using the
+.Fl C , Fl Fl nohook
+option.
+See
+.Xr dhcpcd-run-hooks 8
+for details on how these scripts work.
+.Nm
+currently ignores the exit code of the script.
+.Ss Fine tuning
+You can fine-tune the behaviour of
+.Nm
+with the following options:
+.Bl -tag -width indent
+.It Fl b , Fl Fl background
+Background immediately.
+This is useful for startup scripts which don't disable link messages for
+carrier status.
+.It Fl c , Fl Fl script Ar script
+Use this
+.Ar script
+instead of the default
+.Pa @SCRIPT@ .
+.It Fl D , Fl Fl duid
+Generate an
+.Li RFC 4361
+compliant clientid.
+This requires persistent storage and not all DHCP servers work with it so it
+is not enabled by default.
+.Nm
+generates the DUID and stores it in
+.Pa @SYSCONFDIR@/dhcpcd.duid .
+This file should not be copied to other hosts.
+.It Fl d , Fl Fl debug
+Echo debug messages to the stderr and syslog.
+.It Fl E , Fl Fl lastlease
+If
+.Nm
+cannot obtain a lease, then try to use the last lease acquired for the
+interface.
+If the
+.Fl p, Fl Fl persistent
+option is not given then the lease is used if it hasn't expired.
+.It Fl e , Fl Fl env Ar value
+Push
+.Ar value
+to the environment for use in
+.Xr dhcpcd-run-hooks 8 .
+For example, you can force the hostname hook to always set the hostname with
+.Fl e
+.Va force_hostname=YES .
+.It Fl g , Fl Fl reconfigure
+.Nm
+will re-apply IP address, routing and run
+.Xr dhcpcd-run-hooks 8
+for each interface.
+This is useful so that a 3rd party such as PPP or VPN can change the routing
+table and / or DNS, etc and then instruct
+.Nm
+to put things back afterwards.
+.Nm
+does not read a new configuration when this happens - you should rebind if you
+need that functionality.
+.It Fl F , Fl Fl fqdn Ar fqdn
+Requests that the DHCP server updates DNS using FQDN instead of just a
+hostname.
+Valid values for
+.Ar fqdn
+are disable, none, ptr and both.
+.Nm
+itself never does any DNS updates.
+.Nm
+encodes the FQDN hostname as specified in
+.Li RFC1035 .
+.It Fl f , Fl Fl config Ar file
+Specify a config to load instead of
+.Pa @SYSCONFDIR@/dhcpcd.conf .
+.Nm
+always processes the config file before any command line options.
+.It Fl h , Fl Fl hostname Ar hostname
+Sends
+.Ar hostname
+to the DHCP server so it can be registered in DNS.
+If
+.Ar hostname
+is an empty string then the current system hostname is sent.
+If
+.Ar hostname
+is a FQDN (ie, contains a .) then it will be encoded as such.
+.It Fl I , Fl Fl clientid Ar clientid
+Send the
+.Ar clientid .
+If the string is of the format 01:02:03 then it is encoded as hex.
+For interfaces whose hardware address is longer than 8 bytes, or if the
+.Ar clientid
+is an empty string then
+.Nm
+sends a default
+.Ar clientid
+of the hardware family and the hardware address.
+.It Fl i , Fl Fl vendorclassid Ar vendorclassid
+Override the DHCPv4
+.Ar vendorclassid
+field sent.
+The default is
+dhcpcd-<version>:<os>:<machine>:<platform>.
+For example
+.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386
+If not set then none is sent.
+Some badly configured DHCP servers reject unknown vendorclassids.
+To work around it, try and impersonate Windows by using the MSFT vendorclassid.
+.It Fl j , Fl Fl logfile Ar logfile
+Writes to the specified
+.Ar logfile
+rather than
+.Xr syslog 3 .
+The
+.Ar logfile
+is truncated when opened and is reopened when
+.Nm
+receives the
+.Dv SIGUSR2
+signal.
+.It Fl k , Fl Fl release Op Ar interface
+This causes an existing
+.Nm
+process running on the
+.Ar interface
+to release its lease and de-configure the
+.Ar interface
+regardless of the
+.Fl p , Fl Fl persistent
+option.
+If no
+.Ar interface
+is specified then this applies to all interfaces.
+If no interfaces are left running,
+.Nm
+will exit.
+.It Fl l , Fl Fl leasetime Ar seconds
+Request a specific lease time in
+.Ar seconds .
+By default
+.Nm
+does not request any lease time and leaves it in the hands of the
+DHCP server.
+.It Fl M , Fl Fl master
+Start
+.Nm
+in master mode even if only one interface specified on the command line.
+See the Multiple Interfaces section above.
+.It Fl m , Fl Fl metric Ar metric
+Metrics are used to prefer an interface over another one, lowest wins.
+.Nm
+will supply a default metic of 200 +
+.Xr if_nametoindex 3 .
+An extra 100 will be added for wireless interfaces.
+.It Fl n , Fl Fl rebind Op Ar interface
+Notifies
+.Nm
+to reload its configuration and rebind the specified
+.Ar interface .
+If no interface is specified then this applies to all interfaces.
+If
+.Nm
+is not running, then it starts up as normal.
+This may also cause
+.Xr wpa_supplicant 8
+to reload its configuration for each interface as well.
+.It Fl o , Fl Fl option Ar option
+Request the DHCP
+.Ar option
+variable for use in
+.Pa @SCRIPT@ .
+.It Fl p , Fl Fl persistent
+.Nm
+normally de-configures the
+.Ar interface
+and configuration when it exits.
+Sometimes, this isn't desirable if, for example, you have root mounted over
+NFS or SSH clients connect to this host and they need to be notified of
+the host shutting down.
+You can use this option to stop this from happening.
+.It Fl r , Fl Fl request Op Ar address
+Request the
+.Ar address
+in the DHCP DISCOVER message.
+There is no guarantee this is the address the DHCP server will actually give.
+If no
+.Ar address
+is given then the first address currently assigned to the
+.Ar interface
+is used.
+.It Fl s , Fl Fl inform Op Ar address Ns Op Ar /cidr
+Behaves like
+.Fl r , Fl Fl request
+as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST.
+This does not get a lease as such, just notifies the DHCP server of the
+.Ar address
+in use.
+You should also include the optional
+.Ar cidr
+network number in case the address is not already configured on the interface.
+.Nm
+remains running and pretends it has an infinite lease.
+.Nm
+will not de-configure the interface when it exits.
+If
+.Nm
+fails to contact a DHCP server then it returns a failure instead of falling
+back on IPv4LL.
+.It Fl S, Fl Fl static Ar value
+Configures a static DHCP
+.Ar value .
+If you set
+.Ic ip_address
+then
+.Nm
+will not attempt to obtain a lease and just use the value for the address with
+an infinite lease time.
+.Pp
+Here is an example which configures a static address, routes and dns.
+.D1 dhcpcd -S ip_address=192.168.0.10/24 \e
+.D1 -S routers=192.168.0.1 \e
+.D1 -S domain_name_servers=192.168.0.1 \e
+.D1 eth0
+.Pp
+You cannot presently set static DHCPv6 values.
+Use the
+.Fl e , Fl Fl env
+option instead.
+.It Fl t , Fl Fl timeout Ar seconds
+Timeout after
+.Ar seconds ,
+instead of the default 30.
+A setting of 0
+.Ar seconds
+causes
+.Nm
+to wait forever to get a lease.
+If
+.Nm
+is working on a single interface then
+.Nm
+will exit when a timeout occurs, otherwise
+.Nm
+will fork into the background.
+.It Fl u , Fl Fl userclass Ar class
+Tags the DHCPv4 message with the userclass
+.Ar class .
+DHCP servers use this to give members of the class DHCP options other than the
+default, without having to know things like hardware address or hostname.
+.It Fl v , Fl Fl vendor Ar code , Ns Ar value
+Add an encapsulated vendor option.
+.Ar code
+should be between 1 and 254 inclusive.
+To add a raw vendor string, omit
+.Ar code
+but keep the comma.
+Examples.
+.Pp
+Set the vendor option 01 with an IP address.
+.D1 dhcpcd \-v 01,192.168.0.2 eth0
+Set the vendor option 02 with a hex code.
+.D1 dhcpcd \-v 02,01:02:03:04:05 eth0
+Set the vendor option 03 with an IP address as a string.
+.D1 dhcpcd \-v 03,\e"192.168.0.2\e" eth0
+Set un-encapsulated vendor option to hello world.
+.D1 dhcpcd \-v ,"hello world" eth0
+.It Fl Fl version
+Display both program version and copyright information.
+.Nm
+then exits before doing any configuration.
+.It Fl w
+Wait for an address to be assigned before forking to the background.
+Does not take an argument, unlike the below option.
+.Fl fl waitip
+option.
+.It Fl Fl waitip Op 4 | 6
+Wait for an address to be assigned before forking to the background.
+4 means wait for an IPv4 address to be assigned.
+6 means wait for an IPv6 address to be assigned.
+If no argument is given,
+.Nm
+will wait for any address protocol to be assigned.
+It is possible to wait for more than one address protocol and
+.Nm
+will only fork to the background when all waiting conditions are satisfied.
+.It Fl x , Fl Fl exit Op Ar interface
+This will signal an existing
+.Nm
+process running on the
+.Ar interface
+to exit.
+If no interface is specified, then the above is applied to all interfaces.
+See the
+.Fl p , Fl Fl persistent
+option to control configuration persistence on exit,
+which is enabled by default in
+.Xr dhcpcd.conf 5 .
+.Nm
+then waits until this process has exited.
+.It Fl y , Fl Fl reboot Ar seconds
+Allow
+.Ar reboot
+seconds before moving to the discover phase if we have an old lease to use.
+Allow
+.Ar reboot
+seconds before starting fallback states from the discover phase.
+IPv4LL is started when the first
+.Ar reboot
+timeout is reached.
+The default is 5 seconds.
+A setting of 0 seconds causes
+.Nm
+to skip the reboot phase and go straight into discover.
+This has no effect on DHCPv6 other than skipping the reboot phase.
+.El
+.Ss Restricting behaviour
+.Nm
+will try to do as much as it can by default.
+However, there are sometimes situations where you don't want the things to be
+configured exactly how the the DHCP server wants.
+Here are some options that deal with turning these bits off.
+.Bl -tag -width indent
+.It Fl 4 , Fl Fl ipv4only
+Configure IPv4 only.
+.It Fl 6 , Fl Fl ipv6only
+Configure IPv6 only.
+.It Fl A , Fl Fl noarp
+Don't request or claim the address by ARP.
+This also disables IPv4LL.
+.It Fl B , Fl Fl nobackground
+Don't run in the background when we acquire a lease.
+This is mainly useful for running under the control of another process, such
+as a debugger or a network manager.
+.It Fl C , Fl Fl nohook Ar script
+Don't run this hook script.
+Matches full name, or prefixed with 2 numbers optionally ending with
+.Pa .sh .
+.Pp
+So to stop
+.Nm
+from touching your DNS settings you would do:-
+.D1 dhcpcd -C resolv.conf eth0
+.It Fl G , Fl Fl nogateway
+Don't set any default routes.
+.It Fl H , Fl Fl xidhwaddr
+Use the last four bytes of the hardware address as the DHCP xid instead
+of a randomly generated number.
+.It Fl J , Fl Fl broadcast
+Instructs the DHCP server to broadcast replies back to the client.
+Normally this is only set for non Ethernet interfaces,
+such as FireWire and InfiniBand.
+In most instances,
+.Nm
+will set this automatically.
+.It Fl K , Fl Fl nolink
+Don't receive link messages for carrier status.
+You should only have to use this with buggy device drivers or running
+.Nm
+through a network manager.
+.It Fl L , Fl Fl noipv4ll
+Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf).
+.It Fl O , Fl Fl nooption Ar option
+Don't request the specified option.
+If no option given, then don't request any options other than those to
+configure the interface and routing.
+.It Fl Q , Fl Fl require Ar option
+Requires the
+.Ar option
+to be present in all DHCP messages, otherwise the message is ignored.
+To enforce that
+.Nm
+only responds to DHCP servers and not BOOTP servers, you can
+.Fl Q
+.Ar dhcp_message_type .
+.It Fl q , Fl Fl quiet
+Quiet
+.Nm
+on the command line, only warnings and errors will be displayed.
+The messages are still logged though.
+.It Fl T, Fl Fl test
+On receipt of DHCP messages just call
+.Pa @SCRIPT@
+with the reason of TEST which echos the DHCP variables found in the message
+to the console.
+The interface configuration isn't touched and neither are any configuration
+files.
+The
+.Ar rapid_commit
+option is not sent in TEST mode so that the server does not lease an address.
+To test INFORM the interface needs to be configured with the desired address
+before starting
+.Nm .
+.It Fl U, Fl Fl dumplease Ar interface
+Dumps the last lease for the
+.Ar interface
+to stdout.
+.Ar interface
+could also be a path to a DHCP wire formatted file.
+Use the
+.Fl 4
+or
+.Fl 6
+flags to specify an address family.
+.It Fl V, Fl Fl variables
+Display a list of option codes, the associated variable and encoding for use in
+.Xr dhcpcd-run-hooks 8 .
+Variables are prefixed with new_ and old_ unless the option number is -.
+Variables without an option are part of the DHCP message and cannot be
+directly requested.
+.It Fl W, Fl Fl whitelist Ar address Ns Op /cidr
+Only accept packets from
+.Ar address Ns Op /cidr .
+.Fl X, Fl Fl blacklist
+is ignored if
+.Fl W, Fl Fl whitelist
+is set.
+.It Fl X, Fl Fl blacklist Ar address Ns Op Ar /cidr
+Ignore all packets from
+.Ar address Ns Op Ar /cidr .
+.It Fl Z , Fl Fl denyinterfaces Ar pattern
+When discovering interfaces, the interface name must not match
+.Ar pattern
+which is a space or comma separated list of patterns passed to
+.Xr fnmatch 3 .
+.It Fl z , Fl Fl allowinterfaces Ar pattern
+When discovering interfaces, the interface name must match
+.Ar pattern
+which is a space or comma separated list of patterns passed to
+.Xr fnmatch 3 .
+If the same interface is matched in
+.Fl Z , Fl Fl denyinterfaces
+then it is still denied.
+.It Fl Fl nodev
+Don't load any
+.Pa /dev
+management modules.
+.El
+.Sh 3RDPARTY LINK MANAGEMENT
+Some interfaces require configuration by 3rd parties, such as PPP or VPN.
+When an interface configuration in
+.Nm
+is marked as STATIC or INFORM without an address then
+.Nm
+will monitor the interface until an address is added or removed from it and
+act accordingly.
+For point to point interfaces (like PPP), a default route to its
+destination is automatically added to the configuration.
+If the point to point interface is configured for INFORM, then
+.Nm
+unicasts INFORM to the destination, otherwise it defaults to STATIC.
+.Sh NOTES
+.Nm
+requires a Berkley Packet Filter, or BPF device on BSD based systems and a
+Linux Socket Filter, or LPF device on Linux based systems for all IPv4
+configuration.
+.Pp
+If restricting
+.Nm
+to a single interface and optionally address family via the command-line
+then all futher calls to
+.Nm
+to rebind, reconfigure or exit need to include the same restrictive flags
+so that
+.Nm
+knows which process to signal.
+.Sh FILES
+.Bl -ohang
+.It Pa @SYSCONFDIR@/dhcpcd.conf
+Configuration file for dhcpcd.
+If you always use the same options, put them here.
+.It Pa @SYSCONFDIR@/dhcpcd.duid
+Text file that holds the DUID used to identify the host.
+.It Pa @SYSCONFDIR@/dhcpcd.secret
+Text file that holds a secret key known only to the host.
+.It Pa @SCRIPT@
+Bourne shell script that is run to configure or de-configure an interface.
+.It Pa @LIBDIR@/dhcpcd/dev
+.Pa /dev
+management modules.
+.It Pa @HOOKDIR@
+A directory containing bourne shell scripts that are run by the above script.
+Each script can be disabled by using the
+.Fl C , Fl Fl nohook
+option described above.
+.It Pa @DBDIR@/dhcpcd\- Ns Ar interface Ns Ar -ssid Ns .lease
+The actual DHCP message sent by the server.
+We use this when reading the last
+lease and use the files mtime as when it was issued.
+.It Pa @DBDIR@/dhcpcd\- Ns Ar interface Ns Ar -ssid Ns .lease6
+The actual DHCPv6 message sent by the server.
+We use this when reading the last
+lease and use the files mtime as when it was issued.
+.It Pa @DBDIR@/dhcpcd-rdm.monotonic
+Stores the monotonic counter used in the
+.Ar replay
+field in Authentication Options.
+.It Pa @RUNDIR@/dhcpcd.pid
+Stores the PID of
+.Nm
+running on all interfaces.
+.It Pa @RUNDIR@/dhcpcd\- Ns Ar interface Ns .pid
+Stores the PID of
+.Nm
+running on the
+.Ar interface .
+.It Pa @RUNDIR@/dhcpcd.sock
+Control socket to the master daemon.
+.It Pa @RUNDIR@/dhcpcd.unpriv.sock
+Unpriviledged socket to the master daemon, only allows state retrieval.
+.It Pa @RUNDIR@/dhcpcd\- Ns Ar interface Ns .sock
+Control socket to per interface daemon.
+.El
+.Sh SEE ALSO
+.Xr fnmatch 3 ,
+.Xr if_nametoindex 3 ,
+.Xr dhcpcd.conf 5 ,
+.Xr resolv.conf 5 ,
+.Xr dhcpcd-run-hooks 8 ,
+.Xr resolvconf 8
+.Sh STANDARDS
+RFC\ 951, RFC\ 1534, RFC\ 2104, RFC\ 2131, RFC\ 2132, RFC\ 2563, RFC\ 2855,
+RFC\ 3004, RFC\ 3118, RFC\ 3203, RFC\ 3315, RFC\ 3361, RFC\ 3633, RFC\ 3396,
+RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075,
+RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833,
+RFC\ 4941, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106, RFC\ 6334, RFC\ 6603,
+RFC\ 6704, RFC\ 7217, RFC\ 7550.
+.Sh AUTHORS
+.An Roy Marples Aq Mt roy@marples.name
+.Sh BUGS
+Please report them to
+.Lk http://roy.marples.name/projects/dhcpcd
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: dhcpcd.c,v 1.28 2015/09/04 12:25:01 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+const char dhcpcd_copyright[] = "Copyright (c) 2006-2015 Roy Marples";
+
+#define _WITH_DPRINTF /* Stop FreeBSD bitching */
+
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "config.h"
+#include "arp.h"
+#include "common.h"
+#include "control.h"
+#include "dev.h"
+#include "dhcpcd.h"
+#include "dhcp6.h"
+#include "duid.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+#include "script.h"
+
+#ifdef USE_SIGNALS
+const int dhcpcd_signals[] = {
+ SIGTERM,
+ SIGINT,
+ SIGALRM,
+ SIGHUP,
+ SIGUSR1,
+ SIGUSR2,
+ SIGPIPE
+};
+const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals);
+#endif
+
+#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK)
+static pid_t
+read_pid(const char *pidfile)
+{
+ FILE *fp;
+ pid_t pid;
+
+ if ((fp = fopen(pidfile, "r")) == NULL) {
+ errno = ENOENT;
+ return 0;
+ }
+ if (fscanf(fp, "%d", &pid) != 1)
+ pid = 0;
+ fclose(fp);
+ return pid;
+}
+
+static int
+write_pid(int fd, pid_t pid)
+{
+
+ if (ftruncate(fd, (off_t)0) == -1)
+ return -1;
+ lseek(fd, (off_t)0, SEEK_SET);
+ return dprintf(fd, "%d\n", (int)pid);
+}
+#endif
+
+static void
+usage(void)
+{
+
+printf("usage: "PACKAGE"\t[-46ABbDdEGgHJKkLnpqTVw]\n"
+ "\t\t[-C, --nohook hook] [-c, --script script]\n"
+ "\t\t[-e, --env value] [-F, --fqdn FQDN] [-f, --config file]\n"
+ "\t\t[-h, --hostname hostname] [-I, --clientid clientid]\n"
+ "\t\t[-i, --vendorclassid vendorclassid] [-l, --leasetime seconds]\n"
+ "\t\t[-m, --metric metric] [-O, --nooption option]\n"
+ "\t\t[-o, --option option] [-Q, --require option]\n"
+ "\t\t[-r, --request address] [-S, --static value]\n"
+ "\t\t[-s, --inform address[/cidr]] [-t, --timeout seconds]\n"
+ "\t\t[-u, --userclass class] [-v, --vendor code, value]\n"
+ "\t\t[-W, --whitelist address[/cidr]] [-y, --reboot seconds]\n"
+ "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n"
+ "\t\t[-z, --allowinterfaces pattern] [interface] [...]\n"
+ " "PACKAGE"\t-k, --release [interface]\n"
+ " "PACKAGE"\t-U, --dumplease interface\n"
+ " "PACKAGE"\t--version\n"
+ " "PACKAGE"\t-x, --exit [interface]\n");
+}
+
+static void
+free_globals(struct dhcpcd_ctx *ctx)
+{
+ struct dhcp_opt *opt;
+
+ if (ctx->ifac) {
+ for (; ctx->ifac > 0; ctx->ifac--)
+ free(ctx->ifav[ctx->ifac - 1]);
+ free(ctx->ifav);
+ ctx->ifav = NULL;
+ }
+ if (ctx->ifdc) {
+ for (; ctx->ifdc > 0; ctx->ifdc--)
+ free(ctx->ifdv[ctx->ifdc - 1]);
+ free(ctx->ifdv);
+ ctx->ifdv = NULL;
+ }
+ if (ctx->ifcc) {
+ for (; ctx->ifcc > 0; ctx->ifcc--)
+ free(ctx->ifcv[ctx->ifcc - 1]);
+ free(ctx->ifcv);
+ ctx->ifcv = NULL;
+ }
+
+#ifdef INET
+ if (ctx->dhcp_opts) {
+ for (opt = ctx->dhcp_opts;
+ ctx->dhcp_opts_len > 0;
+ opt++, ctx->dhcp_opts_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ctx->dhcp_opts);
+ ctx->dhcp_opts = NULL;
+ }
+#endif
+#ifdef INET6
+ if (ctx->nd_opts) {
+ for (opt = ctx->nd_opts;
+ ctx->nd_opts_len > 0;
+ opt++, ctx->nd_opts_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ctx->nd_opts);
+ ctx->nd_opts = NULL;
+ }
+ if (ctx->dhcp6_opts) {
+ for (opt = ctx->dhcp6_opts;
+ ctx->dhcp6_opts_len > 0;
+ opt++, ctx->dhcp6_opts_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ctx->dhcp6_opts);
+ ctx->dhcp6_opts = NULL;
+ }
+#endif
+ if (ctx->vivso) {
+ for (opt = ctx->vivso;
+ ctx->vivso_len > 0;
+ opt++, ctx->vivso_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ctx->vivso);
+ ctx->vivso = NULL;
+ }
+}
+
+static void
+handle_exit_timeout(void *arg)
+{
+ struct dhcpcd_ctx *ctx;
+
+ ctx = arg;
+ logger(ctx, LOG_ERR, "timed out");
+ if (!(ctx->options & DHCPCD_MASTER)) {
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+ ctx->options |= DHCPCD_NOWAITIP;
+ dhcpcd_daemonise(ctx);
+}
+
+static const char *
+dhcpcd_af(int af)
+{
+
+ switch (af) {
+ case AF_UNSPEC:
+ return "IP";
+ case AF_INET:
+ return "IPv4";
+ case AF_INET6:
+ return "IPv6";
+ default:
+ return NULL;
+ }
+}
+
+int
+dhcpcd_ifafwaiting(const struct interface *ifp)
+{
+ unsigned long long opts;
+
+ opts = ifp->options->options;
+ if (opts & DHCPCD_WAITIP4 && !ipv4_hasaddr(ifp))
+ return AF_INET;
+ if (opts & DHCPCD_WAITIP6 && !ipv6_hasaddr(ifp))
+ return AF_INET6;
+ if (opts & DHCPCD_WAITIP &&
+ !(opts & (DHCPCD_WAITIP4 | DHCPCD_WAITIP6)) &&
+ !ipv4_hasaddr(ifp) && !ipv6_hasaddr(ifp))
+ return AF_UNSPEC;
+ return AF_MAX;
+}
+
+int
+dhcpcd_afwaiting(const struct dhcpcd_ctx *ctx)
+{
+ unsigned long long opts;
+ const struct interface *ifp;
+ int af;
+
+ if (!(ctx->options & DHCPCD_WAITOPTS))
+ return AF_MAX;
+
+ opts = ctx->options;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP4) &&
+ ipv4_hasaddr(ifp))
+ opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP4);
+ if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP6) &&
+ ipv6_hasaddr(ifp))
+ opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP6);
+ if (!(opts & DHCPCD_WAITOPTS))
+ break;
+ }
+ if (opts & DHCPCD_WAITIP)
+ af = AF_UNSPEC;
+ else if (opts & DHCPCD_WAITIP4)
+ af = AF_INET;
+ else if (opts & DHCPCD_WAITIP6)
+ af = AF_INET6;
+ else
+ return AF_MAX;
+ return af;
+}
+
+static int
+dhcpcd_ipwaited(struct dhcpcd_ctx *ctx)
+{
+ struct interface *ifp;
+ int af;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) {
+ logger(ctx, LOG_DEBUG,
+ "%s: waiting for an %s address",
+ ifp->name, dhcpcd_af(af));
+ return 0;
+ }
+ }
+
+ if ((af = dhcpcd_afwaiting(ctx)) != AF_MAX) {
+ logger(ctx, LOG_DEBUG,
+ "waiting for an %s address",
+ dhcpcd_af(af));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Returns the pid of the child, otherwise 0. */
+pid_t
+dhcpcd_daemonise(struct dhcpcd_ctx *ctx)
+{
+#ifdef THERE_IS_NO_FORK
+ eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx);
+ errno = ENOSYS;
+ return 0;
+#else
+ pid_t pid;
+ char buf = '\0';
+ int sidpipe[2], fd;
+
+ if (ctx->options & DHCPCD_DAEMONISE &&
+ !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP)))
+ {
+ if (!dhcpcd_ipwaited(ctx))
+ return 0;
+ }
+
+ eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx);
+ if (ctx->options & DHCPCD_DAEMONISED ||
+ !(ctx->options & DHCPCD_DAEMONISE))
+ return 0;
+ /* Setup a signal pipe so parent knows when to exit. */
+ if (pipe(sidpipe) == -1) {
+ logger(ctx, LOG_ERR, "pipe: %m");
+ return 0;
+ }
+ logger(ctx, LOG_DEBUG, "forking to background");
+ switch (pid = fork()) {
+ case -1:
+ logger(ctx, LOG_ERR, "fork: %m");
+ return 0;
+ case 0:
+ setsid();
+ /* Some polling methods don't survive after forking,
+ * so ensure we can requeue all our events. */
+ if (eloop_requeue(ctx->eloop) == -1) {
+ logger(ctx, LOG_ERR, "eloop_requeue: %m");
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ }
+ /* Notify parent it's safe to exit as we've detached. */
+ close(sidpipe[0]);
+ if (write(sidpipe[1], &buf, 1) == -1)
+ logger(ctx, LOG_ERR, "failed to notify parent: %m");
+ close(sidpipe[1]);
+ if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ close(fd);
+ }
+ break;
+ default:
+ /* Wait for child to detach */
+ close(sidpipe[1]);
+ if (read(sidpipe[0], &buf, 1) == -1)
+ logger(ctx, LOG_ERR, "failed to read child: %m");
+ close(sidpipe[0]);
+ break;
+ }
+ /* Done with the fd now */
+ if (pid != 0) {
+ logger(ctx, LOG_INFO, "forked to background, child pid %d", pid);
+ write_pid(ctx->pid_fd, pid);
+ close(ctx->pid_fd);
+ ctx->pid_fd = -1;
+ ctx->options |= DHCPCD_FORKED;
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return pid;
+ }
+ ctx->options |= DHCPCD_DAEMONISED;
+ return pid;
+#endif
+}
+
+static void
+dhcpcd_drop(struct interface *ifp, int stop)
+{
+
+ dhcp6_drop(ifp, stop ? NULL : "EXPIRE6");
+ ipv6nd_drop(ifp);
+ ipv6_drop(ifp);
+ ipv4ll_drop(ifp);
+ dhcp_drop(ifp, stop ? "STOP" : "EXPIRE");
+ arp_close(ifp);
+}
+
+static void
+stop_interface(struct interface *ifp)
+{
+ struct dhcpcd_ctx *ctx;
+
+ ctx = ifp->ctx;
+ logger(ctx, LOG_INFO, "%s: removing interface", ifp->name);
+ ifp->options->options |= DHCPCD_STOPPING;
+
+ dhcpcd_drop(ifp, 1);
+ if (ifp->options->options & DHCPCD_DEPARTED)
+ script_runreason(ifp, "DEPARTED");
+ else
+ script_runreason(ifp, "STOPPED");
+
+ /* Delete all timeouts for the interfaces */
+ eloop_q_timeout_delete(ctx->eloop, 0, NULL, ifp);
+
+ /* Remove the interface from our list */
+ TAILQ_REMOVE(ifp->ctx->ifaces, ifp, next);
+ if_free(ifp);
+
+ if (!(ctx->options & (DHCPCD_MASTER | DHCPCD_TEST)))
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+}
+
+static void
+configure_interface1(struct interface *ifp)
+{
+ struct if_options *ifo = ifp->options;
+ int ra_global, ra_iface;
+#ifdef INET6
+ size_t i;
+#endif
+
+ /* Do any platform specific configuration */
+ if_conf(ifp);
+
+ /* If we want to release a lease, we can't really persist the
+ * address either. */
+ if (ifo->options & DHCPCD_RELEASE)
+ ifo->options &= ~DHCPCD_PERSISTENT;
+
+ if (ifp->flags & IFF_POINTOPOINT && !(ifo->options & DHCPCD_INFORM))
+ ifo->options |= DHCPCD_STATIC;
+ if (ifp->flags & IFF_NOARP ||
+ ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))
+ ifo->options &= ~DHCPCD_IPV4LL;
+ if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK) ||
+ !(ifp->flags & IFF_MULTICAST))
+ ifo->options &= ~DHCPCD_IPV6RS;
+
+ if (ifo->metric != -1)
+ ifp->metric = (unsigned int)ifo->metric;
+
+ if (!(ifo->options & DHCPCD_IPV4))
+ ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL | DHCPCD_WAITIP4);
+
+ if (!(ifo->options & DHCPCD_IPV6))
+ ifo->options &=
+ ~(DHCPCD_IPV6RS | DHCPCD_DHCP6 | DHCPCD_WAITIP6);
+
+ if (ifo->options & DHCPCD_SLAACPRIVATE &&
+ !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST)))
+ ifo->options |= DHCPCD_IPV6RA_OWN;
+
+ /* We want to disable kernel interface RA as early as possible. */
+ if (ifo->options & DHCPCD_IPV6RS &&
+ !(ifp->ctx->options & DHCPCD_DUMPLEASE))
+ {
+ /* If not doing any DHCP, disable the RDNSS requirement. */
+ if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6)))
+ ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS;
+ ra_global = if_checkipv6(ifp->ctx, NULL,
+ ifp->ctx->options & DHCPCD_IPV6RA_OWN ? 1 : 0);
+ ra_iface = if_checkipv6(ifp->ctx, ifp,
+ ifp->options->options & DHCPCD_IPV6RA_OWN ? 1 : 0);
+ if (ra_global == -1 || ra_iface == -1)
+ ifo->options &= ~DHCPCD_IPV6RS;
+ else if (ra_iface == 0 &&
+ !(ifp->ctx->options & DHCPCD_TEST))
+ ifo->options |= DHCPCD_IPV6RA_OWN;
+ }
+
+ /* If we haven't specified a ClientID and our hardware address
+ * length is greater than DHCP_CHADDR_LEN then we enforce a ClientID
+ * of the hardware address family and the hardware address.
+ * If there is no hardware address and no ClientID set,
+ * force a DUID based ClientID. */
+ if (ifp->hwlen > DHCP_CHADDR_LEN)
+ ifo->options |= DHCPCD_CLIENTID;
+ else if (ifp->hwlen == 0 && !(ifo->options & DHCPCD_CLIENTID))
+ ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID;
+
+ /* Firewire and InfiniBand interfaces require ClientID and
+ * the broadcast option being set. */
+ switch (ifp->family) {
+ case ARPHRD_IEEE1394: /* FALLTHROUGH */
+ case ARPHRD_INFINIBAND:
+ ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST;
+ break;
+ }
+
+ if (!(ifo->options & DHCPCD_IAID)) {
+ /*
+ * An IAID is for identifying a unqiue interface within
+ * the client. It is 4 bytes long. Working out a default
+ * value is problematic.
+ *
+ * Interface name and number are not stable
+ * between different OS's. Some OS's also cannot make
+ * up their mind what the interface should be called
+ * (yes, udev, I'm looking at you).
+ * Also, the name could be longer than 4 bytes.
+ * Also, with pluggable interfaces the name and index
+ * could easily get swapped per actual interface.
+ *
+ * The MAC address is 6 bytes long, the final 3
+ * being unique to the manufacturer and the initial 3
+ * being unique to the organisation which makes it.
+ * We could use the last 4 bytes of the MAC address
+ * as the IAID as it's the most stable part given the
+ * above, but equally it's not guaranteed to be
+ * unique.
+ *
+ * Given the above, and our need to reliably work
+ * between reboots without persitent storage,
+ * generating the IAID from the MAC address is the only
+ * logical default.
+ *
+ * dhclient uses the last 4 bytes of the MAC address.
+ * dibbler uses an increamenting counter.
+ * wide-dhcpv6 uses 0 or a configured value.
+ * odhcp6c uses 1.
+ * Windows 7 uses the first 3 bytes of the MAC address
+ * and an unknown byte.
+ * dhcpcd-6.1.0 and earlier used the interface name,
+ * falling back to interface index if name > 4.
+ */
+ if (ifp->hwlen >= sizeof(ifo->iaid))
+ memcpy(ifo->iaid,
+ ifp->hwaddr + ifp->hwlen - sizeof(ifo->iaid),
+ sizeof(ifo->iaid));
+ else {
+ uint32_t len;
+
+ len = (uint32_t)strlen(ifp->name);
+ if (len <= sizeof(ifo->iaid)) {
+ memcpy(ifo->iaid, ifp->name, len);
+ if (len < sizeof(ifo->iaid))
+ memset(ifo->iaid + len, 0,
+ sizeof(ifo->iaid) - len);
+ } else {
+ /* IAID is the same size as a uint32_t */
+ len = htonl(ifp->index);
+ memcpy(ifo->iaid, &len, sizeof(len));
+ }
+ }
+ ifo->options |= DHCPCD_IAID;
+ }
+
+#ifdef INET6
+ if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 &&
+ ifp->name[0] != '\0')
+ {
+ ifo->ia = malloc(sizeof(*ifo->ia));
+ if (ifo->ia == NULL)
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ else {
+ ifo->ia_len = 1;
+ ifo->ia->ia_type = D6_OPTION_IA_NA;
+ memcpy(ifo->ia->iaid, ifo->iaid, sizeof(ifo->iaid));
+ memset(&ifo->ia->addr, 0, sizeof(ifo->ia->addr));
+ ifo->ia->sla = NULL;
+ ifo->ia->sla_len = 0;
+ }
+ } else {
+ for (i = 0; i < ifo->ia_len; i++) {
+ if (!ifo->ia[i].iaid_set) {
+ memcpy(&ifo->ia[i].iaid, ifo->iaid,
+ sizeof(ifo->ia[i].iaid));
+ ifo->ia[i].iaid_set = 1;
+ }
+ }
+ }
+#endif
+}
+
+int
+dhcpcd_selectprofile(struct interface *ifp, const char *profile)
+{
+ struct if_options *ifo;
+ char pssid[PROFILE_LEN];
+
+ if (ifp->ssid_len) {
+ ssize_t r;
+
+ r = print_string(pssid, sizeof(pssid), ESCSTRING,
+ ifp->ssid, ifp->ssid_len);
+ if (r == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: %s: %m", ifp->name, __func__);
+ pssid[0] = '\0';
+ }
+ } else
+ pssid[0] = '\0';
+ ifo = read_config(ifp->ctx, ifp->name, pssid, profile);
+ if (ifo == NULL) {
+ logger(ifp->ctx, LOG_DEBUG, "%s: no profile %s",
+ ifp->name, profile);
+ return -1;
+ }
+ if (profile != NULL) {
+ strlcpy(ifp->profile, profile, sizeof(ifp->profile));
+ logger(ifp->ctx, LOG_INFO, "%s: selected profile %s",
+ ifp->name, profile);
+ } else
+ *ifp->profile = '\0';
+
+ free_options(ifp->options);
+ ifp->options = ifo;
+ if (profile)
+ configure_interface1(ifp);
+ return 1;
+}
+
+static void
+configure_interface(struct interface *ifp, int argc, char **argv,
+ unsigned long long options)
+{
+ time_t old;
+
+ old = ifp->options ? ifp->options->mtime : 0;
+ dhcpcd_selectprofile(ifp, NULL);
+ add_options(ifp->ctx, ifp->name, ifp->options, argc, argv);
+ ifp->options->options |= options;
+ configure_interface1(ifp);
+
+ /* If the mtime has changed drop any old lease */
+ if (ifp->options && old != 0 && ifp->options->mtime != old) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: confile file changed, expiring leases", ifp->name);
+ dhcpcd_drop(ifp, 0);
+ }
+}
+
+static void
+dhcpcd_pollup(void *arg)
+{
+ struct interface *ifp = arg;
+ int carrier;
+
+ carrier = if_carrier(ifp); /* will set ifp->flags */
+ if (carrier == LINK_UP && !(ifp->flags & IFF_UP)) {
+ struct timespec tv;
+
+ tv.tv_sec = 0;
+ tv.tv_nsec = IF_POLL_UP * NSEC_PER_MSEC;
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcpcd_pollup, ifp);
+ return;
+ }
+
+ dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name);
+}
+
+void
+dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags,
+ const char *ifname)
+{
+ struct interface *ifp;
+
+ ifp = if_find(ctx->ifaces, ifname);
+ if (ifp == NULL || !(ifp->options->options & DHCPCD_LINK))
+ return;
+
+ switch(carrier) {
+ case LINK_UNKNOWN:
+ carrier = if_carrier(ifp); /* will set ifp->flags */
+ break;
+ case LINK_UP:
+ /* we have a carrier! Still need to check for IFF_UP */
+ if (flags & IFF_UP)
+ ifp->flags = flags;
+ else {
+ /* So we need to poll for IFF_UP as there is no
+ * kernel notification when it's set. */
+ dhcpcd_pollup(ifp);
+ return;
+ }
+ break;
+ default:
+ ifp->flags = flags;
+ }
+
+ /* If we here, we don't need to poll for IFF_UP any longer
+ * if generated by a kernel event. */
+ eloop_timeout_delete(ifp->ctx->eloop, dhcpcd_pollup, ifp);
+
+ if (carrier == LINK_UNKNOWN) {
+ if (errno != ENOTTY) /* For example a PPP link on BSD */
+ logger(ctx, LOG_ERR, "%s: carrier_status: %m", ifname);
+ } else if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) {
+ if (ifp->carrier != LINK_DOWN) {
+ if (ifp->carrier == LINK_UP)
+ logger(ctx, LOG_INFO, "%s: carrier lost",
+ ifp->name);
+ ifp->carrier = LINK_DOWN;
+ script_runreason(ifp, "NOCARRIER");
+#ifdef NOCARRIER_PRESERVE_IP
+ arp_close(ifp);
+ dhcp_abort(ifp);
+ if_sortinterfaces(ctx);
+ ipv4_preferanother(ifp);
+ ipv6nd_expire(ifp, 0);
+#else
+ dhcpcd_drop(ifp, 0);
+#endif
+ }
+ } else if (carrier == LINK_UP && ifp->flags & IFF_UP) {
+ if (ifp->carrier != LINK_UP) {
+ logger(ctx, LOG_INFO, "%s: carrier acquired",
+ ifp->name);
+ ifp->carrier = LINK_UP;
+#if !defined(__linux__) && !defined(__NetBSD__)
+ /* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the
+ * hardware address changes so we have to go
+ * through the disovery process to work it out. */
+ dhcpcd_handleinterface(ctx, 0, ifp->name);
+#endif
+ if (ifp->wireless) {
+ uint8_t ossid[IF_SSIDSIZE];
+#ifdef NOCARRIER_PRESERVE_IP
+ size_t olen;
+
+ olen = ifp->ssid_len;
+#endif
+ memcpy(ossid, ifp->ssid, ifp->ssid_len);
+ if_getssid(ifp);
+#ifdef NOCARRIER_PRESERVE_IP
+ /* If we changed SSID network, drop leases */
+ if (ifp->ssid_len != olen ||
+ memcmp(ifp->ssid, ossid, ifp->ssid_len))
+ dhcpcd_drop(ifp, 0);
+#endif
+ }
+ dhcpcd_initstate(ifp, 0);
+ script_runreason(ifp, "CARRIER");
+#ifdef NOCARRIER_PRESERVE_IP
+ /* Set any IPv6 Routers we remembered to expire
+ * faster than they would normally as we
+ * maybe on a new network. */
+ ipv6nd_expire(ifp, RTR_CARRIER_EXPIRE);
+#endif
+ /* RFC4941 Section 3.5 */
+ if (ifp->options->options & DHCPCD_IPV6RA_OWN)
+ ipv6_gentempifid(ifp);
+ dhcpcd_startinterface(ifp);
+ }
+ }
+}
+
+static void
+warn_iaid_conflict(struct interface *ifp, uint8_t *iaid)
+{
+ struct interface *ifn;
+ size_t i;
+
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ifn == ifp)
+ continue;
+ if (memcmp(ifn->options->iaid, iaid,
+ sizeof(ifn->options->iaid)) == 0)
+ break;
+ for (i = 0; i < ifn->options->ia_len; i++) {
+ if (memcmp(&ifn->options->ia[i].iaid, iaid,
+ sizeof(ifn->options->ia[i].iaid)) == 0)
+ break;
+ }
+ }
+
+ /* This is only a problem if the interfaces are on the same network. */
+ if (ifn)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: IAID conflicts with one assigned to %s",
+ ifp->name, ifn->name);
+}
+
+static void
+pre_start(struct interface *ifp)
+{
+
+ /* Add our link-local address before upping the interface
+ * so our RFC7217 address beats the hwaddr based one.
+ * This is also a safety check incase it was ripped out
+ * from under us. */
+ if (ifp->options->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name);
+ ifp->options->options &= ~DHCPCD_IPV6;
+ }
+}
+
+void
+dhcpcd_startinterface(void *arg)
+{
+ struct interface *ifp = arg;
+ struct if_options *ifo = ifp->options;
+ size_t i;
+ char buf[DUID_LEN * 3];
+ int carrier;
+ struct timespec tv;
+
+ if (ifo->options & DHCPCD_LINK) {
+ switch (ifp->carrier) {
+ case LINK_UP:
+ break;
+ case LINK_DOWN:
+ logger(ifp->ctx, LOG_INFO, "%s: waiting for carrier",
+ ifp->name);
+ return;
+ case LINK_UNKNOWN:
+ /* No media state available.
+ * Loop until both IFF_UP and IFF_RUNNING are set */
+ if ((carrier = if_carrier(ifp)) == LINK_UNKNOWN) {
+ tv.tv_sec = 0;
+ tv.tv_nsec = IF_POLL_UP * NSEC_PER_MSEC;
+ eloop_timeout_add_tv(ifp->ctx->eloop,
+ &tv, dhcpcd_startinterface, ifp);
+ } else
+ dhcpcd_handlecarrier(ifp->ctx, carrier,
+ ifp->flags, ifp->name);
+ return;
+ }
+ }
+
+ if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) {
+ /* Report client DUID */
+ if (ifp->ctx->duid == NULL) {
+ if (duid_init(ifp) == 0)
+ return;
+ logger(ifp->ctx, LOG_INFO, "DUID %s",
+ hwaddr_ntoa(ifp->ctx->duid,
+ ifp->ctx->duid_len,
+ buf, sizeof(buf)));
+ }
+ }
+
+ if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) {
+ /* Report IAIDs */
+ logger(ifp->ctx, LOG_INFO, "%s: IAID %s", ifp->name,
+ hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid),
+ buf, sizeof(buf)));
+ warn_iaid_conflict(ifp, ifo->iaid);
+ for (i = 0; i < ifo->ia_len; i++) {
+ if (memcmp(ifo->iaid, ifo->ia[i].iaid,
+ sizeof(ifo->iaid)))
+ {
+ logger(ifp->ctx, LOG_INFO, "%s: IAID %s",
+ ifp->name, hwaddr_ntoa(ifo->ia[i].iaid,
+ sizeof(ifo->ia[i].iaid),
+ buf, sizeof(buf)));
+ warn_iaid_conflict(ifp, ifo->ia[i].iaid);
+ }
+ }
+ }
+
+ if (ifo->options & DHCPCD_IPV6) {
+ if (ifo->options & DHCPCD_IPV6RS &&
+ !(ifo->options & DHCPCD_INFORM))
+ ipv6nd_startrs(ifp);
+
+ if (ifo->options & DHCPCD_DHCP6)
+ dhcp6_find_delegates(ifp);
+
+ if (!(ifo->options & DHCPCD_IPV6RS) ||
+ ifo->options & DHCPCD_IA_FORCED)
+ {
+ ssize_t nolease;
+
+ if (ifo->options & DHCPCD_IA_FORCED)
+ nolease = dhcp6_start(ifp, DH6S_INIT);
+ else {
+ nolease = 0;
+ /* Enabling the below doesn't really make
+ * sense as there is currently no standard
+ * to push routes via DHCPv6.
+ * (There is an expired working draft,
+ * maybe abandoned?)
+ * You can also get it to work by forcing
+ * an IA as shown above. */
+#if 0
+ /* With no RS or delegates we might
+ * as well try and solicit a DHCPv6 address */
+ if (nolease == 0)
+ nolease = dhcp6_start(ifp, DH6S_INIT);
+#endif
+ }
+ if (nolease == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: dhcp6_start: %m", ifp->name);
+ }
+ }
+
+ if (ifo->options & DHCPCD_IPV4) {
+ /* Ensure we have an IPv4 state before starting DHCP */
+ if (ipv4_getstate(ifp) != NULL)
+ dhcp_start(ifp);
+ }
+}
+
+static void
+dhcpcd_prestartinterface(void *arg)
+{
+ struct interface *ifp = arg;
+
+ pre_start(ifp);
+ if ((!(ifp->ctx->options & DHCPCD_MASTER) ||
+ ifp->options->options & DHCPCD_IF_UP) &&
+ if_up(ifp) == -1)
+ logger(ifp->ctx, LOG_ERR, "%s: if_up: %m", ifp->name);
+
+ if (ifp->options->options & DHCPCD_LINK &&
+ ifp->carrier == LINK_UNKNOWN)
+ {
+ int carrier;
+
+ if ((carrier = if_carrier(ifp)) != LINK_UNKNOWN) {
+ dhcpcd_handlecarrier(ifp->ctx, carrier,
+ ifp->flags, ifp->name);
+ return;
+ }
+ logger(ifp->ctx, LOG_INFO,
+ "%s: unknown carrier, waiting for interface flags",
+ ifp->name);
+ }
+
+ dhcpcd_startinterface(ifp);
+}
+
+static void
+handle_link(void *arg)
+{
+ struct dhcpcd_ctx *ctx;
+
+ ctx = arg;
+ if (if_managelink(ctx) == -1) {
+ logger(ctx, LOG_ERR, "if_managelink: %m");
+ eloop_event_delete(ctx->eloop, ctx->link_fd);
+ close(ctx->link_fd);
+ ctx->link_fd = -1;
+ }
+}
+
+static void
+dhcpcd_initstate1(struct interface *ifp, int argc, char **argv,
+ unsigned long long options)
+{
+ struct if_options *ifo;
+
+ configure_interface(ifp, argc, argv, options);
+ ifo = ifp->options;
+
+ if (ifo->options & DHCPCD_IPV4 && ipv4_init(ifp->ctx) == -1) {
+ logger(ifp->ctx, LOG_ERR, "ipv4_init: %m");
+ ifo->options &= ~DHCPCD_IPV4;
+ }
+ if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "ipv6_init: %m");
+ ifo->options &= ~DHCPCD_IPV6RS;
+ }
+
+ /* Add our link-local address before upping the interface
+ * so our RFC7217 address beats the hwaddr based one.
+ * This needs to happen before PREINIT incase a hook script
+ * inadvertently ups the interface. */
+ if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name);
+ ifo->options &= ~DHCPCD_IPV6;
+ }
+}
+
+void
+dhcpcd_initstate(struct interface *ifp, unsigned long long options)
+{
+
+ dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options);
+}
+
+static void
+run_preinit(struct interface *ifp)
+{
+
+ pre_start(ifp);
+ if (ifp->ctx->options & DHCPCD_TEST)
+ return;
+
+ script_runreason(ifp, "PREINIT");
+
+ if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN)
+ script_runreason(ifp,
+ ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER");
+}
+
+int
+dhcpcd_handleinterface(void *arg, int action, const char *ifname)
+{
+ struct dhcpcd_ctx *ctx;
+ struct if_head *ifs;
+ struct interface *ifp, *iff, *ifn;
+ const char * const argv[] = { ifname };
+ int i;
+
+ ctx = arg;
+ if (action == -1) {
+ ifp = if_find(ctx->ifaces, ifname);
+ if (ifp == NULL) {
+ errno = ESRCH;
+ return -1;
+ }
+ logger(ctx, LOG_DEBUG, "%s: interface departed", ifp->name);
+ ifp->options->options |= DHCPCD_DEPARTED;
+ stop_interface(ifp);
+ return 0;
+ }
+
+ /* If running off an interface list, check it's in it. */
+ if (ctx->ifc && action != 2) {
+ for (i = 0; i < ctx->ifc; i++)
+ if (strcmp(ctx->ifv[i], ifname) == 0)
+ break;
+ if (i >= ctx->ifc)
+ return 0;
+ }
+
+ i = -1;
+ ifs = if_discover(ctx, -1, UNCONST(argv));
+ if (ifs == NULL) {
+ logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__);
+ return -1;
+ }
+ TAILQ_FOREACH_SAFE(ifp, ifs, next, ifn) {
+ if (strcmp(ifp->name, ifname) != 0)
+ continue;
+ i = 0;
+ /* Check if we already have the interface */
+ iff = if_find(ctx->ifaces, ifp->name);
+ if (iff) {
+ logger(ctx, LOG_DEBUG, "%s: interface updated", iff->name);
+ /* The flags and hwaddr could have changed */
+ iff->flags = ifp->flags;
+ iff->hwlen = ifp->hwlen;
+ if (ifp->hwlen != 0)
+ memcpy(iff->hwaddr, ifp->hwaddr, iff->hwlen);
+ } else {
+ logger(ctx, LOG_DEBUG, "%s: interface added", ifp->name);
+ TAILQ_REMOVE(ifs, ifp, next);
+ TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
+ dhcpcd_initstate(ifp, 0);
+ run_preinit(ifp);
+ iff = ifp;
+ }
+ if (action > 0)
+ dhcpcd_prestartinterface(iff);
+ }
+
+ /* Free our discovered list */
+ while ((ifp = TAILQ_FIRST(ifs))) {
+ TAILQ_REMOVE(ifs, ifp, next);
+ if_free(ifp);
+ }
+ free(ifs);
+
+ if (i == -1)
+ errno = ENOENT;
+ return i;
+}
+
+void
+dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname,
+ const uint8_t *hwaddr, uint8_t hwlen)
+{
+ struct interface *ifp;
+ char buf[sizeof(ifp->hwaddr) * 3];
+
+ ifp = if_find(ctx->ifaces, ifname);
+ if (ifp == NULL)
+ return;
+
+ if (hwlen > sizeof(ifp->hwaddr)) {
+ errno = ENOBUFS;
+ logger(ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__);
+ return;
+ }
+
+ if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0)
+ return;
+
+ logger(ctx, LOG_INFO, "%s: new hardware address: %s", ifp->name,
+ hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf)));
+ ifp->hwlen = hwlen;
+ memcpy(ifp->hwaddr, hwaddr, hwlen);
+}
+
+static void
+if_reboot(struct interface *ifp, int argc, char **argv)
+{
+ unsigned long long oldopts;
+
+ oldopts = ifp->options->options;
+ script_runreason(ifp, "RECONFIGURE");
+ dhcpcd_initstate1(ifp, argc, argv, 0);
+ dhcp_reboot_newopts(ifp, oldopts);
+ dhcp6_reboot(ifp);
+ dhcpcd_prestartinterface(ifp);
+}
+
+static void
+reload_config(struct dhcpcd_ctx *ctx)
+{
+ struct if_options *ifo;
+
+ free_globals(ctx);
+ ifo = read_config(ctx, NULL, NULL, NULL);
+ add_options(ctx, NULL, ifo, ctx->argc, ctx->argv);
+ /* We need to preserve these two options. */
+ if (ctx->options & DHCPCD_MASTER)
+ ifo->options |= DHCPCD_MASTER;
+ if (ctx->options & DHCPCD_DAEMONISED)
+ ifo->options |= DHCPCD_DAEMONISED;
+ ctx->options = ifo->options;
+ free_options(ifo);
+}
+
+static void
+reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi)
+{
+ struct if_head *ifs;
+ struct interface *ifn, *ifp;
+
+ ifs = if_discover(ctx, argc - oi, argv + oi);
+ if (ifs == NULL) {
+ logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__);
+ return;
+ }
+
+ while ((ifp = TAILQ_FIRST(ifs))) {
+ TAILQ_REMOVE(ifs, ifp, next);
+ ifn = if_find(ctx->ifaces, ifp->name);
+ if (ifn) {
+ if (action)
+ if_reboot(ifn, argc, argv);
+ else
+ ipv4_applyaddr(ifn);
+ if_free(ifp);
+ } else {
+ TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
+ dhcpcd_initstate1(ifp, argc, argv, 0);
+ run_preinit(ifp);
+ dhcpcd_prestartinterface(ifp);
+ }
+ }
+ free(ifs);
+}
+
+static void
+stop_all_interfaces(struct dhcpcd_ctx *ctx, int do_release)
+{
+ struct interface *ifp;
+
+ /* Drop the last interface first */
+ while ((ifp = TAILQ_LAST(ctx->ifaces, if_head)) != NULL) {
+ if (do_release) {
+ ifp->options->options |= DHCPCD_RELEASE;
+ ifp->options->options &= ~DHCPCD_PERSISTENT;
+ }
+ ifp->options->options |= DHCPCD_EXITING;
+ stop_interface(ifp);
+ }
+}
+
+#ifdef USE_SIGNALS
+#define sigmsg "received %s, %s"
+static void
+signal_cb(int sig, void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ struct interface *ifp;
+ int do_release, exit_code;
+
+ do_release = 0;
+ exit_code = EXIT_FAILURE;
+ switch (sig) {
+ case SIGINT:
+ logger(ctx, LOG_INFO, sigmsg, "SIGINT", "stopping");
+ break;
+ case SIGTERM:
+ logger(ctx, LOG_INFO, sigmsg, "SIGTERM", "stopping");
+ exit_code = EXIT_SUCCESS;
+ break;
+ case SIGALRM:
+ logger(ctx, LOG_INFO, sigmsg, "SIGALRM", "releasing");
+ do_release = 1;
+ exit_code = EXIT_SUCCESS;
+ break;
+ case SIGHUP:
+ logger(ctx, LOG_INFO, sigmsg, "SIGHUP", "rebinding");
+ reload_config(ctx);
+ /* Preserve any options passed on the commandline
+ * when we were started. */
+ reconf_reboot(ctx, 1, ctx->argc, ctx->argv,
+ ctx->argc - ctx->ifc);
+ return;
+ case SIGUSR1:
+ logger(ctx, LOG_INFO, sigmsg, "SIGUSR1", "reconfiguring");
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ ipv4_applyaddr(ifp);
+ }
+ return;
+ case SIGUSR2:
+ logger_close(ctx);
+ logger_open(ctx);
+ logger(ctx, LOG_INFO, sigmsg, "SIGUSR2", "reopened logfile");
+ return;
+ case SIGPIPE:
+ logger(ctx, LOG_WARNING, "received SIGPIPE");
+ return;
+ default:
+ logger(ctx, LOG_ERR,
+ "received signal %d, "
+ "but don't know what to do with it",
+ sig);
+ return;
+ }
+
+ if (!(ctx->options & DHCPCD_TEST))
+ stop_all_interfaces(ctx, do_release);
+ eloop_exit(ctx->eloop, exit_code);
+}
+#endif
+
+static void
+dhcpcd_getinterfaces(void *arg)
+{
+ struct fd_list *fd = arg;
+ struct interface *ifp;
+ size_t len;
+
+ len = 0;
+ TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) {
+ len++;
+ if (D_STATE_RUNNING(ifp))
+ len++;
+ if (IPV4LL_STATE_RUNNING(ifp))
+ len++;
+ if (RS_STATE_RUNNING(ifp))
+ len++;
+ if (D6_STATE_RUNNING(ifp))
+ len++;
+ }
+ if (write(fd->fd, &len, sizeof(len)) != sizeof(len))
+ return;
+ eloop_event_remove_writecb(fd->ctx->eloop, fd->fd);
+ TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) {
+ if (send_interface(fd, ifp) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "send_interface %d: %m", fd->fd);
+ }
+}
+
+int
+dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
+ int argc, char **argv)
+{
+ struct interface *ifp;
+ int do_exit = 0, do_release = 0, do_reboot = 0;
+ int opt, oi = 0;
+ size_t len, l;
+ char *tmp, *p;
+
+ /* Special commands for our control socket
+ * as the other end should be blocking until it gets the
+ * expected reply we should be safely able just to change the
+ * write callback on the fd */
+ if (strcmp(*argv, "--version") == 0) {
+ return control_queue(fd, UNCONST(VERSION),
+ strlen(VERSION) + 1, 0);
+ } else if (strcmp(*argv, "--getconfigfile") == 0) {
+ return control_queue(fd, UNCONST(fd->ctx->cffile),
+ strlen(fd->ctx->cffile) + 1, 0);
+ } else if (strcmp(*argv, "--getinterfaces") == 0) {
+ eloop_event_add(fd->ctx->eloop, fd->fd, NULL, NULL,
+ dhcpcd_getinterfaces, fd);
+ return 0;
+ } else if (strcmp(*argv, "--listen") == 0) {
+ fd->flags |= FD_LISTEN;
+ return 0;
+ }
+
+ /* Only priviledged users can control dhcpcd via the socket. */
+ if (fd->flags & FD_UNPRIV) {
+ errno = EPERM;
+ return -1;
+ }
+
+ /* Log the command */
+ len = 1;
+ for (opt = 0; opt < argc; opt++)
+ len += strlen(argv[opt]) + 1;
+ tmp = malloc(len);
+ if (tmp == NULL)
+ return -1;
+ p = tmp;
+ for (opt = 0; opt < argc; opt++) {
+ l = strlen(argv[opt]);
+ strlcpy(p, argv[opt], len);
+ len -= l + 1;
+ p += l;
+ *p++ = ' ';
+ }
+ *--p = '\0';
+ logger(ctx, LOG_INFO, "control command: %s", tmp);
+ free(tmp);
+
+ optind = 0;
+ while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
+ {
+ switch (opt) {
+ case 'g':
+ /* Assumed if below not set */
+ break;
+ case 'k':
+ do_release = 1;
+ break;
+ case 'n':
+ do_reboot = 1;
+ break;
+ case 'x':
+ do_exit = 1;
+ break;
+ }
+ }
+
+ if (do_release || do_exit) {
+ if (optind == argc) {
+ stop_all_interfaces(ctx, do_release);
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return 0;
+ }
+ for (oi = optind; oi < argc; oi++) {
+ if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL)
+ continue;
+ if (do_release) {
+ ifp->options->options |= DHCPCD_RELEASE;
+ ifp->options->options &= ~DHCPCD_PERSISTENT;
+ }
+ ifp->options->options |= DHCPCD_EXITING;
+ stop_interface(ifp);
+ }
+ return 0;
+ }
+
+ reload_config(ctx);
+ /* XXX: Respect initial commandline options? */
+ reconf_reboot(ctx, do_reboot, argc, argv, optind - 1);
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct dhcpcd_ctx ctx;
+ struct if_options *ifo;
+ struct interface *ifp;
+ uint16_t family = 0;
+ int opt, oi = 0, i;
+ time_t t;
+ ssize_t len;
+#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK)
+ pid_t pid;
+#endif
+#ifdef USE_SIGNALS
+ int sig = 0;
+ const char *siga = NULL;
+#endif
+
+ /* Test for --help and --version */
+ if (argc > 1) {
+ if (strcmp(argv[1], "--help") == 0) {
+ usage();
+ return EXIT_SUCCESS;
+ } else if (strcmp(argv[1], "--version") == 0) {
+ printf(""PACKAGE" "VERSION"\n%s\n", dhcpcd_copyright);
+ return EXIT_SUCCESS;
+ }
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+ closefrom(3);
+
+ ctx.log_fd = -1;
+ logger_open(&ctx);
+ logger_mask(&ctx, LOG_UPTO(LOG_INFO));
+
+ ifo = NULL;
+ ctx.cffile = CONFIG;
+ ctx.pid_fd = ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1;
+ ctx.pf_inet_fd = -1;
+#if defined(INET6) && defined(BSD)
+ ctx.pf_inet6_fd = -1;
+#endif
+#ifdef IFLR_ACTIVE
+ ctx.pf_link_fd = -1;
+#endif
+
+ TAILQ_INIT(&ctx.control_fds);
+#ifdef PLUGIN_DEV
+ ctx.dev_fd = -1;
+#endif
+#ifdef INET
+ ctx.udp_fd = -1;
+#endif
+ i = 0;
+ while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
+ {
+ switch (opt) {
+ case '4':
+ family = AF_INET;
+ break;
+ case '6':
+ family = AF_INET6;
+ break;
+ case 'f':
+ ctx.cffile = optarg;
+ break;
+#ifdef USE_SIGNALS
+ case 'g':
+ sig = SIGUSR1;
+ siga = "USR1";
+ break;
+ case 'j':
+ ctx.logfile = strdup(optarg);
+ logger_close(&ctx);
+ logger_open(&ctx);
+ break;
+ case 'k':
+ sig = SIGALRM;
+ siga = "ARLM";
+ break;
+ case 'n':
+ sig = SIGHUP;
+ siga = "HUP";
+ break;
+ case 'x':
+ sig = SIGTERM;
+ siga = "TERM";;
+ break;
+#endif
+ case 'T':
+ i = 1;
+ break;
+ case 'U':
+ i = 3;
+ break;
+ case 'V':
+ i = 2;
+ break;
+ case '?':
+ usage();
+ goto exit_failure;
+ }
+ }
+
+ ctx.argv = argv;
+ ctx.argc = argc;
+ ctx.ifc = argc - optind;
+ ctx.ifv = argv + optind;
+
+ ifo = read_config(&ctx, NULL, NULL, NULL);
+ if (ifo == NULL)
+ goto exit_failure;
+ opt = add_options(&ctx, NULL, ifo, argc, argv);
+ if (opt != 1) {
+ if (opt == 0)
+ usage();
+ goto exit_failure;
+ }
+ if (i == 2) {
+ printf("Interface options:\n");
+ if (optind == argc - 1) {
+ free_options(ifo);
+ ifo = read_config(&ctx, argv[optind], NULL, NULL);
+ if (ifo == NULL)
+ goto exit_failure;
+ add_options(&ctx, NULL, ifo, argc, argv);
+ }
+ if_printoptions();
+#ifdef INET
+ if (family == 0 || family == AF_INET) {
+ printf("\nDHCPv4 options:\n");
+ dhcp_printoptions(&ctx,
+ ifo->dhcp_override, ifo->dhcp_override_len);
+ }
+#endif
+#ifdef INET6
+ if (family == 0 || family == AF_INET6) {
+ printf("\nND options:\n");
+ ipv6nd_printoptions(&ctx,
+ ifo->nd_override, ifo->nd_override_len);
+ printf("\nDHCPv6 options:\n");
+ dhcp6_printoptions(&ctx,
+ ifo->dhcp6_override, ifo->dhcp6_override_len);
+ }
+#endif
+ goto exit_success;
+ }
+ ctx.options = ifo->options;
+ if (i != 0) {
+ if (i == 1)
+ ctx.options |= DHCPCD_TEST;
+ else
+ ctx.options |= DHCPCD_DUMPLEASE;
+ ctx.options |= DHCPCD_PERSISTENT;
+ ctx.options &= ~DHCPCD_DAEMONISE;
+ }
+
+#ifdef THERE_IS_NO_FORK
+ ctx.options &= ~DHCPCD_DAEMONISE;
+#endif
+
+ if (ctx.options & DHCPCD_DEBUG)
+ logger_mask(&ctx, LOG_UPTO(LOG_DEBUG));
+ if (ctx.options & DHCPCD_QUIET) {
+ i = open(_PATH_DEVNULL, O_RDWR);
+ if (i == -1)
+ logger(&ctx, LOG_ERR, "%s: open: %m", __func__);
+ else {
+ dup2(i, STDERR_FILENO);
+ close(i);
+ }
+ }
+
+ if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) {
+ /* If we have any other args, we should run as a single dhcpcd
+ * instance for that interface. */
+ if (optind == argc - 1 && !(ctx.options & DHCPCD_MASTER)) {
+ const char *per;
+
+ if (strlen(argv[optind]) > IF_NAMESIZE) {
+ logger(&ctx, LOG_ERR,
+ "%s: interface name too long",
+ argv[optind]);
+ goto exit_failure;
+ }
+ /* Allow a dhcpcd interface per address family */
+ switch(family) {
+ case AF_INET:
+ per = "-4";
+ break;
+ case AF_INET6:
+ per = "-6";
+ break;
+ default:
+ per = "";
+ }
+ snprintf(ctx.pidfile, sizeof(ctx.pidfile),
+ PIDFILE, "-", argv[optind], per);
+ } else {
+ snprintf(ctx.pidfile, sizeof(ctx.pidfile),
+ PIDFILE, "", "", "");
+ ctx.options |= DHCPCD_MASTER;
+ }
+ }
+
+ if (chdir("/") == -1)
+ logger(&ctx, LOG_ERR, "chdir `/': %m");
+
+ /* Freeing allocated addresses from dumping leases can trigger
+ * eloop removals as well, so init here. */
+ if ((ctx.eloop = eloop_new()) == NULL) {
+ logger(&ctx, LOG_ERR, "%s: eloop_init: %m", __func__);
+ goto exit_failure;
+ }
+
+ if (ctx.options & DHCPCD_DUMPLEASE) {
+ if (optind != argc - 1) {
+ logger(&ctx, LOG_ERR,
+ "dumplease requires an interface");
+ goto exit_failure;
+ }
+ i = 0;
+ /* We need to try and find the interface so we can
+ * load the hardware address to compare automated IAID */
+ ctx.ifaces = if_discover(&ctx, 1, argv + optind);
+ if (ctx.ifaces == NULL) {
+ logger(&ctx, LOG_ERR, "if_discover: %m");
+ goto exit_failure;
+ }
+ ifp = TAILQ_FIRST(ctx.ifaces);
+ if (ifp == NULL) {
+ ifp = calloc(1, sizeof(*ifp));
+ if (ifp == NULL) {
+ logger(&ctx, LOG_ERR, "%s: %m", __func__);
+ goto exit_failure;
+ }
+ strlcpy(ctx.pidfile, argv[optind], sizeof(ctx.pidfile));
+ ifp->ctx = &ctx;
+ TAILQ_INSERT_HEAD(ctx.ifaces, ifp, next);
+ if (family == 0) {
+ if (ctx.pidfile[strlen(ctx.pidfile) - 1] == '6')
+ family = AF_INET6;
+ else
+ family = AF_INET;
+ }
+ }
+ configure_interface(ifp, ctx.argc, ctx.argv, 0);
+ if (family == 0 || family == AF_INET) {
+ if (dhcp_dump(ifp) == -1)
+ i = 1;
+ }
+ if (family == 0 || family == AF_INET6) {
+ if (dhcp6_dump(ifp) == -1)
+ i = 1;
+ }
+ if (i == -1)
+ goto exit_failure;
+ goto exit_success;
+ }
+
+#ifdef USE_SIGNALS
+ if (!(ctx.options & DHCPCD_TEST) &&
+ (sig == 0 || ctx.ifc != 0))
+ {
+#endif
+ if (ctx.options & DHCPCD_MASTER)
+ i = -1;
+ else
+ i = control_open(&ctx, argv[optind]);
+ if (i == -1)
+ i = control_open(&ctx, NULL);
+ if (i != -1) {
+ logger(&ctx, LOG_INFO,
+ "sending commands to master dhcpcd process");
+ len = control_send(&ctx, argc, argv);
+ control_close(&ctx);
+ if (len > 0) {
+ logger(&ctx, LOG_DEBUG, "send OK");
+ goto exit_success;
+ } else {
+ logger(&ctx, LOG_ERR,
+ "failed to send commands");
+ goto exit_failure;
+ }
+ } else {
+ if (errno != ENOENT)
+ logger(&ctx, LOG_ERR, "control_open: %m");
+ }
+#ifdef USE_SIGNALS
+ }
+#endif
+
+#ifdef USE_SIGNALS
+ if (sig != 0) {
+ pid = read_pid(ctx.pidfile);
+ if (pid != 0)
+ logger(&ctx, LOG_INFO, "sending signal %s to pid %d",
+ siga, pid);
+ if (pid == 0 || kill(pid, sig) != 0) {
+ if (sig != SIGHUP && errno != EPERM)
+ logger(&ctx, LOG_ERR, ""PACKAGE" not running");
+ if (pid != 0 && errno != ESRCH) {
+ logger(&ctx, LOG_ERR, "kill: %m");
+ goto exit_failure;
+ }
+ unlink(ctx.pidfile);
+ if (sig != SIGHUP)
+ goto exit_failure;
+ } else {
+ struct timespec ts;
+
+ if (sig == SIGHUP || sig == SIGUSR1)
+ goto exit_success;
+ /* Spin until it exits */
+ logger(&ctx, LOG_INFO,
+ "waiting for pid %d to exit", pid);
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100000000; /* 10th of a second */
+ for(i = 0; i < 100; i++) {
+ nanosleep(&ts, NULL);
+ if (read_pid(ctx.pidfile) == 0)
+ goto exit_success;
+ }
+ logger(&ctx, LOG_ERR, "pid %d failed to exit", pid);
+ goto exit_failure;
+ }
+ }
+
+ if (!(ctx.options & DHCPCD_TEST)) {
+ if ((pid = read_pid(ctx.pidfile)) > 0 &&
+ kill(pid, 0) == 0)
+ {
+ logger(&ctx, LOG_ERR, ""PACKAGE
+ " already running on pid %d (%s)",
+ pid, ctx.pidfile);
+ goto exit_failure;
+ }
+
+ /* Ensure we have the needed directories */
+ if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST)
+ logger(&ctx, LOG_ERR, "mkdir `%s': %m", RUNDIR);
+ if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST)
+ logger(&ctx, LOG_ERR, "mkdir `%s': %m", DBDIR);
+
+ opt = O_WRONLY | O_CREAT | O_NONBLOCK;
+#ifdef O_CLOEXEC
+ opt |= O_CLOEXEC;
+#endif
+ ctx.pid_fd = open(ctx.pidfile, opt, 0664);
+ if (ctx.pid_fd == -1)
+ logger(&ctx, LOG_ERR, "open `%s': %m", ctx.pidfile);
+ else {
+#ifdef LOCK_EX
+ /* Lock the file so that only one instance of dhcpcd
+ * runs on an interface */
+ if (flock(ctx.pid_fd, LOCK_EX | LOCK_NB) == -1) {
+ logger(&ctx, LOG_ERR, "flock `%s': %m", ctx.pidfile);
+ close(ctx.pid_fd);
+ ctx.pid_fd = -1;
+ goto exit_failure;
+ }
+#endif
+#ifndef O_CLOEXEC
+ if (fcntl(ctx.pid_fd, F_GETFD, &opt) == -1 ||
+ fcntl(ctx.pid_fd, F_SETFD, opt | FD_CLOEXEC) == -1)
+ {
+ logger(&ctx, LOG_ERR, "fcntl: %m");
+ close(ctx.pid_fd);
+ ctx.pid_fd = -1;
+ goto exit_failure;
+ }
+#endif
+ write_pid(ctx.pid_fd, getpid());
+ }
+ }
+
+ if (ctx.options & DHCPCD_MASTER) {
+ if (control_start(&ctx, NULL) == -1)
+ logger(&ctx, LOG_ERR, "control_start: %m");
+ }
+#else
+ if (control_start(&ctx,
+ ctx.options & DHCPCD_MASTER ? NULL : argv[optind]) == -1)
+ {
+ logger(&ctx, LOG_ERR, "control_start: %m");
+ goto exit_failure;
+ }
+#endif
+
+ logger(&ctx, LOG_DEBUG, PACKAGE "-" VERSION " starting");
+ ctx.options |= DHCPCD_STARTED;
+#ifdef USE_SIGNALS
+ if (eloop_signal_set_cb(ctx.eloop,
+ dhcpcd_signals, dhcpcd_signals_len,
+ signal_cb, &ctx) == -1)
+ {
+ logger(&ctx, LOG_ERR, "eloop_signal_mask: %m");
+ goto exit_failure;
+ }
+ /* Save signal mask, block and redirect signals to our handler */
+ if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) {
+ logger(&ctx, LOG_ERR, "eloop_signal_mask: %m");
+ goto exit_failure;
+ }
+#endif
+
+ /* When running dhcpcd against a single interface, we need to retain
+ * the old behaviour of waiting for an IP address */
+ if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND))
+ ctx.options |= DHCPCD_WAITIP;
+
+ /* Open our persistent sockets. */
+ if (if_opensockets(&ctx) == -1) {
+ logger(&ctx, LOG_ERR, "if_opensockets: %m");
+ goto exit_failure;
+ }
+ eloop_event_add(ctx.eloop, ctx.link_fd, handle_link, &ctx, NULL, NULL);
+
+ /* Start any dev listening plugin which may want to
+ * change the interface name provided by the kernel */
+ if ((ctx.options & (DHCPCD_MASTER | DHCPCD_DEV)) ==
+ (DHCPCD_MASTER | DHCPCD_DEV))
+ dev_start(&ctx);
+
+ ctx.ifaces = if_discover(&ctx, ctx.ifc, ctx.ifv);
+ if (ctx.ifaces == NULL) {
+ logger(&ctx, LOG_ERR, "if_discover: %m");
+ goto exit_failure;
+ }
+ for (i = 0; i < ctx.ifc; i++) {
+ if (if_find(ctx.ifaces, ctx.ifv[i]) == NULL)
+ logger(&ctx, LOG_ERR,
+ "%s: interface not found or invalid",
+ ctx.ifv[i]);
+ }
+ if (TAILQ_FIRST(ctx.ifaces) == NULL) {
+ if (ctx.ifc == 0)
+ logger(&ctx, LOG_ERR, "no valid interfaces found");
+ else
+ goto exit_failure;
+ if (!(ctx.options & DHCPCD_LINK)) {
+ logger(&ctx, LOG_ERR,
+ "aborting as link detection is disabled");
+ goto exit_failure;
+ }
+ }
+
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ dhcpcd_initstate1(ifp, argc, argv, 0);
+ }
+
+ if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx))
+ goto exit_success;
+
+ opt = 0;
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ run_preinit(ifp);
+ if (ifp->carrier != LINK_DOWN)
+ opt = 1;
+ }
+
+ if (!(ctx.options & DHCPCD_BACKGROUND)) {
+ if (ctx.options & DHCPCD_MASTER)
+ t = ifo->timeout;
+ else if ((ifp = TAILQ_FIRST(ctx.ifaces)))
+ t = ifp->options->timeout;
+ else
+ t = 0;
+ if (opt == 0 &&
+ ctx.options & DHCPCD_LINK &&
+ !(ctx.options & DHCPCD_WAITIP))
+ {
+ logger(&ctx, LOG_WARNING,
+ "no interfaces have a carrier");
+ if (dhcpcd_daemonise(&ctx))
+ goto exit_success;
+ } else if (t > 0 &&
+ /* Test mode removes the daemonise bit, so check for both */
+ ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST))
+ {
+ eloop_timeout_add_sec(ctx.eloop, t,
+ handle_exit_timeout, &ctx);
+ }
+ }
+ free_options(ifo);
+ ifo = NULL;
+
+ if_sortinterfaces(&ctx);
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ eloop_timeout_add_sec(ctx.eloop, 0,
+ dhcpcd_prestartinterface, ifp);
+ }
+
+ i = eloop_start(ctx.eloop, &ctx.sigset);
+ if (i < 0) {
+ syslog(LOG_ERR, "eloop_start: %m");
+ goto exit_failure;
+ }
+ goto exit1;
+
+exit_success:
+ i = EXIT_SUCCESS;
+ goto exit1;
+
+exit_failure:
+ i = EXIT_FAILURE;
+
+exit1:
+ /* Free memory and close fd's */
+ if (ctx.ifaces) {
+ while ((ifp = TAILQ_FIRST(ctx.ifaces))) {
+ TAILQ_REMOVE(ctx.ifaces, ifp, next);
+ if_free(ifp);
+ }
+ free(ctx.ifaces);
+ }
+ free(ctx.duid);
+ if (ctx.link_fd != -1) {
+ eloop_event_delete(ctx.eloop, ctx.link_fd);
+ close(ctx.link_fd);
+ }
+ if (ctx.pf_inet_fd != -1)
+ close(ctx.pf_inet_fd);
+#if defined(INET6) && defined(BSD)
+ if (ctx.pf_inet6_fd != -1)
+ close(ctx.pf_inet6_fd);
+#endif
+#ifdef IFLR_ACTIVE
+ if (ctx.pf_link_fd != -1)
+ close(ctx.pf_link_fd);
+#endif
+
+
+ free_options(ifo);
+ free_globals(&ctx);
+ ipv4_ctxfree(&ctx);
+ ipv6_ctxfree(&ctx);
+ dev_stop(&ctx);
+ if (control_stop(&ctx) == -1)
+ logger(&ctx, LOG_ERR, "control_stop: %m:");
+ if (ctx.pid_fd != -1) {
+ close(ctx.pid_fd);
+ unlink(ctx.pidfile);
+ }
+ eloop_free(ctx.eloop);
+
+ if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED))
+ logger(&ctx, LOG_INFO, PACKAGE " exited");
+ logger_close(&ctx);
+ free(ctx.logfile);
+ return i;
+}
--- /dev/null
+# $NetBSD: dhcpcd.conf,v 1.17 2015/08/21 10:39:00 roy Exp $
+
+# A sample configuration for dhcpcd.
+# See dhcpcd.conf(5) for details.
+
+# Allow users of this group to interact with dhcpcd via the control socket.
+#controlgroup wheel
+
+# Inform the DHCP server of our hostname for DDNS.
+hostname
+
+# Use the hardware address of the interface for the Client ID.
+#clientid
+# or
+# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
+# Some non-RFC compliant DHCP servers do not reply with this set.
+# In this case, comment out duid and enable clientid above.
+duid
+
+# Persist interface configuration when dhcpcd exits.
+persistent
+
+# Rapid commit support.
+# Safe to enable by default because it requires the equivalent option set
+# on the server to actually work.
+option rapid_commit
+
+# A list of options to request from the DHCP server.
+option domain_name_servers, domain_name, domain_search, host_name
+option classless_static_routes
+# Most distributions have NTP support.
+option ntp_servers
+# Respect the network MTU. This is applied to DHCP routes.
+option interface_mtu
+
+# A ServerID is required by RFC2131.
+require dhcp_server_identifier
+
+# Generate Stable Private IPv6 Addresses instead of hardware based ones
+slaac private
+
+# A hook script is provided to lookup the hostname if not set by the DHCP
+# server, but it should not be run by default.
+nohook lookup-hostname
+
+# MINIX 3 only: the LWIP service supports only one IPv4 address per interface.
+# The 'noalias' directive tells dhcpcd(8) to remove any previous IPv4 addresses
+# before adding a new one. This directive should and does not affect IPv6.
+noalias
--- /dev/null
+.\" $NetBSD: dhcpcd.conf.5.in,v 1.23 2015/08/21 10:39:00 roy Exp $
+.\" Copyright (c) 2006-2015 Roy Marples
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 1, 2015
+.Dt DHCPCD.CONF 5
+.Os
+.Sh NAME
+.Nm dhcpcd.conf
+.Nd dhcpcd configuration file
+.Sh DESCRIPTION
+Although
+.Nm dhcpcd
+can do everything from the command line, there are cases where it's just easier
+to do it once in a configuration file.
+Most of the options found in
+.Xr dhcpcd 8
+can be used here.
+The first word on the line is the option and the rest of the line is the value.
+Leading and trailing whitespace for the option and value are trimmed.
+You can escape characters in the value using the \\ character.
+.Pp
+Blank lines and lines starting with # are ignored.
+.Pp
+Here's a list of available options:
+.Bl -tag -width indent
+.It Ic allowinterfaces Ar pattern
+When discovering interfaces, the interface name must match
+.Ar pattern
+which is a space or comma separated list of patterns passed to
+.Xr fnmatch 3 .
+If the same interface is matched in
+.Ic denyinterfaces
+then it is still denied.
+.It Ic denyinterfaces Ar pattern
+When discovering interfaces, the interface name must not match
+.Ar pattern
+which is a space or comma separated list of patterns passed to
+.Xr fnmatch 3 .
+.It Ic arping Ar address Op address
+.Nm dhcpcd
+will arping each address in order before attempting DHCP.
+If an address is found, we will select the replying hardware address as the
+profile, otherwise the ip address.
+Example:
+.Pp
+.D1 interface bge0
+.D1 arping 192.168.0.1
+.Pp
+.D1 profile 192.168.0.1
+.D1 static ip_address=192.168.0.10/24
+.It Ic authprotocol Ar protocol Ar algorithm Ar rdm
+Authenticate DHCP messages.
+See the Supported Authentication Protocols section.
+.It Ic authtoken Ar secretid Ar realm Ar expire Ar key
+Define a shared key for use in authentication.
+.Ar realm can be "" to for use with the
+.Ar delayed
+prptocol.
+.Ar expire
+is the date the token expires and should be formatted "yyy-mm-dd HH:MM".
+You can use the keyword
+.Ar forever
+or
+.Ar 0
+which means the token never expires.
+For the token protocol,
+.Ar secretid
+needs to be 0 and
+.Ar realm
+needs to be "".
+If
+.Nm dhcpcd
+has the error
+.D1 dhcp_auth_encode: Invalid argument
+then it means that
+.Nm dhcpcd
+could not find the correct authentication token in your configuration.
+.It Ic background
+Background immediately.
+This is useful for startup scripts which don't disable link messages for
+carrier status.
+.It Ic blacklist Ar address Ns Op /cidr
+Ignores all packets from
+.Ar address Ns Op /cidr .
+.It Ic whitelist Ar address Ns Op /cidr
+Only accept packets from
+.Ar address Ns Op /cidr .
+.Ic blacklist
+is ignored if
+.Ic whitelist
+is set.
+.It Ic bootp
+Be a BOOTP client.
+Basically, this just doesn't send a DHCP Message Type option and will only
+interact with a BOOTP server.
+All other DHCP options still work.
+.It Ic broadcast
+Instructs the DHCP server to broadcast replies back to the client.
+Normally this is only set for non Ethernet interfaces,
+such as FireWire and InfiniBand.
+In most cases,
+.Nm dhcpcd
+will set this automatically.
+.It Ic controlgroup Ar group
+Sets the group ownership of
+.Pa @RUNDIR@/dhcpcd.sock
+so that users other than root can connect to
+.Nm dhcpcd .
+.It Ic debug
+Echo debug messages to the stderr and syslog.
+.It Ic dev Ar value
+Load the
+.Ar value
+.Pa /dev
+management module.
+.Nm dhcpcd
+will load the first one found to work, if any.
+.It Ic env Ar value
+Push
+.Ar value
+to the environment for use in
+.Xr dhcpcd-run-hooks 8 .
+For example, you can force the hostname hook to always set the hostname with
+.Ic env
+.Va force_hostname=YES .
+Or set which driver
+.Xr wpa_supplicant 8
+should use with
+.Ic env
+.Va wpa_supplicant_driver=nl80211
+.Pp
+If the hostname is set, will be will set to the FQDN if possible as per
+RFC 4702 section 3.1.
+If the FQDN option is missing,
+.Nm dhcpcd
+will still try and set a FQDN from the hostname and domain options for
+consistency.
+To override this, set
+.Ic env
+.Va hostname_fqdn=[YES|NO|SERVER] .
+A value of server means just what the server says, don't manipulate it.
+This could lead to an inconsistent hostname on a DHCPv4 and DHCPv6 network
+where the DHCPv4 hostname is short and the DHCPv6 has an FQDN.
+DHCPv6 has no hostname option.
+.It Ic clientid Ar string
+Send the
+.Ar clientid .
+If the string is of the format 01:02:03 then it is encoded as hex.
+For interfaces whose hardware address is longer than 8 bytes, or if the
+.Ar clientid
+is an empty string then
+.Nm dhcpcd
+sends a default
+.Ar clientid
+of the hardware family and the hardware address.
+.It Ic duid
+Generate an
+.Rs
+.%T "RFC 4361"
+.Re
+compliant DHCP Unique Identifier.
+If persistent storage is available then a DUID-LLT (link local address + time)
+is generated, otherwise DUID-LL is generated (link local address).
+This, plus the IAID will be used as the
+.Ic clientid .
+The DUID-LLT generated will be held in
+.Pa @SYSCONFDIR@/dhcpcd.duid
+and should not be copied to other hosts.
+.It Ic iaid Ar iaid
+Set the Interface Association Identifier to
+.Ar iaid .
+This option must be used in an
+.Ic interface
+block.
+This defaults to the last 4 bytes of the hardware address assigned to the
+interface.
+Each instance of this should be unique within the scope of the client and
+.Nm dhcpcd
+warns if a conflict is detected.
+If there is a conflict, it is only a problem if the conflicted IAIDs are
+used on the same network.
+.It Ic dhcp
+Enable DHCP on the interface, on by default.
+.It Ic dhcp6
+Enable DHCPv6 on the interface, on by default.
+.It Ic ipv4
+Enable IPv4 on the interface, on by default.
+.It Ic ipv6
+Enable IPv6 on the interface, on by default.
+.It Ic persistent
+.Nm dhcpcd
+normally de-configures the interface and configuration when it exits.
+Sometimes, this isn't desirable if, for example, you have root mounted over
+NFS or SSH clients connect to this host and they need to be notified of
+the host shutting down.
+You can use this option to stop this from happening.
+.It Ic fallback Ar profile
+Fallback to using this profile if DHCP fails.
+This allows you to configure a static profile instead of using ZeroConf.
+.It Ic hostname Ar name
+Sends
+.Ar hostname
+to the DHCP server so it can be registered in DNS.
+If
+.Ar hostname
+is an empty string then the current system hostname is sent.
+If
+.Ar hostname
+is a FQDN (ie, contains a .) then it will be encoded as such.
+.It Ic hostname_short
+Sends the short hostname to the DHCP server instead of the FQDN.
+This is useful because DHCP servers will not register the FQDN in their
+DNS if the domain part does not match theirs.
+.Pp
+Also, see the
+.Ic env
+option above to control how the hostname is set on the host.
+.It Ic ia_na Op Ar iaid Op / address
+Request a DHCPv6 Normal Address for
+.Ar iaid .
+.Ar iaid
+defaults to the
+.Ic iaid
+option as described above.
+You can request more than one ia_na by specifying a unique
+.Ar iaid
+for each one.
+.It Ic ia_ta Op Ar iaid
+Request a DHCPv6 Temporary Address for
+.Ar iaid .
+You can request more than one ia_ta by specifying a unique
+.Ar iaid
+for each one.
+.It Ic ia_pd Op Ar iaid Oo / Ar prefix / Ar prefix_len Oc Op Ar interface Op / Ar sla_id Op / Ar prefix_len
+Request a DHCPv6 Delegated Prefix for
+.Ar iaid .
+This option must be used in an
+.Ic interface
+block.
+Unless a
+.Ar sla_id
+of 0 is assigned, a reject route is installed for the Delegated Prefix to
+stop unallocated addresses being resolved upstream.
+This reject route is in essence SLA 0, thus you need space within the prefix
+to assign a SLA per interface.
+If no
+.Ar interface
+is given then we will assign a prefix to every other interface with a
+.Ar sla_id
+equivalent to the interface index assigned by the OS.
+Otherwise addresses are only assigned for each
+.Ar interface
+and
+.Ar sla_id .
+Each assigned address will have a suffix of 1.
+You cannot assign a prefix to the requesting interface unless the
+DHCPv6 server supports
+.Li RFC6603
+Prefix Exclude Option.
+.Nm dhcpcd
+has to be running for all the interfaces it is delegating to.
+A default
+.Ar prefix_len
+of 64 is assumed, unless the maximum
+.Ar sla_id
+does not fit.
+In this case
+.Ar prefix_len
+is increased to the highest multiple of 8 that can accommodate the
+.Ar sla_id .
+.Ar sla_id
+is an integer and is added to the prefix which must fit inside
+.Ar prefix_len
+less the length of the delegated prefix.
+.Ar sla_id can be 0 only if the Delegated Prefix is assigned to one interface.
+You can specify multiple
+.Ar interface /
+.Ar sla_id /
+.Ar prefix_len
+per
+.Ic ia_pd ,
+space separated.
+IPv6RS should be disabled globally when requesting a Prefix Delegation.
+.Pp
+In the following example eth0 is the externally facing interface to be
+configured for both IPv4 and IPv6.
+The DHCPv4 server will provide us with an IPv4 address and a default route.
+The DHCPv6 server is going to provide us with an IPv6 address, a default
+route and a /64 subnet to be delegated to the internal interface.
+The eth1 interface will be automatically configured
+for IPv6 using the first address (::1) from the delegated prefix.
+A second prefix is requested and assigned to two other interfaces.
+.Xr rtadvd 8
+can be used with an empty configuration file on eth1, eth2 and eth3,
+to provide automatic
+IPv6 address configuration for the internal network.
+.Bd -literal -indent
+noipv6rs # disable routing solicitation
+denyinterfaces eth2 # Don't touch eth2 at all
+interface eth0
+ ipv6rs # enable routing solicitation get the
+ # default IPv6 route
+ ia_na 1 # request an IPv6 address
+ ia_pd 2 eth1/0 # request a PD and assign it to eth1
+ ia_pd 3 eth2/1 eth3/2 # req a PD and assign it to eth2 and eth3
+ # we cannot use SLA 0 above because we are
+ # assinging the PD to more than one interface
+.Ed
+.It Ic ipv4only
+Only configure IPv4.
+.It Ic ipv6only
+Only confgiure IPv6.
+.It Ic fqdn Op disable | ptr | both
+ptr just asks the DHCP server to update the PTR
+record of the host in DNS whereas both also updates the A record.
+disable will disable the FQDN option.
+The default is both.
+.Nm dhcpcd
+itself never does any DNS updates.
+.Nm dhcpcd
+encodes the FQDN hostname as specified in
+.Li RFC1035 .
+.It Ic interface Ar interface
+Subsequent options are only parsed for this
+.Ar interface .
+.It Ic ipv6ra_autoconf
+Generate SLAAC addresses for each Prefix advertised by a
+Router Advertisement message with the Auto flag set.
+On by default.
+.It Ic ipv6ra_noautoconf
+Disables the above option.
+.It Ic ipv6ra_fork
+By default, when
+.Nm dhcpcd
+receives an IPv6 RA,
+.Nm dhcpcd
+will only fork to the background if the RA contains at least one unexpired
+RDNSS option and a valid prefix or no DHCPv6 instruction.
+Set this option so to make
+.Nm dhcpcd
+always fork on an RA.
+.It Ic ipv6ra_own
+Disables kernel IPv6 Router Advertisment processing so dhcpcd can manage
+addresses and routes.
+.It Ic ipv6ra_own_default
+Each time dhcpcd receives an IPv6 Router Adveristment, dhcpcd will manage
+the default route only.
+This allows dhcpcd to prefer an interface for outbound traffic based on metric
+and/or user selection rather than the kernel.
+.It Ic ipv6ra_accept_nopublic
+Some IPv6 routers advertise themselves as a default router without any
+public prefixes or managed addresses.
+Generally, this is incorrect behaviour and
+.Nm dhcpcd
+will ignore the advertisement unless this option is turned on.
+.It Ic ipv6rs
+Enables IPv6 Router Advertisment solicitation.
+This is on by default, but is documented here in the case where it is disabled
+globally but needs to be enabled for one interface.
+.It Ic leasetime Ar seconds
+Request a leasetime of
+.Ar seconds .
+.It Ic logfile Ar logfile
+Writes to the specified
+.Ar logfile
+rather than
+.Xr syslog 3 .
+The
+.Ar logfile
+is truncated when opened and is reopened when
+.Nm dhcpcd
+receives the
+.Dv SIGUSR2
+signal.
+.It Ic metric Ar metric
+Metrics are used to prefer an interface over another one, lowest wins.
+.Nm dhcpcd
+will supply a default metric of 200 +
+.Xr if_nametoindex 3 .
+An extra 100 will be added for wireless interfaces.
+.It Ic noalias
+Any pre-existing IPv4 addresses existing address will be removed from the
+interface when adding a new IPv4 address.
+.It Ic noarp
+Don't send any ARP requests.
+This also disables IPv4LL.
+.It Ic noauthrequired
+Don't require authentication even though we requested it.
+Also allows FORCERENEW and RECONFIGURE messages without authentication.
+.It Ic nodelay
+Don't delay for an initial randomised time when starting protocols.
+.It Ic nodev
+Don't load
+.Pa /dev
+management modules.
+.It Ic nodhcp
+Don't start DHCP or listen to DHCP messages.
+This is only useful when allowing IPv4LL.
+.It Ic nodhcp6
+Don't start DHCPv6 or listen to DHCPv6 messages.
+Normally DHCPv6 is started by a RA instruction or configuration.
+.It Ic nogateway
+Don't install any default routes.
+.It Ic gateway
+Install a default route if available (default).
+.It Ic nohook Ar script
+Don't run this hook script.
+Matches full name, or prefixed with 2 numbers optionally ending with
+.Pa .sh .
+.Pp
+So to stop
+.Nm dhcpcd
+from touching your DNS settings or starting wpa_supplicant you would do:-
+.D1 nohook resolv.conf, wpa_supplicant
+.It Ic noipv4
+Don't attempt to configure an IPv4 address.
+.It Ic noipv4ll
+Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP.
+See
+.Rs
+.%T "RFC 3927"
+.Re
+.It Ic noipv6
+Don't attmept to configure an IPv6 address.
+.It Ic noipv6rs
+Disable solicitation and receipt of IPv6 Router Advertisements.
+.It Ic nolink
+Don't receive link messages about carrier status.
+You should only set this for buggy interface drivers.
+.It Ic noup
+Don't bring the interface up when in master mode.
+If
+.Nm
+cannot determine the carrier state,
+.Nm
+will enter a tight polling loop until the interface is marked up and running
+or a valid carrier state is reported.
+.It Ic option Ar option
+Requests the
+.Ar option
+from the server.
+It can be a variable to be used in
+.Xr dhcpcd-run-hooks 8
+or the numerical value.
+You can specify more
+.Ar option Ns s
+separated by commas, spaces or more
+.Ic option
+lines.
+.Ar option
+Prepend dhcp6_ to
+.Ar option
+to request a DHCPv6 option.
+If no DHCPv6 options are configured,
+then DHCPv4 options are mapped to equivalent DHCPv6 options.
+.Pp
+Prepend nd_ to
+.Ar option
+to handle ND options, but this only works for the
+.Ic nooption ,
+.Ic reject
+and
+.Ic require
+options.
+.It Ic nooption Ar option
+Remove the option from the message before it's processed.
+.It Ic require Ar option
+Requires the
+.Ar option
+to be present in all messages, otherwise the message is ignored.
+To enforce that
+.Nm dhcpcd
+only responds to DHCP servers and not BOOTP servers, you can
+.Ic require
+.Ar dhcp_message_type .
+This isn't an exact science though because a BOOTP server can send DHCP like
+options.
+.It Ic reject Ar option
+Reject a message that contains the
+.Ar option .
+This is useful when you cannot use
+.Ic require
+to select / de-select BOOTP messages.
+.It Ic destination Ar option
+If
+.Nm
+detects an address added to a point to point interface (PPP, TUN, etc) then
+it will set the listed DHCP options to the destination address of the
+interface.
+.It Ic profile Ar name
+Subsequent options are only parsed for this profile
+.Ar name .
+.It Ic quiet
+Suppress any dhcpcd output to the console, except for errors.
+.It Ic reboot Ar seconds
+Allow
+.Ar reboot
+seconds before moving to the DISCOVER phase if we have an old lease to use
+and moving from DISCOVER to IPv4LL if no reply.
+The default is 5 seconds.
+A setting of 0 seconds causes
+.Nm dhcpcd
+to skip the REBOOT phase and go straight into DISCOVER.
+This is desirable for mobile users because if you change from network A to
+network B and they use the same subnet and the address from network A isn't
+in use on network B, then the DHCP server will remain silent even if authorative
+which means
+.Nm dhcpcd
+will timeout before moving back to the DISCOVER phase.
+.It Ic release
+.Nm dhcpcd
+will release the lease prior to stopping the interface.
+.It Ic script Ar script
+Use
+.Ar script
+instead of the default
+.Pa @SCRIPT@ .
+.It Ic ssid Ar ssid
+Subsequent options are only parsed for this wireless
+.Ar ssid .
+.It Ic slaac Op Ar hwaddr | Ar private
+Selects the interface identifier used for SLAAC generated IPv6 addresses.
+If
+.Ar private
+is used, a RFC7217 address is generated.
+.It Ic static Ar value
+Configures a static
+.Ar value .
+If you set
+.Ic ip_address
+then
+.Nm dhcpcd
+will not attempt to obtain a lease and just use the value for the address with
+an infinite lease time.
+.Pp
+Here is an example which configures a static address, routes and dns.
+.D1 interface eth0
+.D1 static ip_address=192.168.0.10/24
+.D1 static routers=192.168.0.1
+.D1 static domain_name_servers=192.168.0.1
+.Pp
+Here is an example for PPP which gives the destination a default route.
+It uses the special destination keyword to insert the destination address
+into the value.
+.D1 interface ppp0
+.D1 static ip_address=
+.D1 destination routers
+.It Ic timeout Ar seconds
+Timeout after
+.Ar seconds ,
+instead of the default 30.
+A setting of 0
+.Ar seconds
+causes
+.Nm dhcpcd
+to wait forever to get a lease.
+If
+.Nm dhcpcd
+is working on a single interface then
+.Nm dhcpcd
+will exit when a timeout occurs, otherwise
+.Nm dhcpcd
+will fork into the background.
+If using IPv4LL then
+.Nm dhcpcd
+start the IPv4LL process after the timeout and then wait a little longer
+before really timing out.
+.It Ic userclass Ar string
+Tag the DHCPv4 messages with the userclass.
+You can specify more than one.
+.It Ic vendor Ar code , Ns Ar value
+Add an encapsulated vendor option.
+.Ar code
+should be between 1 and 254 inclusive.
+To add a raw vendor string, omit
+.Ar code
+but keep the comma.
+Examples.
+.Pp
+Set the vendor option 01 with an IP address.
+.D1 vendor 01,192.168.0.2
+Set the vendor option 02 with a hex code.
+.D1 vendor 02,01:02:03:04:05
+Set the vendor option 03 with an IP address as a string.
+.D1 vendor 03,\e"192.168.0.2\e"
+Set un-encapsulated vendor option to hello world.
+.D1 vendor ,"hello world"
+.It Ic vendorclassid Ar string
+Set the DHCP Vendor Class.
+DHCPv6 has it's own option as shown below.
+The default is
+dhcpcd-<version>:<os>:<machine>:<platform>.
+For example
+.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386
+If not set then none is sent.
+Some badly configured DHCP servers reject unknown vendorclassids.
+To work around it, try and impersonate Windows by using the MSFT vendorclassid.
+.It Ic vendclass Ar en Ar data
+Add the DHCPv6 Vendor Indetifying Vendor Class with the IANA assigned Enterprise
+Number
+.Ar en
+with the
+.Ar data .
+This option can be set more than once to add more data, but the behaviour,
+as per
+.Xr RFC 3925
+is undefined if the Enterprise Number differs.
+.It Ic waitip Op 4 | 6
+Wait for an address to be assigned before forking to the background.
+4 means wait for an IPv4 address to be assigned.
+6 means wait for an IPv6 address to be assigned.
+If no argument is given,
+.Nm
+will wait for any address protocol to be assigned.
+It is possible to wait for more than one address protocol and
+.Nm
+will only fork to the background when all waiting conditions are satisfied.
+.It Ic xidhwaddr
+Use the last four bytes of the hardware address as the DHCP xid instead
+of a randomly generated number.
+.El
+.Ss Defining new options
+DHCP, ND and DHCPv6 allow for the use of custom options.
+Each option needs to be started with the
+.Ic define ,
+.If definend
+or
+.Ic define6
+directive.
+This can optionally be followed by both
+.Ic embed
+or
+.Ic encap
+options.
+Both can be specified more than once and
+.Ic embed
+must come before
+.Ic encap .
+.Bl -tag -width indent
+.It Ic define Ar code Ar type Ar variable
+Defines the DHCP option
+.Ar code
+of
+.Ar type
+with a name of
+.Ar variable
+exported to
+.Xr dhcpcd-run-hooks 8 .
+.It Ic definend Ar code Ar type Ar variable
+Defines the ND option
+.Ar code
+of
+.Ar type
+with a name of
+.Ar variable
+exported to
+.Xr dhcpcd-run-hooks 8 ,
+with a prefix of
+.Va _nd .
+.It Ic define6 Ar code Ar type Ar variable
+Defines the DHCPv6 option
+.Ar code
+of
+.Ar type
+with a name of
+.Ar variable
+exported to
+.Xr dhcpcd-run-hooks 8 ,
+with a prefix of
+.Va _dhcp6 .
+.It Ic vendopt Ar code Ar type Ar variable
+Defines the Vendor-Identifying Vendor Options.
+The
+.Ar code
+is the IANA Enterprise Number which will unqiuely describe the encapsulated
+options.
+.Ar type
+is normally
+.Ar encap .
+.Ar variable
+names the Vendor option to be exported.
+.It Ic embed Ar type Ar variable
+Defines an embedded variable within the defined option.
+The length is determined by the
+.Ar type .
+If the
+.Ar variable
+is not the same as defined in the parent option,
+it is prefixed with the parent
+.Ar variable
+first with an underscore.
+If the
+.Ar variable
+has the name of
+.Ar reserved
+then it is not processed.
+.It Ic encap Ar code Ar type Ar variable
+Defines an encapsulated variable within the defined option.
+The length is determined by the
+.Ar type .
+If the
+.Ar variable
+is not the same as defined in the parent option,
+it is prefixed with the parent
+.Ar variable
+first with an underscore.
+.El
+.Ss Type prefix
+These keywords come before the type itself, to describe it more fully.
+You can use more than one, but they must appear in the order listed below.
+.Bl -tag -width -indent
+.It Ic request
+Requests the option by default without having to be specified in user
+configuration
+.It Ic norequest
+This option cannot be requested, regardless of user configuration
+.It Ic index
+The option can appear more than once and will be indexed.
+.It Ic array
+The option data is split into a space separated array, each element being
+the same type.
+.El
+.Ss Types to define
+The type directly affects the length of data consumed inside the option.
+Any remaining data is normally discarded.
+Lengths can be specified for string and binhex types, but this is generally
+with other data embedded afterwards in the same option.
+.Bl -tag -width indent
+.It Ic ipaddress
+An IPv4 address, 4 bytes.
+.It Ic ip6address
+An IPv6 address, 16 bytes.
+.It Ic string Op : Ic length
+A NVT ASCII string of printable characters.
+.It Ic byte
+A byte.
+.It Ic bitflags : Ic flags
+A byte represented as a string of flags, most significant bit first.
+For example, using ABCDEFGH then A would equal 10000000, B 01000000,
+C 00100000, etc.
+If the bit is not set, the flag is not printed.
+A flag of 0 is not printed even if the bit postition is set.
+This is to allow reservation of the first bits while assinging the last bits.
+.It Ic int16
+A signed 16bit integer, 2 bytes.
+.It Ic uint16
+An unsigned 16bit integer, 2 bytes.
+.It Ic int32
+A signed 32bit integer, 4 bytes.
+.It Ic uint32
+An unsigned 32bit integer, 4 bytes.
+.It Ic flag
+A fixed value (1) to indicate that the option is present, 0 bytes.
+.It Ic domain
+A RFC 3397 encoded string.
+.It Ic dname
+A RFC 1035 validated string.
+.It Ic binhex Op : Ic length
+Binary data expressed as hexadecimal.
+.It Ic embed
+Contains embedded options (implies encap as well).
+.It Ic encap
+Contains encapsulated options (implies embed as well).
+.It Ic option
+References an option from the global definition.
+.El
+.Ss Example definition
+.D1 # DHCP option 81, Fully Qualified Domain Name, RFC4702
+.D1 define 81 embed fqdn
+.D1 embed byte flags
+.D1 embed byte rcode1
+.D1 embed byte rcode2
+.D1 embed domain fqdn
+.Pp
+.D1 # DHCP option 125, Vendor Specific Information Option, RFC3925
+.D1 define 125 encap vsio
+.D1 embed uint32 enterprise_number
+.D1 # Options defined for the enterprise number
+.D1 encap 1 ipaddress ipaddress
+.Ss Supported Authentication Protocols
+.Bl -tag -width -indent
+.It Ic token
+Sends and expects the token with the secretid 0 and realm of "" in each message.
+.It Ic delayedrealm
+Delayed Authentication.
+.Nm dhcpcd
+will send an authentication option with no key or MAC.
+The server will see this option, and select a key for
+.Nm , writing the
+.Ar realm
+and
+.Ar secretid
+in it.
+.Nm dhcpcd
+will then look for a non-expired token with a matching realm and secretid.
+This token is used to authenicate all other messages.
+.It Ic delayed
+Same as above, but without a realm.
+.El
+.Ss Supported Authentication Algorithms
+If none specified,
+.Ic hmac-md5
+is the default.
+.Bl -tag -width -indent
+.It Ic hmac-md5
+.El
+.Ss Supported Replay Detection Mechanisms
+If none specified,
+.Ic monotonic
+is the default.
+If this is changed from what was previously used,
+or the means of calculating or storing it is broken then the DHCP server
+will probably have to have its notion of the clients Replay Detection Value
+reset.
+.Bl -tag -width -indent
+.It Ic monocounter
+Read the number in the file
+.Pa @DBDIR@/dhcpcd-rdm.monotonic
+and add one to it.
+.It Ic monotime
+Create a NTP timestamp from the system time.
+.It Ic monotonic
+Same as
+.Ic monotime .
+.El
+.Sh SEE ALSO
+.Xr fnmatch 3 ,
+.Xr if_nametoindex 3 ,
+.Xr dhcpcd 8 ,
+.Xr dhcpcd-run-hooks 8
+.Sh AUTHORS
+.An Roy Marples Aq Mt roy@marples.name
+.Sh BUGS
+Please report them to
+.Lk http://roy.marples.name/projects/dhcpcd
--- /dev/null
+/* $NetBSD: dhcpcd.h,v 1.13 2015/08/21 10:39:00 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef DHCPCD_H
+#define DHCPCD_H
+
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include "config.h"
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h>
+#endif
+
+#include "defs.h"
+#include "control.h"
+#include "if-options.h"
+
+#define HWADDR_LEN 20
+#define IF_SSIDSIZE 33
+#define PROFILE_LEN 64
+#define SECRET_LEN 64
+
+#define LINK_UP 1
+#define LINK_UNKNOWN 0
+#define LINK_DOWN -1
+
+#define IF_DATA_IPV4 0
+#define IF_DATA_ARP 1
+#define IF_DATA_IPV4LL 2
+#define IF_DATA_DHCP 3
+#define IF_DATA_IPV6 4
+#define IF_DATA_IPV6ND 5
+#define IF_DATA_DHCP6 6
+#define IF_DATA_MAX 7
+
+/* If the interface does not support carrier status (ie PPP),
+ * dhcpcd can poll it for the relevant flags periodically */
+#define IF_POLL_UP 100 /* milliseconds */
+
+#ifdef __QNX__
+/* QNX carries defines for, but does not actually support PF_LINK */
+#undef IFLR_ACTIVE
+#endif
+
+struct interface {
+ struct dhcpcd_ctx *ctx;
+ TAILQ_ENTRY(interface) next;
+ char name[IF_NAMESIZE];
+#ifdef __linux__
+ char alias[IF_NAMESIZE];
+#endif
+ unsigned int index;
+ unsigned int flags;
+ sa_family_t family;
+ unsigned char hwaddr[HWADDR_LEN];
+ uint8_t hwlen;
+ unsigned int metric;
+ int carrier;
+ int wireless;
+ uint8_t ssid[IF_SSIDSIZE];
+ unsigned int ssid_len;
+
+ char profile[PROFILE_LEN];
+ struct if_options *options;
+ void *if_data[IF_DATA_MAX];
+};
+TAILQ_HEAD(if_head, interface);
+
+struct dhcpcd_ctx {
+ int pid_fd;
+ char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1];
+ const char *cffile;
+ unsigned long long options;
+ char *logfile;
+ int log_fd;
+ int argc;
+ char **argv;
+ int ifac; /* allowed interfaces */
+ char **ifav; /* allowed interfaces */
+ int ifdc; /* denied interfaces */
+ char **ifdv; /* denied interfaces */
+ int ifc; /* listed interfaces */
+ char **ifv; /* listed interfaces */
+ int ifcc; /* configured interfaces */
+ char **ifcv; /* configured interfaces */
+ unsigned char *duid;
+ size_t duid_len;
+ struct if_head *ifaces;
+
+ int pf_inet_fd;
+#if defined(INET6) && defined(BSD)
+ int pf_inet6_fd;
+#endif
+#ifdef IFLR_ACTIVE
+ int pf_link_fd;
+#endif
+ int link_fd;
+
+#ifdef USE_SIGNALS
+ sigset_t sigset;
+#endif
+ struct eloop *eloop;
+
+ int control_fd;
+ int control_unpriv_fd;
+ struct fd_list_head control_fds;
+ char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE];
+ gid_t control_group;
+
+ /* DHCP Enterprise options, RFC3925 */
+ struct dhcp_opt *vivso;
+ size_t vivso_len;
+
+ char *randomstate; /* original state */
+
+#ifdef INET
+ struct dhcp_opt *dhcp_opts;
+ size_t dhcp_opts_len;
+ struct rt_head *ipv4_routes;
+ struct rt_head *ipv4_kroutes;
+
+ int udp_fd;
+ uint8_t *packet;
+
+ /* Our aggregate option buffer.
+ * We ONLY use this when options are split, which for most purposes is
+ * practically never. See RFC3396 for details. */
+ uint8_t *opt_buffer;
+#endif
+#ifdef INET6
+ unsigned char secret[SECRET_LEN];
+ size_t secret_len;
+
+ struct dhcp_opt *nd_opts;
+ size_t nd_opts_len;
+ struct dhcp_opt *dhcp6_opts;
+ size_t dhcp6_opts_len;
+ struct ipv6_ctx *ipv6;
+#ifndef __linux__
+ int ra_global;
+#endif
+#endif /* INET6 */
+
+#ifdef PLUGIN_DEV
+ char *dev_load;
+ int dev_fd;
+ struct dev *dev;
+ void *dev_handle;
+#endif
+};
+
+#ifdef USE_SIGNALS
+extern const int dhcpcd_signals[];
+extern const size_t dhcpcd_signals_len;
+#endif
+
+int dhcpcd_ifafwaiting(const struct interface *);
+int dhcpcd_afwaiting(const struct dhcpcd_ctx *);
+pid_t dhcpcd_daemonise(struct dhcpcd_ctx *);
+
+int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **);
+void dhcpcd_handlecarrier(struct dhcpcd_ctx *, int, unsigned int, const char *);
+int dhcpcd_handleinterface(void *, int, const char *);
+void dhcpcd_handlehwaddr(struct dhcpcd_ctx *, const char *,
+ const unsigned char *, uint8_t);
+void dhcpcd_dropinterface(struct interface *, const char *);
+int dhcpcd_selectprofile(struct interface *, const char *);
+
+void dhcpcd_startinterface(void *);
+void dhcpcd_initstate(struct interface *, unsigned long long);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: duid.c,v 1.9 2015/07/09 10:15:34 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#define DUID_TIME_EPOCH 946684800
+#define DUID_LLT 1
+#define DUID_LL 3
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef ARPHRD_NETROM
+# define ARPHRD_NETROM 0
+#endif
+
+#include "common.h"
+#include "dhcpcd.h"
+#include "duid.h"
+
+static size_t
+duid_make(unsigned char *d, const struct interface *ifp, uint16_t type)
+{
+ unsigned char *p;
+ uint16_t u16;
+ time_t t;
+ uint32_t u32;
+
+ p = d;
+ u16 = htons(type);
+ memcpy(p, &u16, 2);
+ p += 2;
+ u16 = htons(ifp->family);
+ memcpy(p, &u16, 2);
+ p += 2;
+ if (type == DUID_LLT) {
+ /* time returns seconds from jan 1 1970, but DUID-LLT is
+ * seconds from jan 1 2000 modulo 2^32 */
+ t = time(NULL) - DUID_TIME_EPOCH;
+ u32 = htonl((uint32_t)t & 0xffffffff);
+ memcpy(p, &u32, 4);
+ p += 4;
+ }
+ /* Finally, add the MAC address of the interface */
+ memcpy(p, ifp->hwaddr, ifp->hwlen);
+ p += ifp->hwlen;
+ return (size_t)(p - d);
+}
+
+#define DUID_STRLEN DUID_LEN * 3
+static size_t
+duid_get(unsigned char *d, const struct interface *ifp)
+{
+ FILE *fp;
+ int x = 0;
+ size_t len = 0;
+ char line[DUID_STRLEN];
+ const struct interface *ifp2;
+
+ /* If we already have a DUID then use it as it's never supposed
+ * to change once we have one even if the interfaces do */
+ if ((fp = fopen(DUID, "r"))) {
+ while (fgets(line, DUID_STRLEN, fp)) {
+ len = strlen(line);
+ if (len) {
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ }
+ len = hwaddr_aton(NULL, line);
+ if (len && len <= DUID_LEN) {
+ hwaddr_aton(d, line);
+ break;
+ }
+ len = 0;
+ }
+ fclose(fp);
+ if (len)
+ return len;
+ } else {
+ if (errno != ENOENT)
+ logger(ifp->ctx, LOG_ERR,
+ "error reading DUID: %s: %m", DUID);
+ }
+
+ /* No file? OK, lets make one based on our interface */
+ if (ifp->family == ARPHRD_NETROM) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: is a NET/ROM psuedo interface", ifp->name);
+ TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+ if (ifp2->family != ARPHRD_NETROM)
+ break;
+ }
+ if (ifp2) {
+ ifp = ifp2;
+ logger(ifp->ctx, LOG_WARNING,
+ "picked interface %s to generate a DUID",
+ ifp->name);
+ } else {
+ logger(ifp->ctx, LOG_WARNING,
+ "no interfaces have a fixed hardware address");
+ return duid_make(d, ifp, DUID_LL);
+ }
+ }
+
+ if (!(fp = fopen(DUID, "w"))) {
+ logger(ifp->ctx, LOG_ERR, "error writing DUID: %s: %m", DUID);
+ return duid_make(d, ifp, DUID_LL);
+ }
+ len = duid_make(d, ifp, DUID_LLT);
+ x = fprintf(fp, "%s\n", hwaddr_ntoa(d, len, line, sizeof(line)));
+ if (fclose(fp) == EOF)
+ x = -1;
+ /* Failed to write the duid? scrub it, we cannot use it */
+ if (x < 1) {
+ logger(ifp->ctx, LOG_ERR, "error writing DUID: %s: %m", DUID);
+ unlink(DUID);
+ return duid_make(d, ifp, DUID_LL);
+ }
+ return len;
+}
+
+size_t duid_init(const struct interface *ifp)
+{
+
+ if (ifp->ctx->duid == NULL) {
+ ifp->ctx->duid = malloc(DUID_LEN);
+ if (ifp->ctx->duid == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return 0;
+ }
+ ifp->ctx->duid_len = duid_get(ifp->ctx->duid, ifp);
+ }
+ return ifp->ctx->duid_len;
+}
--- /dev/null
+/* $NetBSD: duid.h,v 1.7 2015/01/30 09:47:05 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef DUID_H
+#define DUID_H
+
+#define DUID_LEN 128 + 2
+
+size_t duid_init(const struct interface *);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: eloop.c,v 1.11 2015/05/16 23:31:32 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* config.h should define HAVE_KQUEUE, HAVE_EPOLL, etc */
+#include "config.h"
+#include "eloop.h"
+
+#ifndef UNUSED
+#define UNUSED(a) (void)((a))
+#endif
+#ifndef __unused
+#ifdef __GNUC__
+#define __unused __attribute__((__unused__))
+#else
+#define __unused
+#endif
+#endif
+
+#ifndef MSEC_PER_SEC
+#define MSEC_PER_SEC 1000L
+#define NSEC_PER_MSEC 1000000L
+#endif
+
+#if defined(HAVE_KQUEUE)
+#include <sys/event.h>
+#include <fcntl.h>
+#ifdef __NetBSD__
+/* udata is void * except on NetBSD
+ * lengths are int except on NetBSD */
+#define UPTR(x) ((intptr_t)(x))
+#define LENC(x) (x)
+#else
+#define UPTR(x) (x)
+#define LENC(x) ((int)(x))
+#endif
+#define eloop_event_setup_fds(eloop)
+#elif defined(HAVE_EPOLL)
+#include <sys/epoll.h>
+#define eloop_event_setup_fds(eloop)
+#else
+#include <poll.h>
+static void
+eloop_event_setup_fds(struct eloop *eloop)
+{
+ struct eloop_event *e;
+ size_t i;
+
+ i = 0;
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ eloop->fds[i].fd = e->fd;
+ eloop->fds[i].events = 0;
+ if (e->read_cb)
+ eloop->fds[i].events |= POLLIN;
+ if (e->write_cb)
+ eloop->fds[i].events |= POLLOUT;
+ eloop->fds[i].revents = 0;
+ e->pollfd = &eloop->fds[i];
+ i++;
+ }
+}
+
+#ifndef pollts
+/* Wrapper around pselect, to imitate the NetBSD pollts call. */
+#if !defined(__minix)
+static int
+#else /* defined(__minix) */
+int
+#endif /* defined(__minix) */
+pollts(struct pollfd * fds, nfds_t nfds,
+ const struct timespec *ts, const sigset_t *sigmask)
+{
+ fd_set read_fds;
+ nfds_t n;
+ int maxfd, r;
+#if defined(__minix)
+ sigset_t omask;
+ struct timeval tv, *tvp;
+#endif /* defined(__minix) */
+
+ FD_ZERO(&read_fds);
+ maxfd = 0;
+ for (n = 0; n < nfds; n++) {
+ if (fds[n].events & POLLIN) {
+ FD_SET(fds[n].fd, &read_fds);
+ if (fds[n].fd > maxfd)
+ maxfd = fds[n].fd;
+ }
+ }
+
+#if !defined(__minix)
+ r = pselect(maxfd + 1, &read_fds, NULL, NULL, ts, sigmask);
+#else /* defined(__minix) */
+ /* XXX FIXME - horrible hack with race condition */
+ sigprocmask(SIG_SETMASK, sigmask, &omask);
+ if (ts != NULL) {
+ tv.tv_sec = ts->tv_sec;
+ tv.tv_usec = ts->tv_nsec / 1000;
+ tvp = &tv;
+ } else
+ tvp = NULL;
+ r = select(maxfd + 1, &read_fds, NULL, NULL, tvp);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif /* defined(__minix) */
+ if (r > 0) {
+ for (n = 0; n < nfds; n++) {
+ fds[n].revents =
+ FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0;
+ }
+ }
+
+ return r;
+}
+#endif
+#endif
+
+int
+eloop_event_add(struct eloop *eloop, int fd,
+ void (*read_cb)(void *), void *read_cb_arg,
+ void (*write_cb)(void *), void *write_cb_arg)
+{
+ struct eloop_event *e;
+#if defined(HAVE_KQUEUE)
+ struct kevent ke[2];
+#elif defined(HAVE_EPOLL)
+ struct epoll_event epe;
+#else
+ struct pollfd *nfds;
+#endif
+
+ assert(eloop != NULL);
+ assert(read_cb != NULL || write_cb != NULL);
+ if (fd == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#ifdef HAVE_EPOLL
+ memset(&epe, 0, sizeof(epe));
+ epe.data.fd = fd;
+ epe.events = EPOLLIN;
+ if (write_cb)
+ epe.events |= EPOLLOUT;
+#endif
+
+ /* We should only have one callback monitoring the fd */
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->fd == fd) {
+ int error;
+
+#if defined(HAVE_KQUEUE)
+ EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, EV_ADD,
+ 0, 0, UPTR(e));
+ if (write_cb)
+ EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE,
+ EV_ADD, 0, 0, UPTR(e));
+ else if (e->write_cb)
+ EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE,
+ EV_DELETE, 0, 0, UPTR(e));
+ error = kevent(eloop->poll_fd, ke,
+ e->write_cb || write_cb ? 2 : 1, NULL, 0, NULL);
+#elif defined(HAVE_EPOLL)
+ epe.data.ptr = e;
+ error = epoll_ctl(eloop->poll_fd, EPOLL_CTL_MOD,
+ fd, &epe);
+#else
+ error = 0;
+#endif
+ if (read_cb) {
+ e->read_cb = read_cb;
+ e->read_cb_arg = read_cb_arg;
+ }
+ if (write_cb) {
+ e->write_cb = write_cb;
+ e->write_cb_arg = write_cb_arg;
+ }
+ eloop_event_setup_fds(eloop);
+ return error;
+ }
+ }
+
+ /* Allocate a new event if no free ones already allocated */
+ if ((e = TAILQ_FIRST(&eloop->free_events))) {
+ TAILQ_REMOVE(&eloop->free_events, e, next);
+ } else {
+ e = malloc(sizeof(*e));
+ if (e == NULL)
+ goto err;
+ }
+
+ /* Ensure we can actually listen to it */
+ eloop->events_len++;
+#if !defined(HAVE_KQUEUE) && !defined(HAVE_EPOLL)
+ if (eloop->events_len > eloop->fds_len) {
+ nfds = realloc(eloop->fds,
+ sizeof(*eloop->fds) * (eloop->fds_len + 5));
+ if (nfds == NULL)
+ goto err;
+ eloop->fds_len += 5;
+ eloop->fds = nfds;
+ }
+#endif
+
+ /* Now populate the structure and add it to the list */
+ e->fd = fd;
+ e->read_cb = read_cb;
+ e->read_cb_arg = read_cb_arg;
+ e->write_cb = write_cb;
+ e->write_cb_arg = write_cb_arg;
+
+#if defined(HAVE_KQUEUE)
+ if (read_cb != NULL)
+ EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ,
+ EV_ADD, 0, 0, UPTR(e));
+ if (write_cb != NULL)
+ EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE,
+ EV_ADD, 0, 0, UPTR(e));
+ if (kevent(eloop->poll_fd, ke, write_cb ? 2 : 1, NULL, 0, NULL) == -1)
+ goto err;
+#elif defined(HAVE_EPOLL)
+ epe.data.ptr = e;
+ if (epoll_ctl(eloop->poll_fd, EPOLL_CTL_ADD, fd, &epe) == -1)
+ goto err;
+#endif
+
+ /* The order of events should not matter.
+ * However, some PPP servers love to close the link right after
+ * sending their final message. So to ensure dhcpcd processes this
+ * message (which is likely to be that the DHCP addresses are wrong)
+ * we insert new events at the queue head as the link fd will be
+ * the first event added. */
+ TAILQ_INSERT_HEAD(&eloop->events, e, next);
+ eloop_event_setup_fds(eloop);
+ return 0;
+
+err:
+ if (e) {
+ eloop->events_len--;
+ TAILQ_INSERT_TAIL(&eloop->free_events, e, next);
+ }
+ return -1;
+}
+
+void
+eloop_event_delete_write(struct eloop *eloop, int fd, int write_only)
+{
+ struct eloop_event *e;
+#if defined(HAVE_KQUEUE)
+ struct kevent ke[2];
+#elif defined(HAVE_EPOLL)
+ struct epoll_event epe;
+#endif
+
+ assert(eloop != NULL);
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->fd == fd) {
+ if (write_only && e->read_cb != NULL) {
+ if (e->write_cb != NULL) {
+ e->write_cb = NULL;
+ e->write_cb_arg = NULL;
+#if defined(HAVE_KQUEUE)
+ EV_SET(&ke[0], (uintptr_t)fd,
+ EVFILT_WRITE, EV_DELETE,
+ 0, 0, UPTR(NULL));
+ kevent(eloop->poll_fd, ke, 1, NULL, 0,
+ NULL);
+#elif defined(HAVE_EPOLL)
+ memset(&epe, 0, sizeof(epe));
+ epe.data.fd = e->fd;
+ epe.data.ptr = e;
+ epe.events = EPOLLIN;
+ epoll_ctl(eloop->poll_fd,
+ EPOLL_CTL_MOD, fd, &epe);
+#endif
+ }
+ } else {
+ TAILQ_REMOVE(&eloop->events, e, next);
+#if defined(HAVE_KQUEUE)
+ EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ,
+ EV_DELETE, 0, 0, UPTR(NULL));
+ if (e->write_cb)
+ EV_SET(&ke[1], (uintptr_t)fd,
+ EVFILT_WRITE, EV_DELETE,
+ 0, 0, UPTR(NULL));
+ kevent(eloop->poll_fd, ke, e->write_cb ? 2 : 1,
+ NULL, 0, NULL);
+#elif defined(HAVE_EPOLL)
+ /* NULL event is safe because we
+ * rely on epoll_pwait which as added
+ * after the delete without event was fixed. */
+ epoll_ctl(eloop->poll_fd, EPOLL_CTL_DEL,
+ fd, NULL);
+#endif
+ TAILQ_INSERT_TAIL(&eloop->free_events, e, next);
+ eloop->events_len--;
+ }
+ eloop_event_setup_fds(eloop);
+ break;
+ }
+ }
+}
+
+int
+eloop_q_timeout_add_tv(struct eloop *eloop, int queue,
+ const struct timespec *when, void (*callback)(void *), void *arg)
+{
+ struct timespec now, w;
+ struct eloop_timeout *t, *tt = NULL;
+
+ assert(eloop != NULL);
+ assert(when != NULL);
+ assert(callback != NULL);
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecadd(&now, when, &w);
+ /* Check for time_t overflow. */
+ if (timespeccmp(&w, &now, <)) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* Remove existing timeout if present */
+ TAILQ_FOREACH(t, &eloop->timeouts, next) {
+ if (t->callback == callback && t->arg == arg) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ break;
+ }
+ }
+
+ if (t == NULL) {
+ /* No existing, so allocate or grab one from the free pool */
+ if ((t = TAILQ_FIRST(&eloop->free_timeouts))) {
+ TAILQ_REMOVE(&eloop->free_timeouts, t, next);
+ } else {
+ if ((t = malloc(sizeof(*t))) == NULL)
+ return -1;
+ }
+ }
+
+ t->when = w;
+ t->callback = callback;
+ t->arg = arg;
+ t->queue = queue;
+
+ /* The timeout list should be in chronological order,
+ * soonest first. */
+ TAILQ_FOREACH(tt, &eloop->timeouts, next) {
+ if (timespeccmp(&t->when, &tt->when, <)) {
+ TAILQ_INSERT_BEFORE(tt, t, next);
+ return 0;
+ }
+ }
+ TAILQ_INSERT_TAIL(&eloop->timeouts, t, next);
+ return 0;
+}
+
+int
+eloop_q_timeout_add_sec(struct eloop *eloop, int queue, time_t when,
+ void (*callback)(void *), void *arg)
+{
+ struct timespec tv;
+
+ tv.tv_sec = when;
+ tv.tv_nsec = 0;
+ return eloop_q_timeout_add_tv(eloop, queue, &tv, callback, arg);
+}
+
+int
+eloop_q_timeout_add_msec(struct eloop *eloop, int queue, long when,
+ void (*callback)(void *), void *arg)
+{
+ struct timespec tv;
+
+ tv.tv_sec = when / MSEC_PER_SEC;
+ tv.tv_nsec = (when % MSEC_PER_SEC) * NSEC_PER_MSEC;
+ return eloop_q_timeout_add_tv(eloop, queue, &tv, callback, arg);
+}
+
+#if !defined(HAVE_KQUEUE)
+static int
+eloop_timeout_add_now(struct eloop *eloop,
+ void (*callback)(void *), void *arg)
+{
+
+ assert(eloop->timeout0 == NULL);
+ eloop->timeout0 = callback;
+ eloop->timeout0_arg = arg;
+ return 0;
+}
+#endif
+
+void
+eloop_q_timeout_delete(struct eloop *eloop, int queue,
+ void (*callback)(void *), void *arg)
+{
+ struct eloop_timeout *t, *tt;
+
+ assert(eloop != NULL);
+
+ TAILQ_FOREACH_SAFE(t, &eloop->timeouts, next, tt) {
+ if ((queue == 0 || t->queue == queue) &&
+ t->arg == arg &&
+ (!callback || t->callback == callback))
+ {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next);
+ }
+ }
+}
+
+void
+eloop_exit(struct eloop *eloop, int code)
+{
+
+ assert(eloop != NULL);
+
+ eloop->exitcode = code;
+ eloop->exitnow = 1;
+}
+
+#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
+static int
+eloop_open(struct eloop *eloop)
+{
+
+#if defined(HAVE_KQUEUE1)
+ return (eloop->poll_fd = kqueue1(O_CLOEXEC));
+#elif defined(HAVE_KQUEUE)
+ int i;
+
+ if ((eloop->poll_fd = kqueue()) == -1)
+ return -1;
+ if ((i = fcntl(eloop->poll_fd, F_GETFD, 0)) == -1 ||
+ fcntl(eloop->poll_fd, F_SETFD, i | FD_CLOEXEC) == -1)
+ {
+ close(eloop->poll_fd);
+ eloop->poll_fd = -1;
+ return -1;
+ }
+
+ return eloop->poll_fd;
+#elif defined (HAVE_EPOLL)
+ return (eloop->poll_fd = epoll_create1(EPOLL_CLOEXEC));
+#endif
+}
+
+int
+eloop_requeue(struct eloop *eloop)
+{
+ struct eloop_event *e;
+ int error;
+#if defined(HAVE_KQUEUE)
+ size_t i;
+ struct kevent *ke;
+#elif defined(HAVE_EPOLL)
+ struct epoll_event epe;
+#endif
+
+ assert(eloop != NULL);
+
+ if (eloop->poll_fd != -1)
+ close(eloop->poll_fd);
+ if (eloop_open(eloop) == -1)
+ return -1;
+#if defined (HAVE_KQUEUE)
+ i = eloop->signals_len;
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ i++;
+ if (e->write_cb)
+ i++;
+ }
+
+ if ((ke = malloc(sizeof(*ke) * i)) == NULL)
+ return -1;
+
+ for (i = 0; i < eloop->signals_len; i++)
+ EV_SET(&ke[i], (uintptr_t)eloop->signals[i],
+ EVFILT_SIGNAL, EV_ADD, 0, 0, UPTR(NULL));
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ EV_SET(&ke[i], (uintptr_t)e->fd, EVFILT_READ,
+ EV_ADD, 0, 0, UPTR(e));
+ i++;
+ if (e->write_cb) {
+ EV_SET(&ke[i], (uintptr_t)e->fd, EVFILT_WRITE,
+ EV_ADD, 0, 0, UPTR(e));
+ i++;
+ }
+ }
+
+ error = kevent(eloop->poll_fd, ke, LENC(i), NULL, 0, NULL);
+ free(ke);
+
+#elif defined(HAVE_EPOLL)
+
+ error = 0;
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ memset(&epe, 0, sizeof(epe));
+ epe.data.fd = e->fd;
+ epe.events = EPOLLIN;
+ if (e->write_cb)
+ epe.events |= EPOLLOUT;
+ epe.data.ptr = e;
+ if (epoll_ctl(eloop->poll_fd, EPOLL_CTL_ADD, e->fd, &epe) == -1)
+ error = -1;
+ }
+#endif
+
+ return error;
+}
+#endif
+
+int
+eloop_signal_set_cb(struct eloop *eloop,
+ const int *signals, size_t signals_len,
+ void (*signal_cb)(int, void *), void *signal_cb_ctx)
+{
+
+ assert(eloop != NULL);
+
+ eloop->signals = signals;
+ eloop->signals_len = signals_len;
+ eloop->signal_cb = signal_cb;
+ eloop->signal_cb_ctx = signal_cb_ctx;
+ return eloop_requeue(eloop);
+}
+
+#ifndef HAVE_KQUEUE
+struct eloop_siginfo {
+ int sig;
+ struct eloop *eloop;
+};
+static struct eloop_siginfo _eloop_siginfo;
+static struct eloop *_eloop;
+
+static void
+eloop_signal1(void *arg)
+{
+ struct eloop_siginfo *si = arg;
+
+ si->eloop->signal_cb(si->sig, si->eloop->signal_cb_ctx);
+}
+
+static void
+#if !defined(__minix)
+eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg)
+#else /* defined(__minix) */
+eloop_signal3(int sig)
+#endif /* defined(__minix) */
+{
+
+ /* So that we can operate safely under a signal we instruct
+ * eloop to pass a copy of the siginfo structure to handle_signal1
+ * as the very first thing to do. */
+ _eloop_siginfo.eloop = _eloop;
+ _eloop_siginfo.sig = sig;
+ eloop_timeout_add_now(_eloop_siginfo.eloop,
+ eloop_signal1, &_eloop_siginfo);
+}
+#endif
+
+int
+eloop_signal_mask(struct eloop *eloop, sigset_t *oldset)
+{
+ sigset_t newset;
+#ifndef HAVE_KQUEUE
+ size_t i;
+ struct sigaction sa;
+#endif
+
+ assert(eloop != NULL);
+
+ sigfillset(&newset);
+ if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1)
+ return -1;
+
+#ifdef HAVE_KQUEUE
+ UNUSED(eloop);
+#else
+ memset(&sa, 0, sizeof(sa));
+#if !defined(__minix)
+ sa.sa_sigaction = eloop_signal3;
+ sa.sa_flags = SA_SIGINFO;
+#else /* defined(__minix) */
+ sa.sa_handler = eloop_signal3;
+#endif /* defined(__minix) */
+ sigemptyset(&sa.sa_mask);
+
+ for (i = 0; i < eloop->signals_len; i++) {
+ if (sigaction(eloop->signals[i], &sa, NULL) == -1)
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+struct eloop *
+eloop_new(void)
+{
+ struct eloop *eloop;
+ struct timespec now;
+
+ /* Check we have a working monotonic clock. */
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
+ return NULL;
+
+ eloop = calloc(1, sizeof(*eloop));
+ if (eloop) {
+ TAILQ_INIT(&eloop->events);
+ TAILQ_INIT(&eloop->free_events);
+ TAILQ_INIT(&eloop->timeouts);
+ TAILQ_INIT(&eloop->free_timeouts);
+ eloop->exitcode = EXIT_FAILURE;
+#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
+ eloop->poll_fd = -1;
+ eloop_open(eloop);
+#endif
+ }
+
+ return eloop;
+}
+
+void eloop_free(struct eloop *eloop)
+{
+ struct eloop_event *e;
+ struct eloop_timeout *t;
+
+ if (eloop == NULL)
+ return;
+
+ while ((e = TAILQ_FIRST(&eloop->events))) {
+ TAILQ_REMOVE(&eloop->events, e, next);
+ free(e);
+ }
+ while ((e = TAILQ_FIRST(&eloop->free_events))) {
+ TAILQ_REMOVE(&eloop->free_events, e, next);
+ free(e);
+ }
+ while ((t = TAILQ_FIRST(&eloop->timeouts))) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ free(t);
+ }
+ while ((t = TAILQ_FIRST(&eloop->free_timeouts))) {
+ TAILQ_REMOVE(&eloop->free_timeouts, t, next);
+ free(t);
+ }
+#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
+ close(eloop->poll_fd);
+#else
+ free(eloop->fds);
+#endif
+ free(eloop);
+}
+
+int
+eloop_start(struct eloop *eloop, sigset_t *signals)
+{
+ int n;
+ struct eloop_event *e;
+ struct eloop_timeout *t;
+ struct timespec now, ts, *tsp;
+ void (*t0)(void *);
+#if defined(HAVE_KQUEUE)
+ struct kevent ke;
+ UNUSED(signals);
+#elif defined(HAVE_EPOLL)
+ struct epoll_event epe;
+#endif
+#ifndef HAVE_KQUEUE
+ int timeout;
+
+ _eloop = eloop;
+#endif
+
+ assert(eloop != NULL);
+
+ for (;;) {
+ if (eloop->exitnow)
+ break;
+
+ /* Run all timeouts first */
+ if (eloop->timeout0) {
+ t0 = eloop->timeout0;
+ eloop->timeout0 = NULL;
+ t0(eloop->timeout0_arg);
+ continue;
+ }
+ if ((t = TAILQ_FIRST(&eloop->timeouts))) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (timespeccmp(&now, &t->when, >)) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ t->callback(t->arg);
+ TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next);
+ continue;
+ }
+ timespecsub(&t->when, &now, &ts);
+ tsp = &ts;
+ } else
+ /* No timeouts, so wait forever */
+ tsp = NULL;
+
+ if (tsp == NULL && eloop->events_len == 0)
+ break;
+
+#ifndef HAVE_KQUEUE
+ if (tsp == NULL)
+ timeout = -1;
+ else if (tsp->tv_sec > INT_MAX / 1000 ||
+ (tsp->tv_sec == INT_MAX / 1000 &&
+ (tsp->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000))
+ timeout = INT_MAX;
+ else
+ timeout = (int)(tsp->tv_sec * 1000 +
+ (tsp->tv_nsec + 999999) / 1000000);
+#endif
+
+#if defined(HAVE_KQUEUE)
+ n = kevent(eloop->poll_fd, NULL, 0, &ke, 1, tsp);
+#elif defined(HAVE_EPOLL)
+ if (signals)
+ n = epoll_pwait(eloop->poll_fd, &epe, 1,
+ timeout, signals);
+ else
+ n = epoll_wait(eloop->poll_fd, &epe, 1, timeout);
+#else
+ if (signals)
+ n = pollts(eloop->fds, (nfds_t)eloop->events_len,
+ tsp, signals);
+ else
+ n = poll(eloop->fds, (nfds_t)eloop->events_len,
+ timeout);
+#endif
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+
+ /* Process any triggered events.
+ * We go back to the start after calling each callback incase
+ * the current event or next event is removed. */
+#if defined(HAVE_KQUEUE)
+ if (n) {
+ if (ke.filter == EVFILT_SIGNAL) {
+ eloop->signal_cb((int)ke.ident,
+ eloop->signal_cb_ctx);
+ continue;
+ }
+ e = (struct eloop_event *)ke.udata;
+ if (ke.filter == EVFILT_WRITE) {
+ e->write_cb(e->write_cb_arg);
+ continue;
+ } else if (ke.filter == EVFILT_READ) {
+ e->read_cb(e->read_cb_arg);
+ continue;
+ }
+ }
+#elif defined(HAVE_EPOLL)
+ if (n) {
+ e = (struct eloop_event *)epe.data.ptr;
+ if (epe.events & EPOLLOUT && e->write_cb != NULL) {
+ e->write_cb(e->write_cb_arg);
+ continue;
+ }
+ if (epe.events &
+ (EPOLLIN | EPOLLERR | EPOLLHUP) &&
+ e->read_cb != NULL)
+ {
+ e->read_cb(e->read_cb_arg);
+ continue;
+ }
+ }
+#else
+ if (n > 0) {
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->pollfd->revents & POLLOUT &&
+ e->write_cb != NULL)
+ {
+ e->write_cb(e->write_cb_arg);
+ break;
+ }
+ if (e->pollfd->revents && e->read_cb != NULL) {
+ e->read_cb(e->read_cb_arg);
+ break;
+ }
+ }
+ }
+#endif
+ }
+
+ return eloop->exitcode;
+}
--- /dev/null
+/* $NetBSD: eloop.h,v 1.9 2015/05/16 23:31:32 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef ELOOP_H
+#define ELOOP_H
+
+#include <time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#else
+/* Attempt to autodetect kqueue or epoll.
+ * If we can't, the system has to support pselect, which is a POSIX call. */
+#if (defined(__unix__) || defined(unix)) && !defined(USG)
+#include <sys/param.h>
+#endif
+#if defined(BSD)
+/* Assume BSD has a working sys/queue.h and kqueue(2) interface */
+#define HAVE_SYS_QUEUE_H
+#define HAVE_KQUEUE
+#elif defined(__linux__)
+/* Assume Linux has a working epoll(3) interface */
+#define HAVE_EPOLL
+#endif
+#endif
+
+/* Our structures require TAILQ macros, which really every libc should
+ * ship as they are useful beyond belief.
+ * Sadly some libc's don't have sys/queue.h and some that do don't have
+ * the TAILQ_FOREACH macro. For those that don't, the application using
+ * this implementation will need to ship a working queue.h somewhere.
+ * If we don't have sys/queue.h found in config.h, then
+ * allow QUEUE_H to override loading queue.h in the current directory. */
+#ifndef TAILQ_FOREACH
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h>
+#elif defined(QUEUE_H)
+#define __QUEUE_HEADER(x) #x
+#define _QUEUE_HEADER(x) __QUEUE_HEADER(x)
+#include _QUEUE_HEADER(QUEUE_H)
+#else
+#include "queue.h"
+#endif
+#endif
+
+/* Some systems don't define timespec macros */
+#ifndef timespecclear
+#define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L)
+#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec)
+#define timespeccmp(tsp, usp, cmp) \
+ (((tsp)->tv_sec == (usp)->tv_sec) ? \
+ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
+ ((tsp)->tv_sec cmp (usp)->tv_sec))
+#define timespecadd(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (/* CONSTCOND */ 0)
+#define timespecsub(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (/* CONSTCOND */ 0)
+#endif
+
+/* eloop queues are really only for deleting timeouts registered
+ * for a function or object.
+ * The idea being that one interface as different timeouts for
+ * say DHCP and DHCPv6. */
+#ifndef ELOOP_QUEUE
+ #define ELOOP_QUEUE 1
+#endif
+
+struct eloop_event {
+ TAILQ_ENTRY(eloop_event) next;
+ int fd;
+ void (*read_cb)(void *);
+ void *read_cb_arg;
+ void (*write_cb)(void *);
+ void *write_cb_arg;
+#if !defined(HAVE_KQUEUE) && !defined(HAVE_EPOLL)
+ struct pollfd *pollfd;
+#endif
+};
+
+struct eloop_timeout {
+ TAILQ_ENTRY(eloop_timeout) next;
+ struct timespec when;
+ void (*callback)(void *);
+ void *arg;
+ int queue;
+};
+
+struct eloop {
+ size_t events_len;
+ TAILQ_HEAD (event_head, eloop_event) events;
+ struct event_head free_events;
+
+ TAILQ_HEAD (timeout_head, eloop_timeout) timeouts;
+ struct timeout_head free_timeouts;
+
+ void (*timeout0)(void *);
+ void *timeout0_arg;
+ const int *signals;
+ size_t signals_len;
+ void (*signal_cb)(int, void *);
+ void *signal_cb_ctx;
+
+#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
+ int poll_fd;
+#else
+ struct pollfd *fds;
+ size_t fds_len;
+#endif
+
+ int exitnow;
+ int exitcode;
+};
+
+int eloop_event_add(struct eloop *, int,
+ void (*)(void *), void *,
+ void (*)(void *), void *);
+#define eloop_event_delete(eloop, fd) \
+ eloop_event_delete_write((eloop), (fd), 0)
+#define eloop_event_remove_writecb(eloop, fd) \
+ eloop_event_delete_write((eloop), (fd), 1)
+void eloop_event_delete_write(struct eloop *, int, int);
+
+#define eloop_timeout_add_tv(eloop, tv, cb, ctx) \
+ eloop_q_timeout_add_tv((eloop), ELOOP_QUEUE, (tv), (cb), (ctx))
+#define eloop_timeout_add_sec(eloop, tv, cb, ctx) \
+ eloop_q_timeout_add_sec((eloop), ELOOP_QUEUE, (tv), (cb), (ctx))
+#define eloop_timeout_add_msec(eloop, ms, cb, ctx) \
+ eloop_q_timeout_add_msec((eloop), ELOOP_QUEUE, (ms), (cb), (ctx))
+#define eloop_timeout_delete(eloop, cb, ctx) \
+ eloop_q_timeout_delete((eloop), ELOOP_QUEUE, (cb), (ctx))
+int eloop_q_timeout_add_tv(struct eloop *, int,
+ const struct timespec *, void (*)(void *), void *);
+int eloop_q_timeout_add_sec(struct eloop *, int,
+ time_t, void (*)(void *), void *);
+int eloop_q_timeout_add_msec(struct eloop *, int,
+ long, void (*)(void *), void *);
+void eloop_q_timeout_delete(struct eloop *, int, void (*)(void *), void *);
+
+int eloop_signal_set_cb(struct eloop *, const int *, size_t,
+ void (*)(int, void *), void *);
+int eloop_signal_mask(struct eloop *, sigset_t *oldset);
+
+struct eloop * eloop_new(void);
+#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
+int eloop_requeue(struct eloop *);
+#else
+#define eloop_requeue(eloop) (0)
+#endif
+void eloop_free(struct eloop *);
+void eloop_exit(struct eloop *, int);
+int eloop_start(struct eloop *, sigset_t *);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: if-bsd.c,v 1.24 2015/08/21 13:24:47 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/ioctl.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/utsname.h>
+
+#include <arpa/inet.h>
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */
+# include <net/if_var.h>
+#endif
+#include <net/if_media.h>
+#include <net/route.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+#ifdef __DragonFly__
+# include <netproto/802_11/ieee80211_ioctl.h>
+#elif __APPLE__
+ /* FIXME: Add apple includes so we can work out SSID */
+#else
+# include <net80211/ieee80211.h>
+# include <net80211/ieee80211_ioctl.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <paths.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if defined(OpenBSD) && OpenBSD >= 201411
+/* OpenBSD dropped the global setting from sysctl but left the #define
+ * which causes a EPERM error when trying to use it.
+ * I think both the error and keeping the define are wrong, so we #undef it. */
+#undef IPV6CTL_ACCEPT_RTADV
+#endif
+
+#include "config.h"
+#include "common.h"
+#include "dhcp.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+
+#include "bpf-filter.h"
+
+#ifndef RT_ROUNDUP
+#define RT_ROUNDUP(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+#define RT_ADVANCE(x, n) (x += RT_ROUNDUP((n)->sa_len))
+#endif
+
+#define COPYOUT(sin, sa) do { \
+ if ((sa) && ((sa)->sa_family == AF_INET || (sa)->sa_family == 255)) \
+ (sin) = ((struct sockaddr_in*)(void *)(sa))->sin_addr; \
+ } while (0)
+
+#define COPYOUT6(sin, sa) do { \
+ if ((sa) && ((sa)->sa_family == AF_INET6 || (sa)->sa_family == 255)) \
+ (sin) = ((struct sockaddr_in6*)(void *)(sa))->sin6_addr; \
+ } while (0)
+
+#ifndef CLLADDR
+# define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen))
+#endif
+
+int
+if_init(__unused struct interface *iface)
+{
+ /* BSD promotes secondary address by default */
+ return 0;
+}
+
+int
+if_conf(__unused struct interface *iface)
+{
+ /* No extra checks needed on BSD */
+ return 0;
+}
+
+int
+if_openlinksocket(void)
+{
+
+ return xsocket(PF_ROUTE, SOCK_RAW, 0, O_NONBLOCK|O_CLOEXEC);
+}
+
+#if defined(INET) || defined(INET6)
+static void
+if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp)
+{
+
+ memset(sdl, 0, sizeof(*sdl));
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_len = sizeof(*sdl);
+ sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0;
+ sdl->sdl_index = (unsigned short)ifp->index;
+}
+#endif
+
+static int
+if_getssid1(int s, const char *ifname, uint8_t *ssid)
+{
+ int retval = -1;
+#if defined(SIOCG80211NWID)
+ struct ifreq ifr;
+ struct ieee80211_nwid nwid;
+#elif defined(IEEE80211_IOC_SSID)
+ struct ieee80211req ireq;
+ char nwid[IEEE80211_NWID_LEN + 1];
+#endif
+
+#if defined(SIOCG80211NWID) /* NetBSD */
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ memset(&nwid, 0, sizeof(nwid));
+ ifr.ifr_data = (void *)&nwid;
+ if (ioctl(s, SIOCG80211NWID, &ifr) == 0) {
+ if (ssid == NULL)
+ retval = nwid.i_len;
+ else if (nwid.i_len > IF_SSIDSIZE) {
+ errno = ENOBUFS;
+ retval = -1;
+ } else {
+ retval = nwid.i_len;
+ memcpy(ssid, nwid.i_nwid, nwid.i_len);
+ ssid[nwid.i_len] = '\0';
+ }
+ }
+#elif defined(IEEE80211_IOC_SSID) /* FreeBSD */
+ memset(&ireq, 0, sizeof(ireq));
+ strlcpy(ireq.i_name, ifname, sizeof(ireq.i_name));
+ ireq.i_type = IEEE80211_IOC_SSID;
+ ireq.i_val = -1;
+ memset(nwid, 0, sizeof(nwid));
+ ireq.i_data = &nwid;
+ if (ioctl(s, SIOCG80211, &ireq) == 0) {
+ if (ssid == NULL)
+ retval = ireq.i_len;
+ else if (ireq.i_len > IF_SSIDSIZE) {
+ errno = ENOBUFS;
+ retval = -1;
+ } else {
+ retval = ireq.i_len;
+ memcpy(ssid, nwid, ireq.i_len);
+ ssid[ireq.i_len] = '\0';
+ }
+ }
+#endif
+
+ return retval;
+}
+
+int
+if_getssid(struct interface *ifp)
+{
+ int r;
+
+ r = if_getssid1(ifp->ctx->pf_inet_fd, ifp->name, ifp->ssid);
+ if (r != -1)
+ ifp->ssid_len = (unsigned int)r;
+ return r;
+}
+
+/*
+ * FreeBSD allows for Virtual Access Points
+ * We need to check if the interface is a Virtual Interface Master
+ * and if so, don't use it.
+ * This check is made by virtue of being a IEEE80211 device but
+ * returning the SSID gives an error.
+ */
+int
+if_vimaster(const struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ int r;
+ struct ifmediareq ifmr;
+
+ memset(&ifmr, 0, sizeof(ifmr));
+ strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
+ r = ioctl(ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr);
+ if (r == -1)
+ return -1;
+ if (ifmr.ifm_status & IFM_AVALID &&
+ IFM_TYPE(ifmr.ifm_active) == IFM_IEEE80211)
+ {
+ if (if_getssid1(ctx->pf_inet_fd, ifname, NULL) == -1)
+ return 1;
+ }
+ return 0;
+}
+
+static void
+get_addrs(int type, char *cp, struct sockaddr **sa)
+{
+ int i;
+
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (type & (1 << i)) {
+ sa[i] = (struct sockaddr *)cp;
+ RT_ADVANCE(cp, sa[i]);
+ } else
+ sa[i] = NULL;
+ }
+}
+
+#if defined(INET) || defined(INET6)
+static struct interface *
+if_findsdl(struct dhcpcd_ctx *ctx, struct sockaddr_dl *sdl)
+{
+
+ if (sdl->sdl_nlen) {
+ char ifname[IF_NAMESIZE];
+ memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen);
+ ifname[sdl->sdl_nlen] = '\0';
+ return if_find(ctx->ifaces, ifname);
+ }
+ return NULL;
+}
+#endif
+
+#ifdef INET
+const char *if_pfname = "Berkley Packet Filter";
+
+int
+if_openrawsocket(struct interface *ifp, uint16_t protocol)
+{
+ struct ipv4_state *state;
+ int fd = -1;
+ struct ifreq ifr;
+ int ibuf_len = 0;
+ size_t buf_len;
+ struct bpf_version pv;
+ struct bpf_program pf;
+#ifdef BIOCIMMEDIATE
+ int flags;
+#endif
+#ifdef _PATH_BPF
+ fd = open(_PATH_BPF, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+#else
+ char device[32];
+ int n = 0;
+
+ do {
+ snprintf(device, sizeof(device), "/dev/bpf%d", n++);
+ fd = open(device, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ } while (fd == -1 && errno == EBUSY);
+#endif
+
+ if (fd == -1)
+ return -1;
+
+ state = IPV4_STATE(ifp);
+ memset(&pv, 0, sizeof(pv));
+ if (ioctl(fd, BIOCVERSION, &pv) == -1)
+ goto eexit;
+ if (pv.bv_major != BPF_MAJOR_VERSION ||
+ pv.bv_minor < BPF_MINOR_VERSION) {
+ logger(ifp->ctx, LOG_ERR, "BPF version mismatch - recompile");
+ goto eexit;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(fd, BIOCSETIF, &ifr) == -1)
+ goto eexit;
+
+ /* Get the required BPF buffer length from the kernel. */
+ if (ioctl(fd, BIOCGBLEN, &ibuf_len) == -1)
+ goto eexit;
+ buf_len = (size_t)ibuf_len;
+ if (state->buffer_size != buf_len) {
+ free(state->buffer);
+ state->buffer = malloc(buf_len);
+ if (state->buffer == NULL)
+ goto eexit;
+ state->buffer_size = buf_len;
+ state->buffer_len = state->buffer_pos = 0;
+ }
+
+#ifdef BIOCIMMEDIATE
+ flags = 1;
+ if (ioctl(fd, BIOCIMMEDIATE, &flags) == -1)
+ goto eexit;
+#endif
+
+ /* Install the DHCP filter */
+ memset(&pf, 0, sizeof(pf));
+ if (protocol == ETHERTYPE_ARP) {
+ pf.bf_insns = UNCONST(arp_bpf_filter);
+ pf.bf_len = arp_bpf_filter_len;
+ } else {
+ pf.bf_insns = UNCONST(dhcp_bpf_filter);
+ pf.bf_len = dhcp_bpf_filter_len;
+ }
+ if (ioctl(fd, BIOCSETF, &pf) == -1)
+ goto eexit;
+
+ return fd;
+
+eexit:
+ free(state->buffer);
+ state->buffer = NULL;
+ close(fd);
+ return -1;
+}
+
+ssize_t
+if_sendrawpacket(const struct interface *ifp, uint16_t protocol,
+ const void *data, size_t len)
+{
+ struct iovec iov[2];
+ struct ether_header hw;
+ int fd;
+
+ memset(&hw, 0, ETHER_HDR_LEN);
+ memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN);
+ hw.ether_type = htons(protocol);
+ iov[0].iov_base = &hw;
+ iov[0].iov_len = ETHER_HDR_LEN;
+ iov[1].iov_base = UNCONST(data);
+ iov[1].iov_len = len;
+ fd = ipv4_protocol_fd(ifp, protocol);
+ return writev(fd, iov, 2);
+}
+
+/* BPF requires that we read the entire buffer.
+ * So we pass the buffer in the API so we can loop on >1 packet. */
+ssize_t
+if_readrawpacket(struct interface *ifp, uint16_t protocol,
+ void *data, size_t len, int *flags)
+{
+ int fd;
+ struct bpf_hdr packet;
+ ssize_t bytes;
+ const unsigned char *payload;
+ struct ipv4_state *state;
+
+ state = IPV4_STATE(ifp);
+ fd = ipv4_protocol_fd(ifp, protocol);
+
+ *flags = 0;
+ for (;;) {
+ if (state->buffer_len == 0) {
+ bytes = read(fd, state->buffer, state->buffer_size);
+ if (bytes == -1 || bytes == 0)
+ return bytes;
+ state->buffer_len = (size_t)bytes;
+ state->buffer_pos = 0;
+ }
+ bytes = -1;
+ memcpy(&packet, state->buffer + state->buffer_pos,
+ sizeof(packet));
+ if (packet.bh_caplen != packet.bh_datalen)
+ goto next; /* Incomplete packet, drop. */
+ if (state->buffer_pos + packet.bh_caplen + packet.bh_hdrlen >
+ state->buffer_len)
+ goto next; /* Packet beyond buffer, drop. */
+ payload = state->buffer + state->buffer_pos +
+ packet.bh_hdrlen + ETHER_HDR_LEN;
+ bytes = (ssize_t)packet.bh_caplen - ETHER_HDR_LEN;
+ if ((size_t)bytes > len)
+ bytes = (ssize_t)len;
+ memcpy(data, payload, (size_t)bytes);
+next:
+ state->buffer_pos += BPF_WORDALIGN(packet.bh_hdrlen +
+ packet.bh_caplen);
+ if (state->buffer_pos >= state->buffer_len) {
+ state->buffer_len = state->buffer_pos = 0;
+ *flags |= RAW_EOF;
+ }
+ if (bytes != -1)
+ return bytes;
+ }
+}
+
+int
+if_address(const struct interface *ifp, const struct in_addr *address,
+ const struct in_addr *netmask, const struct in_addr *broadcast,
+ int action)
+{
+ int r;
+ struct in_aliasreq ifra;
+
+ memset(&ifra, 0, sizeof(ifra));
+ strlcpy(ifra.ifra_name, ifp->name, sizeof(ifra.ifra_name));
+
+#define ADDADDR(var, addr) do { \
+ (var)->sin_family = AF_INET; \
+ (var)->sin_len = sizeof(*(var)); \
+ (var)->sin_addr = *(addr); \
+ } while (/*CONSTCOND*/0)
+ ADDADDR(&ifra.ifra_addr, address);
+ ADDADDR(&ifra.ifra_mask, netmask);
+ if (action >= 0 && broadcast)
+ ADDADDR(&ifra.ifra_broadaddr, broadcast);
+#undef ADDADDR
+
+ r = ioctl(ifp->ctx->pf_inet_fd,
+ action < 0 ? SIOCDIFADDR : SIOCAIFADDR, &ifra);
+ return r;
+}
+
+static int
+if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct rt_msghdr *rtm)
+{
+ char *cp;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+
+ cp = (char *)(void *)(rtm + 1);
+ sa = (struct sockaddr *)(void *)cp;
+ if (sa->sa_family != AF_INET)
+ return -1;
+ if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY))
+ return -1;
+#ifdef RTF_CLONED
+ if (rtm->rtm_flags & RTF_CLONED)
+ return -1;
+#endif
+#ifdef RTF_LOCAL
+ if (rtm->rtm_flags & RTF_LOCAL)
+ return -1;
+#endif
+#ifdef RTF_BROADCAST
+ if (rtm->rtm_flags & RTF_BROADCAST)
+ return -1;
+#endif
+
+ get_addrs(rtm->rtm_addrs, cp, rti_info);
+ memset(rt, 0, sizeof(*rt));
+ rt->flags = (unsigned int)rtm->rtm_flags;
+ COPYOUT(rt->dest, rti_info[RTAX_DST]);
+ if (rtm->rtm_addrs & RTA_NETMASK)
+ COPYOUT(rt->net, rti_info[RTAX_NETMASK]);
+ else
+ rt->net.s_addr = INADDR_BROADCAST;
+ COPYOUT(rt->gate, rti_info[RTAX_GATEWAY]);
+ COPYOUT(rt->src, rti_info[RTAX_IFA]);
+
+ if (rtm->rtm_inits & RTV_MTU)
+ rt->mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;
+
+ if (rtm->rtm_index)
+ rt->iface = if_findindex(ctx->ifaces, rtm->rtm_index);
+ else if (rtm->rtm_addrs & RTA_IFP) {
+ struct sockaddr_dl *sdl;
+
+ sdl = (struct sockaddr_dl *)(void *)rti_info[RTAX_IFP];
+ rt->iface = if_findsdl(ctx, sdl);
+ }
+
+ /* If we don't have an interface and it's a host route, it maybe
+ * to a local ip via the loopback interface. */
+ if (rt->iface == NULL &&
+ !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY)))
+ {
+ struct ipv4_addr *ia;
+
+ if ((ia = ipv4_findaddr(ctx, &rt->dest)))
+ rt->iface = ia->iface;
+ }
+
+ return 0;
+}
+
+int
+if_route(unsigned char cmd, const struct rt *rt)
+{
+ const struct dhcp_state *state;
+ const struct ipv4ll_state *istate;
+ union sockunion {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_dl sdl;
+ } su;
+ struct rtm
+ {
+ struct rt_msghdr hdr;
+ char buffer[sizeof(su) * RTAX_MAX];
+ } rtm;
+ char *bp = rtm.buffer;
+ size_t l;
+
+#define ADDSU { \
+ l = RT_ROUNDUP(su.sa.sa_len); \
+ memcpy(bp, &su, l); \
+ bp += l; \
+ }
+#define ADDADDR(addr) { \
+ memset(&su, 0, sizeof(su)); \
+ su.sin.sin_family = AF_INET; \
+ su.sin.sin_len = sizeof(su.sin); \
+ (&su.sin)->sin_addr = *(addr); \
+ ADDSU; \
+ }
+
+#if defined(__minix)
+ /*
+ * It seems that when dhcpcd(8) is invoked on a single interface, which
+ * is what netconf(8) configures interfaces to do right now, dhcpcd
+ * sometimes tries to remove routes for interfaces it does not manage.
+ * These routes therefore do not have an associated "iface", causing
+ * dhcpcd to crash on a NULL pointer dereference in this function.
+ * This quick hack prevents it from attempting to perform such
+ * dereferences. The affected scenario seems to be mainly that dhcpcd
+ * is unable to replace a preexisting default route because of this,
+ * but arguably if it didn't set it, it shouldn't remove it either..
+ * Better solutions will have to come from upstream.
+ */
+ if (rt->iface == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+#endif /* defined(__minix) */
+
+ if (cmd != RTM_DELETE) {
+ state = D_CSTATE(rt->iface);
+ istate = IPV4LL_CSTATE(rt->iface);
+ } else {
+ /* appease GCC */
+ state = NULL;
+ istate = NULL;
+ }
+ memset(&rtm, 0, sizeof(rtm));
+ rtm.hdr.rtm_version = RTM_VERSION;
+ rtm.hdr.rtm_seq = 1;
+ rtm.hdr.rtm_type = cmd;
+ rtm.hdr.rtm_addrs = RTA_DST;
+ if (cmd == RTM_ADD || cmd == RTM_CHANGE)
+ rtm.hdr.rtm_addrs |= RTA_GATEWAY;
+ rtm.hdr.rtm_flags = RTF_UP;
+#ifdef RTF_PINNED
+ if (cmd != RTM_ADD)
+ rtm.hdr.rtm_flags |= RTF_PINNED;
+#endif
+
+ if (cmd != RTM_DELETE) {
+ rtm.hdr.rtm_addrs |= RTA_IFA | RTA_IFP;
+ /* None interface subnet routes are static. */
+ if ((rt->gate.s_addr != INADDR_ANY ||
+ rt->net.s_addr != state->net.s_addr ||
+ rt->dest.s_addr !=
+ (state->addr.s_addr & state->net.s_addr)) &&
+ (istate == NULL ||
+ rt->dest.s_addr !=
+ (istate->addr.s_addr & inaddr_llmask.s_addr) ||
+ rt->net.s_addr != inaddr_llmask.s_addr))
+ rtm.hdr.rtm_flags |= RTF_STATIC;
+ else {
+#ifdef RTF_CLONING
+ rtm.hdr.rtm_flags |= RTF_CLONING;
+#endif
+#ifdef RTP_CONNECTED
+ rtm.hdr.rtm_priority = RTP_CONNECTED;
+#endif
+ }
+ }
+ if (rt->net.s_addr == htonl(INADDR_BROADCAST) &&
+ rt->gate.s_addr == htonl(INADDR_ANY))
+ {
+#ifdef RTF_CLONING
+ /* We add a cloning network route for a single host.
+ * Traffic to the host will generate a cloned route and the
+ * hardware address will resolve correctly.
+ * It might be more correct to use RTF_HOST instead of
+ * RTF_CLONING, and that does work, but some OS generate
+ * an arp warning diagnostic which we don't want to do. */
+ rtm.hdr.rtm_flags |= RTF_CLONING;
+ rtm.hdr.rtm_addrs |= RTA_NETMASK;
+#else
+ rtm.hdr.rtm_flags |= RTF_HOST;
+#endif
+ } else if (rt->gate.s_addr == htonl(INADDR_LOOPBACK) &&
+ rt->net.s_addr == htonl(INADDR_BROADCAST))
+ {
+ rtm.hdr.rtm_flags |= RTF_HOST | RTF_GATEWAY;
+ /* Going via lo0 so remove the interface flags */
+ if (cmd == RTM_ADD)
+ rtm.hdr.rtm_addrs &= ~(RTA_IFA | RTA_IFP);
+ } else {
+ rtm.hdr.rtm_addrs |= RTA_NETMASK;
+ if (rtm.hdr.rtm_flags & RTF_STATIC)
+ rtm.hdr.rtm_flags |= RTF_GATEWAY;
+ }
+ if ((cmd == RTM_ADD || cmd == RTM_CHANGE) &&
+ !(rtm.hdr.rtm_flags & RTF_GATEWAY))
+ rtm.hdr.rtm_addrs |= RTA_IFA | RTA_IFP;
+
+ ADDADDR(&rt->dest);
+ if (rtm.hdr.rtm_addrs & RTA_GATEWAY) {
+#ifdef RTF_CLONING
+ if ((rtm.hdr.rtm_flags & (RTF_HOST | RTF_CLONING) &&
+#else
+ if ((rtm.hdr.rtm_flags & RTF_HOST &&
+#endif
+ rt->gate.s_addr != htonl(INADDR_LOOPBACK)) ||
+ !(rtm.hdr.rtm_flags & RTF_STATIC))
+ {
+ if_linkaddr(&su.sdl, rt->iface);
+ ADDSU;
+ } else
+ ADDADDR(&rt->gate);
+ }
+
+ if (rtm.hdr.rtm_addrs & RTA_NETMASK)
+ ADDADDR(&rt->net);
+
+ if ((cmd == RTM_ADD || cmd == RTM_CHANGE) &&
+ (rtm.hdr.rtm_addrs & (RTA_IFP | RTA_IFA)))
+ {
+ rtm.hdr.rtm_index = (unsigned short)rt->iface->index;
+ if (rtm.hdr.rtm_addrs & RTA_IFP) {
+ if_linkaddr(&su.sdl, rt->iface);
+ ADDSU;
+ }
+
+ if (rtm.hdr.rtm_addrs & RTA_IFA)
+ ADDADDR(istate == NULL ? &state->addr : &istate->addr);
+
+ if (rt->mtu) {
+ rtm.hdr.rtm_inits |= RTV_MTU;
+ rtm.hdr.rtm_rmx.rmx_mtu = rt->mtu;
+ }
+ }
+
+#undef ADDADDR
+#undef ADDSU
+
+ rtm.hdr.rtm_msglen = (unsigned short)(bp - (char *)&rtm);
+ return write(rt->iface->ctx->link_fd,
+ &rtm, rtm.hdr.rtm_msglen) == -1 ? -1 : 0;
+}
+
+int
+if_initrt(struct interface *ifp)
+{
+ struct rt_msghdr *rtm;
+ int mib[6];
+ size_t needed;
+ char *buf, *p, *end;
+ struct rt rt;
+
+ ipv4_freerts(ifp->ctx->ipv4_kroutes);
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
+ return -1;
+ if (needed == 0)
+ return 0;
+ if ((buf = malloc(needed)) == NULL)
+ return -1;
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
+ return -1;
+
+ end = buf + needed;
+ for (p = buf; p < end; p += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)(void *)p;
+ if (if_copyrt(ifp->ctx, &rt, rtm) == 0)
+ ipv4_handlert(ifp->ctx, RTM_ADD, &rt);
+ }
+ free(buf);
+ return 0;
+}
+
+#ifdef SIOCGIFAFLAG_IN
+int
+if_addrflags(const struct in_addr *addr, const struct interface *ifp)
+{
+ struct ifreq ifr;
+ struct sockaddr_in *sin;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ sin = (struct sockaddr_in *)(void *)&ifr.ifr_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_addr = *addr;
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFAFLAG_IN, &ifr) == -1)
+ return -1;
+ return ifr.ifr_addrflags;
+}
+#else
+int
+if_addrflags(__unused const struct in_addr *addr,
+ __unused const struct interface *ifp)
+{
+
+ errno = ENOTSUP;
+ return 0;
+}
+#endif
+#endif /* INET */
+
+#ifdef INET6
+static void
+ifa_scope(struct sockaddr_in6 *sin, unsigned int ifindex)
+{
+
+#ifdef __KAME__
+ /* KAME based systems want to store the scope inside the sin6_addr
+ * for link local addreses */
+ if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) {
+ uint16_t scope = htons((uint16_t)ifindex);
+ memcpy(&sin->sin6_addr.s6_addr[2], &scope,
+ sizeof(scope));
+ }
+ sin->sin6_scope_id = 0;
+#else
+ if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr))
+ sin->sin6_scope_id = ifindex;
+ else
+ sin->sin6_scope_id = 0;
+#endif
+}
+
+#ifdef __KAME__
+#define DESCOPE(ia6) do { \
+ if (IN6_IS_ADDR_LINKLOCAL((ia6))) \
+ (ia6)->s6_addr[2] = (ia6)->s6_addr[3] = '\0'; \
+ } while (/*CONSTCOND */0)
+#else
+#define DESCOPE(ia6)
+#endif
+
+int
+if_address6(const struct ipv6_addr *ia, int action)
+{
+ struct in6_aliasreq ifa;
+ struct in6_addr mask;
+
+ memset(&ifa, 0, sizeof(ifa));
+ strlcpy(ifa.ifra_name, ia->iface->name, sizeof(ifa.ifra_name));
+ /*
+ * We should not set IN6_IFF_TENTATIVE as the kernel should be
+ * able to work out if it's a new address or not.
+ *
+ * We should set IN6_IFF_AUTOCONF, but the kernel won't let us.
+ * This is probably a safety measure, but still it's not entirely right
+ * either.
+ */
+#if 0
+ if (ia->autoconf)
+ ifa.ifra_flags |= IN6_IFF_AUTOCONF;
+#endif
+#if defined(__minix)
+ /*
+ * On MINIX 3, do set the IN6_IFF_AUTOCONF flag: this tells the TCP/IP
+ * service that the address does not come with an implied subnet.
+ */
+ if (ia->flags & IPV6_AF_AUTOCONF)
+ ifa.ifra_flags |= IN6_IFF_AUTOCONF;
+#endif
+#ifdef IPV6_MANAGETEMPADDR /* XXX typo fix on MINIX3: "MANGE" -> "MANAGE" */
+ if (ia->flags & IPV6_AF_TEMPORARY)
+ ifa.ifra_flags |= IN6_IFF_TEMPORARY;
+#endif
+
+#define ADDADDR(v, addr) { \
+ (v)->sin6_family = AF_INET6; \
+ (v)->sin6_len = sizeof(*v); \
+ (v)->sin6_addr = *(addr); \
+ }
+
+ ADDADDR(&ifa.ifra_addr, &ia->addr);
+ ifa_scope(&ifa.ifra_addr, ia->iface->index);
+ ipv6_mask(&mask, ia->prefix_len);
+ ADDADDR(&ifa.ifra_prefixmask, &mask);
+ ifa.ifra_lifetime.ia6t_vltime = ia->prefix_vltime;
+ ifa.ifra_lifetime.ia6t_pltime = ia->prefix_pltime;
+#undef ADDADDR
+
+ return ioctl(ia->iface->ctx->pf_inet6_fd,
+ action < 0 ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &ifa);
+}
+
+
+static int
+if_copyrt6(struct dhcpcd_ctx *ctx, struct rt6 *rt, struct rt_msghdr *rtm)
+{
+ char *cp;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+
+ cp = (char *)(void *)(rtm + 1);
+ sa = (struct sockaddr *)(void *)cp;
+ if (sa->sa_family != AF_INET6)
+ return -1;
+ if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY))
+ return -1;
+#ifdef RTF_CLONED
+ if (rtm->rtm_flags & (RTF_CLONED | RTF_HOST))
+ return -1;
+#else
+ if (rtm->rtm_flags & RTF_HOST)
+ return -1;
+#endif
+#ifdef RTF_LOCAL
+ if (rtm->rtm_flags & RTF_LOCAL)
+ return -1;
+#endif
+
+ get_addrs(rtm->rtm_addrs, cp, rti_info);
+ memset(rt, 0, sizeof(*rt));
+ rt->flags = (unsigned int)rtm->rtm_flags;
+ COPYOUT6(rt->dest, rti_info[RTAX_DST]);
+ if (rtm->rtm_addrs & RTA_NETMASK) {
+ /*
+ * We need to zero out the struct beyond sin6_len and
+ * ensure it's valid.
+ * I have no idea what the invalid data is for, could be
+ * a kernel bug or actually used for something.
+ * Either way it needs to be zeroed out.
+ */
+ struct sockaddr_in6 *sin6;
+ size_t e, i, len = 0, final = 0;
+
+ sin6 = (struct sockaddr_in6 *)(void *)rti_info[RTAX_NETMASK];
+ rt->net = sin6->sin6_addr;
+ e = sin6->sin6_len - offsetof(struct sockaddr_in6, sin6_addr);
+ if (e > sizeof(struct in6_addr))
+ e = sizeof(struct in6_addr);
+ for (i = 0; i < e; i++) {
+ switch (rt->net.s6_addr[i] & 0xff) {
+ case 0xff:
+ /* We don't really want the length,
+ * just that it's valid */
+ len++;
+ break;
+ case 0xfe:
+ case 0xfc:
+ case 0xf8:
+ case 0xf0:
+ case 0xe0:
+ case 0xc0:
+ case 0x80:
+ len++;
+ final = 1;
+ break;
+ default:
+ rt->net.s6_addr[i] = 0x00;
+ final = 1;
+ break;
+ }
+ if (final)
+ break;
+ }
+ if (len == 0)
+ i = 0;
+ while (i < sizeof(rt->net.s6_addr))
+ rt->net.s6_addr[i++] = 0x00;
+ } else
+ ipv6_mask(&rt->net, 128);
+ COPYOUT6(rt->gate, rti_info[RTAX_GATEWAY]);
+
+ if (rtm->rtm_inits & RTV_MTU)
+ rt->mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;
+
+ if (rtm->rtm_index)
+ rt->iface = if_findindex(ctx->ifaces, rtm->rtm_index);
+ else if (rtm->rtm_addrs & RTA_IFP) {
+ struct sockaddr_dl *sdl;
+
+ sdl = (struct sockaddr_dl *)(void *)rti_info[RTAX_IFP];
+ rt->iface = if_findsdl(ctx, sdl);
+ }
+ /* If we don't have an interface and it's a host route, it maybe
+ * to a local ip via the loopback interface. */
+ if (rt->iface == NULL &&
+ !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY)))
+ {
+ struct ipv6_addr *ia;
+
+ if ((ia = ipv6_findaddr(ctx, &rt->dest, 0)))
+ rt->iface = ia->iface;
+ }
+
+ return 0;
+}
+
+int
+if_route6(unsigned char cmd, const struct rt6 *rt)
+{
+ union sockunion {
+ struct sockaddr sa;
+ struct sockaddr_in6 sin;
+ struct sockaddr_dl sdl;
+ } su;
+ struct rtm
+ {
+ struct rt_msghdr hdr;
+ char buffer[sizeof(su) * RTAX_MAX];
+ } rtm;
+ char *bp = rtm.buffer;
+ size_t l;
+
+#define ADDSU { \
+ l = RT_ROUNDUP(su.sa.sa_len); \
+ memcpy(bp, &su, l); \
+ bp += l; \
+ }
+#define ADDADDRS(addr, scope) { \
+ memset(&su, 0, sizeof(su)); \
+ su.sin.sin6_family = AF_INET6; \
+ su.sin.sin6_len = sizeof(su.sin); \
+ (&su.sin)->sin6_addr = *addr; \
+ if (scope) \
+ ifa_scope(&su.sin, scope); \
+ ADDSU; \
+ }
+#define ADDADDR(addr) ADDADDRS(addr, 0)
+
+ memset(&rtm, 0, sizeof(rtm));
+ rtm.hdr.rtm_version = RTM_VERSION;
+ rtm.hdr.rtm_seq = 1;
+ rtm.hdr.rtm_type = cmd;
+ rtm.hdr.rtm_flags = RTF_UP | (int)rt->flags;
+#ifdef RTF_PINNED
+ if (rtm.hdr.rtm_type != RTM_ADD)
+ rtm.hdr.rtm_flags |= RTF_PINNED;
+#endif
+ rtm.hdr.rtm_addrs = RTA_DST | RTA_NETMASK;
+ /* None interface subnet routes are static. */
+ if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) {
+#ifdef RTF_CLONING
+ rtm.hdr.rtm_flags |= RTF_CLONING;
+#endif
+#ifdef RTP_CONNECTED
+ rtm.hdr.rtm_priority = RTP_CONNECTED;
+#endif
+ } else
+ rtm.hdr.rtm_flags |= RTF_GATEWAY | RTF_STATIC;
+
+ if (cmd == RTM_ADD)
+ rtm.hdr.rtm_addrs |= RTA_GATEWAY;
+ if (cmd == RTM_ADD && !(rtm.hdr.rtm_flags & RTF_REJECT))
+ rtm.hdr.rtm_addrs |= RTA_IFP | RTA_IFA;
+
+ ADDADDR(&rt->dest);
+ if (rtm.hdr.rtm_addrs & RTA_GATEWAY) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) {
+ if_linkaddr(&su.sdl, rt->iface);
+ ADDSU;
+ } else {
+ ADDADDRS(&rt->gate, rt->iface->index);
+ }
+ }
+
+ if (rtm.hdr.rtm_addrs & RTA_NETMASK)
+ ADDADDR(&rt->net);
+
+ if ((cmd == RTM_ADD || cmd == RTM_CHANGE) &&
+ (rtm.hdr.rtm_addrs & (RTA_IFP | RTA_IFA)))
+ {
+ rtm.hdr.rtm_index = (unsigned short)rt->iface->index;
+ if (rtm.hdr.rtm_addrs & RTA_IFP) {
+ if_linkaddr(&su.sdl, rt->iface);
+ ADDSU;
+ }
+
+ if (rtm.hdr.rtm_addrs & RTA_IFA) {
+ const struct ipv6_addr *lla;
+
+ lla = ipv6_linklocal(rt->iface);
+ if (lla == NULL) /* unlikely */
+ return -1;
+ ADDADDRS(&lla->addr, rt->iface->index);
+ }
+
+ if (rt->mtu) {
+ rtm.hdr.rtm_inits |= RTV_MTU;
+ rtm.hdr.rtm_rmx.rmx_mtu = rt->mtu;
+ }
+ }
+
+#undef ADDADDR
+#undef ADDSU
+
+ rtm.hdr.rtm_msglen = (unsigned short)(bp - (char *)&rtm);
+ return write(rt->iface->ctx->link_fd,
+ &rtm, rtm.hdr.rtm_msglen) == -1 ? -1 : 0;
+}
+
+int
+if_initrt6(struct interface *ifp)
+{
+ struct rt_msghdr *rtm;
+ int mib[6];
+ size_t needed;
+ char *buf, *p, *end;
+ struct rt6 rt;
+
+ ipv6_freerts(&ifp->ctx->ipv6->kroutes);
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET6;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
+ return -1;
+ if (needed == 0)
+ return 0;
+ if ((buf = malloc(needed)) == NULL)
+ return -1;
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
+ return -1;
+
+ end = buf + needed;
+ for (p = buf; p < end; p += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)(void *)p;
+ if (if_copyrt6(ifp->ctx, &rt, rtm) == 0)
+ ipv6_handlert(ifp->ctx, RTM_ADD, &rt);
+ }
+ free(buf);
+ return 0;
+}
+
+int
+if_addrflags6(const struct in6_addr *addr, const struct interface *ifp)
+{
+ int flags;
+ struct in6_ifreq ifr6;
+
+ memset(&ifr6, 0, sizeof(ifr6));
+ strlcpy(ifr6.ifr_name, ifp->name, sizeof(ifr6.ifr_name));
+ ifr6.ifr_addr.sin6_family = AF_INET6;
+ ifr6.ifr_addr.sin6_addr = *addr;
+ ifa_scope(&ifr6.ifr_addr, ifp->index);
+ if (ioctl(ifp->ctx->pf_inet6_fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
+ flags = ifr6.ifr_ifru.ifru_flags6;
+ else
+ flags = -1;
+ return flags;
+}
+
+int
+if_getlifetime6(struct ipv6_addr *ia)
+{
+ struct in6_ifreq ifr6;
+ time_t t;
+ struct in6_addrlifetime *lifetime;
+
+ memset(&ifr6, 0, sizeof(ifr6));
+ strlcpy(ifr6.ifr_name, ia->iface->name, sizeof(ifr6.ifr_name));
+ ifr6.ifr_addr.sin6_family = AF_INET6;
+ ifr6.ifr_addr.sin6_addr = ia->addr;
+ ifa_scope(&ifr6.ifr_addr, ia->iface->index);
+ if (ioctl(ia->iface->ctx->pf_inet6_fd,
+ SIOCGIFALIFETIME_IN6, &ifr6) == -1)
+ return -1;
+
+ t = time(NULL);
+ lifetime = &ifr6.ifr_ifru.ifru_lifetime;
+
+ if (lifetime->ia6t_preferred)
+ ia->prefix_pltime = (uint32_t)(lifetime->ia6t_preferred -
+ MIN(t, lifetime->ia6t_preferred));
+ else
+ ia->prefix_pltime = ND6_INFINITE_LIFETIME;
+ if (lifetime->ia6t_expire) {
+ ia->prefix_vltime = (uint32_t)(lifetime->ia6t_expire -
+ MIN(t, lifetime->ia6t_expire));
+ /* Calculate the created time */
+ clock_gettime(CLOCK_MONOTONIC, &ia->created);
+ ia->created.tv_sec -= lifetime->ia6t_vltime - ia->prefix_vltime;
+ } else
+ ia->prefix_vltime = ND6_INFINITE_LIFETIME;
+ return 0;
+}
+#endif
+
+int
+if_managelink(struct dhcpcd_ctx *ctx)
+{
+ /* route and ifwatchd like a msg buf size of 2048 */
+ char msg[2048], *p, *e, *cp;
+ ssize_t bytes;
+ struct rt_msghdr *rtm;
+ struct if_announcemsghdr *ifan;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ int len;
+ struct sockaddr_dl sdl;
+ struct interface *ifp;
+#ifdef INET
+ struct rt rt;
+#endif
+#ifdef INET6
+ struct rt6 rt6;
+ struct in6_addr ia6, net6;
+ struct sockaddr_in6 *sin6;
+#endif
+#if (defined(INET) && defined(IN_IFF_TENTATIVE)) || defined(INET6)
+ int ifa_flags;
+#elif defined(__minix)
+ int ifa_flags; /* compilation fix for USE_INET6=no */
+#endif
+
+ if ((bytes = read(ctx->link_fd, msg, sizeof(msg))) == -1)
+ return -1;
+ e = msg + bytes;
+ for (p = msg; p < e; p += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)(void *)p;
+ // Ignore messages generated by us
+ if (rtm->rtm_pid == getpid())
+ break;
+ switch(rtm->rtm_type) {
+#ifdef RTM_IFANNOUNCE
+ case RTM_IFANNOUNCE:
+ ifan = (struct if_announcemsghdr *)(void *)p;
+ switch(ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ dhcpcd_handleinterface(ctx, 1,
+ ifan->ifan_name);
+ break;
+ case IFAN_DEPARTURE:
+ dhcpcd_handleinterface(ctx, -1,
+ ifan->ifan_name);
+ break;
+ }
+ break;
+#endif
+ case RTM_IFINFO:
+ ifm = (struct if_msghdr *)(void *)p;
+ ifp = if_findindex(ctx->ifaces, ifm->ifm_index);
+ if (ifp == NULL)
+ break;
+ switch (ifm->ifm_data.ifi_link_state) {
+ case LINK_STATE_DOWN:
+ len = LINK_DOWN;
+ break;
+ case LINK_STATE_UP:
+ len = LINK_UP;
+ break;
+ default:
+ /* handle_carrier will re-load
+ * the interface flags and check for
+ * IFF_RUNNING as some drivers that
+ * don't handle link state also don't
+ * set IFF_RUNNING when this routing
+ * message is generated.
+ * As such, it is a race ...*/
+ len = LINK_UNKNOWN;
+ break;
+ }
+ dhcpcd_handlecarrier(ctx, len,
+ (unsigned int)ifm->ifm_flags, ifp->name);
+ break;
+ case RTM_ADD:
+ case RTM_CHANGE:
+ case RTM_DELETE:
+ cp = (char *)(void *)(rtm + 1);
+ sa = (struct sockaddr *)(void *)cp;
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (if_copyrt(ctx, &rt, rtm) == 0)
+ ipv4_handlert(ctx, rtm->rtm_type, &rt);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY))
+ break;
+ /*
+ * BSD caches host routes in the
+ * routing table.
+ * As such, we should be notified of
+ * reachability by its existance
+ * with a hardware address
+ */
+ if (rtm->rtm_flags & (RTF_HOST)) {
+ get_addrs(rtm->rtm_addrs, cp, rti_info);
+ COPYOUT6(ia6, rti_info[RTAX_DST]);
+ DESCOPE(&ia6);
+ if (rti_info[RTAX_GATEWAY]->sa_family
+ == AF_LINK)
+ memcpy(&sdl,
+ rti_info[RTAX_GATEWAY],
+ sizeof(sdl));
+ else
+ sdl.sdl_alen = 0;
+ ipv6nd_neighbour(ctx, &ia6,
+ rtm->rtm_type != RTM_DELETE &&
+ sdl.sdl_alen ?
+ IPV6ND_REACHABLE : 0);
+ break;
+ }
+
+ if (if_copyrt6(ctx, &rt6, rtm) == 0)
+ ipv6_handlert(ctx, rtm->rtm_type, &rt6);
+ break;
+#endif
+ }
+ break;
+#ifdef RTM_CHGADDR
+ case RTM_CHGADDR: /* FALLTHROUGH */
+#endif
+ case RTM_DELADDR: /* FALLTHROUGH */
+ case RTM_NEWADDR:
+ ifam = (struct ifa_msghdr *)(void *)p;
+ ifp = if_findindex(ctx->ifaces, ifam->ifam_index);
+ if (ifp == NULL)
+ break;
+ cp = (char *)(void *)(ifam + 1);
+ get_addrs(ifam->ifam_addrs, cp, rti_info);
+ if (rti_info[RTAX_IFA] == NULL)
+ break;
+ switch (rti_info[RTAX_IFA]->sa_family) {
+ case AF_LINK:
+#ifdef RTM_CHGADDR
+ if (rtm->rtm_type != RTM_CHGADDR)
+ break;
+#else
+ if (rtm->rtm_type != RTM_NEWADDR)
+ break;
+#endif
+ memcpy(&sdl, rti_info[RTAX_IFA],
+ rti_info[RTAX_IFA]->sa_len);
+ dhcpcd_handlehwaddr(ctx, ifp->name,
+ (const unsigned char*)CLLADDR(&sdl),
+ sdl.sdl_alen);
+ break;
+#ifdef INET
+ case AF_INET:
+ case 255: /* FIXME: Why 255? */
+ COPYOUT(rt.dest, rti_info[RTAX_IFA]);
+ COPYOUT(rt.net, rti_info[RTAX_NETMASK]);
+ COPYOUT(rt.gate, rti_info[RTAX_BRD]);
+ if (rtm->rtm_type == RTM_NEWADDR) {
+ ifa_flags = if_addrflags(&rt.dest, ifp);
+ if (ifa_flags == -1)
+ break;
+ } else
+ ifa_flags = 0;
+ ipv4_handleifa(ctx, rtm->rtm_type,
+ NULL, ifp->name,
+ &rt.dest, &rt.net, &rt.gate, ifa_flags);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6*)(void *)
+ rti_info[RTAX_IFA];
+ ia6 = sin6->sin6_addr;
+ DESCOPE(&ia6);
+ sin6 = (struct sockaddr_in6*)(void *)
+ rti_info[RTAX_NETMASK];
+ net6 = sin6->sin6_addr;
+ DESCOPE(&net6);
+ if (rtm->rtm_type == RTM_NEWADDR) {
+ ifa_flags = if_addrflags6(&ia6, ifp);
+ if (ifa_flags == -1)
+ break;
+ } else
+ ifa_flags = 0;
+ ipv6_handleifa(ctx, rtm->rtm_type, NULL,
+ ifp->name, &ia6, ipv6_prefixlen(&net6),
+ ifa_flags);
+ break;
+#endif
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+#ifndef SYS_NMLN /* OSX */
+# define SYS_NMLN 256
+#endif
+#ifndef HW_MACHINE_ARCH
+# ifdef HW_MODEL /* OpenBSD */
+# define HW_MACHINE_ARCH HW_MODEL
+# endif
+#endif
+int
+if_machinearch(char *str, size_t len)
+{
+ int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
+ char march[SYS_NMLN];
+ size_t marchlen = sizeof(march);
+
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]),
+ march, &marchlen, NULL, 0) != 0)
+ return -1;
+ return snprintf(str, len, ":%s", march);
+}
+
+#ifdef INET6
+#ifdef IPV6CTL_ACCEPT_RTADV
+#define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0)
+#define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1)
+static int
+inet6_sysctl(int code, int val, int action)
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 };
+ size_t size;
+
+ mib[3] = code;
+ size = sizeof(val);
+ if (action) {
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]),
+ NULL, 0, &val, size) == -1)
+ return -1;
+ return 0;
+ }
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &val, &size, NULL, 0) == -1)
+ return -1;
+ return val;
+}
+#endif
+
+#ifdef IPV6_MANAGETEMPADDR
+#ifndef IPV6CTL_TEMPVLTIME
+#define get_inet6_sysctlbyname(code) inet6_sysctlbyname(code, 0, 0)
+#define set_inet6_sysctlbyname(code, val) inet6_sysctlbyname(code, val, 1)
+static int
+inet6_sysctlbyname(const char *name, int val, int action)
+{
+ size_t size;
+
+ size = sizeof(val);
+ if (action) {
+ if (sysctlbyname(name, NULL, 0, &val, size) == -1)
+ return -1;
+ return 0;
+ }
+ if (sysctlbyname(name, &val, &size, NULL, 0) == -1)
+ return -1;
+ return val;
+}
+#endif
+
+int
+ip6_use_tempaddr(__unused const char *ifname)
+{
+ int val;
+
+#ifdef IPV6CTL_USETEMPADDR
+ val = get_inet6_sysctl(IPV6CTL_USETEMPADDR);
+#else
+ val = get_inet6_sysctlbyname("net.inet6.ip6.use_tempaddr");
+#endif
+ return val == -1 ? 0 : val;
+}
+
+int
+ip6_temp_preferred_lifetime(__unused const char *ifname)
+{
+ int val;
+
+#ifdef IPV6CTL_TEMPPLTIME
+ val = get_inet6_sysctl(IPV6CTL_TEMPPLTIME);
+#else
+ val = get_inet6_sysctlbyname("net.inet6.ip6.temppltime");
+#endif
+ return val < 0 ? TEMP_PREFERRED_LIFETIME : val;
+}
+
+int
+ip6_temp_valid_lifetime(__unused const char *ifname)
+{
+ int val;
+
+#ifdef IPV6CTL_TEMPVLTIME
+ val = get_inet6_sysctl(IPV6CTL_TEMPVLTIME);
+#else
+ val = get_inet6_sysctlbyname("net.inet6.ip6.tempvltime");
+#endif
+ return val < 0 ? TEMP_VALID_LIFETIME : val;
+}
+#endif
+
+#define del_if_nd6_flag(s, ifname, flag) if_nd6_flag((s), (ifp), (flag), -1)
+#define get_if_nd6_flag(s, ifname, flag) if_nd6_flag((s), (ifp), (flag), 0)
+#define set_if_nd6_flag(s, ifname, flag) if_nd6_flag((s), (ifp), (flag), 1)
+static int
+if_nd6_flag(int s, const struct interface *ifp, unsigned int flag, int set)
+{
+ struct in6_ndireq nd;
+ unsigned int oflags;
+
+ memset(&nd, 0, sizeof(nd));
+ strlcpy(nd.ifname, ifp->name, sizeof(nd.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, &nd) == -1)
+ return -1;
+ if (set == 0)
+ return nd.ndi.flags & flag ? 1 : 0;
+
+ oflags = nd.ndi.flags;
+ if (set == -1)
+ nd.ndi.flags &= ~flag;
+ else
+ nd.ndi.flags |= flag;
+ if (oflags == nd.ndi.flags)
+ return 0;
+ return ioctl(s, SIOCSIFINFO_FLAGS, &nd);
+}
+
+static int
+if_raflush(int s)
+{
+ char dummy[IFNAMSIZ + 8];
+
+ strlcpy(dummy, "lo0", sizeof(dummy));
+ if (ioctl(s, SIOCSRTRFLUSH_IN6, (void *)&dummy) == -1 ||
+ ioctl(s, SIOCSPFXFLUSH_IN6, (void *)&dummy) == -1)
+ return -1;
+ return 0;
+}
+
+#ifdef SIOCIFAFATTACH
+static int
+af_attach(int s, const struct interface *ifp, int af)
+{
+ struct if_afreq ifar;
+
+ strlcpy(ifar.ifar_name, ifp->name, sizeof(ifar.ifar_name));
+ ifar.ifar_af = af;
+ return ioctl(s, SIOCIFAFATTACH, (void *)&ifar);
+}
+#endif
+
+#ifdef SIOCGIFXFLAGS
+static int
+set_ifxflags(int s, const struct interface *ifp, int own)
+{
+ struct ifreq ifr;
+ int flags;
+
+#ifndef IFXF_NOINET6
+ /* No point in removing the no inet6 flag if it doesn't
+ * exist and we're not owning inet6. */
+ if (! own)
+ return 0;
+#endif
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFXFLAGS, (void *)&ifr) == -1)
+ return -1;
+ flags = ifr.ifr_flags;
+#ifdef IFXF_NOINET6
+ flags &= ~IFXF_NOINET6;
+#endif
+ if (own)
+ flags &= ~IFXF_AUTOCONF6;
+ if (ifr.ifr_flags == flags)
+ return 0;
+ ifr.ifr_flags = flags;
+ return ioctl(s, SIOCSIFXFLAGS, (void *)&ifr);
+}
+#endif
+
+static int
+_if_checkipv6(int s, struct dhcpcd_ctx *ctx,
+ const struct interface *ifp, int own)
+{
+ int ra;
+
+ if (ifp) {
+#ifdef ND6_IFF_OVERRIDE_RTADV
+ int override;
+#endif
+
+#ifdef ND6_IFF_IFDISABLED
+ if (del_if_nd6_flag(s, ifp, ND6_IFF_IFDISABLED) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: del_if_nd6_flag: ND6_IFF_IFDISABLED: %m",
+ ifp->name);
+ return -1;
+ }
+#endif
+
+#ifdef ND6_IFF_PERFORMNUD
+ if (set_if_nd6_flag(s, ifp, ND6_IFF_PERFORMNUD) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: set_if_nd6_flag: ND6_IFF_PERFORMNUD: %m",
+ ifp->name);
+ return -1;
+ }
+#endif
+
+#ifdef ND6_IFF_AUTO_LINKLOCAL
+ if (own) {
+ int all;
+
+ all = get_if_nd6_flag(s, ifp, ND6_IFF_AUTO_LINKLOCAL);
+ if (all == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: get_if_nd6_flag: "
+ "ND6_IFF_AUTO_LINKLOCAL: %m",
+ ifp->name);
+ else if (all != 0) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: disabling Kernel IPv6 "
+ "auto link-local support",
+ ifp->name);
+ if (del_if_nd6_flag(s, ifp,
+ ND6_IFF_AUTO_LINKLOCAL) == -1)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: del_if_nd6_flag: "
+ "ND6_IFF_AUTO_LINKLOCAL: %m",
+ ifp->name);
+ return -1;
+ }
+ }
+ }
+#endif
+
+#ifdef SIOCIFAFATTACH
+ if (af_attach(s, ifp, AF_INET6) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: af_attach: %m", ifp->name);
+ return 1;
+ }
+#endif
+
+#ifdef SIOCGIFXFLAGS
+ if (set_ifxflags(s, ifp, own) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: set_ifxflags: %m", ifp->name);
+ return -1;
+ }
+#endif
+
+#ifdef ND6_IFF_OVERRIDE_RTADV
+ override = get_if_nd6_flag(s, ifp, ND6_IFF_OVERRIDE_RTADV);
+ if (override == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: get_if_nd6_flag: ND6_IFF_OVERRIDE_RTADV: %m",
+ ifp->name);
+ else if (override == 0 && own) {
+ if (set_if_nd6_flag(s, ifp, ND6_IFF_OVERRIDE_RTADV)
+ == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: set_if_nd6_flag: "
+ "ND6_IFF_OVERRIDE_RTADV: %m",
+ ifp->name);
+ else
+ override = 1;
+ }
+#endif
+
+#ifdef ND6_IFF_ACCEPT_RTADV
+ ra = get_if_nd6_flag(s, ifp, ND6_IFF_ACCEPT_RTADV);
+ if (ra == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: get_if_nd6_flag: ND6_IFF_ACCEPT_RTADV: %m",
+ ifp->name);
+ else if (ra != 0 && own) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: disabling Kernel IPv6 RA support",
+ ifp->name);
+ if (del_if_nd6_flag(s, ifp, ND6_IFF_ACCEPT_RTADV)
+ == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: del_if_nd6_flag: "
+ "ND6_IFF_ACCEPT_RTADV: %m",
+ ifp->name);
+ else
+ ra = 0;
+ } else if (ra == 0 && !own)
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: IPv6 kernel autoconf disabled", ifp->name);
+#ifdef ND6_IFF_OVERRIDE_RTADV
+ if (override == 0 && ra)
+ return ctx->ra_global;
+#endif
+ return ra;
+#else
+ return ctx->ra_global;
+#endif
+ }
+
+#ifdef IPV6CTL_ACCEPT_RTADV
+ ra = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV);
+ if (ra == -1)
+ /* The sysctl probably doesn't exist, but this isn't an
+ * error as such so just log it and continue */
+ logger(ifp->ctx, errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
+ "IPV6CTL_ACCEPT_RTADV: %m");
+ else if (ra != 0 && own) {
+ logger(ifp->ctx, LOG_DEBUG, "disabling Kernel IPv6 RA support");
+ if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1) {
+ logger(ifp->ctx, LOG_ERR, "IPV6CTL_ACCEPT_RTADV: %m");
+ return ra;
+ }
+ ra = 0;
+#else
+ ra = 0;
+ if (own) {
+#endif
+ /* Flush the kernel knowledge of advertised routers
+ * and prefixes so the kernel does not expire prefixes
+ * and default routes we are trying to own. */
+ if (if_raflush(s) == -1)
+ logger(ctx, LOG_WARNING, "if_raflush: %m");
+ }
+
+ ctx->ra_global = ra;
+ return ra;
+}
+
+int
+if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *ifp, int own)
+{
+
+ return _if_checkipv6(ctx->pf_inet6_fd, ctx, ifp, own);
+}
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: if-options.c,v 1.27 2015/09/04 12:25:01 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#define _WITH_GETLINE /* Stop FreeBSD bitching */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "config.h"
+#include "common.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "dhcpcd-embedded.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+
+/* These options only make sense in the config file, so don't use any
+ valid short options for them */
+#define O_BASE MAX('z', 'Z') + 1
+#define O_ARPING O_BASE + 1
+#define O_FALLBACK O_BASE + 2
+#define O_DESTINATION O_BASE + 3
+#define O_IPV6RS O_BASE + 4
+#define O_NOIPV6RS O_BASE + 5
+#define O_IPV6RA_FORK O_BASE + 6
+#define O_IPV6RA_OWN O_BASE + 7
+#define O_IPV6RA_OWN_D O_BASE + 8
+#define O_NOALIAS O_BASE + 9
+#define O_IA_NA O_BASE + 10
+#define O_IA_TA O_BASE + 11
+#define O_IA_PD O_BASE + 12
+#define O_HOSTNAME_SHORT O_BASE + 13
+#define O_DEV O_BASE + 14
+#define O_NODEV O_BASE + 15
+#define O_NOIPV4 O_BASE + 16
+#define O_NOIPV6 O_BASE + 17
+#define O_IAID O_BASE + 18
+#define O_DEFINE O_BASE + 19
+#define O_DEFINE6 O_BASE + 20
+#define O_EMBED O_BASE + 21
+#define O_ENCAP O_BASE + 22
+#define O_VENDOPT O_BASE + 23
+#define O_VENDCLASS O_BASE + 24
+#define O_AUTHPROTOCOL O_BASE + 25
+#define O_AUTHTOKEN O_BASE + 26
+#define O_AUTHNOTREQUIRED O_BASE + 27
+#define O_NODHCP O_BASE + 28
+#define O_NODHCP6 O_BASE + 29
+#define O_DHCP O_BASE + 30
+#define O_DHCP6 O_BASE + 31
+#define O_IPV4 O_BASE + 32
+#define O_IPV6 O_BASE + 33
+#define O_CONTROLGRP O_BASE + 34
+#define O_SLAAC O_BASE + 35
+#define O_GATEWAY O_BASE + 36
+#define O_NOUP O_BASE + 37
+#define O_IPV6RA_AUTOCONF O_BASE + 38
+#define O_IPV6RA_NOAUTOCONF O_BASE + 39
+#define O_REJECT O_BASE + 40
+#define O_IPV6RA_ACCEPT_NOPUBLIC O_BASE + 41
+#define O_BOOTP O_BASE + 42
+#define O_DEFINEND O_BASE + 43
+#define O_NODELAY O_BASE + 44
+
+const struct option cf_options[] = {
+ {"background", no_argument, NULL, 'b'},
+ {"script", required_argument, NULL, 'c'},
+ {"debug", no_argument, NULL, 'd'},
+ {"env", required_argument, NULL, 'e'},
+ {"config", required_argument, NULL, 'f'},
+ {"reconfigure", no_argument, NULL, 'g'},
+ {"hostname", optional_argument, NULL, 'h'},
+ {"vendorclassid", optional_argument, NULL, 'i'},
+ {"logfile", required_argument, NULL, 'j'},
+ {"release", no_argument, NULL, 'k'},
+ {"leasetime", required_argument, NULL, 'l'},
+ {"metric", required_argument, NULL, 'm'},
+ {"rebind", no_argument, NULL, 'n'},
+ {"option", required_argument, NULL, 'o'},
+ {"persistent", no_argument, NULL, 'p'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"request", optional_argument, NULL, 'r'},
+ {"inform", optional_argument, NULL, 's'},
+ {"timeout", required_argument, NULL, 't'},
+ {"userclass", required_argument, NULL, 'u'},
+ {"vendor", required_argument, NULL, 'v'},
+ {"waitip", optional_argument, NULL, 'w'},
+ {"exit", no_argument, NULL, 'x'},
+ {"allowinterfaces", required_argument, NULL, 'z'},
+ {"reboot", required_argument, NULL, 'y'},
+ {"noarp", no_argument, NULL, 'A'},
+ {"nobackground", no_argument, NULL, 'B'},
+ {"nohook", required_argument, NULL, 'C'},
+ {"duid", no_argument, NULL, 'D'},
+ {"lastlease", no_argument, NULL, 'E'},
+ {"fqdn", optional_argument, NULL, 'F'},
+ {"nogateway", no_argument, NULL, 'G'},
+ {"xidhwaddr", no_argument, NULL, 'H'},
+ {"clientid", optional_argument, NULL, 'I'},
+ {"broadcast", no_argument, NULL, 'J'},
+ {"nolink", no_argument, NULL, 'K'},
+ {"noipv4ll", no_argument, NULL, 'L'},
+ {"master", no_argument, NULL, 'M'},
+ {"nooption", optional_argument, NULL, 'O'},
+ {"require", required_argument, NULL, 'Q'},
+ {"static", required_argument, NULL, 'S'},
+ {"test", no_argument, NULL, 'T'},
+ {"dumplease", no_argument, NULL, 'U'},
+ {"variables", no_argument, NULL, 'V'},
+ {"whitelist", required_argument, NULL, 'W'},
+ {"blacklist", required_argument, NULL, 'X'},
+ {"denyinterfaces", required_argument, NULL, 'Z'},
+ {"arping", required_argument, NULL, O_ARPING},
+ {"destination", required_argument, NULL, O_DESTINATION},
+ {"fallback", required_argument, NULL, O_FALLBACK},
+ {"ipv6rs", no_argument, NULL, O_IPV6RS},
+ {"noipv6rs", no_argument, NULL, O_NOIPV6RS},
+ {"ipv6ra_autoconf", no_argument, NULL, O_IPV6RA_AUTOCONF},
+ {"ipv6ra_noautoconf", no_argument, NULL, O_IPV6RA_NOAUTOCONF},
+ {"ipv6ra_fork", no_argument, NULL, O_IPV6RA_FORK},
+ {"ipv6ra_own", no_argument, NULL, O_IPV6RA_OWN},
+ {"ipv6ra_own_default", no_argument, NULL, O_IPV6RA_OWN_D},
+ {"ipv6ra_accept_nopublic", no_argument, NULL, O_IPV6RA_ACCEPT_NOPUBLIC},
+ {"ipv4only", no_argument, NULL, '4'},
+ {"ipv6only", no_argument, NULL, '6'},
+ {"ipv4", no_argument, NULL, O_IPV4},
+ {"noipv4", no_argument, NULL, O_NOIPV4},
+ {"ipv6", no_argument, NULL, O_IPV6},
+ {"noipv6", no_argument, NULL, O_NOIPV6},
+ {"noalias", no_argument, NULL, O_NOALIAS},
+ {"iaid", required_argument, NULL, O_IAID},
+ {"ia_na", no_argument, NULL, O_IA_NA},
+ {"ia_ta", no_argument, NULL, O_IA_TA},
+ {"ia_pd", no_argument, NULL, O_IA_PD},
+ {"hostname_short", no_argument, NULL, O_HOSTNAME_SHORT},
+ {"dev", required_argument, NULL, O_DEV},
+ {"nodev", no_argument, NULL, O_NODEV},
+ {"define", required_argument, NULL, O_DEFINE},
+ {"definend", required_argument, NULL, O_DEFINEND},
+ {"define6", required_argument, NULL, O_DEFINE6},
+ {"embed", required_argument, NULL, O_EMBED},
+ {"encap", required_argument, NULL, O_ENCAP},
+ {"vendopt", required_argument, NULL, O_VENDOPT},
+ {"vendclass", required_argument, NULL, O_VENDCLASS},
+ {"authprotocol", required_argument, NULL, O_AUTHPROTOCOL},
+ {"authtoken", required_argument, NULL, O_AUTHTOKEN},
+ {"noauthrequired", no_argument, NULL, O_AUTHNOTREQUIRED},
+ {"dhcp", no_argument, NULL, O_DHCP},
+ {"nodhcp", no_argument, NULL, O_NODHCP},
+ {"dhcp6", no_argument, NULL, O_DHCP6},
+ {"nodhcp6", no_argument, NULL, O_NODHCP6},
+ {"controlgroup", required_argument, NULL, O_CONTROLGRP},
+ {"slaac", required_argument, NULL, O_SLAAC},
+ {"gateway", no_argument, NULL, O_GATEWAY},
+ {"reject", required_argument, NULL, O_REJECT},
+ {"bootp", no_argument, NULL, O_BOOTP},
+ {"nodelay", no_argument, NULL, O_NODELAY},
+ {"noup", no_argument, NULL, O_NOUP},
+ {NULL, 0, NULL, '\0'}
+};
+
+static char *
+add_environ(struct dhcpcd_ctx *ctx, struct if_options *ifo,
+ const char *value, int uniq)
+{
+ char **newlist;
+ char **lst = ifo->environ;
+ size_t i = 0, l, lv;
+ char *match = NULL, *p, *n;
+
+ match = strdup(value);
+ if (match == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ p = strchr(match, '=');
+ if (p == NULL) {
+ logger(ctx, LOG_ERR, "%s: no assignment: %s", __func__, value);
+ free(match);
+ return NULL;
+ }
+ *p++ = '\0';
+ l = strlen(match);
+
+ while (lst && lst[i]) {
+ if (match && strncmp(lst[i], match, l) == 0) {
+ if (uniq) {
+ n = strdup(value);
+ if (n == NULL) {
+ logger(ctx, LOG_ERR,
+ "%s: %m", __func__);
+ free(match);
+ return NULL;
+ }
+ free(lst[i]);
+ lst[i] = n;
+ } else {
+ /* Append a space and the value to it */
+ l = strlen(lst[i]);
+ lv = strlen(p);
+ n = realloc(lst[i], l + lv + 2);
+ if (n == NULL) {
+ logger(ctx, LOG_ERR,
+ "%s: %m", __func__);
+ free(match);
+ return NULL;
+ }
+ lst[i] = n;
+ lst[i][l] = ' ';
+ memcpy(lst[i] + l + 1, p, lv);
+ lst[i][l + lv + 1] = '\0';
+ }
+ free(match);
+ return lst[i];
+ }
+ i++;
+ }
+
+ free(match);
+ n = strdup(value);
+ if (n == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ newlist = realloc(lst, sizeof(char *) * (i + 2));
+ if (newlist == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(n);
+ return NULL;
+ }
+ newlist[i] = n;
+ newlist[i + 1] = NULL;
+ ifo->environ = newlist;
+ return newlist[i];
+}
+
+#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0)
+static ssize_t
+parse_string_hwaddr(char *sbuf, size_t slen, const char *str, int clid)
+{
+ size_t l;
+ const char *p;
+ int i, punt_last = 0;
+ char c[4];
+
+ /* If surrounded by quotes then it's a string */
+ if (*str == '"') {
+ str++;
+ l = strlen(str);
+ p = str + l - 1;
+ if (*p == '"')
+ punt_last = 1;
+ } else {
+ l = (size_t)hwaddr_aton(NULL, str);
+ if ((ssize_t) l != -1 && l > 1) {
+ if (l > slen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ hwaddr_aton((uint8_t *)sbuf, str);
+ return (ssize_t)l;
+ }
+ }
+
+ /* Process escapes */
+ l = 0;
+ /* If processing a string on the clientid, first byte should be
+ * 0 to indicate a non hardware type */
+ if (clid && *str) {
+ if (sbuf)
+ *sbuf++ = 0;
+ l++;
+ }
+ c[3] = '\0';
+ while (*str) {
+ if (++l > slen && sbuf) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ if (*str == '\\') {
+ str++;
+ switch(*str) {
+ case '\0':
+ break;
+ case 'b':
+ if (sbuf)
+ *sbuf++ = '\b';
+ str++;
+ break;
+ case 'n':
+ if (sbuf)
+ *sbuf++ = '\n';
+ str++;
+ break;
+ case 'r':
+ if (sbuf)
+ *sbuf++ = '\r';
+ str++;
+ break;
+ case 't':
+ if (sbuf)
+ *sbuf++ = '\t';
+ str++;
+ break;
+ case 'x':
+ /* Grab a hex code */
+ c[1] = '\0';
+ for (i = 0; i < 2; i++) {
+ if (isxdigit((unsigned char)*str) == 0)
+ break;
+ c[i] = *str++;
+ }
+ if (c[1] != '\0' && sbuf) {
+ c[2] = '\0';
+ *sbuf++ = (char)strtol(c, NULL, 16);
+ } else
+ l--;
+ break;
+ case '0':
+ /* Grab an octal code */
+ c[2] = '\0';
+ for (i = 0; i < 3; i++) {
+ if (*str < '0' || *str > '7')
+ break;
+ c[i] = *str++;
+ }
+ if (c[2] != '\0' && sbuf) {
+ i = (int)strtol(c, NULL, 8);
+ if (i > 255)
+ i = 255;
+ *sbuf ++= (char)i;
+ } else
+ l--;
+ break;
+ default:
+ if (sbuf)
+ *sbuf++ = *str;
+ str++;
+ break;
+ }
+ } else {
+ if (sbuf)
+ *sbuf++ = *str;
+ str++;
+ }
+ }
+ if (punt_last) {
+ if (sbuf)
+ *--sbuf = '\0';
+ l--;
+ }
+ return (ssize_t)l;
+}
+
+static int
+parse_iaid1(uint8_t *iaid, const char *arg, size_t len, int n)
+{
+ int e;
+ uint32_t narg;
+ ssize_t s;
+
+ narg = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e == 0) {
+ if (n)
+ narg = htonl(narg);
+ memcpy(iaid, &narg, sizeof(narg));
+ return 0;
+ }
+
+ if ((s = parse_string((char *)iaid, len, arg)) < 1)
+ return -1;
+ if (s < 4)
+ iaid[3] = '\0';
+ if (s < 3)
+ iaid[2] = '\0';
+ if (s < 2)
+ iaid[1] = '\0';
+ return 0;
+}
+
+static int
+parse_iaid(uint8_t *iaid, const char *arg, size_t len)
+{
+
+ return parse_iaid1(iaid, arg, len, 1);
+}
+
+static int
+parse_uint32(uint32_t *i, const char *arg)
+{
+
+ return parse_iaid1((uint8_t *)i, arg, sizeof(uint32_t), 0);
+}
+
+static char **
+splitv(struct dhcpcd_ctx *ctx, int *argc, char **argv, const char *arg)
+{
+ char **n, **v = argv;
+ char *o = strdup(arg), *p, *t, *nt;
+
+ if (o == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return v;
+ }
+ p = o;
+ while ((t = strsep(&p, ", "))) {
+ nt = strdup(t);
+ if (nt == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(o);
+ return v;
+ }
+ n = realloc(v, sizeof(char *) * ((size_t)(*argc) + 1));
+ if (n == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(o);
+ free(nt);
+ return v;
+ }
+ v = n;
+ v[(*argc)++] = nt;
+ }
+ free(o);
+ return v;
+}
+
+#ifdef INET
+static int
+parse_addr(struct dhcpcd_ctx *ctx,
+ struct in_addr *addr, struct in_addr *net, const char *arg)
+{
+ char *p;
+
+ if (arg == NULL || *arg == '\0') {
+ if (addr != NULL)
+ addr->s_addr = 0;
+ if (net != NULL)
+ net->s_addr = 0;
+ return 0;
+ }
+ if ((p = strchr(arg, '/')) != NULL) {
+ int e;
+ intmax_t i;
+
+ *p++ = '\0';
+ i = strtoi(p, NULL, 10, 0, 32, &e);
+ if (e != 0 ||
+ (net != NULL && inet_cidrtoaddr((int)i, net) != 0))
+ {
+ logger(ctx, LOG_ERR, "`%s' is not a valid CIDR", p);
+ return -1;
+ }
+ }
+
+ if (addr != NULL && inet_aton(arg, addr) == 0) {
+ logger(ctx, LOG_ERR, "`%s' is not a valid IP address", arg);
+ return -1;
+ }
+ if (p != NULL)
+ *--p = '/';
+ else if (net != NULL && addr != NULL)
+ net->s_addr = ipv4_getnetmask(addr->s_addr);
+ return 0;
+}
+#else
+static int
+parse_addr(struct dhcpcd_ctx *ctx,
+ __unused struct in_addr *addr, __unused struct in_addr *net,
+ __unused const char *arg)
+{
+
+ logger(ctx, LOG_ERR, "No IPv4 support");
+ return -1;
+}
+#endif
+
+static const char *
+set_option_space(struct dhcpcd_ctx *ctx,
+ const char *arg,
+ const struct dhcp_opt **d, size_t *dl,
+ const struct dhcp_opt **od, size_t *odl,
+ struct if_options *ifo,
+ uint8_t *request[], uint8_t *require[], uint8_t *no[], uint8_t *reject[])
+{
+
+#if !defined(INET) && !defined(INET6)
+ UNUSED(ctx);
+#endif
+
+#ifdef INET6
+ if (strncmp(arg, "nd_", strlen("nd_")) == 0) {
+ *d = ctx->nd_opts;
+ *dl = ctx->nd_opts_len;
+ *od = ifo->nd_override;
+ *odl = ifo->nd_override_len;
+ *request = ifo->requestmasknd;
+ *require = ifo->requiremasknd;
+ *no = ifo->nomasknd;
+ *reject = ifo->rejectmasknd;
+ return arg + strlen("nd_");
+ }
+
+ if (strncmp(arg, "dhcp6_", strlen("dhcp6_")) == 0) {
+ *d = ctx->dhcp6_opts;
+ *dl = ctx->dhcp6_opts_len;
+ *od = ifo->dhcp6_override;
+ *odl = ifo->dhcp6_override_len;
+ *request = ifo->requestmask6;
+ *require = ifo->requiremask6;
+ *no = ifo->nomask6;
+ *reject = ifo->rejectmask6;
+ return arg + strlen("dhcp6_");
+ }
+#endif
+
+#ifdef INET
+ *d = ctx->dhcp_opts;
+ *dl = ctx->dhcp_opts_len;
+ *od = ifo->dhcp_override;
+ *odl = ifo->dhcp_override_len;
+#else
+ *d = NULL;
+ *dl = 0;
+ *od = NULL;
+ *odl = 0;
+#endif
+ *request = ifo->requestmask;
+ *require = ifo->requiremask;
+ *no = ifo->nomask;
+ *reject = ifo->rejectmask;
+ return arg;
+}
+
+void
+free_dhcp_opt_embenc(struct dhcp_opt *opt)
+{
+ size_t i;
+ struct dhcp_opt *o;
+
+ free(opt->var);
+
+ for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++)
+ free_dhcp_opt_embenc(o);
+ free(opt->embopts);
+ opt->embopts_len = 0;
+ opt->embopts = NULL;
+
+ for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++)
+ free_dhcp_opt_embenc(o);
+ free(opt->encopts);
+ opt->encopts_len = 0;
+ opt->encopts = NULL;
+}
+
+static char *
+strwhite(const char *s)
+{
+
+ if (s == NULL)
+ return NULL;
+ while (*s != ' ' && *s != '\t') {
+ if (*s == '\0')
+ return NULL;
+ s++;
+ }
+ return UNCONST(s);
+}
+
+static char *
+strskipwhite(const char *s)
+{
+
+ if (s == NULL)
+ return NULL;
+ while (*s == ' ' || *s == '\t') {
+ if (*s == '\0')
+ return NULL;
+ s++;
+ }
+ return UNCONST(s);
+}
+
+/* Find the end pointer of a string. */
+static char *
+strend(const char *s)
+{
+
+ s = strskipwhite(s);
+ if (s == NULL)
+ return NULL;
+ if (*s != '"')
+ return strchr(s, ' ');
+ s++;
+ for (; *s != '"' ; s++) {
+ if (*s == '\0')
+ return NULL;
+ if (*s == '\\') {
+ if (*(++s) == '\0')
+ return NULL;
+ }
+ }
+ return UNCONST(++s);
+}
+
+static int
+parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo,
+ int opt, const char *arg, struct dhcp_opt **ldop, struct dhcp_opt **edop)
+{
+ int e, i, t;
+ long l;
+ unsigned long u;
+ char *p = NULL, *bp, *fp, *np, **nconf;
+ ssize_t s;
+ struct in_addr addr, addr2;
+ in_addr_t *naddr;
+ struct rt *rt;
+ const struct dhcp_opt *d, *od;
+ uint8_t *request, *require, *no, *reject;
+ struct dhcp_opt **dop, *ndop;
+ size_t *dop_len, dl, odl;
+ struct vivco *vivco;
+ struct token *token;
+ struct group *grp;
+#ifdef _REENTRANT
+ struct group grpbuf;
+#endif
+#ifdef INET6
+ size_t sl;
+ struct if_ia *ia;
+ uint8_t iaid[4];
+ struct if_sla *sla, *slap;
+#endif
+
+ dop = NULL;
+ dop_len = NULL;
+#ifdef INET6
+ i = 0;
+#endif
+ switch(opt) {
+ case 'f': /* FALLTHROUGH */
+ case 'g': /* FALLTHROUGH */
+ case 'n': /* FALLTHROUGH */
+ case 'x': /* FALLTHROUGH */
+ case 'T': /* FALLTHROUGH */
+ case 'U': /* FALLTHROUGH */
+ case 'V': /* We need to handle non interface options */
+ break;
+ case 'b':
+ ifo->options |= DHCPCD_BACKGROUND;
+ break;
+ case 'c':
+ free(ifo->script);
+ ifo->script = strdup(arg);
+ if (ifo->script == NULL)
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ break;
+ case 'd':
+ ifo->options |= DHCPCD_DEBUG;
+ break;
+ case 'e':
+ add_environ(ctx, ifo, arg, 1);
+ break;
+ case 'h':
+ if (!arg) {
+ ifo->options |= DHCPCD_HOSTNAME;
+ break;
+ }
+ s = parse_string(ifo->hostname, HOSTNAME_MAX_LEN, arg);
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "hostname: %m");
+ return -1;
+ }
+ if (s != 0 && ifo->hostname[0] == '.') {
+ logger(ctx, LOG_ERR, "hostname cannot begin with .");
+ return -1;
+ }
+ ifo->hostname[s] = '\0';
+ if (ifo->hostname[0] == '\0')
+ ifo->options &= ~DHCPCD_HOSTNAME;
+ else
+ ifo->options |= DHCPCD_HOSTNAME;
+ break;
+ case 'i':
+ if (arg)
+ s = parse_string((char *)ifo->vendorclassid + 1,
+ VENDORCLASSID_MAX_LEN, arg);
+ else
+ s = 0;
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "vendorclassid: %m");
+ return -1;
+ }
+ *ifo->vendorclassid = (uint8_t)s;
+ break;
+ case 'j':
+ /* per interface logging is not supported
+ * don't want to overide the commandline */
+ if (ifname == NULL && ctx->logfile == NULL) {
+ logger_close(ctx);
+ ctx->logfile = strdup(arg);
+ logger_open(ctx);
+ }
+ break;
+ case 'k':
+ ifo->options |= DHCPCD_RELEASE;
+ break;
+ case 'l':
+ ifo->leasetime = (uint32_t)strtou(arg, NULL,
+ 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "failed to convert leasetime %s", arg);
+ return -1;
+ }
+ break;
+ case 'm':
+ ifo->metric = (int)strtoi(arg, NULL, 0, 0, INT32_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "failed to convert metric %s", arg);
+ return -1;
+ }
+ break;
+ case 'o':
+ arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo,
+ &request, &require, &no, &reject);
+ if (make_option_mask(d, dl, od, odl, request, arg, 1) != 0 ||
+ make_option_mask(d, dl, od, odl, no, arg, -1) != 0 ||
+ make_option_mask(d, dl, od, odl, reject, arg, -1) != 0)
+ {
+ logger(ctx, LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case O_REJECT:
+ arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo,
+ &request, &require, &no, &reject);
+ if (make_option_mask(d, dl, od, odl, reject, arg, 1) != 0 ||
+ make_option_mask(d, dl, od, odl, request, arg, -1) != 0 ||
+ make_option_mask(d, dl, od, odl, require, arg, -1) != 0)
+ {
+ logger(ctx, LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case 'p':
+ ifo->options |= DHCPCD_PERSISTENT;
+ break;
+ case 'q':
+ ifo->options |= DHCPCD_QUIET;
+ break;
+ case 'r':
+ if (parse_addr(ctx, &ifo->req_addr, NULL, arg) != 0)
+ return -1;
+ ifo->options |= DHCPCD_REQUEST;
+ ifo->req_mask.s_addr = 0;
+ break;
+ case 's':
+ if (ifo->options & DHCPCD_IPV6 &&
+ !(ifo->options & DHCPCD_IPV4))
+ {
+ ifo->options |= DHCPCD_INFORM;
+ break;
+ }
+ if (arg && *arg != '\0') {
+ if (parse_addr(ctx,
+ &ifo->req_addr, &ifo->req_mask, arg) != 0)
+ return -1;
+ } else {
+ ifo->req_addr.s_addr = 0;
+ ifo->req_mask.s_addr = 0;
+ }
+ ifo->options |= DHCPCD_INFORM | DHCPCD_PERSISTENT;
+ ifo->options &= ~DHCPCD_STATIC;
+ break;
+ case 't':
+ ifo->timeout = (time_t)strtoi(arg, NULL, 0, 0, INT32_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "failed to convert timeout");
+ return -1;
+ }
+ break;
+ case 'u':
+ s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1;
+ s = parse_string((char *)ifo->userclass +
+ ifo->userclass[0] + 2, (size_t)s, arg);
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "userclass: %m");
+ return -1;
+ }
+ if (s != 0) {
+ ifo->userclass[ifo->userclass[0] + 1] = (uint8_t)s;
+ ifo->userclass[0] = (uint8_t)(ifo->userclass[0] + s +1);
+ }
+ break;
+ case 'v':
+ p = strchr(arg, ',');
+ if (!p || !p[1]) {
+ logger(ctx, LOG_ERR, "invalid vendor format: %s", arg);
+ return -1;
+ }
+
+ /* If vendor starts with , then it is not encapsulated */
+ if (p == arg) {
+ arg++;
+ s = parse_string((char *)ifo->vendor + 1,
+ VENDOR_MAX_LEN, arg);
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "vendor: %m");
+ return -1;
+ }
+ ifo->vendor[0] = (uint8_t)s;
+ ifo->options |= DHCPCD_VENDORRAW;
+ break;
+ }
+
+ /* Encapsulated vendor options */
+ if (ifo->options & DHCPCD_VENDORRAW) {
+ ifo->options &= ~DHCPCD_VENDORRAW;
+ ifo->vendor[0] = 0;
+ }
+
+ /* Strip and preserve the comma */
+ *p = '\0';
+ i = (int)strtoi(arg, NULL, 0, 1, 254, &e);
+ *p = ',';
+ if (e) {
+ logger(ctx, LOG_ERR, "vendor option should be between"
+ " 1 and 254 inclusive");
+ return -1;
+ }
+
+ arg = p + 1;
+ s = VENDOR_MAX_LEN - ifo->vendor[0] - 2;
+ if (inet_aton(arg, &addr) == 1) {
+ if (s < 6) {
+ s = -1;
+ errno = ENOBUFS;
+ } else {
+ memcpy(ifo->vendor + ifo->vendor[0] + 3,
+ &addr.s_addr, sizeof(addr.s_addr));
+ s = sizeof(addr.s_addr);
+ }
+ } else {
+ s = parse_string((char *)ifo->vendor +
+ ifo->vendor[0] + 3, (size_t)s, arg);
+ }
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "vendor: %m");
+ return -1;
+ }
+ if (s != 0) {
+ ifo->vendor[ifo->vendor[0] + 1] = (uint8_t)i;
+ ifo->vendor[ifo->vendor[0] + 2] = (uint8_t)s;
+ ifo->vendor[0] = (uint8_t)(ifo->vendor[0] + s + 2);
+ }
+ break;
+ case 'w':
+ ifo->options |= DHCPCD_WAITIP;
+ if (arg != NULL && arg[0] != '\0') {
+ if (arg[0] == '4' || arg[1] == '4')
+ ifo->options |= DHCPCD_WAITIP4;
+ if (arg[0] == '6' || arg[1] == '6')
+ ifo->options |= DHCPCD_WAITIP6;
+ }
+ break;
+ case 'y':
+ ifo->reboot = (time_t)strtoi(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "failed to convert reboot %s", arg);
+ return -1;
+ }
+ break;
+ case 'z':
+ if (ifname == NULL)
+ ctx->ifav = splitv(ctx, &ctx->ifac, ctx->ifav, arg);
+ break;
+ case 'A':
+ ifo->options &= ~DHCPCD_ARP;
+ /* IPv4LL requires ARP */
+ ifo->options &= ~DHCPCD_IPV4LL;
+ break;
+ case 'B':
+ ifo->options &= ~DHCPCD_DAEMONISE;
+ break;
+ case 'C':
+ /* Commas to spaces for shell */
+ while ((p = strchr(arg, ',')))
+ *p = ' ';
+ dl = strlen("skip_hooks=") + strlen(arg) + 1;
+ p = malloc(sizeof(char) * dl);
+ if (p == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ snprintf(p, dl, "skip_hooks=%s", arg);
+ add_environ(ctx, ifo, p, 0);
+ free(p);
+ break;
+ case 'D':
+ ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID;
+ break;
+ case 'E':
+ ifo->options |= DHCPCD_LASTLEASE;
+ break;
+ case 'F':
+ if (!arg) {
+ ifo->fqdn = FQDN_BOTH;
+ break;
+ }
+ if (strcmp(arg, "none") == 0)
+ ifo->fqdn = FQDN_NONE;
+ else if (strcmp(arg, "ptr") == 0)
+ ifo->fqdn = FQDN_PTR;
+ else if (strcmp(arg, "both") == 0)
+ ifo->fqdn = FQDN_BOTH;
+ else if (strcmp(arg, "disable") == 0)
+ ifo->fqdn = FQDN_DISABLE;
+ else {
+ logger(ctx, LOG_ERR, "invalid value `%s' for FQDN", arg);
+ return -1;
+ }
+ break;
+ case 'G':
+ ifo->options &= ~DHCPCD_GATEWAY;
+ break;
+ case 'H':
+ ifo->options |= DHCPCD_XID_HWADDR;
+ break;
+ case 'I':
+ /* Strings have a type of 0 */;
+ ifo->clientid[1] = 0;
+ if (arg)
+ s = parse_string_hwaddr((char *)ifo->clientid + 1,
+ CLIENTID_MAX_LEN, arg, 1);
+ else
+ s = 0;
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "clientid: %m");
+ return -1;
+ }
+ ifo->options |= DHCPCD_CLIENTID;
+ ifo->clientid[0] = (uint8_t)s;
+ break;
+ case 'J':
+ ifo->options |= DHCPCD_BROADCAST;
+ break;
+ case 'K':
+ ifo->options &= ~DHCPCD_LINK;
+ break;
+ case 'L':
+ ifo->options &= ~DHCPCD_IPV4LL;
+ break;
+ case 'M':
+ ifo->options |= DHCPCD_MASTER;
+ break;
+ case 'O':
+ arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo,
+ &request, &require, &no, &reject);
+ if (make_option_mask(d, dl, od, odl, request, arg, -1) != 0 ||
+ make_option_mask(d, dl, od, odl, require, arg, -1) != 0 ||
+ make_option_mask(d, dl, od, odl, no, arg, 1) != 0)
+ {
+ logger(ctx, LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case 'Q':
+ arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo,
+ &request, &require, &no, &reject);
+ if (make_option_mask(d, dl, od, odl, require, arg, 1) != 0 ||
+ make_option_mask(d, dl, od, odl, request, arg, 1) != 0 ||
+ make_option_mask(d, dl, od, odl, no, arg, -1) != 0 ||
+ make_option_mask(d, dl, od, odl, reject, arg, -1) != 0)
+ {
+ logger(ctx, LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case 'S':
+ p = strchr(arg, '=');
+ if (p == NULL) {
+ logger(ctx, LOG_ERR, "static assignment required");
+ return -1;
+ }
+ p++;
+ if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) {
+ if (parse_addr(ctx, &ifo->req_addr,
+ ifo->req_mask.s_addr == 0 ? &ifo->req_mask : NULL,
+ p) != 0)
+ return -1;
+
+ ifo->options |= DHCPCD_STATIC;
+ ifo->options &= ~DHCPCD_INFORM;
+ } else if (strncmp(arg, "subnet_mask=",
+ strlen("subnet_mask=")) == 0)
+ {
+ if (parse_addr(ctx, &ifo->req_mask, NULL, p) != 0)
+ return -1;
+ } else if (strncmp(arg, "routes=", strlen("routes=")) == 0 ||
+ strncmp(arg, "static_routes=",
+ strlen("static_routes=")) == 0 ||
+ strncmp(arg, "classless_static_routes=",
+ strlen("classless_static_routes=")) == 0 ||
+ strncmp(arg, "ms_classless_static_routes=",
+ strlen("ms_classless_static_routes=")) == 0)
+ {
+ fp = np = strwhite(p);
+ if (np == NULL) {
+ logger(ctx, LOG_ERR,
+ "all routes need a gateway");
+ return -1;
+ }
+ *np++ = '\0';
+ np = strskipwhite(np);
+ if (ifo->routes == NULL) {
+ ifo->routes = malloc(sizeof(*ifo->routes));
+ if (ifo->routes == NULL) {
+ logger(ctx, LOG_ERR,
+ "%s: %m", __func__);
+ return -1;
+ }
+ TAILQ_INIT(ifo->routes);
+ }
+ rt = calloc(1, sizeof(*rt));
+ if (rt == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ *fp = ' ';
+ return -1;
+ }
+ if (parse_addr(ctx, &rt->dest, &rt->net, p) == -1 ||
+ parse_addr(ctx, &rt->gate, NULL, np) == -1)
+ {
+ free(rt);
+ *fp = ' ';
+ return -1;
+ }
+ TAILQ_INSERT_TAIL(ifo->routes, rt, next);
+ *fp = ' ';
+ } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) {
+ if (ifo->routes == NULL) {
+ ifo->routes = malloc(sizeof(*ifo->routes));
+ if (ifo->routes == NULL) {
+ logger(ctx, LOG_ERR,
+ "%s: %m", __func__);
+ return -1;
+ }
+ TAILQ_INIT(ifo->routes);
+ }
+ rt = calloc(1, sizeof(*rt));
+ if (rt == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ rt->dest.s_addr = INADDR_ANY;
+ rt->net.s_addr = INADDR_ANY;
+ if (parse_addr(ctx, &rt->gate, NULL, p) == -1) {
+ free(rt);
+ return -1;
+ }
+ TAILQ_INSERT_TAIL(ifo->routes, rt, next);
+ } else if (strncmp(arg, "interface_mtu=",
+ strlen("interface_mtu=")) == 0 ||
+ strncmp(arg, "mtu=", strlen("mtu=")) == 0)
+ {
+ ifo->mtu = (unsigned int)strtou(p, NULL, 0,
+ MTU_MIN, MTU_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "invalid MTU %s", p);
+ return -1;
+ }
+ } else {
+ dl = 0;
+ if (ifo->config != NULL) {
+ while (ifo->config[dl] != NULL) {
+ if (strncmp(ifo->config[dl], arg,
+ (size_t)(p - arg)) == 0)
+ {
+ p = strdup(arg);
+ if (p == NULL) {
+ logger(ctx, LOG_ERR,
+ "%s: %m", __func__);
+ return -1;
+ }
+ free(ifo->config[dl]);
+ ifo->config[dl] = p;
+ return 1;
+ }
+ dl++;
+ }
+ }
+ p = strdup(arg);
+ if (p == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ nconf = realloc(ifo->config, sizeof(char *) * (dl + 2));
+ if (nconf == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ifo->config = nconf;
+ ifo->config[dl] = p;
+ ifo->config[dl + 1] = NULL;
+ }
+ break;
+ case 'W':
+ if (parse_addr(ctx, &addr, &addr2, arg) != 0)
+ return -1;
+ if (strchr(arg, '/') == NULL)
+ addr2.s_addr = INADDR_BROADCAST;
+ naddr = realloc(ifo->whitelist,
+ sizeof(in_addr_t) * (ifo->whitelist_len + 2));
+ if (naddr == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ifo->whitelist = naddr;
+ ifo->whitelist[ifo->whitelist_len++] = addr.s_addr;
+ ifo->whitelist[ifo->whitelist_len++] = addr2.s_addr;
+ break;
+ case 'X':
+ if (parse_addr(ctx, &addr, &addr2, arg) != 0)
+ return -1;
+ if (strchr(arg, '/') == NULL)
+ addr2.s_addr = INADDR_BROADCAST;
+ naddr = realloc(ifo->blacklist,
+ sizeof(in_addr_t) * (ifo->blacklist_len + 2));
+ if (naddr == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ifo->blacklist = naddr;
+ ifo->blacklist[ifo->blacklist_len++] = addr.s_addr;
+ ifo->blacklist[ifo->blacklist_len++] = addr2.s_addr;
+ break;
+ case 'Z':
+ if (ifname == NULL)
+ ctx->ifdv = splitv(ctx, &ctx->ifdc, ctx->ifdv, arg);
+ break;
+ case '4':
+ ifo->options &= ~DHCPCD_IPV6;
+ ifo->options |= DHCPCD_IPV4;
+ break;
+ case '6':
+ ifo->options &= ~DHCPCD_IPV4;
+ ifo->options |= DHCPCD_IPV6;
+ break;
+ case O_IPV4:
+ ifo->options |= DHCPCD_IPV4;
+ break;
+ case O_NOIPV4:
+ ifo->options &= ~DHCPCD_IPV4;
+ break;
+ case O_IPV6:
+ ifo->options |= DHCPCD_IPV6;
+ break;
+ case O_NOIPV6:
+ ifo->options &= ~DHCPCD_IPV6;
+ break;
+#ifdef INET
+ case O_ARPING:
+ while (arg && *arg != '\0') {
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ if (parse_addr(ctx, &addr, NULL, arg) != 0)
+ return -1;
+ naddr = realloc(ifo->arping,
+ sizeof(in_addr_t) * (ifo->arping_len + 1));
+ if (naddr == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ifo->arping = naddr;
+ ifo->arping[ifo->arping_len++] = addr.s_addr;
+ arg = strskipwhite(fp);
+ }
+ break;
+ case O_DESTINATION:
+ arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo,
+ &request, &require, &no, &reject);
+ if (make_option_mask(d, dl, od, odl,
+ ifo->dstmask, arg, 2) != 0)
+ {
+ if (errno == EINVAL)
+ logger(ctx, LOG_ERR, "option `%s' does not take"
+ " an IPv4 address", arg);
+ else
+ logger(ctx, LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case O_FALLBACK:
+ free(ifo->fallback);
+ ifo->fallback = strdup(arg);
+ if (ifo->fallback == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ break;
+#endif
+ case O_IAID:
+ if (ifname == NULL) {
+ logger(ctx, LOG_ERR,
+ "IAID must belong in an interface block");
+ return -1;
+ }
+ if (parse_iaid(ifo->iaid, arg, sizeof(ifo->iaid)) == -1) {
+ logger(ctx, LOG_ERR, "invalid IAID %s", arg);
+ return -1;
+ }
+ ifo->options |= DHCPCD_IAID;
+ break;
+ case O_IPV6RS:
+ ifo->options |= DHCPCD_IPV6RS;
+ break;
+ case O_NOIPV6RS:
+ ifo->options &= ~DHCPCD_IPV6RS;
+ break;
+ case O_IPV6RA_FORK:
+ ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS;
+ break;
+ case O_IPV6RA_OWN:
+ ifo->options |= DHCPCD_IPV6RA_OWN;
+ break;
+ case O_IPV6RA_OWN_D:
+ ifo->options |= DHCPCD_IPV6RA_OWN_DEFAULT;
+ break;
+ case O_IPV6RA_ACCEPT_NOPUBLIC:
+ ifo->options |= DHCPCD_IPV6RA_ACCEPT_NOPUBLIC;
+ break;
+ case O_IPV6RA_AUTOCONF:
+ ifo->options |= DHCPCD_IPV6RA_AUTOCONF;
+ break;
+ case O_IPV6RA_NOAUTOCONF:
+ ifo->options &= ~DHCPCD_IPV6RA_AUTOCONF;
+ break;
+ case O_NOALIAS:
+ ifo->options |= DHCPCD_NOALIAS;
+ break;
+#ifdef INET6
+ case O_IA_NA:
+ i = D6_OPTION_IA_NA;
+ /* FALLTHROUGH */
+ case O_IA_TA:
+ if (i == 0)
+ i = D6_OPTION_IA_TA;
+ /* FALLTHROUGH */
+ case O_IA_PD:
+ if (i == 0) {
+ if (ifname == NULL) {
+ logger(ctx, LOG_ERR,
+ "IA PD must belong in an interface block");
+ return -1;
+ }
+ i = D6_OPTION_IA_PD;
+ }
+ if (ifname == NULL && arg) {
+ logger(ctx, LOG_ERR,
+ "IA with IAID must belong in an interface block");
+ return -1;
+ }
+ ifo->options |= DHCPCD_IA_FORCED;
+ fp = strwhite(arg);
+ if (fp) {
+ *fp++ = '\0';
+ fp = strskipwhite(fp);
+ }
+ if (arg) {
+ p = strchr(arg, '/');
+ if (p)
+ *p++ = '\0';
+ if (parse_iaid(iaid, arg, sizeof(iaid)) == -1) {
+ logger(ctx, LOG_ERR, "invalid IAID: %s", arg);
+ return -1;
+ }
+ }
+ ia = NULL;
+ for (sl = 0; sl < ifo->ia_len; sl++) {
+ if ((arg == NULL && !ifo->ia[sl].iaid_set) ||
+ (ifo->ia[sl].iaid_set &&
+ ifo->ia[sl].iaid[0] == iaid[0] &&
+ ifo->ia[sl].iaid[1] == iaid[1] &&
+ ifo->ia[sl].iaid[2] == iaid[2] &&
+ ifo->ia[sl].iaid[3] == iaid[3]))
+ {
+ ia = &ifo->ia[sl];
+ break;
+ }
+ }
+ if (ia && ia->ia_type != (uint16_t)i) {
+ logger(ctx, LOG_ERR, "Cannot mix IA for the same IAID");
+ break;
+ }
+ if (ia == NULL) {
+ ia = realloc(ifo->ia,
+ sizeof(*ifo->ia) * (ifo->ia_len + 1));
+ if (ia == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ifo->ia = ia;
+ ia = &ifo->ia[ifo->ia_len++];
+ ia->ia_type = (uint16_t)i;
+ if (arg) {
+ ia->iaid[0] = iaid[0];
+ ia->iaid[1] = iaid[1];
+ ia->iaid[2] = iaid[2];
+ ia->iaid[3] = iaid[3];
+ ia->iaid_set = 1;
+ } else
+ ia->iaid_set = 0;
+ if (!ia->iaid_set ||
+ p == NULL ||
+ ia->ia_type == D6_OPTION_IA_TA)
+ {
+ memset(&ia->addr, 0, sizeof(ia->addr));
+ ia->prefix_len = 0;
+ } else {
+ arg = p;
+ p = strchr(arg, '/');
+ if (p)
+ *p++ = '\0';
+ if (inet_pton(AF_INET6, arg, &ia->addr) == -1) {
+ logger(ctx, LOG_ERR, "%s: %m", arg);
+ memset(&ia->addr, 0, sizeof(ia->addr));
+ }
+ if (p && ia->ia_type == D6_OPTION_IA_PD) {
+ i = (int)strtoi(p, NULL, 0, 8, 120, &e);
+ if (e) {
+ logger(ctx, LOG_ERR,
+ "%s: failed to convert"
+ " prefix len",
+ p);
+ ia->prefix_len = 0;
+ } else
+ ia->prefix_len = (uint8_t)i;
+ }
+ }
+ ia->sla_max = 0;
+ ia->sla_len = 0;
+ ia->sla = NULL;
+ }
+ if (ia->ia_type != D6_OPTION_IA_PD)
+ break;
+ for (p = fp; p; p = fp) {
+ fp = strwhite(p);
+ if (fp) {
+ *fp++ = '\0';
+ fp = strskipwhite(fp);
+ }
+ sla = realloc(ia->sla,
+ sizeof(*ia->sla) * (ia->sla_len + 1));
+ if (sla == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ia->sla = sla;
+ sla = &ia->sla[ia->sla_len++];
+ np = strchr(p, '/');
+ if (np)
+ *np++ = '\0';
+ if (strlcpy(sla->ifname, p,
+ sizeof(sla->ifname)) >= sizeof(sla->ifname))
+ {
+ logger(ctx, LOG_ERR, "%s: interface name too long",
+ arg);
+ goto err_sla;
+ }
+ p = np;
+ if (p) {
+ np = strchr(p, '/');
+ if (np)
+ *np++ = '\0';
+ if (*p == '\0')
+ sla->sla_set = 0;
+ else {
+ sla->sla = (uint32_t)strtou(p, NULL,
+ 0, 0, UINT32_MAX, &e);
+ sla->sla_set = 1;
+ if (e) {
+ logger(ctx, LOG_ERR,
+ "%s: failed to convert sla",
+ ifname);
+ goto err_sla;
+ }
+ }
+ if (np) {
+ sla->prefix_len = (uint8_t)strtoi(np,
+ NULL, 0, 0, 128, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "%s: failed to "
+ "convert prefix len",
+ ifname);
+ goto err_sla;
+ }
+ } else
+ sla->prefix_len = 0;
+ } else {
+ sla->sla_set = 0;
+ sla->prefix_len = 0;
+ }
+ /* Sanity check */
+ for (sl = 0; sl < ia->sla_len - 1; sl++) {
+ slap = &ia->sla[sl];
+ if (slap->sla_set != sla->sla_set) {
+ logger(ctx, LOG_WARNING,
+ "%s: cannot mix automatic "
+ "and fixed SLA",
+ sla->ifname);
+ goto err_sla;
+ }
+ if (sla->sla_set == 0 &&
+ strcmp(slap->ifname, sla->ifname) == 0)
+ {
+ logger(ctx, LOG_WARNING,
+ "%s: cannot specify the "
+ "same interface twice with "
+ "an automatic SLA",
+ sla->ifname);
+ goto err_sla;
+ }
+ if (slap->sla == 0 || sla->sla == 0) {
+ logger(ctx, LOG_ERR, "%s: cannot"
+ " assign multiple prefixes"
+ " with a SLA of 0",
+ ifname);
+ goto err_sla;
+ }
+ }
+ if (sla->sla_set && sla->sla > ia->sla_max)
+ ia->sla_max = sla->sla;
+ }
+ break;
+err_sla:
+ ia->sla_len--;
+ return -1;
+#endif
+ case O_HOSTNAME_SHORT:
+ ifo->options |= DHCPCD_HOSTNAME | DHCPCD_HOSTNAME_SHORT;
+ break;
+ case O_DEV:
+#ifdef PLUGIN_DEV
+ if (ctx->dev_load)
+ free(ctx->dev_load);
+ ctx->dev_load = strdup(arg);
+#endif
+ break;
+ case O_NODEV:
+ ifo->options &= ~DHCPCD_DEV;
+ break;
+ case O_DEFINE:
+ dop = &ifo->dhcp_override;
+ dop_len = &ifo->dhcp_override_len;
+ /* FALLTHROUGH */
+ case O_DEFINEND:
+ if (dop == NULL) {
+ dop = &ifo->nd_override;
+ dop_len = &ifo->nd_override_len;
+ }
+ /* FALLTHROUGH */
+ case O_DEFINE6:
+ if (dop == NULL) {
+ dop = &ifo->dhcp6_override;
+ dop_len = &ifo->dhcp6_override_len;
+ }
+ /* FALLTHROUGH */
+ case O_VENDOPT:
+ if (dop == NULL) {
+ dop = &ifo->vivso_override;
+ dop_len = &ifo->vivso_override_len;
+ }
+ *edop = *ldop = NULL;
+ /* FALLTHROUGH */
+ case O_EMBED:
+ if (dop == NULL) {
+ if (*edop) {
+ dop = &(*edop)->embopts;
+ dop_len = &(*edop)->embopts_len;
+ } else if (ldop) {
+ dop = &(*ldop)->embopts;
+ dop_len = &(*ldop)->embopts_len;
+ } else {
+ logger(ctx, LOG_ERR,
+ "embed must be after a define or encap");
+ return -1;
+ }
+ }
+ /* FALLTHROUGH */
+ case O_ENCAP:
+ if (dop == NULL) {
+ if (*ldop == NULL) {
+ logger(ctx, LOG_ERR, "encap must be after a define");
+ return -1;
+ }
+ dop = &(*ldop)->encopts;
+ dop_len = &(*ldop)->encopts_len;
+ }
+
+ /* Shared code for define, define6, embed and encap */
+
+ /* code */
+ if (opt == O_EMBED) /* Embedded options don't have codes */
+ u = 0;
+ else {
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "invalid syntax: %s", arg);
+ return -1;
+ }
+ *fp++ = '\0';
+ u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "invalid code: %s", arg);
+ return -1;
+ }
+ arg = strskipwhite(fp);
+ if (arg == NULL) {
+ logger(ctx, LOG_ERR, "invalid syntax");
+ return -1;
+ }
+ }
+ /* type */
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ np = strchr(arg, ':');
+ /* length */
+ if (np) {
+ *np++ = '\0';
+ bp = NULL; /* No bitflag */
+ l = (long)strtou(np, NULL, 0, 0, LONG_MAX, &e);
+ if (e) {
+ logger(ctx,LOG_ERR, "failed to convert length");
+ return -1;
+ }
+ } else {
+ l = 0;
+ bp = strchr(arg, '='); /* bitflag assignment */
+ if (bp)
+ *bp++ = '\0';
+ }
+ t = 0;
+ if (strcasecmp(arg, "request") == 0) {
+ t |= REQUEST;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "incomplete request type");
+ return -1;
+ }
+ *fp++ = '\0';
+ } else if (strcasecmp(arg, "norequest") == 0) {
+ t |= NOREQ;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "incomplete request type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "index") == 0) {
+ t |= INDEX;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "incomplete index type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "array") == 0) {
+ t |= ARRAY;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "incomplete array type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "ipaddress") == 0)
+ t |= ADDRIPV4;
+ else if (strcasecmp(arg, "ip6address") == 0)
+ t |= ADDRIPV6;
+ else if (strcasecmp(arg, "string") == 0)
+ t |= STRING;
+ else if (strcasecmp(arg, "byte") == 0)
+ t |= UINT8;
+ else if (strcasecmp(arg, "bitflags") == 0)
+ t |= BITFLAG;
+ else if (strcasecmp(arg, "uint16") == 0)
+ t |= UINT16;
+ else if (strcasecmp(arg, "int16") == 0)
+ t |= SINT16;
+ else if (strcasecmp(arg, "uint32") == 0)
+ t |= UINT32;
+ else if (strcasecmp(arg, "int32") == 0)
+ t |= SINT32;
+ else if (strcasecmp(arg, "flag") == 0)
+ t |= FLAG;
+ else if (strcasecmp(arg, "raw") == 0)
+ t |= STRING | RAW;
+ else if (strcasecmp(arg, "ascii") == 0)
+ t |= STRING | ASCII;
+ else if (strcasecmp(arg, "domain") == 0)
+ t |= STRING | DOMAIN | RFC1035;
+ else if (strcasecmp(arg, "dname") == 0)
+ t |= STRING | DOMAIN;
+ else if (strcasecmp(arg, "binhex") == 0)
+ t |= STRING | BINHEX;
+ else if (strcasecmp(arg, "embed") == 0)
+ t |= EMBED;
+ else if (strcasecmp(arg, "encap") == 0)
+ t |= ENCAP;
+ else if (strcasecmp(arg, "rfc3361") ==0)
+ t |= STRING | RFC3361;
+ else if (strcasecmp(arg, "rfc3442") ==0)
+ t |= STRING | RFC3442;
+ else if (strcasecmp(arg, "rfc5969") == 0)
+ t |= STRING | RFC5969;
+ else if (strcasecmp(arg, "option") == 0)
+ t |= OPTION;
+ else {
+ logger(ctx, LOG_ERR, "unknown type: %s", arg);
+ return -1;
+ }
+ if (l && !(t & (STRING | BINHEX))) {
+ logger(ctx, LOG_WARNING,
+ "ignoring length for type `%s'", arg);
+ l = 0;
+ }
+ if (t & ARRAY && t & (STRING | BINHEX) &&
+ !(t & (RFC1035 | DOMAIN)))
+ {
+ logger(ctx, LOG_WARNING, "ignoring array for strings");
+ t &= ~ARRAY;
+ }
+ if (t & BITFLAG) {
+ if (bp == NULL)
+ logger(ctx, LOG_WARNING,
+ "missing bitflag assignment");
+ }
+ /* variable */
+ if (!fp) {
+ if (!(t & OPTION)) {
+ logger(ctx, LOG_ERR,
+ "type %s requires a variable name", arg);
+ return -1;
+ }
+ np = NULL;
+ } else {
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ if (strcasecmp(arg, "reserved")) {
+ np = strdup(arg);
+ if (np == NULL) {
+ logger(ctx, LOG_ERR,
+ "%s: %m", __func__);
+ return -1;
+ }
+ } else {
+ np = NULL;
+ t |= RESERVED;
+ }
+ }
+ if (opt != O_EMBED) {
+ for (dl = 0, ndop = *dop; dl < *dop_len; dl++, ndop++)
+ {
+ /* type 0 seems freshly malloced struct
+ * for us to use */
+ if (ndop->option == u || ndop->type == 0)
+ break;
+ }
+ if (dl == *dop_len)
+ ndop = NULL;
+ } else
+ ndop = NULL;
+ if (ndop == NULL) {
+ if ((ndop = realloc(*dop,
+ sizeof(**dop) * ((*dop_len) + 1))) == NULL)
+ {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(np);
+ return -1;
+ }
+ *dop = ndop;
+ ndop = &(*dop)[(*dop_len)++];
+ ndop->embopts = NULL;
+ ndop->embopts_len = 0;
+ ndop->encopts = NULL;
+ ndop->encopts_len = 0;
+ } else
+ free_dhcp_opt_embenc(ndop);
+ ndop->option = (uint32_t)u; /* could have been 0 */
+ ndop->type = t;
+ ndop->len = (size_t)l;
+ ndop->var = np;
+ if (bp) {
+ dl = strlen(bp);
+ memcpy(ndop->bitflags, bp, dl);
+ memset(ndop->bitflags + dl, 0,
+ sizeof(ndop->bitflags) - dl);
+ } else
+ memset(ndop->bitflags, 0, sizeof(ndop->bitflags));
+ /* Save the define for embed and encap options */
+ switch (opt) {
+ case O_DEFINE:
+ case O_DEFINEND:
+ case O_DEFINE6:
+ case O_VENDOPT:
+ *ldop = ndop;
+ break;
+ case O_ENCAP:
+ *edop = ndop;
+ break;
+ }
+ break;
+ case O_VENDCLASS:
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logger(ctx, LOG_ERR, "invalid code: %s", arg);
+ return -1;
+ }
+ if (fp) {
+ s = parse_string(NULL, 0, fp);
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ dl = (size_t)s;
+ if (dl + (sizeof(uint16_t) * 2) > UINT16_MAX) {
+ logger(ctx, LOG_ERR, "vendor class is too big");
+ return -1;
+ }
+ np = malloc(dl);
+ if (np == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ parse_string(np, dl, fp);
+ } else {
+ dl = 0;
+ np = NULL;
+ }
+ vivco = realloc(ifo->vivco, sizeof(*ifo->vivco) *
+ (ifo->vivco_len + 1));
+ if (vivco == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ ifo->vivco = vivco;
+ ifo->vivco_en = (uint32_t)u;
+ vivco = &ifo->vivco[ifo->vivco_len++];
+ vivco->len = dl;
+ vivco->data = (uint8_t *)np;
+ break;
+ case O_AUTHPROTOCOL:
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ if (strcasecmp(arg, "token") == 0)
+ ifo->auth.protocol = AUTH_PROTO_TOKEN;
+ else if (strcasecmp(arg, "delayed") == 0)
+ ifo->auth.protocol = AUTH_PROTO_DELAYED;
+ else if (strcasecmp(arg, "delayedrealm") == 0)
+ ifo->auth.protocol = AUTH_PROTO_DELAYEDREALM;
+ else {
+ logger(ctx, LOG_ERR, "%s: unsupported protocol", arg);
+ return -1;
+ }
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (arg == NULL) {
+ ifo->auth.options |= DHCPCD_AUTH_SEND;
+ ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+ ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+ break;
+ }
+ if (fp)
+ *fp++ = '\0';
+ if (strcasecmp(arg, "hmacmd5") == 0 ||
+ strcasecmp(arg, "hmac-md5") == 0)
+ ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+ else {
+ logger(ctx, LOG_ERR, "%s: unsupported algorithm", arg);
+ return 1;
+ }
+ arg = fp;
+ if (arg == NULL) {
+ ifo->auth.options |= DHCPCD_AUTH_SEND;
+ ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+ break;
+ }
+ if (strcasecmp(arg, "monocounter") == 0) {
+ ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+ ifo->auth.options |= DHCPCD_AUTH_RDM_COUNTER;
+ } else if (strcasecmp(arg, "monotonic") ==0 ||
+ strcasecmp(arg, "monotime") == 0)
+ ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+ else {
+ logger(ctx, LOG_ERR, "%s: unsupported RDM", arg);
+ return -1;
+ }
+ ifo->auth.options |= DHCPCD_AUTH_SEND;
+ break;
+ case O_AUTHTOKEN:
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "authtoken requires a realm");
+ return -1;
+ }
+ *fp++ = '\0';
+ token = malloc(sizeof(*token));
+ if (token == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(token);
+ return -1;
+ }
+ if (parse_uint32(&token->secretid, arg) == -1) {
+ logger(ctx, LOG_ERR, "%s: not a number", arg);
+ free(token);
+ return -1;
+ }
+ arg = fp;
+ fp = strend(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "authtoken requies an a key");
+ free(token);
+ return -1;
+ }
+ *fp++ = '\0';
+ s = parse_string(NULL, 0, arg);
+ if (s == -1) {
+ logger(ctx, LOG_ERR, "realm_len: %m");
+ free(token);
+ return -1;
+ }
+ if (s) {
+ token->realm_len = (size_t)s;
+ token->realm = malloc(token->realm_len);
+ if (token->realm == NULL) {
+ free(token);
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ parse_string((char *)token->realm, token->realm_len,
+ arg);
+ } else {
+ token->realm_len = 0;
+ token->realm = NULL;
+ }
+ arg = fp;
+ fp = strend(arg);
+ if (fp == NULL) {
+ logger(ctx, LOG_ERR, "authtoken requies an an expiry date");
+ free(token->realm);
+ free(token);
+ return -1;
+ }
+ *fp++ = '\0';
+ if (*arg == '"') {
+ arg++;
+ np = strchr(arg, '"');
+ if (np)
+ *np = '\0';
+ }
+ if (strcmp(arg, "0") == 0 || strcasecmp(arg, "forever") == 0)
+ token->expire =0;
+ else {
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) {
+ logger(ctx, LOG_ERR, "%s: invalid date time", arg);
+ free(token->realm);
+ free(token);
+ return -1;
+ }
+ if ((token->expire = mktime(&tm)) == (time_t)-1) {
+ logger(ctx, LOG_ERR, "%s: mktime: %m", __func__);
+ free(token->realm);
+ free(token);
+ return -1;
+ }
+ }
+ arg = fp;
+ s = parse_string(NULL, 0, arg);
+ if (s == -1 || s == 0) {
+ logger(ctx, LOG_ERR, s == -1 ? "token_len: %m" :
+ "authtoken needs a key");
+ free(token->realm);
+ free(token);
+ return -1;
+ }
+ token->key_len = (size_t)s;
+ token->key = malloc(token->key_len);
+ parse_string((char *)token->key, token->key_len, arg);
+ TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next);
+ break;
+ case O_AUTHNOTREQUIRED:
+ ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
+ break;
+ case O_DHCP:
+ ifo->options |= DHCPCD_DHCP | DHCPCD_IPV4;
+ break;
+ case O_NODHCP:
+ ifo->options &= ~DHCPCD_DHCP;
+ break;
+ case O_DHCP6:
+ ifo->options |= DHCPCD_DHCP6 | DHCPCD_IPV6;
+ break;
+ case O_NODHCP6:
+ ifo->options &= ~DHCPCD_DHCP6;
+ break;
+ case O_CONTROLGRP:
+#ifdef _REENTRANT
+ l = sysconf(_SC_GETGR_R_SIZE_MAX);
+ if (l == -1)
+ dl = 1024;
+ else
+ dl = (size_t)l;
+ p = malloc(dl);
+ if (p == NULL) {
+ logger(ctx, LOG_ERR, "%s: malloc: %m", __func__);
+ return -1;
+ }
+ while ((i = getgrnam_r(arg, &grpbuf, p, (size_t)l, &grp)) ==
+ ERANGE)
+ {
+ size_t nl = dl * 2;
+ if (nl < dl) {
+ logger(ctx, LOG_ERR, "control_group: out of buffer");
+ free(p);
+ return -1;
+ }
+ dl = nl;
+ np = realloc(p, dl);
+ if (np == NULL) {
+ logger(ctx, LOG_ERR, "control_group: realloc: %m");
+ free(p);
+ return -1;
+ }
+ p = np;
+ }
+ if (i != 0) {
+ errno = i;
+ logger(ctx, LOG_ERR, "getgrnam_r: %m");
+ free(p);
+ return -1;
+ }
+ if (grp == NULL) {
+ logger(ctx, LOG_ERR, "controlgroup: %s: not found", arg);
+ free(p);
+ return -1;
+ }
+ ctx->control_group = grp->gr_gid;
+ free(p);
+#else
+ grp = getgrnam(arg);
+ if (grp == NULL) {
+ logger(ctx, LOG_ERR, "controlgroup: %s: not found", arg);
+ return -1;
+ }
+ ctx->control_group = grp->gr_gid;
+#endif
+ break;
+ case O_GATEWAY:
+ ifo->options |= DHCPCD_GATEWAY;
+ break;
+ case O_NOUP:
+ ifo->options &= ~DHCPCD_IF_UP;
+ break;
+ case O_SLAAC:
+ if (strcmp(arg, "private") == 0 ||
+ strcmp(arg, "stableprivate") == 0 ||
+ strcmp(arg, "stable") == 0)
+ ifo->options |= DHCPCD_SLAACPRIVATE;
+ else
+ ifo->options &= ~DHCPCD_SLAACPRIVATE;
+ break;
+ case O_BOOTP:
+ ifo->options |= DHCPCD_BOOTP;
+ break;
+ case O_NODELAY:
+ ifo->options &= ~DHCPCD_INITIAL_DELAY;
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+parse_config_line(struct dhcpcd_ctx *ctx, const char *ifname,
+ struct if_options *ifo, const char *opt, char *line,
+ struct dhcp_opt **ldop, struct dhcp_opt **edop)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(cf_options) / sizeof(cf_options[0]); i++) {
+ if (!cf_options[i].name ||
+ strcmp(cf_options[i].name, opt) != 0)
+ continue;
+
+ if (cf_options[i].has_arg == required_argument && !line) {
+ fprintf(stderr,
+ PACKAGE ": option requires an argument -- %s\n",
+ opt);
+ return -1;
+ }
+
+ return parse_option(ctx, ifname, ifo, cf_options[i].val, line,
+ ldop, edop);
+ }
+
+ logger(ctx, LOG_ERR, "unknown option: %s", opt);
+ return -1;
+}
+
+static void
+finish_config(struct if_options *ifo)
+{
+
+ /* Terminate the encapsulated options */
+ if (ifo->vendor[0] && !(ifo->options & DHCPCD_VENDORRAW)) {
+ ifo->vendor[0]++;
+ ifo->vendor[ifo->vendor[0]] = DHO_END;
+ /* We are called twice.
+ * This should be fixed, but in the meantime, this
+ * guard should suffice */
+ ifo->options |= DHCPCD_VENDORRAW;
+ }
+}
+
+/* Handy routine to read very long lines in text files.
+ * This means we read the whole line and avoid any nasty buffer overflows.
+ * We strip leading space and avoid comment lines, making the code that calls
+ * us smaller. */
+static char *
+get_line(char ** __restrict buf, size_t * __restrict buflen,
+ FILE * __restrict fp)
+{
+ char *p;
+ ssize_t bytes;
+
+ do {
+ bytes = getline(buf, buflen, fp);
+ if (bytes == -1)
+ return NULL;
+ for (p = *buf; *p == ' ' || *p == '\t'; p++)
+ ;
+ } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';');
+ if ((*buf)[--bytes] == '\n')
+ (*buf)[bytes] = '\0';
+ return p;
+}
+
+struct if_options *
+read_config(struct dhcpcd_ctx *ctx,
+ const char *ifname, const char *ssid, const char *profile)
+{
+ struct if_options *ifo;
+ FILE *fp;
+ struct stat sb;
+ char *line, *buf, *option, *p;
+ size_t buflen;
+ ssize_t vlen;
+ int skip, have_profile, new_block, had_block;
+#ifndef EMBEDDED_CONFIG
+ const char * const *e;
+ size_t ol;
+#endif
+#if !defined(INET) || !defined(INET6)
+ size_t i;
+ struct dhcp_opt *opt;
+#endif
+ struct dhcp_opt *ldop, *edop;
+
+ /* Seed our default options */
+ ifo = calloc(1, sizeof(*ifo));
+ if (ifo == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ ifo->options |= DHCPCD_DAEMONISE | DHCPCD_LINK | DHCPCD_INITIAL_DELAY;
+ ifo->options |= DHCPCD_IF_UP;
+#ifdef PLUGIN_DEV
+ ifo->options |= DHCPCD_DEV;
+#endif
+#ifdef INET
+ ifo->options |= DHCPCD_IPV4 | DHCPCD_DHCP | DHCPCD_IPV4LL;
+ ifo->options |= DHCPCD_GATEWAY | DHCPCD_ARP;
+#endif
+#ifdef INET6
+ ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS;
+ ifo->options |= DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS;
+ ifo->options |= DHCPCD_DHCP6;
+#endif
+ ifo->timeout = DEFAULT_TIMEOUT;
+ ifo->reboot = DEFAULT_REBOOT;
+ ifo->metric = -1;
+ ifo->auth.options |= DHCPCD_AUTH_REQUIRE;
+ TAILQ_INIT(&ifo->auth.tokens);
+
+ vlen = dhcp_vendor((char *)ifo->vendorclassid + 1,
+ sizeof(ifo->vendorclassid) - 1);
+ ifo->vendorclassid[0] = (uint8_t)(vlen == -1 ? 0 : vlen);
+
+ buf = NULL;
+ buflen = 0;
+
+ /* Parse our embedded options file */
+ if (ifname == NULL) {
+ /* Space for initial estimates */
+#if defined(INET) && defined(INITDEFINES)
+ ifo->dhcp_override =
+ calloc(INITDEFINES, sizeof(*ifo->dhcp_override));
+ if (ifo->dhcp_override == NULL)
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ else
+ ifo->dhcp_override_len = INITDEFINES;
+#endif
+
+#if defined(INET6) && defined(INITDEFINENDS)
+ ifo->nd_override =
+ calloc(INITDEFINENDS, sizeof(*ifo->nd_override));
+ if (ifo->nd_override == NULL)
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ else
+ ifo->nd_override_len = INITDEFINENDS;
+#endif
+#if defined(INET6) && defined(INITDEFINE6S)
+ ifo->dhcp6_override =
+ calloc(INITDEFINE6S, sizeof(*ifo->dhcp6_override));
+ if (ifo->dhcp6_override == NULL)
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ else
+ ifo->dhcp6_override_len = INITDEFINE6S;
+#endif
+
+ /* Now load our embedded config */
+#ifdef EMBEDDED_CONFIG
+ fp = fopen(EMBEDDED_CONFIG, "r");
+ if (fp == NULL)
+ logger(ctx, LOG_ERR, "fopen `%s': %m", EMBEDDED_CONFIG);
+
+ while (fp && (line = get_line(&buf, &buflen, fp))) {
+#else
+ buflen = 80;
+ buf = malloc(buflen);
+ if (buf == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ ldop = edop = NULL;
+ for (e = dhcpcd_embedded_conf; *e; e++) {
+ ol = strlen(*e) + 1;
+ if (ol > buflen) {
+ buflen = ol;
+ buf = realloc(buf, buflen);
+ if (buf == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(buf);
+ return NULL;
+ }
+ }
+ memcpy(buf, *e, ol);
+ line = buf;
+#endif
+ option = strsep(&line, " \t");
+ if (line)
+ line = strskipwhite(line);
+ /* Trim trailing whitespace */
+ if (line && *line) {
+ p = line + strlen(line) - 1;
+ while (p != line &&
+ (*p == ' ' || *p == '\t') &&
+ *(p - 1) != '\\')
+ *p-- = '\0';
+ }
+ parse_config_line(ctx, NULL, ifo, option, line,
+ &ldop, &edop);
+
+ }
+
+#ifdef EMBEDDED_CONFIG
+ if (fp)
+ fclose(fp);
+#endif
+#ifdef INET
+ ctx->dhcp_opts = ifo->dhcp_override;
+ ctx->dhcp_opts_len = ifo->dhcp_override_len;
+#else
+ for (i = 0, opt = ifo->dhcp_override;
+ i < ifo->dhcp_override_len;
+ i++, opt++)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->dhcp_override);
+#endif
+ ifo->dhcp_override = NULL;
+ ifo->dhcp_override_len = 0;
+
+#ifdef INET6
+ ctx->nd_opts = ifo->nd_override;
+ ctx->nd_opts_len = ifo->nd_override_len;
+ ctx->dhcp6_opts = ifo->dhcp6_override;
+ ctx->dhcp6_opts_len = ifo->dhcp6_override_len;
+#else
+ for (i = 0, opt = ifo->nd_override;
+ i < ifo->nd_override_len;
+ i++, opt++)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->nd_override);
+ for (i = 0, opt = ifo->dhcp6_override;
+ i < ifo->dhcp6_override_len;
+ i++, opt++)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->dhcp6_override);
+#endif
+ ifo->nd_override = NULL;
+ ifo->nd_override_len = 0;
+ ifo->dhcp6_override = NULL;
+ ifo->dhcp6_override_len = 0;
+
+ ctx->vivso = ifo->vivso_override;
+ ctx->vivso_len = ifo->vivso_override_len;
+ ifo->vivso_override = NULL;
+ ifo->vivso_override_len = 0;
+ }
+
+ /* Parse our options file */
+ fp = fopen(ctx->cffile, "r");
+ if (fp == NULL) {
+ if (strcmp(ctx->cffile, CONFIG))
+ logger(ctx, LOG_ERR, "fopen `%s': %m", ctx->cffile);
+ free(buf);
+ return ifo;
+ }
+ if (stat(ctx->cffile, &sb) == 0)
+ ifo->mtime = sb.st_mtime;
+
+ ldop = edop = NULL;
+ skip = have_profile = new_block = 0;
+ had_block = ifname == NULL ? 1 : 0;
+ while ((line = get_line(&buf, &buflen, fp))) {
+ option = strsep(&line, " \t");
+ if (line)
+ line = strskipwhite(line);
+ /* Trim trailing whitespace */
+ if (line && *line) {
+ p = line + strlen(line) - 1;
+ while (p != line &&
+ (*p == ' ' || *p == '\t') &&
+ *(p - 1) != '\\')
+ *p-- = '\0';
+ }
+ if (skip == 0 && new_block) {
+ had_block = 1;
+ new_block = 0;
+ ifo->options &= ~DHCPCD_WAITOPTS;
+ }
+ /* Start of an interface block, skip if not ours */
+ if (strcmp(option, "interface") == 0) {
+ char **n;
+
+ new_block = 1;
+ if (ifname && line && strcmp(line, ifname) == 0)
+ skip = 0;
+ else
+ skip = 1;
+ if (ifname)
+ continue;
+
+ n = realloc(ctx->ifcv,
+ sizeof(char *) * ((size_t)ctx->ifcc + 1));
+ if (n == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ continue;
+ }
+ ctx->ifcv = n;
+ ctx->ifcv[ctx->ifcc] = strdup(line);
+ if (ctx->ifcv[ctx->ifcc] == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ continue;
+ }
+ ctx->ifcc++;
+ logger(ctx, LOG_DEBUG, "allowing interface %s",
+ ctx->ifcv[ctx->ifcc - 1]);
+ continue;
+ }
+ /* Start of an ssid block, skip if not ours */
+ if (strcmp(option, "ssid") == 0) {
+ new_block = 1;
+ if (ssid && line && strcmp(line, ssid) == 0)
+ skip = 0;
+ else
+ skip = 1;
+ continue;
+ }
+ /* Start of a profile block, skip if not ours */
+ if (strcmp(option, "profile") == 0) {
+ new_block = 1;
+ if (profile && line && strcmp(line, profile) == 0) {
+ skip = 0;
+ have_profile = 1;
+ } else
+ skip = 1;
+ continue;
+ }
+ /* Skip arping if we have selected a profile but not parsing
+ * one. */
+ if (profile && !have_profile && strcmp(option, "arping") == 0)
+ continue;
+ if (skip)
+ continue;
+ parse_config_line(ctx, ifname, ifo, option, line, &ldop, &edop);
+ }
+ fclose(fp);
+ free(buf);
+
+ if (profile && !have_profile) {
+ free_options(ifo);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (!had_block)
+ ifo->options &= ~DHCPCD_WAITOPTS;
+ finish_config(ifo);
+ return ifo;
+}
+
+int
+add_options(struct dhcpcd_ctx *ctx, const char *ifname,
+ struct if_options *ifo, int argc, char **argv)
+{
+ int oi, opt, r;
+ unsigned long long wait_opts;
+
+ if (argc == 0)
+ return 1;
+
+ optind = 0;
+ r = 1;
+ /* Don't apply the command line wait options to each interface,
+ * only use the dhcpcd.conf entry for that. */
+ if (ifname != NULL)
+ wait_opts = ifo->options & DHCPCD_WAITOPTS;
+ while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
+ {
+ r = parse_option(ctx, ifname, ifo, opt, optarg, NULL, NULL);
+ if (r != 1)
+ break;
+ }
+ if (ifname != NULL) {
+ ifo->options &= ~DHCPCD_WAITOPTS;
+ ifo->options |= wait_opts;
+ }
+
+ finish_config(ifo);
+ return r;
+}
+
+void
+free_options(struct if_options *ifo)
+{
+ size_t i;
+ struct dhcp_opt *opt;
+ struct vivco *vo;
+ struct token *token;
+
+ if (ifo) {
+ if (ifo->environ) {
+ i = 0;
+ while (ifo->environ[i])
+ free(ifo->environ[i++]);
+ free(ifo->environ);
+ }
+ if (ifo->config) {
+ i = 0;
+ while (ifo->config[i])
+ free(ifo->config[i++]);
+ free(ifo->config);
+ }
+ ipv4_freeroutes(ifo->routes);
+ free(ifo->script);
+ free(ifo->arping);
+ free(ifo->blacklist);
+ free(ifo->fallback);
+
+ for (opt = ifo->dhcp_override;
+ ifo->dhcp_override_len > 0;
+ opt++, ifo->dhcp_override_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->dhcp_override);
+ for (opt = ifo->nd_override;
+ ifo->nd_override_len > 0;
+ opt++, ifo->nd_override_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->nd_override);
+ for (opt = ifo->dhcp6_override;
+ ifo->dhcp6_override_len > 0;
+ opt++, ifo->dhcp6_override_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->dhcp6_override);
+ for (vo = ifo->vivco;
+ ifo->vivco_len > 0;
+ vo++, ifo->vivco_len--)
+ free(vo->data);
+ free(ifo->vivco);
+ for (opt = ifo->vivso_override;
+ ifo->vivso_override_len > 0;
+ opt++, ifo->vivso_override_len--)
+ free_dhcp_opt_embenc(opt);
+ free(ifo->vivso_override);
+
+#ifdef INET6
+ for (; ifo->ia_len > 0; ifo->ia_len--)
+ free(ifo->ia[ifo->ia_len - 1].sla);
+#endif
+ free(ifo->ia);
+
+ while ((token = TAILQ_FIRST(&ifo->auth.tokens))) {
+ TAILQ_REMOVE(&ifo->auth.tokens, token, next);
+ if (token->realm_len)
+ free(token->realm);
+ free(token->key);
+ free(token);
+ }
+ free(ifo);
+ }
+}
--- /dev/null
+/* $NetBSD: if-options.h,v 1.14 2015/09/04 12:25:01 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef IF_OPTIONS_H
+#define IF_OPTIONS_H
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <getopt.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include "auth.h"
+
+/* Don't set any optional arguments here so we retain POSIX
+ * compatibility with getopt */
+#define IF_OPTS "46bc:de:f:gh:i:j:kl:m:no:pqr:s:t:u:v:wxy:z:" \
+ "ABC:DEF:GHI:JKLMO:Q:S:TUVW:X:Z:"
+
+#define DEFAULT_TIMEOUT 30
+#define DEFAULT_REBOOT 5
+
+#ifndef HOSTNAME_MAX_LEN
+#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */
+#endif
+#define VENDORCLASSID_MAX_LEN 255
+#define CLIENTID_MAX_LEN 48
+#define USERCLASS_MAX_LEN 255
+#define VENDOR_MAX_LEN 255
+
+#define DHCPCD_ARP (1ULL << 0)
+#define DHCPCD_RELEASE (1ULL << 1)
+#define DHCPCD_DOMAIN (1ULL << 2)
+#define DHCPCD_GATEWAY (1ULL << 3)
+#define DHCPCD_STATIC (1ULL << 4)
+#define DHCPCD_DEBUG (1ULL << 5)
+#define DHCPCD_LASTLEASE (1ULL << 7)
+#define DHCPCD_INFORM (1ULL << 8)
+#define DHCPCD_REQUEST (1ULL << 9)
+#define DHCPCD_IPV4LL (1ULL << 10)
+#define DHCPCD_DUID (1ULL << 11)
+#define DHCPCD_PERSISTENT (1ULL << 12)
+#define DHCPCD_DAEMONISE (1ULL << 14)
+#define DHCPCD_DAEMONISED (1ULL << 15)
+#define DHCPCD_TEST (1ULL << 16)
+#define DHCPCD_MASTER (1ULL << 17)
+#define DHCPCD_HOSTNAME (1ULL << 18)
+#define DHCPCD_CLIENTID (1ULL << 19)
+#define DHCPCD_LINK (1ULL << 20)
+#define DHCPCD_QUIET (1ULL << 21)
+#define DHCPCD_BACKGROUND (1ULL << 22)
+#define DHCPCD_VENDORRAW (1ULL << 23)
+#define DHCPCD_NOWAITIP (1ULL << 24) /* To force daemonise */
+#define DHCPCD_WAITIP (1ULL << 25)
+#define DHCPCD_SLAACPRIVATE (1ULL << 26)
+#define DHCPCD_CSR_WARNED (1ULL << 27)
+#define DHCPCD_XID_HWADDR (1ULL << 28)
+#define DHCPCD_BROADCAST (1ULL << 29)
+#define DHCPCD_DUMPLEASE (1ULL << 30)
+#define DHCPCD_IPV6RS (1ULL << 31)
+#define DHCPCD_IPV6RA_REQRDNSS (1ULL << 32)
+#define DHCPCD_IPV6RA_OWN (1ULL << 33)
+#define DHCPCD_IPV6RA_OWN_DEFAULT (1ULL << 34)
+#define DHCPCD_IPV4 (1ULL << 35)
+#define DHCPCD_FORKED (1ULL << 36)
+#define DHCPCD_IPV6 (1ULL << 37)
+#define DHCPCD_STARTED (1ULL << 38)
+#define DHCPCD_NOALIAS (1ULL << 39)
+#define DHCPCD_IA_FORCED (1ULL << 40)
+#define DHCPCD_STOPPING (1ULL << 41)
+#define DHCPCD_DEPARTED (1ULL << 42)
+#define DHCPCD_HOSTNAME_SHORT (1ULL << 43)
+#define DHCPCD_EXITING (1ULL << 44)
+#define DHCPCD_WAITIP4 (1ULL << 45)
+#define DHCPCD_WAITIP6 (1ULL << 46)
+#define DHCPCD_DEV (1ULL << 47)
+#define DHCPCD_IAID (1ULL << 48)
+#define DHCPCD_DHCP (1ULL << 49)
+#define DHCPCD_DHCP6 (1ULL << 50)
+#define DHCPCD_IF_UP (1ULL << 51)
+// unassigned (1ULL << 52)
+// unassinged (1ULL << 53)
+#define DHCPCD_IPV6RA_AUTOCONF (1ULL << 54)
+#define DHCPCD_ROUTER_HOST_ROUTE_WARNED (1ULL << 55)
+#define DHCPCD_IPV6RA_ACCEPT_NOPUBLIC (1ULL << 56)
+#define DHCPCD_BOOTP (1ULL << 57)
+#define DHCPCD_INITIAL_DELAY (1ULL << 58)
+
+#define DHCPCD_NODROP (DHCPCD_EXITING | DHCPCD_PERSISTENT)
+
+#define DHCPCD_WAITOPTS (DHCPCD_WAITIP | DHCPCD_WAITIP4 | DHCPCD_WAITIP6)
+
+#define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \
+ DHCPCD_ROUTER_HOST_ROUTE_WARNED)
+
+extern const struct option cf_options[];
+
+struct if_sla {
+ char ifname[IF_NAMESIZE];
+ uint32_t sla;
+ uint8_t prefix_len;
+ int8_t sla_set;
+};
+
+struct if_ia {
+ uint8_t iaid[4];
+#ifdef INET6
+ uint16_t ia_type;
+ uint8_t iaid_set;
+ struct in6_addr addr;
+ uint8_t prefix_len;
+ uint32_t sla_max;
+ size_t sla_len;
+ struct if_sla *sla;
+#endif
+};
+
+struct vivco {
+ size_t len;
+ uint8_t *data;
+};
+
+struct if_options {
+ time_t mtime;
+ uint8_t iaid[4];
+ int metric;
+ uint8_t requestmask[256 / NBBY];
+ uint8_t requiremask[256 / NBBY];
+ uint8_t nomask[256 / NBBY];
+ uint8_t rejectmask[256 / NBBY];
+ uint8_t dstmask[256 / NBBY];
+ uint8_t requestmasknd[(UINT16_MAX + 1) / NBBY];
+ uint8_t requiremasknd[(UINT16_MAX + 1) / NBBY];
+ uint8_t nomasknd[(UINT16_MAX + 1) / NBBY];
+ uint8_t rejectmasknd[(UINT16_MAX + 1) / NBBY];
+ uint8_t requestmask6[(UINT16_MAX + 1) / NBBY];
+ uint8_t requiremask6[(UINT16_MAX + 1) / NBBY];
+ uint8_t nomask6[(UINT16_MAX + 1) / NBBY];
+ uint8_t rejectmask6[(UINT16_MAX + 1) / NBBY];
+ uint32_t leasetime;
+ time_t timeout;
+ time_t reboot;
+ unsigned long long options;
+
+ struct in_addr req_addr;
+ struct in_addr req_mask;
+ struct rt_head *routes;
+ unsigned int mtu;
+ char **config;
+
+ char **environ;
+ char *script;
+
+ char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the length */
+ uint8_t fqdn;
+ uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 2];
+ uint8_t clientid[CLIENTID_MAX_LEN + 2];
+ uint8_t userclass[USERCLASS_MAX_LEN + 2];
+ uint8_t vendor[VENDOR_MAX_LEN + 2];
+
+ size_t blacklist_len;
+ in_addr_t *blacklist;
+ size_t whitelist_len;
+ in_addr_t *whitelist;
+ size_t arping_len;
+ in_addr_t *arping;
+ char *fallback;
+
+ struct if_ia *ia;
+ size_t ia_len;
+
+ struct dhcp_opt *dhcp_override;
+ size_t dhcp_override_len;
+ struct dhcp_opt *nd_override;
+ size_t nd_override_len;
+ struct dhcp_opt *dhcp6_override;
+ size_t dhcp6_override_len;
+ uint32_t vivco_en;
+ struct vivco *vivco;
+ size_t vivco_len;
+ struct dhcp_opt *vivso_override;
+ size_t vivso_override_len;
+
+ struct auth auth;
+};
+
+struct if_options *read_config(struct dhcpcd_ctx *,
+ const char *, const char *, const char *);
+int add_options(struct dhcpcd_ctx *, const char *,
+ struct if_options *, int, char **);
+void free_dhcp_opt_embenc(struct dhcp_opt *);
+void free_options(struct if_options *);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: if.c,v 1.16 2015/09/04 12:25:01 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */
+# include <net/if_var.h>
+#endif
+#ifdef AF_LINK
+# include <net/if_dl.h>
+# include <net/if_types.h>
+# include <netinet/in_var.h>
+#endif
+#ifdef AF_PACKET
+# include <netpacket/packet.h>
+#endif
+#ifdef SIOCGIFMEDIA
+# include <net/if_media.h>
+#endif
+#include <net/route.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <fnmatch.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "config.h"
+#include "common.h"
+#include "dev.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "ipv6nd.h"
+
+void
+if_free(struct interface *ifp)
+{
+
+ if (ifp == NULL)
+ return;
+ ipv4ll_free(ifp);
+ dhcp_free(ifp);
+ ipv4_free(ifp);
+ dhcp6_free(ifp);
+ ipv6nd_free(ifp);
+ ipv6_free(ifp);
+ free_options(ifp->options);
+ free(ifp);
+}
+
+int
+if_opensockets(struct dhcpcd_ctx *ctx)
+{
+
+ if ((ctx->link_fd = if_openlinksocket()) == -1)
+ return -1;
+
+ ctx->pf_inet_fd = xsocket(PF_INET, SOCK_DGRAM, 0, O_CLOEXEC);
+ if (ctx->pf_inet_fd == -1)
+ return -1;
+
+#if defined(INET6) && defined(BSD)
+ ctx->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM, 0, O_CLOEXEC);
+ if (ctx->pf_inet6_fd == -1)
+ return -1;
+#endif
+
+#ifdef IFLR_ACTIVE
+ ctx->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM, 0, O_CLOEXEC);
+ if (ctx->pf_link_fd == -1)
+ return -1;
+#endif
+
+ return 0;
+}
+
+int
+if_carrier(struct interface *ifp)
+{
+ int r;
+ struct ifreq ifr;
+#ifdef SIOCGIFMEDIA
+ struct ifmediareq ifmr;
+#endif
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1)
+ return LINK_UNKNOWN;
+ ifp->flags = (unsigned int)ifr.ifr_flags;
+
+#ifdef SIOCGIFMEDIA
+ memset(&ifmr, 0, sizeof(ifmr));
+ strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name));
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr) != -1 &&
+ ifmr.ifm_status & IFM_AVALID)
+ r = (ifmr.ifm_status & IFM_ACTIVE) ? LINK_UP : LINK_DOWN;
+ else
+ r = ifr.ifr_flags & IFF_RUNNING ? LINK_UP : LINK_UNKNOWN;
+#else
+ r = ifr.ifr_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN;
+#endif
+ return r;
+}
+
+int
+if_setflag(struct interface *ifp, short flag)
+{
+ struct ifreq ifr;
+ int r;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ r = -1;
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == 0) {
+ if (flag == 0 || (ifr.ifr_flags & flag) == flag)
+ r = 0;
+ else {
+ ifr.ifr_flags |= flag;
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCSIFFLAGS, &ifr) ==0)
+ r = 0;
+ }
+ ifp->flags = (unsigned int)ifr.ifr_flags;
+ }
+ return r;
+}
+
+static int
+if_hasconf(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ int i;
+
+ for (i = 0; i < ctx->ifcc; i++) {
+ if (strcmp(ctx->ifcv[i], ifname) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static void if_learnaddrs1(struct dhcpcd_ctx *ctx, struct if_head *ifs,
+ struct ifaddrs *ifaddrs)
+{
+ struct ifaddrs *ifa;
+ struct interface *ifp;
+#ifdef INET
+ const struct sockaddr_in *addr, *net, *dst;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6, *net6;
+#endif
+ int ifa_flags;
+
+
+ for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
+ if ((ifp = if_find(ifs, ifa->ifa_name)) == NULL)
+ continue;
+ switch(ifa->ifa_addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ addr = (const struct sockaddr_in *)
+ (void *)ifa->ifa_addr;
+ net = (const struct sockaddr_in *)
+ (void *)ifa->ifa_netmask;
+ if (ifa->ifa_flags & IFF_POINTOPOINT)
+ dst = (const struct sockaddr_in *)
+ (void *)ifa->ifa_dstaddr;
+ else
+ dst = NULL;
+ ifa_flags = if_addrflags(&addr->sin_addr, ifp);
+ ipv4_handleifa(ctx, RTM_NEWADDR, ifs, ifa->ifa_name,
+ &addr->sin_addr,
+ &net->sin_addr,
+ dst ? &dst->sin_addr : NULL, ifa_flags);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr;
+ net6 = (struct sockaddr_in6 *)(void *)ifa->ifa_netmask;
+#ifdef __KAME__
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ /* Remove the scope from the address */
+ sin6->sin6_addr.s6_addr[2] =
+ sin6->sin6_addr.s6_addr[3] = '\0';
+#endif
+ ifa_flags = if_addrflags6(&sin6->sin6_addr, ifp);
+ if (ifa_flags != -1)
+ ipv6_handleifa(ctx, RTM_NEWADDR, ifs,
+ ifa->ifa_name,
+ &sin6->sin6_addr,
+ ipv6_prefixlen(&net6->sin6_addr),
+ ifa_flags);
+ break;
+#endif
+ }
+ }
+}
+
+struct if_head *
+if_discover(struct dhcpcd_ctx *ctx, int argc, char * const *argv)
+{
+ struct ifaddrs *ifaddrs, *ifa;
+ char *p;
+ int i;
+ struct if_head *ifs;
+ struct interface *ifp;
+#ifdef __linux__
+ char ifn[IF_NAMESIZE];
+#endif
+#ifdef AF_LINK
+ const struct sockaddr_dl *sdl;
+#ifdef SIOCGIFPRIORITY
+ struct ifreq ifr;
+#endif
+#ifdef IFLR_ACTIVE
+ struct if_laddrreq iflr;
+#endif
+
+#ifdef IFLR_ACTIVE
+ memset(&iflr, 0, sizeof(iflr));
+#endif
+#elif AF_PACKET
+ const struct sockaddr_ll *sll;
+#endif
+
+ if (getifaddrs(&ifaddrs) == -1)
+ return NULL;
+ ifs = malloc(sizeof(*ifs));
+ if (ifs == NULL)
+ return NULL;
+ TAILQ_INIT(ifs);
+
+ for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr != NULL) {
+#ifdef AF_LINK
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+#elif AF_PACKET
+ if (ifa->ifa_addr->sa_family != AF_PACKET)
+ continue;
+#endif
+ }
+
+ /* It's possible for an interface to have >1 AF_LINK.
+ * For our purposes, we use the first one. */
+ TAILQ_FOREACH(ifp, ifs, next) {
+ if (strcmp(ifp->name, ifa->ifa_name) == 0)
+ break;
+ }
+ if (ifp)
+ continue;
+
+ if (argc > 0) {
+ for (i = 0; i < argc; i++) {
+#ifdef __linux__
+ /* Check the real interface name */
+ strlcpy(ifn, argv[i], sizeof(ifn));
+ p = strchr(ifn, ':');
+ if (p)
+ *p = '\0';
+ if (strcmp(ifn, ifa->ifa_name) == 0)
+ break;
+#else
+ if (strcmp(argv[i], ifa->ifa_name) == 0)
+ break;
+#endif
+ }
+ if (i == argc)
+ continue;
+ p = argv[i];
+ } else {
+ p = ifa->ifa_name;
+#ifdef __linux__
+ strlcpy(ifn, ifa->ifa_name, sizeof(ifn));
+#endif
+ /* -1 means we're discovering against a specific
+ * interface, but we still need the below rules
+ * to apply. */
+ if (argc == -1 && strcmp(argv[0], ifa->ifa_name) != 0)
+ continue;
+ }
+ for (i = 0; i < ctx->ifdc; i++)
+ if (!fnmatch(ctx->ifdv[i], p, 0))
+ break;
+ if (i < ctx->ifdc)
+ continue;
+ for (i = 0; i < ctx->ifac; i++)
+ if (!fnmatch(ctx->ifav[i], p, 0))
+ break;
+ if (ctx->ifac && i == ctx->ifac)
+ continue;
+
+ /* Ensure that the interface name has settled */
+ if (!dev_initialized(ctx, p))
+ continue;
+
+ /* Don't allow loopback or pointopoint unless explicit */
+ if (ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) {
+ if ((argc == 0 || argc == -1) &&
+ ctx->ifac == 0 && !if_hasconf(ctx, p))
+ continue;
+ }
+
+ if (if_vimaster(ctx, p) == 1) {
+ logger(ctx, argc ? LOG_ERR : LOG_DEBUG,
+ "%s: is a Virtual Interface Master, skipping", p);
+ continue;
+ }
+
+ ifp = calloc(1, sizeof(*ifp));
+ if (ifp == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ break;
+ }
+ ifp->ctx = ctx;
+#ifdef __linux__
+ strlcpy(ifp->name, ifn, sizeof(ifp->name));
+ strlcpy(ifp->alias, p, sizeof(ifp->alias));
+#else
+ strlcpy(ifp->name, p, sizeof(ifp->name));
+#endif
+ ifp->flags = ifa->ifa_flags;
+ ifp->carrier = if_carrier(ifp);
+
+ if (ifa->ifa_addr != NULL) {
+#ifdef AF_LINK
+ sdl = (const struct sockaddr_dl *)(void *)ifa->ifa_addr;
+
+#ifdef IFLR_ACTIVE
+ /* We need to check for active address */
+ strlcpy(iflr.iflr_name, ifp->name,
+ sizeof(iflr.iflr_name));
+ memcpy(&iflr.addr, ifa->ifa_addr,
+ MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr)));
+ iflr.flags = IFLR_PREFIX;
+ iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY;
+ if (ioctl(ctx->pf_link_fd, SIOCGLIFADDR, &iflr) == -1 ||
+ !(iflr.flags & IFLR_ACTIVE))
+ {
+ if_free(ifp);
+ continue;
+ }
+#endif
+
+ ifp->index = sdl->sdl_index;
+ switch(sdl->sdl_type) {
+#ifdef IFT_BRIDGE
+ case IFT_BRIDGE: /* FALLTHROUGH */
+#endif
+#ifdef IFT_PPP
+ case IFT_PPP: /* FALLTHROUGH */
+#endif
+#ifdef IFT_PROPVIRTUAL
+ case IFT_PROPVIRTUAL: /* FALLTHROUGH */
+#endif
+#if defined(IFT_BRIDGE) || defined(IFT_PPP) || defined(IFT_PROPVIRTUAL)
+ /* Don't allow unless explicit */
+ if ((argc == 0 || argc == -1) &&
+ ctx->ifac == 0 &&
+ !if_hasconf(ctx, ifp->name))
+ {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ignoring due to"
+ " interface type and"
+ " no config",
+ ifp->name);
+ if_free(ifp);
+ continue;
+ }
+ /* FALLTHROUGH */
+#endif
+#ifdef IFT_L2VLAN
+ case IFT_L2VLAN: /* FALLTHROUGH */
+#endif
+#ifdef IFT_L3IPVLAN
+ case IFT_L3IPVLAN: /* FALLTHROUGH */
+#endif
+ case IFT_ETHER:
+ ifp->family = ARPHRD_ETHER;
+ break;
+#ifdef IFT_IEEE1394
+ case IFT_IEEE1394:
+ ifp->family = ARPHRD_IEEE1394;
+ break;
+#endif
+#ifdef IFT_INFINIBAND
+ case IFT_INFINIBAND:
+ ifp->family = ARPHRD_INFINIBAND;
+ break;
+#endif
+ default:
+ /* Don't allow unless explicit */
+ if ((argc == 0 || argc == -1) &&
+ ctx->ifac == 0 &&
+ !if_hasconf(ctx, ifp->name))
+ {
+ if_free(ifp);
+ continue;
+ }
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: unsupported interface type %.2x",
+ ifp->name, sdl->sdl_type);
+ /* Pretend it's ethernet */
+ ifp->family = ARPHRD_ETHER;
+ break;
+ }
+ ifp->hwlen = sdl->sdl_alen;
+#ifndef CLLADDR
+# define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen))
+#endif
+ memcpy(ifp->hwaddr, CLLADDR(sdl), ifp->hwlen);
+#elif AF_PACKET
+ sll = (const struct sockaddr_ll *)(void *)ifa->ifa_addr;
+ ifp->index = (unsigned int)sll->sll_ifindex;
+ ifp->family = sll->sll_hatype;
+ ifp->hwlen = sll->sll_halen;
+ if (ifp->hwlen != 0)
+ memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen);
+#endif
+ }
+#ifdef __linux__
+ /* PPP addresses on Linux don't have hardware addresses */
+ else
+ ifp->index = if_nametoindex(ifp->name);
+#endif
+
+ /* We only work on ethernet by default */
+ if (ifp->family != ARPHRD_ETHER) {
+ if ((argc == 0 || argc == -1) &&
+ ctx->ifac == 0 && !if_hasconf(ctx, ifp->name))
+ {
+ if_free(ifp);
+ continue;
+ }
+ switch (ifp->family) {
+ case ARPHRD_IEEE1394:
+ case ARPHRD_INFINIBAND:
+#ifdef ARPHRD_LOOPBACK
+ case ARPHRD_LOOPBACK:
+#endif
+#ifdef ARPHRD_PPP
+ case ARPHRD_PPP:
+#endif
+ /* We don't warn for supported families */
+ break;
+
+/* IFT already checked */
+#ifndef AF_LINK
+ default:
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: unsupported interface family %.2x",
+ ifp->name, ifp->family);
+ break;
+#endif
+ }
+ }
+
+ if (!(ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) {
+ /* Handle any platform init for the interface */
+ if (if_init(ifp) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: if_init: %m", p);
+ if_free(ifp);
+ continue;
+ }
+
+ /* Ensure that the MTU is big enough for DHCP */
+ if (if_getmtu(ifp) < MTU_MIN &&
+ if_setmtu(ifp, MTU_MIN) == -1)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: if_setmtu: %m", p);
+ if_free(ifp);
+ continue;
+ }
+ }
+
+#ifdef SIOCGIFPRIORITY
+ /* Respect the interface priority */
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(ctx->pf_inet_fd, SIOCGIFPRIORITY, &ifr) == 0)
+ ifp->metric = ifr.ifr_metric;
+#else
+ /* We reserve the 100 range for virtual interfaces, if and when
+ * we can work them out. */
+ ifp->metric = 200 + ifp->index;
+ if (if_getssid(ifp) != -1) {
+ ifp->wireless = 1;
+ ifp->metric += 100;
+ }
+#endif
+
+ TAILQ_INSERT_TAIL(ifs, ifp, next);
+ }
+
+ if_learnaddrs1(ctx, ifs, ifaddrs);
+ freeifaddrs(ifaddrs);
+
+ return ifs;
+}
+
+static struct interface *
+if_findindexname(struct if_head *ifaces, unsigned int idx, const char *name)
+{
+
+ if (ifaces != NULL) {
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ifaces, next) {
+ if ((name && strcmp(ifp->name, name) == 0) ||
+#ifdef __linux__
+ (name && strcmp(ifp->alias, name) == 0) ||
+#endif
+ (!name && ifp->index == idx))
+ return ifp;
+ }
+ }
+
+ errno = ESRCH;
+ return NULL;
+}
+
+struct interface *
+if_find(struct if_head *ifaces, const char *name)
+{
+
+ return if_findindexname(ifaces, 0, name);
+}
+
+struct interface *
+if_findindex(struct if_head *ifaces, unsigned int idx)
+{
+
+ return if_findindexname(ifaces, idx, NULL);
+}
+
+int
+if_domtu(const struct interface *ifp, short int mtu)
+{
+ int r;
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ ifr.ifr_mtu = mtu;
+ r = ioctl(ifp->ctx->pf_inet_fd, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr);
+ if (r == -1)
+ return -1;
+ return ifr.ifr_mtu;
+}
+
+/* Interface comparer for working out ordering. */
+static int
+if_cmp(const struct interface *si, const struct interface *ti)
+{
+#ifdef INET
+ int r;
+#endif
+
+ /* Check carrier status first */
+ if (si->carrier > ti->carrier)
+ return -1;
+ if (si->carrier < ti->carrier)
+ return 1;
+
+ if (D_STATE_RUNNING(si) && !D_STATE_RUNNING(ti))
+ return -1;
+ if (!D_STATE_RUNNING(si) && D_STATE_RUNNING(ti))
+ return 1;
+ if (RS_STATE_RUNNING(si) && !RS_STATE_RUNNING(ti))
+ return -1;
+ if (!RS_STATE_RUNNING(si) && RS_STATE_RUNNING(ti))
+ return 1;
+ if (D6_STATE_RUNNING(si) && !D6_STATE_RUNNING(ti))
+ return -1;
+ if (!D6_STATE_RUNNING(si) && D6_STATE_RUNNING(ti))
+ return 1;
+
+#ifdef INET
+ /* Special attention needed here due to states and IPv4LL. */
+ if ((r = ipv4_ifcmp(si, ti)) != 0)
+ return r;
+#endif
+
+ /* Finally, metric */
+ if (si->metric < ti->metric)
+ return -1;
+ if (si->metric > ti->metric)
+ return 1;
+ return 0;
+}
+
+/* Sort the interfaces into a preferred order - best first, worst last. */
+void
+if_sortinterfaces(struct dhcpcd_ctx *ctx)
+{
+ struct if_head sorted;
+ struct interface *ifp, *ift;
+
+ if (ctx->ifaces == NULL ||
+ (ifp = TAILQ_FIRST(ctx->ifaces)) == NULL ||
+ TAILQ_NEXT(ifp, next) == NULL)
+ return;
+
+ TAILQ_INIT(&sorted);
+ TAILQ_REMOVE(ctx->ifaces, ifp, next);
+ TAILQ_INSERT_HEAD(&sorted, ifp, next);
+ while ((ifp = TAILQ_FIRST(ctx->ifaces))) {
+ TAILQ_REMOVE(ctx->ifaces, ifp, next);
+ TAILQ_FOREACH(ift, &sorted, next) {
+ if (if_cmp(ifp, ift) == -1) {
+ TAILQ_INSERT_BEFORE(ift, ifp, next);
+ break;
+ }
+ }
+ if (ift == NULL)
+ TAILQ_INSERT_TAIL(&sorted, ifp, next);
+ }
+ TAILQ_CONCAT(ctx->ifaces, &sorted, next);
+}
+
+int
+xsocket(int domain, int type, int protocol, int flags)
+{
+#ifdef SOCK_CLOEXEC
+ if (flags & O_CLOEXEC)
+ type |= SOCK_CLOEXEC;
+ if (flags & O_NONBLOCK)
+ type |= SOCK_NONBLOCK;
+
+ return socket(domain, type, protocol);
+#else
+ int s, xflags;
+
+ if ((s = socket(domain, type, protocol)) == -1)
+ return -1;
+ if ((flags & O_CLOEXEC) && (xflags = fcntl(s, F_GETFD, 0)) == -1 ||
+ fcntl(s, F_SETFD, xflags | FD_CLOEXEC) == -1)
+ goto out;
+ if ((flags & O_NONBLOCK) && (xflags = fcntl(s, F_GETFL, 0)) == -1 ||
+ fcntl(s, F_SETFL, xflags | O_NONBLOCK) == -1)
+ goto out;
+ return s;
+out:
+ close(s);
+ return -1;
+#endif
+}
--- /dev/null
+/* $NetBSD: if.h,v 1.12 2015/08/21 10:39:00 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef INTERFACE_H
+#define INTERFACE_H
+
+#include <net/if.h>
+#ifdef __FreeBSD__
+#include <net/if_var.h>
+#endif
+#include <net/route.h> /* for RTM_ADD et all */
+#include <netinet/in.h>
+#ifdef BSD
+#include <netinet/in_var.h> /* for IN_IFF_TENTATIVE et all */
+#endif
+
+#if defined(__minix)
+/*
+ * These flags are used for IPv4 autoconfiguration (RFC 3927). The MINIX 3
+ * TCP/IP service does not support IPv4 autoconfiguration, because lwIP's
+ * AUTOIP implementation is all-or-nothing by nature: either it implements the
+ * whole thing fully itself, or no support is present at all. dhcpcd(8) needs
+ * a more hybrid implementation if at all. It appears that by undefining the
+ * following flags, dhcpcd(8) will assume that no support is present for them
+ * in the operating system, and do everything itself instead, which is exactly
+ * what we want.
+ */
+#undef IN_IFF_TENTATIVE
+#undef IN_IFF_DUPLICATED
+#undef IN_IFF_DETACHED
+#undef IN_IFF_TRYTENTATIVE
+#undef IN_IFF_NOTREADY
+#endif /* defined(__minix) */
+
+/* Some systems have route metrics.
+ * OpenBSD route priority is not this. */
+#ifndef HAVE_ROUTE_METRIC
+# if defined(__linux__)
+# define HAVE_ROUTE_METRIC 1
+# endif
+#endif
+
+/* Some systems have in-built IPv4 DAD.
+ * However, we need them to do DAD at carrier up as well. */
+#ifdef IN_IFF_TENTATIVE
+# ifdef __NetBSD__
+# define NOCARRIER_PRESERVE_IP
+# endif
+#endif
+
+#include "config.h"
+#include "dhcpcd.h"
+#include "ipv4.h"
+#include "ipv6.h"
+
+#define EUI64_ADDR_LEN 8
+#define INFINIBAND_ADDR_LEN 20
+
+/* Linux 2.4 doesn't define this */
+#ifndef ARPHRD_IEEE1394
+# define ARPHRD_IEEE1394 24
+#endif
+
+/* The BSD's don't define this yet */
+#ifndef ARPHRD_INFINIBAND
+# define ARPHRD_INFINIBAND 32
+#endif
+
+/* Work out if we have a private address or not
+ * 10/8
+ * 172.16/12
+ * 192.168/16
+ */
+#ifndef IN_PRIVATE
+# define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \
+ ((addr & 0xfff00000) == 0xac100000) || \
+ ((addr & IN_CLASSB_NET) == 0xc0a80000))
+#endif
+
+#define RAW_EOF 1 << 0
+#define RAW_PARTIALCSUM 2 << 0
+
+int if_setflag(struct interface *ifp, short flag);
+#define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING))
+struct if_head *if_discover(struct dhcpcd_ctx *, int, char * const *);
+struct interface *if_find(struct if_head *, const char *);
+struct interface *if_findindex(struct if_head *, unsigned int);
+void if_sortinterfaces(struct dhcpcd_ctx *);
+void if_free(struct interface *);
+int if_domtu(const struct interface *, short int);
+#define if_getmtu(ifp) if_domtu((ifp), 0)
+#define if_setmtu(ifp, mtu) if_domtu((ifp), (mtu))
+int if_carrier(struct interface *);
+
+/* The below functions are provided by if-KERNEL.c */
+int if_conf(struct interface *);
+int if_init(struct interface *);
+int if_getssid(struct interface *);
+int if_vimaster(const struct dhcpcd_ctx *ctx, const char *);
+int if_opensockets(struct dhcpcd_ctx *);
+int if_openlinksocket(void);
+int if_managelink(struct dhcpcd_ctx *);
+
+/* dhcpcd uses the same routing flags as BSD.
+ * If the platform doesn't use these flags,
+ * map them in the platform interace file. */
+#ifndef RTM_ADD
+#define RTM_ADD 0x1 /* Add Route */
+#define RTM_DELETE 0x2 /* Delete Route */
+#define RTM_CHANGE 0x3 /* Change Metrics or flags */
+#define RTM_GET 0x4 /* Report Metrics */
+#endif
+
+#ifdef INET
+extern const char *if_pfname;
+int if_openrawsocket(struct interface *, uint16_t);
+ssize_t if_sendrawpacket(const struct interface *,
+ uint16_t, const void *, size_t);
+ssize_t if_readrawpacket(struct interface *, uint16_t, void *, size_t, int *);
+
+int if_address(const struct interface *,
+ const struct in_addr *, const struct in_addr *,
+ const struct in_addr *, int);
+#define if_addaddress(ifp, addr, net, brd) \
+ if_address(ifp, addr, net, brd, 1)
+#define if_deladdress(ifp, addr, net) \
+ if_address(ifp, addr, net, NULL, -1)
+
+int if_addrflags(const struct in_addr *, const struct interface *);
+
+int if_route(unsigned char, const struct rt *rt);
+int if_initrt(struct interface *);
+#endif
+
+#ifdef INET6
+int if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *, int);
+#ifdef IPV6_MANAGETEMPADDR
+int ip6_use_tempaddr(const char *ifname);
+int ip6_temp_preferred_lifetime(const char *ifname);
+int ip6_temp_valid_lifetime(const char *ifname);
+#else
+#define ip6_use_tempaddr(a) (0)
+#endif
+
+int if_address6(const struct ipv6_addr *, int);
+#define if_addaddress6(a) if_address6(a, 1)
+#define if_deladdress6(a) if_address6(a, -1)
+
+int if_addrflags6(const struct in6_addr *, const struct interface *);
+int if_getlifetime6(struct ipv6_addr *);
+
+int if_route6(unsigned char, const struct rt6 *rt);
+int if_initrt6(struct interface *);
+#else
+#define if_checkipv6(a, b, c) (-1)
+#endif
+
+int if_machinearch(char *, size_t);
+int xsocket(int, int, int, int);
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: ipv4.c,v 1.17 2015/08/21 10:39:00 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/socket.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "arp.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "dhcp.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "script.h"
+
+#define IPV4_LOOPBACK_ROUTE
+#if defined(__linux__) || (defined(BSD) && defined(RTF_LOCAL))
+/* Linux has had loopback routes in the local table since 2.2 */
+#undef IPV4_LOOPBACK_ROUTE
+#endif
+
+uint8_t
+inet_ntocidr(struct in_addr address)
+{
+ uint8_t cidr = 0;
+ uint32_t mask = htonl(address.s_addr);
+
+ while (mask) {
+ cidr++;
+ mask <<= 1;
+ }
+ return cidr;
+}
+
+int
+inet_cidrtoaddr(int cidr, struct in_addr *addr)
+{
+ int ocets;
+
+ if (cidr < 1 || cidr > 32) {
+ errno = EINVAL;
+ return -1;
+ }
+ ocets = (cidr + 7) / NBBY;
+
+ addr->s_addr = 0;
+ if (ocets > 0) {
+ memset(&addr->s_addr, 255, (size_t)ocets - 1);
+ memset((unsigned char *)&addr->s_addr + (ocets - 1),
+ (256 - (1 << (32 - cidr) % NBBY)), 1);
+ }
+
+ return 0;
+}
+
+uint32_t
+ipv4_getnetmask(uint32_t addr)
+{
+ uint32_t dst;
+
+ if (addr == 0)
+ return 0;
+
+ dst = htonl(addr);
+ if (IN_CLASSA(dst))
+ return ntohl(IN_CLASSA_NET);
+ if (IN_CLASSB(dst))
+ return ntohl(IN_CLASSB_NET);
+ if (IN_CLASSC(dst))
+ return ntohl(IN_CLASSC_NET);
+
+ return 0;
+}
+
+struct ipv4_addr *
+ipv4_iffindaddr(struct interface *ifp,
+ const struct in_addr *addr, const struct in_addr *net)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ state = IPV4_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if ((addr == NULL || ap->addr.s_addr == addr->s_addr) &&
+ (net == NULL || ap->net.s_addr == net->s_addr))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+struct ipv4_addr *
+ipv4_iffindlladdr(struct interface *ifp)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ state = IPV4_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (IN_LINKLOCAL(htonl(ap->addr.s_addr)))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+struct ipv4_addr *
+ipv4_findaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr)
+{
+ struct interface *ifp;
+ struct ipv4_addr *ap;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ ap = ipv4_iffindaddr(ifp, addr, NULL);
+ if (ap)
+ return ap;
+ }
+ return NULL;
+}
+
+int
+ipv4_hasaddr(const struct interface *ifp)
+{
+ const struct dhcp_state *dstate;
+ const struct ipv4ll_state *istate;
+
+ dstate = D_CSTATE(ifp);
+ istate = IPV4LL_CSTATE(ifp);
+ return ((dstate &&
+ dstate->added == STATE_ADDED &&
+ dstate->addr.s_addr != INADDR_ANY) ||
+ (istate && istate->addr.s_addr != INADDR_ANY));
+}
+
+void
+ipv4_freeroutes(struct rt_head *rts)
+{
+
+ if (rts) {
+ ipv4_freerts(rts);
+ free(rts);
+ }
+}
+
+int
+ipv4_init(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->ipv4_routes == NULL) {
+ ctx->ipv4_routes = malloc(sizeof(*ctx->ipv4_routes));
+ if (ctx->ipv4_routes == NULL)
+ return -1;
+ TAILQ_INIT(ctx->ipv4_routes);
+ }
+ if (ctx->ipv4_kroutes == NULL) {
+ ctx->ipv4_kroutes = malloc(sizeof(*ctx->ipv4_kroutes));
+ if (ctx->ipv4_kroutes == NULL)
+ return -1;
+ TAILQ_INIT(ctx->ipv4_kroutes);
+ }
+ return 0;
+}
+
+int
+ipv4_protocol_fd(const struct interface *ifp, uint16_t protocol)
+{
+
+ if (protocol == ETHERTYPE_ARP) {
+ const struct iarp_state *istate;
+
+ istate = ARP_CSTATE(ifp);
+ assert(istate != NULL);
+ return istate->fd;
+ } else {
+ const struct dhcp_state *dstate;
+
+ dstate = D_CSTATE(ifp);
+ assert(dstate != NULL);
+ return dstate->raw_fd;
+ }
+}
+
+/* Interface comparer for working out ordering. */
+int
+ipv4_ifcmp(const struct interface *si, const struct interface *ti)
+{
+ const struct dhcp_state *sis, *tis;
+
+ sis = D_CSTATE(si);
+ tis = D_CSTATE(ti);
+ if (sis && !tis)
+ return -1;
+ if (!sis && tis)
+ return 1;
+ if (!sis && !tis)
+ return 0;
+ /* If one has a lease and the other not, it takes precedence. */
+ if (sis->new && !tis->new)
+ return -1;
+ if (!sis->new && tis->new)
+ return 1;
+ /* Always prefer proper leases */
+ if (!(sis->added & STATE_FAKE) && (sis->added & STATE_FAKE))
+ return -1;
+ if ((sis->added & STATE_FAKE) && !(sis->added & STATE_FAKE))
+ return 1;
+ /* If we are either, they neither have a lease, or they both have.
+ * We need to check for IPv4LL and make it non-preferred. */
+ if (sis->new && tis->new) {
+ int sill = (sis->new->cookie == htonl(MAGIC_COOKIE));
+ int till = (tis->new->cookie == htonl(MAGIC_COOKIE));
+ if (sill && !till)
+ return -1;
+ if (!sill && till)
+ return 1;
+ }
+ return 0;
+}
+
+static struct rt *
+find_route(struct rt_head *rts, const struct rt *r, const struct rt *srt)
+{
+ struct rt *rt;
+
+ if (rts == NULL)
+ return NULL;
+ TAILQ_FOREACH(rt, rts, next) {
+ if (rt->dest.s_addr == r->dest.s_addr &&
+#ifdef HAVE_ROUTE_METRIC
+ (srt || (r->iface == NULL || rt->iface == NULL ||
+ rt->iface->metric == r->iface->metric)) &&
+#endif
+ (!srt || srt != rt) &&
+ rt->net.s_addr == r->net.s_addr)
+ return rt;
+ }
+ return NULL;
+}
+
+static void
+desc_route(const char *cmd, const struct rt *rt)
+{
+ char addr[sizeof("000.000.000.000") + 1];
+ struct dhcpcd_ctx *ctx = rt->iface ? rt->iface->ctx : NULL;
+ const char *ifname = rt->iface ? rt->iface->name : NULL;
+
+ strlcpy(addr, inet_ntoa(rt->dest), sizeof(addr));
+ if (rt->net.s_addr == htonl(INADDR_BROADCAST) &&
+ rt->gate.s_addr == htonl(INADDR_ANY))
+ logger(ctx, LOG_INFO, "%s: %s host route to %s",
+ ifname, cmd, addr);
+ else if (rt->net.s_addr == htonl(INADDR_BROADCAST))
+ logger(ctx, LOG_INFO, "%s: %s host route to %s via %s",
+ ifname, cmd, addr, inet_ntoa(rt->gate));
+ else if (rt->dest.s_addr == htonl(INADDR_ANY) &&
+ rt->net.s_addr == htonl(INADDR_ANY) &&
+ rt->gate.s_addr == htonl(INADDR_ANY))
+ logger(ctx, LOG_INFO, "%s: %s default route",
+ ifname, cmd);
+ else if (rt->gate.s_addr == htonl(INADDR_ANY))
+ logger(ctx, LOG_INFO, "%s: %s route to %s/%d",
+ ifname, cmd, addr, inet_ntocidr(rt->net));
+ else if (rt->dest.s_addr == htonl(INADDR_ANY) &&
+ rt->net.s_addr == htonl(INADDR_ANY))
+ logger(ctx, LOG_INFO, "%s: %s default route via %s",
+ ifname, cmd, inet_ntoa(rt->gate));
+ else
+ logger(ctx, LOG_INFO, "%s: %s route to %s/%d via %s",
+ ifname, cmd, addr, inet_ntocidr(rt->net),
+ inet_ntoa(rt->gate));
+}
+
+static struct rt *
+ipv4_findrt(struct dhcpcd_ctx *ctx, const struct rt *rt, int flags)
+{
+ struct rt *r;
+
+ if (ctx->ipv4_kroutes == NULL)
+ return NULL;
+ TAILQ_FOREACH(r, ctx->ipv4_kroutes, next) {
+ if (rt->dest.s_addr == r->dest.s_addr &&
+#ifdef HAVE_ROUTE_METRIC
+ rt->iface == r->iface &&
+ (!flags || rt->metric == r->metric) &&
+#else
+ (!flags || rt->iface == r->iface) &&
+#endif
+ rt->net.s_addr == r->net.s_addr)
+ return r;
+ }
+ return NULL;
+}
+
+void
+ipv4_freerts(struct rt_head *routes)
+{
+ struct rt *rt;
+
+ while ((rt = TAILQ_FIRST(routes))) {
+ TAILQ_REMOVE(routes, rt, next);
+ free(rt);
+ }
+}
+
+/* If something other than dhcpcd removes a route,
+ * we need to remove it from our internal table. */
+int
+ipv4_handlert(struct dhcpcd_ctx *ctx, int cmd, struct rt *rt)
+{
+ struct rt *f;
+
+ if (ctx->ipv4_kroutes == NULL)
+ return 0;
+
+ f = ipv4_findrt(ctx, rt, 1);
+ switch (cmd) {
+ case RTM_ADD:
+ if (f == NULL) {
+ if ((f = malloc(sizeof(*f))) == NULL)
+ return -1;
+ *f = *rt;
+ TAILQ_INSERT_TAIL(ctx->ipv4_kroutes, f, next);
+ }
+ break;
+ case RTM_DELETE:
+ if (f) {
+ TAILQ_REMOVE(ctx->ipv4_kroutes, f, next);
+ free(f);
+ }
+
+ /* If we manage the route, remove it */
+ if ((f = find_route(ctx->ipv4_routes, rt, NULL))) {
+ desc_route("removing", f);
+ TAILQ_REMOVE(ctx->ipv4_routes, f, next);
+ free(f);
+ }
+ break;
+ }
+ return 0;
+}
+
+#define n_route(a) nc_route(NULL, a)
+#define c_route(a, b) nc_route(a, b)
+static int
+nc_route(struct rt *ort, struct rt *nrt)
+{
+ int change;
+
+ /* Don't set default routes if not asked to */
+ if (nrt->dest.s_addr == 0 &&
+ nrt->net.s_addr == 0 &&
+ !(nrt->iface->options->options & DHCPCD_GATEWAY))
+ return -1;
+
+ desc_route(ort == NULL ? "adding" : "changing", nrt);
+
+ change = 0;
+ if (ort == NULL) {
+ ort = ipv4_findrt(nrt->iface->ctx, nrt, 0);
+ if (ort &&
+ ((ort->flags & RTF_REJECT && nrt->flags & RTF_REJECT) ||
+ (ort->iface == nrt->iface &&
+#ifdef HAVE_ROUTE_METRIC
+ ort->metric == nrt->metric &&
+#endif
+ ort->gate.s_addr == nrt->gate.s_addr)))
+ {
+ if (ort->mtu == nrt->mtu)
+ return 0;
+ change = 1;
+ }
+ } else if (ort->state & STATE_FAKE && !(nrt->state & STATE_FAKE) &&
+ ort->iface == nrt->iface &&
+#ifdef HAVE_ROUTE_METRIC
+ ort->metric == nrt->metric &&
+#endif
+ ort->dest.s_addr == nrt->dest.s_addr &&
+ ort->net.s_addr == nrt->net.s_addr &&
+ ort->gate.s_addr == nrt->gate.s_addr)
+ {
+ if (ort->mtu == nrt->mtu)
+ return 0;
+ change = 1;
+ }
+
+#ifdef RTF_CLONING
+ /* BSD can set routes to be cloning routes.
+ * Cloned routes inherit the parent flags.
+ * As such, we need to delete and re-add the route to flush children
+ * to correct the flags. */
+ if (change && ort != NULL && ort->flags & RTF_CLONING)
+ change = 0;
+#endif
+
+ if (change) {
+ if (if_route(RTM_CHANGE, nrt) == 0)
+ return 0;
+ if (errno != ESRCH)
+ logger(nrt->iface->ctx, LOG_ERR, "if_route (CHG): %m");
+ }
+
+#ifdef HAVE_ROUTE_METRIC
+ /* With route metrics, we can safely add the new route before
+ * deleting the old route. */
+ if (if_route(RTM_ADD, nrt) == 0) {
+ if (ort && if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
+ logger(nrt->iface->ctx, LOG_ERR, "if_route (DEL): %m");
+ return 0;
+ }
+
+ /* If the kernel claims the route exists we need to rip out the
+ * old one first. */
+ if (errno != EEXIST || ort == NULL)
+ goto logerr;
+#endif
+
+ /* No route metrics, we need to delete the old route before
+ * adding the new one. */
+ if (ort && if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
+ logger(nrt->iface->ctx, LOG_ERR, "if_route (DEL): %m");
+ if (if_route(RTM_ADD, nrt) == 0)
+ return 0;
+#ifdef HAVE_ROUTE_METRIC
+logerr:
+#endif
+ logger(nrt->iface->ctx, LOG_ERR, "if_route (ADD): %m");
+ return -1;
+}
+
+static int
+d_route(struct rt *rt)
+{
+ int retval;
+
+ desc_route("deleting", rt);
+ retval = if_route(RTM_DELETE, rt);
+ if (retval != 0 && errno != ENOENT && errno != ESRCH)
+ logger(rt->iface->ctx, LOG_ERR,
+ "%s: if_delroute: %m", rt->iface->name);
+ return retval;
+}
+
+static struct rt_head *
+add_subnet_route(struct rt_head *rt, const struct interface *ifp)
+{
+ const struct dhcp_state *s;
+ struct rt *r;
+
+ if (rt == NULL) /* earlier malloc failed */
+ return NULL;
+
+ s = D_CSTATE(ifp);
+ /* Don't create a subnet route for these addresses */
+ if (s->net.s_addr == INADDR_ANY)
+ return rt;
+#ifndef BSD
+ /* BSD adds a route in this instance */
+ if (s->net.s_addr == INADDR_BROADCAST)
+ return rt;
+#endif
+
+ if ((r = calloc(1, sizeof(*r))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(rt);
+ return NULL;
+ }
+ r->dest.s_addr = s->addr.s_addr & s->net.s_addr;
+ r->net.s_addr = s->net.s_addr;
+ r->gate.s_addr = INADDR_ANY;
+ r->mtu = dhcp_get_mtu(ifp);
+ r->src = s->addr;
+
+ TAILQ_INSERT_HEAD(rt, r, next);
+ return rt;
+}
+
+#ifdef IPV4_LOOPBACK_ROUTE
+static struct rt_head *
+add_loopback_route(struct rt_head *rt, const struct interface *ifp)
+{
+ struct rt *r;
+ const struct dhcp_state *s;
+
+ if (rt == NULL) /* earlier malloc failed */
+ return NULL;
+
+ s = D_CSTATE(ifp);
+ if (s->addr.s_addr == INADDR_ANY)
+ return rt;
+
+ if ((r = calloc(1, sizeof(*r))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(rt);
+ return NULL;
+ }
+ r->dest = s->addr;
+ r->net.s_addr = INADDR_BROADCAST;
+ r->gate.s_addr = htonl(INADDR_LOOPBACK);
+ r->mtu = dhcp_get_mtu(ifp);
+ r->src = s->addr;
+ TAILQ_INSERT_HEAD(rt, r, next);
+ return rt;
+}
+#endif
+
+static struct rt_head *
+get_routes(struct interface *ifp)
+{
+ struct rt_head *nrt;
+ struct rt *rt, *r = NULL;
+ const struct dhcp_state *state;
+
+ if (ifp->options->routes && TAILQ_FIRST(ifp->options->routes)) {
+ if ((nrt = malloc(sizeof(*nrt))) == NULL)
+ return NULL;
+ TAILQ_INIT(nrt);
+ TAILQ_FOREACH(rt, ifp->options->routes, next) {
+ if (rt->gate.s_addr == 0)
+ break;
+ if ((r = calloc(1, sizeof(*r))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(nrt);
+ return NULL;
+ }
+ memcpy(r, rt, sizeof(*r));
+ TAILQ_INSERT_TAIL(nrt, r, next);
+ }
+ } else
+ nrt = dhcp_get_routes(ifp);
+
+ /* Copy our address as the source address */
+ if (nrt) {
+ state = D_CSTATE(ifp);
+ TAILQ_FOREACH(rt, nrt, next) {
+ rt->src = state->addr;
+ }
+ }
+
+ return nrt;
+}
+
+static struct rt_head *
+add_destination_route(struct rt_head *rt, const struct interface *ifp)
+{
+ struct rt *r;
+ const struct dhcp_state *state;
+
+ if (rt == NULL || /* failed a malloc earlier probably */
+ !(ifp->flags & IFF_POINTOPOINT) ||
+ !has_option_mask(ifp->options->dstmask, DHO_ROUTER) ||
+ (state = D_CSTATE(ifp)) == NULL)
+ return rt;
+
+ if ((r = calloc(1, sizeof(*r))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(rt);
+ return NULL;
+ }
+ r->dest.s_addr = INADDR_ANY;
+ r->net.s_addr = INADDR_ANY;
+ r->gate.s_addr = state->dst.s_addr;
+ r->mtu = dhcp_get_mtu(ifp);
+ r->src = state->addr;
+ TAILQ_INSERT_HEAD(rt, r, next);
+ return rt;
+}
+
+/* We should check to ensure the routers are on the same subnet
+ * OR supply a host route. If not, warn and add a host route. */
+static struct rt_head *
+add_router_host_route(struct rt_head *rt, const struct interface *ifp)
+{
+ struct rt *rtp, *rtn;
+ const char *cp, *cp2, *cp3, *cplim;
+ struct if_options *ifo;
+ const struct dhcp_state *state;
+
+ if (rt == NULL) /* earlier malloc failed */
+ return NULL;
+
+ TAILQ_FOREACH(rtp, rt, next) {
+ if (rtp->dest.s_addr != INADDR_ANY)
+ continue;
+ /* Scan for a route to match */
+ TAILQ_FOREACH(rtn, rt, next) {
+ if (rtn == rtp)
+ break;
+ /* match host */
+ if (rtn->dest.s_addr == rtp->gate.s_addr)
+ break;
+ /* match subnet */
+ cp = (const char *)&rtp->gate.s_addr;
+ cp2 = (const char *)&rtn->dest.s_addr;
+ cp3 = (const char *)&rtn->net.s_addr;
+ cplim = cp3 + sizeof(rtn->net.s_addr);
+ while (cp3 < cplim) {
+ if ((*cp++ ^ *cp2++) & *cp3++)
+ break;
+ }
+ if (cp3 == cplim)
+ break;
+ }
+ if (rtn != rtp)
+ continue;
+ if ((state = D_CSTATE(ifp)) == NULL)
+ continue;
+ ifo = ifp->options;
+ if (ifp->flags & IFF_NOARP) {
+ if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) &&
+ !(state->added & STATE_FAKE))
+ {
+ ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED;
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: forcing router %s through interface",
+ ifp->name, inet_ntoa(rtp->gate));
+ }
+ rtp->gate.s_addr = 0;
+ continue;
+ }
+ if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) &&
+ !(state->added & STATE_FAKE))
+ {
+ ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED;
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: router %s requires a host route",
+ ifp->name, inet_ntoa(rtp->gate));
+ }
+ if ((rtn = calloc(1, sizeof(*rtn))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ ipv4_freeroutes(rt);
+ return NULL;
+ }
+ rtn->dest.s_addr = rtp->gate.s_addr;
+ rtn->net.s_addr = htonl(INADDR_BROADCAST);
+ rtn->gate.s_addr = htonl(INADDR_ANY);
+ rtn->mtu = dhcp_get_mtu(ifp);
+ rtn->src = state->addr;
+ TAILQ_INSERT_BEFORE(rtp, rtn, next);
+ }
+ return rt;
+}
+
+static int
+ipv4_doroute(struct rt *rt, struct rt_head *nrs)
+{
+ const struct dhcp_state *state;
+ struct rt *or;
+
+ state = D_CSTATE(rt->iface);
+ rt->state = state->added & STATE_FAKE;
+#ifdef HAVE_ROUTE_METRIC
+ rt->metric = rt->iface->metric;
+#endif
+ /* Is this route already in our table? */
+ if ((find_route(nrs, rt, NULL)) != NULL)
+ return 0;
+ /* Do we already manage it? */
+ if ((or = find_route(rt->iface->ctx->ipv4_routes, rt, NULL))) {
+ if (state->added & STATE_FAKE)
+ return 0;
+ if (or->state & STATE_FAKE ||
+ or->iface != rt->iface ||
+#ifdef HAVE_ROUTE_METRIC
+ rt->metric != or->metric ||
+#endif
+ rt->src.s_addr != or->src.s_addr ||
+ rt->gate.s_addr != or->gate.s_addr ||
+ rt->mtu != or->mtu)
+ {
+ if (c_route(or, rt) != 0)
+ return 0;
+ }
+ TAILQ_REMOVE(rt->iface->ctx->ipv4_routes, or, next);
+ free(or);
+ } else {
+ if (state->added & STATE_FAKE) {
+ if ((or = ipv4_findrt(rt->iface->ctx, rt, 1)) == NULL)
+ return 0;
+ rt->iface = or->iface;
+ rt->gate.s_addr = or->gate.s_addr;
+#ifdef HAVE_ROUTE_METRIC
+ rt->metric = or->metric;
+#endif
+ rt->mtu = or->mtu;
+ rt->flags = or->flags;
+ } else {
+ if (n_route(rt) != 0)
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void
+ipv4_buildroutes(struct dhcpcd_ctx *ctx)
+{
+ struct rt_head *nrs, *dnr;
+ struct rt *rt, *rtn;
+ struct interface *ifp;
+ const struct dhcp_state *state;
+ int has_default;
+
+ /* We need to have the interfaces in the correct order to ensure
+ * our routes are managed correctly. */
+ if_sortinterfaces(ctx);
+
+ if ((nrs = malloc(sizeof(*nrs))) == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ TAILQ_INIT(nrs);
+
+ has_default = 0;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ state = D_CSTATE(ifp);
+ if (state != NULL && state->new != NULL && state->added) {
+ dnr = get_routes(ifp);
+ dnr = add_subnet_route(dnr, ifp);
+ } else
+ dnr = NULL;
+ if ((rt = ipv4ll_subnet_route(ifp)) != NULL) {
+ if (dnr == NULL) {
+ if ((dnr = malloc(sizeof(*dnr))) == NULL) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: malloc %m", __func__);
+ continue;
+ }
+ TAILQ_INIT(dnr);
+ }
+ TAILQ_INSERT_HEAD(dnr, rt, next);
+ }
+ if (dnr == NULL)
+ continue;
+#ifdef IPV4_LOOPBACK_ROUTE
+ dnr = add_loopback_route(dnr, ifp);
+#endif
+ if (ifp->options->options & DHCPCD_GATEWAY) {
+ dnr = add_router_host_route(dnr, ifp);
+ dnr = add_destination_route(dnr, ifp);
+ }
+ if (dnr == NULL)
+ continue;
+ TAILQ_FOREACH_SAFE(rt, dnr, next, rtn) {
+ rt->iface = ifp;
+ if (ipv4_doroute(rt, nrs) == 1) {
+ TAILQ_REMOVE(dnr, rt, next);
+ TAILQ_INSERT_TAIL(nrs, rt, next);
+ if (rt->dest.s_addr == INADDR_ANY)
+ has_default = 1;
+ }
+ }
+ ipv4_freeroutes(dnr);
+ }
+
+ /* If we don't manage a default route, grab one without a
+ * gateway for any IPv4LL enabled interfaces. */
+ if (!has_default) {
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if ((rt = ipv4ll_default_route(ifp)) != NULL) {
+ if (ipv4_doroute(rt, nrs) == 1)
+ TAILQ_INSERT_TAIL(nrs, rt, next);
+ else
+ free(rt);
+ }
+ }
+ }
+
+ /* Remove old routes we used to manage */
+ if (ctx->ipv4_routes) {
+ TAILQ_FOREACH(rt, ctx->ipv4_routes, next) {
+ if (find_route(nrs, rt, NULL) == NULL &&
+ (rt->iface->options->options &
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT))
+ d_route(rt);
+ }
+ }
+ ipv4_freeroutes(ctx->ipv4_routes);
+ ctx->ipv4_routes = nrs;
+}
+
+int
+ipv4_deladdr(struct interface *ifp,
+ const struct in_addr *addr, const struct in_addr *net, int keeparp)
+{
+ struct dhcp_state *dstate;
+ int r;
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+ struct arp_state *astate;
+
+ logger(ifp->ctx, LOG_DEBUG, "%s: deleting IP address %s/%d",
+ ifp->name, inet_ntoa(*addr), inet_ntocidr(*net));
+
+ r = if_deladdress(ifp, addr, net);
+ if (r == -1 && errno != EADDRNOTAVAIL && errno != ENXIO &&
+ errno != ENODEV)
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__);
+
+ if (!keeparp && (astate = arp_find(ifp, addr)) != NULL)
+ arp_free(astate);
+
+ state = IPV4_STATE(ifp);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->addr.s_addr == addr->s_addr &&
+ ap->net.s_addr == net->s_addr)
+ {
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ free(ap);
+ break;
+ }
+ }
+
+ /* Have to do this last incase the function arguments
+ * were these very pointers. */
+ dstate = D_STATE(ifp);
+ if (dstate &&
+ dstate->addr.s_addr == addr->s_addr &&
+ dstate->net.s_addr == net->s_addr)
+ {
+ dstate->added = 0;
+ dstate->addr.s_addr = 0;
+ dstate->net.s_addr = 0;
+ }
+ return r;
+}
+
+static int
+delete_address(struct interface *ifp)
+{
+ int r;
+ struct if_options *ifo;
+ struct dhcp_state *state;
+
+ state = D_STATE(ifp);
+ ifo = ifp->options;
+ if (ifo->options & DHCPCD_INFORM ||
+ (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0))
+ return 0;
+ r = ipv4_deladdr(ifp, &state->addr, &state->net, 0);
+ return r;
+}
+
+struct ipv4_state *
+ipv4_getstate(struct interface *ifp)
+{
+ struct ipv4_state *state;
+
+ state = IPV4_STATE(ifp);
+ if (state == NULL) {
+ ifp->if_data[IF_DATA_IPV4] = malloc(sizeof(*state));
+ state = IPV4_STATE(ifp);
+ if (state == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ TAILQ_INIT(&state->addrs);
+ TAILQ_INIT(&state->routes);
+#ifdef BSD
+ state->buffer_size = state->buffer_len = state->buffer_pos = 0;
+ state->buffer = NULL;
+#endif
+ }
+ return state;
+}
+
+struct ipv4_addr *
+ipv4_addaddr(struct interface *ifp, const struct in_addr *addr,
+ const struct in_addr *mask, const struct in_addr *bcast)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ia;
+
+ if ((state = ipv4_getstate(ifp)) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: ipv4_getstate: %m", __func__);
+ return NULL;
+ }
+ if (ifp->options->options & DHCPCD_NOALIAS) {
+ struct ipv4_addr *ian;
+
+ TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ian) {
+ if (ia->addr.s_addr != addr->s_addr)
+ ipv4_deladdr(ifp, &ia->addr, &ia->net, 0);
+ }
+ }
+
+ if ((ia = malloc(sizeof(*ia))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+
+ logger(ifp->ctx, LOG_DEBUG, "%s: adding IP address %s/%d",
+ ifp->name, inet_ntoa(*addr), inet_ntocidr(*mask));
+ if (if_addaddress(ifp, addr, mask, bcast) == -1) {
+ if (errno != EEXIST)
+ logger(ifp->ctx, LOG_ERR, "%s: if_addaddress: %m",
+ __func__);
+ free(ia);
+ return NULL;
+ }
+
+ ia->iface = ifp;
+ ia->addr = *addr;
+ ia->net = *mask;
+#ifdef IN_IFF_TENTATIVE
+ ia->addr_flags = IN_IFF_TENTATIVE;
+#endif
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ return ia;
+}
+
+static int
+ipv4_daddaddr(struct interface *ifp, const struct dhcp_lease *lease)
+{
+ struct dhcp_state *state;
+
+ if (ipv4_addaddr(ifp, &lease->addr, &lease->net, &lease->brd) == NULL)
+ return -1;
+
+ state = D_STATE(ifp);
+ state->added = STATE_ADDED;
+
+ state->addr.s_addr = lease->addr.s_addr;
+ state->net.s_addr = lease->net.s_addr;
+
+ return 0;
+}
+
+int
+ipv4_preferanother(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp), *nstate;
+ struct interface *ifn;
+ int preferred;
+
+ if (state == NULL)
+ return 0;
+
+ preferred = 0;
+ if (!state->added)
+ goto out;
+
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ifn == ifp)
+ break; /* We are already the most preferred */
+ nstate = D_STATE(ifn);
+ if (nstate && !nstate->added &&
+ nstate->lease.addr.s_addr == state->addr.s_addr)
+ {
+ preferred = 1;
+ delete_address(ifp);
+ if (ifn->options->options & DHCPCD_ARP)
+ dhcp_bind(ifn);
+ else {
+ ipv4_daddaddr(ifn, &nstate->lease);
+ nstate->added = STATE_ADDED;
+ }
+ break;
+ }
+ }
+
+out:
+ ipv4_buildroutes(ifp->ctx);
+ return preferred;
+}
+
+void
+ipv4_applyaddr(void *arg)
+{
+ struct interface *ifp = arg, *ifn;
+ struct dhcp_state *state = D_STATE(ifp), *nstate;
+ struct dhcp_message *dhcp;
+ struct dhcp_lease *lease;
+ struct if_options *ifo = ifp->options;
+ struct ipv4_addr *ap;
+ int r;
+
+ if (state == NULL)
+ return;
+ dhcp = state->new;
+ lease = &state->lease;
+
+ if_sortinterfaces(ifp->ctx);
+ if (dhcp == NULL) {
+ if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT))
+ {
+ if (state->added && !ipv4_preferanother(ifp)) {
+ delete_address(ifp);
+ ipv4_buildroutes(ifp->ctx);
+ }
+ script_runreason(ifp, state->reason);
+ } else
+ ipv4_buildroutes(ifp->ctx);
+ return;
+ }
+
+ /* Ensure only one interface has the address */
+ r = 0;
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ifn == ifp) {
+ r = 1; /* past ourselves */
+ continue;
+ }
+ nstate = D_STATE(ifn);
+ if (nstate && nstate->added &&
+ nstate->addr.s_addr == lease->addr.s_addr)
+ {
+ if (r == 0) {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: preferring %s on %s",
+ ifp->name,
+ inet_ntoa(lease->addr),
+ ifn->name);
+ return;
+ }
+ logger(ifp->ctx, LOG_INFO, "%s: preferring %s on %s",
+ ifn->name,
+ inet_ntoa(lease->addr),
+ ifp->name);
+ ipv4_deladdr(ifn, &nstate->addr, &nstate->net, 0);
+ break;
+ }
+ }
+
+ /* Does another interface already have the address from a prior boot? */
+ if (ifn == NULL) {
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ifn == ifp)
+ continue;
+ ap = ipv4_iffindaddr(ifn, &lease->addr, NULL);
+ if (ap)
+ ipv4_deladdr(ifn, &ap->addr, &ap->net, 0);
+ }
+ }
+
+ /* If the netmask is different, delete the addresss */
+ ap = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ if (ap && ap->net.s_addr != lease->net.s_addr)
+ ipv4_deladdr(ifp, &ap->addr, &ap->net, 0);
+
+ if (ipv4_iffindaddr(ifp, &lease->addr, &lease->net))
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: IP address %s/%d already exists",
+ ifp->name, inet_ntoa(lease->addr),
+ inet_ntocidr(lease->net));
+ else {
+ r = ipv4_daddaddr(ifp, lease);
+ if (r == -1 && errno != EEXIST)
+ return;
+ }
+
+#ifdef IN_IFF_NOTUSEABLE
+ ap = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ if (ap == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: added address vanished",
+ ifp->name);
+ return;
+ } else if (ap->addr_flags & IN_IFF_NOTUSEABLE)
+ return;
+#endif
+
+ /* Delete the old address if different */
+ if (state->addr.s_addr != lease->addr.s_addr &&
+ state->addr.s_addr != 0 &&
+ ipv4_iffindaddr(ifp, &lease->addr, NULL))
+ delete_address(ifp);
+
+ state->added = STATE_ADDED;
+ state->addr.s_addr = lease->addr.s_addr;
+ state->net.s_addr = lease->net.s_addr;
+
+ /* Find any freshly added routes, such as the subnet route.
+ * We do this because we cannot rely on recieving the kernel
+ * notification right now via our link socket. */
+ if_initrt(ifp);
+ ipv4_buildroutes(ifp->ctx);
+ script_runreason(ifp, state->reason);
+
+ dhcpcd_daemonise(ifp->ctx);
+}
+
+void
+ipv4_handleifa(struct dhcpcd_ctx *ctx,
+ int cmd, struct if_head *ifs, const char *ifname,
+ const struct in_addr *addr, const struct in_addr *net,
+ const struct in_addr *dst, int flags)
+{
+ struct interface *ifp;
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ if (ifs == NULL)
+ ifs = ctx->ifaces;
+ if (ifs == NULL) {
+ errno = ESRCH;
+ return;
+ }
+ if (addr->s_addr == INADDR_ANY) {
+ errno = EINVAL;
+ return;
+ }
+ if ((ifp = if_find(ifs, ifname)) == NULL)
+ return;
+ if ((state = ipv4_getstate(ifp)) == NULL) {
+ errno = ENOENT;
+ return;
+ }
+
+ ap = ipv4_iffindaddr(ifp, addr, net);
+ if (cmd == RTM_NEWADDR) {
+ if (ap == NULL) {
+ if ((ap = malloc(sizeof(*ap))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ ap->iface = ifp;
+ ap->addr = *addr;
+ ap->net = *net;
+ if (dst)
+ ap->dst.s_addr = dst->s_addr;
+ else
+ ap->dst.s_addr = INADDR_ANY;
+ TAILQ_INSERT_TAIL(&state->addrs, ap, next);
+ }
+ ap->addr_flags = flags;
+ } else if (cmd == RTM_DELADDR) {
+ if (ap) {
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ free(ap);
+ }
+ }
+
+ dhcp_handleifa(cmd, ifp, addr, net, dst, flags);
+ arp_handleifa(cmd, ifp, addr, flags);
+}
+
+void
+ipv4_free(struct interface *ifp)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *addr;
+
+ if (ifp) {
+ state = IPV4_STATE(ifp);
+ if (state) {
+ while ((addr = TAILQ_FIRST(&state->addrs))) {
+ TAILQ_REMOVE(&state->addrs, addr, next);
+ free(addr);
+ }
+ ipv4_freerts(&state->routes);
+#ifdef BSD
+ free(state->buffer);
+#endif
+ free(state);
+ }
+ }
+}
+
+void
+ipv4_ctxfree(struct dhcpcd_ctx *ctx)
+{
+
+ ipv4_freeroutes(ctx->ipv4_routes);
+ ipv4_freeroutes(ctx->ipv4_kroutes);
+}
--- /dev/null
+/* $NetBSD: ipv4.h,v 1.13 2015/08/21 10:39:00 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef IPV4_H
+#define IPV4_H
+
+#include "dhcpcd.h"
+
+#ifdef IN_IFF_TENTATIVE
+#define IN_IFF_NOTUSEABLE \
+ (IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED)
+#endif
+
+/* Prefer our macro */
+#ifdef HTONL
+#undef HTONL
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define HTONL(A) (A)
+#elif BYTE_ORDER == LITTLE_ENDIAN
+#define HTONL(A) \
+ ((((uint32_t)(A) & 0xff000000) >> 24) | \
+ (((uint32_t)(A) & 0x00ff0000) >> 8) | \
+ (((uint32_t)(A) & 0x0000ff00) << 8) | \
+ (((uint32_t)(A) & 0x000000ff) << 24))
+#else
+#error Endian unknown
+#endif /* BYTE_ORDER */
+
+struct rt {
+ TAILQ_ENTRY(rt) next;
+ struct in_addr dest;
+ struct in_addr net;
+ struct in_addr gate;
+ const struct interface *iface;
+#ifdef HAVE_ROUTE_METRIC
+ unsigned int metric;
+#endif
+ unsigned int mtu;
+ struct in_addr src;
+ unsigned int flags;
+ unsigned int state;
+};
+TAILQ_HEAD(rt_head, rt);
+
+struct ipv4_addr {
+ TAILQ_ENTRY(ipv4_addr) next;
+ struct in_addr addr;
+ struct in_addr net;
+ struct in_addr dst;
+ struct interface *iface;
+ int addr_flags;
+};
+TAILQ_HEAD(ipv4_addrhead, ipv4_addr);
+
+struct ipv4_state {
+ struct ipv4_addrhead addrs;
+ struct rt_head routes;
+
+#ifdef BSD
+ /* Buffer for BPF */
+ size_t buffer_size, buffer_len, buffer_pos;
+ unsigned char *buffer;
+#endif
+};
+
+#define IPV4_STATE(ifp) \
+ ((struct ipv4_state *)(ifp)->if_data[IF_DATA_IPV4])
+#define IPV4_CSTATE(ifp) \
+ ((const struct ipv4_state *)(ifp)->if_data[IF_DATA_IPV4])
+
+#ifdef INET
+struct ipv4_state *ipv4_getstate(struct interface *);
+int ipv4_init(struct dhcpcd_ctx *);
+int ipv4_protocol_fd(const struct interface *, uint16_t);
+int ipv4_ifcmp(const struct interface *, const struct interface *);
+uint8_t inet_ntocidr(struct in_addr);
+int inet_cidrtoaddr(int, struct in_addr *);
+uint32_t ipv4_getnetmask(uint32_t);
+int ipv4_hasaddr(const struct interface *);
+
+#define STATE_ADDED 0x01
+#define STATE_FAKE 0x02
+
+void ipv4_buildroutes(struct dhcpcd_ctx *);
+int ipv4_deladdr(struct interface *, const struct in_addr *,
+ const struct in_addr *, int);
+int ipv4_preferanother(struct interface *);
+struct ipv4_addr *ipv4_addaddr(struct interface *,
+ const struct in_addr *, const struct in_addr *, const struct in_addr *);
+void ipv4_applyaddr(void *);
+int ipv4_handlert(struct dhcpcd_ctx *, int, struct rt *);
+void ipv4_freerts(struct rt_head *);
+
+struct ipv4_addr *ipv4_iffindaddr(struct interface *,
+ const struct in_addr *, const struct in_addr *);
+struct ipv4_addr *ipv4_iffindlladdr(struct interface *);
+struct ipv4_addr *ipv4_findaddr(struct dhcpcd_ctx *, const struct in_addr *);
+void ipv4_handleifa(struct dhcpcd_ctx *, int, struct if_head *, const char *,
+ const struct in_addr *, const struct in_addr *, const struct in_addr *,
+ int);
+
+void ipv4_freeroutes(struct rt_head *);
+
+void ipv4_free(struct interface *);
+void ipv4_ctxfree(struct dhcpcd_ctx *);
+#else
+#define ipv4_init(a) (-1)
+#define ipv4_sortinterfaces(a) {}
+#define ipv4_applyaddr(a) {}
+#define ipv4_freeroutes(a) {}
+#define ipv4_free(a) {}
+#define ipv4_ctxfree(a) {}
+#define ipv4_hasaddr(a) (0)
+#define ipv4_preferanother(a) (0)
+#endif
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: ipv4ll.c,v 1.12 2015/08/21 10:39:00 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <arpa/inet.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE 6
+#include "config.h"
+#include "arp.h"
+#include "common.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "script.h"
+
+const struct in_addr inaddr_llmask = { HTONL(LINKLOCAL_MASK) };
+const struct in_addr inaddr_llbcast = { HTONL(LINKLOCAL_BRDC) };
+
+static in_addr_t
+ipv4ll_pick_addr(const struct arp_state *astate)
+{
+ struct in_addr addr;
+ struct ipv4ll_state *istate;
+
+ istate = IPV4LL_STATE(astate->iface);
+ setstate(istate->randomstate);
+
+ do {
+ /* RFC 3927 Section 2.1 states that the first 256 and
+ * last 256 addresses are reserved for future use.
+ * See ipv4ll_start for why we don't use arc4_random. */
+ addr.s_addr = ntohl(LINKLOCAL_ADDR |
+ ((uint32_t)(random() % 0xFD00) + 0x0100));
+
+ /* No point using a failed address */
+ if (addr.s_addr == astate->failed.s_addr)
+ continue;
+ /* Ensure we don't have the address on another interface */
+ } while (ipv4_findaddr(astate->iface->ctx, &addr) != NULL);
+
+ /* Restore the original random state */
+ setstate(astate->iface->ctx->randomstate);
+
+ return addr.s_addr;
+}
+
+struct rt *
+ipv4ll_subnet_route(const struct interface *ifp)
+{
+ const struct ipv4ll_state *state;
+ struct rt *rt;
+
+ assert(ifp != NULL);
+ if ((state = IPV4LL_CSTATE(ifp)) == NULL ||
+ state->addr.s_addr == INADDR_ANY)
+ return NULL;
+
+ if ((rt = calloc(1, sizeof(*rt))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: malloc: %m", __func__);
+ return NULL;
+ }
+ rt->iface = ifp;
+ rt->dest.s_addr = state->addr.s_addr & inaddr_llmask.s_addr;
+ rt->net = inaddr_llmask;
+ rt->gate.s_addr = INADDR_ANY;
+ rt->src = state->addr;
+ return rt;
+}
+
+struct rt *
+ipv4ll_default_route(const struct interface *ifp)
+{
+ const struct ipv4ll_state *state;
+ struct rt *rt;
+
+ assert(ifp != NULL);
+ if ((state = IPV4LL_CSTATE(ifp)) == NULL ||
+ state->addr.s_addr == INADDR_ANY)
+ return NULL;
+
+ if ((rt = calloc(1, sizeof(*rt))) == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: malloc: %m", __func__);
+ return NULL;
+ }
+ rt->iface = ifp;
+ rt->dest.s_addr = INADDR_ANY;
+ rt->net.s_addr = INADDR_ANY;
+ rt->gate.s_addr = INADDR_ANY;
+ rt->src = state->addr;
+ return rt;
+}
+
+ssize_t
+ipv4ll_env(char **env, const char *prefix, const struct interface *ifp)
+{
+ const struct ipv4ll_state *state;
+ const char *pf = prefix == NULL ? "" : "_";
+ struct in_addr netnum;
+
+ assert(ifp != NULL);
+ if ((state = IPV4LL_CSTATE(ifp)) == NULL)
+ return 0;
+
+ if (env == NULL)
+ return 5;
+
+ /* Emulate a DHCP environment */
+ if (asprintf(&env[0], "%s%sip_address=%s",
+ prefix, pf, inet_ntoa(state->addr)) == -1)
+ return -1;
+ if (asprintf(&env[1], "%s%ssubnet_mask=%s",
+ prefix, pf, inet_ntoa(inaddr_llmask)) == -1)
+ return -1;
+ if (asprintf(&env[2], "%s%ssubnet_cidr=%d",
+ prefix, pf, inet_ntocidr(inaddr_llmask)) == -1)
+ return -1;
+ if (asprintf(&env[3], "%s%sbroadcast_address=%s",
+ prefix, pf, inet_ntoa(inaddr_llbcast)) == -1)
+ return -1;
+ netnum.s_addr = state->addr.s_addr & inaddr_llmask.s_addr;
+ if (asprintf(&env[4], "%s%snetwork_number=%s",
+ prefix, pf, inet_ntoa(netnum)) == -1)
+ return -1;
+ return 5;
+}
+
+static void
+ipv4ll_probed(struct arp_state *astate)
+{
+ struct interface *ifp;
+ struct ipv4ll_state *state;
+ struct ipv4_addr *ia;
+
+ assert(astate != NULL);
+ assert(astate->iface != NULL);
+
+ ifp = astate->iface;
+ state = IPV4LL_STATE(ifp);
+ assert(state != NULL);
+
+ ia = ipv4_iffindaddr(ifp, &astate->addr, &inaddr_llmask);
+#ifdef IN_IFF_NOTREADY
+ if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY)
+#endif
+ logger(ifp->ctx, LOG_INFO, "%s: using IPv4LL address %s",
+ ifp->name, inet_ntoa(astate->addr));
+ if (ia == NULL)
+ ia = ipv4_addaddr(ifp, &astate->addr,
+ &inaddr_llmask, &inaddr_llbcast);
+ if (ia == NULL)
+ return;
+#ifdef IN_IFF_NOTREADY
+ if (ia->addr_flags & IN_IFF_NOTREADY)
+ return;
+ logger(ifp->ctx, LOG_DEBUG, "%s: DAD completed for %s",
+ ifp->name, inet_ntoa(astate->addr));
+#endif
+ state->addr = astate->addr;
+ timespecclear(&state->defend);
+ if_initrt(ifp);
+ ipv4_buildroutes(ifp->ctx);
+ arp_announce(astate);
+ script_runreason(ifp, "IPV4LL");
+ dhcpcd_daemonise(ifp->ctx);
+}
+
+static void
+ipv4ll_announced(struct arp_state *astate)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(astate->iface);
+
+ state->conflicts = 0;
+ /* Need to keep the arp state so we can defend our IP. */
+}
+
+static void
+ipv4ll_probe(void *arg)
+{
+
+#ifdef IN_IFF_TENTATIVE
+ ipv4ll_probed(arg);
+#else
+ arp_probe(arg);
+#endif
+}
+
+static void
+ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct interface *ifp;
+ struct ipv4ll_state *state;
+ in_addr_t fail;
+
+ assert(astate != NULL);
+ assert(astate->iface != NULL);
+ ifp = astate->iface;
+ state = IPV4LL_STATE(ifp);
+ assert(state != NULL);
+
+ fail = 0;
+ /* RFC 3927 2.2.1, Probe Conflict Detection */
+ if (amsg == NULL ||
+ (amsg->sip.s_addr == astate->addr.s_addr ||
+ (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr)))
+ fail = astate->addr.s_addr;
+
+ /* RFC 3927 2.5, Conflict Defense */
+ if (IN_LINKLOCAL(ntohl(state->addr.s_addr)) &&
+ amsg && amsg->sip.s_addr == state->addr.s_addr)
+ fail = state->addr.s_addr;
+
+ if (fail == 0)
+ return;
+
+ astate->failed.s_addr = fail;
+ arp_report_conflicted(astate, amsg);
+
+ if (astate->failed.s_addr == state->addr.s_addr) {
+ struct timespec now, defend;
+
+ /* RFC 3927 Section 2.5 */
+ defend.tv_sec = state->defend.tv_sec + DEFEND_INTERVAL;
+ defend.tv_nsec = state->defend.tv_nsec;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (timespeccmp(&defend, &now, >)) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: IPv4LL %d second defence failed for %s",
+ ifp->name, DEFEND_INTERVAL,
+ inet_ntoa(state->addr));
+ ipv4_deladdr(ifp, &state->addr, &inaddr_llmask, 1);
+ state->down = 1;
+ script_runreason(ifp, "IPV4LL");
+ state->addr.s_addr = INADDR_ANY;
+ } else {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: defended IPv4LL address %s",
+ ifp->name, inet_ntoa(state->addr));
+ state->defend = now;
+ return;
+ }
+ }
+
+ arp_cancel(astate);
+ if (++state->conflicts == MAX_CONFLICTS)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: failed to acquire an IPv4LL address",
+ ifp->name);
+ astate->addr.s_addr = ipv4ll_pick_addr(astate);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ state->conflicts >= MAX_CONFLICTS ?
+ RATE_LIMIT_INTERVAL : PROBE_WAIT,
+ ipv4ll_probe, astate);
+}
+
+static void
+ipv4ll_arpfree(struct arp_state *astate)
+{
+ struct ipv4ll_state *state;
+
+ state = IPV4LL_STATE(astate->iface);
+ if (state->arp == astate)
+ state->arp = NULL;
+}
+
+void
+ipv4ll_start(void *arg)
+{
+ struct interface *ifp;
+ struct ipv4ll_state *state;
+ struct arp_state *astate;
+ struct ipv4_addr *ia;
+
+ assert(arg != NULL);
+ ifp = arg;
+ if ((state = IPV4LL_STATE(ifp)) == NULL) {
+ ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state));
+ if ((state = IPV4LL_STATE(ifp)) == NULL) {
+ syslog(LOG_ERR, "%s: calloc %m", __func__);
+ return;
+ }
+
+ state->addr.s_addr = INADDR_ANY;
+ }
+
+ if (state->arp != NULL)
+ return;
+
+ /* RFC 3927 Section 2.1 states that the random number generator
+ * SHOULD be seeded with a value derived from persistent information
+ * such as the IEEE 802 MAC address so that it usually picks
+ * the same address without persistent storage. */
+ if (state->conflicts == 0) {
+ unsigned int seed;
+ char *orig;
+
+ if (sizeof(seed) > ifp->hwlen) {
+ seed = 0;
+ memcpy(&seed, ifp->hwaddr, ifp->hwlen);
+ } else
+ memcpy(&seed, ifp->hwaddr + ifp->hwlen - sizeof(seed),
+ sizeof(seed));
+ orig = initstate(seed,
+ state->randomstate, sizeof(state->randomstate));
+
+ /* Save the original state. */
+ if (ifp->ctx->randomstate == NULL)
+ ifp->ctx->randomstate = orig;
+
+ /* Set back the original state until we need the seeded one. */
+ setstate(ifp->ctx->randomstate);
+ }
+
+ if ((astate = arp_new(ifp, NULL)) == NULL)
+ return;
+
+ state->arp = astate;
+ astate->probed_cb = ipv4ll_probed;
+ astate->announced_cb = ipv4ll_announced;
+ astate->conflicted_cb = ipv4ll_conflicted;
+ astate->free_cb = ipv4ll_arpfree;
+
+ /* Find an existing IPv4LL address and ensure we can work with it. */
+ ia = ipv4_iffindlladdr(ifp);
+#ifdef IN_IFF_TENTATIVE
+ if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) {
+ ipv4_deladdr(ifp, &ia->addr, &ia->net, 0);
+ ia = NULL;
+ }
+#endif
+ if (ia != NULL) {
+ astate->addr = ia->addr;
+#ifdef IN_IFF_TENTATIVE
+ if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) {
+ logger(ifp->ctx, LOG_INFO,
+ "%s: waiting for DAD to complete on %s",
+ ifp->name, inet_ntoa(ia->addr));
+ return;
+ }
+ logger(ifp->ctx, LOG_INFO, "%s: using IPv4LL address %s",
+ ifp->name, inet_ntoa(astate->addr));
+#endif
+ ipv4ll_probed(astate);
+ return;
+ }
+
+ logger(ifp->ctx, LOG_INFO, "%s: probing for an IPv4LL address",
+ ifp->name);
+ astate->addr.s_addr = ipv4ll_pick_addr(astate);
+#ifdef IN_IFF_TENTATIVE
+ ipv4ll_probed(astate);
+#else
+ arp_probe(astate);
+#endif
+}
+
+void
+ipv4ll_freedrop(struct interface *ifp, int drop)
+{
+ struct ipv4ll_state *state;
+ int dropped;
+
+ assert(ifp != NULL);
+ state = IPV4LL_STATE(ifp);
+ dropped = 0;
+
+ /* Free ARP state first because ipv4_deladdr might also ... */
+ if (state && state->arp) {
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp);
+ arp_free(state->arp);
+ state->arp = NULL;
+ }
+
+ if (drop && (ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP) {
+ struct ipv4_state *istate;
+
+ if (state && state->addr.s_addr != INADDR_ANY) {
+ ipv4_deladdr(ifp, &state->addr, &inaddr_llmask, 1);
+ state->addr.s_addr = INADDR_ANY;
+ dropped = 1;
+ }
+
+ /* Free any other link local addresses that might exist. */
+ if ((istate = IPV4_STATE(ifp)) != NULL) {
+ struct ipv4_addr *ia, *ian;
+
+ TAILQ_FOREACH_SAFE(ia, &istate->addrs, next, ian) {
+ if (IN_LINKLOCAL(ntohl(ia->addr.s_addr))) {
+ ipv4_deladdr(ifp, &ia->addr,
+ &ia->net, 0);
+ dropped = 1;
+ }
+ }
+ }
+ }
+
+ if (state) {
+ free(state);
+ ifp->if_data[IF_DATA_IPV4LL] = NULL;
+
+ if (dropped) {
+ ipv4_buildroutes(ifp->ctx);
+ script_runreason(ifp, "IPV4LL");
+ }
+ }
+}
--- /dev/null
+/* $NetBSD: ipv4ll.h,v 1.10 2015/08/21 10:39:00 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef IPV4LL_H
+#define IPV4LL_H
+
+#include "arp.h"
+
+extern const struct in_addr inaddr_llmask;
+extern const struct in_addr inaddr_llbcast;
+
+#define LINKLOCAL_ADDR 0xa9fe0000
+#define LINKLOCAL_MASK IN_CLASSB_NET
+#define LINKLOCAL_BRDC (LINKLOCAL_ADDR | ~LINKLOCAL_MASK)
+
+#ifndef IN_LINKLOCAL
+# define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR)
+#endif
+
+struct ipv4ll_state {
+ struct in_addr addr;
+ struct arp_state *arp;
+ unsigned int conflicts;
+ struct timespec defend;
+ char randomstate[128];
+ uint8_t down;
+};
+
+#define IPV4LL_STATE(ifp) \
+ ((struct ipv4ll_state *)(ifp)->if_data[IF_DATA_IPV4LL])
+#define IPV4LL_CSTATE(ifp) \
+ ((const struct ipv4ll_state *)(ifp)->if_data[IF_DATA_IPV4LL])
+#define IPV4LL_STATE_RUNNING(ifp) \
+ (IPV4LL_CSTATE((ifp)) && !IPV4LL_CSTATE((ifp))->down && \
+ IN_LINKLOCAL(ntohl(IPV4LL_CSTATE((ifp))->addr.s_addr)))
+
+struct rt* ipv4ll_subnet_route(const struct interface *);
+struct rt* ipv4ll_default_route(const struct interface *);
+ssize_t ipv4ll_env(char **, const char *, const struct interface *);
+void ipv4ll_start(void *);
+void ipv4ll_claimed(void *);
+void ipv4ll_handle_failure(void *);
+
+#define ipv4ll_free(ifp) ipv4ll_freedrop((ifp), 0);
+#define ipv4ll_drop(ifp) ipv4ll_freedrop((ifp), 1);
+void ipv4ll_freedrop(struct interface *, int);
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: ipv6.c,v 1.14 2015/08/21 10:39:00 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE 7
+#include "common.h"
+#include "if.h"
+#include "dhcpcd.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+
+#ifdef HAVE_MD5_H
+# ifndef DEPGEN
+# include <md5.h>
+# endif
+#else
+# include "md5.h"
+#endif
+
+#ifdef SHA2_H
+# include SHA2_H
+#else
+# include "sha256.h"
+#endif
+
+#ifndef SHA256_DIGEST_LENGTH
+# define SHA256_DIGEST_LENGTH 32
+#endif
+
+#ifdef IPV6_POLLADDRFLAG
+# warning kernel does not report IPv6 address flag changes
+# warning polling tentative address flags periodically
+#endif
+
+#ifdef __linux__
+ /* Match Linux defines to BSD */
+# define IN6_IFF_TEMPORARY IFA_F_TEMPORARY
+# ifdef IFA_F_OPTIMISTIC
+# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC)
+# else
+# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | 0x04)
+# endif
+# ifdef IF_F_DADFAILED
+# define IN6_IFF_DUPLICATED IFA_F_DADFAILED
+# else
+# define IN6_IFF_DUPLICATED 0x08
+# endif
+# define IN6_IFF_DETACHED 0
+#endif
+
+#define IN6_IFF_NOTUSEABLE \
+ (IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED)
+
+/* Hackery at it's finest. */
+#ifndef s6_addr32
+# ifdef __sun
+# define s6_addr32 _S6_un._S6_u32
+# else
+# define s6_addr32 __u6_addr.__u6_addr32
+# endif
+#endif
+
+
+#ifdef IPV6_MANAGETEMPADDR
+static void ipv6_regentempifid(void *);
+static void ipv6_regentempaddr(void *);
+#else
+#define ipv6_regentempifid(a) {}
+#endif
+
+struct ipv6_ctx *
+ipv6_init(struct dhcpcd_ctx *dhcpcd_ctx)
+{
+ struct ipv6_ctx *ctx;
+
+ if (dhcpcd_ctx->ipv6)
+ return dhcpcd_ctx->ipv6;
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ return NULL;
+
+ ctx->routes = malloc(sizeof(*ctx->routes));
+ if (ctx->routes == NULL) {
+ free(ctx);
+ return NULL;
+ }
+ TAILQ_INIT(ctx->routes);
+
+ ctx->ra_routers = malloc(sizeof(*ctx->ra_routers));
+ if (ctx->ra_routers == NULL) {
+ free(ctx->routes);
+ free(ctx);
+ return NULL;
+ }
+ TAILQ_INIT(ctx->ra_routers);
+
+ TAILQ_INIT(&ctx->kroutes);
+
+ ctx->sndhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ ctx->sndhdr.msg_iov = ctx->sndiov;
+ ctx->sndhdr.msg_iovlen = 1;
+ ctx->sndhdr.msg_control = ctx->sndbuf;
+ ctx->sndhdr.msg_controllen = sizeof(ctx->sndbuf);
+ ctx->rcvhdr.msg_name = &ctx->from;
+ ctx->rcvhdr.msg_namelen = sizeof(ctx->from);
+ ctx->rcvhdr.msg_iov = ctx->rcviov;
+ ctx->rcvhdr.msg_iovlen = 1;
+ ctx->rcvhdr.msg_control = ctx->rcvbuf;
+ // controllen is set at recieve
+ //ctx->rcvhdr.msg_controllen = sizeof(ctx->rcvbuf);
+ ctx->rcviov[0].iov_base = ctx->ansbuf;
+ ctx->rcviov[0].iov_len = sizeof(ctx->ansbuf);
+
+ ctx->nd_fd = -1;
+ ctx->dhcp_fd = -1;
+
+ dhcpcd_ctx->ipv6 = ctx;
+
+ return ctx;
+}
+
+ssize_t
+ipv6_printaddr(char *s, size_t sl, const uint8_t *d, const char *ifname)
+{
+ char buf[INET6_ADDRSTRLEN];
+ const char *p;
+ size_t l;
+
+ p = inet_ntop(AF_INET6, d, buf, sizeof(buf));
+ if (p == NULL)
+ return -1;
+
+ l = strlen(p);
+ if (d[0] == 0xfe && (d[1] & 0xc0) == 0x80)
+ l += 1 + strlen(ifname);
+
+ if (s == NULL)
+ return (ssize_t)l;
+
+ if (sl < l) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ s += strlcpy(s, p, sl);
+ if (d[0] == 0xfe && (d[1] & 0xc0) == 0x80) {
+ *s++ = '%';
+ s += strlcpy(s, ifname, sl);
+ }
+ *s = '\0';
+ return (ssize_t)l;
+}
+
+static ssize_t
+ipv6_readsecret(struct dhcpcd_ctx *ctx)
+{
+ FILE *fp;
+ char line[1024];
+ unsigned char *p;
+ size_t len;
+ uint32_t r;
+ int x;
+
+ if ((fp = fopen(SECRET, "r"))) {
+ len = 0;
+ while (fgets(line, sizeof(line), fp)) {
+ len = strlen(line);
+ if (len) {
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ }
+ len = hwaddr_aton(NULL, line);
+ if (len) {
+ ctx->secret_len = hwaddr_aton(ctx->secret,
+ line);
+ break;
+ }
+ len = 0;
+ }
+ fclose(fp);
+ if (len)
+ return (ssize_t)len;
+ } else {
+ if (errno != ENOENT)
+ logger(ctx, LOG_ERR,
+ "error reading secret: %s: %m", SECRET);
+ }
+
+ /* Chaining arc4random should be good enough.
+ * RFC7217 section 5.1 states the key SHOULD be at least 128 bits.
+ * To attempt and future proof ourselves, we'll generate a key of
+ * 512 bits (64 bytes). */
+ p = ctx->secret;
+ ctx->secret_len = 0;
+ for (len = 0; len < 512 / NBBY; len += sizeof(r)) {
+ r = arc4random();
+ memcpy(p, &r, sizeof(r));
+ p += sizeof(r);
+ ctx->secret_len += sizeof(r);
+
+ }
+
+ /* Ensure that only the dhcpcd user can read the secret.
+ * Write permission is also denied as chaning it would remove
+ * it's stability. */
+ if ((fp = fopen(SECRET, "w")) == NULL ||
+ chmod(SECRET, S_IRUSR) == -1)
+ goto eexit;
+ x = fprintf(fp, "%s\n",
+ hwaddr_ntoa(ctx->secret, ctx->secret_len, line, sizeof(line)));
+ if (fclose(fp) == EOF)
+ x = -1;
+ if (x > 0)
+ return (ssize_t)ctx->secret_len;
+
+eexit:
+ logger(ctx, LOG_ERR, "error writing secret: %s: %m", SECRET);
+ unlink(SECRET);
+ ctx->secret_len = 0;
+ return -1;
+}
+
+/* http://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml
+ * RFC5453 */
+static const struct reslowhigh {
+ const uint8_t high[8];
+ const uint8_t low[8];
+} reslowhigh[] = {
+ /* RFC4291 + RFC6543 */
+ { { 0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x00, 0x00 },
+ { 0x02, 0x00, 0x5e, 0xff, 0xfe, 0xff, 0xff, 0xff } },
+ /* RFC2526 */
+ { { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 },
+ { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } }
+};
+
+static int
+ipv6_reserved(const struct in6_addr *addr)
+{
+ uint64_t id, low, high;
+ size_t i;
+ const struct reslowhigh *r;
+
+ id = be64dec(addr->s6_addr + sizeof(id));
+ if (id == 0) /* RFC4291 */
+ return 1;
+ for (i = 0; i < sizeof(reslowhigh) / sizeof(reslowhigh[0]); i++) {
+ r = &reslowhigh[i];
+ low = be64dec(r->low);
+ high = be64dec(r->high);
+ if (id >= low && id <= high)
+ return 1;
+ }
+ return 0;
+}
+
+/* RFC7217 */
+static int
+ipv6_makestableprivate1(struct in6_addr *addr,
+ const struct in6_addr *prefix, int prefix_len,
+ const unsigned char *netiface, size_t netiface_len,
+ const unsigned char *netid, size_t netid_len,
+ uint32_t *dad_counter,
+ const unsigned char *secret, size_t secret_len)
+{
+ unsigned char buf[2048], *p, digest[SHA256_DIGEST_LENGTH];
+ size_t len, l;
+ SHA256_CTX ctx;
+
+ if (prefix_len < 0 || prefix_len > 120) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ l = (size_t)(ROUNDUP8(prefix_len) / NBBY);
+ len = l + netiface_len + netid_len + sizeof(*dad_counter) + secret_len;
+ if (len > sizeof(buf)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ for (;; (*dad_counter)++) {
+ /* Combine all parameters into one buffer */
+ p = buf;
+ memcpy(p, prefix, l);
+ p += l;
+ memcpy(p, netiface, netiface_len);
+ p += netiface_len;
+ memcpy(p, netid, netid_len);
+ p += netid_len;
+ memcpy(p, dad_counter, sizeof(*dad_counter));
+ p += sizeof(*dad_counter);
+ memcpy(p, secret, secret_len);
+
+ /* Make an address using the digest of the above.
+ * RFC7217 Section 5.1 states that we shouldn't use MD5.
+ * Pity as we use that for HMAC-MD5 which is still deemed OK.
+ * SHA-256 is recommended */
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, buf, len);
+ SHA256_Final(digest, &ctx);
+
+ p = addr->s6_addr;
+ memcpy(p, prefix, l);
+ /* RFC7217 section 5.2 says we need to start taking the id from
+ * the least significant bit */
+ len = sizeof(addr->s6_addr) - l;
+ memcpy(p + l, digest + (sizeof(digest) - len), len);
+
+ /* Ensure that the Interface ID does not match a reserved one,
+ * if it does then treat it as a DAD failure.
+ * RFC7217 section 5.2 */
+ if (prefix_len != 64)
+ break;
+ if (!ipv6_reserved(addr))
+ break;
+ }
+
+ return 0;
+}
+
+int
+ipv6_makestableprivate(struct in6_addr *addr,
+ const struct in6_addr *prefix, int prefix_len,
+ const struct interface *ifp,
+ int *dad_counter)
+{
+ uint32_t dad;
+ int r;
+
+ dad = (uint32_t)*dad_counter;
+
+ /* For our implementation, we shall set the hardware address
+ * as the interface identifier */
+ r = ipv6_makestableprivate1(addr, prefix, prefix_len,
+ ifp->hwaddr, ifp->hwlen,
+ ifp->ssid, ifp->ssid_len,
+ &dad,
+ ifp->ctx->secret, ifp->ctx->secret_len);
+
+ if (r == 0)
+ *dad_counter = (int)dad;
+ return r;
+}
+
+int
+ipv6_makeaddr(struct in6_addr *addr, const struct interface *ifp,
+ const struct in6_addr *prefix, int prefix_len)
+{
+ const struct ipv6_addr *ap;
+ int dad;
+
+ if (prefix_len < 0 || prefix_len > 120) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ifp->options->options & DHCPCD_SLAACPRIVATE) {
+ if (ifp->ctx->secret_len == 0) {
+ if (ipv6_readsecret(ifp->ctx) == -1)
+ return -1;
+ }
+ dad = 0;
+ if (ipv6_makestableprivate(addr,
+ prefix, prefix_len, ifp, &dad) == -1)
+ return -1;
+ return dad;
+ }
+
+ if (prefix_len > 64) {
+ errno = EINVAL;
+ return -1;
+ }
+ if ((ap = ipv6_linklocal(ifp)) == NULL) {
+ /* We delay a few functions until we get a local-link address
+ * so this should never be hit. */
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* Make the address from the first local-link address */
+ memcpy(addr, prefix, sizeof(*prefix));
+ addr->s6_addr32[2] = ap->addr.s6_addr32[2];
+ addr->s6_addr32[3] = ap->addr.s6_addr32[3];
+ return 0;
+}
+
+int
+ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len)
+{
+ int bytelen, bitlen;
+
+ if (len < 0 || len > 128) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ bytelen = len / NBBY;
+ bitlen = len % NBBY;
+ memcpy(&prefix->s6_addr, &addr->s6_addr, (size_t)bytelen);
+ if (bitlen != 0)
+ prefix->s6_addr[bytelen] =
+ (uint8_t)(prefix->s6_addr[bytelen] >> (NBBY - bitlen));
+ memset((char *)prefix->s6_addr + bytelen, 0,
+ sizeof(prefix->s6_addr) - (size_t)bytelen);
+ return 0;
+}
+
+int
+ipv6_mask(struct in6_addr *mask, int len)
+{
+ static const unsigned char masks[NBBY] =
+ { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+ int bytes, bits, i;
+
+ if (len < 0 || len > 128) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(mask, 0, sizeof(*mask));
+ bytes = len / NBBY;
+ bits = len % NBBY;
+ for (i = 0; i < bytes; i++)
+ mask->s6_addr[i] = 0xff;
+ if (bits)
+ mask->s6_addr[bytes] = masks[bits - 1];
+ return 0;
+}
+
+uint8_t
+ipv6_prefixlen(const struct in6_addr *mask)
+{
+ int x = 0, y;
+ const unsigned char *lim, *p;
+
+ lim = (const unsigned char *)mask + sizeof(*mask);
+ for (p = (const unsigned char *)mask; p < lim; x++, p++) {
+ if (*p != 0xff)
+ break;
+ }
+ y = 0;
+ if (p < lim) {
+ for (y = 0; y < NBBY; y++) {
+ if ((*p & (0x80 >> y)) == 0)
+ break;
+ }
+ }
+
+ /*
+ * when the limit pointer is given, do a stricter check on the
+ * remaining bits.
+ */
+ if (p < lim) {
+ if (y != 0 && (*p & (0x00ff >> y)) != 0)
+ return 0;
+ for (p = p + 1; p < lim; p++)
+ if (*p != 0)
+ return 0;
+ }
+
+ return (uint8_t)(x * NBBY + y);
+}
+
+static void
+in6_to_h64(uint64_t *vhigh, uint64_t *vlow, const struct in6_addr *addr)
+{
+
+ *vhigh = be64dec(addr->s6_addr);
+ *vlow = be64dec(addr->s6_addr + 8);
+}
+
+static void
+h64_to_in6(struct in6_addr *addr, uint64_t vhigh, uint64_t vlow)
+{
+
+ be64enc(addr->s6_addr, vhigh);
+ be64enc(addr->s6_addr + 8, vlow);
+}
+
+int
+ipv6_userprefix(
+ const struct in6_addr *prefix, // prefix from router
+ short prefix_len, // length of prefix received
+ uint64_t user_number, // "random" number from user
+ struct in6_addr *result, // resultant prefix
+ short result_len) // desired prefix length
+{
+ uint64_t vh, vl, user_low, user_high;
+
+ if (prefix_len < 0 || prefix_len > 120 ||
+ result_len < 0 || result_len > 120)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check that the user_number fits inside result_len less prefix_len */
+ if (result_len < prefix_len || user_number > INT_MAX ||
+ ffs((int)user_number) > result_len - prefix_len)
+ {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* virtually shift user number by dest_len, then split at 64 */
+ if (result_len >= 64) {
+ user_high = user_number << (result_len - 64);
+ user_low = 0;
+ } else {
+ user_high = user_number >> (64 - result_len);
+ user_low = user_number << result_len;
+ }
+
+ /* convert to two 64bit host order values */
+ in6_to_h64(&vh, &vl, prefix);
+
+ vh |= user_high;
+ vl |= user_low;
+
+ /* copy back result */
+ h64_to_in6(result, vh, vl);
+
+ return 0;
+}
+
+#ifdef IPV6_POLLADDRFLAG
+void
+ipv6_checkaddrflags(void *arg)
+{
+ struct ipv6_addr *ap;
+ int ifa_flags;
+
+ ap = arg;
+ ifa_flags = if_addrflags6(&ap->addr, ap->iface);
+ if (ifa_flags == -1)
+ logger(ap->iface->ctx, LOG_ERR,
+ "%s: if_addrflags6: %m", ap->iface->name);
+ else if (!(ifa_flags & IN6_IFF_TENTATIVE)) {
+ ipv6_handleifa(ap->iface->ctx, RTM_NEWADDR,
+ ap->iface->ctx->ifaces, ap->iface->name,
+ &ap->addr, ap->prefix_len, ifa_flags);
+ } else {
+ struct timespec tv;
+
+ ms_to_ts(&tv, RETRANS_TIMER / 2);
+ eloop_timeout_add_tv(ap->iface->ctx->eloop, &tv,
+ ipv6_checkaddrflags, ap);
+ }
+}
+#endif
+
+
+static void
+ipv6_deleteaddr(struct ipv6_addr *ia)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ap;
+
+ logger(ia->iface->ctx, LOG_INFO, "%s: deleting address %s",
+ ia->iface->name, ia->saddr);
+ if (if_deladdress6(ia) == -1 &&
+ errno != EADDRNOTAVAIL && errno != ENXIO && errno != ENODEV)
+ logger(ia->iface->ctx, LOG_ERR, "if_deladdress6: :%m");
+
+ state = IPV6_STATE(ia->iface);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, &ia->addr)) {
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ ipv6_freeaddr(ap);
+ break;
+ }
+ }
+}
+
+int
+ipv6_addaddr(struct ipv6_addr *ap, const struct timespec *now)
+{
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *nap;
+ uint32_t pltime, vltime;
+
+ /* Ensure no other interface has this address */
+ TAILQ_FOREACH(ifp, ap->iface->ctx->ifaces, next) {
+ if (ifp == ap->iface)
+ continue;
+ state = IPV6_STATE(ifp);
+ if (state == NULL)
+ continue;
+ TAILQ_FOREACH(nap, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&nap->addr, &ap->addr)) {
+ ipv6_deleteaddr(nap);
+ break;
+ }
+ }
+ }
+
+ if (!(ap->flags & IPV6_AF_DADCOMPLETED) &&
+ ipv6_iffindaddr(ap->iface, &ap->addr))
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+
+ logger(ap->iface->ctx, ap->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG,
+ "%s: adding %saddress %s", ap->iface->name,
+#ifdef IPV6_AF_TEMPORARY
+ ap->flags & IPV6_AF_TEMPORARY ? "temporary " : "",
+#else
+ "",
+#endif
+ ap->saddr);
+ if (ap->prefix_pltime == ND6_INFINITE_LIFETIME &&
+ ap->prefix_vltime == ND6_INFINITE_LIFETIME)
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: pltime infinity, vltime infinity",
+ ap->iface->name);
+ else if (ap->prefix_pltime == ND6_INFINITE_LIFETIME)
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: pltime infinity, vltime %"PRIu32" seconds",
+ ap->iface->name, ap->prefix_vltime);
+ else if (ap->prefix_vltime == ND6_INFINITE_LIFETIME)
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: pltime %"PRIu32"seconds, vltime infinity",
+ ap->iface->name, ap->prefix_pltime);
+ else
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: pltime %"PRIu32" seconds, vltime %"PRIu32" seconds",
+ ap->iface->name, ap->prefix_pltime, ap->prefix_vltime);
+
+ /* Adjust plftime and vltime based on acquired time */
+ pltime = ap->prefix_pltime;
+ vltime = ap->prefix_vltime;
+ if (timespecisset(&ap->acquired) &&
+ (ap->prefix_pltime != ND6_INFINITE_LIFETIME ||
+ ap->prefix_vltime != ND6_INFINITE_LIFETIME))
+ {
+ struct timespec n;
+
+ if (now == NULL) {
+ clock_gettime(CLOCK_MONOTONIC, &n);
+ now = &n;
+ }
+ timespecsub(now, &ap->acquired, &n);
+ if (ap->prefix_pltime != ND6_INFINITE_LIFETIME) {
+ ap->prefix_pltime -= (uint32_t)n.tv_sec;
+ /* This can happen when confirming a
+ * deprecated but still valid lease. */
+ if (ap->prefix_pltime > pltime)
+ ap->prefix_pltime = 0;
+ }
+ if (ap->prefix_vltime != ND6_INFINITE_LIFETIME)
+ ap->prefix_vltime -= (uint32_t)n.tv_sec;
+
+#if 0
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: acquired %lld.%.9ld, now %lld.%.9ld, diff %lld.%.9ld",
+ ap->iface->name,
+ (long long)ap->acquired.tv_sec, ap->acquired.tv_nsec,
+ (long long)now->tv_sec, now->tv_nsec,
+ (long long)n.tv_sec, n.tv_nsec);
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: adj pltime %"PRIu32" seconds, "
+ "vltime %"PRIu32" seconds",
+ ap->iface->name, ap->prefix_pltime, ap->prefix_vltime);
+#endif
+ }
+
+ if (if_addaddress6(ap) == -1) {
+ logger(ap->iface->ctx, LOG_ERR, "if_addaddress6: %m");
+ /* Restore real pltime and vltime */
+ ap->prefix_pltime = pltime;
+ ap->prefix_vltime = vltime;
+ return -1;
+ }
+
+#ifdef IPV6_MANAGETEMPADDR
+ /* RFC4941 Section 3.4 */
+ if (ap->flags & IPV6_AF_TEMPORARY &&
+ ap->prefix_pltime &&
+ ap->prefix_vltime &&
+ ap->iface->options->options & DHCPCD_IPV6RA_OWN &&
+ ip6_use_tempaddr(ap->iface->name))
+ eloop_timeout_add_sec(ap->iface->ctx->eloop,
+ (time_t)ap->prefix_pltime - REGEN_ADVANCE,
+ ipv6_regentempaddr, ap);
+#endif
+
+ /* Restore real pltime and vltime */
+ ap->prefix_pltime = pltime;
+ ap->prefix_vltime = vltime;
+
+ ap->flags &= ~IPV6_AF_NEW;
+ ap->flags |= IPV6_AF_ADDED;
+ if (ap->delegating_iface)
+ ap->flags |= IPV6_AF_DELEGATED;
+
+#ifdef IPV6_POLLADDRFLAG
+ eloop_timeout_delete(ap->iface->ctx->eloop,
+ ipv6_checkaddrflags, ap);
+ if (!(ap->flags & IPV6_AF_DADCOMPLETED)) {
+ struct timespec tv;
+
+ ms_to_ts(&tv, RETRANS_TIMER / 2);
+ eloop_timeout_add_tv(ap->iface->ctx->eloop,
+ &tv, ipv6_checkaddrflags, ap);
+ }
+#endif
+
+ return 0;
+}
+
+int
+ipv6_publicaddr(const struct ipv6_addr *ia)
+{
+ return (ia->prefix_pltime &&
+ (ia->addr.s6_addr[0] & 0xfe) != 0xfc &&
+ !(ia->addr_flags & IN6_IFF_NOTUSEABLE));
+}
+
+int
+ipv6_findaddrmatch(const struct ipv6_addr *addr, const struct in6_addr *match,
+ short flags)
+{
+
+ if (match == NULL) {
+ if ((addr->flags &
+ (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) ==
+ (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED))
+ return 1;
+ } else if (addr->prefix_vltime &&
+ IN6_ARE_ADDR_EQUAL(&addr->addr, match) &&
+ (!flags || addr->flags & flags))
+ return 1;
+
+ return 0;
+}
+
+struct ipv6_addr *
+ipv6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, short flags)
+{
+ struct ipv6_addr *dap, *nap;
+
+ dap = dhcp6_findaddr(ctx, addr, flags);
+ nap = ipv6nd_findaddr(ctx, addr, flags);
+ if (!dap && !nap)
+ return NULL;
+ if (dap && !nap)
+ return dap;
+ if (nap && !dap)
+ return nap;
+ if (nap->iface->metric < dap->iface->metric)
+ return nap;
+ return dap;
+}
+
+ssize_t
+ipv6_addaddrs(struct ipv6_addrhead *addrs)
+{
+ struct ipv6_addr *ap, *apn, *apf;
+ ssize_t i;
+ struct timespec now;
+
+ i = 0;
+ timespecclear(&now);
+ TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
+ if (ap->prefix_vltime == 0) {
+ if (ap->flags & IPV6_AF_ADDED) {
+ ipv6_deleteaddr(ap);
+ i++;
+ }
+ eloop_q_timeout_delete(ap->iface->ctx->eloop,
+ 0, NULL, ap);
+ if (ap->flags & IPV6_AF_REQUEST) {
+ ap->flags &= ~IPV6_AF_ADDED;
+ } else {
+ TAILQ_REMOVE(addrs, ap, next);
+ ipv6_freeaddr(ap);
+ }
+ } else if (!(ap->flags & IPV6_AF_STALE) &&
+ !IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
+ {
+ apf = ipv6_findaddr(ap->iface->ctx,
+ &ap->addr, IPV6_AF_ADDED);
+ if (apf && apf->iface != ap->iface) {
+ if (apf->iface->metric <= ap->iface->metric) {
+ logger(apf->iface->ctx, LOG_INFO,
+ "%s: preferring %s on %s",
+ ap->iface->name,
+ ap->saddr,
+ apf->iface->name);
+ continue;
+ }
+ logger(apf->iface->ctx, LOG_INFO,
+ "%s: preferring %s on %s",
+ apf->iface->name,
+ ap->saddr,
+ ap->iface->name);
+ if (if_deladdress6(apf) == -1 &&
+ errno != EADDRNOTAVAIL && errno != ENXIO)
+ logger(apf->iface->ctx, LOG_ERR,
+ "if_deladdress6: %m");
+ apf->flags &=
+ ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
+ } else if (apf)
+ apf->flags &= ~IPV6_AF_ADDED;
+ if (ap->flags & IPV6_AF_NEW)
+ i++;
+ if (!timespecisset(&now))
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ ipv6_addaddr(ap, &now);
+ }
+ }
+
+ return i;
+}
+
+void
+ipv6_freeaddr(struct ipv6_addr *ap)
+{
+
+ eloop_q_timeout_delete(ap->iface->ctx->eloop, 0, NULL, ap);
+ free(ap);
+}
+
+void
+ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop,
+ const struct interface *ifd)
+{
+ struct ipv6_addr *ap, *apn, *apf;
+ struct timespec now;
+
+ timespecclear(&now);
+ TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
+ if (ifd && ap->delegating_iface != ifd)
+ continue;
+ if (drop != 2)
+ TAILQ_REMOVE(addrs, ap, next);
+ if (drop && ap->flags & IPV6_AF_ADDED &&
+ (ap->iface->options->options &
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT))
+ {
+ if (drop == 2)
+ TAILQ_REMOVE(addrs, ap, next);
+ /* Don't drop link-local addresses. */
+ if (!IN6_IS_ADDR_LINKLOCAL(&ap->addr)) {
+ /* Find the same address somewhere else */
+ apf = ipv6_findaddr(ap->iface->ctx, &ap->addr,
+ 0);
+ if ((apf == NULL ||
+ (apf->iface != ap->iface)))
+ ipv6_deleteaddr(ap);
+ if (!(ap->iface->options->options &
+ DHCPCD_EXITING) && apf)
+ {
+ if (!timespecisset(&now))
+ clock_gettime(CLOCK_MONOTONIC,
+ &now);
+ ipv6_addaddr(apf, &now);
+ }
+ }
+ if (drop == 2)
+ ipv6_freeaddr(ap);
+ }
+ if (drop != 2)
+ ipv6_freeaddr(ap);
+ }
+}
+
+static struct ipv6_state *
+ipv6_getstate(struct interface *ifp)
+{
+ struct ipv6_state *state;
+
+ state = IPV6_STATE(ifp);
+ if (state == NULL) {
+ ifp->if_data[IF_DATA_IPV6] = calloc(1, sizeof(*state));
+ state = IPV6_STATE(ifp);
+ if (state == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ TAILQ_INIT(&state->addrs);
+ TAILQ_INIT(&state->ll_callbacks);
+
+ /* Regenerate new ids */
+ if (ifp->options &&
+ ifp->options->options & DHCPCD_IPV6RA_OWN &&
+ ip6_use_tempaddr(ifp->name))
+ ipv6_regentempifid(ifp);
+ }
+ return state;
+}
+
+void
+ipv6_handleifa(struct dhcpcd_ctx *ctx,
+ int cmd, struct if_head *ifs, const char *ifname,
+ const struct in6_addr *addr, uint8_t prefix_len, int flags)
+{
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *ap;
+ struct ll_callback *cb;
+
+#if 0
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &addr->s6_addr,
+ buf, INET6_ADDRSTRLEN);
+ logger(ctx, LOG_DEBUG, "%s: cmd %d addr %s flags %d",
+ ifname, cmd, buf, flags);
+#endif
+
+ if (ifs == NULL)
+ ifs = ctx->ifaces;
+ if (ifs == NULL) {
+ errno = ESRCH;
+ return;
+ }
+ if ((ifp = if_find(ifs, ifname)) == NULL)
+ return;
+ if ((state = ipv6_getstate(ifp)) == NULL)
+ return;
+
+ if (!IN6_IS_ADDR_LINKLOCAL(addr)) {
+ ipv6nd_handleifa(ctx, cmd, ifname, addr, flags);
+ dhcp6_handleifa(ctx, cmd, ifname, addr, flags);
+ }
+
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr))
+ break;
+ }
+
+ switch (cmd) {
+ case RTM_DELADDR:
+ if (ap) {
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ ipv6_freeaddr(ap);
+ }
+ break;
+ case RTM_NEWADDR:
+ if (ap == NULL) {
+ char buf[INET6_ADDRSTRLEN];
+ const char *cbp;
+
+ ap = calloc(1, sizeof(*ap));
+ ap->iface = ifp;
+ ap->addr = *addr;
+ ap->prefix_len = prefix_len;
+ ipv6_makeprefix(&ap->prefix, &ap->addr,
+ ap->prefix_len);
+ cbp = inet_ntop(AF_INET6, &addr->s6_addr,
+ buf, sizeof(buf));
+ if (cbp)
+ snprintf(ap->saddr, sizeof(ap->saddr),
+ "%s/%d", cbp, prefix_len);
+ if (if_getlifetime6(ap) == -1) {
+ /* No support or address vanished.
+ * Either way, just set a deprecated
+ * infinite time lifetime and continue.
+ * This is fine because we only want
+ * to know this when trying to extend
+ * temporary addresses.
+ * As we can't extend infinite, we'll
+ * create a new temporary address. */
+ ap->prefix_pltime = 0;
+ ap->prefix_vltime =
+ ND6_INFINITE_LIFETIME;
+ }
+ /* This is a minor regression against RFC 4941
+ * because the kernel only knows when the
+ * lifetimes were last updated, not when the
+ * address was initially created.
+ * Provided dhcpcd is not restarted, this
+ * won't be a problem.
+ * If we don't like it, we can always
+ * pretend lifetimes are infinite and always
+ * generate a new temporary address on
+ * restart. */
+ ap->acquired = ap->created;
+ TAILQ_INSERT_TAIL(&state->addrs,
+ ap, next);
+ }
+ ap->addr_flags = flags;
+#ifdef IPV6_MANAGETEMPADDR
+ if (ap->addr_flags & IN6_IFF_TEMPORARY)
+ ap->flags |= IPV6_AF_TEMPORARY;
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&ap->addr)) {
+#ifdef IPV6_POLLADDRFLAG
+ if (ap->addr_flags & IN6_IFF_TENTATIVE) {
+ struct timespec tv;
+
+ ms_to_ts(&tv, RETRANS_TIMER / 2);
+ eloop_timeout_add_tv(
+ ap->iface->ctx->eloop,
+ &tv, ipv6_checkaddrflags, ap);
+ break;
+ }
+#endif
+
+ if (!(ap->addr_flags & IN6_IFF_NOTUSEABLE)) {
+ /* Now run any callbacks.
+ * Typically IPv6RS or DHCPv6 */
+ while ((cb =
+ TAILQ_FIRST(&state->ll_callbacks)))
+ {
+ TAILQ_REMOVE(
+ &state->ll_callbacks,
+ cb, next);
+ cb->callback(cb->arg);
+ free(cb);
+ }
+ }
+ }
+ break;
+ }
+}
+
+int
+ipv6_hasaddr(const struct interface *ifp)
+{
+
+ if (ipv6nd_iffindaddr(ifp, NULL, 0) != NULL)
+ return 1;
+ if (dhcp6_iffindaddr(ifp, NULL, 0) != NULL)
+ return 1;
+ return 0;
+}
+
+const struct ipv6_addr *
+ipv6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr)
+{
+ const struct ipv6_state *state;
+ const struct ipv6_addr *ap;
+
+ state = IPV6_CSTATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (addr == NULL) {
+ if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) &&
+ !(ap->addr_flags & IN6_IFF_NOTUSEABLE))
+ return ap;
+ } else {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr) &&
+ !(ap->addr_flags & IN6_IFF_TENTATIVE))
+ return ap;
+ }
+ }
+ }
+ return NULL;
+}
+
+int
+ipv6_addlinklocalcallback(struct interface *ifp,
+ void (*callback)(void *), void *arg)
+{
+ struct ipv6_state *state;
+ struct ll_callback *cb;
+
+ state = ipv6_getstate(ifp);
+ TAILQ_FOREACH(cb, &state->ll_callbacks, next) {
+ if (cb->callback == callback && cb->arg == arg)
+ break;
+ }
+ if (cb == NULL) {
+ cb = malloc(sizeof(*cb));
+ if (cb == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return -1;
+ }
+ cb->callback = callback;
+ cb->arg = arg;
+ TAILQ_INSERT_TAIL(&state->ll_callbacks, cb, next);
+ }
+ return 0;
+}
+
+static struct ipv6_addr *
+ipv6_newlinklocal(struct interface *ifp)
+{
+ struct ipv6_addr *ap;
+
+ ap = calloc(1, sizeof(*ap));
+ if (ap != NULL) {
+ ap->iface = ifp;
+ ap->prefix.s6_addr32[0] = htonl(0xfe800000);
+ ap->prefix.s6_addr32[1] = 0;
+ ap->prefix_len = 64;
+ ap->dadcounter = 0;
+ ap->prefix_pltime = ND6_INFINITE_LIFETIME;
+ ap->prefix_vltime = ND6_INFINITE_LIFETIME;
+ ap->flags = IPV6_AF_NEW;
+ ap->addr_flags = IN6_IFF_TENTATIVE;
+ }
+ return ap;
+}
+
+static const uint8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+static const uint8_t allone[8] =
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+static int
+ipv6_addlinklocal(struct interface *ifp)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ap, *ap2;
+ int dadcounter;
+
+ /* Check sanity before malloc */
+ if (!(ifp->options->options & DHCPCD_SLAACPRIVATE)) {
+ switch (ifp->family) {
+ case ARPHRD_ETHER:
+ /* Check for a valid hardware address */
+ if (ifp->hwlen != 6 && ifp->hwlen != 8) {
+ errno = ENOTSUP;
+ return -1;
+ }
+ if (memcmp(ifp->hwaddr, allzero, ifp->hwlen) == 0 ||
+ memcmp(ifp->hwaddr, allone, ifp->hwlen) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ break;
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+ }
+
+ state = ipv6_getstate(ifp);
+ if (state == NULL)
+ return -1;
+
+ ap = ipv6_newlinklocal(ifp);
+ if (ap == NULL)
+ return -1;
+
+ if (ifp->options->options & DHCPCD_SLAACPRIVATE) {
+ dadcounter = 0;
+nextslaacprivate:
+ if (ipv6_makestableprivate(&ap->addr,
+ &ap->prefix, ap->prefix_len, ifp, &dadcounter) == -1)
+ {
+ free(ap);
+ return -1;
+ }
+ ap->dadcounter = dadcounter;
+ } else {
+ memcpy(ap->addr.s6_addr, ap->prefix.s6_addr, 8);
+ switch (ifp->family) {
+ case ARPHRD_ETHER:
+ if (ifp->hwlen == 6) {
+ ap->addr.s6_addr[ 8] = ifp->hwaddr[0];
+ ap->addr.s6_addr[ 9] = ifp->hwaddr[1];
+ ap->addr.s6_addr[10] = ifp->hwaddr[2];
+ ap->addr.s6_addr[11] = 0xff;
+ ap->addr.s6_addr[12] = 0xfe;
+ ap->addr.s6_addr[13] = ifp->hwaddr[3];
+ ap->addr.s6_addr[14] = ifp->hwaddr[4];
+ ap->addr.s6_addr[15] = ifp->hwaddr[5];
+ } else if (ifp->hwlen == 8)
+ memcpy(&ap->addr.s6_addr[8], ifp->hwaddr, 8);
+ else {
+ free(ap);
+ errno = ENOTSUP;
+ return -1;
+ }
+ break;
+ }
+
+ /* Sanity check: g bit must not indciate "group" */
+ if (EUI64_GROUP(&ap->addr)) {
+ free(ap);
+ errno = EINVAL;
+ return -1;
+ }
+ EUI64_TO_IFID(&ap->addr);
+ }
+
+ /* Do we already have this address? */
+ TAILQ_FOREACH(ap2, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, &ap2->addr)) {
+ if (ap2->addr_flags & IN6_IFF_DUPLICATED) {
+ if (ifp->options->options &
+ DHCPCD_SLAACPRIVATE)
+ {
+ dadcounter++;
+ goto nextslaacprivate;
+ }
+ free(ap);
+ errno = EADDRNOTAVAIL;
+ return -1;
+ }
+
+ logger(ap2->iface->ctx, LOG_WARNING,
+ "%s: waiting for %s to complete",
+ ap2->iface->name, ap2->saddr);
+ free(ap);
+ errno = EEXIST;
+ return 0;
+ }
+ }
+
+ inet_ntop(AF_INET6, &ap->addr, ap->saddr, sizeof(ap->saddr));
+ TAILQ_INSERT_TAIL(&state->addrs, ap, next);
+ ipv6_addaddr(ap, NULL);
+ return 1;
+}
+
+/* Ensure the interface has a link-local address */
+int
+ipv6_start(struct interface *ifp)
+{
+ const struct ipv6_state *state;
+ const struct ipv6_addr *ap;
+
+ /* We can't assign a link-locak address to this,
+ * the ppp process has to. */
+ if (ifp->flags & IFF_POINTOPOINT)
+ return 0;
+
+ state = IPV6_CSTATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) &&
+ !(ap->addr_flags & IN6_IFF_DUPLICATED))
+ break;
+ }
+ /* Regenerate new ids */
+ if (ifp->options->options & DHCPCD_IPV6RA_OWN &&
+ ip6_use_tempaddr(ifp->name))
+ ipv6_regentempifid(ifp);
+ } else
+ ap = NULL;
+
+ if (ap == NULL && ipv6_addlinklocal(ifp) == -1)
+ return -1;
+
+ /* Load existing routes */
+ if_initrt6(ifp);
+ return 0;
+}
+
+void
+ipv6_freedrop(struct interface *ifp, int drop)
+{
+ struct ipv6_state *state;
+ struct ll_callback *cb;
+
+ if (ifp == NULL)
+ return;
+
+ if ((state = IPV6_STATE(ifp)) == NULL)
+ return;
+
+ ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL);
+
+ /* Becuase we need to cache the addresses we don't control,
+ * we only free the state on when NOT dropping addresses. */
+ if (drop == 0) {
+ while ((cb = TAILQ_FIRST(&state->ll_callbacks))) {
+ TAILQ_REMOVE(&state->ll_callbacks, cb, next);
+ free(cb);
+ }
+ free(state);
+ ifp->if_data[IF_DATA_IPV6] = NULL;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ }
+}
+
+void
+ipv6_ctxfree(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->ipv6 == NULL)
+ return;
+
+ ipv6_freerts(ctx->ipv6->routes);
+ free(ctx->ipv6->routes);
+ free(ctx->ipv6->ra_routers);
+ ipv6_freerts(&ctx->ipv6->kroutes);
+ free(ctx->ipv6);
+}
+
+int
+ipv6_handleifa_addrs(int cmd,
+ struct ipv6_addrhead *addrs, const struct in6_addr *addr, int flags)
+{
+ struct ipv6_addr *ap, *apn;
+ uint8_t found, alldadcompleted;
+
+ alldadcompleted = 1;
+ found = 0;
+ TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
+ if (!IN6_ARE_ADDR_EQUAL(addr, &ap->addr)) {
+ if (ap->flags & IPV6_AF_ADDED &&
+ !(ap->flags & IPV6_AF_DADCOMPLETED))
+ alldadcompleted = 0;
+ continue;
+ }
+ switch (cmd) {
+ case RTM_DELADDR:
+ if (ap->flags & IPV6_AF_ADDED) {
+ logger(ap->iface->ctx, LOG_INFO,
+ "%s: deleted address %s",
+ ap->iface->name, ap->saddr);
+ ap->flags &= ~IPV6_AF_ADDED;
+ }
+ break;
+ case RTM_NEWADDR:
+ /* Safety - ignore tentative announcements */
+ if (flags & (IN6_IFF_DETACHED |IN6_IFF_TENTATIVE))
+ break;
+ if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) {
+ found++;
+ if (flags & IN6_IFF_DUPLICATED)
+ ap->flags |= IPV6_AF_DUPLICATED;
+ else
+ ap->flags &= ~IPV6_AF_DUPLICATED;
+ if (ap->dadcallback)
+ ap->dadcallback(ap);
+ /* We need to set this here in-case the
+ * dadcallback function checks it */
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ }
+ break;
+ }
+ }
+
+ return alldadcompleted ? found : 0;
+}
+
+#ifdef IPV6_MANAGETEMPADDR
+static const struct ipv6_addr *
+ipv6_findaddrid(struct dhcpcd_ctx *ctx, uint8_t *addr)
+{
+ const struct interface *ifp;
+ const struct ipv6_state *state;
+ const struct ipv6_addr *ia;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if ((state = IPV6_CSTATE(ifp))) {
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (memcmp(&ia->addr.s6_addr[8], addr, 8) == 0)
+ return ia;
+ }
+ }
+ }
+ return NULL;
+}
+
+static const uint8_t nullid[8];
+static const uint8_t anycastid[8] = {
+ 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 };
+static const uint8_t isatapid[4] = { 0x00, 0x00, 0x5e, 0xfe };
+
+static void
+ipv6_regen_desync(struct interface *ifp, int force)
+{
+ struct ipv6_state *state;
+ time_t max;
+
+ state = IPV6_STATE(ifp);
+
+ /* RFC4941 Section 5 states that DESYNC_FACTOR must never be
+ * greater than TEMP_VALID_LIFETIME - REGEN_ADVANCE.
+ * I believe this is an error and it should be never be greateter than
+ * TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE. */
+ max = ip6_temp_preferred_lifetime(ifp->name) - REGEN_ADVANCE;
+ if (state->desync_factor && !force && state->desync_factor < max)
+ return;
+ if (state->desync_factor == 0)
+ state->desync_factor =
+ (time_t)arc4random_uniform(MIN(MAX_DESYNC_FACTOR,
+ (uint32_t)max));
+ max = ip6_temp_preferred_lifetime(ifp->name) -
+ state->desync_factor - REGEN_ADVANCE;
+ eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempifid, ifp);
+}
+
+void
+ipv6_gentempifid(struct interface *ifp)
+{
+ struct ipv6_state *state;
+ MD5_CTX md5;
+ uint8_t seed[16], digest[16];
+ int retry;
+
+ if ((state = IPV6_STATE(ifp)) == NULL)
+ return;
+
+ retry = 0;
+ if (memcmp(nullid, state->randomseed0, sizeof(nullid)) == 0) {
+ uint32_t r;
+
+ r = arc4random();
+ memcpy(seed, &r, sizeof(r));
+ r = arc4random();
+ memcpy(seed + sizeof(r), &r, sizeof(r));
+ } else
+ memcpy(seed, state->randomseed0, sizeof(state->randomseed0));
+
+ memcpy(seed + sizeof(state->randomseed0),
+ state->randomseed1, sizeof(state->randomseed1));
+
+again:
+ /* RFC4941 Section 3.2.1.1
+ * Take the left-most 64bits and set bit 6 to zero */
+ MD5Init(&md5);
+ MD5Update(&md5, seed, sizeof(seed));
+ MD5Final(digest, &md5);
+
+ /* RFC4941 Section 3.2.1.1
+ * Take the left-most 64bits and set bit 6 to zero */
+ memcpy(state->randomid, digest, sizeof(state->randomid));
+ state->randomid[0] = (uint8_t)(state->randomid[0] & ~EUI64_UBIT);
+
+ /* RFC4941 Section 3.2.1.4
+ * Reject reserved or existing id's */
+ if (memcmp(nullid, state->randomid, sizeof(nullid)) == 0 ||
+ (memcmp(anycastid, state->randomid, 7) == 0 &&
+ (anycastid[7] & state->randomid[7]) == anycastid[7]) ||
+ memcmp(isatapid, state->randomid, sizeof(isatapid)) == 0 ||
+ ipv6_findaddrid(ifp->ctx, state->randomid))
+ {
+ if (++retry < GEN_TEMPID_RETRY_MAX) {
+ memcpy(seed, digest + 8, 8);
+ goto again;
+ }
+ memset(state->randomid, 0, sizeof(state->randomid));
+ }
+
+ /* RFC4941 Section 3.2.1.6
+ * Save the right-most 64bits of the digest */
+ memcpy(state->randomseed0, digest + 8,
+ sizeof(state->randomseed0));
+}
+
+/* RFC4941 Section 3.3.7 */
+static void
+ipv6_tempdadcallback(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+
+ if (ia->flags & IPV6_AF_DUPLICATED) {
+ struct ipv6_addr *ia1;
+ struct timespec tv;
+
+ if (++ia->dadcounter == TEMP_IDGEN_RETRIES) {
+ logger(ia->iface->ctx, LOG_ERR,
+ "%s: too many duplicate temporary addresses",
+ ia->iface->name);
+ return;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ if ((ia1 = ipv6_createtempaddr(ia, &tv)) == NULL)
+ logger(ia->iface->ctx, LOG_ERR,
+ "ipv6_createtempaddr: %m");
+ else
+ ia1->dadcounter = ia->dadcounter;
+ ipv6_deleteaddr(ia);
+ if (ia1)
+ ipv6_addaddr(ia1, &ia1->acquired);
+ }
+}
+
+struct ipv6_addr *
+ipv6_createtempaddr(struct ipv6_addr *ia0, const struct timespec *now)
+{
+ struct ipv6_state *state;
+ const struct ipv6_state *cstate;
+ int genid;
+ struct in6_addr addr, mask;
+ uint32_t randid[2];
+ const struct interface *ifp;
+ const struct ipv6_addr *ap;
+ struct ipv6_addr *ia;
+ uint32_t i, trylimit;
+ char buf[INET6_ADDRSTRLEN];
+ const char *cbp;
+
+ trylimit = TEMP_IDGEN_RETRIES;
+ state = IPV6_STATE(ia0->iface);
+ genid = 0;
+
+ addr = ia0->addr;
+ ipv6_mask(&mask, ia0->prefix_len);
+ /* clear the old ifid */
+ for (i = 0; i < 4; i++)
+ addr.s6_addr32[i] &= mask.s6_addr32[i];
+
+again:
+ if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0)
+ genid = 1;
+ if (genid) {
+ memcpy(state->randomseed1, &ia0->addr.s6_addr[8],
+ sizeof(state->randomseed1));
+ ipv6_gentempifid(ia0->iface);
+ if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) {
+ errno = EFAULT;
+ return NULL;
+ }
+ }
+ memcpy(&randid[0], state->randomid, sizeof(randid[0]));
+ memcpy(&randid[1], state->randomid + sizeof(randid[1]),
+ sizeof(randid[2]));
+ addr.s6_addr32[2] |= randid[0] & ~mask.s6_addr32[2];
+ addr.s6_addr32[3] |= randid[1] & ~mask.s6_addr32[3];
+
+ /* Ensure we don't already have it */
+ TAILQ_FOREACH(ifp, ia0->iface->ctx->ifaces, next) {
+ cstate = IPV6_CSTATE(ifp);
+ if (cstate) {
+ TAILQ_FOREACH(ap, &cstate->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, &addr)) {
+ if (--trylimit == 0) {
+ errno = EEXIST;
+ return NULL;
+ }
+ genid = 1;
+ goto again;
+ }
+ }
+ }
+ }
+
+ if ((ia = calloc(1, sizeof(*ia))) == NULL)
+ return NULL;
+
+ ia->iface = ia0->iface;
+ ia->addr = addr;
+ /* Must be made tentative, for our DaD to work */
+ ia->addr_flags = IN6_IFF_TENTATIVE;
+ ia->dadcallback = ipv6_tempdadcallback;
+ ia->flags = IPV6_AF_NEW | IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY;
+ ia->prefix = ia0->prefix;
+ ia->prefix_len = ia0->prefix_len;
+ ia->created = ia->acquired = now ? *now : ia0->acquired;
+
+ /* Ensure desync is still valid */
+ ipv6_regen_desync(ia->iface, 0);
+
+ /* RFC4941 Section 3.3.4 */
+ i = (uint32_t)(ip6_temp_preferred_lifetime(ia0->iface->name) -
+ state->desync_factor);
+ ia->prefix_pltime = MIN(ia0->prefix_pltime, i);
+ i = (uint32_t)ip6_temp_valid_lifetime(ia0->iface->name);
+ ia->prefix_vltime = MIN(ia0->prefix_vltime, i);
+ if (ia->prefix_pltime <= REGEN_ADVANCE ||
+ ia->prefix_pltime > ia0->prefix_vltime)
+ {
+ errno = EINVAL;
+ free(ia);
+ return NULL;
+ }
+
+ cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
+ if (cbp)
+ snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d",
+ cbp, ia->prefix_len); else ia->saddr[0] = '\0';
+
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ return ia;
+}
+
+void
+ipv6_settempstale(struct interface *ifp)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+
+ state = IPV6_STATE(ifp);
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_TEMPORARY)
+ ia->flags |= IPV6_AF_STALE;
+ }
+}
+
+struct ipv6_addr *
+ipv6_settemptime(struct ipv6_addr *ia, int flags)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ap, *first;
+
+ state = IPV6_STATE(ia->iface);
+ first = NULL;
+ TAILQ_FOREACH_REVERSE(ap, &state->addrs, ipv6_addrhead, next) {
+ if (ap->flags & IPV6_AF_TEMPORARY &&
+ ap->prefix_pltime &&
+ IN6_ARE_ADDR_EQUAL(&ia->prefix, &ap->prefix))
+ {
+ time_t max, ext;
+
+ if (flags == 0) {
+ if (ap->prefix_pltime -
+ (uint32_t)(ia->acquired.tv_sec -
+ ap->acquired.tv_sec)
+ < REGEN_ADVANCE)
+ continue;
+
+ return ap;
+ }
+
+ if (!(ap->flags & IPV6_AF_ADDED))
+ ap->flags |= IPV6_AF_NEW | IPV6_AF_AUTOCONF;
+ ap->flags &= ~IPV6_AF_STALE;
+
+ /* RFC4941 Section 3.4
+ * Deprecated prefix, deprecate the temporary address */
+ if (ia->prefix_pltime == 0) {
+ ap->prefix_pltime = 0;
+ goto valid;
+ }
+
+ /* Ensure desync is still valid */
+ ipv6_regen_desync(ap->iface, 0);
+
+ /* RFC4941 Section 3.3.2
+ * Extend temporary times, but ensure that they
+ * never last beyond the system limit. */
+ ext = ia->acquired.tv_sec + (time_t)ia->prefix_pltime;
+ max = ap->created.tv_sec +
+ ip6_temp_preferred_lifetime(ap->iface->name) -
+ state->desync_factor;
+ if (ext < max)
+ ap->prefix_pltime = ia->prefix_pltime;
+ else
+ ap->prefix_pltime =
+ (uint32_t)(max - ia->acquired.tv_sec);
+
+valid:
+ ext = ia->acquired.tv_sec + (time_t)ia->prefix_vltime;
+ max = ap->created.tv_sec +
+ ip6_temp_valid_lifetime(ap->iface->name);
+ if (ext < max)
+ ap->prefix_vltime = ia->prefix_vltime;
+ else
+ ap->prefix_vltime =
+ (uint32_t)(max - ia->acquired.tv_sec);
+
+ /* Just extend the latest matching prefix */
+ ap->acquired = ia->acquired;
+
+ /* If extending return the last match as
+ * it's the most current.
+ * If deprecating, deprecate any other addresses we
+ * may have, although this should not be needed */
+ if (ia->prefix_pltime)
+ return ap;
+ if (first == NULL)
+ first = ap;
+ }
+ }
+ return first;
+}
+
+void
+ipv6_addtempaddrs(struct interface *ifp, const struct timespec *now)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+
+ state = IPV6_STATE(ifp);
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_TEMPORARY &&
+ !(ia->flags & IPV6_AF_STALE))
+ ipv6_addaddr(ia, now);
+ }
+}
+
+static void
+ipv6_regentempaddr(void *arg)
+{
+ struct ipv6_addr *ia = arg, *ia1;
+ struct timespec tv;
+
+ logger(ia->iface->ctx, LOG_DEBUG, "%s: regen temp addr %s",
+ ia->iface->name, ia->saddr);
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ ia1 = ipv6_createtempaddr(ia, &tv);
+ if (ia1)
+ ipv6_addaddr(ia1, &tv);
+ else
+ logger(ia->iface->ctx, LOG_ERR, "ipv6_createtempaddr: %m");
+}
+
+static void
+ipv6_regentempifid(void *arg)
+{
+ struct interface *ifp = arg;
+ struct ipv6_state *state;
+
+ state = IPV6_STATE(ifp);
+ if (memcmp(state->randomid, nullid, sizeof(state->randomid)))
+ ipv6_gentempifid(ifp);
+
+ ipv6_regen_desync(ifp, 1);
+}
+#endif /* IPV6_MANAGETEMPADDR */
+
+static struct rt6 *
+find_route6(struct rt6_head *rts, const struct rt6 *r)
+{
+ struct rt6 *rt;
+
+ TAILQ_FOREACH(rt, rts, next) {
+ if (IN6_ARE_ADDR_EQUAL(&rt->dest, &r->dest) &&
+#ifdef HAVE_ROUTE_METRIC
+ (r->iface == NULL || rt->iface == NULL ||
+ rt->iface->metric == r->iface->metric) &&
+#endif
+ IN6_ARE_ADDR_EQUAL(&rt->net, &r->net))
+ return rt;
+ }
+ return NULL;
+}
+
+static void
+desc_route(const char *cmd, const struct rt6 *rt)
+{
+ char destbuf[INET6_ADDRSTRLEN];
+ char gatebuf[INET6_ADDRSTRLEN];
+ const char *ifname, *dest, *gate;
+ struct dhcpcd_ctx *ctx;
+
+ ctx = rt->iface ? rt->iface->ctx : NULL;
+ ifname = rt->iface ? rt->iface->name : "(no iface)";
+ dest = inet_ntop(AF_INET6, &rt->dest, destbuf, INET6_ADDRSTRLEN);
+ gate = inet_ntop(AF_INET6, &rt->gate, gatebuf, INET6_ADDRSTRLEN);
+ if (IN6_ARE_ADDR_EQUAL(&rt->gate, &in6addr_any))
+ logger(ctx, LOG_INFO, "%s: %s route to %s/%d",
+ ifname, cmd, dest, ipv6_prefixlen(&rt->net));
+ else if (IN6_ARE_ADDR_EQUAL(&rt->dest, &in6addr_any) &&
+ IN6_ARE_ADDR_EQUAL(&rt->net, &in6addr_any))
+ logger(ctx, LOG_INFO, "%s: %s default route via %s",
+ ifname, cmd, gate);
+ else
+ logger(ctx, LOG_INFO, "%s: %s%s route to %s/%d via %s",
+ ifname, cmd,
+ rt->flags & RTF_REJECT ? " reject" : "",
+ dest, ipv6_prefixlen(&rt->net), gate);
+}
+
+static struct rt6*
+ipv6_findrt(struct dhcpcd_ctx *ctx, const struct rt6 *rt, int flags)
+{
+ struct rt6 *r;
+
+ TAILQ_FOREACH(r, &ctx->ipv6->kroutes, next) {
+ if (IN6_ARE_ADDR_EQUAL(&rt->dest, &r->dest) &&
+#ifdef HAVE_ROUTE_METRIC
+ (rt->iface == r->iface ||
+ (rt->flags & RTF_REJECT && r->flags & RTF_REJECT)) &&
+ (!flags || rt->metric == r->metric) &&
+#else
+ (!flags || rt->iface == r->iface ||
+ (rt->flags & RTF_REJECT && r->flags & RTF_REJECT)) &&
+#endif
+ IN6_ARE_ADDR_EQUAL(&rt->net, &r->net))
+ return r;
+ }
+ return NULL;
+}
+
+void
+ipv6_freerts(struct rt6_head *routes)
+{
+ struct rt6 *rt;
+
+ while ((rt = TAILQ_FIRST(routes))) {
+ TAILQ_REMOVE(routes, rt, next);
+ free(rt);
+ }
+}
+
+/* If something other than dhcpcd removes a route,
+ * we need to remove it from our internal table. */
+int
+ipv6_handlert(struct dhcpcd_ctx *ctx, int cmd, struct rt6 *rt)
+{
+ struct rt6 *f;
+
+ if (ctx->ipv6 == NULL)
+ return 0;
+
+ f = ipv6_findrt(ctx, rt, 1);
+ switch(cmd) {
+ case RTM_ADD:
+ if (f == NULL) {
+ if ((f = malloc(sizeof(*f))) == NULL)
+ return -1;
+ *f = *rt;
+ TAILQ_INSERT_TAIL(&ctx->ipv6->kroutes, f, next);
+ }
+ break;
+ case RTM_DELETE:
+ if (f) {
+ TAILQ_REMOVE(&ctx->ipv6->kroutes, f, next);
+ free(f);
+ }
+ /* If we manage the route, remove it */
+ if ((f = find_route6(ctx->ipv6->routes, rt))) {
+ desc_route("removing", f);
+ TAILQ_REMOVE(ctx->ipv6->routes, f, next);
+ free(f);
+ }
+ break;
+ }
+ return 0;
+}
+
+#define n_route(a) nc_route(NULL, a)
+#define c_route(a, b) nc_route(a, b)
+static int
+nc_route(struct rt6 *ort, struct rt6 *nrt)
+{
+ int change;
+
+ /* Don't set default routes if not asked to */
+ if (IN6_IS_ADDR_UNSPECIFIED(&nrt->dest) &&
+ IN6_IS_ADDR_UNSPECIFIED(&nrt->net) &&
+ !(nrt->iface->options->options & DHCPCD_GATEWAY))
+ return -1;
+
+ desc_route(ort == NULL ? "adding" : "changing", nrt);
+
+ change = 0;
+ if (ort == NULL) {
+ ort = ipv6_findrt(nrt->iface->ctx, nrt, 0);
+ if (ort &&
+ ((ort->flags & RTF_REJECT && nrt->flags & RTF_REJECT) ||
+ (ort->iface == nrt->iface &&
+#ifdef HAVE_ROUTE_METRIC
+ ort->metric == nrt->metric &&
+#endif
+ IN6_ARE_ADDR_EQUAL(&ort->gate, &nrt->gate))))
+ {
+ if (ort->mtu == nrt->mtu)
+ return 0;
+ change = 1;
+ }
+ }
+
+#ifdef RTF_CLONING
+ /* BSD can set routes to be cloning routes.
+ * Cloned routes inherit the parent flags.
+ * As such, we need to delete and re-add the route to flush children
+ * to correct the flags. */
+ if (change && ort != NULL && ort->flags & RTF_CLONING)
+ change = 0;
+#endif
+
+ if (change) {
+ if (if_route6(RTM_CHANGE, nrt) == 0)
+ return 0;
+ if (errno != ESRCH)
+ logger(nrt->iface->ctx, LOG_ERR, "if_route6 (CHG): %m");
+ }
+
+#ifdef HAVE_ROUTE_METRIC
+ /* With route metrics, we can safely add the new route before
+ * deleting the old route. */
+ if (if_route6(RTM_ADD, nrt) == 0) {
+ if (ort && if_route6(RTM_DELETE, ort) == -1 &&
+ errno != ESRCH)
+ logger(nrt->iface->ctx, LOG_ERR, "if_route6 (DEL): %m");
+ return 0;
+ }
+
+ /* If the kernel claims the route exists we need to rip out the
+ * old one first. */
+ if (errno != EEXIST || ort == NULL)
+ goto logerr;
+#endif
+
+ /* No route metrics, we need to delete the old route before
+ * adding the new one. */
+ if (ort && if_route6(RTM_DELETE, ort) == -1 && errno != ESRCH)
+ logger(nrt->iface->ctx, LOG_ERR, "if_route6: %m");
+ if (if_route6(RTM_ADD, nrt) == 0)
+ return 0;
+#ifdef HAVE_ROUTE_METRIC
+logerr:
+#endif
+ logger(nrt->iface->ctx, LOG_ERR, "if_route6 (ADD): %m");
+ return -1;
+}
+
+static int
+d_route(struct rt6 *rt)
+{
+ int retval;
+
+ desc_route("deleting", rt);
+ retval = if_route6(RTM_DELETE, rt);
+ if (retval != 0 && errno != ENOENT && errno != ESRCH)
+ logger(rt->iface->ctx, LOG_ERR,
+ "%s: if_delroute6: %m", rt->iface->name);
+ return retval;
+}
+
+static struct rt6 *
+make_route(const struct interface *ifp, const struct ra *rap)
+{
+ struct rt6 *r;
+
+ r = calloc(1, sizeof(*r));
+ if (r == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ r->iface = ifp;
+#ifdef HAVE_ROUTE_METRIC
+ r->metric = ifp->metric;
+#endif
+ if (rap)
+ r->mtu = rap->mtu;
+ else
+ r->mtu = 0;
+ return r;
+}
+
+static struct rt6 *
+make_prefix(const struct interface *ifp, const struct ra *rap,
+ const struct ipv6_addr *addr)
+{
+ struct rt6 *r;
+
+ if (addr == NULL || addr->prefix_len > 128) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* There is no point in trying to manage a /128 prefix,
+ * ones without a lifetime or ones not on link or delegated */
+ if (addr->prefix_len == 128 ||
+ addr->prefix_vltime == 0 ||
+ !(addr->flags & (IPV6_AF_ONLINK | IPV6_AF_DELEGATEDPFX)))
+ return NULL;
+
+ /* Don't install a blackhole route when not creating bigger prefixes */
+ if (addr->flags & IPV6_AF_DELEGATEDZERO)
+ return NULL;
+
+ r = make_route(ifp, rap);
+ if (r == NULL)
+ return NULL;
+ r->dest = addr->prefix;
+ ipv6_mask(&r->net, addr->prefix_len);
+ if (addr->flags & IPV6_AF_DELEGATEDPFX) {
+ r->flags |= RTF_REJECT;
+ r->gate = in6addr_loopback;
+ } else
+ r->gate = in6addr_any;
+ return r;
+}
+
+static struct rt6 *
+make_router(const struct ra *rap)
+{
+ struct rt6 *r;
+
+ r = make_route(rap->iface, rap);
+ if (r == NULL)
+ return NULL;
+ r->dest = in6addr_any;
+ r->net = in6addr_any;
+ r->gate = rap->from;
+ return r;
+}
+
+#define RT_IS_DEFAULT(rtp) \
+ (IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) && \
+ IN6_ARE_ADDR_EQUAL(&((rtp)->net), &in6addr_any))
+
+static void
+ipv6_build_ra_routes(struct ipv6_ctx *ctx, struct rt6_head *dnr, int expired)
+{
+ struct rt6 *rt;
+ struct ra *rap;
+ const struct ipv6_addr *addr;
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (rap->expired != expired)
+ continue;
+ if (rap->iface->options->options & DHCPCD_IPV6RA_OWN) {
+ TAILQ_FOREACH(addr, &rap->addrs, next) {
+ rt = make_prefix(rap->iface, rap, addr);
+ if (rt)
+ TAILQ_INSERT_TAIL(dnr, rt, next);
+ }
+ }
+ if (rap->lifetime && rap->iface->options->options &
+ (DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT) &&
+ !rap->no_public_warned)
+ {
+ rt = make_router(rap);
+ if (rt)
+ TAILQ_INSERT_TAIL(dnr, rt, next);
+ }
+ }
+}
+
+static void
+ipv6_build_dhcp_routes(struct dhcpcd_ctx *ctx,
+ struct rt6_head *dnr, enum DH6S dstate)
+{
+ const struct interface *ifp;
+ const struct dhcp6_state *d6_state;
+ const struct ipv6_addr *addr;
+ struct rt6 *rt;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ d6_state = D6_CSTATE(ifp);
+ if (d6_state && d6_state->state == dstate) {
+ TAILQ_FOREACH(addr, &d6_state->addrs, next) {
+ rt = make_prefix(ifp, NULL, addr);
+ if (rt)
+ TAILQ_INSERT_TAIL(dnr, rt, next);
+ }
+ }
+ }
+}
+
+void
+ipv6_buildroutes(struct dhcpcd_ctx *ctx)
+{
+ struct rt6_head dnr, *nrs;
+ struct rt6 *rt, *rtn, *or;
+ uint8_t have_default;
+ unsigned long long o;
+
+ /* We need to have the interfaces in the correct order to ensure
+ * our routes are managed correctly. */
+ if_sortinterfaces(ctx);
+
+ TAILQ_INIT(&dnr);
+
+ /* First add reachable routers and their prefixes */
+ ipv6_build_ra_routes(ctx->ipv6, &dnr, 0);
+#ifdef HAVE_ROUTE_METRIC
+ have_default = (TAILQ_FIRST(&dnr) != NULL);
+#endif
+
+ /* We have no way of knowing if prefixes added by DHCP are reachable
+ * or not, so we have to assume they are.
+ * Add bound before delegated so we can prefer interfaces better */
+ ipv6_build_dhcp_routes(ctx, &dnr, DH6S_BOUND);
+ ipv6_build_dhcp_routes(ctx, &dnr, DH6S_DELEGATED);
+
+#ifdef HAVE_ROUTE_METRIC
+ /* If we have an unreachable router, we really do need to remove the
+ * route to it beause it could be a lower metric than a reachable
+ * router. Of course, we should at least have some routers if all
+ * are unreachable. */
+ if (!have_default)
+#endif
+ /* Add our non-reachable routers and prefixes
+ * Unsure if this is needed, but it's a close match to kernel
+ * behaviour */
+ ipv6_build_ra_routes(ctx->ipv6, &dnr, 1);
+
+ nrs = malloc(sizeof(*nrs));
+ if (nrs == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ TAILQ_INIT(nrs);
+ have_default = 0;
+
+ TAILQ_FOREACH_SAFE(rt, &dnr, next, rtn) {
+ /* Is this route already in our table? */
+ if (find_route6(nrs, rt) != NULL)
+ continue;
+ //rt->src.s_addr = ifp->addr.s_addr;
+ /* Do we already manage it? */
+ if ((or = find_route6(ctx->ipv6->routes, rt))) {
+ if (or->iface != rt->iface ||
+#ifdef HAVE_ROUTE_METRIC
+ rt->metric != or->metric ||
+#endif
+ // or->src.s_addr != ifp->addr.s_addr ||
+ !IN6_ARE_ADDR_EQUAL(&rt->gate, &or->gate))
+ {
+ if (c_route(or, rt) != 0)
+ continue;
+ }
+ TAILQ_REMOVE(ctx->ipv6->routes, or, next);
+ free(or);
+ } else {
+ if (n_route(rt) != 0)
+ continue;
+ }
+ if (RT_IS_DEFAULT(rt))
+ have_default = 1;
+ TAILQ_REMOVE(&dnr, rt, next);
+ TAILQ_INSERT_TAIL(nrs, rt, next);
+ }
+
+ /* Free any routes we failed to add/change */
+ while ((rt = TAILQ_FIRST(&dnr))) {
+ TAILQ_REMOVE(&dnr, rt, next);
+ free(rt);
+ }
+
+ /* Remove old routes we used to manage
+ * If we own the default route, but not RA management itself
+ * then we need to preserve the last best default route we had */
+ while ((rt = TAILQ_LAST(ctx->ipv6->routes, rt6_head))) {
+ TAILQ_REMOVE(ctx->ipv6->routes, rt, next);
+ if (find_route6(nrs, rt) == NULL) {
+ o = rt->iface->options->options;
+ if (!have_default &&
+ (o & DHCPCD_IPV6RA_OWN_DEFAULT) &&
+ !(o & DHCPCD_IPV6RA_OWN) &&
+ RT_IS_DEFAULT(rt))
+ have_default = 1;
+ /* no need to add it back to our routing table
+ * as we delete an exiting route when we add
+ * a new one */
+ else if ((rt->iface->options->options &
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT))
+ d_route(rt);
+ }
+ free(rt);
+ }
+
+ free(ctx->ipv6->routes);
+ ctx->ipv6->routes = nrs;
+}
--- /dev/null
+/* $NetBSD: ipv6.h,v 1.14 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef IPV6_H
+#define IPV6_H
+
+#include <sys/uio.h>
+#include <netinet/in.h>
+
+#ifndef __linux__
+# ifndef __QNX__
+# include <sys/endian.h>
+# endif
+# include <net/if.h>
+# ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */
+# include <net/if_var.h>
+# endif
+# ifndef __sun
+# include <netinet6/in6_var.h>
+# endif
+#endif
+
+#include "config.h"
+#include "dhcpcd.h"
+
+#define ALLROUTERS "ff02::2"
+
+#define ROUNDUP8(a) (1 + (((a) - 1) | 7))
+#define ROUNDUP16(a) (1 + (((a) - 1) | 16))
+
+#define EUI64_GBIT 0x01
+#define EUI64_UBIT 0x02
+#define EUI64_TO_IFID(in6) do {(in6)->s6_addr[8] ^= EUI64_UBIT; } while (0)
+#define EUI64_GROUP(in6) ((in6)->s6_addr[8] & EUI64_GBIT)
+
+#ifndef ND6_INFINITE_LIFETIME
+# define ND6_INFINITE_LIFETIME ((uint32_t)~0)
+#endif
+
+/* RFC4941 constants */
+#define TEMP_VALID_LIFETIME 604800 /* 1 week */
+#define TEMP_PREFERRED_LIFETIME 86400 /* 1 day */
+#define REGEN_ADVANCE 5 /* seconds */
+#define MAX_DESYNC_FACTOR 600 /* 10 minutes */
+
+#define TEMP_IDGEN_RETRIES 3
+#define GEN_TEMPID_RETRY_MAX 5
+
+/* RFC7217 constants */
+#define IDGEN_RETRIES 3
+#define IDGEN_DELAY 1 /* second */
+
+/*
+ * BSD kernels don't inform userland of DAD results.
+ * See the discussion here:
+ * http://mail-index.netbsd.org/tech-net/2013/03/15/msg004019.html
+ */
+#ifndef __linux__
+/* We guard here to avoid breaking a compile on linux ppc-64 headers */
+# include <sys/param.h>
+#endif
+#ifdef BSD
+# define IPV6_POLLADDRFLAG
+#endif
+
+/* This was fixed in NetBSD */
+#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 699002000
+# undef IPV6_POLLADDRFLAG
+#endif
+
+/* Linux-3.18 can manage temporary addresses even with RA
+ * processing disabled. */
+//#undef IFA_F_MANAGETEMPADDR
+#if defined(__linux__) && defined(IFA_F_MANAGETEMPADDR)
+#define IPV6_MANAGETEMPADDR
+#endif
+
+/* Some BSDs do not allow userland to set temporary addresses. */
+#if defined(BSD) && defined(IN6_IFF_TEMPORARY)
+#define IPV6_MANAGETEMPADDR
+#endif
+
+struct ipv6_addr {
+ TAILQ_ENTRY(ipv6_addr) next;
+ struct interface *iface;
+ struct in6_addr prefix;
+ uint8_t prefix_len;
+ uint32_t prefix_vltime;
+ uint32_t prefix_pltime;
+ struct timespec created;
+ struct timespec acquired;
+ struct in6_addr addr;
+ int addr_flags;
+ short flags;
+ char saddr[INET6_ADDRSTRLEN];
+ uint8_t iaid[4];
+ uint16_t ia_type;
+ struct interface *delegating_iface;
+ uint8_t prefix_exclude_len;
+ struct in6_addr prefix_exclude;
+
+ void (*dadcallback)(void *);
+ int dadcounter;
+ uint8_t *ns;
+ size_t nslen;
+ int nsprobes;
+};
+TAILQ_HEAD(ipv6_addrhead, ipv6_addr);
+
+#define IPV6_AF_ONLINK 0x0001
+#define IPV6_AF_NEW 0x0002
+#define IPV6_AF_STALE 0x0004
+#define IPV6_AF_ADDED 0x0008
+#define IPV6_AF_AUTOCONF 0x0010
+#define IPV6_AF_DUPLICATED 0x0020
+#define IPV6_AF_DADCOMPLETED 0x0040
+#define IPV6_AF_DELEGATED 0x0080
+#define IPV6_AF_DELEGATEDPFX 0x0100
+#define IPV6_AF_DELEGATEDZERO 0x0200
+#define IPV6_AF_REQUEST 0x0400
+#ifdef IPV6_MANAGETEMPADDR
+#define IPV6_AF_TEMPORARY 0X0800
+#endif
+
+struct rt6 {
+ TAILQ_ENTRY(rt6) next;
+ struct in6_addr dest;
+ struct in6_addr net;
+ struct in6_addr gate;
+ const struct interface *iface;
+ unsigned int flags;
+#ifdef HAVE_ROUTE_METRIC
+ unsigned int metric;
+#endif
+ unsigned int mtu;
+};
+TAILQ_HEAD(rt6_head, rt6);
+
+struct ll_callback {
+ TAILQ_ENTRY(ll_callback) next;
+ void (*callback)(void *);
+ void *arg;
+};
+TAILQ_HEAD(ll_callback_head, ll_callback);
+
+struct ipv6_state {
+ struct ipv6_addrhead addrs;
+ struct ll_callback_head ll_callbacks;
+
+#ifdef IPV6_MANAGETEMPADDR
+ time_t desync_factor;
+ uint8_t randomseed0[8]; /* upper 64 bits of MD5 digest */
+ uint8_t randomseed1[8]; /* lower 64 bits */
+ uint8_t randomid[8];
+#endif
+};
+
+#define IPV6_STATE(ifp) \
+ ((struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6])
+#define IPV6_CSTATE(ifp) \
+ ((const struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6])
+
+/* dhcpcd requires CMSG_SPACE to evaluate to a compile time constant. */
+#ifdef __QNX__
+#undef CMSG_SPACE
+#endif
+
+#ifndef ALIGNBYTES
+#define ALIGNBYTES (sizeof(int) - 1)
+#endif
+#ifndef ALIGN
+#define ALIGN(p) (((unsigned int)(p) + ALIGNBYTES) & ~ALIGNBYTES)
+#endif
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(len) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(len))
+#endif
+
+#define IP6BUFLEN (CMSG_SPACE(sizeof(struct in6_pktinfo)) + \
+ CMSG_SPACE(sizeof(int)))
+
+
+#ifdef INET6
+struct ipv6_ctx {
+ struct sockaddr_in6 from;
+ struct msghdr sndhdr;
+ struct iovec sndiov[2];
+ unsigned char sndbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ struct msghdr rcvhdr;
+ struct iovec rcviov[2];
+ unsigned char rcvbuf[IP6BUFLEN];
+ unsigned char ansbuf[1500];
+ char ntopbuf[INET6_ADDRSTRLEN];
+ const char *sfrom;
+
+ int nd_fd;
+ struct ra_head *ra_routers;
+ struct rt6_head *routes;
+
+ struct rt6_head kroutes;
+
+ int dhcp_fd;
+};
+
+struct ipv6_ctx *ipv6_init(struct dhcpcd_ctx *);
+ssize_t ipv6_printaddr(char *, size_t, const uint8_t *, const char *);
+int ipv6_makestableprivate(struct in6_addr *addr,
+ const struct in6_addr *prefix, int prefix_len,
+ const struct interface *ifp, int *dad_counter);
+int ipv6_makeaddr(struct in6_addr *, const struct interface *,
+ const struct in6_addr *, int);
+int ipv6_makeprefix(struct in6_addr *, const struct in6_addr *, int);
+int ipv6_mask(struct in6_addr *, int);
+uint8_t ipv6_prefixlen(const struct in6_addr *);
+int ipv6_userprefix( const struct in6_addr *, short prefix_len,
+ uint64_t user_number, struct in6_addr *result, short result_len);
+void ipv6_checkaddrflags(void *);
+int ipv6_addaddr(struct ipv6_addr *, const struct timespec *);
+ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs);
+void ipv6_freedrop_addrs(struct ipv6_addrhead *, int,
+ const struct interface *);
+void ipv6_handleifa(struct dhcpcd_ctx *ctx, int, struct if_head *,
+ const char *, const struct in6_addr *, uint8_t, int);
+int ipv6_handleifa_addrs(int, struct ipv6_addrhead *,
+ const struct in6_addr *, int);
+int ipv6_publicaddr(const struct ipv6_addr *);
+const struct ipv6_addr *ipv6_iffindaddr(const struct interface *,
+ const struct in6_addr *);
+int ipv6_hasaddr(const struct interface *);
+int ipv6_findaddrmatch(const struct ipv6_addr *, const struct in6_addr *,
+ short);
+struct ipv6_addr *ipv6_findaddr(struct dhcpcd_ctx *,
+ const struct in6_addr *, short);
+#define ipv6_linklocal(ifp) ipv6_iffindaddr((ifp), NULL)
+int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *);
+void ipv6_freeaddr(struct ipv6_addr *);
+void ipv6_freedrop(struct interface *, int);
+#define ipv6_free(ifp) ipv6_freedrop((ifp), 0)
+#define ipv6_drop(ifp) ipv6_freedrop((ifp), 2)
+
+#ifdef IPV6_MANAGETEMPADDR
+void ipv6_gentempifid(struct interface *);
+void ipv6_settempstale(struct interface *);
+struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *,
+ const struct timespec *);
+struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int);
+void ipv6_addtempaddrs(struct interface *, const struct timespec *);
+#else
+#define ipv6_gentempifid(a) {}
+#define ipv6_settempstale(a) {}
+#endif
+
+int ipv6_start(struct interface *);
+void ipv6_ctxfree(struct dhcpcd_ctx *);
+int ipv6_handlert(struct dhcpcd_ctx *, int cmd, struct rt6 *);
+void ipv6_freerts(struct rt6_head *);
+void ipv6_buildroutes(struct dhcpcd_ctx *);
+
+#else
+#define ipv6_init(a) (NULL)
+#define ipv6_start(a) (-1)
+#define ipv6_hasaddr(a) (0)
+#define ipv6_free_ll_callbacks(a) {}
+#define ipv6_free(a) {}
+#define ipv6_drop(a) {}
+#define ipv6_ctxfree(a) {}
+#define ipv6_gentempifid(a) {}
+#endif
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: ipv6nd.c,v 1.26 2015/08/21 10:39:00 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/ioctl.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE 3
+#include "common.h"
+#include "dhcpcd.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "if.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+#include "script.h"
+
+/* Debugging Router Solicitations is a lot of spam, so disable it */
+//#define DEBUG_RS
+
+#ifndef ND_OPT_RDNSS
+#define ND_OPT_RDNSS 25
+struct nd_opt_rdnss { /* RDNSS option RFC 6106 */
+ uint8_t nd_opt_rdnss_type;
+ uint8_t nd_opt_rdnss_len;
+ uint16_t nd_opt_rdnss_reserved;
+ uint32_t nd_opt_rdnss_lifetime;
+ /* followed by list of IP prefixes */
+} __packed;
+#endif
+
+#ifndef ND_OPT_DNSSL
+#define ND_OPT_DNSSL 31
+struct nd_opt_dnssl { /* DNSSL option RFC 6106 */
+ uint8_t nd_opt_dnssl_type;
+ uint8_t nd_opt_dnssl_len;
+ uint16_t nd_opt_dnssl_reserved;
+ uint32_t nd_opt_dnssl_lifetime;
+ /* followed by list of DNS servers */
+} __packed;
+#endif
+
+/* Impossible options, so we can easily add extras */
+#define _ND_OPT_PREFIX_ADDR 255 + 1
+
+/* Minimal IPv6 MTU */
+#ifndef IPV6_MMTU
+#define IPV6_MMTU 1280
+#endif
+
+#ifndef ND_RA_FLAG_RTPREF_HIGH
+#define ND_RA_FLAG_RTPREF_MASK 0x18
+#define ND_RA_FLAG_RTPREF_HIGH 0x08
+#define ND_RA_FLAG_RTPREF_MEDIUM 0x00
+#define ND_RA_FLAG_RTPREF_LOW 0x18
+#define ND_RA_FLAG_RTPREF_RSV 0x10
+#endif
+
+/* RTPREF_MEDIUM has to be 0! */
+#define RTPREF_HIGH 1
+#define RTPREF_MEDIUM 0
+#define RTPREF_LOW (-1)
+#define RTPREF_RESERVED (-2)
+#define RTPREF_INVALID (-3) /* internal */
+
+#define MIN_RANDOM_FACTOR 500 /* millisecs */
+#define MAX_RANDOM_FACTOR 1500 /* millisecs */
+#define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */
+#define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define IPV6_ADDR_INT32_ONE 1
+#define IPV6_ADDR_INT16_MLL 0xff02
+#elif BYTE_ORDER == LITTLE_ENDIAN
+#define IPV6_ADDR_INT32_ONE 0x01000000
+#define IPV6_ADDR_INT16_MLL 0x02ff
+#endif
+
+/* Debugging Neighbor Solicitations is a lot of spam, so disable it */
+//#define DEBUG_NS
+//
+
+static void ipv6nd_handledata(void *);
+
+/*
+ * Android ships buggy ICMP6 filter headers.
+ * Supply our own until they fix their shit.
+ * References:
+ * https://android-review.googlesource.com/#/c/58438/
+ * http://code.google.com/p/android/issues/original?id=32621&seq=24
+ */
+#ifdef __ANDROID__
+#undef ICMP6_FILTER_WILLPASS
+#undef ICMP6_FILTER_WILLBLOCK
+#undef ICMP6_FILTER_SETPASS
+#undef ICMP6_FILTER_SETBLOCK
+#undef ICMP6_FILTER_SETPASSALL
+#undef ICMP6_FILTER_SETBLOCKALL
+#define ICMP6_FILTER_WILLPASS(type, filterp) \
+ ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0)
+#define ICMP6_FILTER_WILLBLOCK(type, filterp) \
+ ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0)
+#define ICMP6_FILTER_SETPASS(type, filterp) \
+ ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))))
+#define ICMP6_FILTER_SETBLOCK(type, filterp) \
+ ((((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31))))
+#define ICMP6_FILTER_SETPASSALL(filterp) \
+ memset(filterp, 0, sizeof(struct icmp6_filter));
+#define ICMP6_FILTER_SETBLOCKALL(filterp) \
+ memset(filterp, 0xff, sizeof(struct icmp6_filter));
+#endif
+
+/* Support older systems with different defines */
+#if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT)
+#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
+#endif
+#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)
+#define IPV6_RECVPKTINFO IPV6_PKTINFO
+#endif
+
+void
+ipv6nd_printoptions(const struct dhcpcd_ctx *ctx,
+ const struct dhcp_opt *opts, size_t opts_len)
+{
+ size_t i, j;
+ const struct dhcp_opt *opt, *opt2;
+ int cols;
+
+ for (i = 0, opt = ctx->nd_opts;
+ i < ctx->nd_opts_len; i++, opt++)
+ {
+ for (j = 0, opt2 = opts; j < opts_len; j++, opt2++)
+ if (opt2->option == opt->option)
+ break;
+ if (j == opts_len) {
+ cols = printf("%03d %s", opt->option, opt->var);
+ dhcp_print_option_encoding(opt, cols);
+ }
+ }
+ for (i = 0, opt = opts; i < opts_len; i++, opt++) {
+ cols = printf("%03d %s", opt->option, opt->var);
+ dhcp_print_option_encoding(opt, cols);
+ }
+}
+
+static int
+ipv6nd_open(struct dhcpcd_ctx *dctx)
+{
+ struct ipv6_ctx *ctx;
+ int on;
+ struct icmp6_filter filt;
+
+ ctx = dctx->ipv6;
+ if (ctx->nd_fd != -1)
+ return ctx->nd_fd;
+ ctx->nd_fd = xsocket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6,
+ O_NONBLOCK|O_CLOEXEC);
+ if (ctx->nd_fd == -1)
+ return -1;
+
+ /* RFC4861 4.1 */
+ on = 255;
+ if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+
+ on = 1;
+ if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+
+ on = 1;
+ if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ if (setsockopt(ctx->nd_fd, IPPROTO_ICMPV6, ICMP6_FILTER,
+ &filt, sizeof(filt)) == -1)
+ goto eexit;
+
+ eloop_event_add(dctx->eloop, ctx->nd_fd,
+ ipv6nd_handledata, dctx, NULL, NULL);
+ return ctx->nd_fd;
+
+eexit:
+ if (ctx->nd_fd != -1) {
+ eloop_event_delete(dctx->eloop, ctx->nd_fd);
+ close(ctx->nd_fd);
+ ctx->nd_fd = -1;
+ }
+ return -1;
+}
+
+static int
+ipv6nd_makersprobe(struct interface *ifp)
+{
+ struct rs_state *state;
+ struct nd_router_solicit *rs;
+ struct nd_opt_hdr *nd;
+
+ state = RS_STATE(ifp);
+ free(state->rs);
+ state->rslen = sizeof(*rs) + (size_t)ROUNDUP8(ifp->hwlen + 2);
+ state->rs = calloc(1, state->rslen);
+ if (state->rs == NULL)
+ return -1;
+ rs = (struct nd_router_solicit *)(void *)state->rs;
+ rs->nd_rs_type = ND_ROUTER_SOLICIT;
+ rs->nd_rs_code = 0;
+ rs->nd_rs_cksum = 0;
+ rs->nd_rs_reserved = 0;
+ nd = (struct nd_opt_hdr *)(state->rs + sizeof(*rs));
+ nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ nd->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
+ memcpy(nd + 1, ifp->hwaddr, ifp->hwlen);
+ return 0;
+}
+
+static void
+ipv6nd_sendrsprobe(void *arg)
+{
+ struct interface *ifp = arg;
+ struct ipv6_ctx *ctx;
+ struct rs_state *state;
+ struct sockaddr_in6 dst;
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi;
+
+ if (ipv6_linklocal(ifp) == NULL) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: delaying Router Solicitation for LL address",
+ ifp->name);
+ ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp);
+ return;
+ }
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+ dst.sin6_len = sizeof(dst);
+#endif
+ dst.sin6_scope_id = ifp->index;
+ if (inet_pton(AF_INET6, ALLROUTERS, &dst.sin6_addr) != 1) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+
+ state = RS_STATE(ifp);
+ ctx = ifp->ctx->ipv6;
+ ctx->sndhdr.msg_name = (void *)&dst;
+ ctx->sndhdr.msg_iov[0].iov_base = state->rs;
+ ctx->sndhdr.msg_iov[0].iov_len = state->rslen;
+
+ /* Set the outbound interface */
+ cm = CMSG_FIRSTHDR(&ctx->sndhdr);
+ if (cm == NULL) /* unlikely */
+ return;
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memset(&pi, 0, sizeof(pi));
+ pi.ipi6_ifindex = ifp->index;
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: sending Router Solicitation", ifp->name);
+ if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: %s: sendmsg: %m", ifp->name, __func__);
+ ipv6nd_drop(ifp);
+ ifp->options->options &= ~(DHCPCD_IPV6 | DHCPCD_IPV6RS);
+ return;
+ }
+
+ if (state->rsprobes++ < MAX_RTR_SOLICITATIONS)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp);
+ else {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: no IPv6 Routers available", ifp->name);
+ ipv6nd_drop(ifp);
+ dhcp6_drop(ifp, "EXPIRE6");
+ }
+}
+
+void
+ipv6nd_expire(struct interface *ifp, uint32_t seconds)
+{
+ struct ra *rap;
+ struct timespec now;
+
+ if (ifp->ctx->ipv6 == NULL)
+ return;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface == ifp) {
+ rap->acquired = now;
+ rap->expired = seconds ? 0 : 1;
+ if (seconds) {
+ struct ipv6_addr *ap;
+
+ rap->lifetime = seconds;
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if (ap->prefix_vltime) {
+ ap->prefix_vltime = seconds;
+ ap->prefix_pltime = seconds / 2;
+ }
+ }
+ ipv6_addaddrs(&rap->addrs);
+ }
+ }
+ }
+ if (seconds)
+ ipv6nd_expirera(ifp);
+ else
+ ipv6_buildroutes(ifp->ctx);
+}
+
+static void
+ipv6nd_reachable(struct ra *rap, int flags)
+{
+
+ if (flags & IPV6ND_REACHABLE) {
+ if (rap->lifetime && rap->expired) {
+ logger(rap->iface->ctx, LOG_INFO,
+ "%s: %s is reachable again",
+ rap->iface->name, rap->sfrom);
+ rap->expired = 0;
+ ipv6_buildroutes(rap->iface->ctx);
+ /* XXX Not really an RA */
+ script_runreason(rap->iface, "ROUTERADVERT");
+ }
+ } else {
+ if (rap->lifetime && !rap->expired) {
+ logger(rap->iface->ctx, LOG_WARNING,
+ "%s: %s is unreachable, expiring it",
+ rap->iface->name, rap->sfrom);
+ rap->expired = 1;
+ ipv6_buildroutes(rap->iface->ctx);
+ /* XXX Not really an RA */
+ script_runreason(rap->iface, "ROUTERADVERT");
+ }
+ }
+}
+
+void
+ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags)
+{
+ struct ra *rap;
+
+ if (ctx->ipv6) {
+ TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
+ if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) {
+ ipv6nd_reachable(rap, flags);
+ break;
+ }
+ }
+ }
+}
+
+const struct ipv6_addr *
+ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr,
+ short flags)
+{
+ struct ra *rap;
+ struct ipv6_addr *ap;
+
+ if (ifp->ctx->ipv6 == NULL)
+ return NULL;
+
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface != ifp)
+ continue;
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if (ipv6_findaddrmatch(ap, addr, flags))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+struct ipv6_addr *
+ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr,
+ short flags)
+{
+ struct ra *rap;
+ struct ipv6_addr *ap;
+
+ if (ctx->ipv6 == NULL)
+ return NULL;
+
+ TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if (ipv6_findaddrmatch(ap, addr, flags))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+static void
+ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra)
+{
+
+ eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface);
+ eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap);
+ if (remove_ra && !drop_ra)
+ TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next);
+ ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL);
+ free(rap->data);
+ free(rap);
+}
+
+void
+ipv6nd_freedrop_ra(struct ra *rap, int drop)
+{
+
+ ipv6nd_removefreedrop_ra(rap, 1, drop);
+}
+
+ssize_t
+ipv6nd_free(struct interface *ifp)
+{
+ struct rs_state *state;
+ struct ra *rap, *ran;
+ struct dhcpcd_ctx *ctx;
+ ssize_t n;
+
+ state = RS_STATE(ifp);
+ if (state == NULL)
+ return 0;
+
+ free(state->rs);
+ free(state);
+ ifp->if_data[IF_DATA_IPV6ND] = NULL;
+ n = 0;
+ TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) {
+ if (rap->iface == ifp) {
+ ipv6nd_free_ra(rap);
+ n++;
+ }
+ }
+
+ /* If we don't have any more IPv6 enabled interfaces,
+ * close the global socket and release resources */
+ ctx = ifp->ctx;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (RS_STATE(ifp))
+ break;
+ }
+ if (ifp == NULL) {
+ if (ctx->ipv6->nd_fd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->ipv6->nd_fd);
+ close(ctx->ipv6->nd_fd);
+ ctx->ipv6->nd_fd = -1;
+ }
+ }
+
+ return n;
+}
+
+static int
+rtpref(struct ra *rap)
+{
+
+ switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) {
+ case ND_RA_FLAG_RTPREF_HIGH:
+ return (RTPREF_HIGH);
+ case ND_RA_FLAG_RTPREF_MEDIUM:
+ case ND_RA_FLAG_RTPREF_RSV:
+ return (RTPREF_MEDIUM);
+ case ND_RA_FLAG_RTPREF_LOW:
+ return (RTPREF_LOW);
+ default:
+ logger(rap->iface->ctx, LOG_ERR,
+ "rtpref: impossible RA flag %x", rap->flags);
+ return (RTPREF_INVALID);
+ }
+ /* NOTREACHED */
+}
+
+static void
+add_router(struct ipv6_ctx *ctx, struct ra *router)
+{
+ struct ra *rap;
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (router->iface->metric < rap->iface->metric ||
+ (router->iface->metric == rap->iface->metric &&
+ rtpref(router) > rtpref(rap)))
+ {
+ TAILQ_INSERT_BEFORE(rap, router, next);
+ return;
+ }
+ }
+ TAILQ_INSERT_TAIL(ctx->ra_routers, router, next);
+}
+
+static int
+ipv6nd_scriptrun(struct ra *rap)
+{
+ int hasdns, hasaddress, pid;
+ struct ipv6_addr *ap;
+
+ hasaddress = 0;
+ /* If all addresses have completed DAD run the script */
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if ((ap->flags & (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) ==
+ (IPV6_AF_AUTOCONF | IPV6_AF_ADDED))
+ {
+ hasaddress = 1;
+ if (!(ap->flags & IPV6_AF_DADCOMPLETED) &&
+ ipv6_iffindaddr(ap->iface, &ap->addr))
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) {
+ logger(ap->iface->ctx, LOG_DEBUG,
+ "%s: waiting for Router Advertisement"
+ " DAD to complete",
+ rap->iface->name);
+ return 0;
+ }
+ }
+ }
+
+ /* If we don't require RDNSS then set hasdns = 1 so we fork */
+ if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS))
+ hasdns = 1;
+ else {
+ hasdns = rap->hasdns;
+ }
+
+ script_runreason(rap->iface, "ROUTERADVERT");
+ pid = 0;
+ if (hasdns && (hasaddress ||
+ !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))))
+ pid = dhcpcd_daemonise(rap->iface->ctx);
+#if 0
+ else if (options & DHCPCD_DAEMONISE &&
+ !(options & DHCPCD_DAEMONISED) && new_data)
+ logger(rap->iface->ctx, LOG_WARNING,
+ "%s: did not fork due to an absent"
+ " RDNSS option in the RA",
+ ifp->name);
+}
+#endif
+ return pid;
+}
+
+static void
+ipv6nd_addaddr(void *arg)
+{
+ struct ipv6_addr *ap = arg;
+
+ ipv6_addaddr(ap, NULL);
+}
+
+int
+ipv6nd_dadcompleted(const struct interface *ifp)
+{
+ const struct ra *rap;
+ const struct ipv6_addr *ap;
+
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface != ifp)
+ continue;
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if (ap->flags & IPV6_AF_AUTOCONF &&
+ ap->flags & IPV6_AF_ADDED &&
+ !(ap->flags & IPV6_AF_DADCOMPLETED))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void
+ipv6nd_dadcallback(void *arg)
+{
+ struct ipv6_addr *ap = arg, *rapap;
+ struct interface *ifp;
+ struct ra *rap;
+ int wascompleted, found;
+ struct timespec tv;
+ char buf[INET6_ADDRSTRLEN];
+ const char *p;
+ int dadcounter;
+
+ ifp = ap->iface;
+ wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED);
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ if (ap->flags & IPV6_AF_DUPLICATED) {
+ ap->dadcounter++;
+ logger(ifp->ctx, LOG_WARNING, "%s: DAD detected %s",
+ ifp->name, ap->saddr);
+
+ /* Try and make another stable private address.
+ * Because ap->dadcounter is always increamented,
+ * a different address is generated. */
+ /* XXX Cache DAD counter per prefix/id/ssid? */
+ if (ifp->options->options & DHCPCD_SLAACPRIVATE) {
+ if (ap->dadcounter >= IDGEN_RETRIES) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: unable to obtain a"
+ " stable private address",
+ ifp->name);
+ goto try_script;
+ }
+ logger(ifp->ctx, LOG_INFO, "%s: deleting address %s",
+ ifp->name, ap->saddr);
+ if (if_deladdress6(ap) == -1 &&
+ errno != EADDRNOTAVAIL && errno != ENXIO)
+ logger(ifp->ctx, LOG_ERR, "if_deladdress6: %m");
+ dadcounter = ap->dadcounter;
+ if (ipv6_makestableprivate(&ap->addr,
+ &ap->prefix, ap->prefix_len,
+ ifp, &dadcounter) == -1)
+ {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: ipv6_makestableprivate: %m",
+ ifp->name);
+ return;
+ }
+ ap->dadcounter = dadcounter;
+ ap->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
+ ap->flags |= IPV6_AF_NEW;
+ p = inet_ntop(AF_INET6, &ap->addr, buf, sizeof(buf));
+ if (p)
+ snprintf(ap->saddr,
+ sizeof(ap->saddr),
+ "%s/%d",
+ p, ap->prefix_len);
+ else
+ ap->saddr[0] = '\0';
+ tv.tv_sec = 0;
+ tv.tv_nsec = (suseconds_t)
+ arc4random_uniform(IDGEN_DELAY * NSEC_PER_SEC);
+ timespecnorm(&tv);
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv,
+ ipv6nd_addaddr, ap);
+ return;
+ }
+ }
+
+try_script:
+ if (!wascompleted) {
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface != ifp)
+ continue;
+ wascompleted = 1;
+ found = 0;
+ TAILQ_FOREACH(rapap, &rap->addrs, next) {
+ if (rapap->flags & IPV6_AF_AUTOCONF &&
+ rapap->flags & IPV6_AF_ADDED &&
+ (rapap->flags & IPV6_AF_DADCOMPLETED) == 0)
+ {
+ wascompleted = 0;
+ break;
+ }
+ if (rapap == ap)
+ found = 1;
+ }
+
+ if (wascompleted && found) {
+ logger(rap->iface->ctx, LOG_DEBUG,
+ "%s: Router Advertisement DAD completed",
+ rap->iface->name);
+ if (ipv6nd_scriptrun(rap))
+ return;
+ }
+ }
+ }
+}
+
+static int
+ipv6nd_has_public_addr(const struct interface *ifp)
+{
+ const struct ra *rap;
+ const struct ipv6_addr *ia;
+
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface == ifp) {
+ TAILQ_FOREACH(ia, &rap->addrs, next) {
+ if (ia->flags & IPV6_AF_AUTOCONF &&
+ ipv6_publicaddr(ia))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void
+ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp,
+ struct icmp6_hdr *icp, size_t len, int hoplimit)
+{
+ struct ipv6_ctx *ctx = dctx->ipv6;
+ size_t i, olen;
+ struct nd_router_advert *nd_ra;
+ struct nd_opt_prefix_info *pi;
+ struct nd_opt_mtu *mtu;
+ struct nd_opt_rdnss *rdnss;
+ uint32_t mtuv;
+ uint8_t *p;
+ char buf[INET6_ADDRSTRLEN];
+ const char *cbp;
+ struct ra *rap;
+ struct nd_opt_hdr *ndo;
+ struct ipv6_addr *ap;
+ struct dhcp_opt *dho;
+ uint8_t new_rap, new_data;
+#ifdef IPV6_MANAGETEMPADDR
+ uint8_t new_ap;
+#endif
+
+ if (ifp == NULL) {
+#ifdef DEBUG_RS
+ logger(dctx, LOG_DEBUG,
+ "RA for unexpected interface from %s", ctx->sfrom);
+#endif
+ return;
+ }
+
+ if (len < sizeof(struct nd_router_advert)) {
+ logger(dctx, LOG_ERR,
+ "IPv6 RA packet too short from %s", ctx->sfrom);
+ return;
+ }
+
+ /* RFC 4861 7.1.2 */
+ if (hoplimit != 255) {
+ logger(dctx, LOG_ERR,
+ "invalid hoplimit(%d) in RA from %s", hoplimit, ctx->sfrom);
+ return;
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&ctx->from.sin6_addr)) {
+ logger(dctx, LOG_ERR,
+ "RA from non local address %s", ctx->sfrom);
+ return;
+ }
+
+ if (!(ifp->options->options & DHCPCD_IPV6RS)) {
+#ifdef DEBUG_RS
+ logger(ifp->ctx, LOG_DEBUG, "%s: unexpected RA from %s",
+ ifp->name, ctx->sfrom);
+#endif
+ return;
+ }
+
+ /* We could receive a RA before we sent a RS*/
+ if (ipv6_linklocal(ifp) == NULL) {
+#ifdef DEBUG_RS
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: received RA from %s (no link-local)",
+ ifp->name, ctx->sfrom);
+#endif
+ return;
+ }
+
+ if (ipv6_iffindaddr(ifp, &ctx->from.sin6_addr)) {
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: ignoring RA from ourself %s", ifp->name, ctx->sfrom);
+ return;
+ }
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (ifp == rap->iface &&
+ IN6_ARE_ADDR_EQUAL(&rap->from, &ctx->from.sin6_addr))
+ break;
+ }
+
+ nd_ra = (struct nd_router_advert *)icp;
+
+ /* We don't want to spam the log with the fact we got an RA every
+ * 30 seconds or so, so only spam the log if it's different. */
+ if (rap == NULL || (rap->data_len != len ||
+ memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0))
+ {
+ if (rap) {
+ free(rap->data);
+ rap->data_len = 0;
+ rap->no_public_warned = 0;
+ }
+ new_data = 1;
+ } else
+ new_data = 0;
+ if (new_data || ifp->options->options & DHCPCD_DEBUG)
+ logger(ifp->ctx, LOG_INFO, "%s: Router Advertisement from %s",
+ ifp->name, ctx->sfrom);
+
+ if (rap == NULL) {
+ rap = calloc(1, sizeof(*rap));
+ if (rap == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ rap->iface = ifp;
+ rap->from = ctx->from.sin6_addr;
+ strlcpy(rap->sfrom, ctx->sfrom, sizeof(rap->sfrom));
+ TAILQ_INIT(&rap->addrs);
+ new_rap = 1;
+ } else
+ new_rap = 0;
+ if (rap->data_len == 0) {
+ rap->data = malloc(len);
+ if (rap->data == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ if (new_rap)
+ free(rap);
+ return;
+ }
+ memcpy(rap->data, icp, len);
+ rap->data_len = len;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &rap->acquired);
+ rap->flags = nd_ra->nd_ra_flags_reserved;
+ rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
+ if (nd_ra->nd_ra_reachable) {
+ rap->reachable = ntohl(nd_ra->nd_ra_reachable);
+ if (rap->reachable > MAX_REACHABLE_TIME)
+ rap->reachable = 0;
+ }
+ if (nd_ra->nd_ra_retransmit)
+ rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
+ if (rap->lifetime)
+ rap->expired = 0;
+ rap->hasdns = 0;
+
+ ipv6_settempstale(ifp);
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ ap->flags |= IPV6_AF_STALE;
+ }
+
+ len -= sizeof(struct nd_router_advert);
+ p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
+ for (; len > 0; p += olen, len -= olen) {
+ if (len < sizeof(struct nd_opt_hdr)) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: short option", ifp->name);
+ break;
+ }
+ ndo = (struct nd_opt_hdr *)p;
+ olen = (size_t)ndo->nd_opt_len * 8;
+ if (olen == 0) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: zero length option", ifp->name);
+ break;
+ }
+ if (olen > len) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: option length exceeds message", ifp->name);
+ break;
+ }
+
+ if (has_option_mask(ifp->options->rejectmasknd,
+ ndo->nd_opt_type))
+ {
+ for (i = 0, dho = dctx->nd_opts;
+ i < dctx->nd_opts_len;
+ i++, dho++)
+ {
+ if (dho->option == ndo->nd_opt_type)
+ break;
+ }
+ if (dho != NULL)
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: reject RA (option %s) from %s",
+ ifp->name, dho->var, ctx->sfrom);
+ else
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: reject RA (option %d) from %s",
+ ifp->name, ndo->nd_opt_type, ctx->sfrom);
+ if (new_rap)
+ ipv6nd_removefreedrop_ra(rap, 0, 0);
+ else
+ ipv6nd_free_ra(rap);
+ return;
+ }
+
+ if (has_option_mask(ifp->options->nomasknd, ndo->nd_opt_type))
+ continue;
+
+ switch (ndo->nd_opt_type) {
+ case ND_OPT_PREFIX_INFORMATION:
+ pi = (struct nd_opt_prefix_info *)(void *)ndo;
+ if (pi->nd_opt_pi_len != 4) {
+ logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG,
+ "%s: invalid option len for prefix",
+ ifp->name);
+ continue;
+ }
+ if (pi->nd_opt_pi_prefix_len > 128) {
+ logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG,
+ "%s: invalid prefix len",
+ ifp->name);
+ continue;
+ }
+ if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) ||
+ IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix))
+ {
+ logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG,
+ "%s: invalid prefix in RA", ifp->name);
+ continue;
+ }
+ if (ntohl(pi->nd_opt_pi_preferred_time) >
+ ntohl(pi->nd_opt_pi_valid_time))
+ {
+ logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG,
+ "%s: pltime > vltime", ifp->name);
+ continue;
+ }
+ TAILQ_FOREACH(ap, &rap->addrs, next)
+ if (ap->prefix_len ==pi->nd_opt_pi_prefix_len &&
+ IN6_ARE_ADDR_EQUAL(&ap->prefix,
+ &pi->nd_opt_pi_prefix))
+ break;
+ if (ap == NULL) {
+ if (!(pi->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_AUTO) &&
+ !(pi->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_ONLINK))
+ continue;
+ ap = calloc(1, sizeof(*ap));
+ if (ap == NULL)
+ break;
+ ap->iface = rap->iface;
+ ap->flags = IPV6_AF_NEW;
+ ap->prefix_len = pi->nd_opt_pi_prefix_len;
+ ap->prefix = pi->nd_opt_pi_prefix;
+ if (pi->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_AUTO &&
+ ap->iface->options->options &
+ DHCPCD_IPV6RA_AUTOCONF)
+ {
+ ap->flags |= IPV6_AF_AUTOCONF;
+ ap->dadcounter =
+ ipv6_makeaddr(&ap->addr, ifp,
+ &ap->prefix,
+ pi->nd_opt_pi_prefix_len);
+ if (ap->dadcounter == -1) {
+ free(ap);
+ break;
+ }
+ cbp = inet_ntop(AF_INET6,
+ &ap->addr,
+ buf, sizeof(buf));
+ if (cbp)
+ snprintf(ap->saddr,
+ sizeof(ap->saddr),
+ "%s/%d",
+ cbp, ap->prefix_len);
+ else
+ ap->saddr[0] = '\0';
+ } else {
+ memset(&ap->addr, 0, sizeof(ap->addr));
+ ap->saddr[0] = '\0';
+ }
+ ap->dadcallback = ipv6nd_dadcallback;
+ ap->created = ap->acquired = rap->acquired;
+ TAILQ_INSERT_TAIL(&rap->addrs, ap, next);
+
+#ifdef IPV6_MANAGETEMPADDR
+ /* New address to dhcpcd RA handling.
+ * If the address already exists and a valid
+ * temporary address also exists then
+ * extend the existing one rather than
+ * create a new one */
+ if (ipv6_iffindaddr(ifp, &ap->addr) &&
+ ipv6_settemptime(ap, 0))
+ new_ap = 0;
+ else
+ new_ap = 1;
+#endif
+ } else {
+#ifdef IPV6_MANAGETEMPADDR
+ new_ap = 0;
+#endif
+ ap->flags &= ~IPV6_AF_STALE;
+ ap->acquired = rap->acquired;
+ }
+ if (pi->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_ONLINK)
+ ap->flags |= IPV6_AF_ONLINK;
+ ap->prefix_vltime =
+ ntohl(pi->nd_opt_pi_valid_time);
+ ap->prefix_pltime =
+ ntohl(pi->nd_opt_pi_preferred_time);
+ ap->nsprobes = 0;
+
+#ifdef IPV6_MANAGETEMPADDR
+ /* RFC4941 Section 3.3.3 */
+ if (ap->flags & IPV6_AF_AUTOCONF &&
+ ap->iface->options->options & DHCPCD_IPV6RA_OWN &&
+ ip6_use_tempaddr(ap->iface->name))
+ {
+ if (!new_ap) {
+ if (ipv6_settemptime(ap, 1) == NULL)
+ new_ap = 1;
+ }
+ if (new_ap && ap->prefix_pltime) {
+ if (ipv6_createtempaddr(ap,
+ &ap->acquired) == NULL)
+ logger(ap->iface->ctx, LOG_ERR,
+ "ipv6_createtempaddr: %m");
+ }
+ }
+#endif
+ break;
+
+ case ND_OPT_MTU:
+ mtu = (struct nd_opt_mtu *)(void *)p;
+ mtuv = ntohl(mtu->nd_opt_mtu_mtu);
+ if (mtuv < IPV6_MMTU) {
+ logger(ifp->ctx, LOG_ERR, "%s: invalid MTU %d",
+ ifp->name, mtuv);
+ break;
+ }
+ rap->mtu = mtuv;
+ break;
+
+ case ND_OPT_RDNSS:
+ rdnss = (struct nd_opt_rdnss *)(void *)p;
+ if (rdnss->nd_opt_rdnss_lifetime &&
+ rdnss->nd_opt_rdnss_len > 1)
+ rap->hasdns = 1;
+
+ default:
+ continue;
+ }
+ }
+
+ for (i = 0, dho = dctx->nd_opts;
+ i < dctx->nd_opts_len;
+ i++, dho++)
+ {
+ if (has_option_mask(ifp->options->requiremasknd,
+ dho->option))
+ {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: reject RA (no option %s) from %s",
+ ifp->name, dho->var, ctx->sfrom);
+ if (new_rap)
+ ipv6nd_removefreedrop_ra(rap, 0, 0);
+ else
+ ipv6nd_free_ra(rap);
+ return;
+ }
+ }
+
+ if (new_rap)
+ add_router(ifp->ctx->ipv6, rap);
+
+ if (!ipv6nd_has_public_addr(rap->iface) &&
+ !(rap->iface->options->options & DHCPCD_IPV6RA_ACCEPT_NOPUBLIC) &&
+ (!(rap->flags & ND_RA_FLAG_MANAGED) ||
+ !dhcp6_has_public_addr(rap->iface)))
+ {
+ logger(rap->iface->ctx,
+ rap->no_public_warned ? LOG_DEBUG : LOG_WARNING,
+ "%s: ignoring RA from %s"
+ " (no public prefix, no managed address)",
+ rap->iface->name, rap->sfrom);
+ rap->no_public_warned = 1;
+ goto handle_flag;
+ }
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ script_runreason(ifp, "TEST");
+ goto handle_flag;
+ }
+ ipv6_addaddrs(&rap->addrs);
+#ifdef IPV6_MANAGETEMPADDR
+ ipv6_addtempaddrs(ifp, &rap->acquired);
+#endif
+
+ /* Find any freshly added routes, such as the subnet route.
+ * We do this because we cannot rely on recieving the kernel
+ * notification right now via our link socket. */
+ if_initrt6(ifp);
+
+ ipv6_buildroutes(ifp->ctx);
+ if (ipv6nd_scriptrun(rap))
+ return;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */
+
+handle_flag:
+ if (!(ifp->options->options & DHCPCD_DHCP6))
+ goto nodhcp6;
+ if (rap->flags & ND_RA_FLAG_MANAGED) {
+ if (new_data && dhcp6_start(ifp, DH6S_INIT) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "dhcp6_start: %s: %m", ifp->name);
+ } else if (rap->flags & ND_RA_FLAG_OTHER) {
+ if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "dhcp6_start: %s: %m", ifp->name);
+ } else {
+ if (new_data)
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: No DHCPv6 instruction in RA", ifp->name);
+nodhcp6:
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+ }
+
+ /* Expire should be called last as the rap object could be destroyed */
+ ipv6nd_expirera(ifp);
+}
+
+/* Run RA's we ignored becuase they had no public addresses
+ * This should only be called when DHCPv6 applies a public address */
+void
+ipv6nd_runignoredra(struct interface *ifp)
+{
+ struct ra *rap;
+
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface == ifp &&
+ !rap->expired &&
+ rap->no_public_warned)
+ {
+ rap->no_public_warned = 0;
+ logger(rap->iface->ctx, LOG_INFO,
+ "%s: applying ignored RA from %s",
+ rap->iface->name, rap->sfrom);
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ script_runreason(ifp, "TEST");
+ continue;
+ }
+ if (ipv6nd_scriptrun(rap))
+ return;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, rap);
+ }
+ }
+}
+
+int
+ipv6nd_hasra(const struct interface *ifp)
+{
+ const struct ra *rap;
+
+ if (ifp->ctx->ipv6) {
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next)
+ if (rap->iface == ifp && !rap->expired)
+ return 1;
+ }
+ return 0;
+}
+
+int
+ipv6nd_hasradhcp(const struct interface *ifp)
+{
+ const struct ra *rap;
+
+ if (ifp->ctx->ipv6) {
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface == ifp &&
+ !rap->expired &&
+ (rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static const uint8_t *
+ipv6nd_getoption(struct dhcpcd_ctx *ctx,
+ size_t *os, unsigned int *code, size_t *len,
+ const uint8_t *od, size_t ol, struct dhcp_opt **oopt)
+{
+ const struct nd_opt_hdr *o;
+ size_t i;
+ struct dhcp_opt *opt;
+
+ if (od) {
+ *os = sizeof(*o);
+ if (ol < *os) {
+ errno = EINVAL;
+ return NULL;
+ }
+ o = (const struct nd_opt_hdr *)od;
+ if (o->nd_opt_len > ol) {
+ errno = EINVAL;
+ return NULL;
+ }
+ *len = ND_OPTION_LEN(o);
+ *code = o->nd_opt_type;
+ } else
+ o = NULL;
+
+ for (i = 0, opt = ctx->nd_opts;
+ i < ctx->nd_opts_len; i++, opt++)
+ {
+ if (opt->option == *code) {
+ *oopt = opt;
+ break;
+ }
+ }
+
+ if (o)
+ return ND_COPTION_DATA(o);
+ return NULL;
+}
+
+ssize_t
+ipv6nd_env(char **env, const char *prefix, const struct interface *ifp)
+{
+ size_t i, j, n, len;
+ struct ra *rap;
+ char ndprefix[32], abuf[24];
+ struct dhcp_opt *opt;
+ const struct nd_opt_hdr *o;
+ struct ipv6_addr *ia;
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ i = n = 0;
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface != ifp)
+ continue;
+ i++;
+ if (prefix != NULL)
+ snprintf(ndprefix, sizeof(ndprefix),
+ "%s_nd%zu", prefix, i);
+ else
+ snprintf(ndprefix, sizeof(ndprefix),
+ "nd%zu", i);
+ if (env)
+ setvar(rap->iface->ctx, &env[n], ndprefix,
+ "from", rap->sfrom);
+ n++;
+ if (env)
+ setvard(rap->iface->ctx, &env[n], ndprefix,
+ "acquired", (size_t)rap->acquired.tv_sec);
+ n++;
+ if (env)
+ setvard(rap->iface->ctx, &env[n], ndprefix,
+ "now", (size_t)now.tv_sec);
+ n++;
+
+ /* Zero our indexes */
+ if (env) {
+ for (j = 0, opt = rap->iface->ctx->nd_opts;
+ j < rap->iface->ctx->nd_opts_len;
+ j++, opt++)
+ dhcp_zero_index(opt);
+ for (j = 0, opt = rap->iface->options->nd_override;
+ j < rap->iface->options->nd_override_len;
+ j++, opt++)
+ dhcp_zero_index(opt);
+ }
+
+ /* Unlike DHCP, ND6 options *may* occur more than once.
+ * There is also no provision for option concatenation
+ * unlike DHCP. */
+ len = rap->data_len -
+ ((size_t)((const uint8_t *)ND_CFIRST_OPTION(rap) -
+ rap->data));
+
+ for (o = ND_CFIRST_OPTION(rap);
+ len >= (ssize_t)sizeof(*o);
+ o = ND_CNEXT_OPTION(o))
+ {
+ if ((size_t)o->nd_opt_len * 8 > len) {
+ errno = EINVAL;
+ break;
+ }
+ len -= (size_t)(o->nd_opt_len * 8);
+ if (has_option_mask(rap->iface->options->nomasknd,
+ o->nd_opt_type))
+ continue;
+ for (j = 0, opt = rap->iface->options->nd_override;
+ j < rap->iface->options->nd_override_len;
+ j++, opt++)
+ if (opt->option == o->nd_opt_type)
+ break;
+ if (j == rap->iface->options->nd_override_len) {
+ for (j = 0, opt = rap->iface->ctx->nd_opts;
+ j < rap->iface->ctx->nd_opts_len;
+ j++, opt++)
+ if (opt->option == o->nd_opt_type)
+ break;
+ if (j == rap->iface->ctx->nd_opts_len)
+ opt = NULL;
+ }
+ if (opt) {
+ n += dhcp_envoption(rap->iface->ctx,
+ env == NULL ? NULL : &env[n],
+ ndprefix, rap->iface->name,
+ opt, ipv6nd_getoption,
+ ND_COPTION_DATA(o), ND_OPTION_LEN(o));
+ }
+ }
+
+ /* We need to output the addresses we actually made
+ * from the prefix information options as well. */
+ j = 0;
+ TAILQ_FOREACH(ia, &rap->addrs, next) {
+ if (!(ia->flags & IPV6_AF_AUTOCONF)
+#ifdef IPV6_AF_TEMPORARY
+ || ia->flags & IPV6_AF_TEMPORARY
+#endif
+ )
+ continue;
+ j++;
+ if (env) {
+ snprintf(abuf, sizeof(abuf), "addr%zu", j);
+ setvar(rap->iface->ctx, &env[n], ndprefix,
+ abuf, ia->saddr);
+ }
+ n++;
+ }
+ }
+ return (ssize_t)n;
+}
+
+void
+ipv6nd_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname,
+ const struct in6_addr *addr, int flags)
+{
+ struct ra *rap;
+
+ if (ctx->ipv6 == NULL)
+ return;
+ TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
+ if (strcmp(rap->iface->name, ifname))
+ continue;
+ ipv6_handleifa_addrs(cmd, &rap->addrs, addr, flags);
+ }
+}
+
+void
+ipv6nd_expirera(void *arg)
+{
+ struct interface *ifp;
+ struct ra *rap, *ran;
+ struct timespec now, lt, expire, next;
+ uint8_t expired, valid, validone;
+
+ ifp = arg;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ expired = 0;
+ timespecclear(&next);
+
+ validone = 0;
+ TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) {
+ if (rap->iface != ifp)
+ continue;
+ valid = 0;
+ if (rap->lifetime) {
+ lt.tv_sec = (time_t)rap->lifetime;
+ lt.tv_nsec = 0;
+ timespecadd(&rap->acquired, <, &expire);
+ if (rap->lifetime == 0 || timespeccmp(&now, &expire, >))
+ {
+ if (!rap->expired) {
+ logger(ifp->ctx, LOG_WARNING,
+ "%s: %s: router expired",
+ ifp->name, rap->sfrom);
+ rap->expired = expired = 1;
+ rap->lifetime = 0;
+ }
+ } else {
+ valid = 1;
+ timespecsub(&expire, &now, <);
+ if (!timespecisset(&next) ||
+ timespeccmp(&next, <, >))
+ next = lt;
+ }
+ }
+
+ /* XXX FixMe!
+ * We need to extract the lifetime from each option and check
+ * if that has expired or not.
+ * If it has, zero the option out in the returned data. */
+
+ /* No valid lifetimes are left on the RA, so we might
+ * as well punt it. */
+ if (!valid && TAILQ_FIRST(&rap->addrs) == NULL)
+ ipv6nd_free_ra(rap);
+ else
+ validone = 1;
+ }
+
+ if (timespecisset(&next))
+ eloop_timeout_add_tv(ifp->ctx->eloop,
+ &next, ipv6nd_expirera, ifp);
+ if (expired) {
+ ipv6_buildroutes(ifp->ctx);
+ script_runreason(ifp, "ROUTERADVERT");
+ }
+
+ /* No valid routers? Kill any DHCPv6. */
+ if (!validone)
+ dhcp6_drop(ifp, "EXPIRE6");
+}
+
+void
+ipv6nd_drop(struct interface *ifp)
+{
+ struct ra *rap;
+ uint8_t expired = 0;
+ TAILQ_HEAD(rahead, ra) rtrs;
+
+ if (ifp->ctx->ipv6 == NULL)
+ return;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ TAILQ_INIT(&rtrs);
+ TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) {
+ if (rap->iface == ifp) {
+ rap->expired = expired = 1;
+ TAILQ_REMOVE(ifp->ctx->ipv6->ra_routers, rap, next);
+ TAILQ_INSERT_TAIL(&rtrs, rap, next);
+ }
+ }
+ if (expired) {
+ while ((rap = TAILQ_FIRST(&rtrs))) {
+ TAILQ_REMOVE(&rtrs, rap, next);
+ ipv6nd_drop_ra(rap);
+ }
+ ipv6_buildroutes(ifp->ctx);
+ if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP)
+ script_runreason(ifp, "ROUTERADVERT");
+ }
+}
+
+static void
+ipv6nd_handlena(struct dhcpcd_ctx *dctx, struct interface *ifp,
+ struct icmp6_hdr *icp, size_t len, int hoplimit)
+{
+ struct ipv6_ctx *ctx = dctx->ipv6;
+ struct nd_neighbor_advert *nd_na;
+ struct ra *rap;
+ uint32_t is_router, is_solicited;
+ char buf[INET6_ADDRSTRLEN];
+ const char *taddr;
+
+ if (ifp == NULL) {
+#ifdef DEBUG_NS
+ logger(ctx, LOG_DEBUG, "NA for unexpected interface from %s",
+ dctx->sfrom);
+#endif
+ return;
+ }
+
+ if ((size_t)len < sizeof(struct nd_neighbor_advert)) {
+ logger(ifp->ctx, LOG_ERR, "%s: IPv6 NA too short from %s",
+ ifp->name, ctx->sfrom);
+ return;
+ }
+
+ /* RFC 4861 7.1.2 */
+ if (hoplimit != 255) {
+ logger(dctx, LOG_ERR,
+ "invalid hoplimit(%d) in NA from %s", hoplimit, ctx->sfrom);
+ return;
+ }
+
+ nd_na = (struct nd_neighbor_advert *)icp;
+ is_router = nd_na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER;
+ is_solicited = nd_na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED;
+ taddr = inet_ntop(AF_INET6, &nd_na->nd_na_target,
+ buf, INET6_ADDRSTRLEN);
+
+ if (IN6_IS_ADDR_MULTICAST(&nd_na->nd_na_target)) {
+ logger(ifp->ctx, LOG_ERR, "%s: NA multicast address %s (%s)",
+ ifp->name, taddr, ctx->sfrom);
+ return;
+ }
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (rap->iface == ifp &&
+ IN6_ARE_ADDR_EQUAL(&rap->from, &nd_na->nd_na_target))
+ break;
+ }
+ if (rap == NULL) {
+#ifdef DEBUG_NS
+ logger(ifp->ctx, LOG_DEBUG, "%s: unexpected NA from %s for %s",
+ ifp->name, ctx->sfrom, taddr);
+#endif
+ return;
+ }
+
+#ifdef DEBUG_NS
+ logger(ifp->ctx, LOG_DEBUG, "%s: %sNA for %s from %s",
+ ifp->name, is_solicited ? "solicited " : "", taddr, ctx->sfrom);
+#endif
+
+ /* Node is no longer a router, so remove it from consideration */
+ if (!is_router && !rap->expired) {
+ logger(ifp->ctx, LOG_INFO, "%s: %s not a router (%s)",
+ ifp->name, taddr, ctx->sfrom);
+ rap->expired = 1;
+ ipv6_buildroutes(ifp->ctx);
+ script_runreason(ifp, "ROUTERADVERT");
+ return;
+ }
+
+ if (is_solicited && is_router && rap->lifetime) {
+ if (rap->expired) {
+ rap->expired = 0;
+ logger(ifp->ctx, LOG_INFO, "%s: %s reachable (%s)",
+ ifp->name, taddr, ctx->sfrom);
+ ipv6_buildroutes(ifp->ctx);
+ script_runreason(rap->iface, "ROUTERADVERT"); /* XXX */
+ }
+ }
+}
+
+static void
+ipv6nd_handledata(void *arg)
+{
+ struct dhcpcd_ctx *dctx;
+ struct ipv6_ctx *ctx;
+ ssize_t len;
+ struct cmsghdr *cm;
+ int hoplimit;
+ struct in6_pktinfo pkt;
+ struct icmp6_hdr *icp;
+ struct interface *ifp;
+
+ dctx = arg;
+ ctx = dctx->ipv6;
+ ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ len = recvmsg(ctx->nd_fd, &ctx->rcvhdr, 0);
+ if (len == -1) {
+ logger(dctx, LOG_ERR, "recvmsg: %m");
+ eloop_event_delete(dctx->eloop, ctx->nd_fd);
+ close(ctx->nd_fd);
+ ctx->nd_fd = -1;
+ return;
+ }
+ ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr,
+ ctx->ntopbuf, INET6_ADDRSTRLEN);
+ if ((size_t)len < sizeof(struct icmp6_hdr)) {
+ logger(dctx, LOG_ERR, "IPv6 ICMP packet too short from %s",
+ ctx->sfrom);
+ return;
+ }
+
+ pkt.ipi6_ifindex = 0;
+ hoplimit = 0;
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&ctx->rcvhdr);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&ctx->rcvhdr, cm))
+ {
+ if (cm->cmsg_level != IPPROTO_IPV6)
+ continue;
+ switch(cm->cmsg_type) {
+ case IPV6_PKTINFO:
+ if (cm->cmsg_len == CMSG_LEN(sizeof(pkt)))
+ memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt));
+ break;
+ case IPV6_HOPLIMIT:
+ if (cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int));
+ break;
+ }
+ }
+
+ if (pkt.ipi6_ifindex == 0) {
+ logger(dctx, LOG_ERR,
+ "IPv6 RA/NA did not contain index from %s",
+ ctx->sfrom);
+ return;
+ }
+
+ TAILQ_FOREACH(ifp, dctx->ifaces, next) {
+ if (ifp->index == (unsigned int)pkt.ipi6_ifindex) {
+ if (!(ifp->options->options & DHCPCD_IPV6))
+ return;
+ break;
+ }
+ }
+
+ icp = (struct icmp6_hdr *)ctx->rcvhdr.msg_iov[0].iov_base;
+ if (icp->icmp6_code == 0) {
+ switch(icp->icmp6_type) {
+ case ND_NEIGHBOR_ADVERT:
+ ipv6nd_handlena(dctx, ifp, icp, (size_t)len,
+ hoplimit);
+ return;
+ case ND_ROUTER_ADVERT:
+ ipv6nd_handlera(dctx, ifp, icp, (size_t)len,
+ hoplimit);
+ return;
+ }
+ }
+
+ logger(dctx, LOG_ERR, "invalid IPv6 type %d or code %d from %s",
+ icp->icmp6_type, icp->icmp6_code, ctx->sfrom);
+}
+
+static void
+ipv6nd_startrs1(void *arg)
+{
+ struct interface *ifp = arg;
+ struct rs_state *state;
+
+ logger(ifp->ctx, LOG_INFO, "%s: soliciting an IPv6 router", ifp->name);
+ if (ipv6nd_open(ifp->ctx) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: ipv6nd_open: %m", __func__);
+ return;
+ }
+
+ state = RS_STATE(ifp);
+ if (state == NULL) {
+ ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state));
+ state = RS_STATE(ifp);
+ if (state == NULL) {
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ }
+
+ /* Always make a new probe as the underlying hardware
+ * address could have changed. */
+ ipv6nd_makersprobe(ifp);
+ if (state->rs == NULL) {
+ logger(ifp->ctx, LOG_ERR,
+ "%s: ipv6ns_makersprobe: %m", __func__);
+ return;
+ }
+
+ state->rsprobes = 0;
+ ipv6nd_sendrsprobe(ifp);
+}
+
+void
+ipv6nd_startrs(struct interface *ifp)
+{
+ struct timespec tv;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) {
+ ipv6nd_startrs1(ifp);
+ return;
+ }
+
+ tv.tv_sec = 0;
+ tv.tv_nsec = (suseconds_t)arc4random_uniform(
+ MAX_RTR_SOLICITATION_DELAY * NSEC_PER_SEC);
+ timespecnorm(&tv);
+ logger(ifp->ctx, LOG_DEBUG,
+ "%s: delaying IPv6 router solicitation for %0.1f seconds",
+ ifp->name, timespec_to_double(&tv));
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_startrs1, ifp);
+ return;
+}
--- /dev/null
+/* $NetBSD: ipv6nd.h,v 1.13 2015/07/09 10:15:34 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef IPV6ND_H
+#define IPV6ND_H
+
+#include <time.h>
+
+#include "config.h"
+#include "dhcpcd.h"
+#include "ipv6.h"
+
+struct ra {
+ TAILQ_ENTRY(ra) next;
+ struct interface *iface;
+ struct in6_addr from;
+ char sfrom[INET6_ADDRSTRLEN];
+ unsigned char *data;
+ size_t data_len;
+ struct timespec acquired;
+ unsigned char flags;
+ uint32_t lifetime;
+ uint32_t reachable;
+ uint32_t retrans;
+ uint32_t mtu;
+ struct ipv6_addrhead addrs;
+ uint8_t hasdns;
+ uint8_t expired;
+ uint8_t no_public_warned;
+};
+
+TAILQ_HEAD(ra_head, ra);
+
+struct rs_state {
+ unsigned char *rs;
+ size_t rslen;
+ int rsprobes;
+};
+
+#define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
+#define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a)))
+
+#define ND_CFIRST_OPTION(m) \
+ ((const struct nd_opt_hdr *) \
+ ((const uint8_t *)(m)->data + sizeof(struct nd_router_advert)))
+#define ND_OPTION_LEN(o) ((size_t)((o)->nd_opt_len * 8) - \
+ sizeof(struct nd_opt_hdr))
+#define ND_CNEXT_OPTION(o) \
+ ((const struct nd_opt_hdr *)((const uint8_t *)(o) + \
+ (size_t)((o)->nd_opt_len * 8)))
+#define ND_COPTION_DATA(o) \
+ ((const uint8_t *)(o) + sizeof(struct nd_opt_hdr))
+
+#define MAX_RTR_SOLICITATION_DELAY 1 /* seconds */
+#define MAX_UNICAST_SOLICIT 3 /* 3 transmissions */
+#define RTR_SOLICITATION_INTERVAL 4 /* seconds */
+#define MAX_RTR_SOLICITATIONS 3 /* times */
+
+/* On carrier up, expire known routers after RTR_CARRIER_EXPIRE seconds. */
+#define RTR_CARRIER_EXPIRE \
+ (MAX_RTR_SOLICITATION_DELAY + \
+ (MAX_RTR_SOLICITATIONS + 1) * \
+ RTR_SOLICITATION_INTERVAL)
+
+#define MAX_REACHABLE_TIME 3600000 /* milliseconds */
+#define REACHABLE_TIME 30000 /* milliseconds */
+#define RETRANS_TIMER 1000 /* milliseconds */
+#define DELAY_FIRST_PROBE_TIME 5 /* seconds */
+
+#define IPV6ND_REACHABLE (1 << 0)
+#define IPV6ND_ROUTER (1 << 1)
+
+#ifdef INET6
+void ipv6nd_printoptions(const struct dhcpcd_ctx *,
+ const struct dhcp_opt *, size_t);
+void ipv6nd_startrs(struct interface *);
+ssize_t ipv6nd_env(char **, const char *, const struct interface *);
+const struct ipv6_addr *ipv6nd_iffindaddr(const struct interface *ifp,
+ const struct in6_addr *addr, short flags);
+struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *,
+ const struct in6_addr *, short);
+void ipv6nd_freedrop_ra(struct ra *, int);
+#define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra), 0)
+#define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra), 1)
+ssize_t ipv6nd_free(struct interface *);
+void ipv6nd_expirera(void *arg);
+int ipv6nd_hasra(const struct interface *);
+int ipv6nd_hasradhcp(const struct interface *);
+void ipv6nd_runignoredra(struct interface *);
+void ipv6nd_handleifa(struct dhcpcd_ctx *, int,
+ const char *, const struct in6_addr *, int);
+int ipv6nd_dadcompleted(const struct interface *);
+void ipv6nd_expire(struct interface *, uint32_t);
+void ipv6nd_drop(struct interface *);
+void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int);
+#else
+#define ipv6nd_startrs(a) {}
+#define ipv6nd_free(a) {}
+#define ipv6nd_hasra(a) (0)
+#define ipv6nd_dadcompleted(a) (0)
+#define ipv6nd_drop(a) {}
+#define ipv6nd_expire(a, b) {}
+#endif
+
+#endif
--- /dev/null
+#include <sys/cdefs.h>
+ __RCSID("$NetBSD: script.c,v 1.23 2015/09/04 12:25:01 roy Exp $");
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/stat.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+/* We can't include spawn.h here because it may not exist.
+ * config.h will pull it in, or our compat one. */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+#include "ipv6nd.h"
+#include "script.h"
+
+#ifdef HAVE_SPAWN_H
+#include <spawn.h>
+#else
+#include "compat/posix_spawn.h"
+#endif
+
+/* Allow the OS to define another script env var name */
+#ifndef RC_SVCNAME
+#define RC_SVCNAME "RC_SVCNAME"
+#endif
+
+#define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin"
+
+static const char * const if_params[] = {
+ "interface",
+ "reason",
+ "pid",
+ "ifcarrier",
+ "ifmetric",
+ "ifwireless",
+ "ifflags",
+ "ssid",
+ "profile",
+ "interface_order",
+ NULL
+};
+
+void
+if_printoptions(void)
+{
+ const char * const *p;
+
+ for (p = if_params; *p; p++)
+ printf(" - %s\n", *p);
+}
+
+static int
+exec_script(const struct dhcpcd_ctx *ctx, char *const *argv, char *const *env)
+{
+ pid_t pid;
+ posix_spawnattr_t attr;
+ int r;
+#ifdef USE_SIGNALS
+ size_t i;
+ short flags;
+ sigset_t defsigs;
+#else
+ UNUSED(ctx);
+#endif
+
+ /* posix_spawn is a safe way of executing another image
+ * and changing signals back to how they should be. */
+ if (posix_spawnattr_init(&attr) == -1)
+ return -1;
+#ifdef USE_SIGNALS
+ flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
+ posix_spawnattr_setflags(&attr, flags);
+ sigemptyset(&defsigs);
+ for (i = 0; i < dhcpcd_signals_len; i++)
+ sigaddset(&defsigs, dhcpcd_signals[i]);
+ posix_spawnattr_setsigdefault(&attr, &defsigs);
+ posix_spawnattr_setsigmask(&attr, &ctx->sigset);
+#endif
+ errno = 0;
+ r = posix_spawn(&pid, argv[0], NULL, &attr, argv, env);
+ if (r) {
+ errno = r;
+ return -1;
+ }
+ return pid;
+}
+
+#ifdef INET
+static char *
+make_var(struct dhcpcd_ctx *ctx, const char *prefix, const char *var)
+{
+ size_t len;
+ char *v;
+
+ len = strlen(prefix) + strlen(var) + 2;
+ if ((v = malloc(len)) == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ return NULL;
+ }
+ snprintf(v, len, "%s_%s", prefix, var);
+ return v;
+}
+
+
+static int
+append_config(struct dhcpcd_ctx *ctx, char ***env, size_t *len,
+ const char *prefix, const char *const *config)
+{
+ size_t i, j, e1;
+ char **ne, *eq, **nep, *p;
+ int ret;
+
+ if (config == NULL)
+ return 0;
+
+ ne = *env;
+ ret = 0;
+ for (i = 0; config[i] != NULL; i++) {
+ eq = strchr(config[i], '=');
+ e1 = (size_t)(eq - config[i] + 1);
+ for (j = 0; j < *len; j++) {
+ if (strncmp(ne[j], prefix, strlen(prefix)) == 0 &&
+ ne[j][strlen(prefix)] == '_' &&
+ strncmp(ne[j] + strlen(prefix) + 1,
+ config[i], e1) == 0)
+ {
+ p = make_var(ctx, prefix, config[i]);
+ if (p == NULL) {
+ ret = -1;
+ break;
+ }
+ free(ne[j]);
+ ne[j] = p;
+ break;
+ }
+ }
+ if (j == *len) {
+ j++;
+ p = make_var(ctx, prefix, config[i]);
+ if (p == NULL) {
+ ret = -1;
+ break;
+ }
+ nep = realloc(ne, sizeof(char *) * (j + 1));
+ if (nep == NULL) {
+ logger(ctx, LOG_ERR, "%s: %m", __func__);
+ free(p);
+ ret = -1;
+ break;
+ }
+ ne = nep;
+ ne[j - 1] = p;
+ *len = j;
+ }
+ }
+ *env = ne;
+ return ret;
+}
+#endif
+
+static ssize_t
+arraytostr(const char *const *argv, char **s)
+{
+ const char *const *ap;
+ char *p;
+ size_t len, l;
+
+ if (*argv == NULL)
+ return 0;
+ len = 0;
+ ap = argv;
+ while (*ap)
+ len += strlen(*ap++) + 1;
+ *s = p = malloc(len);
+ if (p == NULL)
+ return -1;
+ ap = argv;
+ while (*ap) {
+ l = strlen(*ap) + 1;
+ memcpy(p, *ap, l);
+ p += l;
+ ap++;
+ }
+ return (ssize_t)len;
+}
+
+static ssize_t
+make_env(const struct interface *ifp, const char *reason, char ***argv)
+{
+ char **env, **nenv, *p;
+ size_t e, elen, l;
+#if defined(INET) || defined(INET6)
+ ssize_t n;
+#endif
+ const struct if_options *ifo = ifp->options;
+ const struct interface *ifp2;
+ int af;
+#ifdef INET
+ int dhcp, ipv4ll;
+ const struct dhcp_state *state;
+ const struct ipv4ll_state *istate;
+#endif
+#ifdef INET6
+ const struct dhcp6_state *d6_state;
+ int dhcp6, ra;
+#endif
+
+#ifdef INET
+ dhcp = ipv4ll = 0;
+ state = D_STATE(ifp);
+ istate = IPV4LL_CSTATE(ifp);
+#endif
+#ifdef INET6
+ dhcp6 = ra = 0;
+ d6_state = D6_CSTATE(ifp);
+#endif
+ if (strcmp(reason, "TEST") == 0) {
+ if (1 == 2) {}
+#ifdef INET6
+ else if (d6_state && d6_state->new)
+ dhcp6 = 1;
+ else if (ipv6nd_hasra(ifp))
+ ra = 1;
+#endif
+#ifdef INET
+ else if (state->added)
+ dhcp = 1;
+ else
+ ipv4ll = 1;
+#endif
+ }
+#ifdef INET6
+ else if (reason[strlen(reason) - 1] == '6')
+ dhcp6 = 1;
+ else if (strcmp(reason, "ROUTERADVERT") == 0)
+ ra = 1;
+#endif
+ else if (strcmp(reason, "PREINIT") == 0 ||
+ strcmp(reason, "CARRIER") == 0 ||
+ strcmp(reason, "NOCARRIER") == 0 ||
+ strcmp(reason, "UNKNOWN") == 0 ||
+ strcmp(reason, "DEPARTED") == 0 ||
+ strcmp(reason, "STOPPED") == 0)
+ {
+ /* This space left intentionally blank */
+ }
+#ifdef INET
+ else if (strcmp(reason, "IPV4LL") == 0)
+ ipv4ll = 1;
+ else
+ dhcp = 1;
+#endif
+
+ /* When dumping the lease, we only want to report interface and
+ reason - the other interface variables are meaningless */
+ if (ifp->ctx->options & DHCPCD_DUMPLEASE)
+ elen = 2;
+ else
+ elen = 11;
+
+#define EMALLOC(i, l) if ((env[(i)] = malloc((l))) == NULL) goto eexit;
+ /* Make our env + space for profile, wireless and debug */
+ env = calloc(1, sizeof(char *) * (elen + 4 + 1));
+ if (env == NULL)
+ goto eexit;
+ e = strlen("interface") + strlen(ifp->name) + 2;
+ EMALLOC(0, e);
+ snprintf(env[0], e, "interface=%s", ifp->name);
+ e = strlen("reason") + strlen(reason) + 2;
+ EMALLOC(1, e);
+ snprintf(env[1], e, "reason=%s", reason);
+ if (ifp->ctx->options & DHCPCD_DUMPLEASE)
+ goto dumplease;
+ e = 20;
+ EMALLOC(2, e);
+ snprintf(env[2], e, "pid=%d", getpid());
+ EMALLOC(3, e);
+ snprintf(env[3], e, "ifcarrier=%s",
+ ifp->carrier == LINK_UNKNOWN ? "unknown" :
+ ifp->carrier == LINK_UP ? "up" : "down");
+ EMALLOC(4, e);
+ snprintf(env[4], e, "ifmetric=%d", ifp->metric);
+ EMALLOC(5, e);
+ snprintf(env[5], e, "ifwireless=%d", ifp->wireless);
+ EMALLOC(6, e);
+ snprintf(env[6], e, "ifflags=%u", ifp->flags);
+ EMALLOC(7, e);
+ snprintf(env[7], e, "ifmtu=%d", if_getmtu(ifp));
+ l = e = strlen("interface_order=");
+ TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+ e += strlen(ifp2->name) + 1;
+ }
+ EMALLOC(8, e);
+ p = env[8];
+ strlcpy(p, "interface_order=", e);
+ e -= l;
+ p += l;
+ TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+ l = strlcpy(p, ifp2->name, e);
+ p += l;
+ e -= l;
+ *p++ = ' ';
+ e--;
+ }
+ *--p = '\0';
+ if (strcmp(reason, "STOPPED") == 0) {
+ env[9] = strdup("if_up=false");
+ if (ifo->options & DHCPCD_RELEASE)
+ env[10] = strdup("if_down=true");
+ else
+ env[10] = strdup("if_down=false");
+ } else if (strcmp(reason, "TEST") == 0 ||
+ strcmp(reason, "PREINIT") == 0 ||
+ strcmp(reason, "CARRIER") == 0 ||
+ strcmp(reason, "UNKNOWN") == 0)
+ {
+ env[9] = strdup("if_up=false");
+ env[10] = strdup("if_down=false");
+ } else if (1 == 2 /* appease ifdefs */
+#ifdef INET
+ || (dhcp && state && state->new)
+ || (ipv4ll && IPV4LL_STATE_RUNNING(ifp))
+#endif
+#ifdef INET6
+ || (dhcp6 && d6_state && d6_state->new)
+ || (ra && ipv6nd_hasra(ifp))
+#endif
+ )
+ {
+ env[9] = strdup("if_up=true");
+ env[10] = strdup("if_down=false");
+ } else {
+ env[9] = strdup("if_up=false");
+ env[10] = strdup("if_down=true");
+ }
+ if (env[9] == NULL || env[10] == NULL)
+ goto eexit;
+ if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) {
+ e = 20;
+ EMALLOC(elen, e);
+ snprintf(env[elen++], e, "if_afwaiting=%d", af);
+ }
+ if ((af = dhcpcd_afwaiting(ifp->ctx)) != AF_MAX) {
+ TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+ if ((af = dhcpcd_ifafwaiting(ifp2)) != AF_MAX)
+ break;
+ }
+ }
+ if (af != AF_MAX) {
+ e = 20;
+ EMALLOC(elen, e);
+ snprintf(env[elen++], e, "af_waiting=%d", af);
+ }
+ if (ifo->options & DHCPCD_DEBUG) {
+ e = strlen("syslog_debug=true") + 1;
+ EMALLOC(elen, e);
+ snprintf(env[elen++], e, "syslog_debug=true");
+ }
+ if (*ifp->profile) {
+ e = strlen("profile=") + strlen(ifp->profile) + 1;
+ EMALLOC(elen, e);
+ snprintf(env[elen++], e, "profile=%s", ifp->profile);
+ }
+ if (ifp->wireless) {
+ static const char *pfx = "ifssid=";
+ size_t pfx_len;
+ ssize_t psl;
+
+ pfx_len = strlen(pfx);
+ psl = print_string(NULL, 0, ESCSTRING,
+ (const uint8_t *)ifp->ssid, ifp->ssid_len);
+ if (psl != -1) {
+ EMALLOC(elen, pfx_len + (size_t)psl + 1);
+ memcpy(env[elen], pfx, pfx_len);
+ print_string(env[elen] + pfx_len, (size_t)psl + 1,
+ ESCSTRING,
+ (const uint8_t *)ifp->ssid, ifp->ssid_len);
+ elen++;
+ }
+ }
+#ifdef INET
+ if (dhcp && state && state->old) {
+ n = dhcp_env(NULL, NULL, state->old, ifp);
+ if (n == -1)
+ goto eexit;
+ if (n > 0) {
+ nenv = realloc(env, sizeof(char *) *
+ (elen + (size_t)n + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ n = dhcp_env(env + elen, "old", state->old, ifp);
+ if (n == -1)
+ goto eexit;
+ elen += (size_t)n;
+ }
+ if (append_config(ifp->ctx, &env, &elen, "old",
+ (const char *const *)ifo->config) == -1)
+ goto eexit;
+ }
+#endif
+#ifdef INET6
+ if (dhcp6 && d6_state && d6_state->old) {
+ n = dhcp6_env(NULL, NULL, ifp,
+ d6_state->old, d6_state->old_len);
+ if (n > 0) {
+ nenv = realloc(env, sizeof(char *) *
+ (elen + (size_t)n + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ n = dhcp6_env(env + elen, "old", ifp,
+ d6_state->old, d6_state->old_len);
+ if (n == -1)
+ goto eexit;
+ elen += (size_t)n;
+ }
+ }
+#endif
+
+dumplease:
+#ifdef INET
+ if (ipv4ll) {
+ n = ipv4ll_env(NULL, NULL, ifp);
+ if (n > 0) {
+ nenv = realloc(env, sizeof(char *) *
+ (elen + (size_t)n + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ if ((n = ipv4ll_env(env + elen,
+ istate->down ? "old" : "new", ifp)) == -1)
+ goto eexit;
+ elen += (size_t)n;
+ }
+ }
+ if (dhcp && state && state->new) {
+ n = dhcp_env(NULL, NULL, state->new, ifp);
+ if (n > 0) {
+ nenv = realloc(env, sizeof(char *) *
+ (elen + (size_t)n + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ n = dhcp_env(env + elen, "new",
+ state->new, ifp);
+ if (n == -1)
+ goto eexit;
+ elen += (size_t)n;
+ }
+ if (append_config(ifp->ctx, &env, &elen, "new",
+ (const char *const *)ifo->config) == -1)
+ goto eexit;
+ }
+#endif
+#ifdef INET6
+ if (dhcp6 && D6_STATE_RUNNING(ifp)) {
+ n = dhcp6_env(NULL, NULL, ifp,
+ d6_state->new, d6_state->new_len);
+ if (n > 0) {
+ nenv = realloc(env, sizeof(char *) *
+ (elen + (size_t)n + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ n = dhcp6_env(env + elen, "new", ifp,
+ d6_state->new, d6_state->new_len);
+ if (n == -1)
+ goto eexit;
+ elen += (size_t)n;
+ }
+ }
+ if (ra) {
+ n = ipv6nd_env(NULL, NULL, ifp);
+ if (n > 0) {
+ nenv = realloc(env, sizeof(char *) *
+ (elen + (size_t)n + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ n = ipv6nd_env(env + elen, NULL, ifp);
+ if (n == -1)
+ goto eexit;
+ elen += (size_t)n;
+ }
+ }
+#endif
+
+ /* Add our base environment */
+ if (ifo->environ) {
+ e = 0;
+ while (ifo->environ[e++])
+ ;
+ nenv = realloc(env, sizeof(char *) * (elen + e + 1));
+ if (nenv == NULL)
+ goto eexit;
+ env = nenv;
+ e = 0;
+ while (ifo->environ[e]) {
+ env[elen + e] = strdup(ifo->environ[e]);
+ if (env[elen + e] == NULL)
+ goto eexit;
+ e++;
+ }
+ elen += e;
+ }
+ env[elen] = NULL;
+
+ *argv = env;
+ return (ssize_t)elen;
+
+eexit:
+ logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+ if (env) {
+ nenv = env;
+ while (*nenv)
+ free(*nenv++);
+ free(env);
+ }
+ return -1;
+}
+
+static int
+send_interface1(struct fd_list *fd, const struct interface *iface,
+ const char *reason)
+{
+ char **env, **ep, *s;
+ size_t elen;
+ int retval;
+
+ if (make_env(iface, reason, &env) == -1)
+ return -1;
+ s = NULL;
+ elen = (size_t)arraytostr((const char *const *)env, &s);
+ if ((ssize_t)elen == -1) {
+ free(s);
+ return -1;
+ }
+ retval = control_queue(fd, s, elen, 1);
+ ep = env;
+ while (*ep)
+ free(*ep++);
+ free(env);
+ return retval;
+}
+
+int
+send_interface(struct fd_list *fd, const struct interface *ifp)
+{
+ const char *reason;
+ int retval = 0;
+#ifdef INET
+ const struct dhcp_state *d;
+#endif
+#ifdef INET6
+ const struct dhcp6_state *d6;
+#endif
+
+ switch (ifp->carrier) {
+ case LINK_UP:
+ reason = "CARRIER";
+ break;
+ case LINK_DOWN:
+ reason = "NOCARRIER";
+ break;
+ default:
+ reason = "UNKNOWN";
+ break;
+ }
+ if (send_interface1(fd, ifp, reason) == -1)
+ retval = -1;
+#ifdef INET
+ if (D_STATE_RUNNING(ifp)) {
+ d = D_CSTATE(ifp);
+ if (send_interface1(fd, ifp, d->reason) == -1)
+ retval = -1;
+ }
+ if (IPV4LL_STATE_RUNNING(ifp)) {
+ if (send_interface1(fd, ifp, "IPV4LL") == -1)
+ retval = -1;
+ }
+#endif
+
+#ifdef INET6
+ if (RS_STATE_RUNNING(ifp)) {
+ if (send_interface1(fd, ifp, "ROUTERADVERT") == -1)
+ retval = -1;
+ }
+ if (D6_STATE_RUNNING(ifp)) {
+ d6 = D6_CSTATE(ifp);
+ if (send_interface1(fd, ifp, d6->reason) == -1)
+ retval = -1;
+ }
+#endif
+
+ return retval;
+}
+
+int
+script_runreason(const struct interface *ifp, const char *reason)
+{
+ char *argv[2];
+ char **env = NULL, **ep;
+ char *svcname, *path, *bigenv;
+ size_t e, elen = 0;
+ pid_t pid;
+ int status = 0;
+ struct fd_list *fd;
+
+ if (ifp->options->script &&
+ (ifp->options->script[0] == '\0' ||
+ strcmp(ifp->options->script, "/dev/null") == 0) &&
+ TAILQ_FIRST(&ifp->ctx->control_fds) == NULL)
+ return 0;
+
+ /* Make our env */
+ elen = (size_t)make_env(ifp, reason, &env);
+ if (elen == (size_t)-1) {
+ logger(ifp->ctx, LOG_ERR, "%s: make_env: %m", ifp->name);
+ return -1;
+ }
+
+ if (ifp->options->script &&
+ (ifp->options->script[0] == '\0' ||
+ strcmp(ifp->options->script, "/dev/null") == 0))
+ goto send_listeners;
+
+ argv[0] = ifp->options->script ? ifp->options->script : UNCONST(SCRIPT);
+ argv[1] = NULL;
+ logger(ifp->ctx, LOG_DEBUG, "%s: executing `%s' %s",
+ ifp->name, argv[0], reason);
+
+ /* Resize for PATH and RC_SVCNAME */
+ svcname = getenv(RC_SVCNAME);
+ ep = realloc(env, sizeof(char *) * (elen + 2 + (svcname ? 1 : 0)));
+ if (ep == NULL) {
+ elen = 0;
+ goto out;
+ }
+ env = ep;
+ /* Add path to it */
+ path = getenv("PATH");
+ if (path) {
+ e = strlen("PATH") + strlen(path) + 2;
+ env[elen] = malloc(e);
+ if (env[elen] == NULL) {
+ elen = 0;
+ goto out;
+ }
+ snprintf(env[elen], e, "PATH=%s", path);
+ } else {
+ env[elen] = strdup(DEFAULT_PATH);
+ if (env[elen] == NULL) {
+ elen = 0;
+ goto out;
+ }
+ }
+ if (svcname) {
+ e = strlen(RC_SVCNAME) + strlen(svcname) + 2;
+ env[++elen] = malloc(e);
+ if (env[elen] == NULL) {
+ elen = 0;
+ goto out;
+ }
+ snprintf(env[elen], e, "%s=%s", RC_SVCNAME, svcname);
+ }
+ env[++elen] = NULL;
+
+ pid = exec_script(ifp->ctx, argv, env);
+ if (pid == -1)
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %m", __func__, argv[0]);
+ else if (pid != 0) {
+ /* Wait for the script to finish */
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ logger(ifp->ctx, LOG_ERR, "waitpid: %m");
+ status = 0;
+ break;
+ }
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status))
+ logger(ifp->ctx, LOG_ERR,
+ "%s: %s: WEXITSTATUS %d",
+ __func__, argv[0], WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status))
+ logger(ifp->ctx, LOG_ERR, "%s: %s: %s",
+ __func__, argv[0], strsignal(WTERMSIG(status)));
+ }
+
+send_listeners:
+ /* Send to our listeners */
+ bigenv = NULL;
+ status = 0;
+ TAILQ_FOREACH(fd, &ifp->ctx->control_fds, next) {
+ if (!(fd->flags & FD_LISTEN))
+ continue;
+ if (bigenv == NULL) {
+ elen = (size_t)arraytostr((const char *const *)env,
+ &bigenv);
+ if ((ssize_t)elen == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: arraytostr: %m",
+ ifp->name);
+ break;
+ }
+ }
+ if (control_queue(fd, bigenv, elen, 1) == -1)
+ logger(ifp->ctx, LOG_ERR,
+ "%s: control_queue: %m", __func__);
+ else
+ status = 1;
+ }
+ if (!status)
+ free(bigenv);
+
+out:
+ /* Cleanup */
+ ep = env;
+ while (*ep)
+ free(*ep++);
+ free(env);
+ if (elen == 0)
+ return -1;
+ return WEXITSTATUS(status);
+}
--- /dev/null
+/* $NetBSD: script.h,v 1.7 2015/03/26 10:26:37 roy Exp $ */
+
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef SCRIPT_H
+#define SCRIPT_H
+
+#include "control.h"
+
+void if_printoptions(void);
+int send_interface(struct fd_list *, const struct interface *);
+int script_runreason(const struct interface *, const char *);
+
+#endif
--- /dev/null
+# $NetBSD: Makefile,v 1.1 2008/07/27 19:31:03 joerg Exp $
+
+SUBDIR= dhcpcd
+
+.include <bsd.subdir.mk>
--- /dev/null
+# $NetBSD: Makefile.inc,v 1.2 2008/09/19 23:00:49 joerg Exp $
+
+.include <bsd.own.mk>
+
+WARNS?= 4
+BINDIR= /sbin
+
+.if (${MKDYNAMICROOT} == "no")
+LDSTATIC?= -static
+.endif
+
--- /dev/null
+# $NetBSD: Makefile,v 1.26 2015/08/21 10:44:43 roy Exp $
+#
+
+PROG= dhcpcd
+SRCS= common.c control.c dhcpcd.c duid.c eloop.c
+SRCS+= if.c if-options.c script.c
+SRCS+= dhcp-common.c dhcpcd-embedded.c
+SRCS+= if-bsd.c
+
+WARNS?= 6
+USE_FORT?= yes # network client (local server)
+
+CPPFLAGS+= -DHAVE_CONFIG_H
+
+.include <bsd.own.mk>
+
+SRCS+= auth.c hmac_md5.c
+
+USE_INET?= yes
+.if (${USE_INET} != "no")
+CPPFLAGS+= -DINET
+SRCS+= arp.c dhcp.c ipv4.c ipv4ll.c
+.endif
+
+.if (${USE_INET6} != "no")
+CPPFLAGS+= -DINET6
+SRCS+= ipv6.c ipv6nd.c dhcp6.c
+.endif
+
+DIST= ${NETBSDSRCDIR}/external/bsd/dhcpcd/dist
+CPPFLAGS+= -I${DIST}
+
+.PATH: ${DIST} ${DIST}/crypt ${LIBC_NET}
+
+SCRIPTS= dhcpcd-run-hooks
+SCRIPTSDIR_dhcpcd-run-hooks= /libexec
+
+CONFIGFILES= dhcpcd.conf
+FILESDIR_dhcpcd.conf= /etc
+
+HOOKS= 01-test 02-dump 10-wpa_supplicant 15-timezone
+HOOKS+= 20-resolv.conf 29-lookup-hostname 30-hostname
+HOOKS+= 50-ntp.conf
+
+FILES= ${HOOKS:C,^,${DIST}/dhcpcd-hooks/,}
+FILESDIR= /libexec/dhcpcd-hooks
+
+MAN= dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8
+
+CLEANFILES= dhcpcd.conf.5 dhcpcd.8 \
+ dhcpcd-run-hooks dhcpcd-run-hooks.8
+
+.for f in dhcpcd-run-hooks dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8
+${f}: ${f}.in
+ ${TOOL_SED} -e 's:@SYSCONFDIR@:/etc:g' -e 's:@DBDIR@:/var/db:g' \
+ -e 's:@LIBDIR@:/lib:g' \
+ -e 's:@RUNDIR@:/var/run:g' \
+ -e 's:@HOOKDIR@:/libexec/dhcpcd-hooks:g' \
+ -e 's:@SCRIPT@:/libexec/dhcpcd-run-hooks:g' \
+ -e 's:@SERVICEEXISTS@::g' \
+ -e 's:@SERVICECMD@::g' \
+ -e 's:@SERVICESTATUS@::g' \
+ ${DIST}/${f}.in > $@
+.endfor
+
+.include <bsd.prog.mk>