aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Woods <woodsb02@FreeBSD.org>2022-08-29 07:04:52 +0000
committerBen Woods <woodsb02@FreeBSD.org>2022-08-29 07:27:36 +0000
commit96dba636abec6d5451820add99300bda2ca6d86a (patch)
tree4ed81d7e264f23333e2f3943b849aa007ec484fa
downloadsrc-vendor/dhcpcd.tar.gz
src-vendor/dhcpcd.zip
Approved by: philip Differential Revision: https://reviews.freebsd.org/D36196
-rw-r--r--.gitignore38
-rw-r--r--BUILDING.md158
-rw-r--r--LICENSE23
-rw-r--r--Makefile108
-rw-r--r--Makefile.inc36
-rw-r--r--README.md96
-rw-r--r--compat/_strtoi.h97
-rw-r--r--compat/arc4random.c173
-rw-r--r--compat/arc4random.h16
-rw-r--r--compat/arc4random_uniform.c56
-rw-r--r--compat/arc4random_uniform.h23
-rw-r--r--compat/bitops.h188
-rw-r--r--compat/consttime_memequal.h28
-rw-r--r--compat/crypt/hmac.c191
-rw-r--r--compat/crypt/hmac.h40
-rw-r--r--compat/crypt/md5.c242
-rw-r--r--compat/crypt/md5.h33
-rw-r--r--compat/crypt/sha256.c303
-rw-r--r--compat/crypt/sha256.h46
-rw-r--r--compat/dprintf.c64
-rw-r--r--compat/dprintf.h43
-rw-r--r--compat/endian.h71
-rw-r--r--compat/pidfile.c271
-rw-r--r--compat/pidfile.h39
-rw-r--r--compat/queue.h175
-rw-r--r--compat/rb.c1346
-rw-r--r--compat/rbtree.h211
-rw-r--r--compat/reallocarray.c60
-rw-r--r--compat/reallocarray.h37
-rw-r--r--compat/setproctitle.c331
-rw-r--r--compat/setproctitle.h58
-rw-r--r--compat/strlcpy.c51
-rw-r--r--compat/strlcpy.h24
-rw-r--r--compat/strtoi.c68
-rw-r--r--compat/strtoi.h45
-rw-r--r--compat/strtou.c68
-rw-r--r--config-null.mk3
-rwxr-xr-xconfigure1814
-rw-r--r--hooks/01-test37
-rw-r--r--hooks/10-wpa_supplicant113
-rw-r--r--hooks/15-timezone47
-rw-r--r--hooks/20-resolv.conf224
-rw-r--r--hooks/29-lookup-hostname39
-rw-r--r--hooks/30-hostname.in158
-rw-r--r--hooks/50-dhcpcd-compat41
-rw-r--r--hooks/50-ntp.conf144
-rw-r--r--hooks/50-yp.conf59
-rw-r--r--hooks/50-ypbind.in84
-rw-r--r--hooks/Makefile75
-rw-r--r--hooks/dhcpcd-run-hooks.8.in234
-rw-r--r--hooks/dhcpcd-run-hooks.in352
-rw-r--r--iconfig.mk8
-rw-r--r--src/GNUmakefile12
-rw-r--r--src/Makefile147
-rw-r--r--src/arp.c634
-rw-r--r--src/arp.h106
-rw-r--r--src/auth.c738
-rw-r--r--src/auth.h100
-rw-r--r--src/bpf.c713
-rw-r--r--src/bpf.h81
-rw-r--r--src/common.c212
-rw-r--r--src/common.h157
-rw-r--r--src/control.c594
-rw-r--r--src/control.h79
-rw-r--r--src/defs.h78
-rw-r--r--src/dev.c204
-rw-r--r--src/dev.h56
-rw-r--r--src/dev/Makefile45
-rw-r--r--src/dev/udev.c186
-rw-r--r--src/dhcp-common.c1029
-rw-r--r--src/dhcp-common.h146
-rw-r--r--src/dhcp.c4314
-rw-r--r--src/dhcp.h285
-rw-r--r--src/dhcp6.c4364
-rw-r--r--src/dhcp6.h253
-rw-r--r--src/dhcpcd-definitions-small.conf127
-rw-r--r--src/dhcpcd-definitions.conf651
-rw-r--r--src/dhcpcd-embedded.c.in36
-rw-r--r--src/dhcpcd-embedded.h.in38
-rw-r--r--src/dhcpcd.8.in885
-rw-r--r--src/dhcpcd.c2639
-rw-r--r--src/dhcpcd.conf48
-rw-r--r--src/dhcpcd.conf.5.in1004
-rw-r--r--src/dhcpcd.h284
-rw-r--r--src/duid.c236
-rw-r--r--src/duid.h41
-rw-r--r--src/eloop.c787
-rw-r--r--src/eloop.h98
-rwxr-xr-xsrc/genembedc30
-rwxr-xr-xsrc/genembedh24
-rw-r--r--src/if-bsd.c1917
-rw-r--r--src/if-linux-wext.c90
-rw-r--r--src/if-linux.c2175
-rw-r--r--src/if-options.c2773
-rw-r--r--src/if-options.h293
-rw-r--r--src/if-sun.c1761
-rw-r--r--src/if.c1042
-rw-r--r--src/if.h265
-rw-r--r--src/ipv4.c1002
-rw-r--r--src/ipv4.h157
-rw-r--r--src/ipv4ll.c554
-rw-r--r--src/ipv4ll.h79
-rw-r--r--src/ipv6.c2389
-rw-r--r--src/ipv6.h316
-rw-r--r--src/ipv6nd.c2075
-rw-r--r--src/ipv6nd.h130
-rw-r--r--src/logerr.c497
-rw-r--r--src/logerr.h111
-rw-r--r--src/privsep-bpf.c372
-rw-r--r--src/privsep-bpf.h50
-rw-r--r--src/privsep-bsd.c278
-rw-r--r--src/privsep-control.c296
-rw-r--r--src/privsep-control.h41
-rw-r--r--src/privsep-inet.c717
-rw-r--r--src/privsep-inet.h58
-rw-r--r--src/privsep-linux.c455
-rw-r--r--src/privsep-root.c1069
-rw-r--r--src/privsep-root.h75
-rw-r--r--src/privsep-sun.c121
-rw-r--r--src/privsep.c1029
-rw-r--r--src/privsep.h221
-rw-r--r--src/route.c799
-rw-r--r--src/route.h145
-rw-r--r--src/sa.c489
-rw-r--r--src/sa.h75
-rw-r--r--src/script.c788
-rw-r--r--src/script.h41
-rw-r--r--tests/Makefile20
-rw-r--r--tests/crypt/.gitignore1
-rw-r--r--tests/crypt/GNUmakefile7
-rw-r--r--tests/crypt/Makefile36
-rw-r--r--tests/crypt/README.md8
-rw-r--r--tests/crypt/run-test.c38
-rw-r--r--tests/crypt/test.h32
-rw-r--r--tests/crypt/test_hmac_md5.c209
-rw-r--r--tests/eloop-bench/.gitignore1
-rw-r--r--tests/eloop-bench/Makefile45
-rw-r--r--tests/eloop-bench/README.md53
-rw-r--r--tests/eloop-bench/eloop-bench.c184
139 files changed, 55129 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..6b1a56329f4f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# Ignore configure generated files
+config.h
+config.mk
+config.log
+
+# Ignore object files
+.depend
+*.o
+*.So
+*.so
+dhcpcd
+
+# Ignore generated embedded files
+dhcpcd-embedded.c
+dhcpcd-embedded.h
+
+# Ignore generated man pages and scripts
+dhcpcd.8
+dhcpcd-run-hooks
+dhcpcd-run-hooks.8
+dhcpcd.conf.5
+hooks/30-hostname
+hooks/50-ypbind
+
+# Ignore distribution
+dhcpcd*.xz*
+
+# Ignore patch files
+*.diff
+*.patch
+*.orig
+*.rej
+
+# Ignore swap files
+*.swp
+
+# Ignore Coverity
+cov-int
diff --git a/BUILDING.md b/BUILDING.md
new file mode 100644
index 000000000000..8b4e1ceae092
--- /dev/null
+++ b/BUILDING.md
@@ -0,0 +1,158 @@
+# Building dhcpcd
+
+This attempts to document various ways of building dhcpcd for your
+platform.
+
+## Size is an issue
+To compile small dhcpcd, maybe to be used for installation media where
+size is a concern, you can use the `--small` configure option to enable
+a reduced feature set within dhcpcd.
+Currently this just removes non important options out of
+`dhcpcd-definitions.conf`, the logfile option,
+DHCPv6 Prefix Delegation and IPv6 address announcement *(to prefer an
+address on another interface)*.
+Other features maybe dropped as and when required.
+dhcpcd can also be made smaller by removing the IPv4 or IPv6 stack:
+ * `--disable-inet`
+ * `--disable-inet6`
+
+Or by removing the following features:
+ * `--disable-auth`
+ * `--disable-arp`
+ * `--disable-arping`
+ * `--disable-ipv4ll`
+ * `--disable-dhcp6`
+ * `--disable-privsep`
+
+You can also move the embedded extended configuration from the dhcpcd binary
+to an external file (LIBEXECDIR/dhcpcd-definitions.conf)
+ * `--disable-embedded`
+If dhcpcd cannot load this file at runtime, dhcpcd will work but will not be
+able to decode any DHCP/DHCPv6 options that are not defined by the user
+in /etc/dhcpcd.conf. This does not really change the total on disk size.
+
+## Cross compiling
+If you're cross compiling you may need set the platform if OS is different
+from the host.
+`--target=sparc-sun-netbsd5.0`
+
+If you're building for an MMU-less system where fork() does not work, you
+should `./configure --disable-fork`.
+This also puts the `--no-background` flag on and stops the `--background` flag
+from working.
+
+## Default directories
+You can change the default dirs with these knobs.
+For example, to satisfy FHS compliance you would do this:
+`./configure --libexecdir=/lib/dhcpcd dbdir=/var/lib/dhcpcd`
+
+## Compile Issues
+We now default to using `-std=c99`. For 64-bit linux, this always works, but
+for 32-bit linux it requires either gnu99 or a patch to `asm/types.h`.
+Most distros patch linux headers so this should work fine.
+linux-2.6.24 finally ships with a working 32-bit header.
+If your linux headers are older, or your distro hasn't patched them you can
+set `CSTD=gnu99` to work around this.
+
+ArchLinux presently sanitises all kernel headers to the latest version
+regardless of the version for your CPU. As such, Arch presently ships a
+3.12 kernel with 3.17 headers which claim that it supports temporary address
+management and no automatic prefix route generation, both of which are
+obviously false. You will have to patch support either in the kernel or
+out of the headers (or dhcpcd itself) to have correct operation.
+
+Linux netlink headers cause a sign conversion error.
+I [submitted a patch](https://lkml.org/lkml/2019/12/17/680),
+but as yet it's not upstreamed.
+
+GLIBC ships an icmp6.h header which will result in signedness warnings.
+Their [bug #22489](https://sourceware.org/bugzilla/show_bug.cgi?id=22489)
+will solve this once it's actually applied.
+
+## OS specific issues
+Some BSD systems do not allow the manipulation of automatically added subnet
+routes. You can find discussion here:
+ http://mail-index.netbsd.org/tech-net/2008/12/03/msg000896.html
+BSD systems where this has been fixed or is known to work are:
+ NetBSD-5.0
+ FreeBSD-10.0
+
+Some BSD systems protect against IPv6 NS/NA messages by ensuring that the
+source address matches a prefix on the recieved by a RA message.
+This is an error as the correct check is for on-link prefixes as the
+kernel may not be handling RA itself.
+BSD systems where this has been fixed or is known to work are:
+ NetBSD-7.0
+ OpenBSD-5.0
+ patch submitted against FreeBSD-10.0
+
+Some BSD systems do not announce IPv6 address flag changes, such as
+`IN6_IFF_TENTATIVE`, `IN6_IFF_DUPLICATED`, etc. On these systems,
+dhcpcd will poll a freshly added address until either `IN6_IFF_TENTATIVE` is
+cleared or `IN6_IFF_DUPLICATED` is set and take action accordingly.
+BSD systems where this has been fixed or is known to work are:
+ NetBSD-7.0
+
+OpenBSD will always add it's own link-local address if no link-local address
+exists, because it doesn't check if the address we are adding is a link-local
+address or not.
+
+Some BSD systems do not announce cached neighbour route changes based
+on reachability to userland. For such systems, IPv6 routers will always
+be assumed to be reachable until they either stop being a router or expire.
+BSD systems where this has been fixed or is known to work are:
+ NetBSD-7.99.3
+
+Linux prior to 3.17 won't allow userland to manage IPv6 temporary addresses.
+Either upgrade or don't allow dhcpcd to manage the RA,
+so don't set either `ipv6ra_own` or `slaac private` in `dhcpcd.conf` if you
+want to have working IPv6 temporary addresses.
+SLAAC private addresses are just as private, just stable.
+
+## Init systems
+We try and detect how dhcpcd should interact with system services at runtime.
+If we cannot auto-detect how do to this, or it is wrong then
+you can change this by passing shell commands to `--serviceexists`,
+`--servicecmd` and optionally `--servicestatus` to `./configure` or overriding
+the service variables in a hook.
+
+
+## /dev management
+Some systems have `/dev` management systems and some of these like to rename
+interfaces. As this system would listen in the same way as dhcpcd to new
+interface arrivals, dhcpcd needs to listen to the `/dev` management sytem
+instead of the kernel. However, if the `/dev` management system breaks, stops
+working, or changes to a new one, dhcpcd should still try and continue to work.
+To facilitate this, dhcpcd allows a plugin to load to instruct dhcpcd when it
+can use an interface. As of the time of writing only udev support is included.
+You can disable this with `--without-dev`, or `without-udev`.
+NOTE: in Gentoo at least, `sys-fs/udev` as provided by systemd leaks memory
+`sys-fs/eudev`, the fork of udev does not and as such is recommended.
+
+
+## Importing into another source control system
+To import the full sources, use the import target.
+To import only the needed sources and documentation, use the import-src
+target.
+Both targets support DESTDIR to set the installation directory,
+if unset it defaults to `/tmp/dhcpcd-$VERSION`
+Example: `make DESTDIR=/usr/src/contrib/dhcpcd import-src`
+
+
+## Hooks
+Not all the hooks in dhcpcd-hooks are installed by default.
+By default we install `01-test`, `20-resolv.conf`and `30-hostname`.
+The other hooks, `10-wpa_supplicant`, `15-timezone` and `29-lookup-hostname`
+are installed to `$(datadir)/dhcpcd/hooks` by default and need to be
+copied to `$(libexecdir)/dhcpcd-hooks` for use.
+The configure program attempts to find hooks for systems you have installed.
+To add more simply
+`./configure -with-hook=ntp.conf`
+
+If using resolvconf, the `20-resolv.conf` hook now requires a version with the
+`-C` and `-c` options to deprecate and activate interfaces to support wireless
+roaming (Linux) or carrier just drops (NetBSD).
+If your resolvconf does not support this then you will see a warning
+about an illegal option when the carrier changes, but things should still work.
+In this instance the DNS information cannot be Deprecated and may not
+be optimal for multi-homed hosts.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000000..aba8b6aa6bdf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2006-2021 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000000..53f61063476b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,108 @@
+SUBDIRS= src hooks
+
+VERSION!= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' src/defs.h
+
+DIST!= if test -d .git; then echo "dist-git"; \
+ else echo "dist-inst"; fi
+FOSSILID?= current
+GITREF?= HEAD
+
+DISTSUFFIX=
+DISTPREFIX?= dhcpcd-${VERSION}${DISTSUFFIX}
+DISTFILEGZ?= ${DISTPREFIX}.tar.gz
+DISTFILE?= ${DISTPREFIX}.tar.xz
+DISTINFO= ${DISTFILE}.distinfo
+DISTINFOSIGN= ${DISTINFO}.asc
+
+CLEANFILES+= *.tar.xz
+
+.PHONY: hooks import import-bsd tests
+
+.SUFFIXES: .in
+
+all: config.h
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+depend: config.h
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+tests:
+ cd $@; ${MAKE} $@
+
+test: tests
+
+hooks:
+ cd $@; ${MAKE}
+
+eginstall:
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+install:
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+proginstall:
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+clean:
+ rm -rf cov-int dhcpcd.xz
+ for x in ${SUBDIRS} tests; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+distclean: clean
+ rm -f config.h config.mk config.log \
+ ${DISTFILE} ${DISTFILEGZ} ${DISTINFO} ${DISTINFOSIGN}
+ rm -f *.diff *.patch *.orig *.rej
+ for x in ${SUBDIRS} tests; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+dist-git:
+ git archive --prefix=${DISTPREFIX}/ ${GITREF} | xz >${DISTFILE}
+
+dist-inst:
+ mkdir /tmp/${DISTPREFIX}
+ cp -RPp * /tmp/${DISTPREFIX}
+ (cd /tmp/${DISTPREFIX}; make clean)
+ tar -cvjpf ${DISTFILE} -C /tmp ${DISTPREFIX}
+ rm -rf /tmp/${DISTPREFIX}
+
+dist: ${DIST}
+
+distinfo: dist
+ rm -f ${DISTINFO} ${DISTINFOSIGN}
+ ${CKSUM} ${DISTFILE} >${DISTINFO}
+ #printf "SIZE (${DISTFILE}) = %s\n" $$(wc -c <${DISTFILE}) >>${DISTINFO}
+ ${PGP} --clearsign --output=${DISTINFOSIGN} ${DISTINFO}
+ chmod 644 ${DISTINFOSIGN}
+ ls -l ${DISTFILE} ${DISTINFO} ${DISTINFOSIGN}
+
+snapshot:
+ rm -rf /tmp/${DISTPREFIX}
+ ${INSTALL} -d /tmp/${DISTPREFIX}
+ cp -RPp * /tmp/${DISTPREFIX}
+ ${MAKE} -C /tmp/${DISTPREFIX} distclean
+ tar cf - -C /tmp ${DISTPREFIX} | xz >${DISTFILE}
+ ls -l ${DISTFILE}
+
+_import: dist
+ rm -rf ${DESTDIR}/*
+ ${INSTALL} -d ${DESTDIR}
+ tar xvpf ${DISTFILE} -C ${DESTDIR} --strip 1
+ @${ECHO}
+ @${ECHO} "============================================================="
+ @${ECHO} "dhcpcd-${VERSION} imported to ${DESTDIR}"
+
+import:
+ ${MAKE} _import DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi`
+
+
+_import-src: clean
+ rm -rf ${DESTDIR}/*
+ ${INSTALL} -d ${DESTDIR}
+ cp LICENSE README.md ${DESTDIR};
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} DESTDIR=${DESTDIR} $@ || exit $$?; cd ..; done
+ @${ECHO}
+ @${ECHO} "============================================================="
+ @${ECHO} "dhcpcd-${VERSION} imported to ${DESTDIR}"
+
+import-src:
+ ${MAKE} _import-src DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi`
+
+include Makefile.inc
diff --git a/Makefile.inc b/Makefile.inc
new file mode 100644
index 000000000000..18b2824dd172
--- /dev/null
+++ b/Makefile.inc
@@ -0,0 +1,36 @@
+# System definitions
+
+PICFLAG?= -fPIC
+
+BINMODE?= 0555
+NONBINMODE?= 0444
+MANMODE?= ${NONBINMODE}
+CONFMODE?= 0644
+DBMODE?= 0750
+
+CC?= cc
+ECHO?= echo
+INSTALL?= install
+LINT?= lint
+SED?= sed
+HOST_SH?= /bin/sh
+
+# This isn't very portable, but I generaly make releases from NetBSD
+CKSUM?= cksum -a SHA256
+PGP?= netpgp
+
+SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks
+HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks
+
+SED_RUNDIR= -e 's:@RUNDIR@:${RUNDIR}:g'
+SED_DBDIR= -e 's:@DBDIR@:${DBDIR}:g'
+SED_LIBDIR= -e 's:@LIBDIR@:${LIBDIR}:g'
+SED_DATADIR= -e 's:@DATADIR@:${DATADIR}:g'
+SED_HOOKDIR= -e 's:@HOOKDIR@:${HOOKDIR}:g'
+SED_SERVICEEXISTS= -e 's:@SERVICEEXISTS@:${SERVICEEXISTS}:g'
+SED_SERVICECMD= -e 's:@SERVICECMD@:${SERVICECMD}:g'
+SED_SERVICESTATUS= -e 's:@SERVICESTATUS@:${SERVICESTATUS}:g'
+SED_STATUSARG= -e 's:@STATUSARG@:${STATUSARG}:g'
+SED_SCRIPT= -e 's:@SCRIPT@:${SCRIPT}:g'
+SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g'
+SED_DEFAULT_HOSTNAME= -e 's:@DEFAULT_HOSTNAME@:${DEFAULT_HOSTNAME}:g'
diff --git a/README.md b/README.md
new file mode 100644
index 000000000000..751ea3d440b2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# dhcpcd
+
+dhcpcd is a
+[DHCP](http://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) and a
+[DHCPv6](http://en.wikipedia.org/wiki/DHCPv6) client.
+It's also an IPv4LL (aka [ZeroConf](http://en.wikipedia.org/wiki/Zeroconf))
+client.
+In layman's terms, dhcpcd runs on your machine and silently configures your
+computer to work on the attached networks without trouble and mostly without
+configuration.
+
+If you're a desktop user then you may also be interested in
+[Network Configurator (dhcpcd-ui)](http://roy.marples.name/projects/dhcpcd-ui)
+which sits in the notification area and monitors the state of the network via
+dhcpcd.
+It also has a nice configuration dialog and the ability to enter a pass phrase
+for wireless networks.
+
+dhcpcd may not be the only daemon running that wants to configure DNS on the
+host, so it uses [openresolv](http://roy.marples.name/projects/openresolv)
+to ensure they can co-exist.
+
+See [BUILDING.md](BUILDING.md) for how to build dhcpcd.
+
+## Configuration
+
+You should read the dhcpcd.conf man page
+and put your options into `/etc/dhcpcd.conf`.
+The default configuration file should work for most people just fine.
+Here it is, in case you lose it.
+
+```
+# 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
+# Respect the network MTU. This is applied to DHCP routes.
+option interface_mtu
+
+# Most distributions have NTP support.
+#option ntp_servers
+
+# A ServerID is required by RFC2131.
+require dhcp_server_identifier
+
+# Generate SLAAC address using the Hardware Address of the interface
+#slaac hwaddr
+# OR generate Stable Private IPv6 Addresses based from the DUID
+slaac private
+```
+
+The dhcpcd man page has a lot of the same options and more,
+which only apply to calling dhcpcd from the command line.
+
+
+## Compatibility
+dhcpcd-5 is only fully command line compatible with dhcpcd-4
+For compatibility with older versions, use dhcpcd-4
+
+## Upgrading
+dhcpcd-7 defaults the database directory to `/var/db/dhcpcd` instead of
+`/var/db` and now stores dhcpcd.duid and dhcpcd.secret in there instead of
+in /etc.
+
+dhcpcd-9 defaults the run directory to `/var/run/dhcpcd` instead of
+`/var/run` and the prefix of dhcpcd has been removed from the files.
+
+## ChangeLog
+We no longer supply a ChangeLog.
+However, you're more than welcome to read the
+[commit log](https://roy.marples.name/git/dhcpcd/log) and
+[archived release announcements](http://roy.marples.name/archives/dhcpcd-discuss/).
diff --git a/compat/_strtoi.h b/compat/_strtoi.h
new file mode 100644
index 000000000000..fcbd18f9b344
--- /dev/null
+++ b/compat/_strtoi.h
@@ -0,0 +1,97 @@
+/* $NetBSD: _strtoi.h,v 1.1 2015/01/22 02:15:59 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * Original version ID:
+ * NetBSD: src/lib/libc/locale/_wcstoul.h,v 1.2 2003/08/07 16:43:03 agc Exp
+ *
+ * Created by Kamil Rytarowski, based on ID:
+ * NetBSD: src/common/lib/libc/stdlib/_strtoul.h,v 1.7 2013/05/17 12:55:56 joerg Exp
+ */
+
+#ifndef _STRTOI_H
+#define _STRTOI_H
+
+/*
+ * function template for strtoi and strtou
+ *
+ * parameters:
+ * _FUNCNAME : function name
+ * __TYPE : return and range limits type
+ * __WRAPPED : wrapped function, strtoimax or strtoumax
+ */
+
+__TYPE
+_FUNCNAME(const char * __restrict nptr, char ** __restrict endptr, int base,
+ __TYPE lo, __TYPE hi, int * rstatus)
+{
+ int serrno;
+ __TYPE im;
+ char *ep;
+ int rep;
+
+ /* endptr may be NULL */
+
+ if (endptr == NULL)
+ endptr = &ep;
+
+ if (rstatus == NULL)
+ rstatus = &rep;
+
+ serrno = errno;
+ errno = 0;
+
+ im = __WRAPPED(nptr, endptr, base);
+
+ *rstatus = errno;
+ errno = serrno;
+
+ if (*rstatus == 0) {
+ /* No digits were found */
+ if (nptr == *endptr)
+ *rstatus = ECANCELED;
+ /* There are further characters after number */
+ else if (**endptr != '\0')
+ *rstatus = ENOTSUP;
+ }
+
+ if (im < lo) {
+ if (*rstatus == 0)
+ *rstatus = ERANGE;
+ return lo;
+ }
+ if (im > hi) {
+ if (*rstatus == 0)
+ *rstatus = ERANGE;
+ return hi;
+ }
+
+ return im;
+}
+#endif
diff --git a/compat/arc4random.c b/compat/arc4random.c
new file mode 100644
index 000000000000..90098127c954
--- /dev/null
+++ b/compat/arc4random.c
@@ -0,0 +1,173 @@
+/*
+ * Arc4 random number generator for OpenBSD.
+ * Copyright 1996 David Mazieres <dm@lcs.mit.edu>.
+ *
+ * Modification and redistribution in source and binary forms is
+ * permitted provided that due credit is given to the author and the
+ * OpenBSD project by leaving this copyright notice intact.
+ */
+
+/*
+ * This code is derived from section 17.1 of Applied Cryptography,
+ * second edition, which describes a stream cipher allegedly
+ * compatible with RSA Labs "RC4" cipher (the actual description of
+ * which is a trade secret). The same algorithm is used as a stream
+ * cipher called "arcfour" in Tatu Ylonen's ssh package.
+ *
+ * Here the stream cipher has been modified always to include the time
+ * when initializing the state. That makes it impossible to
+ * regenerate the same random sequence twice, so this can't be used
+ * for encryption, but will generate good random numbers.
+ *
+ * RC4 is a registered trademark of RSA Laboratories.
+ */
+
+#include <sys/time.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "arc4random.h"
+
+struct arc4_stream {
+ uint8_t i;
+ uint8_t j;
+ uint8_t s[256];
+ size_t count;
+ pid_t stir_pid;
+ int fd;
+};
+
+#define S(n) (n)
+#define S4(n) S(n), S(n + 1), S(n + 2), S(n + 3)
+#define S16(n) S4(n), S4(n + 4), S4(n + 8), S4(n + 12)
+#define S64(n) S16(n), S16(n + 16), S16(n + 32), S16(n + 48)
+#define S256 S64(0), S64(64), S64(128), S64(192)
+
+static struct arc4_stream rs = { .i = 0xff, .j = 0, .s = { S256 },
+ .count = 0, .stir_pid = 0, .fd = -1 };
+
+#undef S
+#undef S4
+#undef S16
+#undef S64
+#undef S256
+
+static void
+arc4_addrandom(struct arc4_stream *as, unsigned char *dat, int datlen)
+{
+ int n;
+ uint8_t si;
+
+ as->i--;
+ for (n = 0; n < 256; n++) {
+ as->i = (uint8_t)(as->i + 1);
+ si = as->s[as->i];
+ as->j = (uint8_t)(as->j + si + dat[n % datlen]);
+ as->s[as->i] = as->s[as->j];
+ as->s[as->j] = si;
+ }
+ as->j = as->i;
+}
+
+static uint8_t
+arc4_getbyte(struct arc4_stream *as)
+{
+ uint8_t si, sj;
+
+ as->i = (uint8_t)(as->i + 1);
+ si = as->s[as->i];
+ as->j = (uint8_t)(as->j + si);
+ sj = as->s[as->j];
+ as->s[as->i] = sj;
+ as->s[as->j] = si;
+ return (as->s[(si + sj) & 0xff]);
+}
+
+static uint32_t
+arc4_getword(struct arc4_stream *as)
+{
+ int val;
+
+ val = (int)((unsigned int)arc4_getbyte(as) << 24);
+ val |= arc4_getbyte(as) << 16;
+ val |= arc4_getbyte(as) << 8;
+ val |= arc4_getbyte(as);
+ return (uint32_t)val;
+}
+
+/* We don't care about any error on read, just use what we have
+ * on the stack. So mask off this GCC warning. */
+#pragma GCC diagnostic ignored "-Wunused-result"
+static void
+arc4_stir(struct arc4_stream *as)
+{
+ struct {
+ struct timeval tv;
+ unsigned int rnd[(128 - sizeof(struct timeval)) /
+ sizeof(unsigned int)];
+ } rdat;
+ size_t n;
+
+ gettimeofday(&rdat.tv, NULL);
+ if (as->fd == -1) {
+#ifndef O_CLOEXEC
+ int fd_opts;
+#endif
+
+ as->fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK
+#ifdef O_CLOEXEC
+ | O_CLOEXEC
+#endif
+ );
+#ifndef O_CLOEXEC
+ if (as->fd != -1 &&
+ (fd_opts = fcntl(as->fd, F_GETFD)))
+ fcntl(as->fd, F_SETFD, fd_opts | FD_CLOEXEC);
+#endif
+ }
+
+ if (as->fd != -1) {
+ /* If there is an error reading, just use what is
+ * on the stack. */
+ /* coverity[check_return] */
+ (void)read(as->fd, rdat.rnd, sizeof(rdat.rnd));
+ }
+
+ /* fd < 0? Ah, what the heck. We'll just take
+ * whatever was on the stack... */
+ /* coverity[uninit_use_in_call] */
+ arc4_addrandom(as, (void *) &rdat, sizeof(rdat));
+
+ /*
+ * Throw away the first N words of output, as suggested in the
+ * paper "Weaknesses in the Key Scheduling Algorithm of RC4"
+ * by Fluher, Mantin, and Shamir. (N = 256 in our case.)
+ */
+ for (n = 0; n < 256 * sizeof(uint32_t); n++)
+ arc4_getbyte(as);
+ as->count = 1600000;
+}
+
+static void
+arc4_stir_if_needed(struct arc4_stream *as)
+{
+ pid_t pid;
+
+ pid = getpid();
+ if (as->count <= sizeof(uint32_t) || as->stir_pid != pid) {
+ as->stir_pid = pid;
+ arc4_stir(as);
+ } else
+ as->count -= sizeof(uint32_t);
+}
+
+uint32_t
+arc4random()
+{
+
+ arc4_stir_if_needed(&rs);
+ return arc4_getword(&rs);
+}
diff --git a/compat/arc4random.h b/compat/arc4random.h
new file mode 100644
index 000000000000..a975fef3cd1b
--- /dev/null
+++ b/compat/arc4random.h
@@ -0,0 +1,16 @@
+/*
+ * Arc4 random number generator for OpenBSD.
+ * Copyright 1996 David Mazieres <dm@lcs.mit.edu>.
+ *
+ * Modification and redistribution in source and binary forms is
+ * permitted provided that due credit is given to the author and the
+ * OpenBSD project by leaving this copyright notice intact.
+ */
+
+#ifndef ARC4RANDOM_H
+#define ARC4RANDOM_H
+
+#include <stdint.h>
+
+uint32_t arc4random(void);
+#endif
diff --git a/compat/arc4random_uniform.c b/compat/arc4random_uniform.c
new file mode 100644
index 000000000000..0f1b7b296759
--- /dev/null
+++ b/compat/arc4random_uniform.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/* We need to include config.h so we pickup either the system arc4random
+ * or our compat one. */
+#include "config.h"
+
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound). This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+uint32_t
+arc4random_uniform(uint32_t upper_bound)
+{
+ uint32_t r, min;
+
+ if (upper_bound < 2)
+ return 0;
+
+ /* 2**32 % x == (2**32 - x) % x */
+ min = -upper_bound % upper_bound;
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ do
+ r = arc4random();
+ while (r < min);
+
+ return r % upper_bound;
+}
diff --git a/compat/arc4random_uniform.h b/compat/arc4random_uniform.h
new file mode 100644
index 000000000000..d7c0a35d4cba
--- /dev/null
+++ b/compat/arc4random_uniform.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#ifndef ARC4RANDOM_UNIFORM_H
+#define ARC4RANDOM_UNIFORM_H
+
+#include <stdint.h>
+
+uint32_t arc4random_uniform(uint32_t);
+#endif
diff --git a/compat/bitops.h b/compat/bitops.h
new file mode 100644
index 000000000000..31979a20f458
--- /dev/null
+++ b/compat/bitops.h
@@ -0,0 +1,188 @@
+/* $NetBSD: bitops.h,v 1.11 2012/12/07 02:27:58 christos Exp $ */
+
+/*-
+ * Copyright (c) 2007, 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas and Joerg Sonnenberger.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 COMPAT_BITOPS_H
+#define COMPAT_BITOPS_H
+
+#include <stdint.h>
+#include "common.h"
+
+/*
+ * Find First Set functions
+ */
+#ifndef ffs32
+static inline int __unused
+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) {
+ //_n >>= 1;
+ _v += 1;
+ }
+ return _v;
+}
+#endif
+
+#ifndef ffs64
+static inline int __unused
+ffs64(uint64_t _n)
+{
+ int _v;
+
+ if (!_n)
+ return 0;
+
+ _v = 1;
+ if ((_n & 0x00000000FFFFFFFFULL) == 0) {
+ _n >>= 32;
+ _v += 32;
+ }
+ if ((_n & 0x000000000000FFFFULL) == 0) {
+ _n >>= 16;
+ _v += 16;
+ }
+ if ((_n & 0x00000000000000FFULL) == 0) {
+ _n >>= 8;
+ _v += 8;
+ }
+ if ((_n & 0x000000000000000FULL) == 0) {
+ _n >>= 4;
+ _v += 4;
+ }
+ if ((_n & 0x0000000000000003ULL) == 0) {
+ _n >>= 2;
+ _v += 2;
+ }
+ if ((_n & 0x0000000000000001ULL) == 0) {
+ //_n >>= 1;
+ _v += 1;
+ }
+ return _v;
+}
+#endif
+
+/*
+ * Find Last Set functions
+ */
+#ifndef fls32
+static __inline int __unused
+fls32(uint32_t _n)
+{
+ int _v;
+
+ if (!_n)
+ return 0;
+
+ _v = 32;
+ if ((_n & 0xFFFF0000U) == 0) {
+ _n <<= 16;
+ _v -= 16;
+ }
+ if ((_n & 0xFF000000U) == 0) {
+ _n <<= 8;
+ _v -= 8;
+ }
+ if ((_n & 0xF0000000U) == 0) {
+ _n <<= 4;
+ _v -= 4;
+ }
+ if ((_n & 0xC0000000U) == 0) {
+ _n <<= 2;
+ _v -= 2;
+ }
+ if ((_n & 0x80000000U) == 0) {
+ //_n <<= 1;
+ _v -= 1;
+ }
+ return _v;
+}
+#endif
+
+#ifndef fls64
+static int __unused
+fls64(uint64_t _n)
+{
+ int _v;
+
+ if (!_n)
+ return 0;
+
+ _v = 64;
+ if ((_n & 0xFFFFFFFF00000000ULL) == 0) {
+ _n <<= 32;
+ _v -= 32;
+ }
+ if ((_n & 0xFFFF000000000000ULL) == 0) {
+ _n <<= 16;
+ _v -= 16;
+ }
+ if ((_n & 0xFF00000000000000ULL) == 0) {
+ _n <<= 8;
+ _v -= 8;
+ }
+ if ((_n & 0xF000000000000000ULL) == 0) {
+ _n <<= 4;
+ _v -= 4;
+ }
+ if ((_n & 0xC000000000000000ULL) == 0) {
+ _n <<= 2;
+ _v -= 2;
+ }
+ if ((_n & 0x8000000000000000ULL) == 0) {
+ //_n <<= 1;
+ _v -= 1;
+ }
+ return _v;
+}
+#endif
+
+#endif /* COMPAT_BITOPS_H_ */
diff --git a/compat/consttime_memequal.h b/compat/consttime_memequal.h
new file mode 100644
index 000000000000..983064841075
--- /dev/null
+++ b/compat/consttime_memequal.h
@@ -0,0 +1,28 @@
+/*
+ * Written by Matthias Drochner <drochner@NetBSD.org>.
+ * Public domain.
+ */
+
+#ifndef CONSTTIME_MEMEQUAL_H
+#define CONSTTIME_MEMEQUAL_H
+inline static int
+consttime_memequal(const void *b1, const void *b2, size_t len)
+{
+ const unsigned char *c1 = b1, *c2 = b2;
+ unsigned int res = 0;
+
+ while (len--)
+ res |= *c1++ ^ *c2++;
+
+ /*
+ * Map 0 to 1 and [1, 256) to 0 using only constant-time
+ * arithmetic.
+ *
+ * This is not simply `!res' because although many CPUs support
+ * branchless conditional moves and many compilers will take
+ * advantage of them, certain compilers generate branches on
+ * certain CPUs for `!res'.
+ */
+ return (1 & ((res - 1) >> 8));
+}
+#endif /* CONSTTIME_MEMEQUAL_H */
diff --git a/compat/crypt/hmac.c b/compat/crypt/hmac.c
new file mode 100644
index 000000000000..55e331ffa021
--- /dev/null
+++ b/compat/crypt/hmac.c
@@ -0,0 +1,191 @@
+/* $NetBSD: hmac.c,v 1.5 2017/10/05 09:59:04 roy Exp $ */
+
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <string.h>
+#include <stdlib.h>
+
+#include "config.h"
+
+#if defined(HAVE_MD5_H) && !defined(DEPGEN)
+#include <md5.h>
+#endif
+
+#ifdef SHA2_H
+# include SHA2_H
+#endif
+
+#ifndef __arraycount
+#define __arraycount(__x) (sizeof(__x) / sizeof(__x[0]))
+#endif
+
+#if 0
+#include <md2.h>
+#include <md4.h>
+#include <md5.h>
+#include <rmd160.h>
+#include <sha1.h>
+#include <sha2.h>
+#endif
+
+#ifndef MD5_BLOCK_LENGTH
+#define MD5_BLOCK_LENGTH 64
+#endif
+#ifndef SHA256_BLOCK_LENGTH
+#define SHA256_BLOCK_LENGTH 64
+#endif
+
+#define HMAC_SIZE 128
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+static const struct hmac {
+ const char *name;
+ size_t ctxsize;
+ size_t digsize;
+ size_t blocksize;
+ void (*init)(void *);
+ void (*update)(void *, const uint8_t *, unsigned int);
+ void (*final)(uint8_t *, void *);
+} hmacs[] = {
+#if 0
+ {
+ "md2", sizeof(MD2_CTX), MD2_DIGEST_LENGTH, MD2_BLOCK_LENGTH,
+ (void *)MD2Init, (void *)MD2Update, (void *)MD2Final,
+ },
+ {
+ "md4", sizeof(MD4_CTX), MD4_DIGEST_LENGTH, MD4_BLOCK_LENGTH,
+ (void *)MD4Init, (void *)MD4Update, (void *)MD4Final,
+ },
+#endif
+ {
+ "md5", sizeof(MD5_CTX), MD5_DIGEST_LENGTH, MD5_BLOCK_LENGTH,
+ (void *)MD5Init, (void *)MD5Update, (void *)MD5Final,
+ },
+#if 0
+ {
+ "rmd160", sizeof(RMD160_CTX), RMD160_DIGEST_LENGTH,
+ RMD160_BLOCK_LENGTH,
+ (void *)RMD160Init, (void *)RMD160Update, (void *)RMD160Final,
+ },
+ {
+ "sha1", sizeof(SHA1_CTX), SHA1_DIGEST_LENGTH, SHA1_BLOCK_LENGTH,
+ (void *)SHA1Init, (void *)SHA1Update, (void *)SHA1Final,
+ },
+ {
+ "sha224", sizeof(SHA224_CTX), SHA224_DIGEST_LENGTH,
+ SHA224_BLOCK_LENGTH,
+ (void *)SHA224_Init, (void *)SHA224_Update,
+ (void *)SHA224_Final,
+ },
+#endif
+ {
+ "sha256", sizeof(SHA256_CTX), SHA256_DIGEST_LENGTH,
+ SHA256_BLOCK_LENGTH,
+ (void *)SHA256_Init, (void *)SHA256_Update,
+ (void *)SHA256_Final,
+ },
+#if 0
+ {
+ "sha384", sizeof(SHA384_CTX), SHA384_DIGEST_LENGTH,
+ SHA384_BLOCK_LENGTH,
+ (void *)SHA384_Init, (void *)SHA384_Update,
+ (void *)SHA384_Final,
+ },
+ {
+ "sha512", sizeof(SHA512_CTX), SHA512_DIGEST_LENGTH,
+ SHA512_BLOCK_LENGTH,
+ (void *)SHA512_Init, (void *)SHA512_Update,
+ (void *)SHA512_Final,
+ },
+#endif
+};
+
+static const struct hmac *
+hmac_find(const char *name)
+{
+ for (size_t i = 0; i < __arraycount(hmacs); i++) {
+ if (strcmp(hmacs[i].name, name) != 0)
+ continue;
+ return &hmacs[i];
+ }
+ return NULL;
+}
+
+ssize_t
+hmac(const char *name,
+ const void *key, size_t klen,
+ const void *text, size_t tlen,
+ void *digest, size_t dlen)
+{
+ uint8_t ipad[HMAC_SIZE], opad[HMAC_SIZE], d[HMAC_SIZE];
+ const uint8_t *k = key;
+ const struct hmac *h;
+ uint64_t c[32];
+ void *p;
+
+ if ((h = hmac_find(name)) == NULL)
+ return -1;
+
+
+ if (klen > h->blocksize) {
+ (*h->init)(c);
+ (*h->update)(c, k, (unsigned int)klen);
+ (*h->final)(d, c);
+ k = (void *)d;
+ klen = h->digsize;
+ }
+
+ /* Form input and output pads for the digests */
+ for (size_t i = 0; i < sizeof(ipad); i++) {
+ ipad[i] = (i < klen ? k[i] : 0) ^ HMAC_IPAD;
+ opad[i] = (i < klen ? k[i] : 0) ^ HMAC_OPAD;
+ }
+
+ p = dlen >= h->digsize ? digest : d;
+ if (p != digest) {
+ memcpy(p, digest, dlen);
+ memset((char *)p + dlen, 0, h->digsize - dlen);
+ }
+ (*h->init)(c);
+ (*h->update)(c, ipad, (unsigned int)h->blocksize);
+ (*h->update)(c, text, (unsigned int)tlen);
+ (*h->final)(p, c);
+
+ (*h->init)(c);
+ (*h->update)(c, opad, (unsigned int)h->blocksize);
+ (*h->update)(c, digest, (unsigned int)h->digsize);
+ (*h->final)(p, c);
+
+ if (p != digest)
+ memcpy(digest, p, dlen);
+
+ return (ssize_t)h->digsize;
+}
diff --git a/compat/crypt/hmac.h b/compat/crypt/hmac.h
new file mode 100644
index 000000000000..af3f77adb025
--- /dev/null
+++ b/compat/crypt/hmac.h
@@ -0,0 +1,40 @@
+/* $NetBSD: hmac.c,v 1.5 2017/10/05 09:59:04 roy Exp $ */
+
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 HMAC_H
+#define HMAC_H
+
+#include <sys/types.h>
+
+ssize_t hmac(const char *, const void *, size_t, const void *, size_t, void *,
+ size_t);
+
+#endif
diff --git a/compat/crypt/md5.c b/compat/crypt/md5.c
new file mode 100644
index 000000000000..dea2356de76d
--- /dev/null
+++ b/compat/crypt/md5.c
@@ -0,0 +1,242 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include <sys/param.h>
+#include <inttypes.h>
+
+#include <string.h>
+
+#include "md5.h"
+
+#define PUT_64BIT_LE(cp, value) do { \
+ (cp)[7] = (uint8_t)((value) >> 56); \
+ (cp)[6] = (uint8_t)((value) >> 48); \
+ (cp)[5] = (uint8_t)((value) >> 40); \
+ (cp)[4] = (uint8_t)((value) >> 32); \
+ (cp)[3] = (uint8_t)((value) >> 24); \
+ (cp)[2] = (uint8_t)((value) >> 16); \
+ (cp)[1] = (uint8_t)((value) >> 8); \
+ (cp)[0] = (uint8_t)(value); } while (0)
+
+#define PUT_32BIT_LE(cp, value) do { \
+ (cp)[3] = (uint8_t)((value) >> 24); \
+ (cp)[2] = (uint8_t)((value) >> 16); \
+ (cp)[1] = (uint8_t)((value) >> 8); \
+ (cp)[0] = (uint8_t)(value); } while (0)
+
+static uint8_t PADDING[MD5_BLOCK_LENGTH] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(MD5_CTX *ctx)
+{
+ ctx->count = 0;
+ ctx->state[0] = 0x67452301;
+ ctx->state[1] = 0xefcdab89;
+ ctx->state[2] = 0x98badcfe;
+ ctx->state[3] = 0x10325476;
+}
+
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void
+MD5Transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH])
+{
+ uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4];
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ memcpy(in, block, sizeof(in));
+#else
+ for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) {
+ in[a] = (uint32_t)(
+ (uint32_t)(block[a * 4 + 0]) |
+ (uint32_t)(block[a * 4 + 1]) << 8 |
+ (uint32_t)(block[a * 4 + 2]) << 16 |
+ (uint32_t)(block[a * 4 + 3]) << 24);
+ }
+#endif
+
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+
+ MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21);
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len)
+{
+ size_t have, need;
+
+ /* Check how many bytes we already have and how many more we need. */
+ have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
+ need = MD5_BLOCK_LENGTH - have;
+
+ /* Update bitcount */
+ ctx->count += (uint64_t)len << 3;
+
+ if (len >= need) {
+ if (have != 0) {
+ memcpy(ctx->buffer + have, input, need);
+ MD5Transform(ctx->state, ctx->buffer);
+ input += need;
+ len -= need;
+ have = 0;
+ }
+
+ /* Process data in MD5_BLOCK_LENGTH-byte chunks. */
+ while (len >= MD5_BLOCK_LENGTH) {
+ MD5Transform(ctx->state, input);
+ input += MD5_BLOCK_LENGTH;
+ len -= MD5_BLOCK_LENGTH;
+ }
+ }
+
+ /* Handle any remaining bytes of data. */
+ if (len != 0)
+ memcpy(ctx->buffer + have, input, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx)
+{
+ uint8_t count[8];
+ size_t padlen;
+ int i;
+
+ /* Convert count to 8 bytes in little endian order. */
+ PUT_64BIT_LE(count, ctx->count);
+
+ /* Pad out to 56 mod 64. */
+ padlen = MD5_BLOCK_LENGTH -
+ ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
+ if (padlen < 1 + 8)
+ padlen += MD5_BLOCK_LENGTH;
+ MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */
+ MD5Update(ctx, count, 8);
+
+ if (digest != NULL) {
+ for (i = 0; i < 4; i++)
+ PUT_32BIT_LE(digest + i * 4, ctx->state[i]);
+ }
+ memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */
+}
+
+
diff --git a/compat/crypt/md5.h b/compat/crypt/md5.h
new file mode 100644
index 000000000000..dd156163315c
--- /dev/null
+++ b/compat/crypt/md5.h
@@ -0,0 +1,33 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#ifndef MD5_H_
+#define MD5_H_
+
+#define MD5_DIGEST_LENGTH 16
+#define MD5_BLOCK_LENGTH 64ULL
+
+typedef struct MD5Context {
+ uint32_t state[4]; /* state (ABCD) */
+ uint64_t count; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[MD5_BLOCK_LENGTH]; /* input buffer */
+} MD5_CTX;
+
+void MD5Init(MD5_CTX *);
+void MD5Update(MD5_CTX *, const unsigned char *, size_t);
+void MD5Final(unsigned char[MD5_DIGEST_LENGTH], MD5_CTX *);
+#endif
diff --git a/compat/crypt/sha256.c b/compat/crypt/sha256.c
new file mode 100644
index 000000000000..73c43c037fa2
--- /dev/null
+++ b/compat/crypt/sha256.c
@@ -0,0 +1,303 @@
+/*-
+ * Copyright 2005 Colin Percival
+ * 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>
+
+#ifdef __GLIBC__
+# include <endian.h>
+#endif
+#ifdef BSD
+# ifndef __QNX__
+# include <sys/endian.h>
+# endif
+#endif
+
+#include "config.h"
+#include "sha256.h"
+
+#if BYTE_ORDER == BIG_ENDIAN
+
+/* Copy a vector of big-endian uint32_t into a vector of bytes */
+#define be32enc_vect(dst, src, len) \
+ memcpy((void *)dst, (const void *)src, (size_t)len)
+
+/* Copy a vector of bytes into a vector of big-endian uint32_t */
+#define be32dec_vect(dst, src, len) \
+ memcpy((void *)dst, (const void *)src, (size_t)len)
+
+#else /* BYTE_ORDER != BIG_ENDIAN */
+
+/*
+ * Encode a length len/4 vector of (uint32_t) into a length len vector of
+ * (unsigned char) in big-endian form. Assumes len is a multiple of 4.
+ */
+static void
+be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len / 4; i++)
+ be32enc(dst + i * 4, src[i]);
+}
+
+/*
+ * Decode a big-endian length len vector of (unsigned char) into a length
+ * len/4 vector of (uint32_t). Assumes len is a multiple of 4.
+ */
+static void
+be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len / 4; i++)
+ dst[i] = be32dec(src + i * 4);
+}
+
+#endif /* BYTE_ORDER != BIG_ENDIAN */
+
+/* Elementary functions used by SHA256 */
+#define Ch(x, y, z) ((x & (y ^ z)) ^ z)
+#define Maj(x, y, z) ((x & (y | z)) | (y & z))
+#define SHR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << (32 - n)))
+#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3))
+#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10))
+
+/* SHA256 round function */
+#define RND(a, b, c, d, e, f, g, h, k) \
+ t0 = h + S1(e) + Ch(e, f, g) + k; \
+ t1 = S0(a) + Maj(a, b, c); \
+ d += t0; \
+ h = t0 + t1;
+
+/* Adjusted round function for rotating state */
+#define RNDr(S, W, i, k) \
+ RND(S[(64 - i) % 8], S[(65 - i) % 8], \
+ S[(66 - i) % 8], S[(67 - i) % 8], \
+ S[(68 - i) % 8], S[(69 - i) % 8], \
+ S[(70 - i) % 8], S[(71 - i) % 8], \
+ W[i] + k)
+
+/*
+ * SHA256 block compression function. The 256-bit state is transformed via
+ * the 512-bit input block to produce a new state.
+ */
+static void
+SHA256_Transform(uint32_t * state, const unsigned char block[64])
+{
+ uint32_t W[64];
+ uint32_t S[8];
+ uint32_t t0, t1;
+ int i;
+
+ /* 1. Prepare message schedule W. */
+ be32dec_vect(W, block, 64);
+ for (i = 16; i < 64; i++)
+ W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16];
+
+ /* 2. Initialize working variables. */
+ memcpy(S, state, 32);
+
+ /* 3. Mix. */
+ RNDr(S, W, 0, 0x428a2f98);
+ RNDr(S, W, 1, 0x71374491);
+ RNDr(S, W, 2, 0xb5c0fbcf);
+ RNDr(S, W, 3, 0xe9b5dba5);
+ RNDr(S, W, 4, 0x3956c25b);
+ RNDr(S, W, 5, 0x59f111f1);
+ RNDr(S, W, 6, 0x923f82a4);
+ RNDr(S, W, 7, 0xab1c5ed5);
+ RNDr(S, W, 8, 0xd807aa98);
+ RNDr(S, W, 9, 0x12835b01);
+ RNDr(S, W, 10, 0x243185be);
+ RNDr(S, W, 11, 0x550c7dc3);
+ RNDr(S, W, 12, 0x72be5d74);
+ RNDr(S, W, 13, 0x80deb1fe);
+ RNDr(S, W, 14, 0x9bdc06a7);
+ RNDr(S, W, 15, 0xc19bf174);
+ RNDr(S, W, 16, 0xe49b69c1);
+ RNDr(S, W, 17, 0xefbe4786);
+ RNDr(S, W, 18, 0x0fc19dc6);
+ RNDr(S, W, 19, 0x240ca1cc);
+ RNDr(S, W, 20, 0x2de92c6f);
+ RNDr(S, W, 21, 0x4a7484aa);
+ RNDr(S, W, 22, 0x5cb0a9dc);
+ RNDr(S, W, 23, 0x76f988da);
+ RNDr(S, W, 24, 0x983e5152);
+ RNDr(S, W, 25, 0xa831c66d);
+ RNDr(S, W, 26, 0xb00327c8);
+ RNDr(S, W, 27, 0xbf597fc7);
+ RNDr(S, W, 28, 0xc6e00bf3);
+ RNDr(S, W, 29, 0xd5a79147);
+ RNDr(S, W, 30, 0x06ca6351);
+ RNDr(S, W, 31, 0x14292967);
+ RNDr(S, W, 32, 0x27b70a85);
+ RNDr(S, W, 33, 0x2e1b2138);
+ RNDr(S, W, 34, 0x4d2c6dfc);
+ RNDr(S, W, 35, 0x53380d13);
+ RNDr(S, W, 36, 0x650a7354);
+ RNDr(S, W, 37, 0x766a0abb);
+ RNDr(S, W, 38, 0x81c2c92e);
+ RNDr(S, W, 39, 0x92722c85);
+ RNDr(S, W, 40, 0xa2bfe8a1);
+ RNDr(S, W, 41, 0xa81a664b);
+ RNDr(S, W, 42, 0xc24b8b70);
+ RNDr(S, W, 43, 0xc76c51a3);
+ RNDr(S, W, 44, 0xd192e819);
+ RNDr(S, W, 45, 0xd6990624);
+ RNDr(S, W, 46, 0xf40e3585);
+ RNDr(S, W, 47, 0x106aa070);
+ RNDr(S, W, 48, 0x19a4c116);
+ RNDr(S, W, 49, 0x1e376c08);
+ RNDr(S, W, 50, 0x2748774c);
+ RNDr(S, W, 51, 0x34b0bcb5);
+ RNDr(S, W, 52, 0x391c0cb3);
+ RNDr(S, W, 53, 0x4ed8aa4a);
+ RNDr(S, W, 54, 0x5b9cca4f);
+ RNDr(S, W, 55, 0x682e6ff3);
+ RNDr(S, W, 56, 0x748f82ee);
+ RNDr(S, W, 57, 0x78a5636f);
+ RNDr(S, W, 58, 0x84c87814);
+ RNDr(S, W, 59, 0x8cc70208);
+ RNDr(S, W, 60, 0x90befffa);
+ RNDr(S, W, 61, 0xa4506ceb);
+ RNDr(S, W, 62, 0xbef9a3f7);
+ RNDr(S, W, 63, 0xc67178f2);
+
+ /* 4. Mix local working variables into global state */
+ for (i = 0; i < 8; i++)
+ state[i] += S[i];
+}
+
+static unsigned char PAD[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* Add padding and terminating bit-count. */
+static void
+SHA256_Pad(SHA256_CTX * ctx)
+{
+ unsigned char len[8];
+ uint32_t r, plen;
+
+ /*
+ * Convert length to a vector of bytes -- we do this now rather
+ * than later because the length will change after we pad.
+ */
+ be64enc(len, ctx->count);
+
+ /* Add 1--64 bytes so that the resulting length is 56 mod 64 */
+ r = (ctx->count >> 3) & 0x3f;
+ plen = (r < 56) ? (56 - r) : (120 - r);
+ SHA256_Update(ctx, PAD, (size_t)plen);
+
+ /* Add the terminating bit-count */
+ SHA256_Update(ctx, len, 8);
+}
+
+/* SHA-256 initialization. Begins a SHA-256 operation. */
+void
+SHA256_Init(SHA256_CTX * ctx)
+{
+
+ /* Zero bits processed so far */
+ ctx->count = 0;
+
+ /* Magic initialization constants */
+ ctx->state[0] = 0x6A09E667;
+ ctx->state[1] = 0xBB67AE85;
+ ctx->state[2] = 0x3C6EF372;
+ ctx->state[3] = 0xA54FF53A;
+ ctx->state[4] = 0x510E527F;
+ ctx->state[5] = 0x9B05688C;
+ ctx->state[6] = 0x1F83D9AB;
+ ctx->state[7] = 0x5BE0CD19;
+}
+
+/* Add bytes into the hash */
+void
+SHA256_Update(SHA256_CTX * ctx, const void *in, size_t len)
+{
+ uint64_t bitlen;
+ uint32_t r;
+ const unsigned char *src = in;
+
+ /* Number of bytes left in the buffer from previous updates */
+ r = (ctx->count >> 3) & 0x3f;
+
+ /* Convert the length into a number of bits */
+ bitlen = len << 3;
+
+ /* Update number of bits */
+ ctx->count += bitlen;
+
+ /* Handle the case where we don't need to perform any transforms */
+ if (len < 64 - r) {
+ memcpy(&ctx->buf[r], src, len);
+ return;
+ }
+
+ /* Finish the current block */
+ memcpy(&ctx->buf[r], src, 64 - r);
+ SHA256_Transform(ctx->state, ctx->buf);
+ src += 64 - r;
+ len -= 64 - r;
+
+ /* Perform complete blocks */
+ while (len >= 64) {
+ SHA256_Transform(ctx->state, src);
+ src += 64;
+ len -= 64;
+ }
+
+ /* Copy left over data into buffer */
+ memcpy(ctx->buf, src, len);
+}
+
+/*
+ * SHA-256 finalization. Pads the input data, exports the hash value,
+ * and clears the context state.
+ */
+void
+SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx)
+{
+
+ /* Add padding */
+ SHA256_Pad(ctx);
+
+ /* Write the hash */
+ be32enc_vect(digest, ctx->state, 32);
+
+ /* Clear the context state */
+ memset((void *)ctx, 0, sizeof(*ctx));
+}
diff --git a/compat/crypt/sha256.h b/compat/crypt/sha256.h
new file mode 100644
index 000000000000..02a09f935b20
--- /dev/null
+++ b/compat/crypt/sha256.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright 2005 Colin Percival
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef SHA256_H_
+#define SHA256_H_
+
+#include <sys/types.h>
+
+#define SHA256_DIGEST_LENGTH 32
+
+typedef struct SHA256Context {
+ uint32_t state[8];
+ uint64_t count;
+ unsigned char buf[64];
+} SHA256_CTX;
+
+void SHA256_Init(SHA256_CTX *);
+void SHA256_Update(SHA256_CTX *, const void *, size_t);
+void SHA256_Final(unsigned char [32], SHA256_CTX *);
+
+#endif
diff --git a/compat/dprintf.c b/compat/dprintf.c
new file mode 100644
index 000000000000..2ef81ade814b
--- /dev/null
+++ b/compat/dprintf.c
@@ -0,0 +1,64 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2017 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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#include "dprintf.h"
+
+int
+vdprintf(int fd, const char * __restrict fmt, va_list va)
+{
+ int e;
+ FILE *fp;
+
+ if ((e = dup(fd)) == -1)
+ return -1;
+
+ if ((fp = fdopen(e, "a")) == NULL) {
+ close(e);
+ return -1;
+ }
+
+ e = vfprintf(fp, fmt, va);
+ fclose(fp);
+ return e;
+}
+
+int
+dprintf(int fd, const char * __restrict fmt, ...)
+{
+ int e;
+ va_list va;
+
+ va_start(va, fmt);
+ e = vdprintf(fd, fmt, va);
+ va_end(va);
+ return e;
+}
diff --git a/compat/dprintf.h b/compat/dprintf.h
new file mode 100644
index 000000000000..9defbcb835ff
--- /dev/null
+++ b/compat/dprintf.h
@@ -0,0 +1,43 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2017 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 DPRINTF_H
+#define DPRINTF_H
+
+#include <stdarg.h>
+
+#ifndef __printflike
+# if __GNUC__ > 2 || defined(__INTEL_COMPILER)
+# define __printflike(a, b) __attribute__((format(printf, a, b)))
+# else
+# define __printflike(a, b)
+# endif
+#endif
+
+__printflike(2, 0) int vdprintf(int, const char * __restrict, va_list);
+__printflike(2, 3) int dprintf(int, const char * __restrict, ...);
+#endif
diff --git a/compat/endian.h b/compat/endian.h
new file mode 100644
index 000000000000..8d01738bd242
--- /dev/null
+++ b/compat/endian.h
@@ -0,0 +1,71 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 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 ENDIAN_H
+#define ENDIAN_H
+
+#include <stdint.h>
+
+inline static void
+be32enc(uint8_t *buf, uint32_t u)
+{
+
+ buf[0] = (uint8_t)((u >> 24) & 0xff);
+ buf[1] = (uint8_t)((u >> 16) & 0xff);
+ buf[2] = (uint8_t)((u >> 8) & 0xff);
+ buf[3] = (uint8_t)(u & 0xff);
+}
+
+inline static void
+be64enc(uint8_t *buf, uint64_t u)
+{
+
+ be32enc(buf, (uint32_t)(u >> 32));
+ be32enc(buf + sizeof(uint32_t), (uint32_t)(u & 0xffffffffULL));
+}
+
+inline static uint16_t
+be16dec(const uint8_t *buf)
+{
+
+ return (uint16_t)(buf[0] << 8 | buf[1]);
+}
+
+inline static uint32_t
+be32dec(const uint8_t *buf)
+{
+
+ return (uint32_t)((uint32_t)be16dec(buf) << 16 | be16dec(buf + 2));
+}
+
+inline static uint64_t
+be64dec(const uint8_t *buf)
+{
+
+ return (uint64_t)((uint64_t)be32dec(buf) << 32 | be32dec(buf + 4));
+}
+#endif
diff --git a/compat/pidfile.c b/compat/pidfile.c
new file mode 100644
index 000000000000..9dfd9c468a7a
--- /dev/null
+++ b/compat/pidfile.c
@@ -0,0 +1,271 @@
+/* $NetBSD: pidfile.c,v 1.14 2016/04/12 20:40:43 roy Exp $ */
+
+/*-
+ * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/file.h> /* for flock(2) */
+#include "config.h"
+#include "defs.h"
+
+static pid_t pidfile_pid;
+static char pidfile_path[PATH_MAX];
+static int pidfile_fd = -1;
+
+/* Closes pidfile resources.
+ *
+ * Returns 0 on success, otherwise -1. */
+static int
+pidfile_close(void)
+{
+ int error;
+
+ pidfile_pid = 0;
+ error = close(pidfile_fd);
+ pidfile_fd = -1;
+ pidfile_path[0] = '\0';
+ return error;
+}
+
+/* Truncate, close and unlink an existent pidfile,
+ * if and only if it was created by this process.
+ * The pidfile is truncated because we may have dropped permissions
+ * or entered a chroot and thus unable to unlink it.
+ *
+ * Returns 0 on truncation success, otherwise -1. */
+int
+pidfile_clean(void)
+{
+ int error;
+
+ if (pidfile_fd == -1) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (pidfile_pid != getpid())
+ error = EPERM;
+ else if (ftruncate(pidfile_fd, 0) == -1)
+ error = errno;
+ else {
+#ifndef HAVE_PLEDGE /* Avoid a pledge violating segfault. */
+ (void)unlink(pidfile_path);
+#endif
+ error = 0;
+ }
+
+ (void) pidfile_close();
+
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+ return 0;
+}
+
+/* atexit shim for pidfile_clean */
+static void
+pidfile_cleanup(void)
+{
+
+ pidfile_clean();
+}
+
+/* Constructs a name for a pidfile in the default location (/var/run).
+ * If 'bname' is NULL, uses the name of the current program for the name of
+ * the pidfile.
+ *
+ * Returns 0 on success, otherwise -1. */
+static int
+pidfile_varrun_path(char *path, size_t len, const char *bname)
+{
+
+ if (bname == NULL)
+ bname = PACKAGE;
+
+ /* _PATH_VARRUN includes trailing / */
+ if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ return 0;
+}
+
+/* Returns the process ID inside path on success, otherwise -1.
+ * If no path is given, use the last pidfile path, othewise the default one. */
+pid_t
+pidfile_read(const char *path)
+{
+ char dpath[PATH_MAX], buf[16], *eptr;
+ int fd, error;
+ ssize_t n;
+ pid_t pid;
+
+ if (path == NULL && pidfile_path[0] != '\0')
+ path = pidfile_path;
+ if (path == NULL || strchr(path, '/') == NULL) {
+ if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1)
+ return -1;
+ path = dpath;
+ }
+
+ if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1)
+ return -1;
+ n = read(fd, buf, sizeof(buf) - 1);
+ error = errno;
+ (void) close(fd);
+ if (n == -1) {
+ errno = error;
+ return -1;
+ }
+ buf[n] = '\0';
+ pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error);
+ if (error && !(error == ENOTSUP && *eptr == '\n')) {
+ errno = error;
+ return -1;
+ }
+ return pid;
+}
+
+/* Locks the pidfile specified by path and writes the process pid to it.
+ * The new pidfile is "registered" in the global variables pidfile_fd,
+ * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3)
+ * can check if we are recreating the same file or a new one.
+ *
+ * Returns 0 on success, otherwise the pid of the process who owns the
+ * lock if it can be read, otherwise -1. */
+pid_t
+pidfile_lock(const char *path)
+{
+ char dpath[PATH_MAX];
+ static bool registered_atexit = false;
+
+ /* Register for cleanup with atexit. */
+ if (!registered_atexit) {
+ if (atexit(pidfile_cleanup) == -1)
+ return -1;
+ registered_atexit = true;
+ }
+
+ if (path == NULL || strchr(path, '/') == NULL) {
+ if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1)
+ return -1;
+ path = dpath;
+ }
+
+ /* If path has changed (no good reason), clean up the old pidfile. */
+ if (pidfile_fd != -1 && strcmp(pidfile_path, path) != 0)
+ pidfile_clean();
+
+ if (pidfile_fd == -1) {
+ int fd, opts;
+
+ opts = O_WRONLY | O_CREAT | O_NONBLOCK;
+#ifdef O_CLOEXEC
+ opts |= O_CLOEXEC;
+#endif
+#ifdef O_EXLOCK
+ opts |= O_EXLOCK;
+#endif
+ if ((fd = open(path, opts, 0644)) == -1)
+ goto return_pid;
+#ifndef O_CLOEXEC
+ if ((opts = fcntl(fd, F_GETFD)) == -1 ||
+ fcntl(fd, F_SETFL, opts | FD_CLOEXEC) == -1)
+ {
+ int error = errno;
+
+ (void) close(fd);
+ errno = error;
+ return -1;
+ }
+#endif
+#ifndef O_EXLOCK
+ if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
+ int error = errno;
+
+ (void) close(fd);
+ if (error != EAGAIN) {
+ errno = error;
+ return -1;
+ }
+ fd = -1;
+ }
+#endif
+
+return_pid:
+ if (fd == -1) {
+ pid_t pid;
+
+ if (errno == EAGAIN) {
+ /* The pidfile is locked, return the process ID
+ * it contains.
+ * If sucessful, set errno to EEXIST. */
+ if ((pid = pidfile_read(path)) != -1)
+ errno = EEXIST;
+ } else
+ pid = -1;
+
+ return pid;
+ }
+ pidfile_fd = fd;
+ strlcpy(pidfile_path, path, sizeof(pidfile_path));
+ }
+
+ pidfile_pid = getpid();
+
+ /* Truncate the file, as we could be re-writing it.
+ * Then write the process ID. */
+ if (ftruncate(pidfile_fd, 0) == -1 ||
+ lseek(pidfile_fd, 0, SEEK_SET) == -1 ||
+ dprintf(pidfile_fd, "%d\n", pidfile_pid) == -1)
+ {
+ int error = errno;
+
+ pidfile_cleanup();
+ errno = error;
+ return -1;
+ }
+
+ /* Hold the fd open to persist the lock. */
+ return 0;
+}
diff --git a/compat/pidfile.h b/compat/pidfile.h
new file mode 100644
index 000000000000..ed833e15232c
--- /dev/null
+++ b/compat/pidfile.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 PIDFILE_H
+#define PIDFILE_H
+
+#include <unistd.h>
+
+int pidfile_clean(void);
+pid_t pidfile_lock(const char *);
+pid_t pidfile_read(const char *);
+
+#endif
diff --git a/compat/queue.h b/compat/queue.h
new file mode 100644
index 000000000000..99ac6b9460ea
--- /dev/null
+++ b/compat/queue.h
@@ -0,0 +1,175 @@
+/* $NetBSD: queue.h,v 1.65 2013/12/25 17:19:34 christos Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef COMPAT_QUEUE_H
+#define COMPAT_QUEUE_H
+
+/*
+ * Tail queue definitions.
+ */
+#ifndef TAILQ_END
+#define TAILQ_END(head) (NULL)
+#endif
+
+#ifndef TAILQ_HEAD
+#define _TAILQ_HEAD(name, type, qual) \
+struct name { \
+ qual type *tqh_first; /* first element */ \
+ qual type *qual *tqh_last; /* addr of last next element */ \
+}
+#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { TAILQ_END(head), &(head).tqh_first }
+
+#define _TAILQ_ENTRY(type, qual) \
+struct { \
+ qual type *tqe_next; /* next element */ \
+ qual type *qual *tqe_prev; /* address of previous next element */\
+}
+#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,)
+#endif /* !TAILQ_HEAD */
+
+/*
+ * Tail queue access methods.
+ */
+#ifndef TAILQ_FIRST
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head))
+#endif /* !TAILQ_FIRST */
+
+#ifndef TAILQ_FOREACH
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = ((head)->tqh_first); \
+ (var) != TAILQ_END(head); \
+ (var) = ((var)->field.tqe_next))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\
+ (var) != TAILQ_END(head); \
+ (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
+#endif /* !TAILQ_FOREACH */
+
+#ifndef TAILQ_INIT
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = TAILQ_END(head); \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = TAILQ_END(head); \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != \
+ TAILQ_END(head)) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != TAILQ_END(head)) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+#endif /* !TAILQ_INIT */
+
+#ifndef TAILQ_REPLACE
+#define TAILQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != \
+ TAILQ_END(head)) \
+ (elm2)->field.tqe_next->field.tqe_prev = \
+ &(elm2)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm2)->field.tqe_next; \
+ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
+ *(elm2)->field.tqe_prev = (elm2); \
+} while (/*CONSTCOND*/0)
+#endif /* !TAILQ_REPLACE */
+
+#ifndef TAILQ_FOREACH_SAFE
+#define TAILQ_FOREACH_SAFE(var, head, field, next) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head) && \
+ ((next) = TAILQ_NEXT(var, field), 1); (var) = (next))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) != TAILQ_END(head) && \
+ ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev))
+#endif /* !TAILQ_FOREACH_SAFE */
+
+#ifndef TAILQ_CONCAT
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ } \
+} while (/*CONSTCOND*/0)
+#endif /* !TAILQ_CONCAT */
+
+#endif /* !COMAPT_QUEUE_H */
diff --git a/compat/rb.c b/compat/rb.c
new file mode 100644
index 000000000000..3c0bed5f70d5
--- /dev/null
+++ b/compat/rb.c
@@ -0,0 +1,1346 @@
+/* $NetBSD: rb.c,v 1.14 2019/03/08 09:14:54 roy Exp $ */
+
+/*-
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Matt Thomas <matt@3am-software.com>.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 "config.h"
+#include "common.h"
+
+#if !defined(_KERNEL) && !defined(_STANDALONE)
+#include <sys/types.h>
+#include <stddef.h>
+#include <assert.h>
+#include <stdbool.h>
+#ifdef RBDEBUG
+#define KASSERT(s) assert(s)
+#define __rbt_unused
+#else
+#define KASSERT(s) do { } while (/*CONSTCOND*/ 0)
+#define __rbt_unused __unused
+#endif
+__RCSID("$NetBSD: rb.c,v 1.14 2019/03/08 09:14:54 roy Exp $");
+#else
+#include <lib/libkern/libkern.h>
+__KERNEL_RCSID(0, "$NetBSD: rb.c,v 1.14 2019/03/08 09:14:54 roy Exp $");
+#ifndef DIAGNOSTIC
+#define __rbt_unused __unused
+#else
+#define __rbt_unused
+#endif
+#endif
+
+#ifdef _LIBC
+__weak_alias(rb_tree_init, _rb_tree_init)
+__weak_alias(rb_tree_find_node, _rb_tree_find_node)
+__weak_alias(rb_tree_find_node_geq, _rb_tree_find_node_geq)
+__weak_alias(rb_tree_find_node_leq, _rb_tree_find_node_leq)
+__weak_alias(rb_tree_insert_node, _rb_tree_insert_node)
+__weak_alias(rb_tree_remove_node, _rb_tree_remove_node)
+__weak_alias(rb_tree_iterate, _rb_tree_iterate)
+#ifdef RBDEBUG
+__weak_alias(rb_tree_check, _rb_tree_check)
+__weak_alias(rb_tree_depths, _rb_tree_depths)
+#endif
+
+#include "namespace.h"
+#endif
+
+#ifdef RBTEST
+#include "rbtree.h"
+#else
+#include <sys/rbtree.h>
+#endif
+
+static void rb_tree_insert_rebalance(struct rb_tree *, struct rb_node *);
+static void rb_tree_removal_rebalance(struct rb_tree *, struct rb_node *,
+ unsigned int);
+#ifdef RBDEBUG
+static const struct rb_node *rb_tree_iterate_const(const struct rb_tree *,
+ const struct rb_node *, const unsigned int);
+static bool rb_tree_check_node(const struct rb_tree *, const struct rb_node *,
+ const struct rb_node *, bool);
+#else
+#define rb_tree_check_node(a, b, c, d) true
+#endif
+
+#define RB_NODETOITEM(rbto, rbn) \
+ ((void *)((uintptr_t)(rbn) - (rbto)->rbto_node_offset))
+#define RB_ITEMTONODE(rbto, rbn) \
+ ((rb_node_t *)((uintptr_t)(rbn) + (rbto)->rbto_node_offset))
+
+#define RB_SENTINEL_NODE NULL
+
+void
+rb_tree_init(struct rb_tree *rbt, const rb_tree_ops_t *ops)
+{
+
+ rbt->rbt_ops = ops;
+ rbt->rbt_root = RB_SENTINEL_NODE;
+ RB_TAILQ_INIT(&rbt->rbt_nodes);
+#ifndef RBSMALL
+ rbt->rbt_minmax[RB_DIR_LEFT] = rbt->rbt_root; /* minimum node */
+ rbt->rbt_minmax[RB_DIR_RIGHT] = rbt->rbt_root; /* maximum node */
+#endif
+#ifdef RBSTATS
+ rbt->rbt_count = 0;
+ rbt->rbt_insertions = 0;
+ rbt->rbt_removals = 0;
+ rbt->rbt_insertion_rebalance_calls = 0;
+ rbt->rbt_insertion_rebalance_passes = 0;
+ rbt->rbt_removal_rebalance_calls = 0;
+ rbt->rbt_removal_rebalance_passes = 0;
+#endif
+}
+
+void *
+rb_tree_find_node(struct rb_tree *rbt, const void *key)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ rbto_compare_key_fn compare_key = rbto->rbto_compare_key;
+ struct rb_node *parent = rbt->rbt_root;
+
+ while (!RB_SENTINEL_P(parent)) {
+ void *pobj = RB_NODETOITEM(rbto, parent);
+ const signed int diff = (*compare_key)(rbto->rbto_context,
+ pobj, key);
+ if (diff == 0)
+ return pobj;
+ parent = parent->rb_nodes[diff < 0];
+ }
+
+ return NULL;
+}
+
+void *
+rb_tree_find_node_geq(struct rb_tree *rbt, const void *key)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ rbto_compare_key_fn compare_key = rbto->rbto_compare_key;
+ struct rb_node *parent = rbt->rbt_root, *last = NULL;
+
+ while (!RB_SENTINEL_P(parent)) {
+ void *pobj = RB_NODETOITEM(rbto, parent);
+ const signed int diff = (*compare_key)(rbto->rbto_context,
+ pobj, key);
+ if (diff == 0)
+ return pobj;
+ if (diff > 0)
+ last = parent;
+ parent = parent->rb_nodes[diff < 0];
+ }
+
+ return last == NULL ? NULL : RB_NODETOITEM(rbto, last);
+}
+
+void *
+rb_tree_find_node_leq(struct rb_tree *rbt, const void *key)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ rbto_compare_key_fn compare_key = rbto->rbto_compare_key;
+ struct rb_node *parent = rbt->rbt_root, *last = NULL;
+
+ while (!RB_SENTINEL_P(parent)) {
+ void *pobj = RB_NODETOITEM(rbto, parent);
+ const signed int diff = (*compare_key)(rbto->rbto_context,
+ pobj, key);
+ if (diff == 0)
+ return pobj;
+ if (diff < 0)
+ last = parent;
+ parent = parent->rb_nodes[diff < 0];
+ }
+
+ return last == NULL ? NULL : RB_NODETOITEM(rbto, last);
+}
+
+void *
+rb_tree_insert_node(struct rb_tree *rbt, void *object)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ rbto_compare_nodes_fn compare_nodes = rbto->rbto_compare_nodes;
+ struct rb_node *parent, *tmp, *self = RB_ITEMTONODE(rbto, object);
+ unsigned int position;
+ bool rebalance;
+
+ RBSTAT_INC(rbt->rbt_insertions);
+
+ tmp = rbt->rbt_root;
+ /*
+ * This is a hack. Because rbt->rbt_root is just a struct rb_node *,
+ * just like rb_node->rb_nodes[RB_DIR_LEFT], we can use this fact to
+ * avoid a lot of tests for root and know that even at root,
+ * updating RB_FATHER(rb_node)->rb_nodes[RB_POSITION(rb_node)] will
+ * update rbt->rbt_root.
+ */
+ parent = (struct rb_node *)(void *)&rbt->rbt_root;
+ position = RB_DIR_LEFT;
+
+ /*
+ * Find out where to place this new leaf.
+ */
+ while (!RB_SENTINEL_P(tmp)) {
+ void *tobj = RB_NODETOITEM(rbto, tmp);
+ const signed int diff = (*compare_nodes)(rbto->rbto_context,
+ tobj, object);
+ if (__predict_false(diff == 0)) {
+ /*
+ * Node already exists; return it.
+ */
+ return tobj;
+ }
+ parent = tmp;
+ position = (diff < 0);
+ tmp = parent->rb_nodes[position];
+ }
+
+#ifdef RBDEBUG
+ {
+ struct rb_node *prev = NULL, *next = NULL;
+
+ if (position == RB_DIR_RIGHT)
+ prev = parent;
+ else if (tmp != rbt->rbt_root)
+ next = parent;
+
+ /*
+ * Verify our sequential position
+ */
+ KASSERT(prev == NULL || !RB_SENTINEL_P(prev));
+ KASSERT(next == NULL || !RB_SENTINEL_P(next));
+ if (prev != NULL && next == NULL)
+ next = TAILQ_NEXT(prev, rb_link);
+ if (prev == NULL && next != NULL)
+ prev = TAILQ_PREV(next, rb_node_qh, rb_link);
+ KASSERT(prev == NULL || !RB_SENTINEL_P(prev));
+ KASSERT(next == NULL || !RB_SENTINEL_P(next));
+ KASSERT(prev == NULL || (*compare_nodes)(rbto->rbto_context,
+ RB_NODETOITEM(rbto, prev), RB_NODETOITEM(rbto, self)) < 0);
+ KASSERT(next == NULL || (*compare_nodes)(rbto->rbto_context,
+ RB_NODETOITEM(rbto, self), RB_NODETOITEM(rbto, next)) < 0);
+ }
+#endif
+
+ /*
+ * Initialize the node and insert as a leaf into the tree.
+ */
+ RB_SET_FATHER(self, parent);
+ RB_SET_POSITION(self, position);
+ if (__predict_false(parent == (struct rb_node *)(void *)&rbt->rbt_root)) {
+ RB_MARK_BLACK(self); /* root is always black */
+#ifndef RBSMALL
+ rbt->rbt_minmax[RB_DIR_LEFT] = self;
+ rbt->rbt_minmax[RB_DIR_RIGHT] = self;
+#endif
+ rebalance = false;
+ } else {
+ KASSERT(position == RB_DIR_LEFT || position == RB_DIR_RIGHT);
+#ifndef RBSMALL
+ /*
+ * Keep track of the minimum and maximum nodes. If our
+ * parent is a minmax node and we on their min/max side,
+ * we must be the new min/max node.
+ */
+ if (parent == rbt->rbt_minmax[position])
+ rbt->rbt_minmax[position] = self;
+#endif /* !RBSMALL */
+ /*
+ * All new nodes are colored red. We only need to rebalance
+ * if our parent is also red.
+ */
+ RB_MARK_RED(self);
+ rebalance = RB_RED_P(parent);
+ }
+ KASSERT(RB_SENTINEL_P(parent->rb_nodes[position]));
+ self->rb_left = parent->rb_nodes[position];
+ self->rb_right = parent->rb_nodes[position];
+ parent->rb_nodes[position] = self;
+ KASSERT(RB_CHILDLESS_P(self));
+
+ /*
+ * Insert the new node into a sorted list for easy sequential access
+ */
+ RBSTAT_INC(rbt->rbt_count);
+#ifdef RBDEBUG
+ if (RB_ROOT_P(rbt, self)) {
+ RB_TAILQ_INSERT_HEAD(&rbt->rbt_nodes, self, rb_link);
+ } else if (position == RB_DIR_LEFT) {
+ KASSERT((*compare_nodes)(rbto->rbto_context,
+ RB_NODETOITEM(rbto, self),
+ RB_NODETOITEM(rbto, RB_FATHER(self))) < 0);
+ RB_TAILQ_INSERT_BEFORE(RB_FATHER(self), self, rb_link);
+ } else {
+ KASSERT((*compare_nodes)(rbto->rbto_context,
+ RB_NODETOITEM(rbto, RB_FATHER(self)),
+ RB_NODETOITEM(rbto, self)) < 0);
+ RB_TAILQ_INSERT_AFTER(&rbt->rbt_nodes, RB_FATHER(self),
+ self, rb_link);
+ }
+#endif
+ KASSERT(rb_tree_check_node(rbt, self, NULL, !rebalance));
+
+ /*
+ * Rebalance tree after insertion
+ */
+ if (rebalance) {
+ rb_tree_insert_rebalance(rbt, self);
+ KASSERT(rb_tree_check_node(rbt, self, NULL, true));
+ }
+
+ /* Succesfully inserted, return our node pointer. */
+ return object;
+}
+
+/*
+ * Swap the location and colors of 'self' and its child @ which. The child
+ * can not be a sentinel node. This is our rotation function. However,
+ * since it preserves coloring, it great simplifies both insertion and
+ * removal since rotation almost always involves the exchanging of colors
+ * as a separate step.
+ */
+static void
+rb_tree_reparent_nodes(__rbt_unused struct rb_tree *rbt,
+ struct rb_node *old_father, const unsigned int which)
+{
+ const unsigned int other = which ^ RB_DIR_OTHER;
+ struct rb_node * const grandpa = RB_FATHER(old_father);
+ struct rb_node * const old_child = old_father->rb_nodes[which];
+ struct rb_node * const new_father = old_child;
+ struct rb_node * const new_child = old_father;
+
+ KASSERT(which == RB_DIR_LEFT || which == RB_DIR_RIGHT);
+
+ KASSERT(!RB_SENTINEL_P(old_child));
+ KASSERT(RB_FATHER(old_child) == old_father);
+
+ KASSERT(rb_tree_check_node(rbt, old_father, NULL, false));
+ KASSERT(rb_tree_check_node(rbt, old_child, NULL, false));
+ KASSERT(RB_ROOT_P(rbt, old_father) ||
+ rb_tree_check_node(rbt, grandpa, NULL, false));
+
+ /*
+ * Exchange descendant linkages.
+ */
+ grandpa->rb_nodes[RB_POSITION(old_father)] = new_father;
+ new_child->rb_nodes[which] = old_child->rb_nodes[other];
+ new_father->rb_nodes[other] = new_child;
+
+ /*
+ * Update ancestor linkages
+ */
+ RB_SET_FATHER(new_father, grandpa);
+ RB_SET_FATHER(new_child, new_father);
+
+ /*
+ * Exchange properties between new_father and new_child. The only
+ * change is that new_child's position is now on the other side.
+ */
+#if 0
+ {
+ struct rb_node tmp;
+ tmp.rb_info = 0;
+ RB_COPY_PROPERTIES(&tmp, old_child);
+ RB_COPY_PROPERTIES(new_father, old_father);
+ RB_COPY_PROPERTIES(new_child, &tmp);
+ }
+#else
+ RB_SWAP_PROPERTIES(new_father, new_child);
+#endif
+ RB_SET_POSITION(new_child, other);
+
+ /*
+ * Make sure to reparent the new child to ourself.
+ */
+ if (!RB_SENTINEL_P(new_child->rb_nodes[which])) {
+ RB_SET_FATHER(new_child->rb_nodes[which], new_child);
+ RB_SET_POSITION(new_child->rb_nodes[which], which);
+ }
+
+ KASSERT(rb_tree_check_node(rbt, new_father, NULL, false));
+ KASSERT(rb_tree_check_node(rbt, new_child, NULL, false));
+ KASSERT(RB_ROOT_P(rbt, new_father) ||
+ rb_tree_check_node(rbt, grandpa, NULL, false));
+}
+
+static void
+rb_tree_insert_rebalance(struct rb_tree *rbt, struct rb_node *self)
+{
+ struct rb_node * father = RB_FATHER(self);
+ struct rb_node * grandpa = RB_FATHER(father);
+ struct rb_node * uncle;
+ unsigned int which;
+ unsigned int other;
+
+ KASSERT(!RB_ROOT_P(rbt, self));
+ KASSERT(RB_RED_P(self));
+ KASSERT(RB_RED_P(father));
+ RBSTAT_INC(rbt->rbt_insertion_rebalance_calls);
+
+ for (;;) {
+ KASSERT(!RB_SENTINEL_P(self));
+
+ KASSERT(RB_RED_P(self));
+ KASSERT(RB_RED_P(father));
+ /*
+ * We are red and our parent is red, therefore we must have a
+ * grandfather and he must be black.
+ */
+ grandpa = RB_FATHER(father);
+ KASSERT(RB_BLACK_P(grandpa));
+ KASSERT(RB_DIR_RIGHT == 1 && RB_DIR_LEFT == 0);
+ which = (father == grandpa->rb_right);
+ other = which ^ RB_DIR_OTHER;
+ uncle = grandpa->rb_nodes[other];
+
+ if (RB_BLACK_P(uncle))
+ break;
+
+ RBSTAT_INC(rbt->rbt_insertion_rebalance_passes);
+ /*
+ * Case 1: our uncle is red
+ * Simply invert the colors of our parent and
+ * uncle and make our grandparent red. And
+ * then solve the problem up at his level.
+ */
+ RB_MARK_BLACK(uncle);
+ RB_MARK_BLACK(father);
+ if (__predict_false(RB_ROOT_P(rbt, grandpa))) {
+ /*
+ * If our grandpa is root, don't bother
+ * setting him to red, just return.
+ */
+ KASSERT(RB_BLACK_P(grandpa));
+ return;
+ }
+ RB_MARK_RED(grandpa);
+ self = grandpa;
+ father = RB_FATHER(self);
+ KASSERT(RB_RED_P(self));
+ if (RB_BLACK_P(father)) {
+ /*
+ * If our greatgrandpa is black, we're done.
+ */
+ KASSERT(RB_BLACK_P(rbt->rbt_root));
+ return;
+ }
+ }
+
+ KASSERT(!RB_ROOT_P(rbt, self));
+ KASSERT(RB_RED_P(self));
+ KASSERT(RB_RED_P(father));
+ KASSERT(RB_BLACK_P(uncle));
+ KASSERT(RB_BLACK_P(grandpa));
+ /*
+ * Case 2&3: our uncle is black.
+ */
+ if (self == father->rb_nodes[other]) {
+ /*
+ * Case 2: we are on the same side as our uncle
+ * Swap ourselves with our parent so this case
+ * becomes case 3. Basically our parent becomes our
+ * child.
+ */
+ rb_tree_reparent_nodes(rbt, father, other);
+ KASSERT(RB_FATHER(father) == self);
+ KASSERT(self->rb_nodes[which] == father);
+ KASSERT(RB_FATHER(self) == grandpa);
+ self = father;
+ father = RB_FATHER(self);
+ }
+ KASSERT(RB_RED_P(self) && RB_RED_P(father));
+ KASSERT(grandpa->rb_nodes[which] == father);
+ /*
+ * Case 3: we are opposite a child of a black uncle.
+ * Swap our parent and grandparent. Since our grandfather
+ * is black, our father will become black and our new sibling
+ * (former grandparent) will become red.
+ */
+ rb_tree_reparent_nodes(rbt, grandpa, which);
+ KASSERT(RB_FATHER(self) == father);
+ KASSERT(RB_FATHER(self)->rb_nodes[RB_POSITION(self) ^ RB_DIR_OTHER] == grandpa);
+ KASSERT(RB_RED_P(self));
+ KASSERT(RB_BLACK_P(father));
+ KASSERT(RB_RED_P(grandpa));
+
+ /*
+ * Final step: Set the root to black.
+ */
+ RB_MARK_BLACK(rbt->rbt_root);
+}
+
+static void
+rb_tree_prune_node(struct rb_tree *rbt, struct rb_node *self, bool rebalance)
+{
+ const unsigned int which = RB_POSITION(self);
+ struct rb_node *father = RB_FATHER(self);
+#ifndef RBSMALL
+ const bool was_root = RB_ROOT_P(rbt, self);
+#endif
+
+ KASSERT(rebalance || (RB_ROOT_P(rbt, self) || RB_RED_P(self)));
+ KASSERT(!rebalance || RB_BLACK_P(self));
+ KASSERT(RB_CHILDLESS_P(self));
+ KASSERT(rb_tree_check_node(rbt, self, NULL, false));
+
+ /*
+ * Since we are childless, we know that self->rb_left is pointing
+ * to the sentinel node.
+ */
+ father->rb_nodes[which] = self->rb_left;
+
+ /*
+ * Remove ourselves from the node list, decrement the count,
+ * and update min/max.
+ */
+ RB_TAILQ_REMOVE(&rbt->rbt_nodes, self, rb_link);
+ RBSTAT_DEC(rbt->rbt_count);
+#ifndef RBSMALL
+ if (__predict_false(rbt->rbt_minmax[RB_POSITION(self)] == self)) {
+ rbt->rbt_minmax[RB_POSITION(self)] = father;
+ /*
+ * When removing the root, rbt->rbt_minmax[RB_DIR_LEFT] is
+ * updated automatically, but we also need to update
+ * rbt->rbt_minmax[RB_DIR_RIGHT];
+ */
+ if (__predict_false(was_root)) {
+ rbt->rbt_minmax[RB_DIR_RIGHT] = father;
+ }
+ }
+ RB_SET_FATHER(self, NULL);
+#endif
+
+ /*
+ * Rebalance if requested.
+ */
+ if (rebalance)
+ rb_tree_removal_rebalance(rbt, father, which);
+ KASSERT(was_root || rb_tree_check_node(rbt, father, NULL, true));
+}
+
+/*
+ * When deleting an interior node
+ */
+static void
+rb_tree_swap_prune_and_rebalance(struct rb_tree *rbt, struct rb_node *self,
+ struct rb_node *standin)
+{
+ const unsigned int standin_which = RB_POSITION(standin);
+ unsigned int standin_other = standin_which ^ RB_DIR_OTHER;
+ struct rb_node *standin_son;
+ struct rb_node *standin_father = RB_FATHER(standin);
+ bool rebalance = RB_BLACK_P(standin);
+
+ if (standin_father == self) {
+ /*
+ * As a child of self, any childen would be opposite of
+ * our parent.
+ */
+ KASSERT(RB_SENTINEL_P(standin->rb_nodes[standin_other]));
+ standin_son = standin->rb_nodes[standin_which];
+ } else {
+ /*
+ * Since we aren't a child of self, any childen would be
+ * on the same side as our parent.
+ */
+ KASSERT(RB_SENTINEL_P(standin->rb_nodes[standin_which]));
+ standin_son = standin->rb_nodes[standin_other];
+ }
+
+ /*
+ * the node we are removing must have two children.
+ */
+ KASSERT(RB_TWOCHILDREN_P(self));
+ /*
+ * If standin has a child, it must be red.
+ */
+ KASSERT(RB_SENTINEL_P(standin_son) || RB_RED_P(standin_son));
+
+ /*
+ * Verify things are sane.
+ */
+ KASSERT(rb_tree_check_node(rbt, self, NULL, false));
+ KASSERT(rb_tree_check_node(rbt, standin, NULL, false));
+
+ if (__predict_false(RB_RED_P(standin_son))) {
+ /*
+ * We know we have a red child so if we flip it to black
+ * we don't have to rebalance.
+ */
+ KASSERT(rb_tree_check_node(rbt, standin_son, NULL, true));
+ RB_MARK_BLACK(standin_son);
+ rebalance = false;
+
+ if (standin_father == self) {
+ KASSERT(RB_POSITION(standin_son) == standin_which);
+ } else {
+ KASSERT(RB_POSITION(standin_son) == standin_other);
+ /*
+ * Change the son's parentage to point to his grandpa.
+ */
+ RB_SET_FATHER(standin_son, standin_father);
+ RB_SET_POSITION(standin_son, standin_which);
+ }
+ }
+
+ if (standin_father == self) {
+ /*
+ * If we are about to delete the standin's father, then when
+ * we call rebalance, we need to use ourselves as our father.
+ * Otherwise remember our original father. Also, sincef we are
+ * our standin's father we only need to reparent the standin's
+ * brother.
+ *
+ * | R --> S |
+ * | Q S --> Q T |
+ * | t --> |
+ */
+ KASSERT(RB_SENTINEL_P(standin->rb_nodes[standin_other]));
+ KASSERT(!RB_SENTINEL_P(self->rb_nodes[standin_other]));
+ KASSERT(self->rb_nodes[standin_which] == standin);
+ /*
+ * Have our son/standin adopt his brother as his new son.
+ */
+ standin_father = standin;
+ } else {
+ /*
+ * | R --> S . |
+ * | / \ | T --> / \ | / |
+ * | ..... | S --> ..... | T |
+ *
+ * Sever standin's connection to his father.
+ */
+ standin_father->rb_nodes[standin_which] = standin_son;
+ /*
+ * Adopt the far son.
+ */
+ standin->rb_nodes[standin_other] = self->rb_nodes[standin_other];
+ RB_SET_FATHER(standin->rb_nodes[standin_other], standin);
+ KASSERT(RB_POSITION(self->rb_nodes[standin_other]) == standin_other);
+ /*
+ * Use standin_other because we need to preserve standin_which
+ * for the removal_rebalance.
+ */
+ standin_other = standin_which;
+ }
+
+ /*
+ * Move the only remaining son to our standin. If our standin is our
+ * son, this will be the only son needed to be moved.
+ */
+ KASSERT(standin->rb_nodes[standin_other] != self->rb_nodes[standin_other]);
+ standin->rb_nodes[standin_other] = self->rb_nodes[standin_other];
+ RB_SET_FATHER(standin->rb_nodes[standin_other], standin);
+
+ /*
+ * Now copy the result of self to standin and then replace
+ * self with standin in the tree.
+ */
+ RB_COPY_PROPERTIES(standin, self);
+ RB_SET_FATHER(standin, RB_FATHER(self));
+ RB_FATHER(standin)->rb_nodes[RB_POSITION(standin)] = standin;
+
+ /*
+ * Remove ourselves from the node list, decrement the count,
+ * and update min/max.
+ */
+ RB_TAILQ_REMOVE(&rbt->rbt_nodes, self, rb_link);
+ RBSTAT_DEC(rbt->rbt_count);
+#ifndef RBSMALL
+ if (__predict_false(rbt->rbt_minmax[RB_POSITION(self)] == self))
+ rbt->rbt_minmax[RB_POSITION(self)] = RB_FATHER(self);
+ RB_SET_FATHER(self, NULL);
+#endif
+
+ KASSERT(rb_tree_check_node(rbt, standin, NULL, false));
+ KASSERT(RB_FATHER_SENTINEL_P(standin)
+ || rb_tree_check_node(rbt, standin_father, NULL, false));
+ KASSERT(RB_LEFT_SENTINEL_P(standin)
+ || rb_tree_check_node(rbt, standin->rb_left, NULL, false));
+ KASSERT(RB_RIGHT_SENTINEL_P(standin)
+ || rb_tree_check_node(rbt, standin->rb_right, NULL, false));
+
+ if (!rebalance)
+ return;
+
+ rb_tree_removal_rebalance(rbt, standin_father, standin_which);
+ KASSERT(rb_tree_check_node(rbt, standin, NULL, true));
+}
+
+/*
+ * We could do this by doing
+ * rb_tree_node_swap(rbt, self, which);
+ * rb_tree_prune_node(rbt, self, false);
+ *
+ * But it's more efficient to just evalate and recolor the child.
+ */
+static void
+rb_tree_prune_blackred_branch(struct rb_tree *rbt, struct rb_node *self,
+ unsigned int which)
+{
+ struct rb_node *father = RB_FATHER(self);
+ struct rb_node *son = self->rb_nodes[which];
+#ifndef RBSMALL
+ const bool was_root = RB_ROOT_P(rbt, self);
+#endif
+
+ KASSERT(which == RB_DIR_LEFT || which == RB_DIR_RIGHT);
+ KASSERT(RB_BLACK_P(self) && RB_RED_P(son));
+ KASSERT(!RB_TWOCHILDREN_P(son));
+ KASSERT(RB_CHILDLESS_P(son));
+ KASSERT(rb_tree_check_node(rbt, self, NULL, false));
+ KASSERT(rb_tree_check_node(rbt, son, NULL, false));
+
+ /*
+ * Remove ourselves from the tree and give our former child our
+ * properties (position, color, root).
+ */
+ RB_COPY_PROPERTIES(son, self);
+ father->rb_nodes[RB_POSITION(son)] = son;
+ RB_SET_FATHER(son, father);
+
+ /*
+ * Remove ourselves from the node list, decrement the count,
+ * and update minmax.
+ */
+ RB_TAILQ_REMOVE(&rbt->rbt_nodes, self, rb_link);
+ RBSTAT_DEC(rbt->rbt_count);
+#ifndef RBSMALL
+ if (__predict_false(was_root)) {
+ KASSERT(rbt->rbt_minmax[which] == son);
+ rbt->rbt_minmax[which ^ RB_DIR_OTHER] = son;
+ } else if (rbt->rbt_minmax[RB_POSITION(self)] == self) {
+ rbt->rbt_minmax[RB_POSITION(self)] = son;
+ }
+ RB_SET_FATHER(self, NULL);
+#endif
+
+ KASSERT(was_root || rb_tree_check_node(rbt, father, NULL, true));
+ KASSERT(rb_tree_check_node(rbt, son, NULL, true));
+}
+
+void
+rb_tree_remove_node(struct rb_tree *rbt, void *object)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ struct rb_node *standin, *self = RB_ITEMTONODE(rbto, object);
+ unsigned int which;
+
+ KASSERT(!RB_SENTINEL_P(self));
+ RBSTAT_INC(rbt->rbt_removals);
+
+ /*
+ * In the following diagrams, we (the node to be removed) are S. Red
+ * nodes are lowercase. T could be either red or black.
+ *
+ * Remember the major axiom of the red-black tree: the number of
+ * black nodes from the root to each leaf is constant across all
+ * leaves, only the number of red nodes varies.
+ *
+ * Thus removing a red leaf doesn't require any other changes to a
+ * red-black tree. So if we must remove a node, attempt to rearrange
+ * the tree so we can remove a red node.
+ *
+ * The simpliest case is a childless red node or a childless root node:
+ *
+ * | T --> T | or | R --> * |
+ * | s --> * |
+ */
+ if (RB_CHILDLESS_P(self)) {
+ const bool rebalance = RB_BLACK_P(self) && !RB_ROOT_P(rbt, self);
+ rb_tree_prune_node(rbt, self, rebalance);
+ return;
+ }
+ KASSERT(!RB_CHILDLESS_P(self));
+ if (!RB_TWOCHILDREN_P(self)) {
+ /*
+ * The next simpliest case is the node we are deleting is
+ * black and has one red child.
+ *
+ * | T --> T --> T |
+ * | S --> R --> R |
+ * | r --> s --> * |
+ */
+ which = RB_LEFT_SENTINEL_P(self) ? RB_DIR_RIGHT : RB_DIR_LEFT;
+ KASSERT(RB_BLACK_P(self));
+ KASSERT(RB_RED_P(self->rb_nodes[which]));
+ KASSERT(RB_CHILDLESS_P(self->rb_nodes[which]));
+ rb_tree_prune_blackred_branch(rbt, self, which);
+ return;
+ }
+ KASSERT(RB_TWOCHILDREN_P(self));
+
+ /*
+ * We invert these because we prefer to remove from the inside of
+ * the tree.
+ */
+ which = RB_POSITION(self) ^ RB_DIR_OTHER;
+
+ /*
+ * Let's find the node closes to us opposite of our parent
+ * Now swap it with ourself, "prune" it, and rebalance, if needed.
+ */
+ standin = RB_ITEMTONODE(rbto, rb_tree_iterate(rbt, object, which));
+ rb_tree_swap_prune_and_rebalance(rbt, self, standin);
+}
+
+static void
+rb_tree_removal_rebalance(struct rb_tree *rbt, struct rb_node *parent,
+ unsigned int which)
+{
+ KASSERT(!RB_SENTINEL_P(parent));
+ KASSERT(RB_SENTINEL_P(parent->rb_nodes[which]));
+ KASSERT(which == RB_DIR_LEFT || which == RB_DIR_RIGHT);
+ RBSTAT_INC(rbt->rbt_removal_rebalance_calls);
+
+ while (RB_BLACK_P(parent->rb_nodes[which])) {
+ unsigned int other = which ^ RB_DIR_OTHER;
+ struct rb_node *brother = parent->rb_nodes[other];
+
+ RBSTAT_INC(rbt->rbt_removal_rebalance_passes);
+
+ KASSERT(!RB_SENTINEL_P(brother));
+ /*
+ * For cases 1, 2a, and 2b, our brother's children must
+ * be black and our father must be black
+ */
+ if (RB_BLACK_P(parent)
+ && RB_BLACK_P(brother->rb_left)
+ && RB_BLACK_P(brother->rb_right)) {
+ if (RB_RED_P(brother)) {
+ /*
+ * Case 1: Our brother is red, swap its
+ * position (and colors) with our parent.
+ * This should now be case 2b (unless C or E
+ * has a red child which is case 3; thus no
+ * explicit branch to case 2b).
+ *
+ * B -> D
+ * A d -> b E
+ * C E -> A C
+ */
+ KASSERT(RB_BLACK_P(parent));
+ rb_tree_reparent_nodes(rbt, parent, other);
+ brother = parent->rb_nodes[other];
+ KASSERT(!RB_SENTINEL_P(brother));
+ KASSERT(RB_RED_P(parent));
+ KASSERT(RB_BLACK_P(brother));
+ KASSERT(rb_tree_check_node(rbt, brother, NULL, false));
+ KASSERT(rb_tree_check_node(rbt, parent, NULL, false));
+ } else {
+ /*
+ * Both our parent and brother are black.
+ * Change our brother to red, advance up rank
+ * and go through the loop again.
+ *
+ * B -> *B
+ * *A D -> A d
+ * C E -> C E
+ */
+ RB_MARK_RED(brother);
+ KASSERT(RB_BLACK_P(brother->rb_left));
+ KASSERT(RB_BLACK_P(brother->rb_right));
+ if (RB_ROOT_P(rbt, parent))
+ return; /* root == parent == black */
+ KASSERT(rb_tree_check_node(rbt, brother, NULL, false));
+ KASSERT(rb_tree_check_node(rbt, parent, NULL, false));
+ which = RB_POSITION(parent);
+ parent = RB_FATHER(parent);
+ continue;
+ }
+ }
+ /*
+ * Avoid an else here so that case 2a above can hit either
+ * case 2b, 3, or 4.
+ */
+ if (RB_RED_P(parent)
+ && RB_BLACK_P(brother)
+ && RB_BLACK_P(brother->rb_left)
+ && RB_BLACK_P(brother->rb_right)) {
+ KASSERT(RB_RED_P(parent));
+ KASSERT(RB_BLACK_P(brother));
+ KASSERT(RB_BLACK_P(brother->rb_left));
+ KASSERT(RB_BLACK_P(brother->rb_right));
+ /*
+ * We are black, our father is red, our brother and
+ * both nephews are black. Simply invert/exchange the
+ * colors of our father and brother (to black and red
+ * respectively).
+ *
+ * | f --> F |
+ * | * B --> * b |
+ * | N N --> N N |
+ */
+ RB_MARK_BLACK(parent);
+ RB_MARK_RED(brother);
+ KASSERT(rb_tree_check_node(rbt, brother, NULL, true));
+ break; /* We're done! */
+ } else {
+ /*
+ * Our brother must be black and have at least one
+ * red child (it may have two).
+ */
+ KASSERT(RB_BLACK_P(brother));
+ KASSERT(RB_RED_P(brother->rb_nodes[which]) ||
+ RB_RED_P(brother->rb_nodes[other]));
+ if (RB_BLACK_P(brother->rb_nodes[other])) {
+ /*
+ * Case 3: our brother is black, our near
+ * nephew is red, and our far nephew is black.
+ * Swap our brother with our near nephew.
+ * This result in a tree that matches case 4.
+ * (Our father could be red or black).
+ *
+ * | F --> F |
+ * | x B --> x B |
+ * | n --> n |
+ */
+ KASSERT(RB_RED_P(brother->rb_nodes[which]));
+ rb_tree_reparent_nodes(rbt, brother, which);
+ KASSERT(RB_FATHER(brother) == parent->rb_nodes[other]);
+ brother = parent->rb_nodes[other];
+ KASSERT(RB_RED_P(brother->rb_nodes[other]));
+ }
+ /*
+ * Case 4: our brother is black and our far nephew
+ * is red. Swap our father and brother locations and
+ * change our far nephew to black. (these can be
+ * done in either order so we change the color first).
+ * The result is a valid red-black tree and is a
+ * terminal case. (again we don't care about the
+ * father's color)
+ *
+ * If the father is red, we will get a red-black-black
+ * tree:
+ * | f -> f --> b |
+ * | B -> B --> F N |
+ * | n -> N --> |
+ *
+ * If the father is black, we will get an all black
+ * tree:
+ * | F -> F --> B |
+ * | B -> B --> F N |
+ * | n -> N --> |
+ *
+ * If we had two red nephews, then after the swap,
+ * our former father would have a red grandson.
+ */
+ KASSERT(RB_BLACK_P(brother));
+ KASSERT(RB_RED_P(brother->rb_nodes[other]));
+ RB_MARK_BLACK(brother->rb_nodes[other]);
+ rb_tree_reparent_nodes(rbt, parent, other);
+ break; /* We're done! */
+ }
+ }
+ KASSERT(rb_tree_check_node(rbt, parent, NULL, true));
+}
+
+void *
+rb_tree_iterate(struct rb_tree *rbt, void *object, const unsigned int direction)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ const unsigned int other = direction ^ RB_DIR_OTHER;
+ struct rb_node *self;
+
+ KASSERT(direction == RB_DIR_LEFT || direction == RB_DIR_RIGHT);
+
+ if (object == NULL) {
+#ifndef RBSMALL
+ if (RB_SENTINEL_P(rbt->rbt_root))
+ return NULL;
+ return RB_NODETOITEM(rbto, rbt->rbt_minmax[direction]);
+#else
+ self = rbt->rbt_root;
+ if (RB_SENTINEL_P(self))
+ return NULL;
+ while (!RB_SENTINEL_P(self->rb_nodes[direction]))
+ self = self->rb_nodes[direction];
+ return RB_NODETOITEM(rbto, self);
+#endif /* !RBSMALL */
+ }
+ self = RB_ITEMTONODE(rbto, object);
+ KASSERT(!RB_SENTINEL_P(self));
+ /*
+ * We can't go any further in this direction. We proceed up in the
+ * opposite direction until our parent is in direction we want to go.
+ */
+ if (RB_SENTINEL_P(self->rb_nodes[direction])) {
+ while (!RB_ROOT_P(rbt, self)) {
+ if (other == RB_POSITION(self))
+ return RB_NODETOITEM(rbto, RB_FATHER(self));
+ self = RB_FATHER(self);
+ }
+ return NULL;
+ }
+
+ /*
+ * Advance down one in current direction and go down as far as possible
+ * in the opposite direction.
+ */
+ self = self->rb_nodes[direction];
+ KASSERT(!RB_SENTINEL_P(self));
+ while (!RB_SENTINEL_P(self->rb_nodes[other]))
+ self = self->rb_nodes[other];
+ return RB_NODETOITEM(rbto, self);
+}
+
+#ifdef RBDEBUG
+static const struct rb_node *
+rb_tree_iterate_const(const struct rb_tree *rbt, const struct rb_node *self,
+ const unsigned int direction)
+{
+ const unsigned int other = direction ^ RB_DIR_OTHER;
+ KASSERT(direction == RB_DIR_LEFT || direction == RB_DIR_RIGHT);
+
+ if (self == NULL) {
+#ifndef RBSMALL
+ if (RB_SENTINEL_P(rbt->rbt_root))
+ return NULL;
+ return rbt->rbt_minmax[direction];
+#else
+ self = rbt->rbt_root;
+ if (RB_SENTINEL_P(self))
+ return NULL;
+ while (!RB_SENTINEL_P(self->rb_nodes[direction]))
+ self = self->rb_nodes[direction];
+ return self;
+#endif /* !RBSMALL */
+ }
+ KASSERT(!RB_SENTINEL_P(self));
+ /*
+ * We can't go any further in this direction. We proceed up in the
+ * opposite direction until our parent is in direction we want to go.
+ */
+ if (RB_SENTINEL_P(self->rb_nodes[direction])) {
+ while (!RB_ROOT_P(rbt, self)) {
+ if (other == RB_POSITION(self))
+ return RB_FATHER(self);
+ self = RB_FATHER(self);
+ }
+ return NULL;
+ }
+
+ /*
+ * Advance down one in current direction and go down as far as possible
+ * in the opposite direction.
+ */
+ self = self->rb_nodes[direction];
+ KASSERT(!RB_SENTINEL_P(self));
+ while (!RB_SENTINEL_P(self->rb_nodes[other]))
+ self = self->rb_nodes[other];
+ return self;
+}
+
+static unsigned int
+rb_tree_count_black(const struct rb_node *self)
+{
+ unsigned int left, right;
+
+ if (RB_SENTINEL_P(self))
+ return 0;
+
+ left = rb_tree_count_black(self->rb_left);
+ right = rb_tree_count_black(self->rb_right);
+
+ KASSERT(left == right);
+
+ return left + RB_BLACK_P(self);
+}
+
+static bool
+rb_tree_check_node(const struct rb_tree *rbt, const struct rb_node *self,
+ const struct rb_node *prev, bool red_check)
+{
+ const rb_tree_ops_t *rbto = rbt->rbt_ops;
+ rbto_compare_nodes_fn compare_nodes = rbto->rbto_compare_nodes;
+
+ KASSERT(!RB_SENTINEL_P(self));
+ KASSERT(prev == NULL || (*compare_nodes)(rbto->rbto_context,
+ RB_NODETOITEM(rbto, prev), RB_NODETOITEM(rbto, self)) < 0);
+
+ /*
+ * Verify our relationship to our parent.
+ */
+ if (RB_ROOT_P(rbt, self)) {
+ KASSERT(self == rbt->rbt_root);
+ KASSERT(RB_POSITION(self) == RB_DIR_LEFT);
+ KASSERT(RB_FATHER(self)->rb_nodes[RB_DIR_LEFT] == self);
+ KASSERT(RB_FATHER(self) == (const struct rb_node *) &rbt->rbt_root);
+ } else {
+ int diff = (*compare_nodes)(rbto->rbto_context,
+ RB_NODETOITEM(rbto, self),
+ RB_NODETOITEM(rbto, RB_FATHER(self)));
+
+ KASSERT(self != rbt->rbt_root);
+ KASSERT(!RB_FATHER_SENTINEL_P(self));
+ if (RB_POSITION(self) == RB_DIR_LEFT) {
+ KASSERT(diff < 0);
+ KASSERT(RB_FATHER(self)->rb_nodes[RB_DIR_LEFT] == self);
+ } else {
+ KASSERT(diff > 0);
+ KASSERT(RB_FATHER(self)->rb_nodes[RB_DIR_RIGHT] == self);
+ }
+ }
+
+ /*
+ * Verify our position in the linked list against the tree itself.
+ */
+ {
+ const struct rb_node *prev0 = rb_tree_iterate_const(rbt, self, RB_DIR_LEFT);
+ const struct rb_node *next0 = rb_tree_iterate_const(rbt, self, RB_DIR_RIGHT);
+ KASSERT(prev0 == TAILQ_PREV(self, rb_node_qh, rb_link));
+ KASSERT(next0 == TAILQ_NEXT(self, rb_link));
+#ifndef RBSMALL
+ KASSERT(prev0 != NULL || self == rbt->rbt_minmax[RB_DIR_LEFT]);
+ KASSERT(next0 != NULL || self == rbt->rbt_minmax[RB_DIR_RIGHT]);
+#endif
+ }
+
+ /*
+ * The root must be black.
+ * There can never be two adjacent red nodes.
+ */
+ if (red_check) {
+ KASSERT(!RB_ROOT_P(rbt, self) || RB_BLACK_P(self));
+ (void) rb_tree_count_black(self);
+ if (RB_RED_P(self)) {
+ const struct rb_node *brother;
+ KASSERT(!RB_ROOT_P(rbt, self));
+ brother = RB_FATHER(self)->rb_nodes[RB_POSITION(self) ^ RB_DIR_OTHER];
+ KASSERT(RB_BLACK_P(RB_FATHER(self)));
+ /*
+ * I'm red and have no children, then I must either
+ * have no brother or my brother also be red and
+ * also have no children. (black count == 0)
+ */
+ KASSERT(!RB_CHILDLESS_P(self)
+ || RB_SENTINEL_P(brother)
+ || RB_RED_P(brother)
+ || RB_CHILDLESS_P(brother));
+ /*
+ * If I'm not childless, I must have two children
+ * and they must be both be black.
+ */
+ KASSERT(RB_CHILDLESS_P(self)
+ || (RB_TWOCHILDREN_P(self)
+ && RB_BLACK_P(self->rb_left)
+ && RB_BLACK_P(self->rb_right)));
+ /*
+ * If I'm not childless, thus I have black children,
+ * then my brother must either be black or have two
+ * black children.
+ */
+ KASSERT(RB_CHILDLESS_P(self)
+ || RB_BLACK_P(brother)
+ || (RB_TWOCHILDREN_P(brother)
+ && RB_BLACK_P(brother->rb_left)
+ && RB_BLACK_P(brother->rb_right)));
+ } else {
+ /*
+ * If I'm black and have one child, that child must
+ * be red and childless.
+ */
+ KASSERT(RB_CHILDLESS_P(self)
+ || RB_TWOCHILDREN_P(self)
+ || (!RB_LEFT_SENTINEL_P(self)
+ && RB_RIGHT_SENTINEL_P(self)
+ && RB_RED_P(self->rb_left)
+ && RB_CHILDLESS_P(self->rb_left))
+ || (!RB_RIGHT_SENTINEL_P(self)
+ && RB_LEFT_SENTINEL_P(self)
+ && RB_RED_P(self->rb_right)
+ && RB_CHILDLESS_P(self->rb_right)));
+
+ /*
+ * If I'm a childless black node and my parent is
+ * black, my 2nd closet relative away from my parent
+ * is either red or has a red parent or red children.
+ */
+ if (!RB_ROOT_P(rbt, self)
+ && RB_CHILDLESS_P(self)
+ && RB_BLACK_P(RB_FATHER(self))) {
+ const unsigned int which = RB_POSITION(self);
+ const unsigned int other = which ^ RB_DIR_OTHER;
+ const struct rb_node *relative0, *relative;
+
+ relative0 = rb_tree_iterate_const(rbt,
+ self, other);
+ KASSERT(relative0 != NULL);
+ relative = rb_tree_iterate_const(rbt,
+ relative0, other);
+ KASSERT(relative != NULL);
+ KASSERT(RB_SENTINEL_P(relative->rb_nodes[which]));
+#if 0
+ KASSERT(RB_RED_P(relative)
+ || RB_RED_P(relative->rb_left)
+ || RB_RED_P(relative->rb_right)
+ || RB_RED_P(RB_FATHER(relative)));
+#endif
+ }
+ }
+ /*
+ * A grandparent's children must be real nodes and not
+ * sentinels. First check out grandparent.
+ */
+ KASSERT(RB_ROOT_P(rbt, self)
+ || RB_ROOT_P(rbt, RB_FATHER(self))
+ || RB_TWOCHILDREN_P(RB_FATHER(RB_FATHER(self))));
+ /*
+ * If we are have grandchildren on our left, then
+ * we must have a child on our right.
+ */
+ KASSERT(RB_LEFT_SENTINEL_P(self)
+ || RB_CHILDLESS_P(self->rb_left)
+ || !RB_RIGHT_SENTINEL_P(self));
+ /*
+ * If we are have grandchildren on our right, then
+ * we must have a child on our left.
+ */
+ KASSERT(RB_RIGHT_SENTINEL_P(self)
+ || RB_CHILDLESS_P(self->rb_right)
+ || !RB_LEFT_SENTINEL_P(self));
+
+ /*
+ * If we have a child on the left and it doesn't have two
+ * children make sure we don't have great-great-grandchildren on
+ * the right.
+ */
+ KASSERT(RB_TWOCHILDREN_P(self->rb_left)
+ || RB_CHILDLESS_P(self->rb_right)
+ || RB_CHILDLESS_P(self->rb_right->rb_left)
+ || RB_CHILDLESS_P(self->rb_right->rb_left->rb_left)
+ || RB_CHILDLESS_P(self->rb_right->rb_left->rb_right)
+ || RB_CHILDLESS_P(self->rb_right->rb_right)
+ || RB_CHILDLESS_P(self->rb_right->rb_right->rb_left)
+ || RB_CHILDLESS_P(self->rb_right->rb_right->rb_right));
+
+ /*
+ * If we have a child on the right and it doesn't have two
+ * children make sure we don't have great-great-grandchildren on
+ * the left.
+ */
+ KASSERT(RB_TWOCHILDREN_P(self->rb_right)
+ || RB_CHILDLESS_P(self->rb_left)
+ || RB_CHILDLESS_P(self->rb_left->rb_left)
+ || RB_CHILDLESS_P(self->rb_left->rb_left->rb_left)
+ || RB_CHILDLESS_P(self->rb_left->rb_left->rb_right)
+ || RB_CHILDLESS_P(self->rb_left->rb_right)
+ || RB_CHILDLESS_P(self->rb_left->rb_right->rb_left)
+ || RB_CHILDLESS_P(self->rb_left->rb_right->rb_right));
+
+ /*
+ * If we are fully interior node, then our predecessors and
+ * successors must have no children in our direction.
+ */
+ if (RB_TWOCHILDREN_P(self)) {
+ const struct rb_node *prev0;
+ const struct rb_node *next0;
+
+ prev0 = rb_tree_iterate_const(rbt, self, RB_DIR_LEFT);
+ KASSERT(prev0 != NULL);
+ KASSERT(RB_RIGHT_SENTINEL_P(prev0));
+
+ next0 = rb_tree_iterate_const(rbt, self, RB_DIR_RIGHT);
+ KASSERT(next0 != NULL);
+ KASSERT(RB_LEFT_SENTINEL_P(next0));
+ }
+ }
+
+ return true;
+}
+
+void
+rb_tree_check(const struct rb_tree *rbt, bool red_check)
+{
+ const struct rb_node *self;
+ const struct rb_node *prev;
+#ifdef RBSTATS
+ unsigned int count = 0;
+#endif
+
+ KASSERT(rbt->rbt_root != NULL);
+ KASSERT(RB_LEFT_P(rbt->rbt_root));
+
+#if defined(RBSTATS) && !defined(RBSMALL)
+ KASSERT(rbt->rbt_count > 1
+ || rbt->rbt_minmax[RB_DIR_LEFT] == rbt->rbt_minmax[RB_DIR_RIGHT]);
+#endif
+
+ prev = NULL;
+ TAILQ_FOREACH(self, &rbt->rbt_nodes, rb_link) {
+ rb_tree_check_node(rbt, self, prev, false);
+#ifdef RBSTATS
+ count++;
+#endif
+ }
+#ifdef RBSTATS
+ KASSERT(rbt->rbt_count == count);
+#endif
+ if (red_check) {
+ KASSERT(RB_BLACK_P(rbt->rbt_root));
+ KASSERT(RB_SENTINEL_P(rbt->rbt_root)
+ || rb_tree_count_black(rbt->rbt_root));
+
+ /*
+ * The root must be black.
+ * There can never be two adjacent red nodes.
+ */
+ TAILQ_FOREACH(self, &rbt->rbt_nodes, rb_link) {
+ rb_tree_check_node(rbt, self, NULL, true);
+ }
+ }
+}
+#endif /* RBDEBUG */
+
+#ifdef RBSTATS
+static void
+rb_tree_mark_depth(const struct rb_tree *rbt, const struct rb_node *self,
+ size_t *depths, size_t depth)
+{
+ if (RB_SENTINEL_P(self))
+ return;
+
+ if (RB_TWOCHILDREN_P(self)) {
+ rb_tree_mark_depth(rbt, self->rb_left, depths, depth + 1);
+ rb_tree_mark_depth(rbt, self->rb_right, depths, depth + 1);
+ return;
+ }
+ depths[depth]++;
+ if (!RB_LEFT_SENTINEL_P(self)) {
+ rb_tree_mark_depth(rbt, self->rb_left, depths, depth + 1);
+ }
+ if (!RB_RIGHT_SENTINEL_P(self)) {
+ rb_tree_mark_depth(rbt, self->rb_right, depths, depth + 1);
+ }
+}
+
+void
+rb_tree_depths(const struct rb_tree *rbt, size_t *depths)
+{
+ rb_tree_mark_depth(rbt, rbt->rbt_root, depths, 1);
+}
+#endif /* RBSTATS */
diff --git a/compat/rbtree.h b/compat/rbtree.h
new file mode 100644
index 000000000000..656da22a108e
--- /dev/null
+++ b/compat/rbtree.h
@@ -0,0 +1,211 @@
+/* $NetBSD: rbtree.h,v 1.5 2019/03/07 14:39:21 roy Exp $ */
+
+/*-
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Matt Thomas <matt@3am-software.com>.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 _SYS_RBTREE_H_
+#define _SYS_RBTREE_H_
+
+#include "config.h"
+#include "common.h"
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+#include <sys/types.h>
+#else
+#include <stdbool.h>
+#include <inttypes.h>
+#endif
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h>
+#else
+#include "queue.h"
+#endif
+#if !defined(__linux__) && !defined(__QNX__) && !defined(__sun)
+#include <sys/endian.h>
+#else
+#include "endian.h"
+#endif
+
+__BEGIN_DECLS
+
+typedef struct rb_node {
+ struct rb_node *rb_nodes[2];
+#define RB_DIR_LEFT 0
+#define RB_DIR_RIGHT 1
+#define RB_DIR_OTHER 1
+#define rb_left rb_nodes[RB_DIR_LEFT]
+#define rb_right rb_nodes[RB_DIR_RIGHT]
+
+ /*
+ * rb_info contains the two flags and the parent back pointer.
+ * We put the two flags in the low two bits since we know that
+ * rb_node will have an alignment of 4 or 8 bytes.
+ */
+ uintptr_t rb_info;
+#define RB_FLAG_POSITION (uintptr_t)0x2
+#define RB_FLAG_RED (uintptr_t)0x1
+#define RB_FLAG_MASK (RB_FLAG_POSITION|RB_FLAG_RED)
+#define RB_FATHER(rb) \
+ ((struct rb_node *)((rb)->rb_info & ~RB_FLAG_MASK))
+#define RB_SET_FATHER(rb, father) \
+ ((void)((rb)->rb_info = (uintptr_t)(father)|((rb)->rb_info & RB_FLAG_MASK)))
+
+#define RB_SENTINEL_P(rb) ((rb) == NULL)
+#define RB_LEFT_SENTINEL_P(rb) RB_SENTINEL_P((rb)->rb_left)
+#define RB_RIGHT_SENTINEL_P(rb) RB_SENTINEL_P((rb)->rb_right)
+#define RB_FATHER_SENTINEL_P(rb) RB_SENTINEL_P(RB_FATHER((rb)))
+#define RB_CHILDLESS_P(rb) \
+ (RB_SENTINEL_P(rb) || (RB_LEFT_SENTINEL_P(rb) && RB_RIGHT_SENTINEL_P(rb)))
+#define RB_TWOCHILDREN_P(rb) \
+ (!RB_SENTINEL_P(rb) && !RB_LEFT_SENTINEL_P(rb) && !RB_RIGHT_SENTINEL_P(rb))
+
+#define RB_POSITION(rb) \
+ (((rb)->rb_info & RB_FLAG_POSITION) ? RB_DIR_RIGHT : RB_DIR_LEFT)
+#define RB_RIGHT_P(rb) (RB_POSITION(rb) == RB_DIR_RIGHT)
+#define RB_LEFT_P(rb) (RB_POSITION(rb) == RB_DIR_LEFT)
+#define RB_RED_P(rb) (!RB_SENTINEL_P(rb) && ((rb)->rb_info & RB_FLAG_RED) != 0)
+#define RB_BLACK_P(rb) (RB_SENTINEL_P(rb) || ((rb)->rb_info & RB_FLAG_RED) == 0)
+#define RB_MARK_RED(rb) ((void)((rb)->rb_info |= RB_FLAG_RED))
+#define RB_MARK_BLACK(rb) ((void)((rb)->rb_info &= ~RB_FLAG_RED))
+#define RB_INVERT_COLOR(rb) ((void)((rb)->rb_info ^= RB_FLAG_RED))
+#define RB_ROOT_P(rbt, rb) ((rbt)->rbt_root == (rb))
+#define RB_SET_POSITION(rb, position) \
+ ((void)((position) ? ((rb)->rb_info |= RB_FLAG_POSITION) : \
+ ((rb)->rb_info &= ~RB_FLAG_POSITION)))
+#define RB_ZERO_PROPERTIES(rb) ((void)((rb)->rb_info &= ~RB_FLAG_MASK))
+#define RB_COPY_PROPERTIES(dst, src) \
+ ((void)((dst)->rb_info ^= ((dst)->rb_info ^ (src)->rb_info) & RB_FLAG_MASK))
+#define RB_SWAP_PROPERTIES(a, b) do { \
+ uintptr_t xorinfo = ((a)->rb_info ^ (b)->rb_info) & RB_FLAG_MASK; \
+ (a)->rb_info ^= xorinfo; \
+ (b)->rb_info ^= xorinfo; \
+ } while (/*CONSTCOND*/ 0)
+#ifdef RBDEBUG
+ TAILQ_ENTRY(rb_node) rb_link;
+#endif
+} rb_node_t;
+
+#define RB_TREE_MIN(T) rb_tree_iterate((T), NULL, RB_DIR_LEFT)
+#define RB_TREE_MAX(T) rb_tree_iterate((T), NULL, RB_DIR_RIGHT)
+#define RB_TREE_NEXT(T, N) rb_tree_iterate((T), (N), RB_DIR_RIGHT)
+#define RB_TREE_PREV(T, N) rb_tree_iterate((T), (N), RB_DIR_LEFT)
+#define RB_TREE_FOREACH(N, T) \
+ for ((N) = RB_TREE_MIN(T); (N); (N) = RB_TREE_NEXT((T), (N)))
+#define RB_TREE_FOREACH_REVERSE(N, T) \
+ for ((N) = RB_TREE_MAX(T); (N); (N) = RB_TREE_PREV((T), (N)))
+#define RB_TREE_FOREACH_SAFE(N, T, S) \
+ for ((N) = RB_TREE_MIN(T); \
+ (N) && ((S) = RB_TREE_NEXT((T), (N)), 1); \
+ (N) = (S))
+#define RB_TREE_FOREACH_REVERSE_SAFE(N, T, S) \
+ for ((N) = RB_TREE_MAX(T); \
+ (N) && ((S) = RB_TREE_PREV((T), (N)), 1); \
+ (N) = (S))
+
+#ifdef RBDEBUG
+TAILQ_HEAD(rb_node_qh, rb_node);
+
+#define RB_TAILQ_REMOVE(a, b, c) TAILQ_REMOVE(a, b, c)
+#define RB_TAILQ_INIT(a) TAILQ_INIT(a)
+#define RB_TAILQ_INSERT_HEAD(a, b, c) TAILQ_INSERT_HEAD(a, b, c)
+#define RB_TAILQ_INSERT_BEFORE(a, b, c) TAILQ_INSERT_BEFORE(a, b, c)
+#define RB_TAILQ_INSERT_AFTER(a, b, c, d) TAILQ_INSERT_AFTER(a, b, c, d)
+#else
+#define RB_TAILQ_REMOVE(a, b, c) do { } while (/*CONSTCOND*/0)
+#define RB_TAILQ_INIT(a) do { } while (/*CONSTCOND*/0)
+#define RB_TAILQ_INSERT_HEAD(a, b, c) do { } while (/*CONSTCOND*/0)
+#define RB_TAILQ_INSERT_BEFORE(a, b, c) do { } while (/*CONSTCOND*/0)
+#define RB_TAILQ_INSERT_AFTER(a, b, c, d) do { } while (/*CONSTCOND*/0)
+#endif /* RBDEBUG */
+
+/*
+ * rbto_compare_nodes_fn:
+ * return a positive value if the first node > the second node.
+ * return a negative value if the first node < the second node.
+ * return 0 if they are considered same.
+ *
+ * rbto_compare_key_fn:
+ * return a positive value if the node > the key.
+ * return a negative value if the node < the key.
+ * return 0 if they are considered same.
+ */
+
+typedef signed int (*rbto_compare_nodes_fn)(void *, const void *, const void *);
+typedef signed int (*rbto_compare_key_fn)(void *, const void *, const void *);
+
+typedef struct {
+ rbto_compare_nodes_fn rbto_compare_nodes;
+ rbto_compare_key_fn rbto_compare_key;
+ size_t rbto_node_offset;
+ void *rbto_context;
+} rb_tree_ops_t;
+
+typedef struct rb_tree {
+ struct rb_node *rbt_root;
+ const rb_tree_ops_t *rbt_ops;
+ struct rb_node *rbt_minmax[2];
+#ifdef RBDEBUG
+ struct rb_node_qh rbt_nodes;
+#endif
+#ifdef RBSTATS
+ unsigned int rbt_count;
+ unsigned int rbt_insertions;
+ unsigned int rbt_removals;
+ unsigned int rbt_insertion_rebalance_calls;
+ unsigned int rbt_insertion_rebalance_passes;
+ unsigned int rbt_removal_rebalance_calls;
+ unsigned int rbt_removal_rebalance_passes;
+#endif
+} rb_tree_t;
+
+#ifdef RBSTATS
+#define RBSTAT_INC(v) ((void)((v)++))
+#define RBSTAT_DEC(v) ((void)((v)--))
+#else
+#define RBSTAT_INC(v) do { } while (/*CONSTCOND*/0)
+#define RBSTAT_DEC(v) do { } while (/*CONSTCOND*/0)
+#endif
+
+void rb_tree_init(rb_tree_t *, const rb_tree_ops_t *);
+void * rb_tree_insert_node(rb_tree_t *, void *);
+void * rb_tree_find_node(rb_tree_t *, const void *);
+void * rb_tree_find_node_geq(rb_tree_t *, const void *);
+void * rb_tree_find_node_leq(rb_tree_t *, const void *);
+void rb_tree_remove_node(rb_tree_t *, void *);
+void * rb_tree_iterate(rb_tree_t *, void *, const unsigned int);
+#ifdef RBDEBUG
+void rb_tree_check(const rb_tree_t *, bool);
+#endif
+#ifdef RBSTATS
+void rb_tree_depths(const rb_tree_t *, size_t *);
+#endif
+
+__END_DECLS
+
+#endif /* _SYS_RBTREE_H_*/
diff --git a/compat/reallocarray.c b/compat/reallocarray.c
new file mode 100644
index 000000000000..9ff9c20b30fb
--- /dev/null
+++ b/compat/reallocarray.c
@@ -0,0 +1,60 @@
+/* $NetBSD: reallocarr.c,v 1.4 2015/08/20 20:08:04 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2015 Joerg Sonnenberger <joerg@NetBSD.org>.
+ * 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 COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * To be clear, this is NetBSD's more refined reallocarr(3) function
+ * made to look like OpenBSD's more useable reallocarray(3) interface.
+ */
+#include "reallocarray.h"
+
+#define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2))
+void *
+reallocarray(void *ptr, size_t n, size_t size)
+{
+
+ /*
+ * Try to avoid division here.
+ *
+ * It isn't possible to overflow during multiplication if neither
+ * operand uses any of the most significant half of the bits.
+ */
+ if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return realloc(ptr, n * size);
+}
diff --git a/compat/reallocarray.h b/compat/reallocarray.h
new file mode 100644
index 000000000000..d855e7b259fb
--- /dev/null
+++ b/compat/reallocarray.h
@@ -0,0 +1,37 @@
+/* $NetBSD: reallocarr.c,v 1.4 2015/08/20 20:08:04 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2015 Joerg Sonnenberger <joerg@NetBSD.org>.
+ * 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 COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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 REALLOCARRAY_H
+#define REALLOCARRAY_H
+
+void *reallocarray(void *, size_t, size_t);
+
+#endif
diff --git a/compat/setproctitle.c b/compat/setproctitle.c
new file mode 100644
index 000000000000..8c03c3922c07
--- /dev/null
+++ b/compat/setproctitle.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright © 2010 William Ahern
+ * Copyright © 2012-2013 Guillem Jover <guillem@hadrons.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <unistd.h>
+#include <string.h>
+//#include "local-link.h"
+
+#include "config.h"
+
+static struct {
+ /* Original value. */
+ char *arg0;
+
+ /* Title space available. */
+ char *base, *end;
+
+ /* Pointer to original nul character within base. */
+ char *nul;
+
+ bool warned;
+ bool reset;
+ int error;
+
+ /* Our copy of args and environment to free. */
+ int argc;
+ char **argv;
+ char **tmp_environ;
+} SPT;
+
+
+static inline size_t
+spt_min(size_t a, size_t b)
+{
+ return a < b ? a : b;
+}
+
+/*
+ * For discussion on the portability of the various methods, see
+ * https://lists.freebsd.org/pipermail/freebsd-stable/2008-June/043136.html
+ */
+static int
+spt_clearenv(void)
+{
+#ifdef HAVE_CLEARENV
+ return clearenv();
+#else
+ SPT.tmp_environ = malloc(sizeof(*SPT.tmp_environ));
+ if (SPT.tmp_environ == NULL)
+ return errno;
+
+ SPT.tmp_environ[0] = NULL;
+ environ = SPT.tmp_environ;
+
+ return 0;
+#endif
+}
+
+static int
+spt_copyenv(int envc, char *envp[])
+{
+ char **envcopy;
+ char *eq;
+ size_t envsize;
+ int i, error;
+
+ if (environ != envp)
+ return 0;
+
+ /* Make a copy of the old environ array of pointers, in case
+ * clearenv() or setenv() is implemented to free the internal
+ * environ array, because we will need to access the old environ
+ * contents to make the new copy. */
+ envsize = (size_t)(envc + 1) * sizeof(char *);
+ envcopy = malloc(envsize);
+ if (envcopy == NULL)
+ return errno;
+ memcpy(envcopy, envp, envsize);
+
+ error = spt_clearenv();
+ if (error) {
+ environ = envp;
+ free(envcopy);
+ return error;
+ }
+
+ for (i = 0; envcopy[i]; i++) {
+ eq = strchr(envcopy[i], '=');
+ if (eq == NULL)
+ continue;
+
+ *eq = '\0';
+ if (setenv(envcopy[i], eq + 1, 1) < 0)
+ error = errno;
+ *eq = '=';
+
+ if (error) {
+#ifdef HAVE_CLEARENV
+ /* Because the old environ might not be available
+ * anymore we will make do with the shallow copy. */
+ environ = envcopy;
+#else
+ environ = envp;
+ free(envcopy);
+#endif
+ return error;
+ }
+ }
+
+ /* Dispose of the shallow copy, now that we've finished transfering
+ * the old environment. */
+ free(envcopy);
+
+ return 0;
+}
+
+static int
+spt_copyargs(int argc, char *argv[])
+{
+ char *tmp;
+ int i;
+
+ for (i = 1; i < argc || (i >= argc && argv[i]); i++) {
+ if (argv[i] == NULL)
+ continue;
+
+ tmp = strdup(argv[i]);
+ if (tmp == NULL)
+ return errno;
+
+ argv[i] = tmp;
+ }
+
+ return 0;
+}
+
+void
+setproctitle_init(int argc, char *argv[], char *envp[])
+{
+ char *base, *end, *nul, *tmp;
+ int i, envc, error;
+
+ /* Try to make sure we got called with main() arguments. */
+ if (argc < 0)
+ return;
+
+ base = argv[0];
+ if (base == NULL)
+ return;
+
+ nul = &base[strlen(base)];
+ end = nul + 1;
+
+ for (i = 0; i < argc || (i >= argc && argv[i]); i++) {
+ if (argv[i] == NULL || argv[i] != end)
+ continue;
+
+ end = argv[i] + strlen(argv[i]) + 1;
+ }
+
+ for (i = 0; envp[i]; i++) {
+ if (envp[i] != end)
+ continue;
+
+ end = envp[i] + strlen(envp[i]) + 1;
+ }
+ envc = i;
+
+ SPT.arg0 = strdup(argv[0]);
+ if (SPT.arg0 == NULL) {
+ SPT.error = errno;
+ return;
+ }
+
+ tmp = strdup(getprogname());
+ if (tmp == NULL) {
+ SPT.error = errno;
+ return;
+ }
+ setprogname(tmp);
+
+ error = spt_copyenv(envc, envp);
+ if (error) {
+ SPT.error = error;
+ return;
+ }
+
+ error = spt_copyargs(argc, argv);
+ if (error) {
+ SPT.error = error;
+ return;
+ }
+
+ SPT.argc = argc;
+ SPT.argv = argv;
+
+ SPT.nul = nul;
+ SPT.base = base;
+ SPT.end = end;
+}
+
+void
+setproctitle_fini(void)
+{
+ int i;
+
+ free(SPT.arg0);
+ SPT.arg0 = NULL;
+
+ for (i = 1; i < SPT.argc; i++) {
+ if (SPT.argv[i] != NULL)
+ free(SPT.argv[i]);
+ }
+ SPT.argc = 0;
+
+ free(SPT.tmp_environ);
+ SPT.tmp_environ = NULL;
+}
+
+#ifndef SPT_MAXTITLE
+#define SPT_MAXTITLE 255
+#endif
+
+__printflike(1, 2) static void
+setproctitle_impl(const char *fmt, ...)
+{
+ /* Use buffer in case argv[0] is passed. */
+ char buf[SPT_MAXTITLE + 1];
+ va_list ap;
+ char *nul;
+ int l;
+ size_t len, base_len;
+
+ if (SPT.base == NULL) {
+ if (!SPT.warned) {
+ warnx("setproctitle not initialized, please either call "
+ "setproctitle_init() or link against libbsd-ctor.");
+ SPT.warned = true;
+ }
+ return;
+ }
+
+ if (fmt) {
+ if (fmt[0] == '-') {
+ /* Skip program name prefix. */
+ fmt++;
+ len = 0;
+ } else {
+ /* Print program name heading for grep. */
+ l = snprintf(buf, sizeof(buf), "%s: ", getprogname());
+ if (l <= 0)
+ return;
+ len = (size_t)l;
+ }
+
+ va_start(ap, fmt);
+ l = vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
+ va_end(ap);
+ } else {
+ len = 0;
+ l = snprintf(buf, sizeof(buf), "%s", SPT.arg0);
+ }
+
+ if (l <= 0) {
+ SPT.error = errno;
+ return;
+ }
+ len += (size_t)l;
+
+ base_len = (size_t)(SPT.end - SPT.base);
+ if (!SPT.reset) {
+ memset(SPT.base, 0, base_len);
+ SPT.reset = true;
+ } else {
+ memset(SPT.base, 0, spt_min(sizeof(buf), base_len));
+ }
+
+ len = spt_min(len, spt_min(sizeof(buf), base_len) - 1);
+ memcpy(SPT.base, buf, len);
+ nul = &SPT.base[len];
+
+ if (nul < SPT.nul) {
+ *SPT.nul = '.';
+ } else if (nul == SPT.nul && &nul[1] < SPT.end) {
+ *SPT.nul = ' ';
+ *++nul = '\0';
+ }
+}
+libbsd_symver_default(setproctitle, setproctitle_impl, LIBBSD_0.5);
+
+/* The original function introduced in 0.2 was a stub, it only got implemented
+ * in 0.5, make the implementation available in the old version as an alias
+ * for code linking against that version, and change the default to use the
+ * new version, so that new code depends on the implemented version. */
+#ifdef HAVE_TYPEOF
+extern __typeof__(setproctitle_impl)
+setproctitle_stub
+ __attribute__((__alias__("setproctitle_impl")));
+#else
+void
+setproctitle_stub(const char *fmt, ...)
+ __attribute__((__alias__("setproctitle_impl")));
+#endif
+libbsd_symver_variant(setproctitle, setproctitle_stub, LIBBSD_0.2);
diff --git a/compat/setproctitle.h b/compat/setproctitle.h
new file mode 100644
index 000000000000..32dc56bdd38f
--- /dev/null
+++ b/compat/setproctitle.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2010 William Ahern
+ * Copyright © 2012-2013 Guillem Jover <guillem@hadrons.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SETPROCTITLE_H
+#define SETPROCTITLE_H
+
+#ifndef __printflike
+#if __GNUC__ > 2 || defined(__INTEL_COMPILER)
+#define __printflike(a, b) __attribute__((format(printf, a, b)))
+#else
+#define __printflike(a, b)
+#endif
+#endif /* !__printflike */
+
+/* WEXITSTATUS is defined in stdlib.h which defines free() */
+#ifdef WEXITSTATUS
+static inline const char *
+getprogname(void)
+{
+ return "dhcpcd";
+}
+static inline void
+setprogname(char *name)
+{
+ free(name);
+}
+#endif
+
+void setproctitle_init(int, char *[], char *[]);
+__printflike(1, 2) void setproctitle(const char *, ...);
+void setproctitle_fini(void);
+
+#define libbsd_symver_default(alias, symbol, version) \
+ extern __typeof(symbol) alias __attribute__((__alias__(#symbol)))
+
+#define libbsd_symver_variant(alias, symbol, version)
+#endif
diff --git a/compat/strlcpy.c b/compat/strlcpy.c
new file mode 100644
index 000000000000..1fb4a2f9615d
--- /dev/null
+++ b/compat/strlcpy.c
@@ -0,0 +1,51 @@
+/* $OpenBSD: strlcpy.c,v 1.15 2016/10/16 17:37:39 dtucker Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <sys/types.h>
+
+#include "strlcpy.h"
+
+/*
+ * Copy string src to buffer dst of size dsize. At most dsize-1
+ * chars will be copied. Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ ;
+ }
+
+ return (size_t)(src - osrc - 1); /* count does not include NUL */
+}
diff --git a/compat/strlcpy.h b/compat/strlcpy.h
new file mode 100644
index 000000000000..2131cdec305f
--- /dev/null
+++ b/compat/strlcpy.h
@@ -0,0 +1,24 @@
+/* $OpenBSD: strlcpy.c,v 1.15 2016/10/16 17:37:39 dtucker Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#ifndef STRLCPY_H
+#define STRLCPY_H
+
+size_t strlcpy(char *, const char *, size_t);
+
+#endif
diff --git a/compat/strtoi.c b/compat/strtoi.c
new file mode 100644
index 000000000000..567d86edde89
--- /dev/null
+++ b/compat/strtoi.c
@@ -0,0 +1,68 @@
+/* $NetBSD: strtoi.c,v 1.3 2019/11/28 12:33:23 roy Exp $ */
+
+/*-
+ * Copyright (c) 2005 The DragonFly Project. All rights reserved.
+ * Copyright (c) 2003 Citrus Project,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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.
+ *
+ * Created by Kamil Rytarowski, based on ID:
+ * NetBSD: src/common/lib/libc/stdlib/strtoul.c,v 1.3 2008/08/20 19:58:34 oster Exp
+ */
+
+#if defined(HAVE_NBTOOL_CONFIG_H) && HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#ifdef _LIBC
+#include "namespace.h"
+#endif
+
+#if defined(_KERNEL)
+#include <sys/param.h>
+#include <sys/types.h>
+#include <lib/libkern/libkern.h>
+#elif defined(_STANDALONE)
+#include <sys/param.h>
+#include <sys/types.h>
+#include <lib/libkern/libkern.h>
+#include <lib/libsa/stand.h>
+#else
+#include <stddef.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#endif
+
+#include "strtoi.h"
+
+#define _FUNCNAME strtoi
+#define __TYPE intmax_t
+#define __WRAPPED strtoimax
+
+#include "_strtoi.h"
+
+#ifdef _LIBC
+__weak_alias(strtoi, _strtoi)
+__weak_alias(strtoi_l, _strtoi_l)
+#endif
diff --git a/compat/strtoi.h b/compat/strtoi.h
new file mode 100644
index 000000000000..bd976fcf3ca3
--- /dev/null
+++ b/compat/strtoi.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * Original version ID:
+ * NetBSD: src/lib/libc/locale/_wcstoul.h,v 1.2 2003/08/07 16:43:03 agc Exp
+ *
+ * Created by Kamil Rytarowski, based on ID:
+ * NetBSD: src/common/lib/libc/stdlib/_strtoul.h,v 1.7 2013/05/17 12:55:56 joerg Exp
+ */
+
+#ifndef STRTOI_H
+#define STRTOI_H
+
+#include <inttypes.h>
+
+intmax_t strtoi(const char * __restrict nptr, char ** __restrict endptr,
+ int base, intmax_t lo, intmax_t hi, int *rstatus);
+uintmax_t strtou(const char * __restrict nptr, char ** __restrict endptr,
+ int base, uintmax_t lo, uintmax_t hi, int *rstatus);
+#endif
diff --git a/compat/strtou.c b/compat/strtou.c
new file mode 100644
index 000000000000..f300dcc23817
--- /dev/null
+++ b/compat/strtou.c
@@ -0,0 +1,68 @@
+/* $NetBSD: strtou.c,v 1.3 2019/11/28 12:33:23 roy Exp $ */
+
+/*-
+ * Copyright (c) 2005 The DragonFly Project. All rights reserved.
+ * Copyright (c) 2003 Citrus Project,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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.
+ *
+ * Created by Kamil Rytarowski, based on ID:
+ * NetBSD: src/common/lib/libc/stdlib/strtoul.c,v 1.3 2008/08/20 19:58:34 oster Exp
+ */
+
+#if defined(HAVE_NBTOOL_CONFIG_H) && HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#ifdef _LIBC
+#include "namespace.h"
+#endif
+
+#if defined(_KERNEL)
+#include <sys/param.h>
+#include <sys/types.h>
+#include <lib/libkern/libkern.h>
+#elif defined(_STANDALONE)
+#include <sys/param.h>
+#include <sys/types.h>
+#include <lib/libkern/libkern.h>
+#include <lib/libsa/stand.h>
+#else
+#include <stddef.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#endif
+
+#include "strtoi.h"
+
+#define _FUNCNAME strtou
+#define __TYPE uintmax_t
+#define __WRAPPED strtoumax
+
+#include "_strtoi.h"
+
+#ifdef _LIBC
+__weak_alias(strtou, _strtou)
+__weak_alias(strtou_l, _strtou_l)
+#endif
diff --git a/config-null.mk b/config-null.mk
new file mode 100644
index 000000000000..c7a8de384f49
--- /dev/null
+++ b/config-null.mk
@@ -0,0 +1,3 @@
+# This space left intentionally blank
+
+DHCPCD_SRCS+= dhcpcd-embedded.c
diff --git a/configure b/configure
new file mode 100755
index 000000000000..19eb431816e7
--- /dev/null
+++ b/configure
@@ -0,0 +1,1814 @@
+#!/bin/sh
+# Try and be like autotools configure, but without autotools
+
+echo "configure args: $*"
+exec 3>config.log
+
+# Ensure that we do not inherit these from env
+HOOKSET=false
+INET=
+ARP=
+ARPING=
+IPV4LL=
+INET6=
+PRIVSEP=
+PRIVSEP_USER=
+ARC4RANDOM=
+CLOSEFROM=
+RBTREE=
+CONSTTIME_MEMEQUAL=
+OPEN_MEMSTREAM=
+STRLCPY=
+UDEV=
+OS=
+BUILD=
+HOST=
+HOSTCC=
+TARGET=
+INCLUDEDIR=
+DEBUG=
+FORK=
+STATIC=
+DEVS=
+EMBEDDED=
+AUTH=
+POLL=
+SMALL=
+SANITIZE=no
+STATUSARG=
+
+DHCPCD_DEFS=dhcpcd-definitions.conf
+
+for x do
+ opt=${x%%=*}
+ var=${x#*=}
+ case "$opt" in
+ --os|OS) OS=$var;;
+ --debug) DEBUG=$var;;
+ --disable-debug) DEBUG=no;;
+ --enable-debug) DEBUG=yes;;
+ --fork) FORK=$var;;
+ --disable-fork) FORK=no;;
+ --enable-fork) FORK=yes;;
+ --disable-static) STATIC=no;;
+ --enable-static) STATIC=yes;;
+ --disable-ipv4|--disable-inet) INET=no; ARP=no; ARPING=no; IPV4LL=no;;
+ --enable-ipv4|--enable-inet) INET=yes;;
+ --disable-arp) ARP=no; ARPING=no; IPV4LL=no;;
+ --enable-arp) ARP=yes; INET=yes;;
+ --disable-arping) ARPING=no;;
+ --enable-arping) ARPING=yes; ARP=yes; INET=yes;;
+ --disable-ipv4ll) IPV4LL=no;;
+ --enable-ipv4ll) IPV4LL=yes; ARP=yes; INET=yes;;
+ --disable-ipv6|--disable-inet6) INET6=no; DHCP6=no;;
+ --enable-ipv6|--enable-inet6) INET6=yes;;
+ --disable-dhcp6) DHCP6=no;;
+ --enable-dhcp6) DHCP6=yes;;
+ --disable-embedded) EMBEDDED=no;;
+ --enable-embedded) EMBEDDED=yes;;
+ --disable-auth) AUTH=no;;
+ --enable-auth) AUTH=yes;;
+ --disable-privsep) PRIVSEP=no;;
+ --enable-privsep) PRIVSEP=yes;;
+ --privsepuser) PRIVSEP_USER=$var;;
+ --prefix) PREFIX=$var;;
+ --sysconfdir) SYSCONFDIR=$var;;
+ --bindir|--sbindir) SBINDIR=$var;;
+ --libexecdir) LIBEXECDIR=$var;;
+ --statedir|--localstatedir) STATEDIR=$var;;
+ --dbdir) DBDIR=$var;;
+ --rundir) RUNDIR=$var;;
+ --runstatedir) RUNSTATEDIR=$var;;
+ --mandir) MANDIR=$var;;
+ --datadir) DATADIR=$var;;
+ --with-ccopts|CFLAGS) CFLAGS=$var;;
+ -I|--includedir) INCLUDEDIR="$INCLUDEDIR${INCLUDEDIR:+ }-I$var";;
+ CC) CC=$var;;
+ CPPFLAGS) CPPFLAGS=$var;;
+ PKG_CONFIG) PKG_CONFIG=$var;;
+ --with-hook) HOOKSCRIPTS="$HOOKSCRIPTS${HOOKSCRIPTS:+ }$var";;
+ --with-hooks|HOOKSCRIPTS) HOOKSCRIPTS=$var; HOOKSET=true;;
+ --with-eghook) EGHOOKSCRIPTS="$EGHOOKSCRIPTS${EGHOOKSCRIPTS+ }$var";;
+ --with-eghooks|EGHOOKSCRIPTS) EGHOOKSCRIPTS=$var; EGHOOKSET=true;;
+ --with-default-hostname) _DEFAULT_HOSTNAME=$var;;
+ --build) BUILD=$var;;
+ --host) HOST=$var; HOSTCC=$var-;;
+ --target) TARGET=$var;;
+ --libdir) LIBDIR=$var;;
+ --without-arc4random) ARC4RANDOM=no;;
+ --without-strlcpy) STRLCPY=no;;
+ --without-pidfile_lock) PIDFILE_LOCK=no;;
+ --without-reallocarrray) REALLOCARRAY=no;;
+ --without-md5) MD5=no;;
+ --without-sha2) SHA2=no;;
+ --without-sha256) SHA2=no;;
+ --without-hmac) HMAC=no;;
+ --without-dev) DEV=no;;
+ --with-udev) DEV=yes; UDEV=yes;;
+ --without-udev) UDEV=no;;
+ --with-poll) POLL="$var";;
+ --sanitise|--sanitize) SANITIZEADDRESS="yes";;
+ --serviceexists) SERVICEEXISTS=$var;;
+ --servicecmd) SERVICECMD=$var;;
+ --servicestatus) SERVICESTATUS=$var;;
+ --small) SMALL=yes;;
+ --statusarg) STATUSARG=$var;;
+ --infodir) ;; # ignore autotools
+ --disable-maintainer-mode|--disable-dependency-tracking) ;;
+ --disable-silent-rules) ;;
+ -V|--version)
+ v=$(sed -ne 's/.*VERSION[[:space:]]*"\([^"]*\).*/\1/p' defs.h);
+ c=$(sed -ne 's/^.*copyright\[\] = "\([^"]*\).*/\1/p' dhcpcd.c);
+ echo "dhcpcd-$v $c";
+ exit 0;;
+ -h|--help) cat <<EOF
+\`configure' configures this package to adapt to many kinds of systems.
+
+Usage: configure [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ -V, --version display version information and exit
+
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX [/]
+
+By default, \`make install' will install all the files in \'/sbin',
+\`/libexec', etc. You can specify
+an installation prefix other than \`/' using \`--prefix',
+for instance \`--prefix=$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [PREFIX/bin]
+ --sbindir=DIR system admin executables [PREFIX/sbin]
+ --libexecdir=DIR program executables [PREFIX/libexec]
+ --dbdir=DIR database [STATEDIR/db/dhcpcd]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --localstatedir=DIR modifiable single-machine data [/var]
+ --libdir=DIR object code libraries [PREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --mandir=DIR man documentation [PREFIX/man]
+
+System types:
+ --build=BUILD configure for building on BUILD [guessed]
+ --host=HOST build programs to run on HOST [BUILD]
+ --target=TARGET configure for building compilers for TARGET [HOST]
+
+Optional Features:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ CPPFLAGS C/C++ preprocessor flags, e.g. -I<include dir> if you have
+ headers in a nonstandard directory <include dir>
+ CPP C preprocessor
+ PKG_CONFIG pkg-config executable
+
+Use these variables to override the choices made by \`configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+EOF
+exit 0
+;;
+ *) echo "$0: WARNING: unknown option $opt" >&2;;
+ esac
+done
+
+: ${SED:=sed}
+: ${GREP:=grep}
+: ${PKG_CONFIG:=pkg-config}
+: ${WC:=wc}
+
+: ${FORK:=yes}
+_which()
+{
+ x="$(which "$1" 2>/dev/null)"
+ if [ $? = 0 ] && [ -n "$x" ]; then
+ echo "$x"
+ return 0
+ fi
+ for x in /sbin/"$1" /usr/sbin/"$1" \
+ /usr/pkg/sbin/"$1" /usr/local/sbin/"$1"
+ do
+ if [ -e "$x" ]; then
+ echo "$x"
+ return 0
+ fi
+ done
+ return 1
+}
+
+CONFIG_H=config.h
+CONFIG_MK=config.mk
+
+if [ -z "$BUILD" ]; then
+ # autoconf target triplet: cpu-vendor-os
+ BUILD=$(uname -m)-unknown-$(uname -s | tr '[:upper:]' '[:lower:]')
+fi
+: ${HOST:=$BUILD}
+
+if [ -z "$OS" ]; then
+ echo "Deriving operating system from ... $HOST"
+ # Derive OS from cpu-vendor-[kernel-]os
+ CPU=${HOST%%-*}
+ REST=${HOST#*-}
+ if [ "$CPU" != "$REST" ]; then
+ VENDOR=${REST%%-*}
+ REST=${REST#*-}
+ if [ "$VENDOR" != "$REST" ]; then
+ # Use kernel if given, otherwise os
+ OS=${REST%%-*}
+ else
+ # 2 tupple
+ OS=$VENDOR
+ VENDOR=
+ fi
+ fi
+
+ # Work with cpu-kernel-os, ie Debian
+ case "$VENDOR" in
+ linux*|kfreebsd*) OS=$VENDOR; VENDOR= ;;
+ esac
+ case "$REST" in
+ gnu/kfreebsd*) OS="kfreebsd"; VENDOR= ;;
+ esac
+ # Special case
+ case "$OS" in
+ dragonfly*)
+ # This means /usr HAS to be mounted not via dhcpcd
+ : ${LIBEXECDIR:=${PREFIX:-/usr}/libexec}
+ ;;
+ gnu*) OS=hurd;; # No HURD support as yet
+ esac
+fi
+
+echo "Configuring dhcpcd for ... $OS"
+rm -f $CONFIG_H $CONFIG_MK
+echo "# $OS" >$CONFIG_MK
+echo "/* $OS */" >$CONFIG_H
+
+: ${SYSCONFDIR:=$PREFIX/etc}
+: ${SBINDIR:=$PREFIX/sbin}
+: ${LIBDIR:=$PREFIX/lib}
+: ${LIBEXECDIR:=$PREFIX/libexec}
+: ${STATEDIR:=/var}
+: ${DBDIR:=$STATEDIR/db/dhcpcd}
+: ${RUNSTATEDIR:=$STATEDIR/run}
+: ${RUNDIR:=$RUNSTATEDIR/dhcpcd}
+: ${MANDIR:=${PREFIX:-/usr}/share/man}
+: ${DATADIR:=${PREFIX:-/usr}/share}
+
+eval SYSCONFDIR="$SYSCONFDIR"
+eval LIBDIR="$LIBDIR"
+eval LIBEXECDIR="$LIBEXECDIR"
+eval STATEDIR="$STATEDIR"
+eval DBDIR="$DBDIR"
+eval RUNDIR="$RUNDIR"
+eval MANDIR="$MANDIR"
+eval DATADIR="$DATADIR"
+
+echo "#ifndef SYSCONFDIR" >>$CONFIG_H
+for x in SYSCONFDIR SBINDIR LIBDIR LIBEXECDIR DBDIR RUNDIR; do
+ eval v=\$$x
+ # Make files look nice for import
+ l=$((10 - ${#x}))
+ unset t
+ [ $l -gt 3 ] && t=" "
+ echo "$x=$t $v" >>$CONFIG_MK
+ unset t
+ [ $l -gt 2 ] && t=" "
+ echo "#define $x$t \"$v\"" >>$CONFIG_H
+done
+echo "#endif" >>$CONFIG_H
+
+echo "LIBDIR= $LIBDIR" >>$CONFIG_MK
+echo "MANDIR= $MANDIR" >>$CONFIG_MK
+echo "DATADIR= $DATADIR" >>$CONFIG_MK
+
+# Always obey CC.
+if [ -n "$CC" ]; then
+ HOSTCC=
+else
+ CC=cc
+ _COMPILERS="cc clang gcc pcc icc"
+fi
+# Only look for a cross compiler if --host and --build are not the same
+if [ -n "$HOSTCC" ] && [ "$BUILD" != "$HOST" ]; then
+ for _CC in $_COMPILERS; do
+ _CC=$(_which "$HOSTCC$_CC")
+ if [ -x "$_CC" ]; then
+ CC=$_CC
+ break
+ fi
+ done
+fi
+if ! type "$CC" >/dev/null 2>&1; then
+ for _CC in $_COMPILERS; do
+ _CC=$(_which "$_CC")
+ if [ -x "$_CC" ]; then
+ CC=$_CC
+ break
+ fi
+ done
+fi
+
+# Set to blank, then append user config
+# We do this so our SED call to append to XCC remains portable
+if [ -n "$CFLAGS" ]; then
+ echo "CFLAGS=" >>$CONFIG_MK
+ echo "CFLAGS+= $CFLAGS" >>$CONFIG_MK
+fi
+if [ -n "$CPPFLAGS" ]; then
+ echo "CPPFLAGS=" >>$CONFIG_MK
+ echo "CPPFLAGS+= $CPPFLAGS" >>$CONFIG_MK
+fi
+if [ -n "$INCLUDEDIR" ]; then
+ echo "CPPFLAGS+= $INCLUDEDIR" >>$CONFIG_MK
+fi
+if [ -n "$LDFLAGS" ]; then
+ echo "LDFLAGS=" >>$CONFIG_MK
+ echo "LDFLAGS+= $LDFLAGS" >>$CONFIG_MK
+fi
+
+echo "CPPFLAGS+= -DHAVE_CONFIG_H" >>$CONFIG_MK
+
+# NetBSD: Even if we build for $PREFIX, the clueless user might move us to /
+LDELF=/libexec/ld.elf_so
+if [ -e "$LDELF" ]; then
+ echo "Linking against $LDELF"
+ echo "LDFLAGS+= -Wl,-dynamic-linker=$LDELF" >>$CONFIG_MK
+ echo "LDFLAGS+= -Wl,-rpath=${LIBDIR}" >>$CONFIG_MK
+fi
+
+if [ -z "$PREFIX" ] || [ "$PREFIX" = / ]; then
+ ALLOW_USR_LIBS=false
+else
+ ALLOW_USR_LIBS=true
+fi
+case "$OS" in
+linux*|solaris*|sunos*|kfreebsd*) ;;
+*)
+ # There might be more than one ...
+ for LDELFN in /libexec/ld-elf.so.[0-9]*; do
+ [ -x "$LDELFN" ] && break
+ done
+ if ! [ -x "$LDELF" ] || [ -x "$LDELFN" ]; then
+ if [ -z "$PREFIX" ] || [ "$PREFIX" = "/" ]; then
+ echo "Forcing a static build for $OS and \$PREFIX of /"
+ STATIC=yes
+ ALLOW_USR_LIBS=true
+ fi
+ fi
+ ;;
+esac
+if [ "$STATIC" = yes ]; then
+ echo "LDFLAGS+= -static" >>$CONFIG_MK
+fi
+
+if [ -z "$DEBUG" ] && [ -d .git ]; then
+ printf "Found git checkout ... "
+ DEBUG=yes
+fi
+if [ -n "$DEBUG" ] && [ "$DEBUG" != no ] && [ "$DEBUG" != false ]; then
+ echo "Adding debugging CFLAGS"
+
+ cat <<EOF >>$CONFIG_MK
+CFLAGS+= -g -Wall -Wextra -Wundef
+CFLAGS+= -Wmissing-prototypes -Wmissing-declarations
+CFLAGS+= -Wmissing-format-attribute -Wnested-externs
+CFLAGS+= -Winline -Wcast-align -Wcast-qual -Wpointer-arith
+CFLAGS+= -Wreturn-type -Wswitch -Wshadow
+CFLAGS+= -Wcast-qual -Wwrite-strings
+CFLAGS+= -Wformat=2
+CFLAGS+= -Wpointer-sign -Wmissing-noreturn
+EOF
+
+ case "$OS" in
+ mirbsd*|openbsd*);; # OpenBSD has many redundant decs in system headers
+ bitrig*|solaris*|sunos*)
+ echo "CFLAGS+= -Wredundant-decls" >>$CONFIG_MK
+ ;; # Bitrig spouts many conversion errors with htons
+ # sunos has many as well
+ *) echo "CFLAGS+= -Wredundant-decls" >>$CONFIG_MK
+ echo "CFLAGS+= -Wconversion" >>$CONFIG_MK
+ ;;
+ esac
+
+ case "$OS" in
+ solaris*|sunos*);;
+ *) echo "CFLAGS+= -Wstrict-overflow" >>$CONFIG_MK;;
+ esac
+
+ # Turn on extra per compiler debugging
+ case "$CC" in
+ *gcc*) echo "CFLAGS+= -Wlogical-op" >>$CONFIG_MK;;
+ esac
+
+ if [ "$SANITIZEADDRESS" = yes ]; then
+ printf "Testing compiler supports address sanitisation ..."
+ cat <<EOF >_test.c
+int main(void) {
+ return 0;
+}
+EOF
+ if $CC -fsanitize=address _test.c -o _test 2>&3; then
+ echo "yes"
+ echo "CFLAGS+= -fsanitize=address" >>$CONFIG_MK
+ echo "CFLAGS+= -fno-omit-frame-pointer" >>$CONFIG_MK
+ echo "LDFLAGS+= -fsanitize=address" >>$CONFIG_MK
+ else
+ echo "no"
+ fi
+ rm -rf _test.c _test
+ fi
+else
+ echo "CPPFLAGS+= -DNDEBUG" >>$CONFIG_MK
+fi
+
+if [ -n "$FORK" ] && [ "$FORK" != yes ] && [ "$FORK" != true ]; then
+ echo "There is no fork"
+ echo "CPPFLAGS+= -DTHERE_IS_NO_FORK" >>$CONFIG_MK
+fi
+
+if [ "$SMALL" = yes ]; then
+ echo "Building with -DSMALL"
+ echo "CPPFLAGS+= -DSMALL" >>$CONFIG_MK
+ DHCPCD_DEFS=dhcpcd-definitions-small.conf
+ echo "DHCPCD_DEFS= $DHCPCD_DEFS" >>$CONFIG_MK
+fi
+
+case "$OS" in
+freebsd*|kfreebsd*)
+ # FreeBSD hide some newer POSIX APIs behind _GNU_SOURCE ...
+ echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK
+ case "$OS" in
+ kfreebsd*) echo "CPPFLAGS+= -DBSD" >>$CONFIG_MK;;
+ esac
+ echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK
+ # Whacky includes needed to buck the trend
+ case "$OS" in
+ kfreebsd*) echo "#include <inttypes.h>" >>$CONFIG_H;
+ esac
+ echo "#include <net/if.h>" >>$CONFIG_H
+ echo "#include <net/if_var.h>" >>$CONFIG_H
+ ;;
+netbsd*)
+ # reallocarray(3) is guarded by _OPENBSD_SOURCE
+ echo "CPPFLAGS+= -D_OPENBSD_SOURCE" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK
+ ;;
+linux*)
+ echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK
+ # Large File Support, should be fine for 32-bit systems.
+ # But if this is the case, why is it not set by default?
+ echo "CPPFLAGS+= -D_FILE_OFFSET_BITS=64" >>$CONFIG_MK
+ echo "CPPFLAGS+= -D_LARGEFILE_SOURCE" >>$CONFIG_MK
+ echo "CPPFLAGS+= -D_LARGEFILE64_SOURCE" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= if-linux.c" >>$CONFIG_MK
+ # for RTM_NEWADDR and friends
+ echo "#include <asm/types.h> /* fix broken headers */" >>$CONFIG_H
+ echo "#include <sys/socket.h> /* fix broken headers */" >>$CONFIG_H
+ echo "#include <linux/rtnetlink.h>" >>$CONFIG_H
+ # cksum does't support -a and netpgp is rare
+ echo "CKSUM= sha256sum --tag" >>$CONFIG_MK
+ echo "PGP= gpg2" >>$CONFIG_MK
+ ;;
+qnx*)
+ echo "CPPFLAGS+= -D__EXT" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK
+ ;;
+solaris*|sunos*)
+ echo "CPPFLAGS+= -D_XPG4_2 -D__EXTENSIONS__ -DBSD_COMP" \
+ >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= if-sun.c" >>$CONFIG_MK
+ echo "LDADD+= -ldlpi -lkstat" >>$CONFIG_MK
+ ;;
+*)
+ echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK
+ ;;
+esac
+
+if [ -n "${_DEFAULT_HOSTNAME+x}" ]; then
+ DEFAULT_HOSTNAME="${_DEFAULT_HOSTNAME}"
+else
+ case "$OS" in
+ linux*) DEFAULT_HOSTNAME="(none)";;
+ *) DEFAULT_HOSTNAME="";;
+ esac
+fi
+echo "DEFAULT_HOSTNAME= $DEFAULT_HOSTNAME" >>$CONFIG_MK
+
+if [ -z "$INET" ] || [ "$INET" = yes ]; then
+ echo "Enabling INET support"
+ echo "CPPFLAGS+= -DINET" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= dhcp.c ipv4.c bpf.c" >>$CONFIG_MK
+ if [ -z "$ARP" ] || [ "$ARP" = yes ]; then
+ echo "Enabling ARP support"
+ echo "CPPFLAGS+= -DARP" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= arp.c" >>$CONFIG_MK
+ fi
+ if [ -z "$ARPING" ] || [ "$ARPING" = yes ]; then
+ echo "Enabling ARPing support"
+ echo "CPPFLAGS+= -DARPING" >>$CONFIG_MK
+ fi
+ if [ -z "$IPV4LL" ] || [ "$IPV4LL" = yes ]; then
+ echo "Enabling IPv4LL support"
+ echo "CPPFLAGS+= -DIPV4LL" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= ipv4ll.c" >>$CONFIG_MK
+ fi
+fi
+if [ -z "$INET6" ] || [ "$INET6" = yes ]; then
+ echo "Enabling INET6 support"
+ echo "CPPFLAGS+= -DINET6" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= ipv6.c ipv6nd.c" >>$CONFIG_MK
+ if [ -z "$DHCP6" ] || [ "$DHCP6" = yes ]; then
+ echo "Enabling DHCPv6 support"
+ echo "CPPFLAGS+= -DDHCP6" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= dhcp6.c" >>$CONFIG_MK
+ fi
+fi
+if [ -z "$AUTH" ] || [ "$AUTH" = yes ]; then
+ echo "Enabling Authentication"
+ echo "CPPFLAGS+= -DAUTH" >>$CONFIG_MK
+ echo "SRCS+= auth.c" >>$CONFIG_MK
+fi
+
+if [ -z "$PRIVSEP" ]; then
+ # privilege separation works fine .... except on Solaris
+ case "$OS" in
+ solaris*|sunos*) PRIVSEP=no;;
+ *) PRIVSEP=yes;;
+ esac
+fi
+
+if [ "$PRIVSEP" = yes ]; then
+ echo "Enabling Privilege Separation"
+
+ # Try and work out system user
+ if [ -z "$PRIVSEP_USER" ]; then
+ printf "Detecting a suitable user for dhcpcd ... "
+ for x in _dhcpcd _dhcp dhcpcd; do
+ home=$(getent passwd $x 2>/dev/null | cut -d: -f6)
+ if [ -d "$home" ]; then
+ PRIVSEP_USER="$x"
+ break
+ fi
+ done
+ fi
+ if [ -n "$PRIVSEP_USER" ]; then
+ echo "$PRIVSEP_USER"
+ else
+ PRIVSEP_USER=dhcpcd
+ echo
+ echo "No suitable user found for Priviledge Separation!"
+ fi
+
+ echo "CPPFLAGS+= -DPRIVSEP" >>$CONFIG_MK
+ echo "PRIVSEP_USER?= $PRIVSEP_USER" >>$CONFIG_MK
+ echo "#ifndef PRIVSEP_USER" >>$CONFIG_H
+ echo "#define PRIVSEP_USER \"$PRIVSEP_USER\"" >>$CONFIG_H
+ echo "#endif" >>$CONFIG_H
+ echo "PRIVSEP_SRCS= privsep.c privsep-root.c" >>$CONFIG_MK
+ echo "PRIVSEP_SRCS+= privsep-control.c privsep-inet.c" >>$CONFIG_MK
+ if [ -z "$INET" ] || [ "$INET" = yes ]; then
+ echo "PRIVSEP_SRCS+= privsep-bpf.c" >>$CONFIG_MK
+ fi
+ case "$OS" in
+ linux*) echo "PRIVSEP_SRCS+= privsep-linux.c" >>$CONFIG_MK;;
+ solaris*|sunos*) echo "PRIVSEP_SRCS+= privsep-sun.c" >>$CONFIG_MK;;
+ *) echo "PRIVSEP_SRCS+= privsep-bsd.c" >>$CONFIG_MK;;
+ esac
+else
+ echo "PRIVSEP_SRCS=" >>$CONFIG_MK
+fi
+
+echo "Using compiler .. $CC"
+# Add CPPFLAGS and CFLAGS to CC for testing features
+XCC="$CC `$SED -n -e 's/CPPFLAGS+=*\(.*\)/\1/p' $CONFIG_MK`"
+XCC="$XCC `$SED -n -e 's/CFLAGS+=*\(.*\)/\1/p' $CONFIG_MK`"
+
+# When running tests, treat all warnings as errors.
+# This avoids the situation where we link to a libc symbol
+# without the correct header because it might be hidden behind
+# a _*_SOURCE #define guard.
+XCC="$XCC -Wall -Werror"
+
+# Now test we can use the compiler with our CFLAGS
+cat <<EOF >_test.c
+int main(void) {
+ return 0;
+}
+EOF
+_CC=false
+if $XCC _test.c -o _test >/dev/null 2>&3; then
+ [ -x _test ] && _CC=true
+fi
+rm -f _test.c _test
+if ! $_CC; then
+ echo $XCC
+ echo "$CC does not create executables" >&2
+ exit 1
+fi
+[ "$CC" != cc ] && echo "CC= $CC" >>$CONFIG_MK
+$CC --version | $SED -e '1!d'
+
+if [ "$PRIVSEP" = yes ]; then
+ printf "Testing for capsicum ... "
+ cat <<EOF >_capsicum.c
+#include <sys/capsicum.h>
+int main(void) {
+ return cap_enter();
+}
+EOF
+ if $XCC _capsicum.c -o _capsicum 2>&3; then
+ echo "yes"
+ echo "#define HAVE_CAPSICUM" >>$CONFIG_H
+ else
+ echo "no"
+ fi
+ rm -f _capsicum.c _capsicum
+
+ printf "Testing for pledge ... "
+ cat <<EOF >_pledge.c
+#include <unistd.h>
+int main(void) {
+ return pledge("stdio", NULL);
+}
+EOF
+ if $XCC _pledge.c -o _pledge 2>&3; then
+ echo "yes"
+ echo "#define HAVE_PLEDGE" >>$CONFIG_H
+ else
+ echo "no"
+ fi
+ rm -f _pledge.c _pledge
+fi
+
+# This block needs to be after the compiler test due to embedded quotes.
+if [ -z "$EMBEDDED" ] || [ "$EMBEDDED" = yes ]; then
+ echo "$DHCPCD_DEFS will be embedded in dhcpcd itself"
+ echo "DHCPCD_SRCS+= dhcpcd-embedded.c" >>$CONFIG_MK
+else
+ echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR"
+ echo "CPPFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK
+ echo "EMBEDDEDINSTALL= _embeddedinstall" >>$CONFIG_MK
+fi
+
+if [ "$OS" = linux ]; then
+ printf "Testing for nl80211 ... "
+ cat <<EOF >_nl80211.c
+#include <linux/nl80211.h>
+int main(void) {
+ return 0;
+}
+EOF
+ if $XCC _nl80211.c -o _nl80211 2>&3; then
+ echo "yes"
+ echo "#define HAVE_NL80211_H" >>$CONFIG_H
+ else
+ echo "no"
+ echo "DHCPCD_SRCS+= if-linux-wext.c" >>$CONFIG_MK
+ fi
+ rm -f _nl80211.c _nl80211
+
+ printf "Testing for IN6_ADDR_GEN_MODE_NONE ... "
+ cat <<EOF >_IN6_ADDR_GEN_MODE_NONE.c
+#include <linux/if_link.h>
+int main(void) {
+ int x = IN6_ADDR_GEN_MODE_NONE;
+ return x;
+}
+EOF
+ if $XCC _IN6_ADDR_GEN_MODE_NONE.c -o _IN6_ADDR_GEN_MODE_NONE 2>&3; then
+ echo "yes"
+ echo "#define HAVE_IN6_ADDR_GEN_MODE_NONE" >>$CONFIG_H
+ else
+ echo "no"
+ fi
+ rm -f _IN6_ADDR_GEN_MODE_NONE.c _IN6_ADDR_GEN_MODE_NONE
+else
+ printf "Testing for ifam_pid ... "
+ cat <<EOF >_ifam_pid.c
+#include <net/if.h>
+int main(void) {
+ struct ifa_msghdr ifam = { };
+ return (int)ifam.ifam_pid;
+}
+EOF
+ if $XCC _ifam_pid.c -o _ifam_pid 2>&3; then
+ echo "yes"
+ echo "#define HAVE_IFAM_PID" >>$CONFIG_H
+ else
+ echo "no"
+ fi
+ rm -f _ifam_pid.c _ifam_pid
+
+ printf "Testing for ifam_addrflags ... "
+ cat <<EOF >_ifam_addrflags.c
+#include <net/if.h>
+int main(void) {
+ struct ifa_msghdr ifam = { };
+ return (int)ifam.ifam_addrflags;
+}
+EOF
+ if $XCC _ifam_addrflags.c -o _ifam_addrflags 2>&3; then
+ echo "yes"
+ echo "#define HAVE_IFAM_ADDRFLAGS" >>$CONFIG_H
+ else
+ echo "no"
+ fi
+ rm -f _ifam_addrflags.c _ifam_addrflags
+fi
+
+abort=false
+# We require the libc to support non standard functions, like getifaddrs
+printf "Testing for getifaddrs ... "
+cat <<EOF >_getifaddrs.c
+#include <sys/types.h>
+#include <ifaddrs.h>
+int main(void) {
+ struct ifaddrs *ifap;
+ return getifaddrs(&ifap);
+}
+EOF
+LIBSOCKET=
+if $XCC _getifaddrs.c -o _getifaddrs 2>&3; then
+ echo "yes"
+elif $XCC _getifaddrs.c -o _getifaddrs -lsocket 2>&3; then
+ LIBSOCKET=-lsocket
+ echo "yes (-lsocket)"
+ echo "LDADD+= -lsocket" >>$CONFIG_MK
+else
+ echo "no"
+ echo "libc support for getifaddrs is required - aborting" >&2
+ abort=true
+fi
+rm -f _getifaddrs.c _getifaddrs
+$abort && exit 1
+
+printf "Testing for ifaddrs.ifa_addrflags ... "
+cat <<EOF >_getifaddrs_addrflags.c
+#include <sys/types.h>
+#include <ifaddrs.h>
+int main(void) {
+ struct ifaddrs *ifap;
+ getifaddrs(&ifap);
+ return (int)ifap->ifa_addrflags;
+}
+EOF
+if $XCC _getifaddrs_addrflags.c -o _getifaddrs_addrflags $LIBSOCKET 2>&3; then
+ echo "yes"
+ echo "#define HAVE_IFADDRS_ADDRFLAGS" >>$CONFIG_H
+else
+ echo "no"
+fi
+rm -f _getifaddrs_addrflags.c _getifaddrs_addrflags
+
+printf "Testing for clock_gettime ... "
+cat <<EOF >_clock_gettime.c
+#include <time.h>
+int main(void) {
+ struct timespec ts;
+ return clock_gettime(CLOCK_MONOTONIC, &ts);
+}
+EOF
+if $XCC _clock_gettime.c -o _clock_gettime 2>&3; then
+ echo "yes"
+elif $XCC _clock_gettime.c -lrt -o _clock_gettime 2>&3; then
+ echo "yes (-lrt)"
+ echo "LDADD+= -lrt" >>$CONFIG_MK
+else
+ echo "no"
+ echo "libc support for clock_getttime is required - aborting" >&2
+ abort=true
+fi
+rm -f _clock_gettime.c _clock_gettime
+$abort && exit 1
+
+printf "Testing ioctl request type ... "
+cat <<EOF >_ioctl.c
+#include <sys/ioctl.h>
+int main(void) {
+ unsigned long req = 0;
+ return ioctl(3, req, &req);
+}
+EOF
+if $XCC _ioctl.c -o _ioctl 2>&3; then
+ IOCTL_REQ="unsigned long"
+else
+ IOCTL_REQ="int"
+fi
+echo "$IOCTL_REQ"
+# Our default is unsigned long
+# We can still define it, but it makes the code path slightly bigger
+if [ "$IOCTL_REQ" != "unsigned long" ]; then
+ echo "#define IOCTL_REQUEST_TYPE $IOCTL_REQ" >>$CONFIG_H
+fi
+rm -f _ioctl.c _ioctl
+
+printf "Testing for inet_ntoa ... "
+cat <<EOF >_inet_ntoa.c
+#include <netinet/in.h>
+#include <arpa/inet.h>
+int main(void) {
+ struct in_addr in = { .s_addr = 0 };
+ inet_ntoa(in);
+ return 0;
+}
+EOF
+if $XCC _inet_ntoa.c -o _inet_ntoa 2>&3; then
+ echo "yes"
+elif $XCC _inet_ntoa.c -lnsl -o _inet_ntoa 2>&3; then
+ echo "yes (-lnsl)"
+ echo "LDADD+= -lnsl" >>$CONFIG_MK
+elif $XCC _inet_ntoa.c -lsocket -o _inet_ntoa 2>&3; then
+ echo "yes (-lsocket)"
+ echo "LDADD+= -lsocket" >>$CONFIG_MK
+else
+ echo "no"
+ echo "libc support for inet_ntoa is required - aborting" >&2
+ abort=true
+fi
+rm -f _inet_ntoa.c _inet_ntoa
+$abort && exit 1
+
+if [ -z "$ARC4RANDOM" ]; then
+ printf "Testing for arc4random ... "
+ cat <<EOF >_arc4random.c
+#include <stdlib.h>
+int main(void) {
+ arc4random();
+ return 0;
+}
+EOF
+ if $XCC _arc4random.c -o _arc4random 2>&3; then
+ ARC4RANDOM=yes
+ else
+ ARC4RANDOM=no
+ fi
+ echo "$ARC4RANDOM"
+ rm -f _arc4random.c _arc4random
+fi
+if [ "$ARC4RANDOM" = no ]; then
+ echo "COMPAT_SRCS+= compat/arc4random.c" >>$CONFIG_MK
+ echo "#include \"compat/arc4random.h\"" >>$CONFIG_H
+fi
+
+if [ -z "$ARC4RANDOM_UNIFORM" ]; then
+ printf "Testing for arc4random_uniform ... "
+ cat <<EOF >_arc4random_uniform.c
+#include <stdlib.h>
+int main(void) {
+ arc4random_uniform(100);
+ return 0;
+}
+EOF
+ if $XCC _arc4random_uniform.c -o _arc4random_uniform 2>&3; then
+ ARC4RANDOM_UNIFORM=yes
+ else
+ ARC4RANDOM_UNIFORM=no
+ fi
+ echo "$ARC4RANDOM_UNIFORM"
+ rm -f _arc4random_uniform.c _arc4random_uniform
+fi
+if [ "$ARC4RANDOM_UNIFORM" = no ]; then
+ echo "COMPAT_SRCS+= compat/arc4random_uniform.c" >>$CONFIG_MK
+ echo "#include \"compat/arc4random_uniform.h\"" >>$CONFIG_H
+fi
+
+if [ -z "$OPEN_MEMSTREAM" ]; then
+ printf "Testing for open_memstream ... "
+ cat <<EOF >_open_memstream.c
+#include <stdio.h>
+int main(void) {
+ return open_memstream(NULL, NULL) != NULL ? 0 : 1;
+}
+EOF
+ if $XCC _open_memstream.c -o _open_memstream 2>&3; then
+ OPEN_MEMSTREAM=yes
+ else
+ OPEN_MEMSTREAM=no
+ fi
+ echo "$OPEN_MEMSTREAM"
+ rm -f _open_memstream.c _open_memstream
+fi
+if [ "$OPEN_MEMSTREAM" = yes ]; then
+ echo "#define HAVE_OPEN_MEMSTREAM" >>$CONFIG_H
+elif [ "$PRIVSEP" = yes ]; then
+ echo "WARNING: Ensure that /tmp exists in the privsep users chroot"
+fi
+
+if [ -z "$PIDFILE_LOCK" ]; then
+ printf "Testing for pidfile_lock ... "
+ cat <<EOF >_pidfile.c
+#include <stdlib.h>
+#include <util.h>
+int main(void) {
+ pidfile_lock(NULL);
+ return 0;
+}
+EOF
+ # We only want to link to libutil if it exists in /lib
+ if $ALLOW_USR_LIBS; then
+ set -- /
+ else
+ set -- $(ls /lib/libutil.so.* 2>/dev/null)
+ fi
+ if $XCC _pidfile.c -o _pidfile 2>&3; then
+ PIDFILE_LOCK=yes
+ elif [ -e "$1" ] && $XCC _pidfile.c -o _pidfile -lutil 2>&3; then
+ PIDFILE_LOCK="yes (-lutil)"
+ LIBUTIL="-lutil"
+ else
+ PIDFILE_LOCK=no
+ fi
+ echo "$PIDFILE_LOCK"
+ rm -f _pidfile.c _pidfile
+fi
+if [ "$PIDFILE_LOCK" = no ]; then
+ echo "COMPAT_SRCS+= compat/pidfile.c" >>$CONFIG_MK
+ echo "#include \"compat/pidfile.h\"" >>$CONFIG_H
+else
+ echo "#define HAVE_UTIL_H" >>$CONFIG_H
+ if [ -n "$LIBUTIL" ]; then
+ echo "LDADD+= $LIBUTIL" >>$CONFIG_MK
+ fi
+fi
+
+if [ -z "$SETPROCTITLE" ]; then
+ printf "Testing for setproctitle ... "
+ cat << EOF >_setproctitle.c
+#include <stdlib.h>
+#include <unistd.h>
+int main(void) {
+ setproctitle("foo");
+ return 0;
+}
+EOF
+ if $XCC _setproctitle.c -o _setproctitle 2>&3; then
+ SETPROCTITLE=yes
+ else
+ SETPROCTITLE=no
+ fi
+ echo "$SETPROCTITLE"
+ rm -f _setproctitle.c _setproctitle
+fi
+if [ "$SETPROCTITLE" = no ]; then
+ case "$OS" in
+ solaris*|sunos*)
+ echo "$OS has no support for setting process title"
+ echo "#define setproctitle(...)" >>$CONFIG_H
+ ;;
+ *)
+ echo "COMPAT_SRCS+= compat/setproctitle.c" >>$CONFIG_MK
+ echo "#include \"compat/setproctitle.h\"" \
+ >>$CONFIG_H
+ ;;
+ esac
+fi
+
+if [ -z "$STRLCPY" ]; then
+ printf "Testing for strlcpy ... "
+ cat <<EOF >_strlcpy.c
+#include <string.h>
+int main(void) {
+ const char s1[] = "foo";
+ char s2[10];
+ strlcpy(s2, s1, sizeof(s2));
+ return 0;
+}
+EOF
+ if $XCC _strlcpy.c -o _strlcpy 2>&3; then
+ STRLCPY=yes
+ else
+ STRLCPY=no
+ fi
+ echo "$STRLCPY"
+ rm -f _strlcpy.c _strlcpy
+fi
+if [ "$STRLCPY" = no ]; then
+ echo "COMPAT_SRCS+= compat/strlcpy.c" >>$CONFIG_MK
+ echo "#include \"compat/strlcpy.h\"" >>$CONFIG_H
+fi
+
+if [ -z "$STRTOI" ]; then
+ printf "Testing for strtoi ... "
+ cat <<EOF >_strtoi.c
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+int main(void) {
+ int e;
+ strtoi("1234", NULL, 0, 0, INT32_MAX, &e);
+ return 0;
+}
+EOF
+ if $XCC _strtoi.c -o _strtoi 2>&3; then
+ STRTOI=yes
+ else
+ STRTOI=no
+ fi
+ echo "$STRTOI"
+ rm -f _strtoi.c _strtoi
+fi
+if [ "$STRTOI" = no ]; then
+ echo "COMPAT_SRCS+= compat/strtoi.c compat/strtou.c" >>$CONFIG_MK
+ echo "#include \"compat/strtoi.h\"" >>$CONFIG_H
+fi
+
+if [ -z "$CONSTTIME_MEMEQUAL" ]; then
+ printf "Testing for consttime_memequal ... "
+ cat <<EOF >_consttime_memequal.c
+#include <string.h>
+int main(void) {
+ return consttime_memequal("deadbeef", "deadbeef", 8);
+}
+EOF
+ if $XCC _consttime_memequal.c -o _consttime_memequal 2>&3; then
+ CONSTTIME_MEMEQUAL=yes
+ else
+ CONSTTIME_MEMEQUAL=no
+ fi
+ echo "$CONSTTIME_MEMEQUAL"
+ rm -f _consttime_memequal.c _consttime_memequal
+fi
+if [ "$CONSTTIME_MEMEQUAL" = no ]; then
+ echo "#include \"compat/consttime_memequal.h\"" \
+ >>$CONFIG_H
+fi
+
+if [ -z "$DPRINTF" ]; then
+ printf "Testing for dprintf ... "
+ cat <<EOF >_dprintf.c
+#include <stdio.h>
+int main(void) {
+ return dprintf(0, "%d", 0);
+}
+EOF
+ if $XCC _dprintf.c -o _dprintf 2>&3; then
+ DPRINTF=yes
+ else
+ DPRINTF=no
+ fi
+ echo "$DPRINTF"
+ rm -f _dprintf.c _dprintf
+fi
+if [ "$DPRINTF" = no ]; then
+ echo "COMPAT_SRCS+= compat/dprintf.c" >>$CONFIG_MK
+ echo "#include \"compat/dprintf.h\"" >>$CONFIG_H
+fi
+
+if [ -z "$TAILQ_FOREACH_SAFE" ]; then
+ printf "Testing for TAILQ_FOREACH_SAFE ... "
+ cat <<EOF >_queue.c
+#include <sys/queue.h>
+int main(void) {
+#ifndef TAILQ_FOREACH_SAFE
+#error TAILQ_FOREACH_SAFE
+#endif
+ return 0;
+}
+EOF
+ if $XCC _queue.c -o _queue 2>&3; then
+ TAILQ_FOREACH_SAFE=yes
+ TAILQ_FOREACH=yes
+ else
+ TAILQ_FOREACH_SAFE=no
+ fi
+ echo "$TAILQ_FOREACH_SAFE"
+ rm -f _queue.c _queue
+fi
+if [ "$TAILQ_FOREACH_SAFE" = no ] && [ -z "$TAILQ_FOREACH_MUTABLE" ]; then
+ printf "Testing for TAILQ_FOREACH_MUTABLE ... "
+ cat <<EOF >_queue.c
+#include <sys/queue.h>
+int main(void) {
+#ifndef TAILQ_FOREACH_MUTABLE
+#error TAILQ_FOREACH_MUTABLE
+#endif
+ return 0;
+}
+EOF
+ if $XCC _queue.c -o _queue 2>&3; then
+ TAILQ_FOREACH_MUTABLE=yes
+ TAILQ_FOREACH_SAFE=yes
+ TAILQ_FOREACH=yes
+ echo "#define TAILQ_FOREACH_SAFE TAILQ_FOREACH_MUTABLE" \
+ >> $CONFIG_H
+ else
+ TAILQ_FOREACH_MUTABLE=no
+ fi
+ echo "$TAILQ_FOREACH_MUTABLE"
+ rm -f _queue.c _queue
+fi
+
+
+if [ -z "$TAILQ_CONCAT" ]; then
+ printf "Testing for TAILQ_CONCAT ..."
+ cat <<EOF >_queue.c
+#include <sys/queue.h>
+int main(void) {
+#ifndef TAILQ_CONCAT
+#error TAILQ_CONCAT
+#endif
+ return 0;
+}
+EOF
+ if $XCC _queue.c -o _queue 2>&3; then
+ TAILQ_CONCAT=yes
+ TAILQ_FOREACH=yes
+ else
+ TAILQ_CONCAT=no
+ fi
+ echo "$TAILQ_CONCAT"
+ rm -f _queue.c _queue
+fi
+
+if [ -z "$TAILQ_FOREACH" ]; then
+ printf "Testing for TAILQ_FOREACH ... "
+ cat <<EOF >_queue.c
+#include <sys/queue.h>
+int main(void) {
+#ifndef TAILQ_FOREACH
+#error TAILQ_FOREACH
+#endif
+ return 0;
+}
+EOF
+ if $XCC _queue.c -o _queue 2>&3; then
+ TAILQ_FOREACH=yes
+ else
+ TAILQ_FOREACH=no
+ fi
+ echo "$TAILQ_FOREACH"
+ rm -f _queue.c _queue
+fi
+
+if [ "$TAILQ_FOREACH_SAFE" = no ] || [ "$TAILQ_CONCAT" = no ]; then
+ # If we don't include sys/queue.h then clang analyser finds
+ # too many false positives.
+ # See http://llvm.org/bugs/show_bug.cgi?id=18222
+ # Strictly speaking this isn't needed, but I like it to help
+ # catch any nasties.
+ if [ "$TAILQ_FOREACH" = yes ]; then
+ echo "#include <sys/queue.h>">>$CONFIG_H
+ fi
+ echo "#include \"compat/queue.h\"">>$CONFIG_H
+else
+ echo "#define HAVE_SYS_QUEUE_H" >>$CONFIG_H
+fi
+
+if [ -z "$RBTREE" ]; then
+ printf "Testing for rb_tree_init ... "
+ cat <<EOF >_rbtree.c
+#include <sys/rbtree.h>
+static rb_tree_ops_t ops;
+int main(void) {
+ rb_tree_t tree;
+ rb_tree_init(&tree, &ops);
+ return 0;
+}
+EOF
+ if $XCC _rbtree.c -o _rbtree 2>&3; then
+ RBTREE=yes
+ else
+ RBTREE=no
+ fi
+ echo "$RBTREE"
+ rm -f _rbtree.c _rbtree
+fi
+if [ "$RBTREE" = no ]; then
+ echo "#define RBTEST" >>$CONFIG_H
+ echo "COMPAT_SRCS+= compat/rb.c" >>$CONFIG_MK
+ echo "#include \"compat/rbtree.h\"" >>$CONFIG_H
+else
+ echo "#define HAVE_SYS_RBTREE_H" >>$CONFIG_H
+fi
+
+if [ -z "$REALLOCARRAY" ]; then
+ printf "Testing for reallocarray ... "
+ cat <<EOF >_reallocarray.c
+#include <stdlib.h>
+
+int main(void) {
+ void *foo = reallocarray(NULL, 0, 0);
+ return foo == NULL ? 1 : 0;
+}
+EOF
+ if $XCC _reallocarray.c -o _reallocarray 2>&3; then
+ REALLOCARRAY=yes
+ else
+ REALLOCARRAY=no
+ fi
+ echo "$REALLOCARRAY"
+ rm -f _reallocarray.c _reallocarray
+fi
+if [ "$REALLOCARRAY" = no ]; then
+ echo "COMPAT_SRCS+= compat/reallocarray.c" >>$CONFIG_MK
+ echo "#include \"compat/reallocarray.h\"">>$CONFIG_H
+fi
+# Set this for eloop
+echo "#define HAVE_REALLOCARRAY" >>$CONFIG_H
+
+if [ -z "$POLL" ]; then
+ printf "Testing for ppoll ... "
+ cat <<EOF >_ppoll.c
+#include <poll.h>
+#include <stddef.h>
+int main(void) {
+ struct pollfd fds;
+ return ppoll(&fds, 1, NULL, NULL);
+}
+EOF
+ if $XCC _ppoll.c -o _ppoll 2>&3; then
+ POLL=ppoll
+ echo "yes"
+ else
+ echo "no"
+ fi
+ rm -f _ppoll.c _ppoll
+fi
+if [ -z "$POLL" ]; then
+ printf "Testing for pollts ... "
+ cat <<EOF >_pollts.c
+#include <poll.h>
+#include <stddef.h>
+int main(void) {
+ struct pollfd fds;
+ return pollts(&fds, 1, NULL, NULL);
+}
+EOF
+ if $XCC _pollts.c -o _pollts 2>&3; then
+ POLL=pollts
+ echo "yes"
+ else
+ echo "no"
+ fi
+ rm -f _pollts.c _pollts
+fi
+if [ -z "$POLL" ]; then
+ printf "Testing for pselect ... "
+ cat <<EOF >_pselect.c
+#include <sys/select.h>
+#include <stdlib.h>
+int main(void) {
+ pselect(0, NULL, NULL, NULL, NULL, NULL);
+ return 0;
+}
+EOF
+ if $XCC _pselect.c -o _pselect 2>&3; then
+ POLL=pselect
+ echo "yes"
+ else
+ echo "no"
+ fi
+ rm -f _pselect.c _pselect
+fi
+case "$POLL" in
+ppoll)
+ echo "#define HAVE_PPOLL" >>$CONFIG_H
+ ;;
+pollts)
+ echo "#define HAVE_POLLTS" >>$CONFIG_H
+ ;;
+pselect)
+ echo "#define HAVE_PSELECT" >>$CONFIG_H
+ ;;
+*)
+ echo "No suitable polling function is available, not even pselect" >&2
+ exit 1
+ ;;
+esac
+
+if [ -z "$BE64ENC" ]; then
+ printf "Testing for be64enc ... "
+ cat <<EOF >_be64enc.c
+#include <sys/endian.h>
+#include <stdlib.h>
+int main(void) {
+ be64enc(NULL, 0);
+ return 0;
+}
+EOF
+ if $XCC _be64enc.c -o _be64enc 2>&3; then
+ BE64ENC=yes
+ else
+ BE64ENC=no
+ fi
+ echo "$BE64ENC"
+ rm -f _be64enc.c _be64enc
+fi
+if [ "$BE64ENC" = no ]; then
+ echo "#include \"compat/endian.h\"" >>$CONFIG_H
+fi
+
+if [ -z "$FLS64" ]; then
+ printf "Testing for fls64 ... "
+ cat <<EOF >_fls64.c
+#include <sys/bitops.h>
+int main(void) {
+ return (int)fls64(1337);
+}
+EOF
+ if $XCC _fls64.c -o _fls64 2>&3; then
+ FLS64=yes
+ else
+ FLS64=no
+ fi
+ echo "$FLS64"
+ rm -f _fls64.c _fls64
+fi
+if [ "$FLS64" = yes ]; then
+ echo "#define HAVE_SYS_BITOPS_H" >>$CONFIG_H
+fi
+
+# Workaround for DragonFlyBSD import
+if [ "$OS" = dragonfly ]; then
+ echo "#ifdef USE_PRIVATECRYPTO" >>$CONFIG_H
+ echo "#define HAVE_MD5_H" >>$CONFIG_H
+ echo "#define SHA2_H <openssl/sha.h>" >>$CONFIG_H
+ echo "#else" >>$CONFIG_H
+fi
+
+if [ -z "$MD5" ]; then
+ MD5_LIB=
+ printf "Testing for MD5Init ... "
+ cat <<EOF >_md5.c
+#include <sys/types.h>
+#include <md5.h>
+#include <stdlib.h>
+int main(void) {
+ MD5_CTX context;
+ MD5Init(&context);
+ return 0;
+}
+EOF
+ # We only want to link to libmd if it exists in /lib
+ if $ALLOW_USR_LIBS; then
+ set -- /
+ else
+ set -- $(ls /lib/libmd.so.* 2>/dev/null)
+ fi
+ if $XCC _md5.c -o _md5 2>&3; then
+ MD5=yes
+ elif [ -e "$1" ] && $XCC _md5.c -lmd -o _md5 2>&3; then
+ MD5="yes (-lmd)"
+ MD5_LIB=-lmd
+ else
+ MD5=no
+ fi
+ echo "$MD5"
+ rm -f _md5.c _md5
+fi
+if [ "$MD5" = no ]; then
+ echo "#include \"compat/crypt/md5.h\"" >>$CONFIG_H
+ echo "MD5_SRC= compat/crypt/md5.c" >>$CONFIG_MK
+else
+ echo "MD5_SRC=" >>$CONFIG_MK
+ echo "#define HAVE_MD5_H" >>$CONFIG_H
+ [ -n "$MD5_LIB" ] && echo "LDADD+= $MD5_LIB" >>$CONFIG_MK
+fi
+
+if [ -z "$SHA2_H" ]; then
+ if [ -z "$SHA2" ] || [ "$SHA2" != no ]; then
+ printf "Testing for sha2.h ... "
+ if [ -e /usr/include/sha2.h ]; then
+ SHA2_H=sha2.h
+ elif [ -e /usr/include/sha256.h ]; then
+ SHA2_H=sha256.h
+ fi
+ if [ -n "$SHA2_H" ]; then
+ echo "$SHA2_H"
+ else
+ echo "no"
+ fi
+ fi
+fi
+
+if [ -z "$SHA2" ]; then
+ SHA2_LIB=
+ SHA2_RENAMED=
+ printf "Testing for SHA256_Init ... "
+ cat <<EOF >_sha256.c
+#include <sys/types.h>
+EOF
+ [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c
+ cat <<EOF >>_sha256.c
+#include <stdlib.h>
+int main(void) {
+ SHA256_CTX context;
+ SHA256_Init(&context);
+ return 0;
+}
+EOF
+ # We only want to link to libmd if it exists in /lib
+ if $ALLOW_USR_LIBS; then
+ set -- /
+ else
+ set -- $(ls /lib/libmd.so.* 2>/dev/null)
+ fi
+ if $XCC _sha256.c -o _sha256 2>&3; then
+ SHA2=yes
+ elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3; then
+ SHA2="yes (-lmd)"
+ SHA2_LIB=-lmd
+ else
+ SHA2=no
+ fi
+ echo "$SHA2"
+ rm -f _sha256.c _sha256
+ if [ "$SHA2" = no ]; then
+ # Did OpenBSD really change this? grrrr
+ printf "Testing for SHA256Init ... "
+ cat <<EOF >_sha256.c
+#include <sys/types.h>
+EOF
+ [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c
+ cat <<EOF >>_sha256.c
+#include <stdlib.h>
+int main(void) {
+ SHA2_CTX context;
+ SHA256Init(&context);
+ return 0;
+}
+EOF
+ # We only want to link to libmd if it exists in /lib
+ if $ALLOW_USR_LIBS; then
+ set -- /
+ else
+ set -- $(ls /lib/libmd.so.* 2>/dev/null)
+ fi
+ if $XCC _sha256.c -o _sha256 2>&3; then
+ SHA2=yes
+ SHA2_RENAMED=yes
+ elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3
+ then
+ SHA2="yes (-lmd)"
+ SHA2_LIB=-lmd
+ SHA2_RENAMED=yes
+ else
+ SHA2=no
+ fi
+ echo "$SHA2"
+ rm -f _sha256.c _sha256
+ fi
+fi
+if [ "$SHA2" = no ]; then
+ echo "#include \"compat/crypt/sha256.h\"" >>$CONFIG_H
+ echo "SHA256_SRC= compat/crypt/sha256.c" >>$CONFIG_MK
+else
+ echo "SHA256_SRC=" >>$CONFIG_MK
+ echo "#define SHA2_H <$SHA2_H>" >>$CONFIG_H
+ if [ "$SHA2_RENAMED" = yes ]; then
+ echo "#define SHA256_CTX SHA2_CTX" >>$CONFIG_H
+ echo "#define SHA256_Init SHA256Init" >>$CONFIG_H
+ echo "#define SHA256_Update SHA256Update" >>$CONFIG_H
+ echo "#define SHA256_Final SHA256Final" >>$CONFIG_H
+ fi
+ [ -n "$SHA2_LIB" ] && echo "LDADD+= $SHA2_LIB" >>$CONFIG_MK
+fi
+
+# Workarond for DragonFlyBSD import
+[ "$OS" = dragonfly ] && echo "#endif" >>$CONFIG_H
+
+if [ -z "$HMAC" ]; then
+ HMAC_LIB=
+ printf "Testing for hmac ... "
+ cat <<EOF >_hmac.c
+#include <stdlib.h>
+#include <hmac.h>
+int main(void) {
+ hmac(NULL, NULL, 0, NULL, 0, NULL, 0);
+ return 0;
+}
+EOF
+ if $XCC _hmac.c $MD5_LIB -o _hmac 2>&3; then
+ HMAC=yes
+ echo "#define HAVE_HMAC_H" >>$CONFIG_H
+ else
+ # Remove this test if NetBSD-8 ships with
+ # hmac in it's own header and not stdlib.h
+ cat <<EOF >_hmac.c
+#include <stdlib.h>
+int main(void) {
+ hmac(NULL, NULL, 0, NULL, 0, NULL, 0);
+ return 0;
+}
+EOF
+ if $XCC _hmac.c $MD5_LIB -o _hmac 2>&3; then
+ HMAC=yes
+ else
+ HMAC=no
+ fi
+ fi
+ echo "$HMAC"
+ rm -f _hmac.c _hmac
+fi
+if [ "$HMAC" = no ]; then
+ echo "#include \"compat/crypt/hmac.h\"" >>$CONFIG_H
+ echo "HMAC_SRC= compat/crypt/hmac.c" >>$CONFIG_MK
+else
+ # echo "#define HAVE_HMAC_H" >>$CONFIG_H
+ echo "HMAC_SRC=" >>$CONFIG_MK
+fi
+
+if [ -z "$AUTH" ] || [ "$AUTH" = yes ]; then
+ if [ "$HMAC" = no ]; then
+ echo "CRYPT_SRCS+= \${HMAC_SRC}" >>$CONFIG_MK
+ fi
+fi
+if [ -z "$INET6" ] || [ "$INET6" = yes ] || \
+ [ -z "$AUTH" ] || [ "$AUTH" = yes ]; then
+ if [ "$MD5" = no ]; then
+ echo "CRYPT_SRCS+= \${MD5_SRC}" >>$CONFIG_MK
+ fi
+ if [ "$SHA2" = no ]; then
+ echo "CRYPT_SRCS+= \${SHA256_SRC}" >>$CONFIG_MK
+ fi
+fi
+
+if [ "$DEV" != no ] && [ "$UDEV" != no ]; then
+ printf "Checking for libudev ... "
+ if type "$PKG_CONFIG" >/dev/null 2>&1; then
+ LIBUDEV_CFLAGS=$("$PKG_CONFIG" --cflags libudev 2>&3)
+ LIBUDEV_LIBS=$("$PKG_CONFIG" --libs libudev 2>&3)
+ fi
+ if [ -n "$LIBUDEV_LIBS" ] && [ "$UDEV" = yes ]; then
+ echo "yes"
+ elif [ -n "$LIBUDEV_LIBS" ]; then
+ case "$OS" in
+ linux*) echo "yes";;
+ *) echo "yes (disabled)"
+ # FreeBSD libudev fails to return a udev device
+ # with udev_device_new_from_subsystem_sysname
+ # which breaks our test for device initialisation
+ LIBUDEV_CFLAGS=
+ LIBUDEV_LIBS=
+ ;;
+ esac
+ else
+ echo "no"
+ fi
+fi
+
+if [ "$DEV" != no ] && [ "$UDEV" != no ] && [ -n "$LIBUDEV_LIBS" ]; then
+ [ -z "$DEV" ] && DEV=yes
+ echo "DEV_PLUGINS+= udev" >>$CONFIG_MK
+ if [ -n "$LIBUDEV_CFLAGS" ]; then
+ echo "LIBUDEV_CFLAGS= $LIBUDEV_CFLAGS" >>$CONFIG_MK
+ fi
+ echo "LIBUDEV_LIBS= $LIBUDEV_LIBS" >>$CONFIG_MK
+
+ printf "Checking udev_monitor_filter_add_match_subsystem_devtype ... "
+ cat <<EOF >_udev.c
+#include <libudev.h>
+#include <stdlib.h>
+int main(void) {
+ udev_monitor_filter_add_match_subsystem_devtype(NULL, NULL, NULL);
+ return 0;
+}
+EOF
+ if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3
+ then
+ echo "yes"
+ else
+ echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOFILTER" >>$CONFIG_MK
+ echo "no"
+ fi
+ rm -f _udev.c _udev
+
+ printf "Checking udev_device_get_is_initialized ... "
+ cat <<EOF >_udev.c
+#include <libudev.h>
+#include <stdlib.h>
+int main(void) {
+ udev_device_get_is_initialized(NULL);
+ return 0;
+}
+EOF
+ if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3
+ then
+ echo "yes"
+ else
+ echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOINIT" >>$CONFIG_MK
+ echo "no"
+ fi
+ rm -f _udev.c _udev
+elif [ "$DEV" != no ] && [ "$UDEV" != no ] && [ -n "$UDEV" ]; then
+ echo "udev has been explicitly requested ... aborting" >&2
+ exit 1
+fi
+
+if [ "$DEV" = yes ]; then
+ echo "DHCPCD_SRCS+= dev.c" >>$CONFIG_MK
+ echo "CPPFLAGS+= -DPLUGIN_DEV" >>$CONFIG_MK
+ echo "MKDIRS+= dev" >>$CONFIG_MK
+
+ # So the plugins have access to logerr
+ echo "LDFLAGS+= -Wl,-export-dynamic" >>$CONFIG_MK
+
+ printf "Testing for dlopen ... "
+ cat <<EOF >_dlopen.c
+#include <dlfcn.h>
+#include <stdlib.h>
+int main(void) {
+ void *h = dlopen("/foo/bar", 0);
+ void (*s)(char *) = dlsym(h, "deadbeef");
+ s(dlerror());
+ dlclose(h);
+ return 0;
+}
+EOF
+ if $XCC _dlopen.c -o _dlopen 2>&3; then
+ echo "yes"
+ elif $XCC _dlopen.c -ldl -o _dlopen 2>&3; then
+ echo "yes (-ldl)"
+ echo "LDADD+= -ldl" >>$CONFIG_MK
+ else
+ echo "no"
+ echo "libc for dlopen is required - aborting"
+ fi
+ rm -f _dlopen.c _dlopen
+ $abort && exit 1
+fi
+
+# Transform for a make file
+SERVICEEXISTS=$(echo "$SERVICEEXISTS" | $SED \
+ -e 's:\\:\\\\:g' \
+ -e 's:\&:\\\&:g' \
+ -e 's:\$:\\\\\$\$:g' \
+)
+echo "SERVICEEXISTS= $SERVICEEXISTS" >>config.mk
+SERVICECMD=$(echo "$SERVICECMD" | $SED \
+ -e 's:\\:\\\\:g' \
+ -e 's:\&:\\\&:g' \
+ -e 's:\$:\\\\\$\$:g' \
+)
+echo "SERVICECMD= $SERVICECMD" >>config.mk
+SERVICESTATUS=$(echo "$SERVICESTATUS" | $SED \
+ -e 's:\\:\\\\:g' \
+ -e 's:\&:\\\&:g' \
+ -e 's:\$:\\\\\$\$:g' \
+)
+echo "SERVICESTATUS= $SERVICESTATUS" >>config.mk
+if [ -z "$STATUSARG" ]; then
+ case "$OS" in
+ dragonfly*|freebsd*) STATUSARG="onestatus";;
+ esac
+fi
+echo "STATUSARG= $STATUSARG" >>config.mk
+
+HOOKS=
+if ! $HOOKSET; then
+ printf "Checking for ntpd ... "
+ NTPD=$(_which ntpd)
+ if [ -n "$NTPD" ]; then
+ echo "$NTPD (50-ntp.conf)"
+ else
+ echo "not found"
+ fi
+ printf "Checking for chronyd ... "
+ CHRONYD=$(_which chronyd)
+ if [ -n "$CHRONYD" ]; then
+ echo "$CHRONYD (50-ntp.conf)"
+ else
+ echo "not found"
+ fi
+ if [ -n "$NTPD" ] || [ -n "$CHRONYD" ]; then
+ HOOKS="$HOOKS${HOOKS:+ }50-ntp.conf"
+ fi
+ # Warn if both are detected
+ if [ -n "$NTPD" ] && [ -n "$CHRONYD" ]; then
+ echo "NTP will default to $NTPD"
+ fi
+
+ printf "Checking for ypbind ... "
+ YPBIND=$(_which ypbind)
+ if [ -n "$YPBIND" ]; then
+ YPHOOK="50-ypbind"
+ if strings "$YPBIND" | $GREP -q yp\\.conf; then
+ YPHOOK="50-yp.conf"
+ YPOS="Linux"
+ elif strings "$YPBIND" | $GREP -q \\.ypservers; then
+ YPOS="NetBSD"
+ echo "YPDOMAIN_DIR= /var/yp" >>$CONFIG_MK
+ echo "YPDOMAIN_SUFFIX=.ypservers" >>$CONFIG_MK
+ elif strings "$YPBIND" | $GREP -q /etc/yp; then
+ YPOS="OpenBSD"
+ echo "YPDOMAIN_DIR= /etc/yp" >>$CONFIG_MK
+ echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK
+ else
+ YPOS="FreeBSD"
+ echo "YPDOMAIN_DIR=" >>$CONFIG_MK
+ echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK
+ fi
+ echo "$YPBIND ($YPHOOK${YPOS:+ }$YPOS)"
+ EGHOOKS="$EGHOOKS${EGHOOKS:+ }$YPHOOK"
+ else
+ echo "not found"
+ YPHOOK="50-ypbind"
+ case "$OS" in
+ linux*)
+ YPHOOK="50-yp.conf"
+ YPOS="Linux"
+ ;;
+ freebsd*|dragonfly*)
+ YPOS="FreeBSD"
+ echo "YPDOMAIN_DIR=" >>$CONFIG_MK
+ echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK
+ ;;
+ netbsd*)
+ YPOS="NetBSD"
+ echo "YPDOMAIN_DIR= /var/yp" >>$CONFIG_MK
+ echo "YPDOMAIN_SUFFIX=.ypservers" >>$CONFIG_MK
+ ;;
+ openbsd*)
+ YPOS="OpenBSD"
+ echo "YPDOMAIN_DIR= /etc/yp" >>$CONFIG_MK
+ echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK
+ ;;
+ *)
+ YPHOOK=
+ ;;
+ esac
+ if [ -n "$YPHOOK" ]; then
+ echo "Assuming ypbind is $YPOS"
+ EGHOOKS="$EGHOOKS${EGHOOKS:+ }$YPHOOK"
+ fi
+ fi
+fi
+
+find_hook()
+{
+ for h in [0-9][0-9]"-$x" [0-9][0-9]"-$x.in" \
+ [0-9][0-9]"-$x.sh" [0-9][0-9]"-$x.sh.in" \
+ [0-9][0-9]"-$x.conf" [0-9][0-9]"-$x.conf.in"
+ do
+ [ -e "$h" ] && break
+ done
+ [ -e "$h" ] || return 1
+ echo "${h%%.in}"
+ return 0
+}
+
+if cd hooks; then
+ for x in $HOOKSCRIPTS; do
+ printf "Finding hook $x ... "
+ h=$(find_hook "$x")
+ if [ -z "$h" ]; then
+ echo "no"
+ else
+ echo "$h"
+ case " $HOOKS " in
+ *" $h "*) ;;
+ *) HOOKS="$HOOKS${HOOKS:+ }$h";;
+ esac
+ fi
+ done
+ for x in $EGHOOKSCRIPTS; do
+ printf "Finding example hook $x ... "
+ h=$(find_hook "$x")
+ if [ -z "$h" ]; then
+ echo "no"
+ else
+ echo "$h"
+ case " $EGHOOKS " in
+ *" $h "*) ;;
+ *) EGHOOKS="$EGHOOKS${EGHOOKS:+ }$h";;
+ esac
+ fi
+ done
+ cd ..
+fi
+echo "HOOKSCRIPTS= $HOOKS" >>$CONFIG_MK
+echo "EGHOOKSCRIPTS= $EGHOOKS" >>$CONFIG_MK
+
+echo
+echo " SYSCONFDIR = $SYSCONFDIR"
+echo " SBINDIR = $SBINDIR"
+echo " LIBDIR = $LIBDIR"
+echo " LIBEXECDIR = $LIBEXECDIR"
+echo " DBDIR = $DBDIR"
+echo " RUNDIR = $RUNDIR"
+echo " MANDIR = $MANDIR"
+echo " DATADIR = $DATADIR"
+echo " HOOKSCRIPTS = $HOOKS"
+echo " EGHOOKSCRIPTS = $EGHOOKS"
+echo " STATUSARG = $STATUSARG"
+if [ "$PRIVSEP" = yes ]; then
+ echo " PRIVSEPUSER = $PRIVSEP_USER"
+fi
+echo
+
+rm -f dhcpcd tests/test
diff --git a/hooks/01-test b/hooks/01-test
new file mode 100644
index 000000000000..99499425e00d
--- /dev/null
+++ b/hooks/01-test
@@ -0,0 +1,37 @@
+# Echo the interface flags, reason and message options
+
+if [ "$reason" = "TEST" ]; then
+ # General variables at the top
+ set | while read line; do
+ case "$line" in
+ interface=*|pid=*|reason=*|protocol=*|profile=*|skip_hooks=*)
+ echo "$line";;
+ esac
+ done
+ # Interface flags
+ set | while read line; do
+ case "$line" in
+ ifcarrier=*|ifflags=*|ifmetric=*|ifmtu=*|ifwireless=*|ifssid=*)
+ echo "$line";;
+ esac
+ done
+ # Old lease
+ set | while read line; do
+ case "$line" in
+ old_*) echo "$line";;
+ esac
+ done
+ # New lease
+ set | while read line; do
+ case "$line" in
+ new_*) echo "$line";;
+ esac
+ done
+ # Router Advertisements
+ set | while read line; do
+ case "$line" in
+ nd[0-9]*_*) echo "$line";;
+ esac
+ done
+ exit 0
+fi
diff --git a/hooks/10-wpa_supplicant b/hooks/10-wpa_supplicant
new file mode 100644
index 000000000000..04acce088840
--- /dev/null
+++ b/hooks/10-wpa_supplicant
@@ -0,0 +1,113 @@
+# Start, reconfigure and stop wpa_supplicant per wireless interface.
+#
+# This is only needed when using wpa_supplicant-2.5 or older, OR
+# when wpa_supplicant has not been built with CONFIG_MATCH_IFACE, OR
+# wpa_supplicant was launched without the -M flag to activate
+# interface matching.
+
+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()
+{
+ 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()
+{
+ # 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()
+{
+ 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()
+{
+ 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 stop 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
diff --git a/hooks/15-timezone b/hooks/15-timezone
new file mode 100644
index 000000000000..3d5173286335
--- /dev/null
+++ b/hooks/15-timezone
@@ -0,0 +1,47 @@
+# Configure timezone
+
+: ${localtime:=/etc/localtime}
+
+set_zoneinfo()
+{
+ [ -z "$new_tzdb_timezone" ] && return 0
+
+ zoneinfo_dir=
+ 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_configured && $if_up; then
+ set_zoneinfo
+fi
diff --git a/hooks/20-resolv.conf b/hooks/20-resolv.conf
new file mode 100644
index 000000000000..504a6c5373a4
--- /dev/null
+++ b/hooks/20-resolv.conf
@@ -0,0 +1,224 @@
+# 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"
+nocarrier_roaming_dir="$state_dir/roaming"
+NL="
+"
+: ${resolvconf:=resolvconf}
+if type "$resolvconf" >/dev/null 2>&1; then
+ have_resolvconf=true
+else
+ have_resolvconf=false
+fi
+
+build_resolv_conf()
+{
+ cf="$state_dir/resolv.conf.$ifname"
+
+ # Build a list of interfaces
+ interfaces=$(list_interfaces "$resolv_conf_dir")
+
+ # Build the resolv.conf
+ header=
+ 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
+# Obey the lifetimes
+eval_nd_dns()
+{
+
+ eval rdnsstime=\$nd${i}_rdnss${j}_lifetime
+ [ -z "$rdnsstime" ] && return 1
+ ltime=$(($rdnsstime - $offset))
+ if [ "$ltime" -gt 0 ]; then
+ eval rdnss=\$nd${i}_rdnss${j}_servers
+ [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss"
+ fi
+
+ eval dnssltime=\$nd${i}_dnssl${j}_lifetime
+ [ -z "$dnssltime" ] && return 1
+ ltime=$(($dnssltime - $offset))
+ if [ "$ltime" -gt 0 ]; then
+ eval dnssl=\$nd${i}_dnssl${j}_search
+ [ -n "$dnssl" ] && new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl"
+ fi
+
+ j=$(($j + 1))
+ return 0
+}
+
+add_resolv_conf()
+{
+ conf="$signature$NL"
+ warn=true
+
+ # Loop to extract the ND DNS options using our indexed shell values
+ i=1
+ j=1
+ while true; do
+ eval acquired=\$nd${i}_acquired
+ [ -z "$acquired" ] && break
+ eval now=\$nd${i}_now
+ [ -z "$now" ] && break
+ offset=$(($now - $acquired))
+ while true; do
+ eval_nd_dns || break
+ done
+ i=$(($i + 1))
+ j=1
+ done
+ [ -n "$new_rdnss" ] && \
+ new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$new_rdnss"
+ [ -n "$new_dnssl" ] && \
+ 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" ] &&
+ [ -z "$new_domain_name" ] &&
+ [ -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
+ new_domain_search=$(uniqify $new_domain_search)
+ 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
+ new_domain_name_servers=$(uniqify $new_domain_name_servers)
+ for x in ${new_domain_name_servers}; do
+ conf="${conf}nameserver $x$NL"
+ done
+ if $have_resolvconf; 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 $have_resolvconf; 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_configured; then
+ if $have_resolvconf && [ "$reason" = NOCARRIER_ROAMING ]; then
+ # avoid calling resolvconf -c on CARRIER unless we roam
+ mkdir -p "$nocarrier_roaming_dir"
+ echo " " >"$nocarrier_roaming_dir/$interface"
+ "$resolvconf" -C "$interface.*"
+ elif $have_resolvconf && [ "$reason" = CARRIER ]; then
+ # Not all resolvconf implementations support -c
+ if [ -e "$nocarrier_roaming_dir/$interface" ]; then
+ rm -f "$nocarrier_roaming_dir/$interface"
+ "$resolvconf" -c "$interface.*"
+ fi
+ elif $if_up || [ "$reason" = ROUTERADVERT ]; then
+ add_resolv_conf
+ elif $if_down; then
+ remove_resolv_conf
+ fi
+fi
diff --git a/hooks/29-lookup-hostname b/hooks/29-lookup-hostname
new file mode 100644
index 000000000000..811c7a9c89bf
--- /dev/null
+++ b/hooks/29-lookup-hostname
@@ -0,0 +1,39 @@
+# Lookup the hostname in DNS if not set
+
+lookup_hostname()
+{
+ [ -z "$new_ip_address" ] && return 1
+ # 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}${new_fqdn_name}" ]; then
+ export new_host_name="$(lookup_hostname)"
+ fi
+}
+
+if $if_up; then
+ set_hostname
+fi
diff --git a/hooks/30-hostname.in b/hooks/30-hostname.in
new file mode 100644
index 000000000000..abeb36967221
--- /dev/null
+++ b/hooks/30-hostname.in
@@ -0,0 +1,158 @@
+# 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}
+
+# If we used to set the hostname, but relinquish control of it, we should
+# reset to the default value.
+: ${hostname_default=@DEFAULT_HOSTNAME@}
+
+# Some systems don't have hostname(1)
+_hostname()
+{
+ if [ -z "${1+x}" ]; then
+ if [ -r /proc/sys/kernel/hostname ]; then
+ read name </proc/sys/kernel/hostname && echo "$name"
+ elif type hostname >/dev/null 2>/dev/null; then
+ hostname
+ 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
+
+ if [ -w /proc/sys/kernel/hostname ]; then
+ echo "$1" >/proc/sys/kernel/hostname
+ elif [ -n "$1" ] && type hostname >/dev/null 2>&1; then
+ hostname "$1"
+ elif sysctl kern.hostname >/dev/null 2>&1; then
+ sysctl -w "kern.hostname=$1" >/dev/null
+ elif sysctl kernel.hostname >/dev/null 2>&1; then
+ sysctl -w "kernel.hostname=$1" >/dev/null
+ else
+ # May fail to set a blank hostname
+ hostname "$1"
+ fi
+}
+
+is_default_hostname()
+{
+ case "$1" in
+ ""|"$hostname_default"|localhost|localhost.localdomain)
+ return 0;;
+ esac
+ return 1
+}
+
+need_hostname()
+{
+ # Always load the hostname variable for future use
+ hostname="$(_hostname)"
+ is_default_hostname "$hostname" && return 0
+
+ case "$force_hostname" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;;
+ esac
+
+ if [ -n "$old_fqdn" ]; then
+ if ${hfqdn} || ! ${hshort}; then
+ [ "$hostname" = "$old_fqdn" ]
+ else
+ [ "$hostname" = "${old_fqdn%%.*}" ]
+ fi
+ elif [ -n "$old_host_name" ]; then
+ if ${hfqdn}; then
+ if [ -n "$old_domain_name" ] &&
+ [ "$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()
+{
+ [ "$hostname" = "$1" ] && return 0
+ if valid_domainname "$1"; then
+ syslog info "Setting hostname: $1"
+ _hostname "$1"
+ else
+ syslog err "Invalid hostname: $1"
+ fi
+}
+
+set_hostname()
+{
+ hfqdn=false
+ hshort=false
+ case "$hostname_fqdn" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;;
+ ""|[Ss][Ee][Rr][Vv][Ee][Rr]) ;;
+ *) hshort=true;;
+ esac
+
+ need_hostname || return
+
+ 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" ] &&
+ [ "$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
+ elif ! is_default_hostname "$hostname"; then
+ try_hostname "$hostname_default"
+ 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_configured && $if_up && [ "$reason" != ROUTERADVERT ]; then
+ set_hostname
+fi
diff --git a/hooks/50-dhcpcd-compat b/hooks/50-dhcpcd-compat
new file mode 100644
index 000000000000..0d6256e6ae6c
--- /dev/null
+++ b/hooks/50-dhcpcd-compat
@@ -0,0 +1,41 @@
+# Compat enter hook shim for older dhcpcd versions
+
+IPADDR=$new_ip_address
+INTERFACE=$interface
+NETMASK=$new_subnet_mask
+BROADCAST=$new_broadcast_address
+NETWORK=$new_network_number
+DHCPSID=$new_dhcp_server_identifier
+GATEWAYS=$new_routers
+DNSSERVERS=$new_domain_name_servers
+DNSDOMAIN=$new_domain_name
+DNSSEARCH=$new_domain_search
+NISDOMAIN=$new_nis_domain
+NISSERVERS=$new_nis_servers
+NTPSERVERS=$new_ntp_servers
+
+GATEWAY=
+for x in $new_routers; do
+ GATEWAY="$GATEWAY${GATEWAY:+,}$x"
+done
+DNS=
+for x in $new_domain_name_servers; do
+ DNS="$DNS${DNS:+,}$x"
+done
+
+r="down"
+case "$reason" in
+RENEW) r="up";;
+BOUND|INFORM|REBIND|REBOOT|TEST|TIMEOUT|IPV4LL) r="new";;
+esac
+
+if [ "$r" != "down" ]; then
+ rm -f /var/lib/dhcpcd-"$INTERFACE".info
+ for x in IPADDR INTERFACE NETMASK BROADCAST NETWORK DHCPSID GATEWAYS \
+ DNSSERVERS DNSDOMAIN DNSSEARCH NISDOMAIN NISSERVERS \
+ NTPSERVERS GATEWAY DNS; do
+ eval echo "$x=\'\$$x\'" >> /var/lib/dhcpcd-"$INTERFACE".info
+ done
+fi
+
+set -- /var/lib/dhcpcd-"$INTERFACE".info "$r"
diff --git a/hooks/50-ntp.conf b/hooks/50-ntp.conf
new file mode 100644
index 000000000000..046ab6b33a06
--- /dev/null
+++ b/hooks/50-ntp.conf
@@ -0,0 +1,144 @@
+# Sample dhcpcd hook script for NTP
+# It will configure either one of NTP, OpenNTP or Chrony (in that order)
+# and will default to NTP if no default config is found.
+
+# 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 override the derived default on
+# systems with >1 NTP client installed.
+# Here is an example for OpenNTP
+# dhcpcd -e NTP_CONF=/usr/pkg/etc/ntpd.conf
+# or by adding this to /etc/dhcpcd.conf
+# env 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 Chrony instead, simply change ntpd.conf to chrony.conf in the
+# above examples.
+
+: ${ntp_confs:=ntp.conf ntpd.conf chrony.conf}
+: ${ntp_conf_dirs=/etc /usr/pkg/etc /usr/local/etc}
+ntp_conf_dir="$state_dir/ntp.conf"
+
+# If NTP_CONF is not set, work out a good default
+if [ -z "$NTP_CONF" ]; then
+ for d in ${ntp_conf_dirs}; do
+ for f in ${ntp_confs}; do
+ if [ -e "$d/$f" ]; then
+ NTP_CONF="$d/$f"
+ break 2
+ fi
+ done
+ done
+ [ -e "$NTP_CONF" ] || NTP_CONF=/etc/ntp.conf
+fi
+
+# Derive service name from configuration
+if [ -z "$ntp_service" ]; then
+ case "$NTP_CONF" in
+ *chrony.conf) ntp_service=chronyd;;
+ *) ntp_service=ntpd;;
+ esac
+fi
+
+# Debian has a separate file for DHCP config to avoid stamping on
+# the master.
+if [ "$ntp_service" = ntpd ] && type invoke-rc.d >/dev/null 2>&1; then
+ [ -e /var/lib/ntp ] || mkdir /var/lib/ntp
+ : ${ntp_service:=ntp}
+ : ${NTP_DHCP_CONF:=/var/lib/ntp/ntp.conf.dhcp}
+fi
+
+: ${ntp_restart_cmd:=service_condcommand $ntp_service restart}
+
+ntp_conf=${NTP_CONF}
+NL="
+"
+
+build_ntp_conf()
+{
+ cf="$state_dir/ntp.conf.$ifname"
+
+ # Build a list of interfaces
+ interfaces=$(list_interfaces "$ntp_conf_dir")
+
+ header=
+ servers=
+ 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" ] && [ -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()
+{
+ cf="$ntp_conf_dir/$ifname"
+
+ [ -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_configured; then
+ if $if_up; then
+ add_ntp_conf
+ elif $if_down; then
+ remove_ntp_conf
+ fi
+fi
diff --git a/hooks/50-yp.conf b/hooks/50-yp.conf
new file mode 100644
index 000000000000..c5cdad90260d
--- /dev/null
+++ b/hooks/50-yp.conf
@@ -0,0 +1,59 @@
+# Sample dhcpcd hook for ypbind
+# This script is only suitable for the Linux version.
+
+ypbind_pid()
+{
+ [ -s /var/run/ypbind.pid ] && cat /var/run/ypbind.pid
+}
+
+make_yp_conf()
+{
+ [ -z "${new_nis_domain}${new_nis_servers}" ] && return 0
+ cf=/etc/yp.conf."$ifname"
+ rm -f "$cf"
+ echo "$signature" > "$cf"
+ prefix=
+ if [ -n "$new_nis_domain" ]; then
+ if ! valid_domainname "$new_nis_domain"; then
+ syslog err "Invalid NIS domain name: $new_nis_domain"
+ rm -f "$cf"
+ return 1
+ fi
+ domainname "$new_nis_domain"
+ if [ -n "$new_nis_servers" ]; then
+ prefix="domain $new_nis_domain server "
+ else
+ echo "domain $new_nis_domain broadcast" >> "$cf"
+ fi
+ else
+ prefix="ypserver "
+ fi
+ for x in $new_nis_servers; do
+ echo "$prefix$x" >> "$cf"
+ done
+ save_conf /etc/yp.conf
+ cat "$cf" > /etc/yp.conf
+ rm -f "$cf"
+ pid="$(ypbind_pid)"
+ if [ -n "$pid" ]; then
+ kill -HUP "$pid"
+ fi
+}
+
+restore_yp_conf()
+{
+ [ -n "$old_nis_domain" ] && domainname ""
+ restore_conf /etc/yp.conf || return 0
+ pid="$(ypbind_pid)"
+ if [ -n "$pid" ]; then
+ kill -HUP "$pid"
+ fi
+}
+
+if $if_configured; then
+ if $if_up; then
+ make_yp_conf
+ elif $if_down; then
+ restore_yp_conf
+ fi
+fi
diff --git a/hooks/50-ypbind.in b/hooks/50-ypbind.in
new file mode 100644
index 000000000000..6d55228c78c6
--- /dev/null
+++ b/hooks/50-ypbind.in
@@ -0,0 +1,84 @@
+# 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:=@YPDOMAIN_DIR@}
+: ${ypdomain_suffix:=@YPDOMAIN_SUFFIX@}
+
+best_domain()
+{
+ 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
+ cf="$ypdomain_dir/$new_nis_domain$ypdomain_suffix"
+ if [ -n "$new_nis_servers" ]; then
+ ncf="$cf.$ifname"
+ 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
+
+ nd="$(best_domain)"
+ if [ $? = 0 ] && [ "$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"
+ 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 ! $if_configured; then
+ ;
+elif [ "$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
diff --git a/hooks/Makefile b/hooks/Makefile
new file mode 100644
index 000000000000..7a0c3b1d330a
--- /dev/null
+++ b/hooks/Makefile
@@ -0,0 +1,75 @@
+TOP= ../
+include ${TOP}/iconfig.mk
+
+PROG= dhcpcd-run-hooks
+BINDIR= ${LIBEXECDIR}
+CLEANFILES= dhcpcd-run-hooks
+MAN8= dhcpcd-run-hooks.8
+CLEANFILES+= dhcpcd-run-hooks.8
+
+SCRIPTSDIR= ${HOOKDIR}
+SCRIPTS= 01-test
+SCRIPTS+= 20-resolv.conf
+SCRIPTS+= 30-hostname
+SCRIPTS+= ${HOOKSCRIPTS}
+CLEANFILES+= 30-hostname
+
+# Some hooks should not be installed by default
+FILESDIR= ${DATADIR}/dhcpcd/hooks
+FILES= 10-wpa_supplicant
+FILES+= 15-timezone
+FILES+= 29-lookup-hostname
+FILES+= ${EGHOOKSCRIPTS}
+
+.SUFFIXES: .in
+
+.in: Makefile ${TOP}/config.mk
+ ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \
+ ${SED_SYS} ${SED_SCRIPT} ${SED_DATADIR} \
+ ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \
+ ${SED_STATUSARG} \
+ ${SED_DEFAULT_HOSTNAME} \
+ -e 's:@YPDOMAIN_DIR@:${YPDOMAIN_DIR}:g' \
+ -e 's:@YPDOMAIN_SUFFIX@:${YPDOMAIN_SUFFIX}:g' \
+ $< > $@
+
+all: ${PROG} ${MAN8} ${SCRIPTS} ${EGHOOKSCRIPTS}
+
+clean:
+ rm -f ${CLEANFILES} 50-ypbind
+
+distclean: clean
+ rm -f *.diff *.patch *.orig *.rej
+
+depend:
+
+proginstall: ${PROG} ${SCRIPTS}
+ ${INSTALL} -d ${DESTDIR}${BINDIR}
+ ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${BINDIR}
+ ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR}
+ ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR}
+ # We need to remove the old MTU change script if we at all can.
+ rm -f ${DESTDIR}${SCRIPTSDIR}/10-mtu
+
+eginstall: ${FILES}
+ ${INSTALL} -d ${DESTDIR}${FILESDIR}
+ ${INSTALL} -m ${NONBINMODE} ${FILES} ${DESTDIR}${FILESDIR}
+
+maninstall: ${MAN8}
+ ${INSTALL} -d ${DESTDIR}${MANDIR}/man8
+ ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}/man8
+
+install: proginstall eginstall maninstall
+
+import: ${SCRIPTS} ${FILES}
+ ${INSTALL} -d /tmp/${DISTPREFIX}/dhcpcd-hooks
+ ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} /tmp/${DISTPREFIX}/dhcpcd-hooks
+ ${INSTALL} -m ${NONBINMODE} ${FILES} /tmp/${DISTPREFIX}/dhcpcd-hooks
+
+_import-src: all
+ ${INSTALL} -d ${DESTDIR}/hooks
+ ${INSTALL} -m ${NONBINMODE} ${PROG} ${MAN8} ${DESTDIR}/hooks
+ ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} ${DESTDIR}/hooks
+ ${INSTALL} -m ${NONBINMODE} ${FILES} ${DESTDIR}/hooks
+
+include ${TOP}/Makefile.inc
diff --git a/hooks/dhcpcd-run-hooks.8.in b/hooks/dhcpcd-run-hooks.8.in
new file mode 100644
index 000000000000..db88d8e2ab17
--- /dev/null
+++ b/hooks/dhcpcd-run-hooks.8.in
@@ -0,0 +1,234 @@
+.\" Copyright (c) 2006-2021 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 December 27, 2020
+.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
+The hooks scripts are loaded into the current shell rather than executed
+in their own process.
+This allows each hook script, such as
+.Pa @SYSCONFDIR@/dhcpcd.enter-hook
+to customise environment variables or provide alternative functions to hooks
+further down the chain.
+As such, using the shell builtins
+.Ic exit ,
+.Ic exec
+or similar will cause
+.Nm
+to exit at that point.
+.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
+q
+.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 NOCARRIER_ROAMING
+dhcpcd lost the carrier but the interface configuration is persisted.
+The OS has to support wireless roaming or IP Persistance for this to happen.
+.It Dv INFORM | Dv INFORM6
+dhcpcd informed a DHCP server about its 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 obtained 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 Advertisement, 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 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 .
+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 $protocol
+the protocol that triggered the event.
+.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 .
+This is more than IFF_UP and may not be equal.
+.It Ev $if_down
+.Dv true
+if the
+.Ev interface
+is down, otherwise
+.Dv false .
+This is more than IFF_UP and may not be equal.
+.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.
diff --git a/hooks/dhcpcd-run-hooks.in b/hooks/dhcpcd-run-hooks.in
new file mode 100644
index 000000000000..a237f6af5340
--- /dev/null
+++ b/hooks/dhcpcd-run-hooks.in
@@ -0,0 +1,352 @@
+#!/bin/sh
+# dhcpcd client configuration script
+
+# Handy variables and functions for our hooks to use
+ifname="$interface${protocol+.}$protocol"
+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@/hook-state
+_detected_init=false
+
+: ${if_up:=false}
+: ${if_down:=false}
+: ${syslog_debug:=false}
+
+# Ensure that all arguments are unique
+uniqify()
+{
+ result=
+ for i do
+ case " $result " in
+ *" $i "*);;
+ *) result="$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()
+{
+ 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()
+{
+ 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()
+{
+ key="$1"
+ 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()
+{
+ m1="$1"
+ m2="$2"
+ 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" ] && [ -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()
+{
+ 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 name as per RFC952 and RFC1123 section 2.1
+valid_domainname()
+{
+ name="$1"
+ [ -z "$name" ] || [ ${#name} -gt 255 ] && return 1
+
+ while [ -n "$name" ]; do
+ label="${name%%.*}"
+ [ -z "$label" ] || [ ${#label} -gt 63 ] && return 1
+ case "$label" in
+ -*|_*|*-|*_) return 1;;
+ *[![:alnum:]_-]*) return 1;;
+ "$name") return 0;;
+ esac
+ name="${name#*.}"
+ done
+ return 0
+}
+
+valid_domainname_list()
+{
+ for name do
+ valid_domainname "$name" || return $?
+ done
+ return 0
+}
+
+# With the advent of alternative init systems, it's possible to have
+# more than one installed. So we need to try to guess what one we're
+# using unless overridden 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.
+ status="@STATUSARG@"
+ : ${status:=status}
+ if [ -x /bin/systemctl ] && [ -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 ] && [ -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 ] &&
+ { [ -s /libexec/rc/init.d/softlevel ] ||
+ [ -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 /usr/sbin/service ]; then
+ _service_exists="/usr/sbin/service \$1 $status >/dev/null 2>&1"
+ _service_cmd="/usr/sbin/service \$1 \$2"
+ elif [ -x /bin/sv ]; then
+ _service_exists="/bin/sv status \$1 >/dev/null 2>&1"
+ _service_cmd="/bin/sv \$2 \$1"
+ elif [ -x /usr/bin/sv ]; then
+ _service_exists="/usr/bin/sv status \$1 >/dev/null 2>&1"
+ _service_cmd="/usr/bin/sv \$2 \$1"
+ elif [ -e /etc/slackware-version ] && [ -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 >/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"
+ _service_status="$x/\$1 $status >/dev/null 2>&1"
+ break
+ fi
+ done
+ if [ -e /etc/arch-release ]; then
+ _service_status="[ -e /var/run/daemons/\$1 ]"
+ elif [ "$x" = "/etc/rc.d" ] && [ -e /etc/rc.d/rc.subr ]; then
+ _service_status="$x/\$1 check >/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
diff --git a/iconfig.mk b/iconfig.mk
new file mode 100644
index 000000000000..50c50340c17e
--- /dev/null
+++ b/iconfig.mk
@@ -0,0 +1,8 @@
+# Nasty hack so that make clean works without configure being run
+TOP?= .
+_CONFIG_MK!= test -e ${TOP}/config.mk && \
+ echo config.mk || echo config-null.mk
+_CONFIG_MK?= $(shell test -e ${TOP}/config.mk && \
+ echo config.mk || echo config-null.mk)
+CONFIG_MK?= ${_CONFIG_MK}
+include ${TOP}/${CONFIG_MK}
diff --git a/src/GNUmakefile b/src/GNUmakefile
new file mode 100644
index 000000000000..f66ad94c6e0e
--- /dev/null
+++ b/src/GNUmakefile
@@ -0,0 +1,12 @@
+# GNU Make does not automagically include .depend
+# Luckily it does read GNUmakefile over Makefile so we can work around it
+
+# Nasty hack so that make clean works without configure being run
+TOP?= ..
+CONFIG_MK?= $(shell test -e ${TOP}/config.mk && \
+ echo config.mk || echo config-null.mk)
+
+include Makefile
+ifneq ($(wildcard .depend), )
+include .depend
+endif
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 000000000000..4849a77abd99
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,147 @@
+# dhcpcd Makefile
+
+PROG= dhcpcd
+SRCS= common.c control.c dhcpcd.c duid.c eloop.c logerr.c
+SRCS+= if.c if-options.c sa.c route.c
+SRCS+= dhcp-common.c script.c
+
+CFLAGS?= -O2
+SUBDIRS+= ${MKDIRS}
+
+TOP= ..
+include ${TOP}/iconfig.mk
+
+CSTD?= c99
+CFLAGS+= -std=${CSTD}
+CPPFLAGS+= -I${TOP} -I${TOP}/src -I./crypt
+
+SRCS+= ${DHCPCD_SRCS} ${PRIVSEP_SRCS}
+DHCPCD_DEF?= dhcpcd-definitions.conf
+DHCPCD_DEFS= dhcpcd-definitions.conf dhcpcd-definitions-small.conf
+
+PCOMPAT_SRCS= ${COMPAT_SRCS:compat/%=${TOP}/compat/%}
+PCRYPT_SRCS= ${CRYPT_SRCS:compat/%=${TOP}/compat/%}
+OBJS+= ${SRCS:.c=.o} ${PCRYPT_SRCS:.c=.o} ${PCOMPAT_SRCS:.c=.o}
+
+MAN5= dhcpcd.conf.5
+MAN8= dhcpcd.8
+CLEANFILES= dhcpcd.conf.5 dhcpcd.8
+
+FILES= dhcpcd.conf
+FILESDIR= ${SYSCONFDIR}
+
+DEPEND!= test -e .depend && echo ".depend" || echo ""
+
+CLEANFILES+= *.tar.xz
+
+.PHONY: import import-bsd dev test
+
+.SUFFIXES: .in
+
+.in: Makefile ${TOP}/config.mk
+ ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \
+ ${SED_SYS} ${SED_SCRIPT} ${SED_DATADIR} \
+ ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \
+ ${SED_STATUSARG} \
+ $< > $@
+
+all: ${TOP}/config.h ${PROG} ${SCRIPTS} ${MAN5} ${MAN8}
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+dev:
+ cd dev && ${MAKE}
+
+.c.o: Makefile ${TOP}/config.mk
+ ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@
+
+CLEANFILES+= dhcpcd-embedded.h dhcpcd-embedded.c
+
+dhcpcd-embedded.h: genembedh ${DHCPCD_DEFS} dhcpcd-embedded.h.in
+ ${HOST_SH} ${.ALLSRC} $^ > $@
+
+dhcpcd-embedded.c: genembedc ${DHCPCD_DEFS} dhcpcd-embedded.c.in
+ ${HOST_SH} ${.ALLSRC} $^ > $@
+
+if-options.c: dhcpcd-embedded.h
+
+.depend: ${SRCS} ${COMPAT_SRCS} ${CRYPT_SRCS}
+ ${CC} ${CPPFLAGS} -MM ${SRCS} ${COMPAT_SRCS} ${CRYPT_SRCS} > .depend
+
+depend: .depend
+
+${PROG}: ${DEPEND} ${OBJS}
+ ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
+
+lint:
+ ${LINT} -Suz ${CPPFLAGS} ${SRCS} ${PCRYPT_SRCS} ${PCOMPAT_SRCS}
+
+_embeddedinstall: ${DHCPCD_DEF}
+ ${INSTALL} -d ${DESTDIR}${LIBEXECDIR}
+ ${INSTALL} -m ${CONFMODE} ${DHCPCD_DEF} ${DESTDIR}${LIBEXECDIR}
+
+_proginstall: ${PROG}
+ ${INSTALL} -d ${DESTDIR}${SBINDIR}
+ ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${SBINDIR}
+ ${INSTALL} -m ${DBMODE} -d ${DESTDIR}${DBDIR}
+
+proginstall: _proginstall ${EMBEDDEDINSTALL}
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+_maninstall: ${MAN5} ${MAN8}
+ ${INSTALL} -d ${DESTDIR}${MANDIR}/man5
+ ${INSTALL} -m ${MANMODE} ${MAN5} ${DESTDIR}${MANDIR}/man5
+ ${INSTALL} -d ${DESTDIR}${MANDIR}/man8
+ ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}/man8
+
+_confinstall:
+ ${INSTALL} -d ${DESTDIR}${SYSCONFDIR}
+ # Install a new default config if not present
+ test -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.conf || \
+ ${INSTALL} -m ${CONFMODE} dhcpcd.conf ${DESTDIR}${SYSCONFDIR}
+
+eginstall:
+
+install: proginstall _maninstall _confinstall eginstall
+
+clean:
+ rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+distclean: clean
+ rm -f .depend
+ rm -f *.diff *.patch *.orig *.rej
+
+_import-src: ${SRCS} ${MAN5} ${MAN8}
+ ${INSTALL} -d ${DESTDIR}/src
+ for x in defs.h ${SRCS} ${SRCS:.c=.h} dev.h ${MAN5} ${MAN8}; do \
+ [ ! -e "$$x" ] || cp $$x ${DESTDIR}/src; \
+ done
+ cp dhcpcd.conf ${DESTDIR}/src
+ if [ -n "${COMPAT_SRCS}" ]; then \
+ ${INSTALL} -d ${DESTDIR}/compat; \
+ for x in ${COMPAT_SRCS} ${COMPAT_SRCS:.c=.h}; do \
+ [ ! -e "../$$x" ] || cp "../$$x" ${DESTDIR}/compat; \
+ done; \
+ fi
+ if ! grep HAVE_SYS_BITOPS_H ../config.h; then \
+ cp ../compat/bitops.h ${DESTDIR}/compat; \
+ fi
+ if grep compat/consttime_memequal.h ../config.h; then \
+ cp ../compat/consttime_memequal.h ${DESTDIR}/compat; \
+ fi
+ if [ -e ${DESTDIR}/compat/rb.c ]; then \
+ cp ../compat/rbtree.h ${DESTDIR}/compat; \
+ fi
+ if [ -e ${DESTDIR}/compat/strtoi.c ]; then \
+ cp ../compat/_strtoi.h ${DESTDIR}/compat; \
+ fi
+ if [ -n "${CRYPT_SRCS}" ]; then \
+ ${INSTALL} -d ${DESTDIR}/compat/crypt; \
+ for x in ${CRYPT_SRCS} ${CRYPT_SRCS:.c=.h}; do \
+ cp "../$$x" ${DESTDIR}/compat/crypt; \
+ done; \
+ fi
+ # DragonFlyBSD builds base version with private crypto
+ if [ `uname` = DragonFly ]; then rm ${DESTDIR}/compat/crypt/md5* ${DESTDIR}/compat/crypt/sha256*; fi
+
+include ${TOP}/Makefile.inc
diff --git a/src/arp.c b/src/arp.c
new file mode 100644
index 000000000000..e8a27f42257b
--- /dev/null
+++ b/src/arp.c
@@ -0,0 +1,634 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - ARP handler
+ * Copyright (c) 2006-2021 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE ELOOP_ARP
+#include "config.h"
+#include "arp.h"
+#include "bpf.h"
+#include "ipv4.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+#include "logerr.h"
+#include "privsep.h"
+
+#if defined(ARP)
+#define ARP_LEN \
+ (FRAMEHDRLEN_MAX + \
+ sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN))
+
+/* ARP debugging can be quite noisy. Enable this for more noise! */
+//#define ARP_DEBUG
+
+/* Assert the correct structure size for on wire */
+__CTASSERT(sizeof(struct arphdr) == 8);
+
+static ssize_t
+arp_request(const struct arp_state *astate,
+ const struct in_addr *sip)
+{
+ const struct interface *ifp = astate->iface;
+ const struct in_addr *tip = &astate->addr;
+ uint8_t arp_buffer[ARP_LEN];
+ struct arphdr ar;
+ size_t len;
+ uint8_t *p;
+
+ ar.ar_hrd = htons(ifp->hwtype);
+ ar.ar_pro = htons(ETHERTYPE_IP);
+ ar.ar_hln = ifp->hwlen;
+ ar.ar_pln = sizeof(tip->s_addr);
+ 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);
+ if (sip != NULL)
+ APPEND(&sip->s_addr, sizeof(sip->s_addr));
+ else
+ ZERO(sizeof(tip->s_addr));
+ ZERO(ifp->hwlen);
+ APPEND(&tip->s_addr, sizeof(tip->s_addr));
+
+#ifdef PRIVSEP
+ if (ifp->ctx->options & DHCPCD_PRIVSEP)
+ return ps_bpf_sendarp(ifp, tip, arp_buffer, len);
+#endif
+ /* Note that well formed ethernet will add extra padding
+ * to ensure that the packet is at least 60 bytes (64 including FCS). */
+ return bpf_send(astate->bpf, ETHERTYPE_ARP, arp_buffer, len);
+
+eexit:
+ errno = ENOBUFS;
+ return -1;
+}
+
+static void
+arp_report_conflicted(const struct arp_state *astate,
+ const struct arp_msg *amsg)
+{
+ char abuf[HWADDR_LEN * 3];
+ char fbuf[HWADDR_LEN * 3];
+
+ if (amsg == NULL) {
+ logerrx("%s: DAD detected %s",
+ astate->iface->name, inet_ntoa(astate->addr));
+ return;
+ }
+
+ hwaddr_ntoa(amsg->sha, astate->iface->hwlen, abuf, sizeof(abuf));
+ if (bpf_frame_header_len(astate->iface) == 0) {
+ logwarnx("%s: %s claims %s",
+ astate->iface->name, abuf, inet_ntoa(astate->addr));
+ return;
+ }
+
+ logwarnx("%s: %s(%s) claims %s",
+ astate->iface->name, abuf,
+ hwaddr_ntoa(amsg->fsha, astate->iface->hwlen, fbuf, sizeof(fbuf)),
+ inet_ntoa(astate->addr));
+}
+
+static void
+arp_found(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct interface *ifp;
+ struct ipv4_addr *ia;
+#ifndef KERNEL_RFC5227
+ struct timespec now;
+#endif
+
+ arp_report_conflicted(astate, amsg);
+ ifp = astate->iface;
+
+ /* If we haven't added the address we're doing a probe. */
+ ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
+ if (ia == NULL) {
+ if (astate->found_cb != NULL)
+ astate->found_cb(astate, amsg);
+ return;
+ }
+
+#ifndef KERNEL_RFC5227
+ /* RFC 3927 Section 2.5 says a defence should
+ * broadcast an ARP announcement.
+ * Because the kernel will also unicast a reply to the
+ * hardware address which requested the IP address
+ * the other IPv4LL client will receieve two ARP
+ * messages.
+ * If another conflict happens within DEFEND_INTERVAL
+ * then we must drop our address and negotiate a new one. */
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (timespecisset(&astate->defend) &&
+ eloop_timespec_diff(&now, &astate->defend, NULL) < DEFEND_INTERVAL)
+ logwarnx("%s: %d second defence failed for %s",
+ ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr));
+ else if (arp_request(astate, &astate->addr) == -1)
+ logerr(__func__);
+ else {
+ logdebugx("%s: defended address %s",
+ ifp->name, inet_ntoa(astate->addr));
+ astate->defend = now;
+ return;
+ }
+#endif
+
+ if (astate->defend_failed_cb != NULL)
+ astate->defend_failed_cb(astate);
+}
+
+static bool
+arp_validate(const struct interface *ifp, struct arphdr *arp)
+{
+
+ /* Address type must match */
+ if (arp->ar_hrd != htons(ifp->hwtype))
+ return false;
+
+ /* Protocol must be IP. */
+ if (arp->ar_pro != htons(ETHERTYPE_IP))
+ return false;
+
+ /* lladdr length matches */
+ if (arp->ar_hln != ifp->hwlen)
+ return false;
+
+ /* Protocol length must match in_addr_t */
+ if (arp->ar_pln != sizeof(in_addr_t))
+ return false;
+
+ /* Only these types are recognised */
+ if (arp->ar_op != htons(ARPOP_REPLY) &&
+ arp->ar_op != htons(ARPOP_REQUEST))
+ return false;
+
+ return true;
+}
+
+void
+arp_packet(struct interface *ifp, uint8_t *data, size_t len,
+ unsigned int bpf_flags)
+{
+ size_t fl = bpf_frame_header_len(ifp), falen;
+ const struct interface *ifn;
+ struct arphdr ar;
+ struct arp_msg arm;
+ const struct iarp_state *state;
+ struct arp_state *astate, *astaten;
+ uint8_t *hw_s, *hw_t;
+
+ /* Copy the frame header source and destination out */
+ memset(&arm, 0, sizeof(arm));
+ if (fl != 0) {
+ hw_s = bpf_frame_header_src(ifp, data, &falen);
+ if (hw_s != NULL && falen <= sizeof(arm.fsha))
+ memcpy(arm.fsha, hw_s, falen);
+ hw_t = bpf_frame_header_dst(ifp, data, &falen);
+ if (hw_t != NULL && falen <= sizeof(arm.ftha))
+ memcpy(arm.ftha, hw_t, falen);
+
+ /* Skip past the frame header */
+ data += fl;
+ len -= fl;
+ }
+
+ /* We must have a full ARP header */
+ if (len < sizeof(ar))
+ return;
+ memcpy(&ar, data, sizeof(ar));
+
+ if (!arp_validate(ifp, &ar)) {
+#ifdef BPF_DEBUG
+ logerrx("%s: ARP BPF validation failure", ifp->name);
+#endif
+ return;
+ }
+
+ /* Get pointers to the hardware addresses */
+ hw_s = data + sizeof(ar);
+ hw_t = hw_s + ar.ar_hln + ar.ar_pln;
+ /* Ensure we got all the data */
+ if ((size_t)((hw_t + ar.ar_hln + ar.ar_pln) - data) > len)
+ return;
+ /* 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) {
+#ifdef ARP_DEBUG
+ logdebugx("%s: ignoring ARP from self", ifp->name);
+#endif
+ return;
+ }
+ /* 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);
+
+ /* Match the ARP probe to our states.
+ * Ignore Unicast Poll, RFC1122. */
+ state = ARP_CSTATE(ifp);
+ if (state == NULL)
+ return;
+ TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) {
+ if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) ||
+ (IN_IS_ADDR_UNSPECIFIED(&arm.sip) &&
+ IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr) &&
+ bpf_flags & BPF_BCAST))
+ arp_found(astate, &arm);
+ }
+}
+
+static void
+arp_read(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct bpf *bpf = astate->bpf;
+ struct interface *ifp = astate->iface;
+ uint8_t buf[ARP_LEN];
+ ssize_t bytes;
+ struct in_addr addr = astate->addr;
+
+ /* Some RAW mechanisms are generic file descriptors, not sockets.
+ * This means we have no kernel call to just get one packet,
+ * so we have to process the entire buffer. */
+ bpf->bpf_flags &= ~BPF_EOF;
+ while (!(bpf->bpf_flags & BPF_EOF)) {
+ bytes = bpf_read(bpf, buf, sizeof(buf));
+ if (bytes == -1) {
+ logerr("%s: %s", __func__, ifp->name);
+ arp_free(astate);
+ return;
+ }
+ arp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags);
+ /* Check we still have a state after processing. */
+ if ((astate = arp_find(ifp, &addr)) == NULL)
+ break;
+ if ((bpf = astate->bpf) == NULL)
+ break;
+ }
+}
+
+static void
+arp_probed(void *arg)
+{
+ struct arp_state *astate = arg;
+
+ timespecclear(&astate->defend);
+ astate->not_found_cb(astate);
+}
+
+static void
+arp_probe1(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct interface *ifp = astate->iface;
+ unsigned int delay;
+
+ if (++astate->probes < PROBE_NUM) {
+ delay = (PROBE_MIN * MSEC_PER_SEC) +
+ (arc4random_uniform(
+ (PROBE_MAX - PROBE_MIN) * MSEC_PER_SEC));
+ eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probe1, astate);
+ } else {
+ delay = ANNOUNCE_WAIT * MSEC_PER_SEC;
+ eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probed, astate);
+ }
+ logdebugx("%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,
+ (float)delay / MSEC_PER_SEC);
+ if (arp_request(astate, NULL) == -1)
+ logerr(__func__);
+}
+
+void
+arp_probe(struct arp_state *astate)
+{
+
+ astate->probes = 0;
+ logdebugx("%s: probing for %s",
+ astate->iface->name, inet_ntoa(astate->addr));
+ arp_probe1(astate);
+}
+#endif /* ARP */
+
+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;
+}
+
+static void
+arp_announced(void *arg)
+{
+ struct arp_state *astate = arg;
+
+ if (astate->announced_cb) {
+ astate->announced_cb(astate);
+ return;
+ }
+
+ /* Keep the ARP state open to handle ongoing ACD. */
+}
+
+static void
+arp_announce1(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct interface *ifp = astate->iface;
+ struct ipv4_addr *ia;
+
+ if (++astate->claims < ANNOUNCE_NUM)
+ logdebugx("%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
+ logdebugx("%s: ARP announcing %s (%d of %d)",
+ ifp->name, inet_ntoa(astate->addr),
+ astate->claims, ANNOUNCE_NUM);
+
+ /* The kernel will send a Gratuitous ARP for newly added addresses.
+ * So we can avoid sending the same.
+ * Linux is special and doesn't send one. */
+ ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
+#ifndef __linux__
+ if (astate->claims == 1 && ia != NULL && ia->flags & IPV4_AF_NEW)
+ goto skip_request;
+#endif
+
+ if (arp_request(astate, &astate->addr) == -1)
+ logerr(__func__);
+
+#ifndef __linux__
+skip_request:
+#endif
+ /* No longer a new address. */
+ if (ia != NULL)
+ ia->flags |= ~IPV4_AF_NEW;
+
+ eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT,
+ astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced,
+ astate);
+}
+
+static void
+arp_announce(struct arp_state *astate)
+{
+ struct iarp_state *state;
+ struct interface *ifp;
+ struct arp_state *a2;
+ int r;
+
+ /* Cancel any other ARP announcements for this address. */
+ TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) {
+ state = ARP_STATE(ifp);
+ if (state == NULL)
+ continue;
+ TAILQ_FOREACH(a2, &state->arp_states, next) {
+ if (astate == a2 ||
+ a2->addr.s_addr != astate->addr.s_addr)
+ continue;
+ r = eloop_timeout_delete(a2->iface->ctx->eloop,
+ a2->claims < ANNOUNCE_NUM
+ ? arp_announce1 : arp_announced,
+ a2);
+ if (r == -1)
+ logerr(__func__);
+ else if (r != 0) {
+ logdebugx("%s: ARP announcement "
+ "of %s cancelled",
+ a2->iface->name,
+ inet_ntoa(a2->addr));
+ arp_announced(a2);
+ }
+ }
+ }
+
+ astate->claims = 0;
+ arp_announce1(astate);
+}
+
+struct arp_state *
+arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia)
+{
+ struct arp_state *astate;
+
+ if (ifp->flags & IFF_NOARP || !(ifp->options->options & DHCPCD_ARP))
+ return NULL;
+
+ astate = arp_find(ifp, ia);
+ if (astate == NULL) {
+ astate = arp_new(ifp, ia);
+ if (astate == NULL)
+ return NULL;
+ astate->announced_cb = arp_free;
+ }
+ arp_announce(astate);
+ return astate;
+}
+
+struct arp_state *
+arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia)
+{
+ struct interface *ifp, *iff = NULL;
+ struct ipv4_addr *iap;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (!ifp->active || !if_is_link_up(ifp))
+ continue;
+ iap = ipv4_iffindaddr(ifp, ia, NULL);
+ if (iap == NULL)
+ continue;
+#ifdef IN_IFF_NOTUSEABLE
+ if (iap->addr_flags & IN_IFF_NOTUSEABLE)
+ continue;
+#endif
+ if (iff != NULL && iff->metric < ifp->metric)
+ continue;
+ iff = ifp;
+ }
+ if (iff == NULL)
+ return NULL;
+
+ return arp_ifannounceaddr(iff, ia);
+}
+
+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) {
+ logerr(__func__);
+ return NULL;
+ }
+ TAILQ_INIT(&state->arp_states);
+ } else {
+ if ((astate = arp_find(ifp, addr)) != NULL)
+ return astate;
+ }
+
+ if ((astate = calloc(1, sizeof(*astate))) == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ astate->iface = ifp;
+ astate->addr = *addr;
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ifp->ctx)) {
+ if (ps_bpf_openarp(ifp, addr) == -1) {
+ logerr(__func__);
+ free(astate);
+ return NULL;
+ }
+ } else
+#endif
+ {
+ astate->bpf = bpf_open(ifp, bpf_arp, addr);
+ if (astate->bpf == NULL) {
+ logerr(__func__);
+ free(astate);
+ return NULL;
+ }
+ eloop_event_add(ifp->ctx->eloop, astate->bpf->bpf_fd,
+ arp_read, astate);
+ }
+
+
+ state = ARP_STATE(ifp);
+ TAILQ_INSERT_TAIL(&state->arp_states, astate, next);
+ return astate;
+}
+
+void
+arp_free(struct arp_state *astate)
+{
+ struct interface *ifp;
+ struct dhcpcd_ctx *ctx;
+ struct iarp_state *state;
+
+ if (astate == NULL)
+ return;
+
+ ifp = astate->iface;
+ ctx = ifp->ctx;
+ eloop_timeout_delete(ctx->eloop, NULL, astate);
+
+ state = ARP_STATE(ifp);
+ TAILQ_REMOVE(&state->arp_states, astate, next);
+ if (astate->free_cb)
+ astate->free_cb(astate);
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ctx) && ps_bpf_closearp(ifp, &astate->addr) == -1)
+ logerr(__func__);
+#endif
+ if (astate->bpf != NULL) {
+ eloop_event_delete(ctx->eloop, astate->bpf->bpf_fd);
+ bpf_close(astate->bpf);
+ }
+
+ free(astate);
+
+ if (TAILQ_FIRST(&state->arp_states) == NULL) {
+ free(state);
+ ifp->if_data[IF_DATA_ARP] = NULL;
+ }
+}
+
+void
+arp_freeaddr(struct interface *ifp, const struct in_addr *ia)
+{
+ struct arp_state *astate;
+
+ astate = arp_find(ifp, ia);
+ arp_free(astate);
+}
+
+void
+arp_drop(struct interface *ifp)
+{
+ struct iarp_state *state;
+ struct arp_state *astate;
+
+ while ((state = ARP_STATE(ifp)) != NULL &&
+ (astate = TAILQ_FIRST(&state->arp_states)) != NULL)
+ arp_free(astate);
+}
diff --git a/src/arp.h b/src/arp.h
new file mode 100644
index 000000000000..0ac8ef711622
--- /dev/null
+++ b/src/arp.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 "bpf.h"
+#include "dhcpcd.h"
+#include "if.h"
+
+#ifdef IN_IFF_DUPLICATED
+/* NetBSD gained RFC 5227 support in the kernel.
+ * This means dhcpcd doesn't need ARP except for ARPing support
+ * and ARP announcing an address. */
+#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 799003900
+#define KERNEL_RFC5227
+#endif
+#endif
+
+struct arp_msg {
+ uint16_t op;
+ uint8_t sha[HWADDR_LEN];
+ struct in_addr sip;
+ uint8_t tha[HWADDR_LEN];
+ struct in_addr tip;
+ /* Frame header and sender to diagnose failures */
+ uint8_t fsha[HWADDR_LEN];
+ uint8_t ftha[HWADDR_LEN];
+};
+
+struct arp_state {
+ TAILQ_ENTRY(arp_state) next;
+ struct interface *iface;
+ struct in_addr addr;
+ struct bpf *bpf;
+
+ int probes;
+ int claims;
+ struct timespec defend;
+
+ void (*found_cb)(struct arp_state *, const struct arp_msg *);
+ void (*not_found_cb)(struct arp_state *);
+ void (*announced_cb)(struct arp_state *);
+ void (*defend_failed_cb)(struct arp_state *);
+ void (*free_cb)(struct arp_state *);
+};
+TAILQ_HEAD(arp_statehead, arp_state);
+
+struct iarp_state {
+ 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 ARP
+void arp_packet(struct interface *, uint8_t *, size_t, unsigned int);
+struct arp_state *arp_new(struct interface *, const struct in_addr *);
+void arp_probe(struct arp_state *);
+struct arp_state *arp_announceaddr(struct dhcpcd_ctx *, const struct in_addr *);
+struct arp_state *arp_ifannounceaddr(struct interface *, const struct in_addr *);
+struct arp_state * arp_find(struct interface *, const struct in_addr *);
+void arp_free(struct arp_state *);
+void arp_freeaddr(struct interface *, const struct in_addr *);
+void arp_drop(struct interface *);
+#endif /* ARP */
+#endif /* ARP_H */
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 000000000000..bfb2b5dda57a
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,738 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "auth.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "dhcpcd.h"
+#include "privsep-root.h"
+
+#ifdef HAVE_HMAC_H
+#include <hmac.h>
+#endif
+
+#ifdef __sun
+#define htonll
+#define ntohll
+#endif
+
+#ifndef htonll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define htonll(x) ((uint64_t)htonl((uint32_t)((x) >> 32)) | \
+ (uint64_t)htonl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
+#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define htonll(x) (x)
+#endif
+#endif /* htonll */
+
+#ifndef ntohll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define ntohll(x) ((uint64_t)ntohl((uint32_t)((x) >> 32)) | \
+ (uint64_t)ntohl((uint32_t)((x) & 0x00000000ffffffffULL)) << 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 void *vm, size_t mlen, int mp, int mt,
+ const void *vdata, size_t dlen)
+{
+ const uint8_t *m, *data;
+ 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_code[HMAC_LENGTH];
+
+ if (dlen < 3 + sizeof(replay)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ m = vm;
+ data = vdata;
+ /* Ensure that d is inside m which *may* not be the case for DHCPv4.
+ * This can occur if the authentication option is split using
+ * DHCP long option from RFC 3399. Section 9 which does infact note that
+ * implementations should take this into account.
+ * Fixing this would be problematic, patches welcome. */
+ 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);
+ /*
+ * Test for a replay attack.
+ *
+ * NOTE: Some servers always send a replay data value of zero.
+ * This is strictly compliant with RFC 3315 and 3318 which say:
+ * "If the RDM field contains 0x00, the replay detection field MUST be
+ * set to the value of a monotonically increasing counter."
+ * An example of a monotonically increasing sequence is:
+ * 1, 2, 2, 2, 2, 2, 2
+ * Errata 3474 updates RFC 3318 to say:
+ * "If the RDM field contains 0x00, the replay detection field MUST be
+ * set to the value of a strictly increasing counter."
+ *
+ * Taking the above into account, dhcpcd will only test for
+ * strictly speaking replay attacks if it receives any non zero
+ * replay data to validate against.
+ */
+ if (state->token && state->replay != 0) {
+ 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 = auth->token_rcv_secretid;
+ break;
+ case AUTH_PROTO_DELAYED:
+ if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ memcpy(&secretid, d, sizeof(secretid));
+ secretid = ntohl(secretid);
+ d += sizeof(secretid);
+ dlen -= sizeof(secretid);
+ break;
+ case AUTH_PROTO_DELAYEDREALM:
+ if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code));
+ if (realm_len) {
+ realm = d;
+ d += realm_len;
+ dlen -= realm_len;
+ }
+ memcpy(&secretid, d, sizeof(secretid));
+ secretid = ntohl(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 */
+ 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) {
+ /* Assert the bootp structure is correct size. */
+ __CTASSERT(sizeof(struct bootp) == 300);
+
+ *(mm + offsetof(struct bootp, hops)) = '\0';
+ memset(mm + offsetof(struct bootp, giaddr), 0, 4);
+ }
+
+ memset(hmac_code, 0, sizeof(hmac_code));
+ switch (algorithm) {
+ case AUTH_ALG_HMAC_MD5:
+ hmac("md5", t->key, t->key_len, mm, mlen,
+ hmac_code, sizeof(hmac_code));
+ break;
+ default:
+ errno = ENOSYS;
+ free(mm);
+ return NULL;
+ }
+
+ free(mm);
+ if (!consttime_memequal(d, &hmac_code, 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;
+}
+
+int
+auth_get_rdm_monotonic(uint64_t *rdm)
+{
+ FILE *fp;
+ int err;
+#ifdef LOCK_EX
+ int flocked;
+#endif
+
+ fp = fopen(RDM_MONOFILE, "r+");
+ if (fp == NULL) {
+ if (errno != ENOENT)
+ return -1;
+ fp = fopen(RDM_MONOFILE, "w");
+ if (fp == NULL)
+ return -1;
+ if (chmod(RDM_MONOFILE, 0400) == -1) {
+ fclose(fp);
+ unlink(RDM_MONOFILE);
+ return -1;
+ }
+#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) {
+ fclose(fp);
+ return -1;
+ }
+ }
+
+ (*rdm)++;
+ if (fseek(fp, 0, SEEK_SET) == -1 ||
+ ftruncate(fileno(fp), 0) == -1 ||
+ fprintf(fp, "0x%016" PRIu64 "\n", *rdm) != 19 ||
+ fflush(fp) == EOF)
+ err = -1;
+ else
+ err = 0;
+#ifdef LOCK_EX
+ if (flocked == 0)
+ flock(fileno(fp), LOCK_UN);
+#endif
+ fclose(fp);
+ return err;
+}
+
+#define NTP_EPOCH 2208988800U /* 1970 - 1900 in seconds */
+#define NTP_SCALE_FRAC 4294967295.0 /* max value of the fractional part */
+static uint64_t
+get_next_rdm_monotonic_clock(struct auth *auth)
+{
+ struct timespec ts;
+ uint64_t secs, rdm;
+ double frac;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ return ++auth->last_replay; /* report error? */
+
+ secs = (uint64_t)ts.tv_sec + NTP_EPOCH;
+ frac = ((double)ts.tv_nsec / 1e9 * NTP_SCALE_FRAC);
+ rdm = (secs << 32) | (uint64_t)frac;
+ return rdm;
+}
+
+static uint64_t
+get_next_rdm_monotonic(struct dhcpcd_ctx *ctx, struct auth *auth)
+{
+#ifndef PRIVSEP
+ UNUSED(ctx);
+#endif
+
+ if (auth->options & DHCPCD_AUTH_RDM_COUNTER) {
+ uint64_t rdm;
+ int err;
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ctx)) {
+
+ err = ps_root_getauthrdm(ctx, &rdm);
+ } else
+#endif
+ err = auth_get_rdm_monotonic(&rdm);
+ if (err == -1)
+ return ++auth->last_replay;
+
+ auth->last_replay = rdm;
+ return rdm;
+ }
+ 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 dhcpcd_ctx *ctx, struct auth *auth,
+ const struct token *t,
+ void *vm, size_t mlen, int mp, int mt,
+ void *vdata, size_t dlen)
+{
+ uint64_t rdm;
+ uint8_t hmac_code[HMAC_LENGTH];
+ time_t now;
+ uint8_t hops, *p, *m, *data;
+ uint32_t giaddr, secretid;
+ bool auth_info;
+
+ /* Ignore the token argument given to us - always send using the
+ * configured token. */
+ if (auth->protocol == AUTH_PROTO_TOKEN) {
+ TAILQ_FOREACH(t, &auth->tokens, next) {
+ if (t->secretid == auth->token_snd_secretid)
+ 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_NONE:
+ 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)))
+ auth_info = false;
+ else
+ auth_info = true;
+
+ /* Work out the auth area size.
+ * We only need to do this for DISCOVER messages */
+ if (vdata == NULL) {
+ dlen = 1 + 1 + 1 + 8;
+ switch(auth->protocol) {
+ case AUTH_PROTO_TOKEN:
+ dlen += t->key_len;
+ break;
+ case AUTH_PROTO_DELAYEDREALM:
+ if (auth_info && t)
+ dlen += t->realm_len;
+ /* FALLTHROUGH */
+ case AUTH_PROTO_DELAYED:
+ if (auth_info && t)
+ dlen += sizeof(t->secretid) + sizeof(hmac_code);
+ 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 */
+ m = vm;
+ data = vdata;
+ if (data < m || data > m + mlen || data + dlen > m + mlen) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* Write out our option */
+ *data++ = auth->protocol;
+ *data++ = auth->algorithm;
+ /*
+ * RFC 3315 21.4.4.1 says that SOLICIT in DELAYED authentication
+ * should not set RDM or it's data.
+ * An expired draft draft-ietf-dhc-dhcpv6-clarify-auth-01 suggets
+ * this should not be set for INFORMATION REQ messages as well,
+ * which is probably a good idea because both states start from zero.
+ */
+ if (auth_info ||
+ !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM)))
+ {
+ *data++ = auth->rdm;
+ switch (auth->rdm) {
+ case AUTH_RDM_MONOTONIC:
+ rdm = get_next_rdm_monotonic(ctx, auth);
+ break;
+ default:
+ /* This block appeases gcc, clang doesn't need it */
+ rdm = get_next_rdm_monotonic(ctx, auth);
+ break;
+ }
+ rdm = htonll(rdm);
+ memcpy(data, &rdm, 8);
+ } else {
+ *data++ = 0; /* rdm */
+ memset(data, 0, 8); /* replay detection data */
+ }
+ 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 (!auth_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 bootp, hops);
+ hops = *p;
+ *p = '\0';
+ p = m + offsetof(struct bootp, 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", t->key, t->key_len, m, mlen,
+ hmac_code, sizeof(hmac_code));
+ memcpy(data, hmac_code, sizeof(hmac_code));
+ break;
+ }
+
+ /* RFC3318, section 5.2 - restore giaddr and hops */
+ if (mp == 4) {
+ p = m + offsetof(struct bootp, hops);
+ *p = hops;
+ p = m + offsetof(struct bootp, giaddr);
+ memcpy(p, &giaddr, sizeof(giaddr));
+ }
+
+ /* Done! */
+ return (int)(dlen - sizeof(hmac_code)); /* should be zero */
+}
diff --git a/src/auth.h b/src/auth.h
new file mode 100644
index 000000000000..c6eb428512f6
--- /dev/null
+++ b/src/auth.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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_NONE 0
+#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;
+#ifdef AUTH
+ uint8_t protocol;
+ uint8_t algorithm;
+ uint8_t rdm;
+ uint64_t last_replay;
+ uint8_t last_replay_set;
+ struct token_head tokens;
+ uint32_t token_snd_secretid;
+ uint32_t token_rcv_secretid;
+#endif
+};
+
+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 void *, size_t, int, int,
+ const void *, size_t);
+
+struct dhcpcd_ctx;
+ssize_t dhcp_auth_encode(struct dhcpcd_ctx *, struct auth *,
+ const struct token *,
+ void *, size_t, int, int,
+ void *, size_t);
+
+int auth_get_rdm_monotonic(uint64_t *rdm);
+#endif
diff --git a/src/bpf.c b/src/bpf.c
new file mode 100644
index 000000000000..21b73af207be
--- /dev/null
+++ b/src/bpf.c
@@ -0,0 +1,713 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd: BPF arp and bootp filtering
+ * Copyright (c) 2006-2021 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/socket.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#ifdef __linux__
+/* Special BPF snowflake. */
+#include <linux/filter.h>
+#define bpf_insn sock_filter
+#else
+#include <net/bpf.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "arp.h"
+#include "bpf.h"
+#include "dhcp.h"
+#include "if.h"
+#include "logerr.h"
+
+/* BPF helper macros */
+#ifdef __linux__
+#define BPF_WHOLEPACKET 0x7fffffff /* work around buggy LPF filters */
+#else
+#define BPF_WHOLEPACKET ~0U
+#endif
+
+/* Macros to update the BPF structure */
+#define BPF_SET_STMT(insn, c, v) { \
+ (insn)->code = (c); \
+ (insn)->jt = 0; \
+ (insn)->jf = 0; \
+ (insn)->k = (uint32_t)(v); \
+}
+
+#define BPF_SET_JUMP(insn, c, v, t, f) { \
+ (insn)->code = (c); \
+ (insn)->jt = (t); \
+ (insn)->jf = (f); \
+ (insn)->k = (uint32_t)(v); \
+}
+
+size_t
+bpf_frame_header_len(const struct interface *ifp)
+{
+
+ switch (ifp->hwtype) {
+ case ARPHRD_ETHER:
+ return sizeof(struct ether_header);
+ default:
+ return 0;
+ }
+}
+
+void *
+bpf_frame_header_src(const struct interface *ifp, void *fh, size_t *len)
+{
+ uint8_t *f = fh;
+
+ switch (ifp->hwtype) {
+ case ARPHRD_ETHER:
+ *len = sizeof(((struct ether_header *)0)->ether_shost);
+ return f + offsetof(struct ether_header, ether_shost);
+ default:
+ *len = 0;
+ errno = ENOTSUP;
+ return NULL;
+ }
+}
+
+void *
+bpf_frame_header_dst(const struct interface *ifp, void *fh, size_t *len)
+{
+ uint8_t *f = fh;
+
+ switch (ifp->hwtype) {
+ case ARPHRD_ETHER:
+ *len = sizeof(((struct ether_header *)0)->ether_dhost);
+ return f + offsetof(struct ether_header, ether_dhost);
+ default:
+ *len = 0;
+ errno = ENOTSUP;
+ return NULL;
+ }
+}
+
+static const uint8_t etherbcastaddr[] =
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+int
+bpf_frame_bcast(const struct interface *ifp, const void *frame)
+{
+
+ switch (ifp->hwtype) {
+ case ARPHRD_ETHER:
+ return memcmp((const char *)frame +
+ offsetof(struct ether_header, ether_dhost),
+ etherbcastaddr, sizeof(etherbcastaddr));
+ default:
+ return -1;
+ }
+}
+
+#ifndef __linux__
+/* Linux is a special snowflake for opening, attaching and reading BPF.
+ * See if-linux.c for the Linux specific BPF functions. */
+
+const char *bpf_name = "Berkley Packet Filter";
+
+struct bpf *
+bpf_open(const struct interface *ifp,
+ int (*filter)(const struct bpf *, const struct in_addr *),
+ const struct in_addr *ia)
+{
+ struct bpf *bpf;
+ struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 };
+ struct ifreq ifr = { .ifr_flags = 0 };
+ int ibuf_len = 0;
+#ifdef BIOCIMMEDIATE
+ unsigned int flags;
+#endif
+#ifndef O_CLOEXEC
+ int fd_opts;
+#endif
+
+ bpf = calloc(1, sizeof(*bpf));
+ if (bpf == NULL)
+ return NULL;
+ bpf->bpf_ifp = ifp;
+
+#ifdef _PATH_BPF
+ bpf->bpf_fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK
+#ifdef O_CLOEXEC
+ | O_CLOEXEC
+#endif
+ );
+#else
+ char device[32];
+ int n = 0;
+
+ do {
+ snprintf(device, sizeof(device), "/dev/bpf%d", n++);
+ bpf->bpf_fd = open(device, O_RDWR | O_NONBLOCK
+#ifdef O_CLOEXEC
+ | O_CLOEXEC
+#endif
+ );
+ } while (bpf->bpf_fd == -1 && errno == EBUSY);
+#endif
+
+ if (bpf->bpf_fd == -1)
+ goto eexit;
+
+#ifndef O_CLOEXEC
+ if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 ||
+ fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1)
+ goto eexit;
+#endif
+
+ if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1)
+ goto eexit;
+ if (pv.bv_major != BPF_MAJOR_VERSION ||
+ pv.bv_minor < BPF_MINOR_VERSION) {
+ logerrx("BPF version mismatch - recompile");
+ goto eexit;
+ }
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1)
+ goto eexit;
+
+#ifdef BIOCIMMEDIATE
+ flags = 1;
+ if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1)
+ goto eexit;
+#endif
+
+ if (filter(bpf, ia) != 0)
+ goto eexit;
+
+ /* Get the required BPF buffer length from the kernel. */
+ if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1)
+ goto eexit;
+ bpf->bpf_size = (size_t)ibuf_len;
+ bpf->bpf_buffer = malloc(bpf->bpf_size);
+ if (bpf->bpf_buffer == NULL)
+ goto eexit;
+ return bpf;
+
+eexit:
+ if (bpf->bpf_fd != -1)
+ close(bpf->bpf_fd);
+ free(bpf);
+ return NULL;
+}
+
+/* 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
+bpf_read(struct bpf *bpf, void *data, size_t len)
+{
+ ssize_t bytes;
+ struct bpf_hdr packet;
+ const char *payload;
+
+ bpf->bpf_flags &= ~BPF_EOF;
+ for (;;) {
+ if (bpf->bpf_len == 0) {
+ bytes = read(bpf->bpf_fd, bpf->bpf_buffer,
+ bpf->bpf_size);
+#if defined(__sun)
+ /* After 2^31 bytes, the kernel offset overflows.
+ * To work around this bug, lseek 0. */
+ if (bytes == -1 && errno == EINVAL) {
+ lseek(bpf->bpf_fd, 0, SEEK_SET);
+ continue;
+ }
+#endif
+ if (bytes == -1 || bytes == 0)
+ return bytes;
+ bpf->bpf_len = (size_t)bytes;
+ bpf->bpf_pos = 0;
+ }
+ bytes = -1;
+ payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos;
+ memcpy(&packet, payload, sizeof(packet));
+ if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen >
+ bpf->bpf_len)
+ goto next; /* Packet beyond buffer, drop. */
+ payload += packet.bh_hdrlen;
+ if (packet.bh_caplen > len)
+ bytes = (ssize_t)len;
+ else
+ bytes = (ssize_t)packet.bh_caplen;
+ if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0)
+ bpf->bpf_flags |= BPF_BCAST;
+ else
+ bpf->bpf_flags &= ~BPF_BCAST;
+ memcpy(data, payload, (size_t)bytes);
+next:
+ bpf->bpf_pos += BPF_WORDALIGN(packet.bh_hdrlen +
+ packet.bh_caplen);
+ if (bpf->bpf_pos >= bpf->bpf_len) {
+ bpf->bpf_len = bpf->bpf_pos = 0;
+ bpf->bpf_flags |= BPF_EOF;
+ }
+ if (bytes != -1)
+ return bytes;
+ }
+
+ /* NOTREACHED */
+}
+
+int
+bpf_attach(int fd, void *filter, unsigned int filter_len)
+{
+ struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
+
+ /* Install the filter. */
+ return ioctl(fd, BIOCSETF, &pf);
+}
+
+#ifdef BIOCSETWF
+static int
+bpf_wattach(int fd, void *filter, unsigned int filter_len)
+{
+ struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
+
+ /* Install the filter. */
+ return ioctl(fd, BIOCSETWF, &pf);
+}
+#endif
+#endif
+
+#ifndef __sun
+/* SunOS is special too - sending via BPF goes nowhere. */
+ssize_t
+bpf_send(const struct bpf *bpf, uint16_t protocol,
+ const void *data, size_t len)
+{
+ struct iovec iov[2];
+ struct ether_header eh;
+
+ switch(bpf->bpf_ifp->hwtype) {
+ case ARPHRD_ETHER:
+ memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost));
+ memcpy(&eh.ether_shost, bpf->bpf_ifp->hwaddr,
+ sizeof(eh.ether_shost));
+ eh.ether_type = htons(protocol);
+ iov[0].iov_base = &eh;
+ iov[0].iov_len = sizeof(eh);
+ break;
+ default:
+ iov[0].iov_base = NULL;
+ iov[0].iov_len = 0;
+ break;
+ }
+ iov[1].iov_base = UNCONST(data);
+ iov[1].iov_len = len;
+ return writev(bpf->bpf_fd, iov, 2);
+}
+#endif
+
+void
+bpf_close(struct bpf *bpf)
+{
+
+ close(bpf->bpf_fd);
+ free(bpf->bpf_buffer);
+ free(bpf);
+}
+
+#ifdef ARP
+#define BPF_CMP_HWADDR_LEN ((((HWADDR_LEN / 4) + 2) * 2) + 1)
+static unsigned int
+bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off,
+ bool equal, const uint8_t *hwaddr, size_t hwaddr_len)
+{
+ struct bpf_insn *bp;
+ size_t maclen, nlft, njmps;
+ uint32_t mac32;
+ uint16_t mac16;
+ uint8_t jt, jf;
+
+ /* Calc the number of jumps */
+ if ((hwaddr_len / 4) >= 128) {
+ errno = EINVAL;
+ return 0;
+ }
+ njmps = (hwaddr_len / 4) * 2; /* 2 instructions per check */
+ /* We jump after the 1st check. */
+ if (njmps)
+ njmps -= 2;
+ nlft = hwaddr_len % 4;
+ if (nlft) {
+ njmps += (nlft / 2) * 2;
+ nlft = nlft % 2;
+ if (nlft)
+ njmps += 2;
+
+ }
+
+ /* Skip to positive finish. */
+ njmps++;
+ if (equal) {
+ jt = (uint8_t)njmps;
+ jf = 0;
+ } else {
+ jt = 0;
+ jf = (uint8_t)njmps;
+ }
+
+ bp = bpf;
+ for (; hwaddr_len > 0;
+ hwaddr += maclen, hwaddr_len -= maclen, off += maclen)
+ {
+ if (bpf_len < 3) {
+ errno = ENOBUFS;
+ return 0;
+ }
+ bpf_len -= 3;
+
+ if (hwaddr_len >= 4) {
+ maclen = sizeof(mac32);
+ memcpy(&mac32, hwaddr, maclen);
+ BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, off);
+ bp++;
+ BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+ htonl(mac32), jt, jf);
+ } else if (hwaddr_len >= 2) {
+ maclen = sizeof(mac16);
+ memcpy(&mac16, hwaddr, maclen);
+ BPF_SET_STMT(bp, BPF_LD + BPF_H + BPF_IND, off);
+ bp++;
+ BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+ htons(mac16), jt, jf);
+ } else {
+ maclen = sizeof(*hwaddr);
+ BPF_SET_STMT(bp, BPF_LD + BPF_B + BPF_IND, off);
+ bp++;
+ BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+ *hwaddr, jt, jf);
+ }
+ if (jt)
+ jt = (uint8_t)(jt - 2);
+ if (jf)
+ jf = (uint8_t)(jf - 2);
+ bp++;
+ }
+
+ /* Last step is always return failure.
+ * Next step is a positive finish. */
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
+ bp++;
+
+ return (unsigned int)(bp - bpf);
+}
+#endif
+
+#ifdef ARP
+static const struct bpf_insn bpf_arp_ether [] = {
+ /* Check this is an ARP packet. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+ offsetof(struct ether_header, ether_type)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+
+ /* Load frame header length into X */
+ BPF_STMT(BPF_LDX + BPF_W + BPF_IMM, sizeof(struct ether_header)),
+
+ /* Make sure the hardware type matches. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_hrd)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+
+ /* Make sure the hardware length matches. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_hln)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K,
+ sizeof(((struct ether_arp *)0)->arp_sha), 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define BPF_ARP_ETHER_LEN __arraycount(bpf_arp_ether)
+
+static const struct bpf_insn bpf_arp_filter [] = {
+ /* Make sure this is for IP. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_pro)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+ /* Make sure this is an ARP REQUEST. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_op)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),
+ /* or ARP REPLY. */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+ /* Make sure the protocol length matches. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_pln)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(in_addr_t), 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define BPF_ARP_FILTER_LEN __arraycount(bpf_arp_filter)
+
+/* One address is two checks of two statements. */
+#define BPF_NADDRS 1
+#define BPF_ARP_ADDRS_LEN 5 + ((BPF_NADDRS * 2) * 2)
+
+#define BPF_ARP_LEN BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN + \
+ BPF_CMP_HWADDR_LEN + BPF_ARP_ADDRS_LEN
+
+static int
+bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv)
+{
+ const struct interface *ifp = bpf->bpf_ifp;
+ struct bpf_insn buf[BPF_ARP_LEN + 1];
+ struct bpf_insn *bp;
+ uint16_t arp_len;
+
+ bp = buf;
+ /* Check frame header. */
+ switch(ifp->hwtype) {
+ case ARPHRD_ETHER:
+ memcpy(bp, bpf_arp_ether, sizeof(bpf_arp_ether));
+ bp += BPF_ARP_ETHER_LEN;
+ arp_len = sizeof(struct ether_header)+sizeof(struct ether_arp);
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Copy in the main filter. */
+ memcpy(bp, bpf_arp_filter, sizeof(bpf_arp_filter));
+ bp += BPF_ARP_FILTER_LEN;
+
+ /* Ensure it's not from us. */
+ bp += bpf_cmp_hwaddr(bp, BPF_CMP_HWADDR_LEN, sizeof(struct arphdr),
+ !recv, ifp->hwaddr, ifp->hwlen);
+
+ /* Match sender protocol address */
+ BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND,
+ sizeof(struct arphdr) + ifp->hwlen);
+ bp++;
+ BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1);
+ bp++;
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len);
+ bp++;
+
+ /* If we didn't match sender, then we're only interested in
+ * ARP probes to us, so check the null host sender. */
+ BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, INADDR_ANY, 1, 0);
+ bp++;
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
+ bp++;
+
+ /* Match target protocol address */
+ BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, (sizeof(struct arphdr) +
+ (size_t)(ifp->hwlen * 2) + sizeof(in_addr_t)));
+ bp++;
+ BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1);
+ bp++;
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len);
+ bp++;
+
+ /* No match, drop it */
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
+ bp++;
+
+#ifdef BIOCSETWF
+ if (!recv)
+ return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+#endif
+
+ return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+}
+
+int
+bpf_arp(const struct bpf *bpf, const struct in_addr *ia)
+{
+
+#ifdef BIOCSETWF
+ if (bpf_arp_rw(bpf, ia, true) == -1 ||
+ bpf_arp_rw(bpf, ia, false) == -1 ||
+ ioctl(bpf->bpf_fd, BIOCLOCK) == -1)
+ return -1;
+ return 0;
+#else
+ return bpf_arp_rw(bpf, ia, true);
+#endif
+}
+#endif
+
+#ifdef ARPHRD_NONE
+static const struct bpf_insn bpf_bootp_none[] = {
+};
+#define BPF_BOOTP_NONE_LEN __arraycount(bpf_bootp_none)
+#endif
+
+static const struct bpf_insn bpf_bootp_ether[] = {
+ /* Make sure this is an IP packet. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+ offsetof(struct ether_header, ether_type)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+
+ /* Advance to the IP header. */
+ BPF_STMT(BPF_LDX + BPF_K, sizeof(struct ether_header)),
+};
+#define BPF_BOOTP_ETHER_LEN __arraycount(bpf_bootp_ether)
+
+static const struct bpf_insn bpf_bootp_base[] = {
+ /* Make sure it's an IPv4 packet. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0),
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0xf0),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x40, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+
+ /* Make sure it's a UDP packet. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct ip, ip_p)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+
+ /* Make sure this isn't a fragment. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct ip, ip_off)),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 0, 1),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+
+ /* Advance to the UDP header. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0),
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x0f),
+ BPF_STMT(BPF_ALU + BPF_MUL + BPF_K, 4),
+ BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0),
+ BPF_STMT(BPF_MISC + BPF_TAX, 0),
+};
+#define BPF_BOOTP_BASE_LEN __arraycount(bpf_bootp_base)
+
+static const struct bpf_insn bpf_bootp_read[] = {
+ /* Make sure it's from and to the right port. */
+ BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define BPF_BOOTP_READ_LEN __arraycount(bpf_bootp_read)
+
+#ifdef BIOCSETWF
+static const struct bpf_insn bpf_bootp_write[] = {
+ /* Make sure it's from and to the right port. */
+ BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPC << 16) + BOOTPS, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define BPF_BOOTP_WRITE_LEN __arraycount(bpf_bootp_write)
+#endif
+
+#define BPF_BOOTP_CHADDR_LEN ((BOOTP_CHADDR_LEN / 4) * 3)
+#define BPF_BOOTP_XID_LEN 4 /* BOUND check is 4 instructions */
+
+#define BPF_BOOTP_LEN BPF_BOOTP_ETHER_LEN + \
+ BPF_BOOTP_BASE_LEN + BPF_BOOTP_READ_LEN + \
+ BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4
+
+static int
+bpf_bootp_rw(const struct bpf *bpf, bool read)
+{
+ struct bpf_insn buf[BPF_BOOTP_LEN + 1];
+ struct bpf_insn *bp;
+
+ bp = buf;
+ /* Check frame header. */
+ switch(bpf->bpf_ifp->hwtype) {
+#ifdef ARPHRD_NONE
+ case ARPHRD_NONE:
+ memcpy(bp, bpf_bootp_none, sizeof(bpf_bootp_none));
+ bp += BPF_BOOTP_NONE_LEN;
+ break;
+#endif
+ case ARPHRD_ETHER:
+ memcpy(bp, bpf_bootp_ether, sizeof(bpf_bootp_ether));
+ bp += BPF_BOOTP_ETHER_LEN;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Copy in the main filter. */
+ memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base));
+ bp += BPF_BOOTP_BASE_LEN;
+
+#ifdef BIOCSETWF
+ if (!read) {
+ memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write));
+ bp += BPF_BOOTP_WRITE_LEN;
+
+ /* All passed, return the packet. */
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
+ bp++;
+
+ return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+ }
+#else
+ UNUSED(read);
+#endif
+
+ memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read));
+ bp += BPF_BOOTP_READ_LEN;
+
+ /* All passed, return the packet. */
+ BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
+ bp++;
+
+ return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+}
+
+int
+bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia)
+{
+
+#ifdef BIOCSETWF
+ if (bpf_bootp_rw(bpf, true) == -1 ||
+ bpf_bootp_rw(bpf, false) == -1 ||
+ ioctl(bpf->bpf_fd, BIOCLOCK) == -1)
+ return -1;
+ return 0;
+#else
+#ifdef PRIVSEP
+#if defined(__sun) /* Solaris cannot send via BPF. */
+#elif defined(BIOCSETF)
+#warning No BIOCSETWF support - a compromised BPF can be used as a raw socket
+#else
+#warning A compromised PF_PACKET socket can be used as a raw socket
+#endif
+#endif
+ return bpf_bootp_rw(bpf, true);
+#endif
+}
diff --git a/src/bpf.h b/src/bpf.h
new file mode 100644
index 000000000000..40da95e61ee6
--- /dev/null
+++ b/src/bpf.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd: BPF arp and bootp filtering
+ * Copyright (c) 2006-2021 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 BPF_HEADER
+#define BPF_HEADER
+
+#define BPF_EOF 0x01U
+#define BPF_PARTIALCSUM 0x02U
+#define BPF_BCAST 0x04U
+
+/*
+ * Even though we program the BPF filter should we trust it?
+ * On Linux at least there is a window between opening the socket,
+ * binding the interface and setting the filter where we receive data.
+ * This data is NOT checked OR flushed and IS returned when reading.
+ * We have no way of flushing it other than reading these packets!
+ * But we don't know if they passed the filter or not ..... so we need
+ * to validate each and every packet that comes through ourselves as well.
+ * Even if Linux does fix this sorry state, who is to say other kernels
+ * don't have bugs causing a similar effect?
+ *
+ * As such, let's strive to keep the filters just for pattern matching
+ * to avoid waking dhcpcd up.
+ *
+ * If you want to be notified of any packet failing the BPF filter,
+ * define BPF_DEBUG below.
+ */
+//#define BPF_DEBUG
+
+#include "dhcpcd.h"
+
+struct bpf {
+ const struct interface *bpf_ifp;
+ int bpf_fd;
+ unsigned int bpf_flags;
+ void *bpf_buffer;
+ size_t bpf_size;
+ size_t bpf_len;
+ size_t bpf_pos;
+};
+
+extern const char *bpf_name;
+size_t bpf_frame_header_len(const struct interface *);
+void *bpf_frame_header_src(const struct interface *, void *, size_t *);
+void *bpf_frame_header_dst(const struct interface *, void *, size_t *);
+int bpf_frame_bcast(const struct interface *, const void *);
+struct bpf * bpf_open(const struct interface *,
+ int (*)(const struct bpf *, const struct in_addr *),
+ const struct in_addr *);
+void bpf_close(struct bpf *);
+int bpf_attach(int, void *, unsigned int);
+ssize_t bpf_send(const struct bpf *, uint16_t, const void *, size_t);
+ssize_t bpf_read(struct bpf *, void *, size_t);
+int bpf_arp(const struct bpf *, const struct in_addr *);
+int bpf_bootp(const struct bpf *, const struct in_addr *);
+#endif
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 000000000000..bb89100ef2cf
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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/statvfs.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "dhcpcd.h"
+#include "if-options.h"
+
+const char *
+hwaddr_ntoa(const void *hwaddr, size_t hwlen, char *buf, size_t buflen)
+{
+ const unsigned char *hp, *ep;
+ char *p;
+
+ if (buf == NULL || hwlen == 0)
+ return NULL;
+
+ if (hwlen * 3 > buflen) {
+ errno = ENOBUFS;
+ return NULL;
+ }
+
+ hp = hwaddr;
+ ep = hp + hwlen;
+ p = buf;
+
+ while (hp < ep) {
+ if (hp != hwaddr)
+ *p ++= ':';
+ p += snprintf(p, 3, "%.2x", *hp++);
+ }
+ *p ++= '\0';
+ return buf;
+}
+
+size_t
+hwaddr_aton(uint8_t *buffer, const char *addr)
+{
+ char c[3];
+ const char *p = addr;
+ uint8_t *bp = buffer;
+ size_t len = 0;
+
+ c[2] = '\0';
+ while (*p != '\0') {
+ /* Skip separators */
+ c[0] = *p++;
+ switch (c[0]) {
+ case '\n': /* long duid split on lines */
+ case ':': /* typical mac address */
+ case '-': /* uuid */
+ continue;
+ }
+ 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;
+ }
+ if (bp)
+ *bp++ = (uint8_t)strtol(c, NULL, 16);
+ len++;
+ }
+ return len;
+}
+
+ssize_t
+readfile(const char *file, void *data, size_t len)
+{
+ int fd;
+ ssize_t bytes;
+
+ fd = open(file, O_RDONLY);
+ if (fd == -1)
+ return -1;
+ bytes = read(fd, data, len);
+ close(fd);
+ if ((size_t)bytes == len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ return bytes;
+}
+
+ssize_t
+writefile(const char *file, mode_t mode, const void *data, size_t len)
+{
+ int fd;
+ ssize_t bytes;
+
+ fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
+ if (fd == -1)
+ return -1;
+ bytes = write(fd, data, len);
+ close(fd);
+ return bytes;
+}
+
+int
+filemtime(const char *file, time_t *time)
+{
+ struct stat st;
+
+ if (stat(file, &st) == -1)
+ return -1;
+ *time = st.st_mtime;
+ return 0;
+}
+
+/* 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. */
+char *
+get_line(char ** __restrict buf, ssize_t * __restrict buflen)
+{
+ char *p, *c;
+ bool quoted;
+
+ do {
+ p = *buf;
+ if (*buf == NULL)
+ return NULL;
+ c = memchr(*buf, '\n', (size_t)*buflen);
+ if (c == NULL) {
+ c = memchr(*buf, '\0', (size_t)*buflen);
+ if (c == NULL)
+ return NULL;
+ *buflen = c - *buf;
+ *buf = NULL;
+ } else {
+ *c++ = '\0';
+ *buflen -= c - *buf;
+ *buf = c;
+ }
+ for (; *p == ' ' || *p == '\t'; p++)
+ ;
+ } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';');
+
+ /* Strip embedded comments unless in a quoted string or escaped */
+ quoted = false;
+ for (c = p; *c != '\0'; c++) {
+ if (*c == '\\') {
+ c++; /* escaped */
+ continue;
+ }
+ if (*c == '"')
+ quoted = !quoted;
+ else if (*c == '#' && !quoted) {
+ *c = '\0';
+ break;
+ }
+ }
+ return p;
+}
+
+
+int
+is_root_local(void)
+{
+#ifdef ST_LOCAL
+ struct statvfs vfs;
+
+ if (statvfs("/", &vfs) == -1)
+ return -1;
+ return vfs.f_flag & ST_LOCAL ? 1 : 0;
+#else
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 000000000000..ff8f3f8b943b
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <sys/types.h>
+#include <stdint.h>
+#include <stdio.h>
+
+/* Define eloop queues here, as other apps share eloop.h */
+#define ELOOP_DHCPCD 1 /* default queue */
+#define ELOOP_DHCP 2
+#define ELOOP_ARP 3
+#define ELOOP_IPV4LL 4
+#define ELOOP_IPV6 5
+#define ELOOP_IPV6ND 6
+#define ELOOP_IPV6RA_EXPIRE 7
+#define ELOOP_DHCP6 8
+#define ELOOP_IF 9
+
+#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 ROUNDUP4(a) (1 + (((a) - 1) | 3))
+#define ROUNDUP8(a) (1 + (((a) - 1) | 7))
+
+/* 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)
+#endif
+
+#if __GNUC__ > 2 || defined(__INTEL_COMPILER)
+# ifndef __packed
+# define __packed __attribute__((__packed__))
+# endif
+# ifndef __unused
+# define __unused __attribute__((__unused__))
+# endif
+#else
+# ifndef __packed
+# define __packed
+# endif
+# ifndef __unused
+# define __unused
+# endif
+#endif
+
+/* Needed for rbtree(3) compat */
+#ifndef __RCSID
+#define __RCSID(a)
+#endif
+#ifndef __predict_false
+# if __GNUC__ > 2
+# define __predict_true(exp) __builtin_expect((exp) != 0, 1)
+# define __predict_false(exp) __builtin_expect((exp) != 0, 0)
+#else
+# define __predict_true(exp) (exp)
+# define __predict_false(exp) (exp)
+# endif
+#endif
+#ifndef __BEGIN_DECLS
+# if defined(__cplusplus)
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS };
+# else /* __BEGIN_DECLS */
+# define __BEGIN_DECLS
+# define __END_DECLS
+# endif /* __BEGIN_DECLS */
+#endif /* __BEGIN_DECLS */
+
+#ifndef __fallthrough
+# if __GNUC__ >= 7
+# define __fallthrough __attribute__((fallthrough))
+# else
+# define __fallthrough
+# endif
+#endif
+
+/*
+ * Compile Time Assertion.
+ */
+#ifndef __CTASSERT
+# ifdef __COUNTER__
+# define __CTASSERT(x) __CTASSERT0(x, __ctassert, __COUNTER__)
+# else
+# define __CTASSERT(x) __CTASSERT99(x, __INCLUDE_LEVEL__, __LINE__)
+# define __CTASSERT99(x, a, b) __CTASSERT0(x, __CONCAT(__ctassert,a), \
+ __CONCAT(_,b))
+# endif
+# define __CTASSERT0(x, y, z) __CTASSERT1(x, y, z)
+# define __CTASSERT1(x, y, z) typedef char y ## z[/*CONSTCOND*/(x) ? 1 : -1] __unused
+#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
+
+const char *hwaddr_ntoa(const void *, size_t, char *, size_t);
+size_t hwaddr_aton(uint8_t *, const char *);
+ssize_t readfile(const char *, void *, size_t);
+ssize_t writefile(const char *, mode_t, const void *, size_t);
+int filemtime(const char *, time_t *);
+char *get_line(char ** __restrict, ssize_t * __restrict);
+int is_root_local(void);
+#endif
diff --git a/src/control.c b/src/control.c
new file mode 100644
index 000000000000..4d768d887b9f
--- /dev/null
+++ b/src/control.c
@@ -0,0 +1,594 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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"
+#include "logerr.h"
+#include "privsep.h"
+
+#ifndef SUN_LEN
+#define SUN_LEN(su) \
+ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
+
+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->data_size != 0)
+ free(fdp->data);
+ free(fdp);
+ }
+
+#ifdef CTL_FREE_LIST
+ while ((fdp = TAILQ_FIRST(&fd->free_queue))) {
+ TAILQ_REMOVE(&fd->free_queue, fdp, next);
+ if (fdp->data_size != 0)
+ free(fdp->data);
+ free(fdp);
+ }
+#endif
+}
+
+void
+control_free(struct fd_list *fd)
+{
+
+#ifdef PRIVSEP
+ if (fd->ctx->ps_control_client == fd)
+ fd->ctx->ps_control_client = NULL;
+#endif
+
+ if (eloop_event_remove_writecb(fd->ctx->eloop, fd->fd) == -1)
+ logerr(__func__);
+ TAILQ_REMOVE(&fd->ctx->control_fds, fd, next);
+ control_queue_free(fd);
+ free(fd);
+}
+
+void
+control_delete(struct fd_list *fd)
+{
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(fd->ctx))
+ return;
+#endif
+
+ eloop_event_delete(fd->ctx->eloop, fd->fd);
+ close(fd->fd);
+ control_free(fd);
+}
+
+static void
+control_handle_data(void *arg)
+{
+ struct fd_list *fd = arg;
+ char buffer[1024];
+ ssize_t bytes;
+
+ 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;
+ }
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(fd->ctx)) {
+ ssize_t err;
+
+ fd->flags |= FD_SENDLEN;
+ err = ps_ctl_handleargs(fd, buffer, (size_t)bytes);
+ fd->flags &= ~FD_SENDLEN;
+ if (err == -1) {
+ logerr(__func__);
+ return;
+ }
+ if (err == 1 &&
+ ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) {
+ logerr(__func__);
+ control_delete(fd);
+ }
+ return;
+ }
+#endif
+
+ control_recvdata(fd, buffer, (size_t)bytes);
+}
+
+void
+control_recvdata(struct fd_list *fd, char *data, size_t len)
+{
+ char *p = data, *e;
+ char *argvp[255], **ap;
+ int argc;
+
+ /* Each command is \n terminated
+ * Each argument is NULL separated */
+ while (len != 0) {
+ argc = 0;
+ ap = argvp;
+ while (len != 0) {
+ if (*p == '\0') {
+ p++;
+ len--;
+ continue;
+ }
+ e = memchr(p, '\0', len);
+ if (e == NULL) {
+ errno = EINVAL;
+ logerrx("%s: no terminator", __func__);
+ return;
+ }
+ if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) {
+ errno = ENOBUFS;
+ logerrx("%s: no arg buffer", __func__);
+ return;
+ }
+ *ap++ = p;
+ argc++;
+ e++;
+ len -= (size_t)(e - p);
+ p = e;
+ e--;
+ if (*(--e) == '\n') {
+ *e = '\0';
+ break;
+ }
+ }
+ if (argc == 0) {
+ logerrx("%s: no args", __func__);
+ continue;
+ }
+ *ap = NULL;
+ if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) {
+ logerr(__func__);
+ if (errno != EINTR && errno != EAGAIN) {
+ control_delete(fd);
+ return;
+ }
+ }
+ }
+}
+
+struct fd_list *
+control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags)
+{
+ struct fd_list *l;
+
+ l = malloc(sizeof(*l));
+ if (l == NULL)
+ return NULL;
+
+ l->ctx = ctx;
+ l->fd = fd;
+ l->flags = flags;
+ TAILQ_INIT(&l->queue);
+#ifdef CTL_FREE_LIST
+ TAILQ_INIT(&l->free_queue);
+#endif
+ TAILQ_INSERT_TAIL(&ctx->control_fds, l, next);
+ return l;
+}
+
+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)
+ goto error;
+ if ((flags = fcntl(fd, F_GETFD, 0)) == -1 ||
+ fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ goto error;
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1 ||
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+ goto error;
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ctx) && !IN_PRIVSEP_SE(ctx))
+ ;
+ else
+#endif
+ fd_flags |= FD_SENDLEN;
+
+ l = control_new(ctx, fd, fd_flags);
+ if (l == NULL)
+ goto error;
+
+ if (eloop_event_add(ctx->eloop, l->fd, control_handle_data, l) == -1)
+ logerr(__func__);
+ return;
+
+error:
+ logerr(__func__);
+ if (fd != -1)
+ 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_path(char *path, size_t len, const char *ifname, sa_family_t family,
+ bool unpriv)
+{
+ const char *per;
+ const char *sunpriv;
+
+ switch(family) {
+ case AF_INET:
+ per = "-4";
+ break;
+ case AF_INET6:
+ per = "-6";
+ break;
+ default:
+ per = "";
+ break;
+ }
+ if (unpriv)
+ sunpriv = ifname ? ".unpriv" : "unpriv.";
+ else
+ sunpriv = "";
+ return snprintf(path, len, CONTROLSOCKET,
+ ifname ? ifname : "", ifname ? per : "",
+ sunpriv, ifname ? "." : "");
+}
+
+static int
+make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family,
+ bool unpriv)
+{
+ int fd;
+
+ if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0)) == -1)
+ return -1;
+ memset(sa, 0, sizeof(*sa));
+ sa->sun_family = AF_UNIX;
+ make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family, unpriv);
+ 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, sa_family_t family,
+ mode_t fmode)
+{
+ struct sockaddr_un sa;
+ int fd;
+ socklen_t len;
+
+ fd = make_sock(&sa, ifname, family, (fmode & S_UNPRIV) == S_UNPRIV);
+ if (fd == -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;
+ }
+
+#ifdef PRIVSEP_RIGHTS
+ if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1) {
+ close(fd);
+ unlink(sa.sun_path);
+ return -1;
+ }
+#endif
+
+ if ((fmode & S_UNPRIV) == S_UNPRIV)
+ strlcpy(ctx->control_sock_unpriv, sa.sun_path,
+ sizeof(ctx->control_sock_unpriv));
+ else
+ strlcpy(ctx->control_sock, sa.sun_path,
+ sizeof(ctx->control_sock));
+ return fd;
+}
+
+int
+control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family)
+{
+ int fd;
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ctx)) {
+ make_path(ctx->control_sock, sizeof(ctx->control_sock),
+ ifname, family, false);
+ make_path(ctx->control_sock_unpriv,
+ sizeof(ctx->control_sock_unpriv),
+ ifname, family, true);
+ return 0;
+ }
+#endif
+
+ if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1)
+ return -1;
+
+ ctx->control_fd = fd;
+ eloop_event_add(ctx->eloop, fd, control_handle, ctx);
+
+ if ((fd = control_start1(ctx, ifname, family, S_UNPRIV)) != -1) {
+ ctx->control_unpriv_fd = fd;
+ eloop_event_add(ctx->eloop, fd, control_handle_unpriv, ctx);
+ }
+ return ctx->control_fd;
+}
+
+static int
+control_unlink(struct dhcpcd_ctx *ctx, const char *file)
+{
+ int retval = 0;
+
+ errno = 0;
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ctx))
+ retval = (int)ps_root_unlink(ctx, file);
+ else
+#else
+ UNUSED(ctx);
+#endif
+ retval = unlink(file);
+
+ return retval == -1 && errno != ENOENT ? -1 : 0;
+}
+
+int
+control_stop(struct dhcpcd_ctx *ctx)
+{
+ int retval = 0;
+ struct fd_list *l;
+
+ while ((l = TAILQ_FIRST(&ctx->control_fds)) != NULL) {
+ control_free(l);
+ }
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ctx)) {
+ if (ps_root_unlink(ctx, ctx->control_sock) == -1)
+ retval = -1;
+ if (ps_root_unlink(ctx, ctx->control_sock_unpriv) == -1)
+ retval = -1;
+ return retval;
+ } else if (ctx->options & DHCPCD_FORKED)
+ return retval;
+#endif
+
+ if (ctx->control_fd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->control_fd);
+ close(ctx->control_fd);
+ ctx->control_fd = -1;
+ if (control_unlink(ctx, 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 (control_unlink(ctx, ctx->control_sock_unpriv) == -1)
+ retval = -1;
+ }
+
+ return retval;
+}
+
+int
+control_open(const char *ifname, sa_family_t family, bool unpriv)
+{
+ struct sockaddr_un sa;
+ int fd;
+
+ if ((fd = make_sock(&sa, ifname, family, unpriv)) != -1) {
+ socklen_t len;
+
+ len = (socklen_t)SUN_LEN(&sa);
+ if (connect(fd, (struct sockaddr *)&sa, len) == -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ return fd;
+}
+
+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];
+ int iov_len;
+ struct fd_data *data;
+
+ fd = arg;
+ data = TAILQ_FIRST(&fd->queue);
+
+ if (data->data_flags & FD_SENDLEN) {
+ 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;
+ iov_len = 2;
+ } else {
+ iov[0].iov_base = data->data;
+ iov[0].iov_len = data->data_len;
+ iov_len = 1;
+ }
+
+ if (writev(fd->fd, iov, iov_len) == -1) {
+ logerr("%s: write", __func__);
+ control_delete(fd);
+ return;
+ }
+
+ TAILQ_REMOVE(&fd->queue, data, next);
+#ifdef CTL_FREE_LIST
+ TAILQ_INSERT_TAIL(&fd->free_queue, data, next);
+#else
+ if (data->data_size != 0)
+ free(data->data);
+ free(data);
+#endif
+
+ if (TAILQ_FIRST(&fd->queue) != NULL)
+ return;
+
+ if (eloop_event_remove_writecb(fd->ctx->eloop, fd->fd) == -1)
+ logerr(__func__);
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) {
+ if (ps_ctl_sendeof(fd) == -1)
+ logerr(__func__);
+ control_free(fd);
+ }
+#endif
+}
+
+int
+control_queue(struct fd_list *fd, void *data, size_t data_len)
+{
+ struct fd_data *d;
+
+ if (data_len == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#ifdef CTL_FREE_LIST
+ struct fd_data *df;
+
+ d = NULL;
+ TAILQ_FOREACH(df, &fd->free_queue, next) {
+ if (d == NULL || d->data_size < df->data_size) {
+ d = df;
+ if (d->data_size <= data_len)
+ break;
+ }
+ }
+ if (d != NULL)
+ TAILQ_REMOVE(&fd->free_queue, d, next);
+ else
+#endif
+ {
+ d = calloc(1, sizeof(*d));
+ if (d == NULL)
+ return -1;
+ }
+
+ if (d->data_size == 0)
+ d->data = NULL;
+ if (d->data_size < data_len) {
+ void *nbuf = realloc(d->data, data_len);
+ if (nbuf == NULL) {
+ free(d->data);
+ free(d);
+ return -1;
+ }
+ d->data = nbuf;
+ d->data_size = data_len;
+ }
+ memcpy(d->data, data, data_len);
+ d->data_len = data_len;
+ d->data_flags = fd->flags & FD_SENDLEN;
+
+ TAILQ_INSERT_TAIL(&fd->queue, d, next);
+ eloop_event_add_w(fd->ctx->eloop, fd->fd, control_writeone, fd);
+ return 0;
+}
diff --git a/src/control.h b/src/control.h
new file mode 100644
index 000000000000..110b0a7b2f0b
--- /dev/null
+++ b/src/control.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <stdbool.h>
+
+#include "dhcpcd.h"
+
+#if !defined(CTL_FREE_LIST)
+#define CTL_FREE_LIST 1
+#elif CTL_FREE_LIST == 0
+#undef CTL_FREE_LIST
+#endif
+
+/* Limit queue size per fd */
+#define CONTROL_QUEUE_MAX 100
+
+struct fd_data {
+ TAILQ_ENTRY(fd_data) next;
+ void *data;
+ size_t data_size;
+ size_t data_len;
+ unsigned int data_flags;
+};
+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;
+#ifdef CTL_FREE_LIST
+ struct fd_data_head free_queue;
+#endif
+};
+TAILQ_HEAD(fd_list_head, fd_list);
+
+#define FD_LISTEN 0x01U
+#define FD_UNPRIV 0x02U
+#define FD_SENDLEN 0x04U
+
+int control_start(struct dhcpcd_ctx *, const char *, sa_family_t);
+int control_stop(struct dhcpcd_ctx *);
+int control_open(const char *, sa_family_t, bool);
+ssize_t control_send(struct dhcpcd_ctx *, int, char * const *);
+struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int);
+void control_free(struct fd_list *);
+void control_delete(struct fd_list *);
+int control_queue(struct fd_list *, void *, size_t);
+void control_recvdata(struct fd_list *fd, char *, size_t);
+#endif
diff --git a/src/defs.h b/src/defs.h
new file mode 100644
index 000000000000..cde76f8857b0
--- /dev/null
+++ b/src/defs.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 "9.4.1"
+
+#ifndef PRIVSEP_USER
+# define PRIVSEP_USER "_" PACKAGE
+#endif
+
+#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 DBDIR "/duid"
+#endif
+#ifndef SECRET
+# define SECRET DBDIR "/secret"
+#endif
+#ifndef LEASEFILE
+# define LEASEFILE DBDIR "/%s%s.lease"
+#endif
+#ifndef LEASEFILE6
+# define LEASEFILE6 LEASEFILE "6"
+#endif
+#ifndef PIDFILE
+# define PIDFILE RUNDIR "/%s%s%spid"
+#endif
+#ifndef CONTROLSOCKET
+# define CONTROLSOCKET RUNDIR "/%s%s%s%ssock"
+#endif
+#ifndef RDM_MONOFILE
+# define RDM_MONOFILE DBDIR "/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
diff --git a/src/dev.c b/src/dev.c
new file mode 100644
index 000000000000..d33c8faaa230
--- /dev/null
+++ b/src/dev.c
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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.
+ */
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define _INDEV
+#include "common.h"
+#include "dev.h"
+#include "eloop.h"
+#include "dhcpcd.h"
+#include "logerr.h"
+
+int
+dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return ps_root_dev_initialised(ctx, ifname);
+#endif
+
+ if (ctx->dev == NULL)
+ return 1;
+ return ctx->dev->initialised(ifname);
+}
+
+int
+dev_listening(struct dhcpcd_ctx *ctx)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return ps_root_dev_listening(ctx);
+#endif
+
+ if (ctx->dev == NULL)
+ return 0;
+ return ctx->dev->listening();
+}
+
+static void
+dev_stop1(struct dhcpcd_ctx *ctx, int stop)
+{
+
+ if (ctx->dev) {
+ if (stop)
+ logdebugx("dev: unloaded %s", ctx->dev->name);
+ eloop_event_delete(ctx->eloop, ctx->dev_fd);
+ ctx->dev->stop();
+ free(ctx->dev);
+ ctx->dev = NULL;
+ ctx->dev_fd = -1;
+ }
+ if (ctx->dev_handle) {
+ dlclose(ctx->dev_handle);
+ ctx->dev_handle = NULL;
+ }
+}
+
+void
+dev_stop(struct dhcpcd_ctx *ctx)
+{
+
+ dev_stop1(ctx, !(ctx->options & DHCPCD_FORKED));
+}
+
+static int
+dev_start2(struct dhcpcd_ctx *ctx, const struct dev_dhcpcd *dev_dhcpcd,
+ const char *name)
+{
+ char file[PATH_MAX];
+ void *h;
+ void (*fptr)(struct dev *, const struct dev_dhcpcd *);
+ int r;
+
+ snprintf(file, sizeof(file), DEVDIR "/%s", name);
+ h = dlopen(file, RTLD_LAZY);
+ if (h == NULL) {
+ logerrx("dlopen: %s", dlerror());
+ return -1;
+ }
+ fptr = dlsym(h, "dev_init");
+ if (fptr == NULL) {
+ logerrx("dlsym: %s", dlerror());
+ dlclose(h);
+ return -1;
+ }
+ if ((ctx->dev = calloc(1, sizeof(*ctx->dev))) == NULL) {
+ logerr("%s: calloc", __func__);
+ dlclose(h);
+ return -1;
+ }
+ fptr(ctx->dev, dev_dhcpcd);
+ if (ctx->dev->start == NULL || (r = ctx->dev->start()) == -1) {
+ free(ctx->dev);
+ ctx->dev = NULL;
+ dlclose(h);
+ return -1;
+ }
+ loginfox("dev: loaded %s", ctx->dev->name);
+ ctx->dev_handle = h;
+ return r;
+}
+
+static int
+dev_start1(struct dhcpcd_ctx *ctx, const struct dev_dhcpcd *dev_dhcpcd)
+{
+ DIR *dp;
+ struct dirent *d;
+ int r;
+
+ if (ctx->dev) {
+ logerrx("dev: already started %s", ctx->dev->name);
+ return -1;
+ }
+
+ if (ctx->dev_load)
+ return dev_start2(ctx, dev_dhcpcd, ctx->dev_load);
+
+ dp = opendir(DEVDIR);
+ if (dp == NULL) {
+ logdebug("dev: %s", DEVDIR);
+ return 0;
+ }
+
+ r = 0;
+ while ((d = readdir(dp))) {
+ if (d->d_name[0] == '.')
+ continue;
+
+ r = dev_start2(ctx, dev_dhcpcd, d->d_name);
+ if (r != -1)
+ break;
+ }
+ closedir(dp);
+ return r;
+}
+
+static void
+dev_handle_data(void *arg)
+{
+ struct dhcpcd_ctx *ctx;
+
+ ctx = arg;
+ if (ctx->dev->handle_device(arg) == -1) {
+ /* XXX: an error occured. should we restart dev? */
+ }
+}
+
+int
+dev_start(struct dhcpcd_ctx *ctx, int (*handler)(void *, int, const char *))
+{
+ struct dev_dhcpcd dev_dhcpcd = {
+ .handle_interface = handler,
+ };
+
+ if (ctx->dev_fd != -1) {
+ logerrx("%s: already started on fd %d", __func__, ctx->dev_fd);
+ return ctx->dev_fd;
+ }
+
+ ctx->dev_fd = dev_start1(ctx, &dev_dhcpcd);
+ if (ctx->dev_fd != -1) {
+ if (eloop_event_add(ctx->eloop, ctx->dev_fd,
+ dev_handle_data, ctx) == -1)
+ {
+ logerr(__func__);
+ dev_stop1(ctx, 1);
+ return -1;
+ }
+ }
+
+ return ctx->dev_fd;
+}
diff --git a/src/dev.h b/src/dev.h
new file mode 100644
index 000000000000..e7263c5b87da
--- /dev/null
+++ b/src/dev.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 (*initialised)(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_initialised(struct dhcpcd_ctx *, const char *);
+int dev_listening(struct dhcpcd_ctx *);
+int dev_start(struct dhcpcd_ctx *, int (*)(void *, int, const char *));
+void dev_stop(struct dhcpcd_ctx *);
+#endif
+
+#endif
diff --git a/src/dev/Makefile b/src/dev/Makefile
new file mode 100644
index 000000000000..7961149f390d
--- /dev/null
+++ b/src/dev/Makefile
@@ -0,0 +1,45 @@
+TOP= ../../
+include ${TOP}/Makefile.inc
+include ${TOP}/config.mk
+
+CFLAGS?= -O2
+CSTD?= c99
+CFLAGS+= -std=${CSTD}
+CPPFLAGS+= -I${TOP} -I${TOP}/src
+
+DEVDIR= ${LIBDIR}/dhcpcd/dev
+DSRC= ${DEV_PLUGINS:=.c}
+DOBJ= ${DSRC:.c=.o}
+DSOBJ= ${DOBJ:.o=.So}
+DPLUGS= ${DEV_PLUGINS:=.so}
+
+CLEANFILES+= ${DSOBJ} ${DPLUGS}
+
+.SUFFIXES: .So .so
+
+.c.So:
+ ${CC} ${PICFLAG} -DPIC ${CPPFLAGS} ${CFLAGS} -c $< -o $@
+
+.So.so: ${DSOBJ}
+ ${CC} ${LDFLAGS} -shared -Wl,-x -o $@ -Wl,-soname,$@ \
+ $< ${LIBS}
+
+all: ${DPLUGS}
+
+udev.So:
+CFLAGS+= ${LIBUDEV_CFLAGS}
+CPPFLAGS+= ${LIBUDEV_CPPFLAGS}
+
+udev.so:
+LIBS+= ${LIBUDEV_LIBS}
+
+proginstall: ${DPLUGS}
+ ${INSTALL} -d ${DESTDIR}${DEVDIR}
+ ${INSTALL} -m ${BINMODE} ${PROG} ${DPLUGS} ${DESTDIR}${DEVDIR}
+
+eginstall:
+
+install: proginstall
+
+clean:
+ rm -f ${CLEANFILES}
diff --git a/src/dev/udev.c b/src/dev/udev.c
new file mode 100644
index 000000000000..552f37bc8505
--- /dev/null
+++ b/src/dev/udev.c
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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.
+ */
+
+#ifdef LIBUDEV_NOINIT
+# define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
+# warning This version of udev is too old does not support
+# warning per device initialization checks.
+# warning As such, dhcpcd will need to depend on the
+# warning udev-settle service or similar if starting
+# warning in master mode.
+#endif
+
+#include <libudev.h>
+#include <string.h>
+
+#include "../common.h"
+#include "../dev.h"
+#include "../if.h"
+#include "../logerr.h"
+
+static const char udev_name[] = "udev";
+static struct udev *udev;
+static struct udev_monitor *monitor;
+
+static struct dev_dhcpcd dhcpcd;
+
+static int
+udev_listening(void)
+{
+
+ return monitor ? 1 : 0;
+}
+
+static int
+udev_initialised(const char *ifname)
+{
+ struct udev_device *device;
+ int r;
+
+ device = udev_device_new_from_subsystem_sysname(udev, "net", ifname);
+ if (device) {
+#ifndef LIBUDEV_NOINIT
+ r = udev_device_get_is_initialized(device);
+#else
+ r = 1;
+#endif
+ udev_device_unref(device);
+ } else
+ r = 0;
+ return r;
+}
+
+static int
+udev_handle_device(void *ctx)
+{
+ struct udev_device *device;
+ const char *subsystem, *ifname, *action;
+
+ device = udev_monitor_receive_device(monitor);
+ if (device == NULL) {
+ logerrx("libudev: received NULL device");
+ return -1;
+ }
+
+ subsystem = udev_device_get_subsystem(device);
+ ifname = udev_device_get_sysname(device);
+ action = udev_device_get_action(device);
+
+ /* udev filter documentation says "usually" so double check */
+ if (strcmp(subsystem, "net") == 0) {
+ logdebugx("%s: libudev: %s", ifname, action);
+ if (strcmp(action, "add") == 0 || strcmp(action, "move") == 0)
+ dhcpcd.handle_interface(ctx, 1, ifname);
+ else if (strcmp(action, "remove") == 0)
+ dhcpcd.handle_interface(ctx, -1, ifname);
+ }
+
+ udev_device_unref(device);
+ return 1;
+}
+
+static void
+udev_stop(void)
+{
+
+ if (monitor) {
+ udev_monitor_unref(monitor);
+ monitor = NULL;
+ }
+
+ if (udev) {
+ udev_unref(udev);
+ udev = NULL;
+ }
+}
+
+static int
+udev_start(void)
+{
+ char netns[PATH_MAX];
+ int fd;
+
+ if (if_getnetworknamespace(netns, sizeof(netns)) != NULL) {
+ logdebugx("udev does not work in a network namespace");
+ return -1;
+ }
+
+ if (udev) {
+ logerrx("udev: already started");
+ return -1;
+ }
+
+ logdebugx("udev: starting");
+ udev = udev_new();
+ if (udev == NULL) {
+ logerr("udev_new");
+ return -1;
+ }
+ monitor = udev_monitor_new_from_netlink(udev, "udev");
+ if (monitor == NULL) {
+ logerr("udev_monitor_new_from_netlink");
+ goto bad;
+ }
+#ifndef LIBUDEV_NOFILTER
+ if (udev_monitor_filter_add_match_subsystem_devtype(monitor,
+ "net", NULL) != 0)
+ {
+ logerr("udev_monitor_filter_add_match_subsystem_devtype");
+ goto bad;
+ }
+#endif
+ if (udev_monitor_enable_receiving(monitor) != 0) {
+ logerr("udev_monitor_enable_receiving");
+ goto bad;
+ }
+ fd = udev_monitor_get_fd(monitor);
+ if (fd == -1) {
+ logerr("udev_monitor_get_fd");
+ goto bad;
+ }
+ return fd;
+
+bad:
+ udev_stop();
+ return -1;
+}
+
+int
+dev_init(struct dev *dev, const struct dev_dhcpcd *dev_dhcpcd)
+{
+
+ dev->name = udev_name;
+ dev->initialised = udev_initialised;
+ dev->listening = udev_listening;
+ dev->handle_device = udev_handle_device;
+ dev->stop = udev_stop;
+ dev->start = udev_start;
+
+ dhcpcd = *dev_dhcpcd;
+
+ return 0;
+}
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
new file mode 100644
index 000000000000..dbcfcc564df8
--- /dev/null
+++ b/src/dhcp-common.c
@@ -0,0 +1,1029 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <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"
+#include "logerr.h"
+#include "script.h"
+
+const char *
+dhcp_get_hostname(char *buf, size_t buf_len, const struct if_options *ifo)
+{
+
+ if (ifo->hostname[0] == '\0') {
+ if (gethostname(buf, buf_len) != 0)
+ return NULL;
+ buf[buf_len - 1] = '\0';
+ } else
+ strlcpy(buf, ifo->hostname, buf_len);
+
+ /* Deny sending of these local hostnames */
+ if (buf[0] == '\0' || buf[0] == '.' ||
+ strcmp(buf, "(none)") == 0 ||
+ strcmp(buf, "localhost") == 0 ||
+ strncmp(buf, "localhost.", strlen("localhost.")) == 0)
+ return NULL;
+
+ /* Shorten the hostname if required */
+ if (ifo->options & DHCPCD_HOSTNAME_SHORT) {
+ char *hp;
+
+ hp = strchr(buf, '.');
+ if (hp != NULL)
+ *hp = '\0';
+ }
+
+ return buf;
+}
+
+void
+dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols)
+{
+
+ while (cols < 40) {
+ putchar(' ');
+ cols++;
+ }
+ putchar('\t');
+ if (opt->type & OT_EMBED)
+ printf(" embed");
+ if (opt->type & OT_ENCAP)
+ printf(" encap");
+ if (opt->type & OT_INDEX)
+ printf(" index");
+ if (opt->type & OT_ARRAY)
+ printf(" array");
+ if (opt->type & OT_UINT8)
+ printf(" uint8");
+ else if (opt->type & OT_INT8)
+ printf(" int8");
+ else if (opt->type & OT_UINT16)
+ printf(" uint16");
+ else if (opt->type & OT_INT16)
+ printf(" int16");
+ else if (opt->type & OT_UINT32)
+ printf(" uint32");
+ else if (opt->type & OT_INT32)
+ printf(" int32");
+ else if (opt->type & OT_ADDRIPV4)
+ printf(" ipaddress");
+ else if (opt->type & OT_ADDRIPV6)
+ printf(" ip6address");
+ else if (opt->type & OT_FLAG)
+ printf(" flag");
+ else if (opt->type & OT_BITFLAG)
+ printf(" bitflags");
+ else if (opt->type & OT_RFC1035)
+ printf(" domain");
+ else if (opt->type & OT_DOMAIN)
+ printf(" dname");
+ else if (opt->type & OT_ASCII)
+ printf(" ascii");
+ else if (opt->type & OT_RAW)
+ printf(" raw");
+ else if (opt->type & OT_BINHEX)
+ printf(" binhex");
+ else if (opt->type & OT_STRING)
+ printf(" string");
+ if (opt->type & OT_RFC3361)
+ printf(" rfc3361");
+ if (opt->type & OT_RFC3442)
+ printf(" rfc3442");
+ if (opt->type & OT_REQUEST)
+ printf(" request");
+ if (opt->type & OT_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) == -1)
+ 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 + 1, len - 1);
+ if (l == -1 || (size_t)(l + 1) > len)
+ return -1;
+ *p = ':';
+ 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;
+ if (strncmp(token, "dhcp6_", 6) == 0)
+ token += 6;
+ if (strncmp(token, "nd_", 3) == 0)
+ token += 3;
+ match = 0;
+ for (i = 0, opt = odopts; i < odopts_len; i++, opt++) {
+ if (opt->var == NULL || opt->option == 0)
+ continue; /* buggy dhcpcd-definitions.conf */
+ 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 & OT_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, d_len, o_len;
+ const uint8_t *r, *q = p, *e;
+ int hops;
+ uint8_t ltype;
+
+ o_len = 0;
+ start = out;
+ start_len = len;
+ q = p;
+ e = p + pl;
+ while (q < e) {
+ r = NULL;
+ d_len = 0;
+ 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;
+ }
+ if (l > NS_MAXLABEL) {
+ errno = EINVAL;
+ return -1;
+ }
+ d_len += l + 1;
+ if (out) {
+ if (l + 1 > len) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(out, q, l);
+ out += l;
+ *out++ = '.';
+ len -= l;
+ len--;
+ }
+ q += l;
+ }
+ }
+
+ /* Don't count the trailing NUL */
+ if (d_len > NS_MAXDNAME + 1) {
+ errno = E2BIG;
+ return -1;
+ }
+ o_len += d_len;
+
+ /* 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';
+ }
+
+ /* Remove the trailing NUL */
+ if (o_len != 0)
+ o_len--;
+
+ return (ssize_t)o_len;
+}
+
+/* Check for a valid name as per RFC952 and RFC1123 section 2.1 */
+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 & OT_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 & OT_BINHEX) {
+ if (dst) {
+ if (len == 0 || len == 1) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *dst++ = hexchrs[(c & 0xF0) >> 4];
+ *dst++ = hexchrs[(c & 0x0F)];
+ len -= 2;
+ }
+ bytes += 2;
+ continue;
+ }
+ if (type & OT_ASCII && (!isascii(c))) {
+ errno = EINVAL;
+ break;
+ }
+ if (!(type & (OT_ASCII | OT_RAW | OT_ESCSTRING | OT_ESCFILE)) &&
+ (!isascii(c) && !isprint(c)))
+ {
+ errno = EINVAL;
+ break;
+ }
+ if ((type & (OT_ESCSTRING | OT_ESCFILE) &&
+ (c == '\\' || !isascii(c) || !isprint(c))) ||
+ (type & OT_ESCFILE && (c == '/' || c == ' ')))
+ {
+ errno = EINVAL;
+ if (c == '\\') {
+ if (dst) {
+ if (len == 0 || len == 1) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *dst++ = '\\'; *dst++ = '\\';
+ len -= 2;
+ }
+ bytes += 2;
+ continue;
+ }
+ if (dst) {
+ if (len < 5) {
+ errno = ENOBUFS;
+ 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 = ENOBUFS;
+ return -1;
+ }
+ *dst++ = (char)c;
+ len--;
+ }
+ bytes++;
+ }
+ }
+
+ /* NULL */
+ if (dst) {
+ if (len == 0) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *dst = '\0';
+
+ /* Now we've printed it, validate the domain */
+ if (type & OT_DOMAIN && !valid_domainname(odst, type)) {
+ *odst = '\0';
+ return 1;
+ }
+
+ }
+
+ return (ssize_t)bytes;
+}
+
+#define ADDR6SZ 16
+static ssize_t
+dhcp_optlen(const struct dhcp_opt *opt, size_t dl)
+{
+ size_t sz;
+
+ if (opt->type & OT_ADDRIPV6)
+ sz = ADDR6SZ;
+ else if (opt->type & (OT_INT32 | OT_UINT32 | OT_ADDRIPV4))
+ sz = sizeof(uint32_t);
+ else if (opt->type & (OT_INT16 | OT_UINT16))
+ sz = sizeof(uint16_t);
+ else if (opt->type & (OT_INT8 | OT_UINT8 | OT_BITFLAG))
+ sz = sizeof(uint8_t);
+ else if (opt->type & OT_FLAG)
+ return 0;
+ else {
+ /* All other types are variable length */
+ if (opt->len) {
+ if ((size_t)opt->len > dl) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ return (ssize_t)opt->len;
+ }
+ return (ssize_t)dl;
+ }
+ if (dl < sz) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ /* Trim any extra data.
+ * Maybe we need a setting to reject DHCP options with extra data? */
+ if (opt->type & OT_ARRAY)
+ return (ssize_t)(dl - (dl % sz));
+ return (ssize_t)sz;
+}
+
+static ssize_t
+print_option(FILE *fp, const char *prefix, const struct dhcp_opt *opt,
+ int vname,
+ const uint8_t *data, size_t dl, const char *ifname)
+{
+ fpos_t fp_pos;
+ const uint8_t *e, *t;
+ uint16_t u16;
+ int16_t s16;
+ uint32_t u32;
+ int32_t s32;
+ struct in_addr addr;
+ ssize_t sl;
+ size_t l;
+
+ /* Ensure a valid length */
+ dl = (size_t)dhcp_optlen(opt, dl);
+ if ((ssize_t)dl == -1)
+ return 0;
+
+ if (fgetpos(fp, &fp_pos) == -1)
+ return -1;
+ if (fprintf(fp, "%s", prefix) == -1)
+ goto err;
+
+ /* We printed something, so always goto err from now-on
+ * to terminate the string. */
+ if (vname) {
+ if (fprintf(fp, "_%s", opt->var) == -1)
+ goto err;
+ }
+ if (fputc('=', fp) == EOF)
+ goto err;
+ if (dl == 0)
+ goto done;
+
+ if (opt->type & OT_RFC1035) {
+ char domain[NS_MAXDNAME];
+
+ sl = decode_rfc1035(domain, sizeof(domain), data, dl);
+ if (sl == -1)
+ goto err;
+ if (sl == 0)
+ goto done;
+ if (valid_domainname(domain, opt->type) == -1)
+ goto err;
+ return efprintf(fp, "%s", domain);
+ }
+
+#ifdef INET
+ if (opt->type & OT_RFC3361)
+ return print_rfc3361(fp, data, dl);
+
+ if (opt->type & OT_RFC3442)
+ return print_rfc3442(fp, data, dl);
+#endif
+
+ if (opt->type & OT_STRING) {
+ char buf[1024];
+
+ if (print_string(buf, sizeof(buf), opt->type, data, dl) == -1)
+ goto err;
+ return efprintf(fp, "%s", buf);
+ }
+
+ if (opt->type & OT_FLAG)
+ return efprintf(fp, "1");
+
+ if (opt->type & OT_BITFLAG) {
+ /* bitflags are a string, MSB first, such as ABCDEFGH
+ * where A is 10000000, B is 01000000, etc. */
+ 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 (fputc(opt->bitflags[l], fp) == EOF)
+ goto err;
+ }
+ }
+ goto done;
+ }
+
+ t = data;
+ e = data + dl;
+ while (data < e) {
+ if (data != t) {
+ if (fputc(' ', fp) == EOF)
+ goto err;
+ }
+ if (opt->type & OT_UINT8) {
+ if (fprintf(fp, "%u", *data) == -1)
+ goto err;
+ data++;
+ } else if (opt->type & OT_INT8) {
+ if (fprintf(fp, "%d", *data) == -1)
+ goto err;
+ data++;
+ } else if (opt->type & OT_UINT16) {
+ memcpy(&u16, data, sizeof(u16));
+ u16 = ntohs(u16);
+ if (fprintf(fp, "%u", u16) == -1)
+ goto err;
+ data += sizeof(u16);
+ } else if (opt->type & OT_INT16) {
+ memcpy(&u16, data, sizeof(u16));
+ s16 = (int16_t)ntohs(u16);
+ if (fprintf(fp, "%d", s16) == -1)
+ goto err;
+ data += sizeof(u16);
+ } else if (opt->type & OT_UINT32) {
+ memcpy(&u32, data, sizeof(u32));
+ u32 = ntohl(u32);
+ if (fprintf(fp, "%u", u32) == -1)
+ goto err;
+ data += sizeof(u32);
+ } else if (opt->type & OT_INT32) {
+ memcpy(&u32, data, sizeof(u32));
+ s32 = (int32_t)ntohl(u32);
+ if (fprintf(fp, "%d", s32) == -1)
+ goto err;
+ data += sizeof(u32);
+ } else if (opt->type & OT_ADDRIPV4) {
+ memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
+ if (fprintf(fp, "%s", inet_ntoa(addr)) == -1)
+ goto err;
+ data += sizeof(addr.s_addr);
+ } else if (opt->type & OT_ADDRIPV6) {
+ char buf[INET6_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET6, data, buf, sizeof(buf)) == NULL)
+ goto err;
+ if (fprintf(fp, "%s", buf) == -1)
+ goto err;
+ if (data[0] == 0xfe && (data[1] & 0xc0) == 0x80) {
+ if (fprintf(fp,"%%%s", ifname) == -1)
+ goto err;
+ }
+ data += 16;
+ } else {
+ errno = EINVAL;
+ goto err;
+ }
+ }
+
+done:
+ if (fputc('\0', fp) == EOF)
+ return -1;
+ return 1;
+
+err:
+ (void)fsetpos(fp, &fp_pos);
+ return -1;
+}
+
+int
+dhcp_set_leasefile(char *leasefile, size_t len, int family,
+ const struct interface *ifp)
+{
+ char ssid[1 + (IF_SSIDLEN * 4) + 1]; /* - prefix and NUL terminated. */
+
+ 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,
+ OT_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);
+}
+
+void
+dhcp_envoption(struct dhcpcd_ctx *ctx, FILE *fp, 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 i, 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 & OT_RESERVED)
+ return;
+ if (print_option(fp, prefix, opt, 1, od, ol, ifname) == -1)
+ logerr("%s: %s %d", ifname, __func__, opt->option);
+ return;
+ }
+
+ /* Create a new prefix based on the option */
+ if (opt->type & OT_INDEX) {
+ if (asprintf(&pfx, "%s_%s%d",
+ prefix, opt->var, ++opt->index) == -1)
+ pfx = NULL;
+ } else {
+ if (asprintf(&pfx, "%s_%s", prefix, opt->var) == -1)
+ pfx = NULL;
+ }
+ if (pfx == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ /* Embedded options are always processed first as that
+ * is a fixed layout */
+ for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) {
+ eo = dhcp_optlen(eopt, ol);
+ if (eo == -1) {
+ logerrx("%s: %s %d.%d/%zu: "
+ "malformed embedded option",
+ ifname, __func__, opt->option,
+ eopt->option, i);
+ 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. */
+ if (ol != 0 || !(eopt->type & OT_OPTIONAL))
+ logerrx("%s: %s %d.%d/%zu: "
+ "missing embedded option",
+ ifname, __func__, opt->option,
+ eopt->option, i);
+ 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 & OT_RESERVED)) {
+ ov = strcmp(opt->var, eopt->var);
+ if (print_option(fp, pfx, eopt, ov, od, (size_t)eo,
+ ifname) == -1)
+ logerr("%s: %s %d.%d/%zu",
+ ifname, __func__,
+ opt->option, eopt->option, i);
+ }
+ 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 & OT_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)
+ continue;
+ if (eopt->type & OT_OPTION) {
+ if (oopt == NULL)
+ /* Report error? */
+ continue;
+ }
+ dhcp_envoption(ctx, fp, pfx, ifname,
+ eopt->type & OT_OPTION ? oopt:eopt,
+ dgetopt, eod, eol);
+ }
+ od += eos + eol;
+ ol -= eos + eol;
+ }
+ }
+
+out:
+ free(pfx);
+}
+
+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);
+}
+
+ssize_t
+dhcp_readfile(struct dhcpcd_ctx *ctx, const char *file, void *data, size_t len)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return ps_root_readfile(ctx, file, data, len);
+#else
+ UNUSED(ctx);
+#endif
+
+ return readfile(file, data, len);
+}
+
+ssize_t
+dhcp_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode,
+ const void *data, size_t len)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return ps_root_writefile(ctx, file, mode, data, len);
+#else
+ UNUSED(ctx);
+#endif
+
+ return writefile(file, mode, data, len);
+}
+
+int
+dhcp_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return (int)ps_root_filemtime(ctx, file, time);
+#else
+ UNUSED(ctx);
+#endif
+
+ return filemtime(file, time);
+}
+
+int
+dhcp_unlink(struct dhcpcd_ctx *ctx, const char *file)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ return (int)ps_root_unlink(ctx, file);
+#else
+ UNUSED(ctx);
+#endif
+
+ return unlink(file);
+}
+
+size_t
+dhcp_read_hwaddr_aton(struct dhcpcd_ctx *ctx, uint8_t **data, const char *file)
+{
+ char buf[BUFSIZ];
+ ssize_t bytes;
+ size_t len;
+
+ bytes = dhcp_readfile(ctx, file, buf, sizeof(buf));
+ if (bytes == -1 || bytes == sizeof(buf))
+ return 0;
+
+ bytes[buf] = '\0';
+ len = hwaddr_aton(NULL, buf);
+ if (len == 0)
+ return 0;
+ *data = malloc(len);
+ if (*data == NULL)
+ return 0;
+ hwaddr_aton(*data, buf);
+ return len;
+}
diff --git a/src/dhcp-common.h b/src/dhcp-common.h
new file mode 100644
index 000000000000..a82fcd4cec8e
--- /dev/null
+++ b/src/dhcp-common.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <arpa/nameser.h> /* after normal includes for sunos */
+
+#include "common.h"
+#include "dhcpcd.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
+
+/* Max MTU - defines dhcp option length */
+#define IP_UDP_SIZE 28
+#define MTU_MAX 1500 - IP_UDP_SIZE
+#define MTU_MIN 576 + IP_UDP_SIZE
+
+#define OT_REQUEST (1 << 0)
+#define OT_UINT8 (1 << 1)
+#define OT_INT8 (1 << 2)
+#define OT_UINT16 (1 << 3)
+#define OT_INT16 (1 << 4)
+#define OT_UINT32 (1 << 5)
+#define OT_INT32 (1 << 6)
+#define OT_ADDRIPV4 (1 << 7)
+#define OT_STRING (1 << 8)
+#define OT_ARRAY (1 << 9)
+#define OT_RFC3361 (1 << 10)
+#define OT_RFC1035 (1 << 11)
+#define OT_RFC3442 (1 << 12)
+#define OT_OPTIONAL (1 << 13)
+#define OT_ADDRIPV6 (1 << 14)
+#define OT_BINHEX (1 << 15)
+#define OT_FLAG (1 << 16)
+#define OT_NOREQ (1 << 17)
+#define OT_EMBED (1 << 18)
+#define OT_ENCAP (1 << 19)
+#define OT_INDEX (1 << 20)
+#define OT_OPTION (1 << 21)
+#define OT_DOMAIN (1 << 22)
+#define OT_ASCII (1 << 23)
+#define OT_RAW (1 << 24)
+#define OT_ESCSTRING (1 << 25)
+#define OT_ESCFILE (1 << 26)
+#define OT_BITFLAG (1 << 27)
+#define OT_RESERVED (1 << 28)
+
+#define DHC_REQ(r, n, o) \
+ (has_option_mask((r), (o)) && !has_option_mask((n), (o)))
+
+#define DHC_REQOPT(o, r, n) \
+ (!((o)->type & OT_NOREQ) && \
+ ((o)->type & OT_REQUEST || has_option_mask((r), (o)->option)) && \
+ !has_option_mask((n), (o)->option))
+
+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;
+};
+
+const char *dhcp_get_hostname(char *, size_t, const struct if_options *);
+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 *);
+
+void dhcp_envoption(struct dhcpcd_ctx *,
+ FILE *, 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 *);
+
+ssize_t dhcp_readfile(struct dhcpcd_ctx *, const char *, void *, size_t);
+ssize_t dhcp_writefile(struct dhcpcd_ctx *, const char *, mode_t,
+ const void *, size_t);
+int dhcp_filemtime(struct dhcpcd_ctx *, const char *, time_t *);
+int dhcp_unlink(struct dhcpcd_ctx *, const char *);
+size_t dhcp_read_hwaddr_aton(struct dhcpcd_ctx *, uint8_t **, const char *);
+#endif
diff --git a/src/dhcp.c b/src/dhcp.c
new file mode 100644
index 000000000000..fbed2f3ca2e2
--- /dev/null
+++ b/src/dhcp.c
@@ -0,0 +1,4314 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <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
+
+#ifdef AF_LINK
+# include <net/if_dl.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#define ELOOP_QUEUE ELOOP_DHCP
+#include "config.h"
+#include "arp.h"
+#include "bpf.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 "logerr.h"
+#include "privsep.h"
+#include "sa.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
+
+/* Support older systems with different defines */
+#if !defined(IP_RECVPKTINFO) && defined(IP_PKTINFO)
+#define IP_RECVPKTINFO IP_PKTINFO
+#endif
+
+/* Assert the correct structure size for on wire */
+__CTASSERT(sizeof(struct ip) == 20);
+__CTASSERT(sizeof(struct udphdr) == 8);
+__CTASSERT(sizeof(struct bootp) == 300);
+
+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, "FORCERENEW"},
+ { 0, NULL }
+};
+
+static const char * const dhcp_params[] = {
+ "ip_address",
+ "subnet_cidr",
+ "network_number",
+ "filename",
+ "server_name",
+ NULL
+};
+
+static int dhcp_openbpf(struct interface *);
+static void dhcp_start1(void *);
+#if defined(ARP) && (!defined(KERNEL_RFC5227) || defined(ARPING))
+static void dhcp_arp_found(struct arp_state *, const struct arp_msg *);
+#endif
+static void dhcp_handledhcp(struct interface *, struct bootp *, size_t,
+ const struct in_addr *);
+static void dhcp_handleifudp(void *);
+static int dhcp_initstate(struct interface *);
+
+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);
+ }
+}
+
+static const uint8_t *
+get_option(struct dhcpcd_ctx *ctx,
+ const struct bootp *bootp, size_t bootp_len,
+ unsigned int opt, size_t *opt_len)
+{
+ const uint8_t *p, *e;
+ uint8_t l, o, ol, overl, *bp;
+ const uint8_t *op;
+ size_t bl;
+
+ if (bootp == NULL || bootp_len < DHCP_MIN_LEN) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Check we have the magic cookie */
+ if (!IS_DHCP(bootp)) {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ p = bootp->vend + 4; /* options after the 4 byte cookie */
+ e = (const uint8_t *)bootp + bootp_len;
+ ol = o = overl = 0;
+ bp = NULL;
+ op = NULL;
+ bl = 0;
+ 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 = bootp->file;
+ e = p + sizeof(bootp->file);
+ } else if (overl & 2) {
+ /* bit 2 set means parse server name */
+ overl = (uint8_t)(overl & ~2);
+ p = bootp->sname;
+ e = p + sizeof(bootp->sname);
+ } else
+ goto exit;
+ /* No length to read */
+ continue;
+ }
+
+ /* Check we can read the length */
+ if (p == e) {
+ errno = EINVAL;
+ return NULL;
+ }
+ l = *p++;
+
+ /* Check we can read the option data, if present */
+ if (p + l > e) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (o == DHO_OPTSOVERLOADED) {
+ /* 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) {
+ /* We must concatonate the options. */
+ if (bl + l > ctx->opt_buffer_len) {
+ size_t pos;
+ uint8_t *nb;
+
+ if (bp)
+ pos = (size_t)
+ (bp - ctx->opt_buffer);
+ else
+ pos = 0;
+ nb = realloc(ctx->opt_buffer, bl + l);
+ if (nb == NULL)
+ return NULL;
+ ctx->opt_buffer = nb;
+ ctx->opt_buffer_len = bl + l;
+ bp = ctx->opt_buffer + pos;
+ }
+ if (bp == NULL)
+ bp = ctx->opt_buffer;
+ memcpy(bp, op, ol);
+ bp += ol;
+ }
+ ol = l;
+ op = p;
+ bl += ol;
+ }
+ p += l;
+ }
+
+exit:
+ if (opt_len)
+ *opt_len = bl;
+ if (bp) {
+ memcpy(bp, op, ol);
+ return (const uint8_t *)ctx->opt_buffer;
+ }
+ if (op)
+ return op;
+ errno = ENOENT;
+ return NULL;
+}
+
+static int
+get_option_addr(struct dhcpcd_ctx *ctx,
+ struct in_addr *a, const struct bootp *bootp, size_t bootp_len,
+ uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+
+ p = get_option(ctx, bootp, bootp_len, 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 bootp *bootp, size_t bootp_len, uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+ uint32_t d;
+
+ p = get_option(ctx, bootp, bootp_len, 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 bootp *bootp, size_t bootp_len, uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+ uint16_t d;
+
+ p = get_option(ctx, bootp, bootp_len, 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 bootp *bootp, size_t bootp_len, uint8_t option)
+{
+ const uint8_t *p;
+ size_t len;
+
+ p = get_option(ctx, bootp, bootp_len, option, &len);
+ if (!p || len < (ssize_t)sizeof(*p))
+ return -1;
+ if (i)
+ *i = *(p);
+ return 0;
+}
+
+ssize_t
+print_rfc3442(FILE *fp, const uint8_t *data, size_t data_len)
+{
+ const uint8_t *p = data, *e;
+ size_t ocets;
+ uint8_t cidr;
+ struct in_addr addr;
+
+ /* Minimum is 5 -first is CIDR and a router length of 4 */
+ if (data_len < 5) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ e = p + data_len;
+ while (p < e) {
+ if (p != data) {
+ if (fputc(' ', fp) == EOF)
+ return -1;
+ }
+ 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 we have ocets then we have a destination and netmask */
+ addr.s_addr = 0;
+ if (ocets > 0) {
+ memcpy(&addr.s_addr, p, ocets);
+ p += ocets;
+ }
+ if (fprintf(fp, "%s/%d", inet_ntoa(addr), cidr) == -1)
+ return -1;
+
+ /* Finally, snag the router */
+ memcpy(&addr.s_addr, p, 4);
+ p += 4;
+ if (fprintf(fp, " %s", inet_ntoa(addr)) == -1)
+ return -1;
+ }
+
+ if (fputc('\0', fp) == EOF)
+ return -1;
+ return 1;
+}
+
+static int
+decode_rfc3442_rt(rb_tree_t *routes, struct interface *ifp,
+ const uint8_t *data, size_t dl, const struct bootp *bootp)
+{
+ const uint8_t *p = data;
+ const uint8_t *e;
+ uint8_t cidr;
+ size_t ocets;
+ struct rt *rt = NULL;
+ struct in_addr dest, netmask, gateway;
+ int n;
+
+ /* Minimum is 5 -first is CIDR and a router length of 4 */
+ if (dl < 5) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ n = 0;
+ e = p + dl;
+ 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 ((rt = rt_new(ifp)) == NULL)
+ return -1;
+
+ /* If we have ocets then we have a destination and netmask */
+ dest.s_addr = 0;
+ if (ocets > 0) {
+ memcpy(&dest.s_addr, p, ocets);
+ p += ocets;
+ netmask.s_addr = htonl(~0U << (32 - cidr));
+ } else
+ netmask.s_addr = 0;
+
+ /* Finally, snag the router */
+ memcpy(&gateway.s_addr, p, 4);
+ p += 4;
+
+ /* An on-link host route is normally set by having the
+ * gateway match the destination or assigned address */
+ if (gateway.s_addr == dest.s_addr ||
+ (gateway.s_addr == bootp->yiaddr ||
+ gateway.s_addr == bootp->ciaddr))
+ {
+ gateway.s_addr = INADDR_ANY;
+ netmask.s_addr = INADDR_BROADCAST;
+ }
+ if (netmask.s_addr == INADDR_BROADCAST)
+ rt->rt_flags = RTF_HOST;
+
+ sa_in_init(&rt->rt_dest, &dest);
+ sa_in_init(&rt->rt_netmask, &netmask);
+ sa_in_init(&rt->rt_gateway, &gateway);
+ if (rt_proto_add(routes, rt))
+ n = 1;
+ }
+ return n;
+}
+
+ssize_t
+print_rfc3361(FILE *fp, const uint8_t *data, size_t dl)
+{
+ uint8_t enc;
+ char sip[NS_MAXDNAME];
+ struct in_addr addr;
+
+ if (dl < 2) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ enc = *data++;
+ dl--;
+ switch (enc) {
+ case 0:
+ if (decode_rfc1035(sip, sizeof(sip), data, dl) == -1)
+ return -1;
+ if (efprintf(fp, "%s", sip) == -1)
+ return -1;
+ break;
+ case 1:
+ if (dl % 4 != 0) {
+ errno = EINVAL;
+ break;
+ }
+ addr.s_addr = INADDR_BROADCAST;
+ for (;
+ dl != 0;
+ data += sizeof(addr.s_addr), dl -= sizeof(addr.s_addr))
+ {
+ memcpy(&addr.s_addr, data, sizeof(addr.s_addr));
+ if (fprintf(fp, "%s", inet_ntoa(addr)) == -1)
+ return -1;
+ if (dl != sizeof(addr.s_addr)) {
+ if (fputc(' ', fp) == EOF)
+ return -1;
+ }
+ }
+ if (fputc('\0', fp) == EOF)
+ return -1;
+ break;
+ default:
+ errno = EINVAL;
+ return 0;
+ }
+
+ return 1;
+}
+
+static char *
+get_option_string(struct dhcpcd_ctx *ctx,
+ const struct bootp *bootp, size_t bootp_len, uint8_t option)
+{
+ size_t len;
+ const uint8_t *p;
+ char *s;
+
+ p = get_option(ctx, bootp, bootp_len, 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 int
+get_option_routes(rb_tree_t *routes, struct interface *ifp,
+ const struct bootp *bootp, size_t bootp_len)
+{
+ struct if_options *ifo = ifp->options;
+ const uint8_t *p;
+ const uint8_t *e;
+ struct rt *rt = NULL;
+ struct in_addr dest, netmask, gateway;
+ size_t len;
+ const char *csr = "";
+ int n;
+
+ /* If we have CSR's then we MUST use these only */
+ if (!has_option_mask(ifo->nomask, DHO_CSR))
+ p = get_option(ifp->ctx, bootp, bootp_len, 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, bootp, bootp_len, DHO_MSCSR, &len);
+ if (p)
+ csr = "MS ";
+ }
+ if (p && (n = decode_rfc3442_rt(routes, ifp, p, len, bootp)) != -1) {
+ const struct dhcp_state *state;
+
+ state = D_CSTATE(ifp);
+ if (!(ifo->options & DHCPCD_CSR_WARNED) &&
+ !(state->added & STATE_FAKE))
+ {
+ logdebugx("%s: using %sClassless Static Routes",
+ ifp->name, csr);
+ ifo->options |= DHCPCD_CSR_WARNED;
+ }
+ return n;
+ }
+
+ n = 0;
+ /* OK, get our static routes first. */
+ if (!has_option_mask(ifo->nomask, DHO_STATICROUTE))
+ p = get_option(ifp->ctx, bootp, bootp_len,
+ 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) {
+ memcpy(&dest.s_addr, p, sizeof(dest.s_addr));
+ p += 4;
+ memcpy(&gateway.s_addr, p, sizeof(gateway.s_addr));
+ p += 4;
+ /* RFC 2131 Section 5.8 states default route is
+ * illegal */
+ if (gateway.s_addr == INADDR_ANY)
+ continue;
+ if ((rt = rt_new(ifp)) == NULL)
+ return -1;
+
+ /* A on-link host route is normally set by having the
+ * gateway match the destination or assigned address */
+ if (gateway.s_addr == dest.s_addr ||
+ (gateway.s_addr == bootp->yiaddr ||
+ gateway.s_addr == bootp->ciaddr))
+ {
+ gateway.s_addr = INADDR_ANY;
+ netmask.s_addr = INADDR_BROADCAST;
+ } else
+ netmask.s_addr = route_netmask(dest.s_addr);
+ if (netmask.s_addr == INADDR_BROADCAST)
+ rt->rt_flags = RTF_HOST;
+
+ sa_in_init(&rt->rt_dest, &dest);
+ sa_in_init(&rt->rt_netmask, &netmask);
+ sa_in_init(&rt->rt_gateway, &gateway);
+ if (rt_proto_add(routes, rt))
+ n++;
+ }
+ }
+
+ /* Now grab our routers */
+ if (!has_option_mask(ifo->nomask, DHO_ROUTER))
+ p = get_option(ifp->ctx, bootp, bootp_len, DHO_ROUTER, &len);
+ else
+ p = NULL;
+ if (p && len % 4 == 0) {
+ e = p + len;
+ dest.s_addr = INADDR_ANY;
+ netmask.s_addr = INADDR_ANY;
+ while (p < e) {
+ if ((rt = rt_new(ifp)) == NULL)
+ return -1;
+ memcpy(&gateway.s_addr, p, sizeof(gateway.s_addr));
+ p += 4;
+ sa_in_init(&rt->rt_dest, &dest);
+ sa_in_init(&rt->rt_netmask, &netmask);
+ sa_in_init(&rt->rt_gateway, &gateway);
+ if (rt_proto_add(routes, rt))
+ n++;
+ }
+ }
+
+ return n;
+}
+
+uint16_t
+dhcp_get_mtu(const struct interface *ifp)
+{
+ const struct dhcp_state *state;
+ uint16_t mtu;
+
+ if (ifp->options->mtu)
+ return (uint16_t)ifp->options->mtu;
+ mtu = 0; /* bogus gcc warning */
+ if ((state = D_CSTATE(ifp)) == NULL ||
+ has_option_mask(ifp->options->nomask, DHO_MTU) ||
+ get_option_uint16(ifp->ctx, &mtu,
+ state->new, state->new_len, DHO_MTU) == -1)
+ return 0;
+ return mtu;
+}
+
+/* Grab our routers from the DHCP message and apply any MTU value
+ * the message contains */
+int
+dhcp_get_routes(rb_tree_t *routes, struct interface *ifp)
+{
+ const struct dhcp_state *state;
+
+ if ((state = D_CSTATE(ifp)) == NULL || !(state->added & STATE_ADDED))
+ return 0;
+ return get_option_routes(routes, ifp, state->new, state->new_len);
+}
+
+/* Assumes DHCP options */
+static int
+dhcp_message_add_addr(struct bootp *bootp,
+ uint8_t type, struct in_addr addr)
+{
+ uint8_t *p;
+ size_t len;
+
+ p = bootp->vend;
+ while (*p != DHO_END) {
+ p++;
+ p += *p + 1;
+ }
+
+ len = (size_t)(p - bootp->vend);
+ if (len + 6 > sizeof(bootp->vend)) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ *p++ = type;
+ *p++ = 4;
+ memcpy(p, &addr.s_addr, 4);
+ p += 4;
+ *p = DHO_END;
+ return 0;
+}
+
+static ssize_t
+make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
+{
+ struct bootp *bootp;
+ uint8_t *lp, *p, *e;
+ uint8_t *n_params = NULL;
+ 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;
+ int mtu;
+#ifdef AUTH
+ uint8_t *auth, auth_len;
+#endif
+
+ if ((mtu = if_getmtu(ifp)) == -1)
+ logerr("%s: if_getmtu", ifp->name);
+ else if (mtu < MTU_MIN) {
+ if (if_setmtu(ifp, MTU_MIN) == -1)
+ logerr("%s: if_setmtu", ifp->name);
+ mtu = MTU_MIN;
+ }
+
+ if (ifo->options & DHCPCD_BOOTP)
+ bootp = calloc(1, sizeof (*bootp));
+ else
+ /* Make the maximal message we could send */
+ bootp = calloc(1, (size_t)(mtu - IP_UDP_SIZE));
+
+ if (bootp == NULL)
+ return -1;
+ *bootpm = bootp;
+
+ if (state->addr != NULL &&
+ (type == DHCP_INFORM || type == DHCP_RELEASE ||
+ (type == DHCP_REQUEST &&
+ state->addr->mask.s_addr == lease->mask.s_addr &&
+ (state->new == NULL || IS_DHCP(state->new)) &&
+ !(state->added & (STATE_FAKE | STATE_EXPIRED)))))
+ bootp->ciaddr = state->addr->addr.s_addr;
+
+ bootp->op = BOOTREQUEST;
+ bootp->htype = (uint8_t)ifp->hwtype;
+ if (ifp->hwlen != 0 && ifp->hwlen < sizeof(bootp->chaddr)) {
+ bootp->hlen = (uint8_t)ifp->hwlen;
+ memcpy(&bootp->chaddr, &ifp->hwaddr, ifp->hwlen);
+ }
+
+ if (ifo->options & DHCPCD_BROADCAST &&
+ bootp->ciaddr == 0 &&
+ type != DHCP_DECLINE &&
+ type != DHCP_RELEASE)
+ bootp->flags = htons(BROADCAST_FLAG);
+
+ if (type != DHCP_DECLINE && type != DHCP_RELEASE) {
+ struct timespec tv;
+ unsigned long long secs;
+
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ secs = eloop_timespec_diff(&tv, &state->started, NULL);
+ if (secs > UINT16_MAX)
+ bootp->secs = htons((uint16_t)UINT16_MAX);
+ else
+ bootp->secs = htons((uint16_t)secs);
+ }
+
+ bootp->xid = htonl(state->xid);
+
+ if (ifo->options & DHCPCD_BOOTP)
+ return sizeof(*bootp);
+
+ p = bootp->vend;
+ e = (uint8_t *)bootp + (mtu - IP_UDP_SIZE) - 1; /* -1 for DHO_END */
+
+ ul = htonl(MAGIC_COOKIE);
+ memcpy(p, &ul, sizeof(ul));
+ p += sizeof(ul);
+
+#define AREA_LEFT (size_t)(e - p)
+#define AREA_FIT(s) if ((s) > AREA_LEFT) goto toobig
+#define AREA_CHECK(s) if ((s) + 2UL > AREA_LEFT) goto toobig
+#define PUT_ADDR(o, a) do { \
+ AREA_CHECK(4); \
+ *p++ = (o); \
+ *p++ = 4; \
+ memcpy(p, &(a)->s_addr, 4); \
+ p += 4; \
+} while (0 /* CONSTCOND */)
+
+ /* Options are listed in numerical order as per RFC 7844 Section 3.1
+ * XXX: They should be randomised. */
+
+ bool putip = false;
+ if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) {
+ if (type == DHCP_DECLINE ||
+ (type == DHCP_REQUEST &&
+ (state->addr == NULL ||
+ state->added & (STATE_FAKE | STATE_EXPIRED) ||
+ lease->addr.s_addr != state->addr->addr.s_addr)))
+ {
+ putip = true;
+ PUT_ADDR(DHO_IPADDRESS, &lease->addr);
+ }
+ }
+
+ AREA_CHECK(3);
+ *p++ = DHO_MESSAGETYPE;
+ *p++ = 1;
+ *p++ = type;
+
+ if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) {
+ if (type == DHCP_RELEASE || putip) {
+ if (lease->server.s_addr)
+ PUT_ADDR(DHO_SERVERID, &lease->server);
+ }
+ }
+
+ if (type == DHCP_DECLINE) {
+ len = strlen(DAD);
+ if (len > AREA_LEFT) {
+ *p++ = DHO_MESSAGE;
+ *p++ = (uint8_t)len;
+ memcpy(p, DAD, len);
+ p += len;
+ }
+ }
+
+#define DHCP_DIR(type) ((type) == DHCP_DISCOVER || (type) == DHCP_INFORM || \
+ (type) == DHCP_REQUEST)
+
+ if (DHCP_DIR(type)) {
+ /* vendor is already encoded correctly, so just add it */
+ if (ifo->vendor[0]) {
+ AREA_CHECK(ifo->vendor[0]);
+ *p++ = DHO_VENDOR;
+ memcpy(p, ifo->vendor, (size_t)ifo->vendor[0] + 1);
+ p += ifo->vendor[0] + 1;
+ }
+ }
+
+ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST)
+ PUT_ADDR(DHO_IPADDRESS, &ifo->req_addr);
+
+ if (DHCP_DIR(type)) {
+ if (type != DHCP_INFORM) {
+ if (ifo->leasetime != 0) {
+ AREA_CHECK(4);
+ *p++ = DHO_LEASETIME;
+ *p++ = 4;
+ ul = htonl(ifo->leasetime);
+ memcpy(p, &ul, 4);
+ p += 4;
+ }
+ }
+
+ AREA_CHECK(0);
+ *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 (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask))
+ continue;
+ if (type == DHCP_INFORM &&
+ (opt->option == DHO_RENEWALTIME ||
+ opt->option == DHO_REBINDTIME))
+ continue;
+ AREA_FIT(1);
+ *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 (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask))
+ continue;
+ if (type == DHCP_INFORM &&
+ (opt->option == DHO_RENEWALTIME ||
+ opt->option == DHO_REBINDTIME))
+ continue;
+ AREA_FIT(1);
+ *p++ = (uint8_t)opt->option;
+ }
+ *n_params = (uint8_t)(p - n_params - 1);
+
+ if (mtu != -1 &&
+ !(has_option_mask(ifo->nomask, DHO_MAXMESSAGESIZE)))
+ {
+ AREA_CHECK(2);
+ *p++ = DHO_MAXMESSAGESIZE;
+ *p++ = 2;
+ sz = htons((uint16_t)(mtu - IP_UDP_SIZE));
+ memcpy(p, &sz, 2);
+ p += 2;
+ }
+
+ if (ifo->userclass[0] &&
+ !has_option_mask(ifo->nomask, DHO_USERCLASS))
+ {
+ AREA_CHECK(ifo->userclass[0]);
+ *p++ = DHO_USERCLASS;
+ memcpy(p, ifo->userclass,
+ (size_t)ifo->userclass[0] + 1);
+ p += ifo->userclass[0] + 1;
+ }
+ }
+
+ if (state->clientid) {
+ AREA_CHECK(state->clientid[0]);
+ *p++ = DHO_CLIENTID;
+ memcpy(p, state->clientid, (size_t)state->clientid[0] + 1);
+ p += state->clientid[0] + 1;
+ }
+
+ if (DHCP_DIR(type) &&
+ !has_option_mask(ifo->nomask, DHO_VENDORCLASSID) &&
+ ifo->vendorclassid[0])
+ {
+ AREA_CHECK(ifo->vendorclassid[0]);
+ *p++ = DHO_VENDORCLASSID;
+ memcpy(p, ifo->vendorclassid, (size_t)ifo->vendorclassid[0]+1);
+ p += ifo->vendorclassid[0] + 1;
+ }
+
+ if (type == DHCP_DISCOVER &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+ DHC_REQ(ifo->requestmask, ifo->nomask, DHO_RAPIDCOMMIT))
+ {
+ /* RFC 4039 Section 3 */
+ AREA_CHECK(0);
+ *p++ = DHO_RAPIDCOMMIT;
+ *p++ = 0;
+ }
+
+ if (DHCP_DIR(type)) {
+ hostname = dhcp_get_hostname(hbuf, sizeof(hbuf), ifo);
+
+ /*
+ * 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 */
+ i = 3;
+ if (hostname)
+ i += encode_rfc1035(hostname, NULL);
+ AREA_CHECK(i);
+ *p++ = DHO_FQDN;
+ *p++ = (uint8_t)i;
+ /*
+ * 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);
+ p += i;
+ }
+ } else if (ifo->options & DHCPCD_HOSTNAME && hostname) {
+ len = strlen(hostname);
+ AREA_CHECK(len);
+ *p++ = DHO_HOSTNAME;
+ *p++ = (uint8_t)len;
+ memcpy(p, hostname, len);
+ p += len;
+ }
+ }
+
+#ifdef AUTH
+ auth = NULL; /* appease GCC */
+ auth_len = 0;
+ if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ ssize_t alen = dhcp_auth_encode(ifp->ctx, &ifo->auth,
+ state->auth.token,
+ NULL, 0, 4, type, NULL, 0);
+ if (alen != -1 && alen > UINT8_MAX) {
+ errno = ERANGE;
+ alen = -1;
+ }
+ if (alen == -1)
+ logerr("%s: dhcp_auth_encode", ifp->name);
+ else if (alen != 0) {
+ auth_len = (uint8_t)alen;
+ AREA_CHECK(auth_len);
+ *p++ = DHO_AUTHENTICATION;
+ *p++ = auth_len;
+ auth = p;
+ p += auth_len;
+ }
+ }
+#endif
+
+ /* RFC 2563 Auto Configure */
+ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL &&
+ !(has_option_mask(ifo->nomask, DHO_AUTOCONFIGURE)))
+ {
+ AREA_CHECK(1);
+ *p++ = DHO_AUTOCONFIGURE;
+ *p++ = 1;
+ *p++ = 1;
+ }
+
+ if (DHCP_DIR(type)) {
+ if (ifo->mudurl[0]) {
+ AREA_CHECK(ifo->mudurl[0]);
+ *p++ = DHO_MUDURL;
+ memcpy(p, ifo->mudurl, (size_t)ifo->mudurl[0] + 1);
+ p += ifo->mudurl[0] + 1;
+ }
+
+ if (ifo->vivco_len &&
+ !has_option_mask(ifo->nomask, DHO_VIVCO))
+ {
+ AREA_CHECK(sizeof(ul));
+ *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++)
+ {
+ AREA_FIT(vivco->len);
+ if (vivco->len + 2 + *lp > 255) {
+ logerrx("%s: VIVCO option too big",
+ ifp->name);
+ free(bootp);
+ return -1;
+ }
+ *p++ = (uint8_t)vivco->len;
+ memcpy(p, vivco->data, vivco->len);
+ p += vivco->len;
+ *lp = (uint8_t)(*lp + vivco->len + 1);
+ }
+ }
+
+#ifdef AUTH
+ if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
+ DHCPCD_AUTH_SENDREQUIRE &&
+ !has_option_mask(ifo->nomask, DHO_FORCERENEW_NONCE))
+ {
+ /* We support HMAC-MD5 */
+ AREA_CHECK(1);
+ *p++ = DHO_FORCERENEW_NONCE;
+ *p++ = 1;
+ *p++ = AUTH_ALG_HMAC_MD5;
+ }
+#endif
+ }
+
+ *p++ = DHO_END;
+ len = (size_t)(p - (uint8_t *)bootp);
+
+ /* Pad out to the BOOTP message length.
+ * Even if we send a DHCP packet with a variable length vendor area,
+ * some servers / relay agents don't like packets smaller than
+ * a BOOTP message which is fine because that's stipulated
+ * in RFC1542 section 2.1. */
+ while (len < sizeof(*bootp)) {
+ *p++ = DHO_PAD;
+ len++;
+ }
+
+#ifdef AUTH
+ if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0)
+ dhcp_auth_encode(ifp->ctx, &ifo->auth, state->auth.token,
+ (uint8_t *)bootp, len, 4, type, auth, auth_len);
+#endif
+
+ return (ssize_t)len;
+
+toobig:
+ logerrx("%s: DHCP message too big", ifp->name);
+ free(bootp);
+ return -1;
+}
+
+static size_t
+read_lease(struct interface *ifp, struct bootp **bootp)
+{
+ union {
+ struct bootp bootp;
+ uint8_t buf[FRAMELEN_MAX];
+ } buf;
+ struct dhcp_state *state = D_STATE(ifp);
+ ssize_t sbytes;
+ size_t bytes;
+ uint8_t type;
+#ifdef AUTH
+ const uint8_t *auth;
+ size_t auth_len;
+#endif
+
+ /* Safety */
+ *bootp = NULL;
+
+ if (state->leasefile[0] == '\0') {
+ logdebugx("reading standard input");
+ sbytes = read(fileno(stdin), buf.buf, sizeof(buf.buf));
+ } else {
+ logdebugx("%s: reading lease: %s",
+ ifp->name, state->leasefile);
+ sbytes = dhcp_readfile(ifp->ctx, state->leasefile,
+ buf.buf, sizeof(buf.buf));
+ }
+ if (sbytes == -1) {
+ if (errno != ENOENT)
+ logerr("%s: %s", ifp->name, state->leasefile);
+ return 0;
+ }
+ bytes = (size_t)sbytes;
+
+ /* Ensure the packet is at lease BOOTP sized
+ * with a vendor area of 4 octets
+ * (it should be more, and our read packet enforces this so this
+ * code should not be needed, but of course people could
+ * scribble whatever in the stored lease file. */
+ if (bytes < DHCP_MIN_LEN) {
+ logerrx("%s: %s: truncated lease", ifp->name, __func__);
+ return 0;
+ }
+
+ if (ifp->ctx->options & DHCPCD_DUMPLEASE)
+ goto out;
+
+ /* We may have found a BOOTP server */
+ if (get_option_uint8(ifp->ctx, &type, &buf.bootp, bytes,
+ DHO_MESSAGETYPE) == -1)
+ type = 0;
+
+#ifdef AUTH
+ /* Authenticate the message */
+ auth = get_option(ifp->ctx, &buf.bootp, bytes,
+ DHO_AUTHENTICATION, &auth_len);
+ if (auth) {
+ if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+ &buf.bootp, bytes, 4, type, auth, auth_len) == NULL)
+ {
+ logerr("%s: authentication failed", ifp->name);
+ return 0;
+ }
+ if (state->auth.token)
+ logdebugx("%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ logdebugx("%s: accepted reconfigure key", ifp->name);
+ } else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) ==
+ DHCPCD_AUTH_SENDREQUIRE)
+ {
+ logerrx("%s: authentication now required", ifp->name);
+ return 0;
+ }
+#endif
+
+out:
+ *bootp = malloc(bytes);
+ if (*bootp == NULL) {
+ logerr(__func__);
+ return 0;
+ }
+ memcpy(*bootp, buf.buf, bytes);
+ return bytes;
+}
+
+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 - *os) {
+ errno = ERANGE;
+ return NULL;
+ }
+ }
+
+ *oopt = 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(FILE *fenv, const char *prefix, const struct interface *ifp,
+ const struct bootp *bootp, size_t bootp_len)
+{
+ 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 i, pl;
+ char safe[(BOOTP_FILE_LEN * 4) + 1];
+ uint8_t overl = 0;
+ uint32_t en;
+
+ ifo = ifp->options;
+ if (get_option_uint8(ifp->ctx, &overl, bootp, bootp_len,
+ DHO_OPTSOVERLOADED) == -1)
+ overl = 0;
+
+ if (bootp->yiaddr || bootp->ciaddr) {
+ /* Set some useful variables that we derive from the DHCP
+ * message but are not necessarily in the options */
+ addr.s_addr = bootp->yiaddr ? bootp->yiaddr : bootp->ciaddr;
+ if (efprintf(fenv, "%s_ip_address=%s",
+ prefix, inet_ntoa(addr)) == -1)
+ return -1;
+ if (get_option_addr(ifp->ctx, &net,
+ bootp, bootp_len, DHO_SUBNETMASK) == -1) {
+ net.s_addr = ipv4_getnetmask(addr.s_addr);
+ if (efprintf(fenv, "%s_subnet_mask=%s",
+ prefix, inet_ntoa(net)) == -1)
+ return -1;
+ }
+ if (efprintf(fenv, "%s_subnet_cidr=%d",
+ prefix, inet_ntocidr(net))== -1)
+ return -1;
+ if (get_option_addr(ifp->ctx, &brd,
+ bootp, bootp_len, DHO_BROADCAST) == -1)
+ {
+ brd.s_addr = addr.s_addr | ~net.s_addr;
+ if (efprintf(fenv, "%s_broadcast_address=%s",
+ prefix, inet_ntoa(brd)) == -1)
+ return -1;
+ }
+ addr.s_addr = bootp->yiaddr & net.s_addr;
+ if (efprintf(fenv, "%s_network_number=%s",
+ prefix, inet_ntoa(addr)) == -1)
+ return -1;
+ }
+
+ if (*bootp->file && !(overl & 1)) {
+ print_string(safe, sizeof(safe), OT_STRING,
+ bootp->file, sizeof(bootp->file));
+ if (efprintf(fenv, "%s_filename=%s", prefix, safe) == -1)
+ return -1;
+ }
+ if (*bootp->sname && !(overl & 2)) {
+ print_string(safe, sizeof(safe), OT_STRING | OT_DOMAIN,
+ bootp->sname, sizeof(bootp->sname));
+ if (efprintf(fenv, "%s_server_name=%s", prefix, safe) == -1)
+ return -1;
+ }
+
+ /* Zero our indexes */
+ 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;
+ p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl);
+ if (p == NULL)
+ continue;
+ dhcp_envoption(ifp->ctx, fenv, prefix, ifp->name,
+ opt, dhcp_getoption, p, pl);
+
+ if (opt->option != DHO_VIVSO || pl <= (int)sizeof(uint32_t))
+ continue;
+ memcpy(&en, p, sizeof(en));
+ en = ntohl(en);
+ vo = vivso_find(en, ifp);
+ if (vo == NULL)
+ continue;
+ /* Skip over en + total size */
+ p += sizeof(en) + 1;
+ pl -= sizeof(en) + 1;
+ dhcp_envoption(ifp->ctx, fenv, 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;
+ p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl);
+ if (p == NULL)
+ continue;
+ dhcp_envoption(ifp->ctx, fenv, prefix, ifp->name,
+ opt, dhcp_getoption, p, pl);
+ }
+
+ return 1;
+}
+
+static void
+get_lease(struct interface *ifp,
+ struct dhcp_lease *lease, const struct bootp *bootp, size_t len)
+{
+ struct dhcpcd_ctx *ctx;
+
+ assert(bootp != NULL);
+
+ memcpy(&lease->cookie, bootp->vend, sizeof(lease->cookie));
+ /* BOOTP does not set yiaddr for replies when ciaddr is set. */
+ lease->addr.s_addr = bootp->yiaddr ? bootp->yiaddr : bootp->ciaddr;
+ ctx = ifp->ctx;
+ if (ifp->options->options & (DHCPCD_STATIC | DHCPCD_INFORM)) {
+ if (ifp->options->req_addr.s_addr != INADDR_ANY) {
+ lease->mask = ifp->options->req_mask;
+ if (ifp->options->req_brd.s_addr != INADDR_ANY)
+ lease->brd = ifp->options->req_brd;
+ else
+ lease->brd.s_addr =
+ lease->addr.s_addr | ~lease->mask.s_addr;
+ } else {
+ const struct ipv4_addr *ia;
+
+ ia = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ assert(ia != NULL);
+ lease->mask = ia->mask;
+ lease->brd = ia->brd;
+ }
+ } else {
+ if (get_option_addr(ctx, &lease->mask, bootp, len,
+ DHO_SUBNETMASK) == -1)
+ lease->mask.s_addr =
+ ipv4_getnetmask(lease->addr.s_addr);
+ if (get_option_addr(ctx, &lease->brd, bootp, len,
+ DHO_BROADCAST) == -1)
+ lease->brd.s_addr =
+ lease->addr.s_addr | ~lease->mask.s_addr;
+ }
+ if (get_option_uint32(ctx, &lease->leasetime,
+ bootp, len, DHO_LEASETIME) != 0)
+ lease->leasetime = DHCP_INFINITE_LIFETIME;
+ if (get_option_uint32(ctx, &lease->renewaltime,
+ bootp, len, DHO_RENEWALTIME) != 0)
+ lease->renewaltime = 0;
+ if (get_option_uint32(ctx, &lease->rebindtime,
+ bootp, len, DHO_REBINDTIME) != 0)
+ lease->rebindtime = 0;
+ if (get_option_addr(ctx, &lease->server, bootp, len, 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);
+}
+
+static void
+dhcp_new_xid(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ const struct interface *ifp1;
+ const struct dhcp_state *state1;
+
+ state = D_STATE(ifp);
+ if (ifp->options->options & DHCPCD_XID_HWADDR &&
+ ifp->hwlen >= sizeof(state->xid))
+ /* The lower bits are probably more unique on the network */
+ memcpy(&state->xid,
+ (ifp->hwaddr + ifp->hwlen) - sizeof(state->xid),
+ sizeof(state->xid));
+ else {
+again:
+ state->xid = arc4random();
+ }
+
+ /* Ensure it's unique */
+ TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) {
+ if (ifp == ifp1)
+ continue;
+ if ((state1 = D_CSTATE(ifp1)) == NULL)
+ continue;
+ if (state1->xid == state->xid)
+ break;
+ }
+ if (ifp1 != NULL) {
+ if (ifp->options->options & DHCPCD_XID_HWADDR &&
+ ifp->hwlen >= sizeof(state->xid))
+ {
+ logerrx("%s: duplicate xid on %s",
+ ifp->name, ifp1->name);
+ return;
+ }
+ goto again;
+ }
+
+ /* We can't do this when sharing leases across interfaes */
+#if 0
+ /* As the XID changes, re-apply the filter. */
+ if (state->bpf_fd != -1) {
+ if (bpf_bootp(ifp, state->bpf_fd) == -1)
+ logerr(__func__); /* try to continue */
+ }
+#endif
+}
+
+static void
+dhcp_closebpf(struct interface *ifp)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct dhcp_state *state = D_STATE(ifp);
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ctx))
+ ps_bpf_closebootp(ifp);
+#endif
+
+ if (state->bpf != NULL) {
+ eloop_event_delete(ctx->eloop, state->bpf->bpf_fd);
+ bpf_close(state->bpf);
+ state->bpf = NULL;
+ }
+}
+
+static void
+dhcp_closeinet(struct interface *ifp)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct dhcp_state *state = D_STATE(ifp);
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ctx)) {
+ if (state->addr != NULL)
+ ps_inet_closebootp(state->addr);
+ }
+#endif
+
+ if (state->udp_rfd != -1) {
+ eloop_event_delete(ctx->eloop, state->udp_rfd);
+ close(state->udp_rfd);
+ state->udp_rfd = -1;
+ }
+}
+
+void
+dhcp_close(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+
+ if (state == NULL)
+ return;
+
+ dhcp_closebpf(ifp);
+ dhcp_closeinet(ifp);
+
+ state->interval = 0;
+}
+
+int
+dhcp_openudp(struct in_addr *ia)
+{
+ int s;
+ struct sockaddr_in sin;
+ int n;
+
+ if ((s = xsocket(PF_INET, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP)) == -1)
+ return -1;
+
+ n = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1)
+ goto errexit;
+#ifdef IP_RECVIF
+ if (setsockopt(s, IPPROTO_IP, IP_RECVIF, &n, sizeof(n)) == -1)
+ goto errexit;
+#else
+ if (setsockopt(s, IPPROTO_IP, IP_RECVPKTINFO, &n, sizeof(n)) == -1)
+ goto errexit;
+#endif
+#ifdef SO_RERROR
+ if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1)
+ goto errexit;
+#endif
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(BOOTPC);
+ if (ia != NULL)
+ sin.sin_addr = *ia;
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1)
+ goto errexit;
+
+ return s;
+
+errexit:
+ close(s);
+ return -1;
+}
+
+static uint16_t
+in_cksum(const void *data, size_t len, uint32_t *isum)
+{
+ const uint16_t *word = data;
+ uint32_t sum = isum != NULL ? *isum : 0;
+
+ for (; len > 1; len -= sizeof(*word))
+ sum += *word++;
+
+ if (len == 1)
+ sum += htons((uint16_t)(*(const uint8_t *)word << 8));
+
+ if (isum != NULL)
+ *isum = sum;
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ return (uint16_t)~sum;
+}
+
+static struct bootp_pkt *
+dhcp_makeudppacket(size_t *sz, const uint8_t *data, size_t length,
+ struct in_addr source, struct in_addr dest)
+{
+ struct bootp_pkt *udpp;
+ struct ip *ip;
+ struct udphdr *udp;
+
+ if ((udpp = calloc(1, sizeof(*ip) + sizeof(*udp) + length)) == 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->bootp, 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(BOOTPC);
+ udp->uh_dport = htons(BOOTPS);
+ udp->uh_ulen = htons((uint16_t)(sizeof(*udp) + length));
+ ip->ip_len = udp->uh_ulen;
+ udp->uh_sum = in_cksum(udpp, sizeof(*ip) + sizeof(*udp) + length, NULL);
+
+ 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 = in_cksum(ip, sizeof(*ip), NULL);
+ if (ip->ip_sum == 0)
+ ip->ip_sum = 0xffff; /* RFC 768 */
+
+ *sz = sizeof(*ip) + sizeof(*udp) + length;
+ return udpp;
+}
+
+static ssize_t
+dhcp_sendudp(struct interface *ifp, struct in_addr *to, void *data, size_t len)
+{
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_addr = *to,
+ .sin_port = htons(BOOTPS),
+#ifdef HAVE_SA_LEN
+ .sin_len = sizeof(sin),
+#endif
+ };
+ struct udphdr udp = {
+ .uh_sport = htons(BOOTPC),
+ .uh_dport = htons(BOOTPS),
+ .uh_ulen = htons((uint16_t)(sizeof(udp) + len)),
+ };
+ struct iovec iov[] = {
+ { .iov_base = &udp, .iov_len = sizeof(udp), },
+ { .iov_base = data, .iov_len = len, },
+ };
+ struct msghdr msg = {
+ .msg_name = (void *)&sin,
+ .msg_namelen = sizeof(sin),
+ .msg_iov = iov,
+ .msg_iovlen = __arraycount(iov),
+ };
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP)
+ return ps_inet_sendbootp(ifp, &msg);
+#endif
+ return sendmsg(ctx->udp_wfd, &msg, 0);
+}
+
+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 bootp *bootp;
+ struct bootp_pkt *udp;
+ size_t len, ulen;
+ ssize_t r;
+ struct in_addr from, to;
+ unsigned int RT;
+
+ if (callback == NULL) {
+ /* No carrier? Don't bother sending the packet. */
+ if (!if_is_link_up(ifp))
+ return;
+ logdebugx("%s: sending %s with xid 0x%x",
+ ifp->name,
+ ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type),
+ state->xid);
+ RT = 0; /* bogus gcc warning */
+ } else {
+ if (state->interval == 0)
+ state->interval = 4;
+ else {
+ state->interval *= 2;
+ if (state->interval > 64)
+ state->interval = 64;
+ }
+ RT = (state->interval * MSEC_PER_SEC) +
+ (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC);
+ /* No carrier? Don't bother sending the packet.
+ * However, we do need to advance the timeout. */
+ if (!if_is_link_up(ifp))
+ goto fail;
+ logdebugx("%s: sending %s (xid 0x%x), next in %0.1f seconds",
+ ifp->name,
+ ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type),
+ state->xid,
+ (float)RT / MSEC_PER_SEC);
+ }
+
+ r = make_message(&bootp, ifp, type);
+ if (r == -1)
+ goto fail;
+ len = (size_t)r;
+
+ if (!(state->added & (STATE_FAKE | STATE_EXPIRED)) &&
+ state->addr != NULL &&
+ ipv4_iffindaddr(ifp, &state->lease.addr, NULL) != NULL)
+ from.s_addr = state->lease.addr.s_addr;
+ else
+ from.s_addr = INADDR_ANY;
+ if (from.s_addr != INADDR_ANY &&
+ state->lease.server.s_addr != INADDR_ANY)
+ to.s_addr = state->lease.server.s_addr;
+ else
+ to.s_addr = INADDR_BROADCAST;
+
+ /*
+ * If not listening on the unspecified address we can
+ * only receive broadcast messages via BPF.
+ * Sockets bound to an address cannot receive broadcast messages
+ * even if they are setup to send them.
+ * Broadcasting from UDP is only an optimisation for rebinding
+ * and on BSD, at least, is reliant on the subnet route being
+ * correctly configured to receive the unicast reply.
+ * As such, we always broadcast and receive the reply to it via BPF.
+ * This also guarantees we have a DHCP server attached to the
+ * interface we want to configure because we can't dictate the
+ * interface via IP_PKTINFO unlike for IPv6.
+ */
+ if (to.s_addr != INADDR_BROADCAST) {
+ if (dhcp_sendudp(ifp, &to, bootp, len) != -1)
+ goto out;
+ logerr("%s: dhcp_sendudp", ifp->name);
+ }
+
+ if (dhcp_openbpf(ifp) == -1)
+ goto out;
+
+ udp = dhcp_makeudppacket(&ulen, (uint8_t *)bootp, len, from, to);
+ if (udp == NULL) {
+ logerr("%s: dhcp_makeudppacket", ifp->name);
+ r = 0;
+#ifdef PRIVSEP
+ } else if (ifp->ctx->options & DHCPCD_PRIVSEP) {
+ r = ps_bpf_sendbootp(ifp, udp, ulen);
+ free(udp);
+#endif
+ } else {
+ r = bpf_send(state->bpf, ETHERTYPE_IP, 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) {
+ logerr("%s: bpf_send", ifp->name);
+ switch(errno) {
+ case ENETDOWN:
+ case ENETRESET:
+ case ENETUNREACH:
+ case ENOBUFS:
+ break;
+ default:
+ if (!(ifp->ctx->options & DHCPCD_TEST))
+ dhcp_drop(ifp, "FAIL");
+ eloop_timeout_delete(ifp->ctx->eloop,
+ NULL, ifp);
+ callback = NULL;
+ }
+ }
+
+out:
+ free(bootp);
+
+fail:
+ /* 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 != NULL)
+ eloop_timeout_add_msec(ifp->ctx->eloop, RT, 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;
+ dhcp_new_xid(ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ if (!(state->added & STATE_EXPIRED)) {
+ if (ifo->fallback)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, dhcp_fallback, ifp);
+#ifdef IPV4LL
+ else if (ifo->options & DHCPCD_IPV4LL)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, ipv4ll_start, ifp);
+#endif
+ }
+ if (ifo->options & DHCPCD_REQUEST)
+ loginfox("%s: soliciting a DHCP lease (requesting %s)",
+ ifp->name, inet_ntoa(ifo->req_addr));
+ else
+ loginfox("%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);
+
+ if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
+ logwarnx("%s: DHCP lease expired, extending lease", ifp->name);
+ state->added |= STATE_EXPIRED;
+ } else {
+ logerrx("%s: DHCP lease expired", ifp->name);
+ dhcp_drop(ifp, "EXPIRE");
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ }
+ state->interval = 0;
+ dhcp_discover(ifp);
+}
+
+#if defined(ARP) || defined(IN_IFF_DUPLICATED)
+static void
+dhcp_decline(struct interface *ifp)
+{
+
+ send_message(ifp, DHCP_DECLINE, NULL);
+}
+#endif
+
+static void
+dhcp_startrenew(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state;
+ struct dhcp_lease *lease;
+
+ if ((state = D_STATE(ifp)) == NULL)
+ return;
+
+ /* Only renew in the bound or renew states */
+ if (state->state != DHS_BOUND &&
+ state->state != DHS_RENEW)
+ return;
+
+ /* Remove the timeout as the renew may have been forced. */
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp_startrenew, ifp);
+
+ lease = &state->lease;
+ logdebugx("%s: renewing lease of %s", ifp->name,
+ inet_ntoa(lease->addr));
+ state->state = DHS_RENEW;
+ dhcp_new_xid(ifp);
+ state->interval = 0;
+ send_renew(ifp);
+}
+
+void
+dhcp_renew(struct interface *ifp)
+{
+
+ dhcp_startrenew(ifp);
+}
+
+static void
+dhcp_rebind(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct dhcp_lease *lease = &state->lease;
+
+ logwarnx("%s: failed to renew DHCP, rebinding", ifp->name);
+ logdebugx("%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 = INADDR_ANY;
+ state->interval = 0;
+ ifp->options->options &= ~(DHCPCD_CSR_WARNED |
+ DHCPCD_ROUTER_HOST_ROUTE_WARNED);
+ send_rebind(ifp);
+}
+
+#if defined(ARP) || defined(IN_IFF_DUPLICATED)
+static void
+dhcp_finish_dad(struct interface *ifp, struct in_addr *ia)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+
+ if (state->state != DHS_PROBE)
+ return;
+ if (state->offer == NULL || state->offer->yiaddr != ia->s_addr)
+ return;
+
+ logdebugx("%s: DAD completed for %s", ifp->name, inet_ntoa(*ia));
+ if (!(ifp->options->options & DHCPCD_INFORM))
+ dhcp_bind(ifp);
+#ifndef IN_IFF_DUPLICATED
+ else {
+ struct bootp *bootp;
+ size_t len;
+
+ bootp = state->new;
+ len = state->new_len;
+ state->new = state->offer;
+ state->new_len = state->offer_len;
+ get_lease(ifp, &state->lease, state->new, state->new_len);
+ ipv4_applyaddr(ifp);
+ state->new = bootp;
+ state->new_len = len;
+ }
+#endif
+
+#ifdef IPV4LL
+ /* Stop IPv4LL now we have a working DHCP address */
+ if (!IN_LINKLOCAL(ntohl(ia->s_addr)))
+ ipv4ll_drop(ifp);
+#endif
+
+ if (ifp->options->options & DHCPCD_INFORM)
+ dhcp_inform(ifp);
+}
+
+static bool
+dhcp_addr_duplicated(struct interface *ifp, struct in_addr *ia)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ unsigned long long opts = ifp->options->options;
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ bool deleted = false;
+#ifdef IN_IFF_DUPLICATED
+ struct ipv4_addr *iap;
+#endif
+
+ if ((state->offer == NULL || state->offer->yiaddr != ia->s_addr) &&
+ !IN_ARE_ADDR_EQUAL(ia, &state->lease.addr))
+ return deleted;
+
+ /* RFC 2131 3.1.5, Client-server interaction */
+ logerrx("%s: DAD detected %s", ifp->name, inet_ntoa(*ia));
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ if (!(opts & DHCPCD_STATIC) && !state->lease.frominfo)
+ dhcp_decline(ifp);
+#ifdef IN_IFF_DUPLICATED
+ if ((iap = ipv4_iffindaddr(ifp, ia, NULL)) != NULL) {
+ ipv4_deladdr(iap, 0);
+ deleted = true;
+ }
+#endif
+ eloop_timeout_delete(ctx->eloop, NULL, ifp);
+ if (opts & (DHCPCD_STATIC | DHCPCD_INFORM)) {
+ state->reason = "EXPIRE";
+ script_runreason(ifp, state->reason);
+#define NOT_ONLY_SELF (DHCPCD_MANAGER | DHCPCD_IPV6RS | DHCPCD_DHCP6)
+ if (!(ctx->options & NOT_ONLY_SELF))
+ eloop_exit(ifp->ctx->eloop, EXIT_FAILURE);
+ return deleted;
+ }
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ DHCP_RAND_MAX, dhcp_discover, ifp);
+ return deleted;
+}
+#endif
+
+#ifdef ARP
+#ifdef KERNEL_RFC5227
+#ifdef ARPING
+static void
+dhcp_arp_announced(struct arp_state *state)
+{
+
+ arp_free(state);
+}
+#endif
+#else
+static void
+dhcp_arp_defend_failed(struct arp_state *astate)
+{
+ struct interface *ifp = astate->iface;
+
+ dhcp_drop(ifp, "EXPIRED");
+ dhcp_start1(ifp);
+}
+#endif
+
+#if !defined(KERNEL_RFC5227) || defined(ARPING)
+static void dhcp_arp_not_found(struct arp_state *);
+
+static struct arp_state *
+dhcp_arp_new(struct interface *ifp, struct in_addr *addr)
+{
+ struct arp_state *astate;
+
+ astate = arp_new(ifp, addr);
+ if (astate == NULL)
+ return NULL;
+
+ astate->found_cb = dhcp_arp_found;
+ astate->not_found_cb = dhcp_arp_not_found;
+#ifdef KERNEL_RFC5227
+ astate->announced_cb = dhcp_arp_announced;
+#else
+ astate->announced_cb = NULL;
+ astate->defend_failed_cb = dhcp_arp_defend_failed;
+#endif
+ return astate;
+}
+#endif
+
+#ifdef ARPING
+static int
+dhcp_arping(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+ struct arp_state *astate;
+ struct in_addr addr;
+
+ state = D_STATE(ifp);
+ ifo = ifp->options;
+
+ if (ifo->arping_len == 0 || state->arping_index > ifo->arping_len)
+ return 0;
+
+ if (state->arping_index + 1 == ifo->arping_len) {
+ state->arping_index++;
+ dhcpcd_startinterface(ifp);
+ return 1;
+ }
+
+ addr.s_addr = ifo->arping[++state->arping_index];
+ astate = dhcp_arp_new(ifp, &addr);
+ if (astate == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ arp_probe(astate);
+ return 1;
+}
+#endif
+
+#if !defined(KERNEL_RFC5227) || defined(ARPING)
+static void
+dhcp_arp_not_found(struct arp_state *astate)
+{
+ struct interface *ifp;
+
+ ifp = astate->iface;
+#ifdef ARPING
+ if (dhcp_arping(ifp) == 1) {
+ arp_free(astate);
+ return;
+ }
+#endif
+
+ dhcp_finish_dad(ifp, &astate->addr);
+}
+
+static void
+dhcp_arp_found(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct in_addr addr;
+ struct interface *ifp = astate->iface;
+#ifdef ARPING
+ struct dhcp_state *state;
+ struct if_options *ifo;
+
+ state = D_STATE(ifp);
+
+ ifo = ifp->options;
+ if (state->arping_index != -1 &&
+ state->arping_index < ifo->arping_len &&
+ amsg &&
+ amsg->sip.s_addr == ifo->arping[state->arping_index])
+ {
+ char buf[HWADDR_LEN * 3];
+
+ hwaddr_ntoa(amsg->sha, ifp->hwlen, buf, sizeof(buf));
+ if (dhcpcd_selectprofile(ifp, buf) == -1 &&
+ dhcpcd_selectprofile(ifp, inet_ntoa(amsg->sip)) == -1)
+ {
+ /* We didn't find a profile for this
+ * address or hwaddr, so move to the next
+ * arping profile */
+ dhcp_arp_not_found(astate);
+ return;
+ }
+ arp_free(astate);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ dhcpcd_startinterface(ifp);
+ return;
+ }
+#else
+ UNUSED(amsg);
+#endif
+
+ addr = astate->addr;
+ arp_free(astate);
+ dhcp_addr_duplicated(ifp, &addr);
+}
+#endif
+
+#endif /* ARP */
+
+void
+dhcp_bind(struct interface *ifp)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct if_options *ifo = ifp->options;
+ struct dhcp_lease *lease = &state->lease;
+ uint8_t old_state;
+
+ state->reason = NULL;
+ /* If we don't have an offer, we are re-binding a lease on preference,
+ * normally when two interfaces have a lease matching IP addresses. */
+ if (state->offer) {
+ free(state->old);
+ state->old = state->new;
+ state->old_len = state->new_len;
+ state->new = state->offer;
+ state->new_len = state->offer_len;
+ state->offer = NULL;
+ state->offer_len = 0;
+ }
+ get_lease(ifp, lease, state->new, state->new_len);
+ if (ifo->options & DHCPCD_STATIC) {
+ loginfox("%s: using static address %s/%d",
+ ifp->name, inet_ntoa(lease->addr),
+ inet_ntocidr(lease->mask));
+ lease->leasetime = DHCP_INFINITE_LIFETIME;
+ state->reason = "STATIC";
+ } else if (ifo->options & DHCPCD_INFORM) {
+ loginfox("%s: received approval for %s",
+ ifp->name, inet_ntoa(lease->addr));
+ lease->leasetime = DHCP_INFINITE_LIFETIME;
+ state->reason = "INFORM";
+ } else {
+ if (lease->frominfo)
+ state->reason = "TIMEOUT";
+ if (lease->leasetime == DHCP_INFINITE_LIFETIME) {
+ lease->renewaltime =
+ lease->rebindtime =
+ lease->leasetime;
+ loginfox("%s: leased %s for infinity",
+ ifp->name, inet_ntoa(lease->addr));
+ } else {
+ if (lease->leasetime < DHCP_MIN_LEASE) {
+ logwarnx("%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);
+ logwarnx("%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);
+ logwarnx("%s: renewal time greater than "
+ "rebind time, forcing to %"PRIu32" seconds",
+ ifp->name, lease->renewaltime);
+ }
+ if (state->state == DHS_RENEW && state->addr &&
+ lease->addr.s_addr == state->addr->addr.s_addr &&
+ !(state->added & STATE_FAKE))
+ logdebugx("%s: leased %s for %"PRIu32" seconds",
+ ifp->name, inet_ntoa(lease->addr),
+ lease->leasetime);
+ else
+ loginfox("%s: leased %s for %"PRIu32" seconds",
+ ifp->name, inet_ntoa(lease->addr),
+ lease->leasetime);
+ }
+ }
+ if (ctx->options & DHCPCD_TEST) {
+ state->reason = "TEST";
+ script_runreason(ifp, state->reason);
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+ if (state->reason == NULL) {
+ if (state->old &&
+ !(state->added & (STATE_FAKE | STATE_EXPIRED)))
+ {
+ if (state->old->yiaddr == state->new->yiaddr &&
+ lease->server.s_addr &&
+ state->state != DHS_REBIND)
+ state->reason = "RENEW";
+ else
+ state->reason = "REBIND";
+ } else if (state->state == DHS_REBOOT)
+ state->reason = "REBOOT";
+ else
+ state->reason = "BOUND";
+ }
+ if (lease->leasetime == DHCP_INFINITE_LIFETIME)
+ lease->renewaltime = lease->rebindtime = lease->leasetime;
+ else {
+ eloop_timeout_add_sec(ctx->eloop,
+ lease->renewaltime, dhcp_startrenew, ifp);
+ eloop_timeout_add_sec(ctx->eloop,
+ lease->rebindtime, dhcp_rebind, ifp);
+ eloop_timeout_add_sec(ctx->eloop,
+ lease->leasetime, dhcp_expire, ifp);
+ logdebugx("%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))) {
+ logdebugx("%s: writing lease: %s",
+ ifp->name, state->leasefile);
+ if (dhcp_writefile(ifp->ctx, state->leasefile, 0640,
+ state->new, state->new_len) == -1)
+ logerr("dhcp_writefile: %s", state->leasefile);
+ }
+
+ old_state = state->added;
+
+ if (!(ifo->options & DHCPCD_CONFIGURE)) {
+ struct ipv4_addr *ia;
+
+ script_runreason(ifp, state->reason);
+ dhcpcd_daemonise(ifp->ctx);
+
+ /* We we are not configuring the address, we need to keep
+ * the BPF socket open if the address does not exist. */
+ ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL);
+ if (ia != NULL) {
+ state->addr = ia;
+ state->added = STATE_ADDED;
+ dhcp_closebpf(ifp);
+ goto openudp;
+ }
+ return;
+ }
+
+ /* Add the address */
+ if (ipv4_applyaddr(ifp) == NULL) {
+ /* There was an error adding the address.
+ * If we are in oneshot, exit with a failure. */
+ if (ctx->options & DHCPCD_ONESHOT) {
+ loginfox("exiting due to oneshot");
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ }
+ return;
+ }
+
+ /* Close the BPF filter as we can now receive DHCP messages
+ * on a UDP socket. */
+ dhcp_closebpf(ifp);
+
+openudp:
+ /* If not in manager mode, open an address specific socket. */
+ if (ctx->options & DHCPCD_MANAGER ||
+ ifo->options & DHCPCD_STATIC ||
+ (state->old != NULL &&
+ state->old->yiaddr == state->new->yiaddr &&
+ old_state & STATE_ADDED && !(old_state & STATE_FAKE)))
+ return;
+
+ dhcp_closeinet(ifp);
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ctx)) {
+ if (ps_inet_openbootp(state->addr) == -1)
+ logerr(__func__);
+ return;
+ }
+#endif
+
+ state->udp_rfd = dhcp_openudp(&state->addr->addr);
+ if (state->udp_rfd == -1) {
+ logerr(__func__);
+ /* Address sharing without manager mode is not supported.
+ * It's also possible another DHCP client could be running,
+ * which is even worse.
+ * We still need to work, so re-open BPF. */
+ dhcp_openbpf(ifp);
+ return;
+ }
+ eloop_event_add(ctx->eloop, state->udp_rfd, dhcp_handleifudp, ifp);
+}
+
+static size_t
+dhcp_message_new(struct bootp **bootp,
+ const struct in_addr *addr, const struct in_addr *mask)
+{
+ uint8_t *p;
+ uint32_t cookie;
+
+ if ((*bootp = calloc(1, sizeof(**bootp))) == NULL)
+ return 0;
+
+ (*bootp)->yiaddr = addr->s_addr;
+ p = (*bootp)->vend;
+
+ cookie = htonl(MAGIC_COOKIE);
+ memcpy(p, &cookie, sizeof(cookie));
+ p += sizeof(cookie);
+
+ if (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 sizeof(**bootp);
+}
+
+#if defined(ARP) || defined(KERNEL_RFC5227)
+static int
+dhcp_arp_address(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ struct in_addr addr;
+ struct ipv4_addr *ia;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+ state = D_STATE(ifp);
+ addr.s_addr = state->offer->yiaddr == INADDR_ANY ?
+ state->offer->ciaddr : state->offer->yiaddr;
+ /* If the interface already has the address configured
+ * then we can't ARP for duplicate detection. */
+ ia = ipv4_iffindaddr(ifp, &addr, NULL);
+#ifdef IN_IFF_NOTUSEABLE
+ if (ia == NULL || ia->addr_flags & IN_IFF_NOTUSEABLE) {
+ state->state = DHS_PROBE;
+ if (ia == NULL) {
+ struct dhcp_lease l;
+
+ get_lease(ifp, &l, state->offer, state->offer_len);
+ /* Add the address now, let the kernel handle DAD. */
+ ipv4_addaddr(ifp, &l.addr, &l.mask, &l.brd,
+ l.leasetime, l.rebindtime);
+ } else if (ia->addr_flags & IN_IFF_DUPLICATED)
+ dhcp_addr_duplicated(ifp, &ia->addr);
+ else
+ loginfox("%s: waiting for DAD on %s",
+ ifp->name, inet_ntoa(addr));
+ return 0;
+ }
+#else
+ if (!(ifp->flags & IFF_NOARP) &&
+ ifp->options->options & DHCPCD_ARP)
+ {
+ struct arp_state *astate;
+ struct dhcp_lease l;
+
+ /* Even if the address exists, we need to defend it. */
+ astate = dhcp_arp_new(ifp, &addr);
+ if (astate == NULL)
+ return -1;
+
+ if (ia == NULL) {
+ state->state = DHS_PROBE;
+ get_lease(ifp, &l, state->offer, state->offer_len);
+ loginfox("%s: probing address %s/%d",
+ ifp->name, inet_ntoa(l.addr), inet_ntocidr(l.mask));
+ /* We need to handle DAD. */
+ arp_probe(astate);
+ return 0;
+ }
+ }
+#endif
+
+ return 1;
+}
+
+static void
+dhcp_arp_bind(struct interface *ifp)
+{
+
+ if (ifp->ctx->options & DHCPCD_TEST ||
+ dhcp_arp_address(ifp) == 1)
+ dhcp_bind(ifp);
+}
+#endif
+
+static void
+dhcp_lastlease(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+
+ loginfox("%s: timed out contacting a DHCP server, using last lease",
+ ifp->name);
+#if defined(ARP) || defined(KERNEL_RFC5227)
+ dhcp_arp_bind(ifp);
+#else
+ dhcp_bind(ifp);
+#endif
+ /* Set expired here because dhcp_bind() -> ipv4_addaddr() will reset
+ * state */
+ state->added |= STATE_EXPIRED;
+ state->interval = 0;
+ dhcp_discover(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)
+ {
+ loginfox("%s: waiting for 3rd party to "
+ "configure IP address", ifp->name);
+ state->reason = "3RDPARTY";
+ script_runreason(ifp, state->reason);
+ return;
+ }
+
+ state->offer_len = dhcp_message_new(&state->offer,
+ ia ? &ia->addr : &ifo->req_addr,
+ ia ? &ia->mask : &ifo->req_mask);
+ if (state->offer_len)
+#if defined(ARP) || defined(KERNEL_RFC5227)
+ dhcp_arp_bind(ifp);
+#else
+ dhcp_bind(ifp);
+#endif
+}
+
+void
+dhcp_inform(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+ struct ipv4_addr *ia;
+
+ state = D_STATE(ifp);
+ ifo = ifp->options;
+
+ free(state->offer);
+ state->offer = NULL;
+ state->offer_len = 0;
+
+ if (ifo->req_addr.s_addr == INADDR_ANY) {
+ ia = ipv4_iffindaddr(ifp, NULL, NULL);
+ if (ia == NULL) {
+ loginfox("%s: waiting for 3rd party to "
+ "configure IP address",
+ ifp->name);
+ if (!(ifp->ctx->options & DHCPCD_TEST)) {
+ state->reason = "3RDPARTY";
+ script_runreason(ifp, state->reason);
+ }
+ return;
+ }
+ } else {
+ ia = ipv4_iffindaddr(ifp, &ifo->req_addr, &ifo->req_mask);
+ if (ia == NULL) {
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ logerrx("%s: cannot add IP address in test mode",
+ ifp->name);
+ return;
+ }
+ ia = ipv4_iffindaddr(ifp, &ifo->req_addr, NULL);
+ if (ia != NULL)
+ /* Netmask must be different, delete it. */
+ ipv4_deladdr(ia, 1);
+ state->offer_len = dhcp_message_new(&state->offer,
+ &ifo->req_addr, &ifo->req_mask);
+#ifdef ARP
+ if (dhcp_arp_address(ifp) != 1)
+ return;
+#endif
+ ia = ipv4_iffindaddr(ifp,
+ &ifo->req_addr, &ifo->req_mask);
+ assert(ia != NULL);
+ }
+ }
+
+ state->state = DHS_INFORM;
+ state->addr = ia;
+ state->offer_len = dhcp_message_new(&state->offer,
+ &ia->addr, &ia->mask);
+ if (state->offer_len) {
+ dhcp_new_xid(ifp);
+ get_lease(ifp, &state->lease, state->offer, state->offer_len);
+ 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 || state->state == DHS_NONE)
+ return;
+ ifo = ifp->options;
+ if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) &&
+ (state->addr == NULL ||
+ state->addr->addr.s_addr != ifo->req_addr.s_addr)) ||
+ (oldopts & (DHCPCD_INFORM | DHCPCD_STATIC) &&
+ !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))))
+ {
+ dhcp_drop(ifp, "EXPIRE");
+ }
+}
+
+#ifdef ARP
+static int
+dhcp_activeaddr(const struct interface *ifp, const struct in_addr *addr)
+{
+ const struct interface *ifp1;
+ const struct dhcp_state *state;
+
+ TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) {
+ if (ifp1 == ifp)
+ continue;
+ if ((state = D_CSTATE(ifp1)) == NULL)
+ continue;
+ switch(state->state) {
+ case DHS_REBOOT:
+ case DHS_RENEW:
+ case DHS_REBIND:
+ case DHS_BOUND:
+ case DHS_INFORM:
+ break;
+ default:
+ continue;
+ }
+ if (state->lease.addr.s_addr == addr->s_addr)
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+static void
+dhcp_reboot(struct interface *ifp)
+{
+ struct if_options *ifo;
+ struct dhcp_state *state = D_STATE(ifp);
+#ifdef ARP
+ struct ipv4_addr *ia;
+#endif
+
+ if (state == NULL || state->state == DHS_NONE)
+ return;
+ ifo = ifp->options;
+ state->state = DHS_REBOOT;
+ state->interval = 0;
+
+ if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) {
+ loginfox("%s: waiting for carrier", ifp->name);
+ return;
+ }
+ if (ifo->options & DHCPCD_STATIC) {
+ dhcp_static(ifp);
+ return;
+ }
+ if (ifo->options & DHCPCD_INFORM) {
+ loginfox("%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 (!IS_DHCP(state->offer))
+ return;
+
+ loginfox("%s: rebinding lease of %s",
+ ifp->name, inet_ntoa(state->lease.addr));
+
+#ifdef ARP
+#ifndef KERNEL_RFC5227
+ /* Create the DHCP ARP state so we can defend it. */
+ (void)dhcp_arp_new(ifp, &state->lease.addr);
+#endif
+
+ /* If the address exists on the interface and no other interface
+ * is currently using it then announce it to ensure this
+ * interface gets the reply. */
+ ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL);
+ if (ia != NULL &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+#ifdef IN_IFF_NOTUSEABLE
+ !(ia->addr_flags & IN_IFF_NOTUSEABLE) &&
+#endif
+ dhcp_activeaddr(ifp, &state->lease.addr) == 0)
+ arp_ifannounceaddr(ifp, &state->lease.addr);
+#endif
+
+ dhcp_new_xid(ifp);
+ state->lease.server.s_addr = INADDR_ANY;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+#ifdef IPV4LL
+ /* 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);
+#endif
+
+ if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ifo->reboot, dhcp_lastlease, 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 || state->state == DHS_NONE) {
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ return;
+ }
+
+#ifdef ARP
+ if (state->addr != NULL)
+ arp_freeaddr(ifp, &state->addr->addr);
+#endif
+#ifdef ARPING
+ state->arping_index = -1;
+#endif
+
+ if (ifp->options->options & DHCPCD_RELEASE &&
+ !(ifp->options->options & DHCPCD_INFORM))
+ {
+ /* 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;
+
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ if (if_is_link_up(ifp) &&
+ state->new != NULL &&
+ state->lease.server.s_addr != INADDR_ANY)
+ {
+ loginfox("%s: releasing lease of %s",
+ ifp->name, inet_ntoa(state->lease.addr));
+ dhcp_new_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
+ }
+ }
+#ifdef AUTH
+ else if (state->auth.reconf != NULL) {
+ /*
+ * Drop the lease as the token may only be present
+ * in the initial reply message and not subsequent
+ * renewals.
+ * If dhcpcd is restarted, the token is lost.
+ * XXX persist this in another file?
+ */
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ }
+#endif
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+#ifdef AUTH
+ dhcp_auth_reset(&state->auth);
+#endif
+
+ /* Close DHCP ports so a changed interface family is picked
+ * up by a new BPF state. */
+ dhcp_close(ifp);
+
+ state->state = DHS_NONE;
+ free(state->offer);
+ state->offer = NULL;
+ state->offer_len = 0;
+ free(state->old);
+ state->old = state->new;
+ state->old_len = state->new_len;
+ state->new = NULL;
+ state->new_len = 0;
+ state->reason = reason;
+ if (ifp->options->options & DHCPCD_CONFIGURE)
+ ipv4_applyaddr(ifp);
+ else {
+ state->addr = NULL;
+ state->added = 0;
+ script_runreason(ifp, state->reason);
+ }
+ free(state->old);
+ state->old = NULL;
+ state->old_len = 0;
+ state->lease.addr.s_addr = 0;
+ ifp->options->options &= ~(DHCPCD_CSR_WARNED |
+ DHCPCD_ROUTER_HOST_ROUTE_WARNED);
+}
+
+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;
+}
+
+#define WHTLST_NONE 0
+#define WHTLST_MATCH 1
+#define WHTLST_NOMATCH 2
+static unsigned int
+whitelisted_ip(const struct if_options *ifo, in_addr_t addr)
+{
+ size_t i;
+
+ if (ifo->whitelist_len == 0)
+ return WHTLST_NONE;
+ for (i = 0; i < ifo->whitelist_len; i += 2)
+ if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1]))
+ return WHTLST_MATCH;
+ return WHTLST_NOMATCH;
+}
+
+static void
+log_dhcp(int loglevel, const char *msg,
+ const struct interface *ifp, const struct bootp *bootp, size_t bootp_len,
+ const struct in_addr *from, int ad)
+{
+ const char *tfrom;
+ char *a, sname[sizeof(bootp->sname) * 4];
+ struct in_addr addr;
+ int r;
+ uint8_t overl;
+
+ if (strcmp(msg, "NAK:") == 0) {
+ a = get_option_string(ifp->ctx, bootp, bootp_len, DHO_MESSAGE);
+ if (a) {
+ char *tmp;
+ size_t al, tmpl;
+
+ al = strlen(a);
+ tmpl = (al * 4) + 1;
+ tmp = malloc(tmpl);
+ if (tmp == NULL) {
+ logerr(__func__);
+ free(a);
+ return;
+ }
+ print_string(tmp, tmpl, OT_STRING, (uint8_t *)a, al);
+ free(a);
+ a = tmp;
+ }
+ } else if (ad && bootp->yiaddr != 0) {
+ addr.s_addr = bootp->yiaddr;
+ a = strdup(inet_ntoa(addr));
+ if (a == NULL) {
+ logerr(__func__);
+ return;
+ }
+ } else
+ a = NULL;
+
+ tfrom = "from";
+ r = get_option_addr(ifp->ctx, &addr, bootp, bootp_len, DHO_SERVERID);
+ if (get_option_uint8(ifp->ctx, &overl, bootp, bootp_len,
+ DHO_OPTSOVERLOADED) == -1)
+ overl = 0;
+ if (bootp->sname[0] && r == 0 && !(overl & 2)) {
+ print_string(sname, sizeof(sname), OT_STRING | OT_DOMAIN,
+ bootp->sname, sizeof(bootp->sname));
+ if (a == NULL)
+ logmessage(loglevel, "%s: %s %s %s %s",
+ ifp->name, msg, tfrom, inet_ntoa(addr), sname);
+ else
+ logmessage(loglevel, "%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)
+ logmessage(loglevel, "%s: %s %s %s",
+ ifp->name, msg, tfrom, inet_ntoa(addr));
+ else
+ logmessage(loglevel, "%s: %s %s %s %s",
+ ifp->name, msg, a, tfrom, inet_ntoa(addr));
+ }
+ free(a);
+}
+
+/* If we're sharing the same IP address with another interface on the
+ * same network, we may receive the DHCP reply on the wrong interface.
+ * Try and re-direct it here. */
+static void
+dhcp_redirect_dhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
+ const struct in_addr *from)
+{
+ struct interface *ifn;
+ const struct dhcp_state *state;
+ uint32_t xid;
+
+ xid = ntohl(bootp->xid);
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ifn == ifp)
+ continue;
+ state = D_CSTATE(ifn);
+ if (state == NULL || state->state == DHS_NONE)
+ continue;
+ if (state->xid != xid)
+ continue;
+ if (ifn->hwlen <= sizeof(bootp->chaddr) &&
+ memcmp(bootp->chaddr, ifn->hwaddr, ifn->hwlen))
+ continue;
+ logdebugx("%s: redirecting DHCP message to %s",
+ ifp->name, ifn->name);
+ dhcp_handledhcp(ifn, bootp, bootp_len, from);
+ }
+}
+
+static void
+dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
+ const struct in_addr *from)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct if_options *ifo = ifp->options;
+ struct dhcp_lease *lease = &state->lease;
+ uint8_t type, tmp;
+ struct in_addr addr;
+ unsigned int i;
+ char *msg;
+ bool bootp_copied;
+ uint32_t v6only_time = 0;
+ bool use_v6only = false;
+#ifdef AUTH
+ const uint8_t *auth;
+ size_t auth_len;
+#endif
+#ifdef IN_IFF_DUPLICATED
+ struct ipv4_addr *ia;
+#endif
+
+#define LOGDHCP0(l, m) \
+ log_dhcp((l), (m), ifp, bootp, bootp_len, from, 0)
+#define LOGDHCP(l, m) \
+ log_dhcp((l), (m), ifp, bootp, bootp_len, from, 1)
+
+#define IS_STATE_ACTIVE(s) ((s)-state != DHS_NONE && \
+ (s)->state != DHS_INIT && (s)->state != DHS_BOUND)
+
+ if (bootp->op != BOOTREPLY) {
+ if (IS_STATE_ACTIVE(state))
+ logdebugx("%s: op (%d) is not BOOTREPLY",
+ ifp->name, bootp->op);
+ return;
+ }
+
+ if (state->xid != ntohl(bootp->xid)) {
+ if (IS_STATE_ACTIVE(state))
+ logdebugx("%s: wrong xid 0x%x (expecting 0x%x) from %s",
+ ifp->name, ntohl(bootp->xid), state->xid,
+ inet_ntoa(*from));
+ dhcp_redirect_dhcp(ifp, bootp, bootp_len, from);
+ return;
+ }
+
+ if (ifp->hwlen <= sizeof(bootp->chaddr) &&
+ memcmp(bootp->chaddr, ifp->hwaddr, ifp->hwlen))
+ {
+ if (IS_STATE_ACTIVE(state)) {
+ char buf[sizeof(bootp->chaddr) * 3];
+
+ logdebugx("%s: xid 0x%x is for hwaddr %s",
+ ifp->name, ntohl(bootp->xid),
+ hwaddr_ntoa(bootp->chaddr, sizeof(bootp->chaddr),
+ buf, sizeof(buf)));
+ }
+ dhcp_redirect_dhcp(ifp, bootp, bootp_len, from);
+ return;
+ }
+
+ if (!ifp->active)
+ return;
+
+ i = whitelisted_ip(ifp->options, from->s_addr);
+ switch (i) {
+ case WHTLST_NOMATCH:
+ logwarnx("%s: non whitelisted DHCP packet from %s",
+ ifp->name, inet_ntoa(*from));
+ return;
+ case WHTLST_MATCH:
+ break;
+ case WHTLST_NONE:
+ if (blacklisted_ip(ifp->options, from->s_addr) == 1) {
+ logwarnx("%s: blacklisted DHCP packet from %s",
+ ifp->name, inet_ntoa(*from));
+ return;
+ }
+ }
+
+ /* We may have found a BOOTP server */
+ if (get_option_uint8(ifp->ctx, &type,
+ bootp, bootp_len, DHO_MESSAGETYPE) == -1)
+ type = 0;
+ else if (ifo->options & DHCPCD_BOOTP) {
+ logdebugx("%s: ignoring DHCP reply (expecting BOOTP)",
+ ifp->name);
+ return;
+ }
+
+#ifdef AUTH
+ /* Authenticate the message */
+ auth = get_option(ifp->ctx, bootp, bootp_len,
+ DHO_AUTHENTICATION, &auth_len);
+ if (auth) {
+ if (dhcp_auth_validate(&state->auth, &ifo->auth,
+ (uint8_t *)bootp, bootp_len, 4, type,
+ auth, auth_len) == NULL)
+ {
+ LOGDHCP0(LOG_ERR, "authentication failed");
+ return;
+ }
+ if (state->auth.token)
+ logdebugx("%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ loginfox("%s: accepted reconfigure key", ifp->name);
+ } else if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+ LOGDHCP0(LOG_ERR, "no authentication");
+ return;
+ }
+ LOGDHCP0(LOG_WARNING, "no authentication");
+ }
+#endif
+
+ /* RFC 3203 */
+ if (type == DHCP_FORCERENEW) {
+ if (from->s_addr == INADDR_ANY ||
+ from->s_addr == INADDR_BROADCAST)
+ {
+ LOGDHCP(LOG_ERR, "discarding Force Renew");
+ return;
+ }
+#ifdef AUTH
+ if (auth == NULL) {
+ LOGDHCP(LOG_ERR, "unauthenticated Force Renew");
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE)
+ return;
+ }
+ if (state->state != DHS_BOUND && state->state != DHS_INFORM) {
+ LOGDHCP(LOG_DEBUG, "not bound, ignoring Force Renew");
+ return;
+ }
+ LOGDHCP(LOG_INFO, "Force Renew from");
+ /* The rebind and expire timings are still the same, we just
+ * enter the renew state early */
+ if (state->state == DHS_BOUND)
+ dhcp_renew(ifp);
+ else {
+ eloop_timeout_delete(ifp->ctx->eloop,
+ send_inform, ifp);
+ dhcp_inform(ifp);
+ }
+#else
+ LOGDHCP(LOG_ERR, "unauthenticated Force Renew");
+#endif
+ return;
+ }
+
+ if (state->state == DHS_BOUND) {
+ LOGDHCP(LOG_DEBUG, "bound, ignoring");
+ return;
+ }
+
+ if (state->state == DHS_PROBE) {
+ /* Ignore any DHCP messages whilst probing a lease to bind. */
+ LOGDHCP(LOG_DEBUG, "probing, ignoring");
+ 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,
+ bootp, bootp_len, (uint8_t)i) == 0)
+ {
+ LOGDHCP(LOG_WARNING, "reject DHCP");
+ 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,
+ bootp, bootp_len, DHO_SERVERID) == -1)
+ {
+ LOGDHCP(LOG_WARNING, "reject NAK");
+ return;
+ }
+
+ /* We should restart on a NAK */
+ LOGDHCP(LOG_WARNING, "NAK:");
+ if ((msg = get_option_string(ifp->ctx,
+ bootp, bootp_len, DHO_MESSAGE)))
+ {
+ logwarnx("%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");
+ dhcp_unlink(ifp->ctx, 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,
+ bootp, bootp_len, (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;
+ LOGDHCP(LOG_WARNING, "reject DHCP");
+ return;
+ }
+ }
+
+ if (has_option_mask(ifo->requestmask, DHO_IPV6_PREFERRED_ONLY)) {
+ if (get_option_uint32(ifp->ctx, &v6only_time, bootp, bootp_len,
+ DHO_IPV6_PREFERRED_ONLY) == 0 &&
+ (state->state == DHS_DISCOVER || state->state == DHS_REBOOT))
+ {
+ char v6msg[128];
+
+ use_v6only = true;
+ if (v6only_time < MIN_V6ONLY_WAIT)
+ v6only_time = MIN_V6ONLY_WAIT;
+ snprintf(v6msg, sizeof(v6msg),
+ "IPv6-Only Preferred received (%u seconds)",
+ v6only_time);
+ LOGDHCP(LOG_INFO, v6msg);
+ }
+ }
+
+ /* DHCP Auto-Configure, RFC 2563 */
+ if (type == DHCP_OFFER && bootp->yiaddr == 0) {
+ LOGDHCP(LOG_WARNING, "no address given");
+ if ((msg = get_option_string(ifp->ctx,
+ bootp, bootp_len, DHO_MESSAGE)))
+ {
+ logwarnx("%s: message: %s", ifp->name, msg);
+ free(msg);
+ }
+#ifdef IPV4LL
+ if (state->state == DHS_DISCOVER &&
+ get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len,
+ DHO_AUTOCONFIGURE) == 0)
+ {
+ switch (tmp) {
+ case 0:
+ LOGDHCP(LOG_WARNING, "IPv4LL disabled from");
+ ipv4ll_drop(ifp);
+#ifdef ARP
+ arp_drop(ifp);
+#endif
+ break;
+ case 1:
+ LOGDHCP(LOG_WARNING, "IPv4LL enabled from");
+ ipv4ll_start(ifp);
+ break;
+ default:
+ logerrx("%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,
+ use_v6only ? v6only_time : DHCP_MAX,
+ dhcp_discover, ifp);
+ }
+#endif
+ return;
+ }
+
+ if (use_v6only) {
+ dhcp_drop(ifp, "EXPIRE");
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop, v6only_time,
+ dhcp_discover, ifp);
+ return;
+ }
+
+ /* Ensure that the address offered is valid */
+ if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) &&
+ (bootp->ciaddr == INADDR_ANY || bootp->ciaddr == INADDR_BROADCAST)
+ &&
+ (bootp->yiaddr == INADDR_ANY || bootp->yiaddr == INADDR_BROADCAST))
+ {
+ LOGDHCP(LOG_WARNING, "reject invalid address");
+ return;
+ }
+
+#ifdef IN_IFF_DUPLICATED
+ ia = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ if (ia && ia->addr_flags & IN_IFF_DUPLICATED) {
+ LOGDHCP(LOG_WARNING, "declined duplicate address");
+ if (type)
+ dhcp_decline(ifp);
+ ipv4_deladdr(ia, 0);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ DHCP_RAND_MAX, dhcp_discover, ifp);
+ return;
+ }
+#endif
+
+ bootp_copied = false;
+ if ((type == 0 || type == DHCP_OFFER) && state->state == DHS_DISCOVER) {
+ lease->frominfo = 0;
+ lease->addr.s_addr = bootp->yiaddr;
+ memcpy(&lease->cookie, bootp->vend, sizeof(lease->cookie));
+ if (type == 0 ||
+ get_option_addr(ifp->ctx,
+ &lease->server, bootp, bootp_len, DHO_SERVERID) != 0)
+ lease->server.s_addr = INADDR_ANY;
+
+ /* Test for rapid commit in the OFFER */
+ if (!(ifp->ctx->options & DHCPCD_TEST) &&
+ has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT) &&
+ get_option(ifp->ctx, bootp, bootp_len,
+ DHO_RAPIDCOMMIT, NULL))
+ {
+ state->state = DHS_REQUEST;
+ goto rapidcommit;
+ }
+
+ LOGDHCP(LOG_INFO, "offered");
+ if (state->offer_len < bootp_len) {
+ free(state->offer);
+ if ((state->offer = malloc(bootp_len)) == NULL) {
+ logerr(__func__);
+ state->offer_len = 0;
+ return;
+ }
+ }
+ state->offer_len = bootp_len;
+ memcpy(state->offer, bootp, bootp_len);
+ bootp_copied = true;
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ free(state->old);
+ state->old = state->new;
+ state->old_len = state->new_len;
+ state->new = state->offer;
+ state->new_len = state->offer_len;
+ state->offer = NULL;
+ state->offer_len = 0;
+ state->reason = "TEST";
+ script_runreason(ifp, state->reason);
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ state->bpf->bpf_flags |= BPF_EOF;
+ 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) {
+ LOGDHCP(LOG_WARNING, "ignoring offer of");
+ return;
+ }
+
+ /* We should only be dealing with acks */
+ if (type != DHCP_ACK) {
+ LOGDHCP(LOG_ERR, "not ACK or OFFER");
+ return;
+ }
+
+ if (state->state == DHS_DISCOVER) {
+ /* We only allow ACK of rapid commit DISCOVER. */
+ if (has_option_mask(ifo->requestmask,
+ DHO_RAPIDCOMMIT) &&
+ get_option(ifp->ctx, bootp, bootp_len,
+ DHO_RAPIDCOMMIT, NULL))
+ state->state = DHS_REQUEST;
+ else {
+ LOGDHCP(LOG_DEBUG, "ignoring ack of");
+ return;
+ }
+ }
+
+rapidcommit:
+ if (!(ifo->options & DHCPCD_INFORM))
+ LOGDHCP(LOG_DEBUG, "acknowledged");
+ 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. */
+ if (!bootp_copied) {
+ if (state->offer_len < bootp_len) {
+ free(state->offer);
+ if ((state->offer = malloc(bootp_len)) == NULL) {
+ logerr(__func__);
+ state->offer_len = 0;
+ return;
+ }
+ }
+ state->offer_len = bootp_len;
+ memcpy(state->offer, bootp, bootp_len);
+ }
+
+ lease->frominfo = 0;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+#if defined(ARP) || defined(KERNEL_RFC5227)
+ dhcp_arp_bind(ifp);
+#else
+ dhcp_bind(ifp);
+#endif
+}
+
+static void *
+get_udp_data(void *packet, size_t *len)
+{
+ const struct ip *ip = packet;
+ size_t ip_hl = (size_t)ip->ip_hl * 4;
+ char *p = packet;
+
+ p += ip_hl + sizeof(struct udphdr);
+ *len = (size_t)ntohs(ip->ip_len) - sizeof(struct udphdr) - ip_hl;
+ return p;
+}
+
+static bool
+is_packet_udp_bootp(void *packet, size_t plen)
+{
+ struct ip *ip = packet;
+ size_t ip_hlen;
+ struct udphdr udp;
+
+ if (plen < sizeof(*ip))
+ return false;
+
+ if (ip->ip_v != IPVERSION || ip->ip_p != IPPROTO_UDP)
+ return false;
+
+ /* Sanity. */
+ if (ntohs(ip->ip_len) > plen)
+ return false;
+
+ ip_hlen = (size_t)ip->ip_hl * 4;
+ if (ip_hlen < sizeof(*ip))
+ return false;
+
+ /* Check we have a UDP header and BOOTP. */
+ if (ip_hlen + sizeof(udp) + offsetof(struct bootp, vend) > plen)
+ return false;
+
+ /* Sanity. */
+ memcpy(&udp, (char *)ip + ip_hlen, sizeof(udp));
+ if (ntohs(udp.uh_ulen) < sizeof(udp))
+ return false;
+ if (ip_hlen + ntohs(udp.uh_ulen) > plen)
+ return false;
+
+ /* Check it's to and from the right ports. */
+ if (udp.uh_dport != htons(BOOTPC) || udp.uh_sport != htons(BOOTPS))
+ return false;
+
+ return true;
+}
+
+/* Lengths have already been checked. */
+static bool
+checksums_valid(void *packet,
+ struct in_addr *from, unsigned int flags)
+{
+ struct ip *ip = packet;
+ union pip {
+ struct ip ip;
+ uint16_t w[sizeof(struct ip) / 2];
+ } pip = {
+ .ip = {
+ .ip_p = IPPROTO_UDP,
+ .ip_src = ip->ip_src,
+ .ip_dst = ip->ip_dst,
+ }
+ };
+ size_t ip_hlen;
+ struct udphdr udp;
+ char *udpp, *uh_sump;
+ uint32_t csum;
+
+ if (from != NULL)
+ from->s_addr = ip->ip_src.s_addr;
+
+ ip_hlen = (size_t)ip->ip_hl * 4;
+ if (in_cksum(ip, ip_hlen, NULL) != 0)
+ return false;
+
+ if (flags & BPF_PARTIALCSUM)
+ return true;
+
+ udpp = (char *)ip + ip_hlen;
+ memcpy(&udp, udpp, sizeof(udp));
+ if (udp.uh_sum == 0)
+ return true;
+
+ /* UDP checksum is based on a pseudo IP header alongside
+ * the UDP header and payload. */
+ pip.ip.ip_len = udp.uh_ulen;
+ csum = 0;
+
+ /* Need to zero the UDP sum in the packet for the checksum to work. */
+ uh_sump = udpp + offsetof(struct udphdr, uh_sum);
+ memset(uh_sump, 0, sizeof(udp.uh_sum));
+
+ /* Checksum pseudo header and then UDP + payload. */
+ in_cksum(pip.w, sizeof(pip.w), &csum);
+ csum = in_cksum(udpp, ntohs(udp.uh_ulen), &csum);
+
+#if 0 /* Not needed, just here for completeness. */
+ /* Put the checksum back. */
+ memcpy(uh_sump, &udp.uh_sum, sizeof(udp.uh_sum));
+#endif
+
+ return csum == udp.uh_sum;
+}
+
+static void
+dhcp_handlebootp(struct interface *ifp, struct bootp *bootp, size_t len,
+ struct in_addr *from)
+{
+ size_t v;
+
+ if (len < offsetof(struct bootp, vend)) {
+ logerrx("%s: truncated packet (%zu) from %s",
+ ifp->name, len, inet_ntoa(*from));
+ return;
+ }
+
+ /* Unlikely, but appeases sanitizers. */
+ if (len > FRAMELEN_MAX) {
+ logerrx("%s: packet exceeded frame length (%zu) from %s",
+ ifp->name, len, inet_ntoa(*from));
+ return;
+ }
+
+ /* To make our IS_DHCP macro easy, ensure the vendor
+ * area has at least 4 octets. */
+ v = len - offsetof(struct bootp, vend);
+ while (v < 4) {
+ bootp->vend[v++] = '\0';
+ len++;
+ }
+
+ dhcp_handledhcp(ifp, bootp, len, from);
+}
+
+void
+dhcp_packet(struct interface *ifp, uint8_t *data, size_t len,
+ unsigned int bpf_flags)
+{
+ struct bootp *bootp;
+ struct in_addr from;
+ size_t udp_len;
+ size_t fl = bpf_frame_header_len(ifp);
+#ifdef PRIVSEP
+ const struct dhcp_state *state = D_CSTATE(ifp);
+
+ /* It's possible that an interface departs and arrives in short
+ * order to receive a BPF frame out of order.
+ * There is a similar check in ARP, but much lower down the stack.
+ * It's not needed for other inet protocols because we send the
+ * message as a whole and select the interface off that and then
+ * check state. BPF on the other hand is very interface
+ * specific and we do need this check. */
+ if (state == NULL)
+ return;
+
+ /* Ignore double reads */
+ if (IN_PRIVSEP(ifp->ctx)) {
+ switch (state->state) {
+ case DHS_BOUND: /* FALLTHROUGH */
+ case DHS_RENEW:
+ return;
+ default:
+ break;
+ }
+ }
+#endif
+
+ /* Trim frame header */
+ if (fl != 0) {
+ if (len < fl) {
+ logerrx("%s: %s: short frame header %zu",
+ __func__, ifp->name, len);
+ return;
+ }
+ len -= fl;
+ /* Move the data to avoid alignment errors. */
+ memmove(data, data + fl, len);
+ }
+
+ /* Validate filter. */
+ if (!is_packet_udp_bootp(data, len)) {
+#ifdef BPF_DEBUG
+ logerrx("%s: DHCP BPF validation failure", ifp->name);
+#endif
+ return;
+ }
+
+ if (!checksums_valid(data, &from, bpf_flags)) {
+ logerrx("%s: checksum failure from %s",
+ ifp->name, inet_ntoa(from));
+ return;
+ }
+
+ /*
+ * DHCP has a variable option area rather than a fixed vendor area.
+ * Because DHCP uses the BOOTP protocol it should still send BOOTP
+ * sized packets to be RFC compliant.
+ * However some servers send a truncated vendor area.
+ * dhcpcd can work fine without the vendor area being sent.
+ */
+ bootp = get_udp_data(data, &udp_len);
+ dhcp_handlebootp(ifp, bootp, udp_len, &from);
+}
+
+static void
+dhcp_readbpf(void *arg)
+{
+ struct interface *ifp = arg;
+ uint8_t buf[FRAMELEN_MAX];
+ ssize_t bytes;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct bpf *bpf = state->bpf;
+
+ bpf->bpf_flags &= ~BPF_EOF;
+ while (!(bpf->bpf_flags & BPF_EOF)) {
+ bytes = bpf_read(bpf, buf, sizeof(buf));
+ if (bytes == -1) {
+ if (state->state != DHS_NONE) {
+ logerr("%s: %s", __func__, ifp->name);
+ dhcp_close(ifp);
+ }
+ break;
+ }
+ dhcp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags);
+ /* Check we still have a state after processing. */
+ if ((state = D_STATE(ifp)) == NULL)
+ break;
+ if ((bpf = state->bpf) == NULL)
+ break;
+ }
+}
+
+void
+dhcp_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg)
+{
+ struct sockaddr_in *from = (struct sockaddr_in *)msg->msg_name;
+ struct iovec *iov = &msg->msg_iov[0];
+ struct interface *ifp;
+ const struct dhcp_state *state;
+
+ ifp = if_findifpfromcmsg(ctx, msg, NULL);
+ if (ifp == NULL) {
+ logerr(__func__);
+ return;
+ }
+ state = D_CSTATE(ifp);
+ if (state == NULL) {
+ /* Try re-directing it to another interface. */
+ dhcp_redirect_dhcp(ifp, (struct bootp *)iov->iov_base,
+ iov->iov_len, &from->sin_addr);
+ return;
+ }
+
+ if (state->bpf != NULL) {
+ /* Avoid a duplicate read if BPF is open for the interface. */
+ return;
+ }
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ctx)) {
+ switch (state->state) {
+ case DHS_BOUND: /* FALLTHROUGH */
+ case DHS_RENEW:
+ break;
+ default:
+ /* Any other state we ignore it or will receive
+ * via BPF. */
+ return;
+ }
+ }
+#endif
+
+ dhcp_handlebootp(ifp, iov->iov_base, iov->iov_len,
+ &from->sin_addr);
+}
+
+static void
+dhcp_readudp(struct dhcpcd_ctx *ctx, struct interface *ifp)
+{
+ const struct dhcp_state *state;
+ struct sockaddr_in from;
+ union {
+ struct bootp bootp;
+ uint8_t buf[10 * 1024]; /* Maximum MTU */
+ } iovbuf;
+ struct iovec iov = {
+ .iov_base = iovbuf.buf,
+ .iov_len = sizeof(iovbuf.buf),
+ };
+ union {
+ struct cmsghdr hdr;
+#ifdef IP_RECVIF
+ uint8_t buf[CMSG_SPACE(sizeof(struct sockaddr_dl))];
+#else
+ uint8_t buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#endif
+ } cmsgbuf = { .buf = { 0 } };
+ struct msghdr msg = {
+ .msg_name = &from, .msg_namelen = sizeof(from),
+ .msg_iov = &iov, .msg_iovlen = 1,
+ .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ int s;
+ ssize_t bytes;
+
+ if (ifp != NULL) {
+ state = D_CSTATE(ifp);
+ s = state->udp_rfd;
+ } else
+ s = ctx->udp_rfd;
+
+ bytes = recvmsg(s, &msg, 0);
+ if (bytes == -1) {
+ logerr(__func__);
+ return;
+ }
+
+ iov.iov_len = (size_t)bytes;
+ dhcp_recvmsg(ctx, &msg);
+}
+
+static void
+dhcp_handleudp(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ dhcp_readudp(ctx, NULL);
+}
+
+static void
+dhcp_handleifudp(void *arg)
+{
+ struct interface *ifp = arg;
+
+ dhcp_readudp(ifp->ctx, ifp);
+}
+
+static int
+dhcp_openbpf(struct interface *ifp)
+{
+ struct dhcp_state *state;
+
+ state = D_STATE(ifp);
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ifp->ctx)) {
+ if (ps_bpf_openbootp(ifp) == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ return 0;
+ }
+#endif
+
+ if (state->bpf != NULL)
+ return 0;
+
+ state->bpf = bpf_open(ifp, bpf_bootp, NULL);
+ if (state->bpf == NULL) {
+ if (errno == ENOENT) {
+ logerrx("%s not found", bpf_name);
+ /* May as well disable IPv4 entirely at
+ * this point as we really need it. */
+ ifp->options->options &= ~DHCPCD_IPV4;
+ } else
+ logerr("%s: %s", __func__, ifp->name);
+ return -1;
+ }
+
+ eloop_event_add(ifp->ctx->eloop,
+ state->bpf->bpf_fd, dhcp_readbpf, ifp);
+ return 0;
+}
+
+void
+dhcp_free(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct dhcpcd_ctx *ctx;
+
+ dhcp_close(ifp);
+#ifdef ARP
+ arp_drop(ifp);
+#endif
+ if (state) {
+ state->state = DHS_NONE;
+ free(state->old);
+ free(state->new);
+ free(state->offer);
+ free(state->clientid);
+ free(state);
+ }
+
+ 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) {
+ state = D_STATE(ifp);
+ if (state != NULL && state->state != DHS_NONE)
+ break;
+ }
+ }
+ if (ifp == NULL) {
+ if (ctx->udp_rfd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->udp_rfd);
+ close(ctx->udp_rfd);
+ ctx->udp_rfd = -1;
+ }
+ if (ctx->udp_wfd != -1) {
+ close(ctx->udp_wfd);
+ ctx->udp_wfd = -1;
+ }
+
+ free(ctx->opt_buffer);
+ ctx->opt_buffer = NULL;
+ }
+}
+
+static int
+dhcp_initstate(struct interface *ifp)
+{
+ struct dhcp_state *state;
+
+ state = D_STATE(ifp);
+ if (state != NULL)
+ return 0;
+
+ ifp->if_data[IF_DATA_DHCP] = calloc(1, sizeof(*state));
+ state = D_STATE(ifp);
+ if (state == NULL)
+ return -1;
+
+ state->state = DHS_NONE;
+ /* 0 is a valid fd, so init to -1 */
+ state->udp_rfd = -1;
+#ifdef ARPING
+ state->arping_index = -1;
+#endif
+ return 1;
+}
+
+static int
+dhcp_init(struct interface *ifp)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+ uint8_t len;
+ char buf[(sizeof(ifo->clientid) - 1) * 3];
+
+ if (dhcp_initstate(ifp) == -1)
+ return -1;
+
+ state = D_STATE(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)
+ dhcp_unlink(ifp->ctx, state->leasefile);
+
+ free(state->clientid);
+ state->clientid = NULL;
+
+ if (ifo->options & DHCPCD_ANONYMOUS) {
+ /* Removing the option could show that we want anonymous.
+ * As such keep it as it's already in the hwaddr field. */
+ goto make_clientid;
+ } else 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 {
+make_clientid:
+ 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->hwtype;
+ 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 && state->clientid != NULL)
+ logdebugx("%s: using ClientID %s", ifp->name,
+ hwaddr_ntoa(state->clientid + 1, state->clientid[0],
+ buf, sizeof(buf)));
+ else if (ifp->hwlen)
+ logdebugx("%s: using hwaddr %s", ifp->name,
+ hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf)));
+ return 0;
+
+eexit:
+ logerr(__func__);
+ return -1;
+}
+
+static void
+dhcp_start1(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct if_options *ifo = ifp->options;
+ struct dhcp_state *state;
+ 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.
+ * Only do this in manager mode so we don't swallow messages
+ * for dhcpcd running on another interface. */
+ if ((ctx->options & (DHCPCD_MANAGER|DHCPCD_PRIVSEP)) == DHCPCD_MANAGER
+ && ctx->udp_rfd == -1)
+ {
+ ctx->udp_rfd = dhcp_openudp(NULL);
+ if (ctx->udp_rfd == -1) {
+ logerr(__func__);
+ return;
+ }
+ eloop_event_add(ctx->eloop, ctx->udp_rfd, dhcp_handleudp, ctx);
+ }
+ if (!IN_PRIVSEP(ctx) && ctx->udp_wfd == -1) {
+ ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW|SOCK_CXNB,IPPROTO_UDP);
+ if (ctx->udp_wfd == -1) {
+ logerr(__func__);
+ return;
+ }
+ }
+
+ if (dhcp_init(ifp) == -1) {
+ logerr("%s: dhcp_init", ifp->name);
+ return;
+ }
+
+ state = D_STATE(ifp);
+ clock_gettime(CLOCK_MONOTONIC, &state->started);
+ state->interval = 0;
+ free(state->offer);
+ state->offer = NULL;
+ state->offer_len = 0;
+
+#ifdef ARPING
+ if (ifo->arping_len && state->arping_index < ifo->arping_len) {
+ dhcp_arping(ifp);
+ return;
+ }
+#endif
+
+ if (ifo->options & DHCPCD_STATIC) {
+ dhcp_static(ifp);
+ return;
+ }
+
+ if (ifo->options & DHCPCD_INFORM) {
+ dhcp_inform(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_len = read_lease(ifp, &state->offer);
+ /* 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_DHCP(state->offer) &&
+ !(ifo->options & DHCPCD_BOOTP)) ||
+#ifdef IN_IFF_DUPLICATED
+ (ia && ia->addr_flags & IN_IFF_DUPLICATED) ||
+#endif
+ (IS_DHCP(state->offer) &&
+ ifo->options & DHCPCD_BOOTP))
+ {
+ free(state->offer);
+ state->offer = NULL;
+ state->offer_len = 0;
+ }
+ }
+ }
+ if (state->offer) {
+ struct ipv4_addr *ia;
+ time_t mtime;
+
+ get_lease(ifp, &state->lease, state->offer, state->offer_len);
+ state->lease.frominfo = 1;
+ if (state->new == NULL &&
+ (ia = ipv4_iffindaddr(ifp,
+ &state->lease.addr, &state->lease.mask)) != NULL)
+ {
+ /* 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(state->offer_len);
+ if (state->new) {
+ memcpy(state->new,
+ state->offer, state->offer_len);
+ state->new_len = state->offer_len;
+ state->addr = ia;
+ state->added |= STATE_ADDED | STATE_FAKE;
+ rt_build(ifp->ctx, AF_INET);
+ } else
+ logerr(__func__);
+ }
+ if (!IS_DHCP(state->offer)) {
+ free(state->offer);
+ state->offer = NULL;
+ state->offer_len = 0;
+ } else if (!(ifo->options & DHCPCD_LASTLEASE_EXTEND) &&
+ state->lease.leasetime != DHCP_INFINITE_LIFETIME &&
+ dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == 0)
+ {
+ time_t now;
+
+ /* Offset lease times and check expiry */
+ now = time(NULL);
+ if (now == -1 ||
+ (time_t)state->lease.leasetime < now - mtime)
+ {
+ logdebugx("%s: discarding expired lease",
+ ifp->name);
+ free(state->offer);
+ state->offer = NULL;
+ state->offer_len = 0;
+ 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 - mtime);
+ state->lease.leasetime -= l;
+ state->lease.renewaltime -= l;
+ state->lease.rebindtime -= l;
+ }
+ }
+ }
+
+#ifdef IPV4LL
+ if (!(ifo->options & DHCPCD_DHCP)) {
+ if (ifo->options & DHCPCD_IPV4LL)
+ ipv4ll_start(ifp);
+ return;
+ }
+#endif
+
+ if (state->offer == NULL ||
+ !IS_DHCP(state->offer) ||
+ ifo->options & DHCPCD_ANONYMOUS)
+ dhcp_discover(ifp);
+ else
+ dhcp_reboot(ifp);
+}
+
+void
+dhcp_start(struct interface *ifp)
+{
+ unsigned int delay;
+#ifdef ARPING
+ const struct dhcp_state *state;
+#endif
+
+ if (!(ifp->options->options & DHCPCD_IPV4))
+ return;
+
+ /* If we haven't been given a netmask for our requested address,
+ * set it now. */
+ if (ifp->options->req_addr.s_addr != INADDR_ANY &&
+ ifp->options->req_mask.s_addr == INADDR_ANY)
+ ifp->options->req_mask.s_addr =
+ ipv4_getnetmask(ifp->options->req_addr.s_addr);
+
+ /* If we haven't specified a ClientID and our hardware address
+ * length is greater than BOOTP CHADDR then we enforce a ClientID
+ * of the hardware address type and the hardware address.
+ * If there is no hardware address and no ClientID set,
+ * force a DUID based ClientID. */
+ if (ifp->hwlen > 16)
+ ifp->options->options |= DHCPCD_CLIENTID;
+ else if (ifp->hwlen == 0 && !(ifp->options->options & DHCPCD_CLIENTID))
+ ifp->options->options |= DHCPCD_CLIENTID | DHCPCD_DUID;
+
+ /* Firewire and InfiniBand interfaces require ClientID and
+ * the broadcast option being set. */
+ switch (ifp->hwtype) {
+ case ARPHRD_IEEE1394: /* FALLTHROUGH */
+ case ARPHRD_INFINIBAND:
+ ifp->options->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST;
+ break;
+ }
+
+ /* If we violate RFC2131 section 3.7 then require ARP
+ * to detect if any other client wants our address. */
+ if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND)
+ ifp->options->options |= DHCPCD_ARP;
+
+ /* No point in delaying a static configuration */
+ if (ifp->options->options & DHCPCD_STATIC ||
+ !(ifp->options->options & DHCPCD_INITIAL_DELAY))
+ {
+ dhcp_start1(ifp);
+ return;
+ }
+
+#ifdef ARPING
+ /* If we have arpinged then we have already delayed. */
+ state = D_CSTATE(ifp);
+ if (state != NULL && state->arping_index != -1) {
+ dhcp_start1(ifp);
+ return;
+ }
+#endif
+ delay = MSEC_PER_SEC +
+ (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC);
+ logdebugx("%s: delaying IPv4 for %0.1f seconds",
+ ifp->name, (float)delay / MSEC_PER_SEC);
+
+ eloop_timeout_add_msec(ifp->ctx->eloop, delay, dhcp_start1, ifp);
+}
+
+void
+dhcp_abort(struct interface *ifp)
+{
+ struct dhcp_state *state;
+
+ state = D_STATE(ifp);
+#ifdef ARPING
+ if (state != NULL)
+ state->arping_index = -1;
+#endif
+
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp_start1, ifp);
+
+ if (state != NULL && state->added) {
+ rt_build(ifp->ctx, AF_INET);
+#ifdef ARP
+ if (ifp->options->options & DHCPCD_ARP)
+ arp_announceaddr(ifp->ctx, &state->addr->addr);
+#endif
+ }
+}
+
+struct ipv4_addr *
+dhcp_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid)
+{
+ struct interface *ifp;
+ struct dhcp_state *state;
+ struct if_options *ifo;
+ uint8_t i;
+
+ ifp = ia->iface;
+ state = D_STATE(ifp);
+ if (state == NULL || state->state == DHS_NONE)
+ return ia;
+
+ if (cmd == RTM_DELADDR) {
+ if (state->addr == ia) {
+ loginfox("%s: pid %d deleted IP address %s",
+ ifp->name, pid, ia->saddr);
+ dhcp_close(ifp);
+ state->addr = NULL;
+ /* Don't clear the added state as we need
+ * to drop the lease. */
+ dhcp_drop(ifp, "EXPIRE");
+ dhcp_start1(ifp);
+ return ia;
+ }
+ }
+
+ if (cmd != RTM_NEWADDR)
+ return ia;
+
+#ifdef IN_IFF_NOTUSEABLE
+ if (!(ia->addr_flags & IN_IFF_NOTUSEABLE))
+ dhcp_finish_dad(ifp, &ia->addr);
+ else if (ia->addr_flags & IN_IFF_DUPLICATED)
+ return dhcp_addr_duplicated(ifp, &ia->addr) ? NULL : ia;
+#endif
+
+ ifo = ifp->options;
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ifp->ctx) &&
+ !(ifp->ctx->options & (DHCPCD_MANAGER | DHCPCD_CONFIGURE)) &&
+ IN_ARE_ADDR_EQUAL(&state->lease.addr, &ia->addr))
+ {
+ state->addr = ia;
+ state->added = STATE_ADDED;
+ dhcp_closebpf(ifp);
+ if (ps_inet_openbootp(ia) == -1)
+ logerr(__func__);
+ }
+#endif
+
+ /* If we have requested a specific address, return now.
+ * The below code is only for when inform or static has been
+ * requested without a specific address. */
+ if (ifo->req_addr.s_addr != INADDR_ANY)
+ return ia;
+
+ /* Only inform if we are NOT in the inform state or bound. */
+ if (ifo->options & DHCPCD_INFORM) {
+ if (state->state != DHS_INFORM && state->state != DHS_BOUND)
+ dhcp_inform(ifp);
+ return ia;
+ }
+
+ /* Static and inform are mutually exclusive. If not static, return. */
+ if (!(ifo->options & DHCPCD_STATIC))
+ return ia;
+
+ free(state->old);
+ state->old = state->new;
+ state->new_len = dhcp_message_new(&state->new, &ia->addr, &ia->mask);
+ if (state->new == NULL)
+ return ia;
+
+ if (ifp->flags & IFF_POINTOPOINT) {
+ for (i = 1; i < 255; i++)
+ if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i))
+ dhcp_message_add_addr(state->new, i, ia->brd);
+ }
+
+ state->reason = "STATIC";
+ rt_build(ifp->ctx, AF_INET);
+ script_runreason(ifp, state->reason);
+
+ return ia;
+}
+
+#ifndef SMALL
+int
+dhcp_dump(struct interface *ifp)
+{
+ struct dhcp_state *state;
+
+ ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state));
+ if (state == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ state->new_len = read_lease(ifp, &state->new);
+ if (state->new == NULL) {
+ logerr("read_lease");
+ return -1;
+ }
+ state->reason = "DUMP";
+ return script_runreason(ifp, state->reason);
+}
+#endif
diff --git a/src/dhcp.h b/src/dhcp.h
new file mode 100644
index 000000000000..9359d564da4d
--- /dev/null
+++ b/src/dhcp.h
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <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 <limits.h>
+#include <stdint.h>
+
+#include "arp.h"
+#include "bpf.h"
+#include "auth.h"
+#include "dhcp-common.h"
+
+/* UDP port numbers for BOOTP */
+#define BOOTPS 67
+#define BOOTPC 68
+
+#define MAGIC_COOKIE 0x63825363
+#define BROADCAST_FLAG 0x8000
+
+/* BOOTP message OP code */
+#define BOOTREQUEST 1
+#define 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_OPTSOVERLOADED = 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_IPV6_PREFERRED_ONLY = 108, /* RFC 8925 */
+ 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_MUDURL = 161, /* draft-ietf-opsawg-mud */
+ 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
+};
+
+#define MIN_V6ONLY_WAIT 300 /* seconds, RFC 8925 */
+
+/* Sizes for BOOTP options */
+#define BOOTP_CHADDR_LEN 16
+#define BOOTP_SNAME_LEN 64
+#define BOOTP_FILE_LEN 128
+#define BOOTP_VEND_LEN 64
+
+/* DHCP is basically an extension to BOOTP */
+struct bootp {
+ uint8_t op; /* message type */
+ uint8_t htype; /* hardware address type */
+ uint8_t hlen; /* hardware address length */
+ uint8_t hops; /* should be zero in client message */
+ uint32_t xid; /* transaction id */
+ uint16_t secs; /* elapsed time in sec. from boot */
+ uint16_t flags; /* such as broadcast flag */
+ 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[BOOTP_CHADDR_LEN]; /* client's hardware address */
+ uint8_t sname[BOOTP_SNAME_LEN]; /* server host name */
+ uint8_t file[BOOTP_FILE_LEN]; /* boot file name */
+ uint8_t vend[BOOTP_VEND_LEN]; /* vendor specific area */
+ /* DHCP allows a variable length vendor area */
+};
+
+#define DHCP_MIN_LEN (offsetof(struct bootp, vend) + 4)
+
+struct bootp_pkt
+{
+ struct ip ip;
+ struct udphdr udp;
+ struct bootp bootp;
+};
+
+struct dhcp_lease {
+ struct in_addr addr;
+ struct in_addr mask;
+ struct in_addr brd;
+ uint32_t leasetime;
+ uint32_t renewaltime;
+ uint32_t rebindtime;
+ struct in_addr server;
+ uint8_t frominfo;
+ uint32_t cookie;
+};
+
+#ifndef DHCP_INFINITE_LIFETIME
+# define DHCP_INFINITE_LIFETIME (~0U)
+#endif
+
+enum DHS {
+ DHS_NONE,
+ DHS_INIT,
+ DHS_DISCOVER,
+ DHS_REQUEST,
+ DHS_PROBE,
+ DHS_BOUND,
+ DHS_RENEW,
+ DHS_REBIND,
+ DHS_REBOOT,
+ DHS_INFORM,
+ DHS_RENEW_REQUESTED,
+ DHS_RELEASE
+};
+
+struct dhcp_state {
+ enum DHS state;
+ struct bootp *sent;
+ size_t sent_len;
+ struct bootp *offer;
+ size_t offer_len;
+ struct bootp *new;
+ size_t new_len;
+ struct bootp *old;
+ size_t old_len;
+ struct dhcp_lease lease;
+ const char *reason;
+ unsigned int interval;
+ unsigned int nakoff;
+ uint32_t xid;
+ int socket;
+
+ struct bpf *bpf;
+ int udp_rfd;
+ struct ipv4_addr *addr;
+ uint8_t added;
+
+ char leasefile[sizeof(LEASEFILE) + IF_NAMESIZE + (IF_SSIDLEN * 4)];
+ struct timespec started;
+ unsigned char *clientid;
+ struct authstate auth;
+#ifdef ARPING
+ ssize_t arping_index;
+#endif
+};
+
+#ifdef INET
+#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)
+
+#define IS_DHCP(b) ((b)->vend[0] == 0x63 && \
+ (b)->vend[1] == 0x82 && \
+ (b)->vend[2] == 0x53 && \
+ (b)->vend[3] == 0x63)
+
+#include "dhcpcd.h"
+#include "if-options.h"
+
+ssize_t print_rfc3361(FILE *, const uint8_t *, size_t);
+ssize_t print_rfc3442(FILE *, const uint8_t *, size_t);
+
+int dhcp_openudp(struct in_addr *);
+void dhcp_packet(struct interface *, uint8_t *, size_t, unsigned int);
+void dhcp_recvmsg(struct dhcpcd_ctx *, struct msghdr *);
+void dhcp_printoptions(const struct dhcpcd_ctx *,
+ const struct dhcp_opt *, size_t);
+uint16_t dhcp_get_mtu(const struct interface *);
+int dhcp_get_routes(rb_tree_t *, struct interface *);
+ssize_t dhcp_env(FILE *, const char *, const struct interface *,
+ const struct bootp *, size_t);
+
+struct ipv4_addr *dhcp_handleifa(int, struct ipv4_addr *, pid_t pid);
+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_renew(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 *);
+#endif /* INET */
+
+#endif /* DHCP_H */
diff --git a/src/dhcp6.c b/src/dhcp6.c
new file mode 100644
index 000000000000..21198c42c059
--- /dev/null
+++ b/src/dhcp6.c
@@ -0,0 +1,4364 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <sys/types.h>
+
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+
+#define ELOOP_QUEUE ELOOP_DHCP6
+#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 "logerr.h"
+#include "privsep.h"
+#include "script.h"
+
+#ifdef HAVE_SYS_BITOPS_H
+#include <sys/bitops.h>
+#else
+#include "compat/bitops.h"
+#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
+
+#ifdef DHCP6
+
+/* Assert the correct structure size for on wire */
+struct dhcp6_message {
+ uint8_t type;
+ uint8_t xid[3];
+ /* followed by options */
+};
+__CTASSERT(sizeof(struct dhcp6_message) == 4);
+
+struct dhcp6_option {
+ uint16_t code;
+ uint16_t len;
+ /* followed by data */
+};
+__CTASSERT(sizeof(struct dhcp6_option) == 4);
+
+struct dhcp6_ia_na {
+ uint8_t iaid[4];
+ uint32_t t1;
+ uint32_t t2;
+};
+__CTASSERT(sizeof(struct dhcp6_ia_na) == 12);
+
+struct dhcp6_ia_ta {
+ uint8_t iaid[4];
+};
+__CTASSERT(sizeof(struct dhcp6_ia_ta) == 4);
+
+struct dhcp6_ia_addr {
+ struct in6_addr addr;
+ uint32_t pltime;
+ uint32_t vltime;
+};
+__CTASSERT(sizeof(struct dhcp6_ia_addr) == 16 + 8);
+
+/* XXX FIXME: This is the only packed structure and it does not align.
+ * Maybe manually decode it? */
+struct dhcp6_pd_addr {
+ uint32_t pltime;
+ uint32_t vltime;
+ uint8_t prefix_len;
+ struct in6_addr prefix;
+} __packed;
+__CTASSERT(sizeof(struct dhcp6_pd_addr) == 8 + 1 + 16);
+
+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, "RECONFIGURE6" },
+ { DHCP6_DECLINE, "DECLINE6" },
+ { 0, NULL }
+};
+
+struct dhcp_compat {
+ uint8_t dhcp_opt;
+ uint16_t dhcp6_opt;
+};
+
+static 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",
+ "No Prefix Available"
+};
+
+static void dhcp6_bind(struct interface *, const char *, const char *);
+static void dhcp6_failinform(void *);
+static void dhcp6_recvaddr(void *);
+static void dhcp6_startdecline(struct interface *);
+
+#ifdef SMALL
+#define dhcp6_hasprefixdelegation(a) (0)
+#else
+static int dhcp6_hasprefixdelegation(struct interface *);
+#endif
+
+#define DECLINE_IA(ia) \
+ ((ia)->addr_flags & IN6_IFF_DUPLICATED && \
+ (ia)->ia_type != 0 && (ia)->ia_type != D6_OPTION_IA_PD && \
+ !((ia)->flags & IPV6_AF_STALE) && \
+ (ia)->prefix_vltime != 0)
+
+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_makeuser(void *data, const struct interface *ifp)
+{
+ const struct if_options *ifo = ifp->options;
+ struct dhcp6_option o;
+ uint8_t *p;
+ const uint8_t *up, *ue;
+ uint16_t ulen, unlen;
+ size_t olen;
+
+ /* Convert the DHCPv4 user class option to DHCPv6 */
+ up = ifo->userclass;
+ ulen = *up++;
+ if (ulen == 0)
+ return 0;
+
+ p = data;
+ olen = 0;
+ if (p != NULL)
+ p += sizeof(o);
+
+ ue = up + ulen;
+ for (; up < ue; up += ulen) {
+ ulen = *up++;
+ olen += sizeof(ulen) + ulen;
+ if (data == NULL)
+ continue;
+ unlen = htons(ulen);
+ memcpy(p, &unlen, sizeof(unlen));
+ p += sizeof(unlen);
+ memcpy(p, up, ulen);
+ p += ulen;
+ }
+ if (data != NULL) {
+ o.code = htons(D6_OPTION_USER_CLASS);
+ o.len = htons((uint16_t)olen);
+ memcpy(data, &o, sizeof(o));
+ }
+
+ return sizeof(o) + olen;
+}
+
+static size_t
+dhcp6_makevendor(void *data, const struct interface *ifp)
+{
+ const struct if_options *ifo;
+ size_t len, vlen, i;
+ uint8_t *p;
+ const struct vivco *vivco;
+ struct dhcp6_option o;
+
+ ifo = ifp->options;
+ len = sizeof(uint32_t); /* IANA PEN */
+ if (ifo->vivco_en) {
+ vlen = 0;
+ for (i = 0, vivco = ifo->vivco;
+ i < ifo->vivco_len;
+ i++, vivco++)
+ vlen += sizeof(uint16_t) + vivco->len;
+ len += vlen;
+ } else if (ifo->vendorclassid[0] != '\0') {
+ /* dhcpcd owns DHCPCD_IANA_PEN.
+ * If you need your own string, get your own IANA PEN. */
+ vlen = strlen(ifp->ctx->vendor);
+ len += sizeof(uint16_t) + vlen;
+ } else
+ return 0;
+
+ if (len > UINT16_MAX) {
+ logerrx("%s: DHCPv6 Vendor Class too big", ifp->name);
+ return 0;
+ }
+
+ if (data != NULL) {
+ uint32_t pen;
+ uint16_t hvlen;
+
+ p = data;
+ o.code = htons(D6_OPTION_VENDOR_CLASS);
+ o.len = htons((uint16_t)len);
+ memcpy(p, &o, sizeof(o));
+ p += sizeof(o);
+ pen = htonl(ifo->vivco_en ? ifo->vivco_en : DHCPCD_IANA_PEN);
+ memcpy(p, &pen, sizeof(pen));
+ p += sizeof(pen);
+
+ if (ifo->vivco_en) {
+ for (i = 0, vivco = ifo->vivco;
+ i < ifo->vivco_len;
+ i++, vivco++)
+ {
+ hvlen = htons((uint16_t)vivco->len);
+ memcpy(p, &hvlen, sizeof(hvlen));
+ p += sizeof(hvlen);
+ memcpy(p, vivco->data, vivco->len);
+ p += vivco->len;
+ }
+ } else if (ifo->vendorclassid[0] != '\0') {
+ hvlen = htons((uint16_t)vlen);
+ memcpy(p, &hvlen, sizeof(hvlen));
+ p += sizeof(hvlen);
+ memcpy(p, ifp->ctx->vendor, vlen);
+ }
+ }
+
+ return sizeof(o) + len;
+}
+
+static void *
+dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len)
+{
+ uint8_t *d;
+ struct dhcp6_option o;
+
+ code = htons(code);
+ for (d = data; data_len != 0; d += o.len, data_len -= o.len) {
+ if (data_len < sizeof(o)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ memcpy(&o, d, sizeof(o));
+ d += sizeof(o);
+ data_len -= sizeof(o);
+ o.len = htons(o.len);
+ if (data_len < o.len) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (o.code == code) {
+ if (len != NULL)
+ *len = o.len;
+ return d;
+ }
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+static void *
+dhcp6_findmoption(void *data, size_t data_len, uint16_t code,
+ uint16_t *len)
+{
+ uint8_t *d;
+
+ if (data_len < sizeof(struct dhcp6_message)) {
+ errno = EINVAL;
+ return false;
+ }
+ d = data;
+ d += sizeof(struct dhcp6_message);
+ data_len -= sizeof(struct dhcp6_message);
+ return dhcp6_findoption(d, data_len, code, len);
+}
+
+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)
+{
+ struct dhcp6_option o;
+ size_t i;
+ struct dhcp_opt *opt;
+
+ if (od != NULL) {
+ *os = sizeof(o);
+ if (ol < *os) {
+ errno = EINVAL;
+ return NULL;
+ }
+ memcpy(&o, od, sizeof(o));
+ *len = ntohs(o.len);
+ if (*len > ol - *os) {
+ errno = ERANGE;
+ return NULL;
+ }
+ *code = ntohs(o.code);
+ }
+
+ *oopt = NULL;
+ for (i = 0, opt = ctx->dhcp6_opts;
+ i < ctx->dhcp6_opts_len; i++, opt++)
+ {
+ if (opt->option == *code) {
+ *oopt = opt;
+ break;
+ }
+ }
+
+ if (od != NULL)
+ return od + sizeof(o);
+ return NULL;
+}
+
+static bool
+dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, size_t len)
+{
+ uint8_t *opt;
+ uint16_t opt_len;
+ struct dhcp6_state *state;
+ struct timespec tv;
+ unsigned long long hsec;
+ uint16_t sec;
+
+ opt = dhcp6_findmoption(m, len, D6_OPTION_ELAPSED, &opt_len);
+ if (opt == NULL)
+ return false;
+ if (opt_len != sizeof(sec)) {
+ errno = EINVAL;
+ return false;
+ }
+
+ 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 {
+ unsigned long long secs;
+ unsigned int nsecs;
+
+ secs = eloop_timespec_diff(&tv, &state->started, &nsecs);
+ /* Elapsed time is measured in centiseconds.
+ * We need to be sure it will not potentially overflow. */
+ if (secs >= (UINT16_MAX / CSEC_PER_SEC) + 1)
+ hsec = UINT16_MAX;
+ else {
+ hsec = (secs * CSEC_PER_SEC) +
+ (nsecs / NSEC_PER_CSEC);
+ if (hsec > UINT16_MAX)
+ hsec = UINT16_MAX;
+ }
+ }
+ sec = htons((uint16_t)hsec);
+ memcpy(opt, &sec, sizeof(sec));
+ return true;
+}
+
+static void
+dhcp6_newxid(const struct interface *ifp, struct dhcp6_message *m)
+{
+ const struct interface *ifp1;
+ const struct dhcp6_state *state1;
+ 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 {
+again:
+ xid = arc4random();
+ }
+
+ m->xid[0] = (xid >> 16) & 0xff;
+ m->xid[1] = (xid >> 8) & 0xff;
+ m->xid[2] = xid & 0xff;
+
+ /* Ensure it's unique */
+ TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) {
+ if (ifp == ifp1)
+ continue;
+ if ((state1 = D6_CSTATE(ifp1)) == NULL)
+ continue;
+ if (state1->send != NULL &&
+ state1->send->xid[0] == m->xid[0] &&
+ state1->send->xid[1] == m->xid[1] &&
+ state1->send->xid[2] == m->xid[2])
+ break;
+ }
+
+ if (ifp1 != NULL) {
+ if (ifp->options->options & DHCPCD_XID_HWADDR &&
+ ifp->hwlen >= sizeof(xid))
+ {
+ logerrx("%s: duplicate xid on %s",
+ ifp->name, ifp1->name);
+ return;
+ }
+ goto again;
+ }
+}
+
+#ifndef SMALL
+static const struct if_sla *
+dhcp6_findselfsla(struct interface *ifp)
+{
+ size_t i, j;
+ struct if_ia *ia;
+
+ for (i = 0; i < ifp->options->ia_len; i++) {
+ ia = &ifp->options->ia[i];
+ if (ia->ia_type != D6_OPTION_IA_PD)
+ continue;
+ for (j = 0; j < ia->sla_len; j++) {
+ if (strcmp(ia->sla[j].ifname, ifp->name) == 0)
+ return &ia->sla[j];
+ }
+ }
+ return NULL;
+}
+
+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) {
+ logerr(__func__);
+ return -1;
+ }
+
+ TAILQ_INIT(&state->addrs);
+ state->state = DH6S_DELEGATED;
+ state->reason = "DELEGATED6";
+ }
+
+ if (sla == NULL || !sla->sla_set) {
+ /* No SLA set, so make an assumption of
+ * desired SLA and prefix length. */
+ asla.sla = ifp->index;
+ asla.prefix_len = 0;
+ asla.sla_set = false;
+ sla = &asla;
+ } else if (sla->prefix_len == 0) {
+ /* An SLA was given, but prefix length was not.
+ * We need to work out a suitable prefix length for
+ * potentially more than one interface. */
+ asla.sla = sla->sla;
+ asla.prefix_len = 0;
+ asla.sla_set = sla->sla_set;
+ sla = &asla;
+ }
+
+ if (sla->prefix_len == 0) {
+ uint32_t sla_max;
+ int bits;
+
+ sla_max = ia->sla_max;
+ if (sla_max == 0 && (sla == NULL || !sla->sla_set)) {
+ const struct interface *ifi;
+
+ TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) {
+ if (ifi->index > sla_max)
+ sla_max = ifi->index;
+ }
+ }
+
+ bits = fls32(sla_max);
+
+ if (prefix->prefix_len + bits > (int)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 makes SLAAC
+ * possible.
+ * Otherwise round up to the nearest 4 bits. */
+ if (asla.prefix_len <= 64)
+ asla.prefix_len = 64;
+ else
+ asla.prefix_len =
+ (uint8_t)ROUNDUP4(asla.prefix_len);
+ }
+
+#define BIT(n) (1UL << (n))
+#define BIT_MASK(len) (BIT(len) - 1)
+ if (ia->sla_max == 0) {
+ /* Work out the real sla_max from our bits used */
+ bits = asla.prefix_len - prefix->prefix_len;
+ /* Make static analysis happy.
+ * Bits cannot be bigger than 32 thanks to fls32. */
+ assert(bits <= 32);
+ ia->sla_max = (uint32_t)BIT_MASK(bits);
+ }
+ }
+
+ 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));
+ logerr("%s: invalid prefix %s/%d + %d/%d",
+ 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));
+ logerrx("%s: cannot delegate excluded prefix %s/%d",
+ ifp->name, sa, prefix->prefix_exclude_len);
+ return -1;
+ }
+
+ return sla->prefix_len;
+}
+#endif
+
+static int
+dhcp6_makemessage(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ struct dhcp6_message *m;
+ struct dhcp6_option o;
+ uint8_t *p, *si, *unicast, IA;
+ size_t n, l, len, ml, hl;
+ uint8_t type;
+ uint16_t si_len, uni_len, n_options;
+ uint8_t *o_lenp;
+ struct if_options *ifo = ifp->options;
+ const struct dhcp_opt *opt, *opt2;
+ const struct ipv6_addr *ap;
+ char hbuf[HOSTNAME_MAX_LEN + 1];
+ const char *hostname;
+ int fqdn;
+ struct dhcp6_ia_na ia_na;
+ uint16_t ia_na_len;
+ struct if_ia *ifia;
+#ifdef AUTH
+ uint16_t auth_len;
+#endif
+ uint8_t duid[DUID_LEN];
+ size_t duid_len = 0;
+
+ state = D6_STATE(ifp);
+ if (state->send) {
+ free(state->send);
+ state->send = NULL;
+ }
+
+ switch(state->state) {
+ case DH6S_INIT: /* FALLTHROUGH */
+ case DH6S_DISCOVER:
+ type = DHCP6_SOLICIT;
+ break;
+ case DH6S_REQUEST:
+ type = DHCP6_REQUEST;
+ break;
+ case DH6S_CONFIRM:
+ type = DHCP6_CONFIRM;
+ break;
+ case DH6S_REBIND:
+ type = DHCP6_REBIND;
+ break;
+ case DH6S_RENEW:
+ type = DHCP6_RENEW;
+ break;
+ case DH6S_INFORM:
+ type = DHCP6_INFORMATION_REQ;
+ break;
+ case DH6S_RELEASE:
+ type = DHCP6_RELEASE;
+ break;
+ case DH6S_DECLINE:
+ type = DHCP6_DECLINE;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* RFC 4704 Section 5 says we can only send FQDN for these
+ * message types. */
+ switch(type) {
+ case DHCP6_SOLICIT:
+ case DHCP6_REQUEST:
+ case DHCP6_RENEW:
+ case DHCP6_REBIND:
+ fqdn = ifo->fqdn;
+ break;
+ default:
+ fqdn = FQDN_DISABLE;
+ break;
+ }
+
+ 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)
+ hostname = dhcp_get_hostname(hbuf, sizeof(hbuf), ifo);
+ else
+ hostname = NULL; /* appearse gcc */
+
+ /* Work out option size first */
+ n_options = 0;
+ len = 0;
+ si = NULL;
+ hl = 0; /* Appease gcc */
+ if (state->state != DH6S_RELEASE && state->state != DH6S_DECLINE) {
+ 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 (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
+ continue;
+ n_options++;
+ len += sizeof(o.len);
+ }
+#ifndef SMALL
+ for (l = 0, opt = ifo->dhcp6_override;
+ l < ifo->dhcp6_override_len;
+ l++, opt++)
+ {
+ if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
+ continue;
+ n_options++;
+ len += sizeof(o.len);
+ }
+ if (dhcp6_findselfsla(ifp)) {
+ n_options++;
+ len += sizeof(o.len);
+ }
+#endif
+ if (len)
+ len += sizeof(o);
+
+ if (fqdn != FQDN_DISABLE) {
+ hl = encode_rfc1035(hostname, NULL);
+ len += sizeof(o) + 1 + hl;
+ }
+
+ if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) &&
+ ifo->mudurl[0])
+ len += sizeof(o) + ifo->mudurl[0];
+
+#ifdef AUTH
+ if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
+ DHCPCD_AUTH_SENDREQUIRE &&
+ DHC_REQ(ifo->requestmask6, ifo->nomask6,
+ D6_OPTION_RECONF_ACCEPT))
+ len += sizeof(o); /* Reconfigure Accept */
+#endif
+ }
+
+ len += sizeof(*state->send);
+ len += sizeof(o) + sizeof(uint16_t); /* elapsed */
+
+ if (ifo->options & DHCPCD_ANONYMOUS) {
+ duid_len = duid_make(duid, ifp, DUID_LL);
+ len += sizeof(o) + duid_len;
+ } else {
+ len += sizeof(o) + ifp->ctx->duid_len;
+ }
+
+ if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS))
+ len += dhcp6_makeuser(NULL, ifp);
+ if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
+ len += 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_DECLINE:
+ /* FALLTHROUGH */
+ case DH6S_RELEASE:
+ /* FALLTHROUGH */
+ case DH6S_RENEW:
+ if (m == NULL) {
+ m = state->new;
+ ml = state->new_len;
+ }
+ si = dhcp6_findmoption(m, ml, D6_OPTION_SERVERID, &si_len);
+ if (si == NULL)
+ return -1;
+ len += sizeof(o) + 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->flags & IPV6_AF_STALE)
+ continue;
+ if (!(ap->flags & IPV6_AF_REQUEST) &&
+ (ap->prefix_vltime == 0 ||
+ state->state == DH6S_DISCOVER))
+ continue;
+ if (DECLINE_IA(ap) && state->state != DH6S_DECLINE)
+ continue;
+ if (ap->ia_type == D6_OPTION_IA_PD) {
+#ifndef SMALL
+ len += sizeof(o) + sizeof(struct dhcp6_pd_addr);
+ if (ap->prefix_exclude_len)
+ len += sizeof(o) + 1 +
+ (uint8_t)((ap->prefix_exclude_len -
+ ap->prefix_len - 1) / NBBY) + 1;
+#endif
+ } else
+ len += sizeof(o) + sizeof(struct dhcp6_ia_addr);
+ }
+ /* FALLTHROUGH */
+ case DH6S_INIT:
+ for (l = 0; l < ifo->ia_len; l++) {
+ len += sizeof(o) + sizeof(uint32_t); /* IAID */
+ /* IA_TA does not have T1 or T2 timers */
+ if (ifo->ia[l].ia_type != D6_OPTION_IA_TA)
+ len += sizeof(uint32_t) + sizeof(uint32_t);
+ }
+ IA = 1;
+ break;
+ default:
+ IA = 0;
+ }
+
+ if (state->state == DH6S_DISCOVER &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+ DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT))
+ len += sizeof(o);
+
+ if (m == NULL) {
+ m = state->new;
+ ml = state->new_len;
+ }
+
+ switch(state->state) {
+ case DH6S_REQUEST: /* FALLTHROUGH */
+ case DH6S_RENEW: /* FALLTHROUGH */
+ case DH6S_RELEASE:
+ if (has_option_mask(ifo->nomask6, D6_OPTION_UNICAST)) {
+ unicast = NULL;
+ break;
+ }
+ unicast = dhcp6_findmoption(m, ml, D6_OPTION_UNICAST, &uni_len);
+ break;
+ default:
+ unicast = NULL;
+ break;
+ }
+
+ /* In non manager mode we listen and send from fixed addresses.
+ * We should try and match an address we have to unicast to,
+ * but for now this is the safest policy. */
+ if (unicast != NULL && !(ifp->ctx->options & DHCPCD_MANAGER)) {
+ logdebugx("%s: ignoring unicast option as not manager",
+ ifp->name);
+ unicast = NULL;
+ }
+
+#ifdef AUTH
+ auth_len = 0;
+ if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ ssize_t alen = dhcp_auth_encode(ifp->ctx, &ifo->auth,
+ state->auth.token, NULL, 0, 6, type, NULL, 0);
+ if (alen != -1 && alen > UINT16_MAX) {
+ errno = ERANGE;
+ alen = -1;
+ }
+ if (alen == -1)
+ logerr("%s: %s: dhcp_auth_encode", __func__, ifp->name);
+ else if (alen != 0) {
+ auth_len = (uint16_t)alen;
+ len += sizeof(o) + auth_len;
+ }
+ }
+#endif
+
+ 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 && uni_len == sizeof(state->unicast))
+ memcpy(&state->unicast, unicast, sizeof(state->unicast));
+ else
+ state->unicast = in6addr_any;
+
+ dhcp6_newxid(ifp, state->send);
+
+#define COPYIN1(_code, _len) { \
+ o.code = htons((_code)); \
+ o.len = htons((_len)); \
+ memcpy(p, &o, sizeof(o)); \
+ p += sizeof(o); \
+}
+#define COPYIN(_code, _data, _len) do { \
+ COPYIN1((_code), (_len)); \
+ if ((_len) != 0) { \
+ memcpy(p, (_data), (_len)); \
+ p += (_len); \
+ } \
+} while (0 /* CONSTCOND */)
+#define NEXTLEN (p + offsetof(struct dhcp6_option, len))
+
+ /* Options are listed in numerical order as per RFC 7844 Section 4.1
+ * XXX: They should be randomised. */
+
+ p = (uint8_t *)state->send + sizeof(*state->send);
+ if (ifo->options & DHCPCD_ANONYMOUS)
+ COPYIN(D6_OPTION_CLIENTID, duid,
+ (uint16_t)duid_len);
+ else
+ COPYIN(D6_OPTION_CLIENTID, ifp->ctx->duid,
+ (uint16_t)ifp->ctx->duid_len);
+
+ if (si != NULL)
+ COPYIN(D6_OPTION_SERVERID, si, si_len);
+
+ for (l = 0; IA && l < ifo->ia_len; l++) {
+ ifia = &ifo->ia[l];
+ o_lenp = NEXTLEN;
+ /* TA structure is the same as the others,
+ * it just lacks the T1 and T2 timers.
+ * These happen to be at the end of the struct,
+ * so we just don't copy them in. */
+ if (ifia->ia_type == D6_OPTION_IA_TA)
+ ia_na_len = sizeof(struct dhcp6_ia_ta);
+ else
+ ia_na_len = sizeof(ia_na);
+ memcpy(ia_na.iaid, ifia->iaid, sizeof(ia_na.iaid));
+ ia_na.t1 = 0;
+ ia_na.t2 = 0;
+ COPYIN(ifia->ia_type, &ia_na, ia_na_len);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->flags & IPV6_AF_STALE)
+ continue;
+ if (!(ap->flags & IPV6_AF_REQUEST) &&
+ (ap->prefix_vltime == 0 ||
+ state->state == DH6S_DISCOVER))
+ continue;
+ if (DECLINE_IA(ap) && state->state != DH6S_DECLINE)
+ continue;
+ if (ap->ia_type != ifia->ia_type)
+ continue;
+ if (memcmp(ap->iaid, ifia->iaid, sizeof(ap->iaid)))
+ continue;
+ if (ap->ia_type == D6_OPTION_IA_PD) {
+#ifndef SMALL
+ struct dhcp6_pd_addr pdp;
+
+ pdp.pltime = htonl(ap->prefix_pltime);
+ pdp.vltime = htonl(ap->prefix_vltime);
+ pdp.prefix_len = ap->prefix_len;
+ /* pdp.prefix is not aligned, so copy it in. */
+ memcpy(&pdp.prefix, &ap->prefix, sizeof(pdp.prefix));
+ COPYIN(D6_OPTION_IAPREFIX, &pdp, sizeof(pdp));
+ ia_na_len = (uint16_t)
+ (ia_na_len + sizeof(o) + sizeof(pdp));
+
+ /* RFC6603 Section 4.2 */
+ if (ap->prefix_exclude_len) {
+ uint8_t exb[16], *ep, u8;
+ const uint8_t *pp;
+
+ n = (size_t)((ap->prefix_exclude_len -
+ ap->prefix_len - 1) / NBBY) + 1;
+ ep = exb;
+ *ep++ = (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)
+ *ep++ = *pp--;
+ if (u8)
+ *ep = (uint8_t)(*pp << u8);
+ n++;
+ COPYIN(D6_OPTION_PD_EXCLUDE, exb,
+ (uint16_t)n);
+ ia_na_len = (uint16_t)
+ (ia_na_len + sizeof(o) + n);
+ }
+#endif
+ } else {
+ struct dhcp6_ia_addr ia;
+
+ ia.addr = ap->addr;
+ ia.pltime = htonl(ap->prefix_pltime);
+ ia.vltime = htonl(ap->prefix_vltime);
+ COPYIN(D6_OPTION_IA_ADDR, &ia, sizeof(ia));
+ ia_na_len = (uint16_t)
+ (ia_na_len + sizeof(o) + sizeof(ia));
+ }
+ }
+
+ /* Update the total option lenth. */
+ ia_na_len = htons(ia_na_len);
+ memcpy(o_lenp, &ia_na_len, sizeof(ia_na_len));
+ }
+
+ if (state->send->type != DHCP6_RELEASE &&
+ state->send->type != DHCP6_DECLINE &&
+ n_options)
+ {
+ o_lenp = NEXTLEN;
+ o.len = 0;
+ COPYIN1(D6_OPTION_ORO, 0);
+ for (l = 0, opt = ifp->ctx->dhcp6_opts;
+ l < ifp->ctx->dhcp6_opts_len;
+ l++, opt++)
+ {
+#ifndef SMALL
+ 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;
+#endif
+ if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
+ continue;
+ o.code = htons((uint16_t)opt->option);
+ memcpy(p, &o.code, sizeof(o.code));
+ p += sizeof(o.code);
+ o.len = (uint16_t)(o.len + sizeof(o.code));
+ }
+#ifndef SMALL
+ for (l = 0, opt = ifo->dhcp6_override;
+ l < ifo->dhcp6_override_len;
+ l++, opt++)
+ {
+ if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
+ continue;
+ o.code = htons((uint16_t)opt->option);
+ memcpy(p, &o.code, sizeof(o.code));
+ p += sizeof(o.code);
+ o.len = (uint16_t)(o.len + sizeof(o.code));
+ }
+ if (dhcp6_findselfsla(ifp)) {
+ o.code = htons(D6_OPTION_PD_EXCLUDE);
+ memcpy(p, &o.code, sizeof(o.code));
+ p += sizeof(o.code);
+ o.len = (uint16_t)(o.len + sizeof(o.code));
+ }
+#endif
+ o.len = htons(o.len);
+ memcpy(o_lenp, &o.len, sizeof(o.len));
+ }
+
+ si_len = 0;
+ COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len));
+
+ if (state->state == DH6S_DISCOVER &&
+ !(ifp->ctx->options & DHCPCD_TEST) &&
+ DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT))
+ COPYIN1(D6_OPTION_RAPID_COMMIT, 0);
+
+ if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS))
+ p += dhcp6_makeuser(p, ifp);
+ if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
+ p += dhcp6_makevendor(p, ifp);
+
+ if (state->send->type != DHCP6_RELEASE &&
+ state->send->type != DHCP6_DECLINE)
+ {
+ if (fqdn != FQDN_DISABLE) {
+ o_lenp = NEXTLEN;
+ COPYIN1(D6_OPTION_FQDN, 0);
+ if (hl == 0)
+ *p = D6_FQDN_NONE;
+ else {
+ switch (fqdn) {
+ case FQDN_BOTH:
+ *p = D6_FQDN_BOTH;
+ break;
+ case FQDN_PTR:
+ *p = D6_FQDN_PTR;
+ break;
+ default:
+ *p = D6_FQDN_NONE;
+ break;
+ }
+ }
+ p++;
+ encode_rfc1035(hostname, p);
+ p += hl;
+ o.len = htons((uint16_t)(hl + 1));
+ memcpy(o_lenp, &o.len, sizeof(o.len));
+ }
+
+ if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) &&
+ ifo->mudurl[0])
+ COPYIN(D6_OPTION_MUDURL,
+ ifo->mudurl + 1, ifo->mudurl[0]);
+
+#ifdef AUTH
+ if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
+ DHCPCD_AUTH_SENDREQUIRE &&
+ DHC_REQ(ifo->requestmask6, ifo->nomask6,
+ D6_OPTION_RECONF_ACCEPT))
+ COPYIN1(D6_OPTION_RECONF_ACCEPT, 0);
+#endif
+
+ }
+
+#ifdef AUTH
+ /* This has to be the last option */
+ if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) {
+ COPYIN1(D6_OPTION_AUTH, auth_len);
+ /* data will be filled at send message time */
+ }
+#endif
+
+ 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)
+ rt_build(ifp->ctx, AF_INET6);
+ }
+}
+
+#ifndef SMALL
+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);
+ }
+ }
+}
+#endif
+
+#ifdef AUTH
+static ssize_t
+dhcp6_update_auth(struct interface *ifp, struct dhcp6_message *m, size_t len)
+{
+ struct dhcp6_state *state;
+ uint8_t *opt;
+ uint16_t opt_len;
+
+ opt = dhcp6_findmoption(m, len, D6_OPTION_AUTH, &opt_len);
+ if (opt == NULL)
+ return -1;
+
+ state = D6_STATE(ifp);
+ return dhcp_auth_encode(ifp->ctx, &ifp->options->auth,
+ state->auth.token, (uint8_t *)state->send, state->send_len, 6,
+ state->send->type, opt, opt_len);
+}
+#endif
+
+static const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT;
+static int
+dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *))
+{
+ struct dhcp6_state *state = D6_STATE(ifp);
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ unsigned int RT;
+ bool broadcast = true;
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ /* Setting the port on Linux gives EINVAL when sending.
+ * This looks like a kernel bug as the equivalent works
+ * fine with the DHCP counterpart. */
+#ifndef __linux__
+ .sin6_port = htons(DHCP6_SERVER_PORT),
+#endif
+ };
+ struct udphdr udp = {
+ .uh_sport = htons(DHCP6_CLIENT_PORT),
+ .uh_dport = htons(DHCP6_SERVER_PORT),
+ .uh_ulen = htons((uint16_t)(sizeof(udp) + state->send_len)),
+ };
+ struct iovec iov[] = {
+ { .iov_base = &udp, .iov_len = sizeof(udp), },
+ { .iov_base = state->send, .iov_len = state->send_len, },
+ };
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct msghdr msg = {
+ .msg_name = &dst, .msg_namelen = sizeof(dst),
+ .msg_iov = iov, .msg_iovlen = __arraycount(iov),
+ };
+ char uaddr[INET6_ADDRSTRLEN];
+
+ if (!callback && !if_is_link_up(ifp))
+ return 0;
+
+ if (!IN6_IS_ADDR_UNSPECIFIED(&state->unicast)) {
+ switch (state->send->type) {
+ case DHCP6_SOLICIT: /* FALLTHROUGH */
+ case DHCP6_CONFIRM: /* FALLTHROUGH */
+ case DHCP6_REBIND:
+ /* Unicasting is denied for these types. */
+ break;
+ default:
+ broadcast = false;
+ inet_ntop(AF_INET6, &state->unicast, uaddr,
+ sizeof(uaddr));
+ break;
+ }
+ }
+ dst.sin6_addr = broadcast ? alldhcp : state->unicast;
+
+ if (!callback) {
+ logdebugx("%s: %s %s with xid 0x%02x%02x%02x%s%s",
+ ifp->name,
+ broadcast ? "broadcasting" : "unicasting",
+ dhcp6_get_op(state->send->type),
+ state->send->xid[0],
+ state->send->xid[1],
+ state->send->xid[2],
+ !broadcast ? " " : "",
+ !broadcast ? uaddr : "");
+ RT = 0;
+ } else {
+ if (state->IMD &&
+ !(ifp->options->options & DHCPCD_INITIAL_DELAY))
+ state->IMD = 0;
+ if (state->IMD) {
+ state->RT = state->IMD * MSEC_PER_SEC;
+ /* 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 += MSEC_PER_SEC;
+ } else if (state->RTC == 0)
+ state->RT = state->IRT * MSEC_PER_SEC;
+
+ if (state->MRT != 0) {
+ unsigned int mrt = state->MRT * MSEC_PER_SEC;
+
+ if (state->RT > mrt)
+ state->RT = mrt;
+ }
+
+ /* Add -.1 to .1 * RT randomness as per RFC8415 section 15 */
+ uint32_t lru = arc4random_uniform(
+ state->RTC == 0 ? DHCP6_RAND_MAX
+ : DHCP6_RAND_MAX - DHCP6_RAND_MIN);
+ int lr = (int)lru - (state->RTC == 0 ? 0 : DHCP6_RAND_MAX);
+ RT = state->RT
+ + (unsigned int)((float)state->RT
+ * ((float)lr / DHCP6_RAND_DIV));
+
+ if (if_is_link_up(ifp))
+ logdebugx("%s: %s %s (xid 0x%02x%02x%02x)%s%s,"
+ " next in %0.1f seconds",
+ ifp->name,
+ state->IMD != 0 ? "delaying" :
+ broadcast ? "broadcasting" : "unicasting",
+ dhcp6_get_op(state->send->type),
+ state->send->xid[0],
+ state->send->xid[1],
+ state->send->xid[2],
+ state->IMD == 0 && !broadcast ? " " : "",
+ state->IMD == 0 && !broadcast ? uaddr : "",
+ (float)RT / MSEC_PER_SEC);
+
+ /* Wait the initial delay */
+ if (state->IMD != 0) {
+ state->IMD = 0;
+ eloop_timeout_add_msec(ctx->eloop, RT, callback, ifp);
+ return 0;
+ }
+ }
+
+ if (!if_is_link_up(ifp))
+ return 0;
+
+ /* Update the elapsed time */
+ dhcp6_updateelapsed(ifp, state->send, state->send_len);
+#ifdef AUTH
+ if (ifp->options->auth.options & DHCPCD_AUTH_SEND &&
+ dhcp6_update_auth(ifp, state->send, state->send_len) == -1)
+ {
+ logerr("%s: %s: dhcp6_updateauth", __func__, ifp->name);
+ if (errno != ESRCH)
+ return -1;
+ }
+#endif
+
+ /* Set the outbound interface */
+ if (broadcast) {
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
+
+ dst.sin6_scope_id = ifp->index;
+ msg.msg_control = cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+ cm = CMSG_FIRSTHDR(&msg);
+ if (cm == NULL) /* unlikely */
+ return -1;
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+ }
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ifp->ctx)) {
+ if (ps_inet_senddhcp6(ifp, &msg) == -1)
+ logerr(__func__);
+ goto sent;
+ }
+#endif
+
+ if (sendmsg(ctx->dhcp6_wfd, &msg, 0) == -1) {
+ logerr("%s: %s: sendmsg", __func__, ifp->name);
+ /* Allow DHCPv6 to continue .... the errors
+ * would be rate limited by the protocol.
+ * Generally the error is ENOBUFS when struggling to
+ * associate with an access point. */
+ }
+
+#ifdef PRIVSEP
+sent:
+#endif
+ state->RTC++;
+ if (callback) {
+ state->RT = RT * 2;
+ if (state->RT < RT) /* Check overflow */
+ state->RT = RT;
+ if (state->MRC == 0 || state->RTC < state->MRC)
+ eloop_timeout_add_msec(ctx->eloop,
+ RT, callback, ifp);
+ else if (state->MRC != 0 && state->MRCcallback)
+ eloop_timeout_add_msec(ctx->eloop,
+ RT, state->MRCcallback, ifp);
+ else
+ logwarnx("%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_senddecline(void *arg)
+{
+
+ dhcp6_sendmessage(arg, dhcp6_senddecline);
+}
+
+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;
+ if ((state = D6_STATE(ifp)) == NULL)
+ return;
+
+ /* Only renew in the bound or renew states */
+ if (state->state != DH6S_BOUND &&
+ state->state != DH6S_RENEW)
+ return;
+
+ /* Remove the timeout as the renew may have been forced. */
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startrenew, ifp);
+
+ state->state = DH6S_RENEW;
+ state->RTC = 0;
+ state->IMD = REN_MAX_DELAY;
+ state->IRT = REN_TIMEOUT;
+ state->MRT = REN_MAX_RT;
+ state->MRC = 0;
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logerr("%s: %s", __func__, ifp->name);
+ else
+ dhcp6_sendrenew(ifp);
+}
+
+void dhcp6_renew(struct interface *ifp)
+{
+
+ dhcp6_startrenew(ifp);
+}
+
+bool
+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 false;
+ }
+ return true;
+}
+
+static void
+dhcp6_dadcallback(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+ struct interface *ifp;
+ struct dhcp6_state *state;
+ struct ipv6_addr *ia2;
+ bool completed, valid, oneduplicated;
+
+ completed = (ia->flags & IPV6_AF_DADCOMPLETED);
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+ if (ia->addr_flags & IN6_IFF_DUPLICATED)
+ logwarnx("%s: DAD detected %s", ia->iface->name, ia->saddr);
+
+#ifdef ND6_ADVERTISE
+ else
+ ipv6nd_advertise(ia);
+#endif
+ if (completed)
+ return;
+
+ ifp = ia->iface;
+ state = D6_STATE(ifp);
+ if (state->state != DH6S_BOUND && state->state != DH6S_DELEGATED)
+ return;
+
+#ifdef SMALL
+ valid = true;
+#else
+ valid = (ia->delegating_prefix == NULL);
+#endif
+ completed = true;
+ oneduplicated = false;
+ TAILQ_FOREACH(ia2, &state->addrs, next) {
+ if (ia2->flags & IPV6_AF_ADDED &&
+ !(ia2->flags & IPV6_AF_DADCOMPLETED))
+ {
+ completed = false;
+ break;
+ }
+ if (DECLINE_IA(ia))
+ oneduplicated = true;
+ }
+ if (!completed)
+ return;
+
+ logdebugx("%s: DHCPv6 DAD completed", ifp->name);
+
+ if (oneduplicated && state->state == DH6S_BOUND) {
+ dhcp6_startdecline(ifp);
+ return;
+ }
+
+ script_runreason(ifp,
+#ifndef SMALL
+ ia->delegating_prefix ? "DELEGATED6" :
+#endif
+ 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;
+
+ 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 = ipv6_newaddr(ifp, &ia->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.
+ */
+ ia->ia_type == D6_OPTION_IA_PD ? ia->prefix_len : 128,
+ IPV6_AF_REQUEST);
+ if (a == NULL)
+ continue;
+ a->dadcallback = dhcp6_dadcallback;
+ memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid));
+ a->ia_type = ia->ia_type;
+ TAILQ_INSERT_TAIL(&state->addrs, a, next);
+ }
+}
+
+static void
+dhcp6_startdiscover(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+ int llevel;
+
+ ifp = arg;
+ state = D6_STATE(ifp);
+#ifndef SMALL
+ if (state->reason == NULL || strcmp(state->reason, "TIMEOUT6") != 0)
+ dhcp6_delete_delegates(ifp);
+#endif
+ if (state->new == NULL && !state->failed)
+ llevel = LOG_INFO;
+ else
+ llevel = LOG_DEBUG;
+ logmessage(llevel, "%s: soliciting a DHCPv6 lease", ifp->name);
+ state->state = DH6S_DISCOVER;
+ state->RTC = 0;
+ state->IMD = SOL_MAX_DELAY;
+ state->IRT = SOL_TIMEOUT;
+ state->MRT = state->sol_max_rt;
+ state->MRC = SOL_MAX_RC;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ free(state->new);
+ state->new = NULL;
+ state->new_len = 0;
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logerr("%s: %s", __func__, ifp->name);
+ else
+ dhcp6_senddiscover(ifp);
+}
+
+static void
+dhcp6_startinform(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+ int llevel;
+
+ ifp = arg;
+ state = D6_STATE(ifp);
+ if (state->new_start || (state->new == NULL && !state->failed))
+ llevel = LOG_INFO;
+ else
+ llevel = LOG_DEBUG;
+ logmessage(llevel, "%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;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ if (dhcp6_makemessage(ifp) == -1) {
+ logerr("%s: %s", __func__, ifp->name);
+ return;
+ }
+ dhcp6_sendinform(ifp);
+ /* RFC3315 18.1.2 says that if CONFIRM failed then the prior addresses
+ * SHOULD be used. The wording here is poor, because the addresses are
+ * merely one facet of the lease as a whole.
+ * This poor wording might explain the lack of similar text for INFORM
+ * in 18.1.5 because there are no addresses in the INFORM message. */
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ INF_MAX_RD, dhcp6_failinform, ifp);
+}
+
+static bool
+dhcp6_startdiscoinform(struct interface *ifp)
+{
+ unsigned long long opts = ifp->options->options;
+
+ if (opts & DHCPCD_IA_FORCED || ipv6nd_hasradhcp(ifp, true))
+ dhcp6_startdiscover(ifp);
+ else if (opts & DHCPCD_INFORM6 || ipv6nd_hasradhcp(ifp, false))
+ dhcp6_startinform(ifp);
+ else
+ return false;
+ return true;
+}
+
+static void
+dhcp6_leaseextend(struct interface *ifp)
+{
+ struct dhcp6_state *state = D6_STATE(ifp);
+ struct ipv6_addr *ia;
+
+ logwarnx("%s: extending DHCPv6 lease", ifp->name);
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ ia->flags |= IPV6_AF_EXTENDED;
+ /* Set infinite lifetimes. */
+ ia->prefix_pltime = ND6_INFINITE_LIFETIME;
+ ia->prefix_vltime = ND6_INFINITE_LIFETIME;
+ }
+}
+
+static void
+dhcp6_fail(struct interface *ifp)
+{
+ struct dhcp6_state *state = D6_STATE(ifp);
+
+ state->failed = true;
+
+ /* RFC3315 18.1.2 says that prior addresses SHOULD be used on failure.
+ * RFC2131 3.2.3 says that MAY chose to use the prior address.
+ * Because dhcpcd was written first for RFC2131, we have the LASTLEASE
+ * option which defaults to off as that makes the most sense for
+ * mobile clients.
+ * dhcpcd also has LASTLEASE_EXTEND to extend this lease past it's
+ * expiry, but this is strictly not RFC compliant in any way or form. */
+ if (state->new != NULL &&
+ ifp->options->options & DHCPCD_LASTLEASE_EXTEND)
+ {
+ dhcp6_leaseextend(ifp);
+ dhcp6_bind(ifp, NULL, NULL);
+ } else {
+ dhcp6_freedrop_addrs(ifp, 1, NULL);
+#ifndef SMALL
+ dhcp6_delete_delegates(ifp);
+#endif
+ free(state->old);
+ state->old = state->new;
+ state->old_len = state->new_len;
+ state->new = NULL;
+ state->new_len = 0;
+ if (state->old != NULL)
+ script_runreason(ifp, "EXPIRE6");
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ dhcp6_addrequestedaddrs(ifp);
+ }
+
+ if (!dhcp6_startdiscoinform(ifp)) {
+ logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name);
+ state->state = DH6S_INIT;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ }
+}
+
+static int
+dhcp6_failloglevel(struct interface *ifp)
+{
+ const struct dhcp6_state *state = D6_CSTATE(ifp);
+
+ return state->failed ? LOG_DEBUG : LOG_ERR;
+}
+
+static void
+dhcp6_failconfirm(void *arg)
+{
+ struct interface *ifp = arg;
+ int llevel = dhcp6_failloglevel(ifp);
+
+ logmessage(llevel, "%s: failed to confirm prior DHCPv6 address",
+ ifp->name);
+ dhcp6_fail(ifp);
+}
+
+static void
+dhcp6_failrequest(void *arg)
+{
+ struct interface *ifp = arg;
+ int llevel = dhcp6_failloglevel(ifp);
+
+ logmessage(llevel, "%s: failed to request DHCPv6 address", ifp->name);
+ dhcp6_fail(ifp);
+}
+
+static void
+dhcp6_failinform(void *arg)
+{
+ struct interface *ifp = arg;
+ int llevel = dhcp6_failloglevel(ifp);
+
+ logmessage(llevel, "%s: failed to request DHCPv6 information",
+ ifp->name);
+ dhcp6_fail(ifp);
+}
+
+#ifndef SMALL
+static void
+dhcp6_failrebind(void *arg)
+{
+ struct interface *ifp = arg;
+
+ logerrx("%s: failed to rebind prior DHCPv6 delegation", ifp->name);
+ dhcp6_fail(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;
+}
+#endif
+
+static void
+dhcp6_startrebind(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+#ifndef SMALL
+ int pd;
+#endif
+
+ ifp = arg;
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp);
+ state = D6_STATE(ifp);
+ if (state->state == DH6S_RENEW)
+ logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name);
+ else
+ loginfox("%s: rebinding prior DHCPv6 lease", ifp->name);
+ state->state = DH6S_REBIND;
+ state->RTC = 0;
+ state->MRC = 0;
+
+#ifndef SMALL
+ /* 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
+#endif
+ {
+ state->IMD = REB_MAX_DELAY;
+ state->IRT = REB_TIMEOUT;
+ state->MRT = REB_MAX_RT;
+ }
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logerr("%s: %s", __func__, ifp->name);
+ else
+ dhcp6_sendrebind(ifp);
+
+#ifndef SMALL
+ /* RFC 3633 section 12.1 */
+ if (pd)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ CNF_MAX_RD, dhcp6_failrebind, ifp);
+#endif
+}
+
+
+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->IMD = 0;
+ state->IRT = REQ_TIMEOUT;
+ state->MRT = REQ_MAX_RT;
+ state->MRC = REQ_MAX_RC;
+ state->MRCcallback = dhcp6_failrequest;
+
+ if (dhcp6_makemessage(ifp) == -1) {
+ logerr("%s: %s", __func__, ifp->name);
+ return;
+ }
+
+ dhcp6_sendrequest(ifp);
+}
+
+static void
+dhcp6_startconfirm(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ struct ipv6_addr *ia;
+
+ state = D6_STATE(ifp);
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (!DECLINE_IA(ia))
+ continue;
+ logerrx("%s: prior DHCPv6 has a duplicated address", ifp->name);
+ dhcp6_startdecline(ifp);
+ return;
+ }
+
+ state->state = DH6S_CONFIRM;
+ state->RTC = 0;
+ state->IMD = CNF_MAX_DELAY;
+ state->IRT = CNF_TIMEOUT;
+ state->MRT = CNF_MAX_RT;
+ state->MRC = CNF_MAX_RC;
+
+ loginfox("%s: confirming prior DHCPv6 lease", ifp->name);
+
+ if (dhcp6_makemessage(ifp) == -1) {
+ logerr("%s: %s", __func__, ifp->name);
+ return;
+ }
+ dhcp6_sendconfirm(ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ CNF_MAX_RD, dhcp6_failconfirm, ifp);
+}
+
+static void
+dhcp6_startexpire(void *arg)
+{
+ struct interface *ifp;
+
+ ifp = arg;
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp);
+
+ logerrx("%s: DHCPv6 lease expired", ifp->name);
+ dhcp6_fail(ifp);
+}
+
+static void
+dhcp6_faildecline(void *arg)
+{
+ struct interface *ifp = arg;
+
+ logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name);
+ dhcp6_fail(ifp);
+}
+
+static void
+dhcp6_startdecline(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ loginfox("%s: declining failed DHCPv6 addresses", ifp->name);
+ state->state = DH6S_DECLINE;
+ state->RTC = 0;
+ state->IMD = 0;
+ state->IRT = DEC_TIMEOUT;
+ state->MRT = 0;
+ state->MRC = DEC_MAX_RC;
+ state->MRCcallback = dhcp6_faildecline;
+
+ if (dhcp6_makemessage(ifp) == -1)
+ logerr("%s: %s", __func__, ifp->name);
+ else
+ dhcp6_senddecline(ifp);
+}
+
+static void
+dhcp6_finishrelease(void *arg)
+{
+ struct interface *ifp;
+ struct dhcp6_state *state;
+
+ ifp = (struct interface *)arg;
+ if ((state = D6_STATE(ifp)) != NULL) {
+ 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->IMD = REL_MAX_DELAY;
+ state->IRT = REL_TIMEOUT;
+ state->MRT = REL_MAX_RT;
+ /* 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)
+ logerr("%s: %s", __func__, ifp->name);
+ else {
+ dhcp6_sendrelease(ifp);
+ dhcp6_finishrelease(ifp);
+ }
+}
+
+static int
+dhcp6_checkstatusok(const struct interface *ifp,
+ struct dhcp6_message *m, uint8_t *p, size_t len)
+{
+ struct dhcp6_state *state;
+ uint8_t *opt;
+ uint16_t opt_len, code;
+ size_t mlen;
+ void * (*f)(void *, size_t, uint16_t, uint16_t *), *farg;
+ char buf[32], *sbuf;
+ const char *status;
+ int loglevel;
+
+ state = D6_STATE(ifp);
+ f = p ? dhcp6_findoption : dhcp6_findmoption;
+ if (p)
+ farg = p;
+ else
+ farg = m;
+ if ((opt = f(farg, len, D6_OPTION_STATUS_CODE, &opt_len)) == NULL) {
+ //logdebugx("%s: no status", ifp->name);
+ state->lerror = 0;
+ errno = ESRCH;
+ return 0;
+ }
+
+ if (opt_len < sizeof(code)) {
+ logerrx("%s: status truncated", ifp->name);
+ return -1;
+ }
+ memcpy(&code, opt, sizeof(code));
+ code = ntohs(code);
+ if (code == D6_STATUS_OK) {
+ state->lerror = 0;
+ errno = 0;
+ return 0;
+ }
+
+ /* Anything after the code is a message. */
+ opt += sizeof(code);
+ mlen = opt_len - sizeof(code);
+ if (mlen == 0) {
+ sbuf = NULL;
+ if (code < sizeof(dhcp6_statuses) / sizeof(char *))
+ status = dhcp6_statuses[code];
+ else {
+ snprintf(buf, sizeof(buf), "Unknown Status (%d)", code);
+ status = buf;
+ }
+ } else {
+ if ((sbuf = malloc(mlen + 1)) == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ memcpy(sbuf, opt, mlen);
+ sbuf[mlen] = '\0';
+ status = sbuf;
+ }
+
+ if (state->lerror == code || state->state == DH6S_INIT)
+ loglevel = LOG_DEBUG;
+ else
+ loglevel = LOG_ERR;
+ logmessage(loglevel, "%s: DHCPv6 REPLY: %s", ifp->name, status);
+ free(sbuf);
+ state->lerror = code;
+ errno = 0;
+
+ /* code cannot be D6_STATUS_OK, so there is a failure */
+ if (ifp->ctx->options & DHCPCD_TEST)
+ eloop_exit(ifp->ctx->eloop, EXIT_FAILURE);
+
+ return (int)code;
+}
+
+const struct ipv6_addr *
+dhcp6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr,
+ unsigned int 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,
+ unsigned int 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,
+ uint8_t *d, size_t l, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ uint8_t *o, *nd;
+ uint16_t ol;
+ struct ipv6_addr *a;
+ int i;
+ struct dhcp6_ia_addr ia;
+
+ i = 0;
+ state = D6_STATE(ifp);
+ while ((o = dhcp6_findoption(d, l, D6_OPTION_IA_ADDR, &ol))) {
+ /* Set d and l first to ensure we find the next option. */
+ nd = o + ol;
+ l -= (size_t)(nd - d);
+ d = nd;
+ if (ol < sizeof(ia)) {
+ errno = EINVAL;
+ logerrx("%s: IA Address option truncated", ifp->name);
+ continue;
+ }
+ memcpy(&ia, o, sizeof(ia));
+ ia.pltime = ntohl(ia.pltime);
+ ia.vltime = ntohl(ia.vltime);
+ /* RFC 3315 22.6 */
+ if (ia.pltime > ia.vltime) {
+ errno = EINVAL;
+ logerr("%s: IA Address pltime %"PRIu32
+ " > vltime %"PRIu32,
+ ifp->name, ia.pltime, ia.vltime);
+ continue;
+ }
+ TAILQ_FOREACH(a, &state->addrs, next) {
+ if (ipv6_findaddrmatch(a, &ia.addr, 0))
+ break;
+ }
+ if (a == NULL) {
+ /*
+ * 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 = ipv6_newaddr(ifp, &ia.addr, 128, IPV6_AF_ONLINK);
+ a->dadcallback = dhcp6_dadcallback;
+ a->ia_type = ot;
+ memcpy(a->iaid, iaid, sizeof(a->iaid));
+ a->created = *acquired;
+
+ 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 | IPV6_AF_EXTENDED);
+ }
+ a->acquired = *acquired;
+ a->prefix_pltime = ia.pltime;
+ if (a->prefix_vltime != ia.vltime) {
+ a->flags |= IPV6_AF_NEW;
+ a->prefix_vltime = ia.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++;
+ }
+ return i;
+}
+
+#ifndef SMALL
+static int
+dhcp6_findpd(struct interface *ifp, const uint8_t *iaid,
+ uint8_t *d, size_t l, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ uint8_t *o, *nd;
+ struct ipv6_addr *a;
+ int i;
+ uint8_t nb, *pw;
+ uint16_t ol;
+ struct dhcp6_pd_addr pdp;
+ struct in6_addr pdp_prefix;
+
+ i = 0;
+ state = D6_STATE(ifp);
+ while ((o = dhcp6_findoption(d, l, D6_OPTION_IAPREFIX, &ol))) {
+ /* Set d and l first to ensure we find the next option. */
+ nd = o + ol;
+ l -= (size_t)(nd - d);
+ d = nd;
+ if (ol < sizeof(pdp)) {
+ errno = EINVAL;
+ logerrx("%s: IA Prefix option truncated", ifp->name);
+ continue;
+ }
+
+ memcpy(&pdp, o, sizeof(pdp));
+ pdp.pltime = ntohl(pdp.pltime);
+ pdp.vltime = ntohl(pdp.vltime);
+ /* RFC 3315 22.6 */
+ if (pdp.pltime > pdp.vltime) {
+ errno = EINVAL;
+ logerrx("%s: IA Prefix pltime %"PRIu32
+ " > vltime %"PRIu32,
+ ifp->name, pdp.pltime, pdp.vltime);
+ continue;
+ }
+
+ o += sizeof(pdp);
+ ol = (uint16_t)(ol - sizeof(pdp));
+
+ /* pdp.prefix is not aligned so copy it out. */
+ memcpy(&pdp_prefix, &pdp.prefix, sizeof(pdp_prefix));
+ TAILQ_FOREACH(a, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&a->prefix, &pdp_prefix))
+ break;
+ }
+
+ if (a == NULL) {
+ a = ipv6_newaddr(ifp, &pdp_prefix, pdp.prefix_len,
+ IPV6_AF_DELEGATEDPFX);
+ if (a == NULL)
+ break;
+ a->created = *acquired;
+ a->dadcallback = dhcp6_dadcallback;
+ a->ia_type = D6_OPTION_IA_PD;
+ memcpy(a->iaid, iaid, sizeof(a->iaid));
+ 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_EXTENDED |
+ IPV6_AF_REQUEST);
+ if (a->prefix_vltime != pdp.vltime)
+ a->flags |= IPV6_AF_NEW;
+ }
+
+ a->acquired = *acquired;
+ a->prefix_pltime = pdp.pltime;
+ a->prefix_vltime = 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++;
+
+ a->prefix_exclude_len = 0;
+ memset(&a->prefix_exclude, 0, sizeof(a->prefix_exclude));
+ o = dhcp6_findoption(o, ol, D6_OPTION_PD_EXCLUDE, &ol);
+ if (o == NULL)
+ continue;
+
+ /* RFC 6603 4.2 says option length MUST be between 2 and 17.
+ * This allows 1 octet for prefix length and 16 for the
+ * subnet ID. */
+ if (ol < 2 || ol > 17) {
+ logerrx("%s: invalid PD Exclude option", ifp->name);
+ continue;
+ }
+
+ /* RFC 6603 4.2 says prefix length MUST be between the
+ * length of the IAPREFIX prefix length + 1 and 128. */
+ if (*o < a->prefix_len + 1 || *o > 128) {
+ logerrx("%s: invalid PD Exclude length", ifp->name);
+ continue;
+ }
+
+ ol--;
+ /* Check option length matches prefix length. */
+ if (((*o - a->prefix_len - 1) / NBBY) + 1 != ol) {
+ logerrx("%s: PD Exclude length mismatch", ifp->name);
+ continue;
+ }
+ a->prefix_exclude_len = *o++;
+
+ memcpy(&a->prefix_exclude, &a->prefix,
+ sizeof(a->prefix_exclude));
+ nb = a->prefix_len % NBBY;
+ if (nb)
+ ol--;
+ pw = a->prefix_exclude.s6_addr +
+ (a->prefix_exclude_len / NBBY) - 1;
+ while (ol-- > 0)
+ *pw-- = *o++;
+ if (nb)
+ *pw = (uint8_t)(*pw | (*o >> nb));
+ }
+ return i;
+}
+#endif
+
+static int
+dhcp6_findia(struct interface *ifp, struct dhcp6_message *m, size_t l,
+ const char *sfrom, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ const struct if_options *ifo;
+ struct dhcp6_option o;
+ uint8_t *d, *p;
+ struct dhcp6_ia_na ia;
+ int i, e, error;
+ size_t j;
+ uint16_t nl;
+ uint8_t iaid[4];
+ char buf[sizeof(iaid) * 3];
+ struct ipv6_addr *ap;
+ struct if_ia *ifia;
+
+ 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) {
+ if (!(ap->flags & IPV6_AF_DELEGATED))
+ ap->flags |= IPV6_AF_STALE;
+ }
+
+ d = (uint8_t *)m + sizeof(*m);
+ l -= sizeof(*m);
+ while (l > sizeof(o)) {
+ memcpy(&o, d, sizeof(o));
+ o.len = ntohs(o.len);
+ if (o.len > l || sizeof(o) + o.len > l) {
+ errno = EINVAL;
+ logerrx("%s: option overflow", ifp->name);
+ break;
+ }
+ p = d + sizeof(o);
+ d = p + o.len;
+ l -= sizeof(o) + o.len;
+
+ o.code = ntohs(o.code);
+ switch(o.code) {
+ case D6_OPTION_IA_TA:
+ nl = 4;
+ break;
+ case D6_OPTION_IA_NA:
+ case D6_OPTION_IA_PD:
+ nl = 12;
+ break;
+ default:
+ continue;
+ }
+ if (o.len < nl) {
+ errno = EINVAL;
+ logerrx("%s: IA option truncated", ifp->name);
+ continue;
+ }
+
+ memcpy(&ia, p, nl);
+ p += nl;
+ o.len = (uint16_t)(o.len - nl);
+
+ for (j = 0; j < ifo->ia_len; j++) {
+ ifia = &ifo->ia[j];
+ if (ifia->ia_type == o.code &&
+ memcmp(ifia->iaid, ia.iaid, sizeof(ia.iaid)) == 0)
+ break;
+ }
+ if (j == ifo->ia_len &&
+ !(ifo->ia_len == 0 && ifp->ctx->options & DHCPCD_DUMPLEASE))
+ {
+ logdebugx("%s: ignoring unrequested IAID %s",
+ ifp->name,
+ hwaddr_ntoa(ia.iaid, sizeof(ia.iaid),
+ buf, sizeof(buf)));
+ continue;
+ }
+
+ if (o.code != D6_OPTION_IA_TA) {
+ ia.t1 = ntohl(ia.t1);
+ ia.t2 = ntohl(ia.t2);
+ /* RFC 3315 22.4 */
+ if (ia.t2 > 0 && ia.t1 > ia.t2) {
+ logwarnx("%s: IAID %s T1(%d) > T2(%d) from %s",
+ ifp->name,
+ hwaddr_ntoa(iaid, sizeof(iaid), buf,
+ sizeof(buf)),
+ ia.t1, ia.t2, sfrom);
+ continue;
+ }
+ } else
+ ia.t1 = ia.t2 = 0; /* appease gcc */
+ if ((error = dhcp6_checkstatusok(ifp, NULL, p, o.len)) != 0) {
+ if (error == D6_STATUS_NOBINDING)
+ state->has_no_binding = true;
+ e = 1;
+ continue;
+ }
+ if (o.code == D6_OPTION_IA_PD) {
+#ifndef SMALL
+ if (dhcp6_findpd(ifp, ia.iaid, p, o.len,
+ acquired) == 0)
+ {
+ logwarnx("%s: %s: DHCPv6 REPLY missing Prefix",
+ ifp->name, sfrom);
+ continue;
+ }
+#endif
+ } else {
+ if (dhcp6_findna(ifp, o.code, ia.iaid, p, o.len,
+ acquired) == 0)
+ {
+ logwarnx("%s: %s: DHCPv6 REPLY missing "
+ "IA Address",
+ ifp->name, sfrom);
+ continue;
+ }
+ }
+ if (o.code != D6_OPTION_IA_TA) {
+ if (ia.t1 != 0 &&
+ (ia.t1 < state->renew || state->renew == 0))
+ state->renew = ia.t1;
+ if (ia.t2 != 0 &&
+ (ia.t2 < state->rebind || state->rebind == 0))
+ state->rebind = ia.t2;
+ }
+ i++;
+ }
+
+ if (i == 0 && e)
+ return -1;
+ return i;
+}
+
+#ifndef SMALL
+static void
+dhcp6_deprecatedele(struct ipv6_addr *ia)
+{
+ struct ipv6_addr *da, *dan, *dda;
+ struct timespec now;
+ struct dhcp6_state *state;
+
+ timespecclear(&now);
+ TAILQ_FOREACH_SAFE(da, &ia->pd_pfxs, pd_next, dan) {
+ if (ia->prefix_vltime == 0) {
+ if (da->prefix_vltime != 0)
+ da->prefix_vltime = 0;
+ else
+ continue;
+ } else if (da->prefix_pltime != 0)
+ da->prefix_pltime = 0;
+ else
+ continue;
+
+ if (ipv6_doaddr(da, &now) != -1)
+ continue;
+
+ /* Delegation deleted, forget it. */
+ TAILQ_REMOVE(&ia->pd_pfxs, da, pd_next);
+
+ /* Delete it from the interface. */
+ state = D6_STATE(da->iface);
+ TAILQ_FOREACH(dda, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&dda->addr, &da->addr))
+ break;
+ }
+ if (dda != NULL) {
+ TAILQ_REMOVE(&state->addrs, dda, next);
+ ipv6_freeaddr(dda);
+ }
+ }
+}
+#endif
+
+static void
+dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs)
+{
+ struct ipv6_addr *ia, *ian;
+
+ TAILQ_FOREACH_SAFE(ia, addrs, next, ian) {
+ if (ia->flags & IPV6_AF_EXTENDED)
+ ;
+ else if (ia->flags & IPV6_AF_STALE) {
+ if (ia->prefix_vltime != 0)
+ logdebugx("%s: %s: became stale",
+ ia->iface->name, ia->saddr);
+ /* Technically this violates RFC 8415 18.2.10.1,
+ * but we need a mechanism to tell the kernel to
+ * try and prefer other addresses. */
+ ia->prefix_pltime = 0;
+ } else if (ia->prefix_vltime == 0)
+ loginfox("%s: %s: no valid lifetime",
+ ia->iface->name, ia->saddr);
+ else
+ continue;
+
+#ifndef SMALL
+ /* If we delegated from this prefix, deprecate or remove
+ * the delegations. */
+ if (ia->flags & IPV6_AF_DELEGATEDPFX)
+ dhcp6_deprecatedele(ia);
+#endif
+
+ if (ia->flags & IPV6_AF_REQUEST) {
+ ia->prefix_vltime = ia->prefix_pltime = 0;
+ eloop_q_timeout_delete(ia->iface->ctx->eloop,
+ ELOOP_QUEUE_ALL, NULL, ia);
+ continue;
+ }
+ TAILQ_REMOVE(addrs, ia, next);
+ if (ia->flags & IPV6_AF_EXTENDED)
+ ipv6_deleteaddr(ia);
+ ipv6_freeaddr(ia);
+ }
+}
+
+static int
+dhcp6_validatelease(struct interface *ifp,
+ struct dhcp6_message *m, size_t len,
+ const char *sfrom, const struct timespec *acquired)
+{
+ struct dhcp6_state *state;
+ int nia, ok_errno;
+ struct timespec aq;
+
+ if (len <= sizeof(*m)) {
+ logerrx("%s: DHCPv6 lease truncated", ifp->name);
+ return -1;
+ }
+
+ state = D6_STATE(ifp);
+ errno = 0;
+ if (dhcp6_checkstatusok(ifp, m, NULL, len) != 0)
+ return -1;
+ ok_errno = errno;
+
+ state->renew = state->rebind = state->expire = 0;
+ state->lowpl = ND6_INFINITE_LIFETIME;
+ if (!acquired) {
+ clock_gettime(CLOCK_MONOTONIC, &aq);
+ acquired = &aq;
+ }
+ state->has_no_binding = false;
+ nia = dhcp6_findia(ifp, m, len, sfrom, acquired);
+ if (nia == 0) {
+ if (state->state != DH6S_CONFIRM && ok_errno != 0) {
+ logerrx("%s: no useable IA found in lease", ifp->name);
+ return -1;
+ }
+
+ /* We are confirming and have an OK,
+ * so look for ia's in our old lease.
+ * IA's must have existed here otherwise we would
+ * have rejected it earlier. */
+ assert(state->new != NULL && state->new_len != 0);
+ state->has_no_binding = false;
+ nia = dhcp6_findia(ifp, state->new, state->new_len,
+ sfrom, acquired);
+ }
+ return nia;
+}
+
+static ssize_t
+dhcp6_readlease(struct interface *ifp, int validate)
+{
+ union {
+ struct dhcp6_message dhcp6;
+ uint8_t buf[UDPLEN_MAX];
+ } buf;
+ struct dhcp6_state *state;
+ ssize_t bytes;
+ int fd;
+ time_t mtime, now;
+#ifdef AUTH
+ uint8_t *o;
+ uint16_t ol;
+#endif
+
+ state = D6_STATE(ifp);
+ if (state->leasefile[0] == '\0') {
+ logdebugx("reading standard input");
+ bytes = read(fileno(stdin), buf.buf, sizeof(buf.buf));
+ } else {
+ logdebugx("%s: reading lease: %s",
+ ifp->name, state->leasefile);
+ bytes = dhcp_readfile(ifp->ctx, state->leasefile,
+ buf.buf, sizeof(buf.buf));
+ }
+ if (bytes == -1)
+ goto ex;
+
+ if (ifp->ctx->options & DHCPCD_DUMPLEASE || state->leasefile[0] == '\0')
+ goto out;
+
+ if (bytes == 0)
+ goto ex;
+
+ /* If not validating IA's and if they have expired,
+ * skip to the auth check. */
+ if (!validate)
+ goto auth;
+
+ if (dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == -1)
+ goto ex;
+ clock_gettime(CLOCK_MONOTONIC, &state->acquired);
+ if ((now = time(NULL)) == -1)
+ goto ex;
+ state->acquired.tv_sec -= now - mtime;
+
+ /* Check to see if the lease is still valid */
+ fd = dhcp6_validatelease(ifp, &buf.dhcp6, (size_t)bytes, NULL,
+ &state->acquired);
+ if (fd == -1)
+ goto ex;
+
+ if (state->expire != ND6_INFINITE_LIFETIME &&
+ (time_t)state->expire < now - mtime &&
+ !(ifp->options->options & DHCPCD_LASTLEASE_EXTEND))
+ {
+ logdebugx("%s: discarding expired lease", ifp->name);
+ bytes = 0;
+ goto ex;
+ }
+
+auth:
+#ifdef AUTH
+ /* Authenticate the message */
+ o = dhcp6_findmoption(&buf.dhcp6, (size_t)bytes, D6_OPTION_AUTH, &ol);
+ if (o) {
+ if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+ buf.buf, (size_t)bytes, 6, buf.dhcp6.type, o, ol) == NULL)
+ {
+ logerr("%s: authentication failed", ifp->name);
+ bytes = 0;
+ goto ex;
+ }
+ if (state->auth.token)
+ logdebugx("%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ loginfox("%s: accepted reconfigure key", ifp->name);
+ } else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) ==
+ DHCPCD_AUTH_SENDREQUIRE)
+ {
+ logerrx("%s: authentication now required", ifp->name);
+ goto ex;
+ }
+#endif
+
+out:
+ free(state->new);
+ state->new = malloc((size_t)bytes);
+ if (state->new == NULL) {
+ logerr(__func__);
+ goto ex;
+ }
+
+ memcpy(state->new, buf.buf, (size_t)bytes);
+ state->new_len = (size_t)bytes;
+ return bytes;
+
+ex:
+ dhcp6_freedrop_addrs(ifp, 0, NULL);
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ free(state->new);
+ state->new = NULL;
+ state->new_len = 0;
+ dhcp6_addrequestedaddrs(ifp);
+ return bytes == 0 ? 0 : -1;
+}
+
+static void
+dhcp6_startinit(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ ssize_t 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 && errno != ESRCH)
+ logerr("%s: %s", __func__, state->leasefile);
+ } else if (r != 0 &&
+ !(ifp->options->options & DHCPCD_ANONYMOUS))
+ {
+ /* RFC 3633 section 12.1 */
+#ifndef SMALL
+ if (dhcp6_hasprefixdelegation(ifp))
+ dhcp6_startrebind(ifp);
+ else
+#endif
+ dhcp6_startconfirm(ifp);
+ return;
+ }
+ }
+ dhcp6_startdiscoinform(ifp);
+}
+
+#ifndef SMALL
+static struct ipv6_addr *
+dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix,
+ const struct if_sla *sla, struct if_ia *if_ia)
+{
+ struct dhcp6_state *state;
+ struct in6_addr addr, daddr;
+ struct ipv6_addr *ia;
+ int pfxlen, dadcounter;
+ uint64_t vl;
+
+ /* RFC6603 Section 4.2 */
+ if (strcmp(ifp->name, prefix->iface->name) == 0) {
+ if (prefix->prefix_exclude_len == 0) {
+ /* Don't spam the log automatically */
+ if (sla != NULL)
+ logwarnx("%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, if_ia)) == -1)
+ return NULL;
+
+ if (sla != NULL && fls64(sla->suffix) > 128 - pfxlen) {
+ logerrx("%s: suffix %" PRIu64 " + prefix_len %d > 128",
+ ifp->name, sla->suffix, pfxlen);
+ return NULL;
+ }
+
+ /* Add our suffix */
+ if (sla != NULL && sla->suffix != 0) {
+ daddr = addr;
+ vl = be64dec(addr.s6_addr + 8);
+ vl |= sla->suffix;
+ be64enc(daddr.s6_addr + 8, vl);
+ } else {
+ dadcounter = ipv6_makeaddr(&daddr, ifp, &addr, pfxlen, 0);
+ if (dadcounter == -1) {
+ logerrx("%s: error adding slaac to prefix_len %d",
+ ifp->name, pfxlen);
+ return NULL;
+ }
+ }
+
+ /* Find an existing address */
+ state = D6_STATE(ifp);
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ia->addr, &daddr))
+ break;
+ }
+ if (ia == NULL) {
+ ia = ipv6_newaddr(ifp, &daddr, (uint8_t)pfxlen, IPV6_AF_ONLINK);
+ if (ia == NULL)
+ return NULL;
+ ia->dadcallback = dhcp6_dadcallback;
+ memcpy(&ia->iaid, &prefix->iaid, sizeof(ia->iaid));
+ ia->created = prefix->acquired;
+
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ TAILQ_INSERT_TAIL(&prefix->pd_pfxs, ia, pd_next);
+ }
+ ia->delegating_prefix = prefix;
+ ia->prefix = addr;
+ ia->prefix_len = (uint8_t)pfxlen;
+ ia->acquired = prefix->acquired;
+ ia->prefix_pltime = prefix->prefix_pltime;
+ ia->prefix_vltime = prefix->prefix_vltime;
+
+ /* If the prefix length hasn't changed,
+ * don't install a reject route. */
+ if (prefix->prefix_len == pfxlen)
+ prefix->flags |= IPV6_AF_NOREJECT;
+ else
+ prefix->flags &= ~IPV6_AF_NOREJECT;
+
+ return ia;
+}
+#endif
+
+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,
+ IN6_IFF_TENTATIVE))
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0
+#ifndef SMALL
+ && ((delegated && ap->delegating_prefix) ||
+ (!delegated && !ap->delegating_prefix))
+#endif
+ )
+ {
+ completed = 0;
+ break;
+ }
+ }
+ }
+ if (completed) {
+ script_runreason(ifp, delegated ? "DELEGATED6" : state->reason);
+ if (!delegated)
+ dhcpcd_daemonise(ifp->ctx);
+ } else
+ logdebugx("%s: waiting for DHCPv6 DAD to complete", ifp->name);
+}
+
+#ifdef SMALL
+size_t
+dhcp6_find_delegates(__unused struct interface *ifp)
+{
+
+ return 0;
+}
+#else
+static void
+dhcp6_delegate_prefix(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;
+ bool carrier_warned;
+
+ ifo = ifp->options;
+ state = D6_STATE(ifp);
+
+ /* Clear the logged flag. */
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ ap->flags &= ~IPV6_AF_DELEGATEDLOG;
+ }
+
+ TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) {
+ if (!ifd->active)
+ continue;
+ if (!(ifd->options->options & DHCPCD_CONFIGURE))
+ continue;
+ k = 0;
+ carrier_warned = false;
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
+ continue;
+ if (!(ap->flags & IPV6_AF_DELEGATEDLOG)) {
+ int loglevel;
+
+ if (ap->flags & IPV6_AF_NEW)
+ loglevel = LOG_INFO;
+ else
+ loglevel = LOG_DEBUG;
+ /* We only want to log this the once as we loop
+ * through many interfaces first. */
+ ap->flags |= IPV6_AF_DELEGATEDLOG;
+ logmessage(loglevel, "%s: delegated prefix %s",
+ ifp->name, ap->saddr);
+ ap->flags &= ~IPV6_AF_NEW;
+ }
+ for (i = 0; i < ifo->ia_len; i++) {
+ ia = &ifo->ia[i];
+ if (ia->ia_type != D6_OPTION_IA_PD)
+ continue;
+ if (memcmp(ia->iaid, ap->iaid,
+ sizeof(ia->iaid)))
+ continue;
+ if (ia->sla_len == 0) {
+ /* no SLA configured, so lets
+ * automate it */
+ if (!if_is_link_up(ifd)) {
+ logdebugx(
+ "%s: has no carrier, cannot"
+ " delegate addresses",
+ ifd->name);
+ carrier_warned = true;
+ break;
+ }
+ if (dhcp6_ifdelegateaddr(ifd, ap,
+ NULL, ia))
+ k++;
+ }
+ for (j = 0; j < ia->sla_len; j++) {
+ sla = &ia->sla[j];
+ if (strcmp(ifd->name, sla->ifname))
+ continue;
+ if (!if_is_link_up(ifd)) {
+ logdebugx(
+ "%s: has no carrier, cannot"
+ " delegate addresses",
+ ifd->name);
+ carrier_warned = true;
+ break;
+ }
+ if (dhcp6_ifdelegateaddr(ifd, ap,
+ sla, ia))
+ k++;
+ }
+ if (carrier_warned)
+ break;
+ }
+ if (carrier_warned)
+ break;
+ }
+ if (k && !carrier_warned) {
+ struct dhcp6_state *s = D6_STATE(ifd);
+
+ ipv6_addaddrs(&s->addrs);
+ dhcp6_script_try_run(ifd, 1);
+ }
+ }
+
+ /* Now all addresses have been added, rebuild the routing table. */
+ rt_build(ifp->ctx, AF_INET6);
+}
+
+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;
+
+ if (ifp->options != NULL &&
+ !(ifp->options->options & DHCPCD_CONFIGURE))
+ return 0;
+
+ 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 (ia->ia_type != D6_OPTION_IA_PD)
+ continue;
+ 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) {
+ logdebugx(
+ "%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))
+ k++;
+ }
+ }
+ }
+ }
+
+ if (k) {
+ loginfox("%s: adding delegated prefixes", ifp->name);
+ state = D6_STATE(ifp);
+ state->state = DH6S_DELEGATED;
+ ipv6_addaddrs(&state->addrs);
+ rt_build(ifp->ctx, AF_INET6);
+ dhcp6_script_try_run(ifp, 1);
+ }
+ return k;
+}
+#endif
+
+static void
+dhcp6_bind(struct interface *ifp, const char *op, const char *sfrom)
+{
+ struct dhcp6_state *state = D6_STATE(ifp);
+ bool timedout = (op == NULL), confirmed;
+ struct ipv6_addr *ia;
+ int loglevel;
+ struct timespec now;
+
+ if (state->state == DH6S_RENEW && !state->new_start) {
+ loglevel = LOG_DEBUG;
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_NEW) {
+ loglevel = LOG_INFO;
+ break;
+ }
+ }
+ } else if (state->state == DH6S_INFORM)
+ loglevel = state->new_start ? LOG_INFO : LOG_DEBUG;
+ else
+ loglevel = LOG_INFO;
+ state->new_start = false;
+
+ if (!timedout) {
+ logmessage(loglevel, "%s: %s received from %s",
+ ifp->name, op, sfrom);
+#ifndef SMALL
+ /* If we delegated from an unconfirmed lease we MUST drop
+ * them now. Hopefully we have new delegations. */
+ if (state->reason != NULL &&
+ strcmp(state->reason, "TIMEOUT6") == 0)
+ dhcp6_delete_delegates(ifp);
+#endif
+ state->reason = NULL;
+ } else
+ state->reason = "TIMEOUT6";
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ switch(state->state) {
+ case DH6S_INFORM:
+ {
+ struct dhcp6_option *o;
+ uint16_t ol;
+
+ if (state->reason == NULL)
+ state->reason = "INFORM6";
+ o = dhcp6_findmoption(state->new, state->new_len,
+ D6_OPTION_INFO_REFRESH_TIME, &ol);
+ if (o == NULL || ol != sizeof(uint32_t))
+ state->renew = IRT_DEFAULT;
+ else {
+ memcpy(&state->renew, o, ol);
+ state->renew = ntohl(state->renew);
+ if (state->renew < IRT_MINIMUM)
+ state->renew = IRT_MINIMUM;
+ }
+ state->rebind = 0;
+ state->expire = ND6_INFINITE_LIFETIME;
+ state->lowpl = ND6_INFINITE_LIFETIME;
+ }
+ 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) {
+ bool all_expired = true;
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_STALE)
+ continue;
+ if (!(state->renew == ND6_INFINITE_LIFETIME
+ && ia->prefix_vltime == ND6_INFINITE_LIFETIME)
+ && ia->prefix_vltime != 0
+ && ia->prefix_vltime <= state->renew)
+ logwarnx(
+ "%s: %s will expire before renewal",
+ ifp->name, ia->saddr);
+ else
+ all_expired = false;
+ }
+ if (all_expired) {
+ /* All address's vltime happens at or before
+ * the configured T1 in the IA.
+ * This is a badly configured server and we
+ * have to use our own notion of what
+ * T1 and T2 should be as a result.
+ *
+ * Doing this violates RFC 3315 22.4:
+ * In a message sent by a server to a client,
+ * the client MUST use the values in the T1
+ * and T2 fields for the T1 and T2 parameters,
+ * unless those values in those fields are 0.
+ */
+ logwarnx("%s: ignoring T1 %"PRIu32
+ " due to address expiry",
+ ifp->name, state->renew);
+ state->renew = state->rebind = 0;
+ }
+ }
+ if (state->renew == 0 && state->lowpl != ND6_INFINITE_LIFETIME)
+ state->renew = (uint32_t)(state->lowpl * 0.5);
+ if (state->rebind == 0 && state->lowpl != ND6_INFINITE_LIFETIME)
+ state->rebind = (uint32_t)(state->lowpl * 0.8);
+ break;
+ default:
+ state->reason = "UNKNOWN6";
+ break;
+ }
+
+ if (state->state != DH6S_CONFIRM && !timedout) {
+ state->acquired = now;
+ 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;
+ confirmed = false;
+ } else {
+ /* Reduce timers based on when we got the lease. */
+ uint32_t elapsed;
+
+ elapsed = (uint32_t)eloop_timespec_diff(&now,
+ &state->acquired, NULL);
+ if (state->renew && state->renew != ND6_INFINITE_LIFETIME) {
+ if (state->renew > elapsed)
+ state->renew -= elapsed;
+ else
+ state->renew = 0;
+ }
+ if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) {
+ if (state->rebind > elapsed)
+ state->rebind -= elapsed;
+ else
+ state->rebind = 0;
+ }
+ if (state->expire && state->expire != ND6_INFINITE_LIFETIME) {
+ if (state->expire > elapsed)
+ state->expire -= elapsed;
+ else
+ state->expire = 0;
+ }
+ confirmed = true;
+ }
+
+ 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;
+ state->failed = false;
+
+ if (state->renew && state->renew != ND6_INFINITE_LIFETIME)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ 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,
+ state->rebind, dhcp6_startrebind, ifp);
+ if (state->expire != ND6_INFINITE_LIFETIME)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ state->expire, dhcp6_startexpire, ifp);
+
+ if (ifp->options->options & DHCPCD_CONFIGURE) {
+ ipv6_addaddrs(&state->addrs);
+ if (!timedout)
+ dhcp6_deprecateaddrs(&state->addrs);
+ }
+
+ if (state->state == DH6S_INFORMED)
+ logmessage(loglevel, "%s: refresh in %"PRIu32" seconds",
+ ifp->name, state->renew);
+ else if (state->renew == ND6_INFINITE_LIFETIME)
+ logmessage(loglevel, "%s: leased for infinity",
+ ifp->name);
+ else if (state->renew || state->rebind)
+ logmessage(loglevel, "%s: renew in %"PRIu32", "
+ "rebind in %"PRIu32", "
+ "expire in %"PRIu32" seconds",
+ ifp->name,
+ state->renew, state->rebind, state->expire);
+ else if (state->expire == 0)
+ logmessage(loglevel, "%s: will expire", ifp->name);
+ else
+ logmessage(loglevel, "%s: expire in %"PRIu32" seconds",
+ ifp->name, state->expire);
+ rt_build(ifp->ctx, AF_INET6);
+ if (!confirmed && !timedout) {
+ logdebugx("%s: writing lease: %s",
+ ifp->name, state->leasefile);
+ if (dhcp_writefile(ifp->ctx, state->leasefile, 0640,
+ state->new, state->new_len) == -1)
+ logerr("dhcp_writefile: %s",state->leasefile);
+ }
+#ifndef SMALL
+ dhcp6_delegate_prefix(ifp);
+#endif
+ dhcp6_script_try_run(ifp, 0);
+ }
+
+ if (ifp->ctx->options & DHCPCD_TEST ||
+ (ifp->options->options & DHCPCD_INFORM &&
+ !(ifp->ctx->options & DHCPCD_MANAGER)))
+ {
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ }
+}
+
+static void
+dhcp6_recvif(struct interface *ifp, const char *sfrom,
+ struct dhcp6_message *r, size_t len)
+{
+ struct dhcpcd_ctx *ctx;
+ size_t i;
+ const char *op;
+ struct dhcp6_state *state;
+ uint8_t *o;
+ uint16_t ol;
+ const struct dhcp_opt *opt;
+ const struct if_options *ifo;
+ bool valid_op;
+#ifdef AUTH
+ uint8_t *auth;
+ uint16_t auth_len;
+#endif
+
+ ctx = ifp->ctx;
+ state = D6_STATE(ifp);
+ if (state == NULL || state->send == NULL) {
+ logdebugx("%s: DHCPv6 reply received but not running",
+ ifp->name);
+ return;
+ }
+
+ /* 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))
+ {
+ logdebugx("%s: DHCPv6 reply received but already bound",
+ ifp->name);
+ return;
+ }
+
+ if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) {
+ logdebugx("%s: no DHCPv6 server ID from %s", ifp->name, sfrom);
+ return;
+ }
+
+ ifo = ifp->options;
+ for (i = 0, opt = ctx->dhcp6_opts;
+ i < ctx->dhcp6_opts_len;
+ i++, opt++)
+ {
+ if (has_option_mask(ifo->requiremask6, opt->option) &&
+ !dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL))
+ {
+ logwarnx("%s: reject DHCPv6 (no option %s) from %s",
+ ifp->name, opt->var, sfrom);
+ return;
+ }
+ if (has_option_mask(ifo->rejectmask6, opt->option) &&
+ dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL))
+ {
+ logwarnx("%s: reject DHCPv6 (option %s) from %s",
+ ifp->name, opt->var, sfrom);
+ return;
+ }
+ }
+
+#ifdef AUTH
+ /* Authenticate the message */
+ auth = dhcp6_findmoption(r, len, D6_OPTION_AUTH, &auth_len);
+ if (auth != NULL) {
+ if (dhcp_auth_validate(&state->auth, &ifo->auth,
+ (uint8_t *)r, len, 6, r->type, auth, auth_len) == NULL)
+ {
+ logerr("%s: authentication failed from %s",
+ ifp->name, sfrom);
+ return;
+ }
+ if (state->auth.token)
+ logdebugx("%s: validated using 0x%08" PRIu32,
+ ifp->name, state->auth.token->secretid);
+ else
+ loginfox("%s: accepted reconfigure key", ifp->name);
+ } else if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+ logerrx("%s: no authentication from %s",
+ ifp->name, sfrom);
+ return;
+ }
+ logwarnx("%s: no authentication from %s", ifp->name, sfrom);
+ }
+#endif
+
+ op = dhcp6_get_op(r->type);
+ valid_op = op != NULL;
+ switch(r->type) {
+ case DHCP6_REPLY:
+ switch(state->state) {
+ case DH6S_INFORM:
+ if (dhcp6_checkstatusok(ifp, r, NULL, len) != 0)
+ return;
+ break;
+ case DH6S_CONFIRM:
+ if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
+ {
+ dhcp6_startdiscoinform(ifp);
+ return;
+ }
+ break;
+ case DH6S_DISCOVER:
+ /* Only accept REPLY in DISCOVER for RAPID_COMMIT.
+ * Normally we get an ADVERTISE for a DISCOVER. */
+ if (!has_option_mask(ifo->requestmask6,
+ D6_OPTION_RAPID_COMMIT) ||
+ !dhcp6_findmoption(r, len, D6_OPTION_RAPID_COMMIT,
+ NULL))
+ {
+ valid_op = false;
+ break;
+ }
+ /* Validate lease before setting state to REQUEST. */
+ /* FALLTHROUGH */
+ case DH6S_REQUEST: /* FALLTHROUGH */
+ case DH6S_RENEW: /* FALLTHROUGH */
+ case DH6S_REBIND:
+ if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
+ {
+ /*
+ * If we can't use the lease, fallback to
+ * DISCOVER and try and get a new one.
+ *
+ * This is needed become some servers
+ * renumber the prefix or address
+ * and deny the current one before it expires
+ * rather than sending it back with a zero
+ * lifetime along with the new prefix or
+ * address to use.
+ * This behavior is wrong, but moving to the
+ * DISCOVER phase works around it.
+ *
+ * The currently held lease is still valid
+ * until a new one is found.
+ */
+ if (state->state != DH6S_DISCOVER)
+ dhcp6_startdiscoinform(ifp);
+ return;
+ }
+ /* RFC8415 18.2.10.1 */
+ if ((state->state == DH6S_RENEW ||
+ state->state == DH6S_REBIND) &&
+ state->has_no_binding)
+ {
+ dhcp6_startrequest(ifp);
+ return;
+ }
+ if (state->state == DH6S_DISCOVER)
+ state->state = DH6S_REQUEST;
+ break;
+ case DH6S_DECLINE:
+ /* This isnt really a failure, but an
+ * acknowledgement of one. */
+ loginfox("%s: %s acknowledged DECLINE6",
+ ifp->name, sfrom);
+ dhcp6_fail(ifp);
+ return;
+ default:
+ valid_op = false;
+ break;
+ }
+ break;
+ case DHCP6_ADVERTISE:
+ if (state->state != DH6S_DISCOVER) {
+ valid_op = false;
+ break;
+ }
+ /* RFC7083 */
+ o = dhcp6_findmoption(r, len, D6_OPTION_SOL_MAX_RT, &ol);
+ if (o && ol == sizeof(uint32_t)) {
+ uint32_t max_rt;
+
+ memcpy(&max_rt, o, sizeof(max_rt));
+ max_rt = ntohl(max_rt);
+ if (max_rt >= 60 && max_rt <= 86400) {
+ logdebugx("%s: SOL_MAX_RT %llu -> %u",
+ ifp->name,
+ (unsigned long long)state->sol_max_rt,
+ max_rt);
+ state->sol_max_rt = max_rt;
+ } else
+ logerr("%s: invalid SOL_MAX_RT %u",
+ ifp->name, max_rt);
+ }
+ o = dhcp6_findmoption(r, len, D6_OPTION_INF_MAX_RT, &ol);
+ if (o && ol == sizeof(uint32_t)) {
+ uint32_t max_rt;
+
+ memcpy(&max_rt, o, sizeof(max_rt));
+ max_rt = ntohl(max_rt);
+ if (max_rt >= 60 && max_rt <= 86400) {
+ logdebugx("%s: INF_MAX_RT %llu -> %u",
+ ifp->name,
+ (unsigned long long)state->inf_max_rt,
+ max_rt);
+ state->inf_max_rt = max_rt;
+ } else
+ logerrx("%s: invalid INF_MAX_RT %u",
+ ifp->name, max_rt);
+ }
+ if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
+ return;
+ break;
+ case DHCP6_RECONFIGURE:
+#ifdef AUTH
+ if (auth == NULL) {
+#endif
+ logerrx("%s: unauthenticated %s from %s",
+ ifp->name, op, sfrom);
+ if (ifo->auth.options & DHCPCD_AUTH_REQUIRE)
+ return;
+#ifdef AUTH
+ }
+ loginfox("%s: %s from %s", ifp->name, op, sfrom);
+ o = dhcp6_findmoption(r, len, D6_OPTION_RECONF_MSG, &ol);
+ if (o == NULL) {
+ logerrx("%s: missing Reconfigure Message option",
+ ifp->name);
+ return;
+ }
+ if (ol != 1) {
+ logerrx("%s: missing Reconfigure Message type",
+ ifp->name);
+ return;
+ }
+ switch(*o) {
+ case DHCP6_RENEW:
+ if (state->state != DH6S_BOUND) {
+ logerrx("%s: not bound, ignoring %s",
+ ifp->name, op);
+ return;
+ }
+ dhcp6_startrenew(ifp);
+ break;
+ case DHCP6_INFORMATION_REQ:
+ if (state->state != DH6S_INFORMED) {
+ logerrx("%s: not informed, ignoring %s",
+ ifp->name, op);
+ return;
+ }
+ eloop_timeout_delete(ifp->ctx->eloop,
+ dhcp6_sendinform, ifp);
+ dhcp6_startinform(ifp);
+ break;
+ default:
+ logerr("%s: unsupported %s type %d",
+ ifp->name, op, *o);
+ break;
+ }
+ return;
+#else
+ break;
+#endif
+ default:
+ logerrx("%s: invalid DHCP6 type %s (%d)",
+ ifp->name, op, r->type);
+ return;
+ }
+ if (!valid_op) {
+ logwarnx("%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) {
+ logerr(__func__);
+ return;
+ }
+ }
+ memcpy(state->recv, r, len);
+ state->recv_len = len;
+
+ if (r->type == DHCP6_ADVERTISE) {
+ struct ipv6_addr *ia;
+
+ if (state->state == DH6S_REQUEST) /* rapid commit */
+ goto bind;
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (!(ia->flags & (IPV6_AF_STALE | IPV6_AF_REQUEST)))
+ break;
+ }
+ if (ia == NULL)
+ ia = TAILQ_FIRST(&state->addrs);
+ if (ia == NULL)
+ loginfox("%s: ADV (no address) from %s",
+ ifp->name, sfrom);
+ else
+ loginfox("%s: ADV %s from %s",
+ ifp->name, ia->saddr, sfrom);
+ dhcp6_startrequest(ifp);
+ return;
+ }
+
+bind:
+ dhcp6_bind(ifp, op, sfrom);
+}
+
+void
+dhcp6_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, struct ipv6_addr *ia)
+{
+ struct sockaddr_in6 *from = msg->msg_name;
+ size_t len = msg->msg_iov[0].iov_len;
+ char sfrom[INET6_ADDRSTRLEN];
+ struct interface *ifp;
+ struct dhcp6_message *r;
+ const struct dhcp6_state *state;
+ uint8_t *o;
+ uint16_t ol;
+
+ inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom));
+ if (len < sizeof(struct dhcp6_message)) {
+ logerrx("DHCPv6 packet too short from %s", sfrom);
+ return;
+ }
+
+ if (ia != NULL)
+ ifp = ia->iface;
+ else {
+ ifp = if_findifpfromcmsg(ctx, msg, NULL);
+ if (ifp == NULL) {
+ logerr(__func__);
+ return;
+ }
+ }
+
+ r = (struct dhcp6_message *)msg->msg_iov[0].iov_base;
+
+ uint8_t duid[DUID_LEN], *dp;
+ size_t duid_len;
+ o = dhcp6_findmoption(r, len, D6_OPTION_CLIENTID, &ol);
+ if (ifp->options->options & DHCPCD_ANONYMOUS) {
+ duid_len = duid_make(duid, ifp, DUID_LL);
+ dp = duid;
+ } else {
+ duid_len = ctx->duid_len;
+ dp = ctx->duid;
+ }
+ if (o == NULL || ol != duid_len || memcmp(o, dp, ol) != 0) {
+ logdebugx("%s: incorrect client ID from %s",
+ ifp->name, sfrom);
+ return;
+ }
+
+ if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) {
+ logdebugx("%s: no DHCPv6 server ID from %s",
+ ifp->name, sfrom);
+ return;
+ }
+
+ if (r->type == DHCP6_RECONFIGURE) {
+ if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) {
+ logerrx("%s: RECONFIGURE6 recv from %s, not LL",
+ ifp->name, sfrom);
+ return;
+ }
+ goto recvif;
+ }
+
+ state = D6_CSTATE(ifp);
+ if (state == NULL ||
+ r->xid[0] != state->send->xid[0] ||
+ r->xid[1] != state->send->xid[1] ||
+ r->xid[2] != state->send->xid[2])
+ {
+ struct interface *ifp1;
+ const struct dhcp6_state *state1;
+
+ /* Find an interface with a matching xid. */
+ TAILQ_FOREACH(ifp1, ctx->ifaces, next) {
+ state1 = D6_CSTATE(ifp1);
+ if (state1 == NULL || state1->send == NULL)
+ continue;
+ if (r->xid[0] == state1->send->xid[0] &&
+ r->xid[1] == state1->send->xid[1] &&
+ r->xid[2] == state1->send->xid[2])
+ break;
+ }
+
+ if (ifp1 == NULL) {
+ if (state != NULL)
+ logdebugx("%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],
+ sfrom);
+ return;
+ }
+ logdebugx("%s: redirecting DHCP6 message to %s",
+ ifp->name, ifp1->name);
+ ifp = ifp1;
+ }
+
+#if 0
+ /*
+ * Handy code to inject raw DHCPv6 packets over responses
+ * from our server.
+ * This allows me to take a 3rd party wireshark trace and
+ * replay it in my code.
+ */
+ static int replyn = 0;
+ char fname[PATH_MAX], tbuf[UDPLEN_MAX];
+ int fd;
+ ssize_t tlen;
+ uint8_t *si1, *si2;
+ uint16_t si_len1, si_len2;
+
+ snprintf(fname, sizeof(fname),
+ "/tmp/dhcp6.reply%d.raw", replyn++);
+ fd = open(fname, O_RDONLY, 0);
+ if (fd == -1) {
+ logerr("%s: open: %s", __func__, fname);
+ return;
+ }
+ tlen = read(fd, tbuf, sizeof(tbuf));
+ if (tlen == -1)
+ logerr("%s: read: %s", __func__, fname);
+ close(fd);
+
+ /* Copy across ServerID so we can work with our own server. */
+ si1 = dhcp6_findmoption(r, len, D6_OPTION_SERVERID, &si_len1);
+ si2 = dhcp6_findmoption(tbuf, (size_t)tlen,
+ D6_OPTION_SERVERID, &si_len2);
+ if (si1 != NULL && si2 != NULL && si_len1 == si_len2)
+ memcpy(si2, si1, si_len2);
+ r = (struct dhcp6_message *)tbuf;
+ len = (size_t)tlen;
+#endif
+
+recvif:
+ dhcp6_recvif(ifp, sfrom, r, len);
+}
+
+static void
+dhcp6_recv(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia)
+{
+ struct sockaddr_in6 from;
+ union {
+ struct dhcp6_message dhcp6;
+ uint8_t buf[UDPLEN_MAX]; /* Maximum UDP message size */
+ } iovbuf;
+ struct iovec iov = {
+ .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf),
+ };
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct msghdr msg = {
+ .msg_name = &from, .msg_namelen = sizeof(from),
+ .msg_iov = &iov, .msg_iovlen = 1,
+ .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ int s;
+ ssize_t bytes;
+
+ s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_rfd;
+ bytes = recvmsg(s, &msg, 0);
+ if (bytes == -1) {
+ logerr(__func__);
+ return;
+ }
+
+ iov.iov_len = (size_t)bytes;
+ dhcp6_recvmsg(ctx, &msg, ia);
+}
+
+static void
+dhcp6_recvaddr(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+
+ dhcp6_recv(ia->iface->ctx, ia);
+}
+
+static void
+dhcp6_recvctx(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ dhcp6_recv(ctx, NULL);
+}
+
+int
+dhcp6_openraw(void)
+{
+ int fd, v;
+
+ fd = socket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
+ if (fd == -1)
+ return -1;
+
+ v = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &v, sizeof(v)) == -1)
+ goto errexit;
+
+ v = offsetof(struct udphdr, uh_sum);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &v, sizeof(v)) == -1)
+ goto errexit;
+
+ return fd;
+
+errexit:
+ close(fd);
+ return -1;
+}
+
+int
+dhcp6_openudp(unsigned int ifindex, struct in6_addr *ia)
+{
+ struct sockaddr_in6 sa;
+ int n, s;
+
+ s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP);
+ if (s == -1)
+ goto errexit;
+
+ 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
+
+ if (ia != NULL) {
+ memcpy(&sa.sin6_addr, ia, sizeof(sa.sin6_addr));
+ ipv6_setscope(&sa, ifindex);
+ }
+
+ if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
+ goto errexit;
+
+ n = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &n, sizeof(n)) == -1)
+ goto errexit;
+
+#ifdef SO_RERROR
+ n = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1)
+ goto errexit;
+#endif
+
+ return s;
+
+errexit:
+ logerr(__func__);
+ if (s != -1)
+ close(s);
+ return -1;
+}
+
+#ifndef SMALL
+static void
+dhcp6_activateinterfaces(struct interface *ifp)
+{
+ struct interface *ifd;
+ size_t i, j;
+ struct if_ia *ia;
+ struct if_sla *sla;
+
+ for (i = 0; i < ifp->options->ia_len; i++) {
+ ia = &ifp->options->ia[i];
+ if (ia->ia_type != D6_OPTION_IA_PD)
+ continue;
+ for (j = 0; j < ia->sla_len; j++) {
+ sla = &ia->sla[j];
+ ifd = if_find(ifp->ctx->ifaces, sla->ifname);
+ if (ifd == NULL) {
+ logwarn("%s: cannot delegate to %s",
+ ifp->name, sla->ifname);
+ continue;
+ }
+ if (!ifd->active) {
+ loginfox("%s: activating for delegation",
+ sla->ifname);
+ dhcpcd_activateinterface(ifd,
+ DHCPCD_IPV6 | DHCPCD_DHCP6);
+ }
+ }
+ }
+}
+#endif
+
+static void
+dhcp6_start1(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct if_options *ifo = ifp->options;
+ struct dhcp6_state *state;
+ size_t i;
+ const struct dhcp_compat *dhc;
+
+ if ((ctx->options & (DHCPCD_MANAGER|DHCPCD_PRIVSEP)) == DHCPCD_MANAGER &&
+ ctx->dhcp6_rfd == -1)
+ {
+ ctx->dhcp6_rfd = dhcp6_openudp(0, NULL);
+ if (ctx->dhcp6_rfd == -1) {
+ logerr(__func__);
+ return;
+ }
+ eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, dhcp6_recvctx, ctx);
+ }
+
+ if (!IN_PRIVSEP(ctx) && ctx->dhcp6_wfd == -1) {
+ ctx->dhcp6_wfd = dhcp6_openraw();
+ if (ctx->dhcp6_wfd == -1) {
+ logerr(__func__);
+ return;
+ }
+ }
+
+ 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 (DHC_REQ(ifo->requestmask, ifo->nomask, 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);
+ }
+
+#ifndef SMALL
+ /* Rapid commit won't work with Prefix Delegation Exclusion */
+ if (dhcp6_findselfsla(ifp))
+ del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT);
+#endif
+
+ 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);
+ }
+
+#ifndef SMALL
+ dhcp6_activateinterfaces(ifp);
+#endif
+}
+
+int
+dhcp6_start(struct interface *ifp, enum DH6S init_state)
+{
+ struct dhcp6_state *state;
+
+ state = D6_STATE(ifp);
+ if (state != NULL) {
+ switch (init_state) {
+ case DH6S_INIT:
+ goto gogogo;
+ case DH6S_INFORM:
+ if (state->state == DH6S_INIT ||
+ state->state == DH6S_INFORMED ||
+ (state->state == DH6S_DISCOVER &&
+ !(ifp->options->options & DHCPCD_IA_FORCED) &&
+ !ipv6nd_hasradhcp(ifp, true)))
+ {
+ /* We don't want log spam when the RA
+ * has just adjusted it's prefix times. */
+ if (state->state != DH6S_INFORMED)
+ state->new_start = true;
+ dhcp6_startinform(ifp);
+ }
+ break;
+ case DH6S_REQUEST:
+ if (ifp->options->options & DHCPCD_DHCP6 &&
+ (state->state == DH6S_INIT ||
+ state->state == DH6S_INFORM ||
+ state->state == DH6S_INFORMED ||
+ state->state == DH6S_DELEGATED))
+ {
+ /* Change from stateless to stateful */
+ init_state = DH6S_INIT;
+ goto gogogo;
+ }
+ break;
+ case DH6S_CONFIRM:
+ init_state = DH6S_INIT;
+ goto gogogo;
+ default:
+ /* Not possible, but sushes some compiler warnings. */
+ break;
+ }
+ return 0;
+ } else {
+ switch (init_state) {
+ case DH6S_CONFIRM:
+ /* No DHCPv6 config, no existing state
+ * so nothing to do. */
+ return 0;
+ case DH6S_INFORM:
+ break;
+ default:
+ init_state = DH6S_INIT;
+ break;
+ }
+ }
+
+ if (!(ifp->options->options & DHCPCD_DHCP6))
+ return 0;
+
+ 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->new_start = true;
+ state->state = init_state;
+ state->lerror = 0;
+ state->failed = false;
+ dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
+ AF_INET6, ifp);
+ if (ipv6_linklocal(ifp) == NULL) {
+ logdebugx("%s: delaying DHCPv6 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 == NULL)
+ return;
+
+ state->lerror = 0;
+ switch (state->state) {
+ case DH6S_BOUND:
+ dhcp6_startrebind(ifp);
+ break;
+ default:
+ dhcp6_startdiscoinform(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;
+
+ if (ifp->options)
+ options = ifp->options->options;
+ else
+ options = ifp->ctx->options;
+
+ if (ifp->ctx->eloop)
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+
+#ifndef SMALL
+ /* If we're dropping the lease, drop delegated addresses.
+ * If, for whatever reason, we don't drop them in the future
+ * then they should at least be marked as deprecated (pltime 0). */
+ if (drop && (options & DHCPCD_NODROP) != DHCPCD_NODROP)
+ dhcp6_delete_delegates(ifp);
+#endif
+
+ 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 &&
+ state->state != DH6S_DELEGATED)
+ {
+ if (if_is_link_up(ifp) &&
+ state->state != DH6S_RELEASED &&
+ state->state != DH6S_INFORMED)
+ {
+ dhcp6_startrelease(ifp);
+ return;
+ }
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ }
+#ifdef AUTH
+ else if (state->auth.reconf != NULL) {
+ /*
+ * Drop the lease as the token may only be present
+ * in the initial reply message and not subsequent
+ * renewals.
+ * If dhcpcd is restarted, the token is lost.
+ * XXX persist this in another file?
+ */
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ }
+#endif
+
+ 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->dhcp6_rfd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->dhcp6_rfd);
+ close(ctx->dhcp6_rfd);
+ ctx->dhcp6_rfd = -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_abort(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+#ifdef ND6_ADVERTISE
+ struct ipv6_addr *ia;
+#endif
+
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp);
+ state = D6_STATE(ifp);
+ if (state == NULL)
+ return;
+
+#ifdef ND6_ADVERTISE
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ ipv6nd_advertise(ia);
+ }
+#endif
+
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startdiscover, ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startinform, ifp);
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendinform, ifp);
+
+ switch (state->state) {
+ case DH6S_DISCOVER: /* FALLTHROUGH */
+ case DH6S_REQUEST: /* FALLTHROUGH */
+ case DH6S_INFORM:
+ state->state = DH6S_INIT;
+ break;
+ default:
+ break;
+ }
+}
+
+void
+dhcp6_handleifa(int cmd, struct ipv6_addr *ia, pid_t pid)
+{
+ struct dhcp6_state *state;
+ struct interface *ifp = ia->iface;
+
+ /* If not running in manager mode, listen to this address */
+ if (cmd == RTM_NEWADDR &&
+ !(ia->addr_flags & IN6_IFF_NOTUSEABLE) &&
+ ifp->active == IF_ACTIVE_USER &&
+ !(ifp->ctx->options & DHCPCD_MANAGER) &&
+ ifp->options->options & DHCPCD_DHCP6)
+ {
+#ifdef PRIVSEP
+ if (IN_PRIVSEP_SE(ifp->ctx)) {
+ if (ps_inet_opendhcp6(ia) == -1)
+ logerr(__func__);
+ } else
+#endif
+ {
+ if (ia->dhcp6_fd == -1)
+ ia->dhcp6_fd = dhcp6_openudp(ia->iface->index,
+ &ia->addr);
+ if (ia->dhcp6_fd != -1)
+ eloop_event_add(ia->iface->ctx->eloop,
+ ia->dhcp6_fd, dhcp6_recvaddr, ia);
+ }
+ }
+
+
+ if ((state = D6_STATE(ifp)) != NULL)
+ ipv6_handleifa_addrs(cmd, &state->addrs, ia, pid);
+}
+
+ssize_t
+dhcp6_env(FILE *fp, 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 uint8_t *p;
+ struct dhcp6_option o;
+ size_t i;
+ char *pfx;
+ uint32_t en;
+ const struct dhcpcd_ctx *ctx;
+#ifndef SMALL
+ const struct dhcp6_state *state;
+ const struct ipv6_addr *ap;
+#endif
+
+ 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 */
+ 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);
+ if (asprintf(&pfx, "%s_dhcp6", prefix) == -1)
+ return -1;
+
+ /* Unlike DHCP, DHCPv6 options *may* occur more than once.
+ * There is also no provision for option concatenation unlike DHCP. */
+ p = (const uint8_t *)m + sizeof(*m);
+ len -= sizeof(*m);
+ for (; len != 0; p += o.len, len -= o.len) {
+ if (len < sizeof(o)) {
+ errno = EINVAL;
+ break;
+ }
+ memcpy(&o, p, sizeof(o));
+ p += sizeof(o);
+ len -= sizeof(o);
+ o.len = ntohs(o.len);
+ if (len < o.len) {
+ errno = EINVAL;
+ break;
+ }
+ o.code = ntohs(o.code);
+ if (has_option_mask(ifo->nomask6, o.code))
+ continue;
+ for (i = 0, opt = ifo->dhcp6_override;
+ i < ifo->dhcp6_override_len;
+ i++, opt++)
+ if (opt->option == o.code)
+ break;
+ if (i == ifo->dhcp6_override_len &&
+ o.code == D6_OPTION_VENDOR_OPTS &&
+ o.len > sizeof(en))
+ {
+ memcpy(&en, p, 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 == o.code)
+ break;
+ if (i == ctx->dhcp6_opts_len)
+ opt = NULL;
+ }
+ if (opt) {
+ dhcp_envoption(ifp->ctx,
+ fp, pfx, ifp->name,
+ opt, dhcp6_getoption, p, o.len);
+ }
+ if (vo) {
+ dhcp_envoption(ifp->ctx,
+ fp, pfx, ifp->name,
+ vo, dhcp6_getoption,
+ p + sizeof(en),
+ o.len - sizeof(en));
+ }
+ }
+ free(pfx);
+
+delegated:
+#ifndef SMALL
+ /* Needed for Delegated Prefixes */
+ state = D6_CSTATE(ifp);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->delegating_prefix)
+ break;
+ }
+ if (ap == NULL)
+ return 1;
+ if (fprintf(fp, "%s_delegated_dhcp6_prefix=", prefix) == -1)
+ return -1;
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ap->delegating_prefix == NULL)
+ continue;
+ if (ap != TAILQ_FIRST(&state->addrs)) {
+ if (fputc(' ', fp) == EOF)
+ return -1;
+ }
+ if (fprintf(fp, "%s", ap->saddr) == -1)
+ return -1;
+ }
+ if (fputc('\0', fp) == EOF)
+ return -1;
+#endif
+
+ return 1;
+}
+#endif
+
+#ifndef SMALL
+int
+dhcp6_dump(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+
+ ifp->if_data[IF_DATA_DHCP6] = state = calloc(1, sizeof(*state));
+ if (state == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ TAILQ_INIT(&state->addrs);
+ if (dhcp6_readlease(ifp, 0) == -1) {
+ logerr("dhcp6_readlease");
+ return -1;
+ }
+ state->reason = "DUMP6";
+ return script_runreason(ifp, state->reason);
+}
+#endif
diff --git a/src/dhcp6.h b/src/dhcp6.h
new file mode 100644
index 000000000000..0b257fab6c8d
--- /dev/null
+++ b/src/dhcp6.h
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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
+
+#ifdef DHCP6
+
+#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_USER_CLASS 15
+#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_OPTION_MUDURL 112
+
+#define D6_FQDN_PTR 0x00
+#define D6_FQDN_BOTH 0x01
+#define D6_FQDN_NONE 0x04
+
+#include "dhcp.h"
+#include "ipv6.h"
+
+#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 SOL_MAX_RC 0
+#define REQ_MAX_DELAY 0
+#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_RC 0
+#define CNF_MAX_RD 10
+#define REN_MAX_DELAY 0
+#define REN_TIMEOUT 10
+#define REN_MAX_RT 600
+#define REB_MAX_DELAY 0
+#define REB_TIMEOUT 10
+#define REB_MAX_RT 600
+#define INF_MAX_DELAY 1
+#define INF_TIMEOUT 1
+#define INF_MAX_RD CNF_MAX_RD /* NOT RFC defined */
+#define INF_MAX_RT 3600 /* RFC7083 */
+#define REL_MAX_DELAY 0
+#define REL_TIMEOUT 1
+#define REL_MAX_RT 0
+#define REL_MAX_RC 5
+#define DEC_MAX_DELAY 0
+#define DEC_TIMEOUT 1
+#define DEC_MAX_RC 5
+#define REC_MAX_DELAY 0
+#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
+
+/* These should give -.1 to .1 randomness */
+#define DHCP6_RAND_MIN -100
+#define DHCP6_RAND_MAX 100
+#define DHCP6_RAND_DIV 1000.0f
+
+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_DECLINE,
+ DH6S_DELEGATED,
+ DH6S_RELEASE,
+ DH6S_RELEASED,
+};
+
+struct dhcp6_state {
+ enum DH6S state;
+ struct timespec started;
+
+ /* Message retransmission timings in seconds */
+ unsigned int IMD;
+ unsigned int RTC;
+ unsigned int IRT;
+ unsigned int MRC;
+ unsigned int MRT;
+ void (*MRCcallback)(void *);
+ unsigned int sol_max_rt;
+ unsigned int inf_max_rt;
+ unsigned int RT; /* retransmission timer in milliseconds
+ * maximal RT is 1 day + RAND,
+ * so should be enough */
+
+ 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;
+
+ struct timespec acquired;
+ 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_SSIDLEN * 4) +3];
+ const char *reason;
+ uint16_t lerror; /* Last error received from DHCPv6 reply. */
+ bool has_no_binding;
+ bool failed; /* Entered the failed state - used to rate limit log. */
+ bool new_start; /* New external start, to determine log type. */
+#ifdef AUTH
+ struct authstate auth;
+#endif
+};
+
+#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)))
+
+int dhcp6_openraw(void);
+int dhcp6_openudp(unsigned int, struct in6_addr *);
+void dhcp6_recvmsg(struct dhcpcd_ctx *, struct msghdr *, struct ipv6_addr *);
+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, unsigned int flags);
+struct ipv6_addr *dhcp6_findaddr(struct dhcpcd_ctx *, const struct in6_addr *,
+ unsigned int);
+size_t dhcp6_find_delegates(struct interface *);
+int dhcp6_start(struct interface *, enum DH6S);
+void dhcp6_reboot(struct interface *);
+void dhcp6_renew(struct interface *);
+ssize_t dhcp6_env(FILE *, const char *, const struct interface *,
+ const struct dhcp6_message *, size_t);
+void dhcp6_free(struct interface *);
+void dhcp6_handleifa(int, struct ipv6_addr *, pid_t);
+bool dhcp6_dadcompleted(const struct interface *);
+void dhcp6_abort(struct interface *);
+void dhcp6_drop(struct interface *, const char *);
+int dhcp6_dump(struct interface *);
+#endif /* DHCP6 */
+
+#endif /* DHCP6_H */
diff --git a/src/dhcpcd-definitions-small.conf b/src/dhcpcd-definitions-small.conf
new file mode 100644
index 000000000000..7f5b8185cbea
--- /dev/null
+++ b/src/dhcpcd-definitions-small.conf
@@ -0,0 +1,127 @@
+# Copyright (c) 2006-2021 Roy Marples <roy@marples.name>
+# All rights reserved
+
+# Bare essentials for automatic IP configuration
+
+##############################################################################
+# 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
+define 3 request array ipaddress routers
+define 6 array ipaddress domain_name_servers
+define 12 dname host_name
+define 15 array dname domain_name
+define 26 uint16 interface_mtu
+define 28 request ipaddress broadcast_address
+define 33 request array ipaddress static_routes
+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
+
+# 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 optional domain fqdn
+
+# DHCP Domain Search, RFC3397
+define 119 array domain domain_search
+
+# 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
+
+##############################################################################
+# ND6 options, RFC4861
+definend 1 binhex source_address
+definend 2 binhex target_address
+
+definend 3 index embed prefix_information
+embed byte length
+embed bitflags=LAH 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 12 ip6address unicast
+
+define6 13 norequest embed status_code
+embed uint16 status_code
+embed optional string message
+
+define6 18 binhex interface_id
+define6 19 byte reconfigure_msg
+define6 20 flag reconfigure_accept
+
+# DHCPv6 DNS Configuration Options, RFC3646
+define6 23 array ip6address name_servers
+define6 24 array domain domain_search
+
+# DHCPv6 Fully Qualified Domain Name, RFC4704
+define6 39 embed fqdn
+embed bitflags=00000NOS flags
+embed optional domain fqdn
+
+# DHCPv6 SOL_MAX_RT, RFC7083
+define6 82 request uint32 sol_max_rt
+define6 83 request uint32 inf_max_rt
diff --git a/src/dhcpcd-definitions.conf b/src/dhcpcd-definitions.conf
new file mode 100644
index 000000000000..43e914097923
--- /dev/null
+++ b/src/dhcpcd-definitions.conf
@@ -0,0 +1,651 @@
+# Copyright (c) 2006-2021 Roy Marples <roy@marples.name>
+# 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
+define 2 uint32 time_offset
+define 3 request array ipaddress routers
+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 uint16 max_dgram_reassembly
+define 23 byte 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 33 request array ipaddress static_routes
+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 optional domain fqdn
+
+# Option 82 is for Relay Agents and DHCP servers
+
+# iSNS, RFC4174
+define 83 embed isns
+embed byte reserved1
+embed bitflags=00000SAE functions
+embed byte reserved2
+embed bitflags=00fFsSCE dd
+embed byte reserved3
+embed bitflags=0000DMHE admin
+embed uint16 reserved4
+embed byte reserved5
+embed bitflags=0TXPAMSE server_security
+embed array ipaddress servers
+
+# Option 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 IPv6-Only Preferred, RFC8925
+define 108 uint32 ipv6_only_preferred
+
+# 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
+
+# DHCP Bulk Lease Query, RFC6926
+# dhcpcd doesn't perform a lease query, but if it did these
+# fields might be of use
+#define 151 embed blklqry
+#embed byte status_code
+#embed string status_msg
+
+#define 152 uint32 blklqry_base_time
+#define 153 uint32 blklqry_state_start_time
+#define 154 uint32 blklqry_start_time
+#define 155 uint32 blklqry_end_time
+#define 156 byte blklqry_state
+#define 157 bitflags=0000000R blklqry_source
+
+# DHCP MUD URL, draft-ietf-opsawg-mud
+define 161 string mudurl
+
+# Apart from 161...
+# 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 embed sixrd
+embed byte mask_len
+embed byte prefix_len
+embed ip6address prefix
+embed array ipaddress brip_address
+
+# 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
+
+# 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
+
+# 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=LAH 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 Mobile IP, RFC6275
+definend 8 embed homeagent_information
+embed uint16 reserved
+embed uint16 preference
+embed uint16 lifetime
+
+# 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 optional 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 optional 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 domain 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 Softwire Address and Port-Mapped Clients, RFC7598
+define6 89 embed s46_rule
+embed bitflags=0000000F flags
+embed byte ea_len
+embed byte prefix4_len
+embed ipaddress ipv4_prefix
+embed ip6address ipv6_prefix
+define6 90 ip6address s64_br
+define6 91 embed s46_dmr
+embed byte prefix_len
+embed binhex prefix
+define6 92 embed s46_v4v6bind
+embed ipaddress ipv4_address
+embed byte ipv6_prefix_len
+embed binhex ipv6_prefix_and_options
+# Cannot decode options after variable length address ...
+#encap 93 option
+define6 93 embed s46_portparams
+embed byte offset
+embed byte psid_len
+embed uint16 psid
+define6 94 embed s46_cont_mape
+encap 89 option
+encap 90 option
+define6 95 embed s46_cont_mapt
+encap 89 option
+encap 91 option
+define6 96 embed s46_cont_lw
+encap 90 option
+encap 92 option
+
+# DHCPv6 Address Selection Policy
+# Currently not supported
+
+# DHCPv6 MUD URL, draft-ietf-opsawg-mud
+define6 112 string mudurl
+
+# 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
diff --git a/src/dhcpcd-embedded.c.in b/src/dhcpcd-embedded.c.in
new file mode 100644
index 000000000000..5ab167055b8b
--- /dev/null
+++ b/src/dhcpcd-embedded.c.in
@@ -0,0 +1,36 @@
+/*
+ * 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-2021 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 dhcpcd_embedded_conf[] =
diff --git a/src/dhcpcd-embedded.h.in b/src/dhcpcd-embedded.h.in
new file mode 100644
index 000000000000..af9122d02429
--- /dev/null
+++ b/src/dhcpcd-embedded.h.in
@@ -0,0 +1,38 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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.
+ */
+
+#ifdef SMALL
+#define INITDEFINES @INITDEFINES_SMALL@
+#define INITDEFINENDS @INITDEFINENDS_SMALL@
+#define INITDEFINE6S @INITDEFINE6S_SMALL@
+#else
+#define INITDEFINES @INITDEFINES@
+#define INITDEFINENDS @INITDEFINENDS@
+#define INITDEFINE6S @INITDEFINE6S@
+#endif
+
+extern const char dhcpcd_embedded_conf[];
diff --git a/src/dhcpcd.8.in b/src/dhcpcd.8.in
new file mode 100644
index 000000000000..bc6b3b57d49a
--- /dev/null
+++ b/src/dhcpcd.8.in
@@ -0,0 +1,885 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2006-2021 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 23, 2021
+.Dt DHCPCD 8
+.Os
+.Sh NAME
+.Nm dhcpcd
+.Nd a DHCP client
+.Sh SYNOPSIS
+.Nm
+.Op Fl 146ABbDdEGgHJKLMNPpqTV
+.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 Ns Op Ar /broadcast_address
+.Op Fl Fl inform6
+.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 Ns = Ns 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 Fl Fl inactive
+.Op Fl Fl configure
+.Op Fl Fl noconfigure
+.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
+.Op 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 begins to expire or the DHCP server sends a message
+to renew early.
+.Pp
+If any interface reports a working carrier then
+.Nm
+will try to 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 implementation 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
+not limited to one interface on the command line,
+it is running in Manager mode.
+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 to other
+.Nm
+processes.
+.Fl w , Fl Fl waitip
+option is enabled in this instance to maintain compatibility with older
+versions.
+Using a single interface also affects the
+.Fl k ,
+.Fl N ,
+.Fl n
+and
+.Fl x
+options, where the same interface will need to be specified, as a lack of an
+interface will imply Manager mode which this is not.
+To force starting in Manager mode with only one interface, the
+.Fl M , Fl Fl manager
+option can be used.
+.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.
+.Pp
+Non-ethernet interfaces and some virtual ethernet interfaces
+such as TAP and bridge are ignored by default,
+as is the FireWire interface.
+To work with these devices they either need to be specified on the command line,
+be listed in
+.Fl Fl allowinterfaces
+or have an interface directive in
+.Pa @SYSCONFDIR@/dhcpcd.conf .
+.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 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.
+.Pp
+More scripts are supplied in
+.Pa @DATADIR@/dhcpcd/hooks
+and need to be copied to
+.Pa @HOOKDIR@
+if you intend to use them.
+For example, you could install
+.Pa 29-lookup-hostname
+so that
+.Nm
+can lookup the hostname of the IP address in DNS if no hostname
+is given by the lease and one is not already set.
+.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 Op Ar ll | lt | uuid | value
+Use a DHCP Unique Identifier.
+If a system UUID is available, that will be used to create a DUID-UUID,
+otheriwse if persistent storage is available then a DUID-LLT
+(link local address + time) is generated,
+otherwise DUID-LL is generated (link local address).
+The DUID type can be hinted as an optional parameter if the file
+.Pa @DBDIR@/duid
+does not exist.
+If not
+.Va ll ,
+.Va lt
+or
+.Va uuid
+then
+.Va value
+will be converted from 00:11:22:33 format.
+This, plus the IAID will be used as the
+.Fl I , Fl Fl clientid .
+The DUID generated will be held in
+.Pa @DBDIR@/duid
+and should not be copied to other hosts.
+This file also takes precedence over the above rules except for setting a value.
+.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.
+.It Fl Fl lastleaseextend
+Same as the above, but the lease will be retained even if it expires.
+.Nm
+will give it up if any other host tries to claim it for their own via ARP.
+This violates RFC 2131, section 3.7, which states the lease should be
+dropped once it has 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 RFC 1035 .
+.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 (i.e., 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 .
+.Nm
+still writes to
+.Xr syslog 3 .
+The
+.Ar logfile
+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 in Manager mode.
+If no interfaces are left running,
+.Nm
+will exit.
+.It Fl l , Fl Fl leasetime Ar seconds
+Request a lease time of
+.Ar seconds .
+.Ar -1
+represents an infinite lease time.
+By default
+.Nm
+does not request any lease time and leaves it in the hands of the
+DHCP server.
+.It Fl M , Fl Fl manager
+Start
+.Nm
+in Manager 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 metric of 1000 +
+.Xr if_nametoindex 3 .
+This will be offset by 2000 for wireless interfaces, with additional offsets
+of 1000000 for IPv4LL and 2000000 for roaming interfaces.
+.It Fl n , Fl Fl rebind Op Ar interface
+Notifies
+.Nm
+to reload its configuration and rebind the specified
+.Ar interface .
+If no
+.Ar interface
+is specified then this applies to all interfaces in Manager mode.
+If
+.Nm
+is not running, then it starts up as normal.
+.It Fl N , Fl Fl renew Op Ar interface
+Notifies
+.Nm
+to renew existing addresses on the specified
+.Ar interface .
+If no
+.Ar interface
+is specified then this applies to all interfaces in Manager mode.
+If
+.Nm
+is not running, then it starts up as normal.
+Unlike the
+.Fl n , Fl Fl rebind
+option above, the configuration for
+.Nm
+is not reloaded.
+.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 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 Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address
+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 Fl inform6
+Performs a DHCPv6 Information Request.
+No address is requested or specified, but all other DHCPv6 options are allowed.
+This is normally performed automatically when the IPv6 Router Advertises
+that the client should perform this operation.
+This option is only needed when
+.Nm
+is not processing IPv6RA messages and the need for DHCPv6 Information Request
+exists.
+.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.
+.It Fl Fl waitip Ns = Ns 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
+.Ar interface
+is specified, then the above is applied to all interfaces in Manager mode.
+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 DHCP server wants.
+Here are some options that deal with turning these bits off.
+.Pp
+Note that when
+.Nm
+is restricted to a single interface then the interface also needs to be
+specified when asking
+.Nm
+to exit using the commandline.
+If the protocol is restricted as well then the protocol needs to be included
+with the exit instruction.
+.Bl -tag -width indent
+.It Fl 1 , Fl Fl oneshot
+Exit after configuring an interface.
+Use the
+.Fl w , Fl Fl waitip
+option to specify which protocol(s) to configure before exiting.
+.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
+Removes the
+.Ar option
+from the DHCP message before processing.
+.It Fl P , Fl Fl printpidfile
+Print the
+.Pa pidfile
+.Nm
+will use based on commmand-line arguments to stdout.
+.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.
+If this option is used another time then all console output is disabled.
+These messages are still logged via
+.Xr syslog 3 .
+.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 Op Ar interface
+Dumps the current lease for the
+.Ar interface
+to stdout.
+If no
+.Ar interface
+is given then all interfaces are dumped.
+Use the
+.Fl 4
+or
+.Fl 6
+flags to specify an address family.
+If a lease is piped in via standard input then that is dumped.
+In this case, specifying an address family is mandatory.
+.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 inactive
+Don't start any interfaces other than those specified on the command line.
+This allows
+.Nm
+to be started in Manager mode and then wait for subsequent
+.Nm
+commands to start each interface as required.
+.It Fl Fl configure
+Allows
+.Nm
+to configure the system.
+This is the default behaviour and sets
+.Ev if_configured=true .
+.It Fl Fl noconfigure
+.Nm
+will not configure the system at all.
+This is only of use if the
+.Fl Fl script
+that
+.Nm
+calls at each network event configures the system instead.
+This is different from
+.Fl T , Fl Fl test
+mode in that it's not one shot and the only change to the environment is the
+addition of
+.Ev if_configured=false .
+.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 further calls to
+.Nm
+to rebind, reconfigure or exit need to include the same restrictive flags
+so that
+.Nm
+knows which process to signal.
+.Pp
+Some DHCP servers implement ClientID filtering.
+If
+.Nm
+is replacing an in-use DHCP client then you might need to adjust the clientid
+option
+.Nm
+sends to match.
+If using a DUID in place of the ClientID, edit
+.Pa @DBDIR@/duid
+accordingly.
+.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 @SCRIPT@
+Bourne shell script that is run to configure or de-configure an interface.
+.It Pa @LIBDIR@/dhcpcd/dev
+Linux
+.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@/duid
+Text file that holds the DUID used to identify the host.
+.It Pa @DBDIR@/secret
+Text file that holds a secret key known only to the host.
+.It Pa @DBDIR@/ 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 file's mtime as when it was issued.
+.It Pa @DBDIR@/ 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 file's mtime as when it was issued.
+.It Pa @DBDIR@/rdm_monotonic
+Stores the monotonic counter used in the
+.Ar replay
+field in Authentication Options.
+.It Pa @RUNDIR@/pid
+Stores the PID of
+.Nm
+running on all interfaces.
+.It Pa @RUNDIR@/ Ns Ar interface Ns .pid
+Stores the PID of
+.Nm
+running on the
+.Ar interface .
+.It Pa @RUNDIR@/sock
+Control socket to the manager daemon.
+.It Pa @RUNDIR@/unpriv.sock
+Unprivileged socket to the manager daemon, only allows state retrieval.
+.It Pa @RUNDIR@/ Ns Ar interface Ns .sock
+Control socket to per interface daemon.
+.It Pa @RUNDIR@/ Ns Ar interface Ns .unpriv.sock
+Unprivileged socket to per interface daemon, only allows state retrieval.
+.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\ 6355,
+RFC\ 6603, RFC\ 6704, RFC\ 7217, RFC\ 7550, RFC\ 7844.
+.Sh AUTHORS
+.An Roy Marples Aq Mt roy@marples.name
+.Sh BUGS
+Please report them to
+.Lk http://roy.marples.name/projects/dhcpcd
diff --git a/src/dhcpcd.c b/src/dhcpcd.c
new file mode 100644
index 000000000000..6a4c9723742b
--- /dev/null
+++ b/src/dhcpcd.c
@@ -0,0 +1,2639 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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.
+ */
+
+static const char dhcpcd_copyright[] = "Copyright (c) 2006-2021 Roy Marples";
+
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.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 <syslog.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "config.h"
+#include "arp.h"
+#include "common.h"
+#include "control.h"
+#include "dev.h"
+#include "dhcp-common.h"
+#include "dhcpcd.h"
+#include "dhcp.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 "logerr.h"
+#include "privsep.h"
+#include "script.h"
+
+#ifdef HAVE_CAPSICUM
+#include <sys/capsicum.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+#ifdef USE_SIGNALS
+const int dhcpcd_signals[] = {
+ SIGTERM,
+ SIGINT,
+ SIGALRM,
+ SIGHUP,
+ SIGUSR1,
+ SIGUSR2,
+ SIGCHLD,
+};
+const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals);
+
+const int dhcpcd_signals_ignore[] = {
+ SIGPIPE,
+};
+const size_t dhcpcd_signals_ignore_len = __arraycount(dhcpcd_signals_ignore);
+#endif
+
+const char *dhcpcd_default_script = SCRIPT;
+
+static void
+usage(void)
+{
+
+printf("usage: "PACKAGE"\t[-146ABbDdEGgHJKLMNPpqTV]\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] [-j, --logfile logfile]\n"
+ "\t\t[-l, --leasetime seconds] [-m, --metric metric]\n"
+ "\t\t[-O, --nooption option] [-o, --option option]\n"
+ "\t\t[-Q, --require option] [-r, --request address]\n"
+ "\t\t[-S, --static value]\n"
+ "\t\t[-s, --inform address[/cidr[/broadcast_address]]]\n [--inform6]"
+ "\t\t[-t, --timeout seconds] [-u, --userclass class]\n"
+ "\t\t[-v, --vendor code, value] [-W, --whitelist address[/cidr]] [-w]\n"
+ "\t\t[--waitip [4 | 6]] [-y, --reboot seconds]\n"
+ "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n"
+ "\t\t[-z, --allowinterfaces pattern] [--inactive] [interface] [...]\n"
+ " "PACKAGE"\t-n, --rebind [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;
+ }
+#ifdef DHCP6
+ 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
+#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;
+ logerrx("timed out");
+ if (!(ctx->options & DHCPCD_MANAGER)) {
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (ifp->active == IF_ACTIVE_USER)
+ script_runreason(ifp, "STOPPED");
+ }
+ 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;
+ bool foundany = false;
+
+ if (ifp->active != IF_ACTIVE_USER)
+ return AF_MAX;
+
+#define DHCPCD_WAITALL (DHCPCD_WAITIP4 | DHCPCD_WAITIP6)
+ opts = ifp->options->options;
+#ifdef INET
+ if (opts & DHCPCD_WAITIP4 ||
+ (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL)))
+ {
+ bool foundaddr = ipv4_hasaddr(ifp);
+
+ if (opts & DHCPCD_WAITIP4 && !foundaddr)
+ return AF_INET;
+ if (foundaddr)
+ foundany = true;
+ }
+#endif
+#ifdef INET6
+ if (opts & DHCPCD_WAITIP6 ||
+ (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL)))
+ {
+ bool foundaddr = ipv6_hasaddr(ifp);
+
+ if (opts & DHCPCD_WAITIP6 && !foundaddr)
+ return AF_INET;
+ if (foundaddr)
+ foundany = true;
+ }
+#endif
+
+ if (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL) && !foundany)
+ 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) {
+#ifdef INET
+ if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP4) &&
+ ipv4_hasaddr(ifp))
+ opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP4);
+#endif
+#ifdef INET6
+ if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP6) &&
+ ipv6_hasaddr(ifp))
+ opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP6);
+#endif
+ 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) {
+ logdebugx("%s: waiting for an %s address",
+ ifp->name, dhcpcd_af(af));
+ return 0;
+ }
+ }
+
+ if ((af = dhcpcd_afwaiting(ctx)) != AF_MAX) {
+ logdebugx("waiting for an %s address",
+ dhcpcd_af(af));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Returns the pid of the child, otherwise 0. */
+void
+dhcpcd_daemonise(struct dhcpcd_ctx *ctx)
+{
+#ifdef THERE_IS_NO_FORK
+ eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx);
+ errno = ENOSYS;
+ return;
+#else
+ int i;
+ unsigned int logopts = loggetopts();
+
+ if (ctx->options & DHCPCD_DAEMONISE &&
+ !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP)))
+ {
+ if (!dhcpcd_ipwaited(ctx))
+ return;
+ }
+
+ if (ctx->options & DHCPCD_ONESHOT) {
+ loginfox("exiting due to oneshot");
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+
+ eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx);
+ if (ctx->options & DHCPCD_DAEMONISED ||
+ !(ctx->options & DHCPCD_DAEMONISE))
+ return;
+
+ /* Don't use loginfo because this makes no sense in a log. */
+ if (!(logopts & LOGERR_QUIET) && ctx->stderr_valid)
+ (void)fprintf(stderr,
+ "forked to background, child pid %d\n", getpid());
+ i = EXIT_SUCCESS;
+ if (write(ctx->fork_fd, &i, sizeof(i)) == -1)
+ logerr("write");
+ ctx->options |= DHCPCD_DAEMONISED;
+ eloop_event_delete(ctx->eloop, ctx->fork_fd);
+ close(ctx->fork_fd);
+ ctx->fork_fd = -1;
+
+ /*
+ * Stop writing to stderr.
+ * On the happy path, only the manager process writes to stderr,
+ * so this just stops wasting fprintf calls to nowhere.
+ * All other calls - ie errors in privsep processes or script output,
+ * will error when printing.
+ * If we *really* want to fix that, then we need to suck
+ * stderr/stdout in the manager process and either disacrd it or pass
+ * it to the launcher process and then to stderr.
+ */
+ logopts &= ~LOGERR_ERR;
+ logsetopts(logopts);
+#endif
+}
+
+static void
+dhcpcd_drop(struct interface *ifp, int stop)
+{
+
+#ifdef DHCP6
+ dhcp6_drop(ifp, stop ? NULL : "EXPIRE6");
+#endif
+#ifdef INET6
+ ipv6nd_drop(ifp);
+ ipv6_drop(ifp);
+#endif
+#ifdef IPV4LL
+ ipv4ll_drop(ifp);
+#endif
+#ifdef INET
+ dhcp_drop(ifp, stop ? "STOP" : "EXPIRE");
+#endif
+#ifdef ARP
+ arp_drop(ifp);
+#endif
+#if !defined(DHCP6) && !defined(DHCP)
+ UNUSED(stop);
+#endif
+}
+
+static void
+stop_interface(struct interface *ifp, const char *reason)
+{
+ struct dhcpcd_ctx *ctx;
+
+ ctx = ifp->ctx;
+ loginfox("%s: removing interface", ifp->name);
+ ifp->options->options |= DHCPCD_STOPPING;
+
+ dhcpcd_drop(ifp, 1);
+ script_runreason(ifp, reason == NULL ? "STOPPED" : reason);
+
+ /* Delete all timeouts for the interfaces */
+ eloop_q_timeout_delete(ctx->eloop, ELOOP_QUEUE_ALL, NULL, ifp);
+
+ /* De-activate the interface */
+ ifp->active = IF_INACTIVE;
+ ifp->options->options &= ~DHCPCD_STOPPING;
+
+ if (!(ctx->options & (DHCPCD_MANAGER | DHCPCD_TEST)))
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+}
+
+static void
+configure_interface1(struct interface *ifp)
+{
+ struct if_options *ifo = ifp->options;
+
+ /* 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 | IFF_LOOPBACK)) {
+ ifo->options &= ~DHCPCD_ARP;
+ if (!(ifp->flags & IFF_MULTICAST))
+ ifo->options &= ~DHCPCD_IPV6RS;
+ if (!(ifo->options & (DHCPCD_INFORM | DHCPCD_WANTDHCP)))
+ ifo->options |= DHCPCD_STATIC;
+ }
+
+ if (ifo->metric != -1)
+ ifp->metric = (unsigned int)ifo->metric;
+
+#ifdef INET6
+ /* We want to setup INET6 on the interface as soon as possible. */
+ if (ifp->active == IF_ACTIVE_USER &&
+ ifo->options & DHCPCD_IPV6 &&
+ !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST)))
+ {
+ /* If not doing any DHCP, disable the RDNSS requirement. */
+ if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6)))
+ ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS;
+ if_setup_inet6(ifp);
+ }
+#endif
+
+ 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.
+ * Saying that, if a VLANID has been specified then we
+ * can use that. It's possible that different interfaces
+ * can have the same VLANID, but this is no worse than
+ * generating the IAID from the duplicate MAC address.
+ *
+ * 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->vlanid != 0) {
+ uint32_t vlanid;
+
+ /* Maximal VLANID is 4095, so prefix with 0xff
+ * so we don't conflict with an interface index. */
+ vlanid = htonl(ifp->vlanid | 0xff000000);
+ memcpy(ifo->iaid, &vlanid, sizeof(vlanid));
+ } else if (ifo->options & DHCPCD_ANONYMOUS)
+ memset(ifo->iaid, 0, sizeof(ifo->iaid));
+ else 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(ifo->iaid));
+ }
+ }
+ ifo->options |= DHCPCD_IAID;
+ }
+
+#ifdef DHCP6
+ if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 &&
+ ifp->name[0] != '\0')
+ {
+ ifo->ia = malloc(sizeof(*ifo->ia));
+ if (ifo->ia == NULL)
+ logerr(__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));
+#ifndef SMALL
+ ifo->ia->sla = NULL;
+ ifo->ia->sla_len = 0;
+#endif
+ }
+ } else {
+ size_t i;
+
+ 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
+
+ /* If root is network mounted, we don't want to kill the connection
+ * if the DHCP server goes the way of the dodo OR dhcpcd is rebooting
+ * and the lease file has expired. */
+ if (is_root_local() == 0)
+ ifo->options |= DHCPCD_LASTLEASE_EXTEND;
+}
+
+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), OT_ESCSTRING,
+ ifp->ssid, ifp->ssid_len);
+ if (r == -1) {
+ logerr(__func__);
+ pssid[0] = '\0';
+ }
+ } else
+ pssid[0] = '\0';
+ ifo = read_config(ifp->ctx, ifp->name, pssid, profile);
+ if (ifo == NULL) {
+ logdebugx("%s: no profile %s", ifp->name, profile);
+ return -1;
+ }
+ if (profile != NULL) {
+ strlcpy(ifp->profile, profile, sizeof(ifp->profile));
+ loginfox("%s: selected profile %s", ifp->name, profile);
+ } else
+ *ifp->profile = '\0';
+
+ free_options(ifp->ctx, ifp->options);
+ ifp->options = ifo;
+ if (profile) {
+ add_options(ifp->ctx, ifp->name, ifp->options,
+ ifp->ctx->argc, ifp->ctx->argv);
+ 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);
+ if (ifp->options == NULL) {
+ /* dhcpcd cannot continue with this interface. */
+ ifp->active = IF_INACTIVE;
+ return;
+ }
+ 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 (old != 0 && ifp->options->mtime != old) {
+ logwarnx("%s: config file changed, expiring leases",
+ ifp->name);
+ dhcpcd_drop(ifp, 0);
+ }
+}
+
+static void
+dhcpcd_initstate2(struct interface *ifp, unsigned long long options)
+{
+ struct if_options *ifo;
+
+ if (options) {
+ if ((ifo = default_config(ifp->ctx)) == NULL) {
+ logerr(__func__);
+ return;
+ }
+ ifo->options |= options;
+ free(ifp->options);
+ ifp->options = ifo;
+ } else
+ ifo = ifp->options;
+
+#ifdef INET6
+ if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == -1) {
+ logerr(__func__);
+ ifo->options &= ~DHCPCD_IPV6;
+ }
+#endif
+}
+
+static void
+dhcpcd_initstate1(struct interface *ifp, int argc, char **argv,
+ unsigned long long options)
+{
+
+ configure_interface(ifp, argc, argv, options);
+ if (ifp->active)
+ dhcpcd_initstate2(ifp, 0);
+}
+
+static void
+dhcpcd_initstate(struct interface *ifp, unsigned long long options)
+{
+
+ dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options);
+}
+
+static void
+dhcpcd_reportssid(struct interface *ifp)
+{
+ char pssid[IF_SSIDLEN * 4];
+
+ if (print_string(pssid, sizeof(pssid), OT_ESCSTRING,
+ ifp->ssid, ifp->ssid_len) == -1)
+ {
+ logerr(__func__);
+ return;
+ }
+
+ loginfox("%s: connected to Access Point: %s", ifp->name, pssid);
+}
+
+static void
+dhcpcd_nocarrier_roaming(struct interface *ifp)
+{
+
+ loginfox("%s: carrier lost - roaming", ifp->name);
+
+#ifdef ARP
+ arp_drop(ifp);
+#endif
+#ifdef INET
+ dhcp_abort(ifp);
+#endif
+#ifdef DHCP6
+ dhcp6_abort(ifp);
+#endif
+
+ rt_build(ifp->ctx, AF_UNSPEC);
+ script_runreason(ifp, "NOCARRIER_ROAMING");
+}
+
+void
+dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags)
+{
+ bool was_link_up = if_is_link_up(ifp);
+ bool was_roaming = if_roaming(ifp);
+
+ ifp->carrier = carrier;
+ ifp->flags = flags;
+
+ if (!if_is_link_up(ifp)) {
+ if (!ifp->active || (!was_link_up && !was_roaming))
+ return;
+
+ /*
+ * If the interface is roaming (generally on wireless)
+ * then while we are not up, we are not down either.
+ * Preserve the network state until we either disconnect
+ * or re-connect.
+ */
+ if (!ifp->options->randomise_hwaddr && if_roaming(ifp)) {
+ dhcpcd_nocarrier_roaming(ifp);
+ return;
+ }
+
+ loginfox("%s: carrier lost", ifp->name);
+ script_runreason(ifp, "NOCARRIER");
+ dhcpcd_drop(ifp, 0);
+
+ if (ifp->options->randomise_hwaddr) {
+ bool is_up = ifp->flags & IFF_UP;
+
+ if (is_up)
+ if_down(ifp);
+ if (if_randomisemac(ifp) == -1 && errno != ENXIO)
+ logerr(__func__);
+ if (is_up)
+ if_up(ifp);
+ }
+
+ return;
+ }
+
+ /*
+ * At this point carrier is NOT DOWN and we have IFF_UP.
+ * We should treat LINK_UNKNOWN as up as the driver may not support
+ * link state changes.
+ * The consideration of any other information about carrier should
+ * be handled in the OS specific if_carrier() function.
+ */
+ if (was_link_up)
+ return;
+
+ if (ifp->active) {
+ if (carrier == LINK_UNKNOWN)
+ loginfox("%s: carrier unknown, assuming up", ifp->name);
+ else
+ loginfox("%s: carrier acquired", ifp->name);
+ }
+
+#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(ifp->ctx, 0, ifp->name);
+#endif
+
+ if (ifp->wireless) {
+ uint8_t ossid[IF_SSIDLEN];
+ size_t olen;
+
+ olen = ifp->ssid_len;
+ memcpy(ossid, ifp->ssid, ifp->ssid_len);
+ if_getssid(ifp);
+
+ /* If we changed SSID network, drop leases */
+ if ((ifp->ssid_len != olen ||
+ memcmp(ifp->ssid, ossid, ifp->ssid_len)) && ifp->active)
+ {
+ dhcpcd_reportssid(ifp);
+ dhcpcd_drop(ifp, 0);
+#ifdef IPV4LL
+ ipv4ll_reset(ifp);
+#endif
+ }
+ }
+
+ if (!ifp->active)
+ return;
+
+ dhcpcd_initstate(ifp, 0);
+ script_runreason(ifp, "CARRIER");
+
+#ifdef INET6
+ /* Set any IPv6 Routers we remembered to expire faster than they
+ * would normally as we maybe on a new network. */
+ ipv6nd_startexpire(ifp);
+#ifdef IPV6_MANAGETEMPADDR
+ /* RFC4941 Section 3.5 */
+ ipv6_regentempaddrs(ifp);
+#endif
+#endif
+
+ dhcpcd_startinterface(ifp);
+}
+
+static void
+warn_iaid_conflict(struct interface *ifp, uint16_t ia_type, uint8_t *iaid)
+{
+ struct interface *ifn;
+#ifdef INET6
+ size_t i;
+ struct if_ia *ia;
+#endif
+
+ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+ if (ifn == ifp || !ifn->active)
+ continue;
+ if (ifn->options->options & DHCPCD_ANONYMOUS)
+ continue;
+ if (ia_type == 0 &&
+ memcmp(ifn->options->iaid, iaid,
+ sizeof(ifn->options->iaid)) == 0)
+ break;
+#ifdef INET6
+ for (i = 0; i < ifn->options->ia_len; i++) {
+ ia = &ifn->options->ia[i];
+ if (ia->ia_type == ia_type &&
+ memcmp(ia->iaid, iaid, sizeof(ia->iaid)) == 0)
+ break;
+ }
+#endif
+ }
+
+ /* This is only a problem if the interfaces are on the same network. */
+ if (ifn)
+ logerrx("%s: IAID conflicts with one assigned to %s",
+ ifp->name, ifn->name);
+}
+
+static void
+dhcpcd_initduid(struct dhcpcd_ctx *ctx, struct interface *ifp)
+{
+ char buf[DUID_LEN * 3];
+
+ if (ctx->duid != NULL) {
+ if (ifp == NULL)
+ goto log;
+ return;
+ }
+
+ duid_init(ctx, ifp);
+ if (ctx->duid == NULL)
+ return;
+
+log:
+ loginfox("DUID %s",
+ hwaddr_ntoa(ctx->duid, ctx->duid_len, buf, sizeof(buf)));
+}
+
+void
+dhcpcd_startinterface(void *arg)
+{
+ struct interface *ifp = arg;
+ struct if_options *ifo = ifp->options;
+
+ if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) {
+ loginfox("%s: waiting for carrier", ifp->name);
+ return;
+ }
+
+ if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) &&
+ !(ifo->options & DHCPCD_ANONYMOUS))
+ {
+ char buf[sizeof(ifo->iaid) * 3];
+#ifdef INET6
+ size_t i;
+ struct if_ia *ia;
+#endif
+
+ /* Try and init DUID from the interface hardware address */
+ dhcpcd_initduid(ifp->ctx, ifp);
+
+ /* Report IAIDs */
+ loginfox("%s: IAID %s", ifp->name,
+ hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid),
+ buf, sizeof(buf)));
+ warn_iaid_conflict(ifp, 0, ifo->iaid);
+
+#ifdef INET6
+ for (i = 0; i < ifo->ia_len; i++) {
+ ia = &ifo->ia[i];
+ if (memcmp(ifo->iaid, ia->iaid, sizeof(ifo->iaid))) {
+ loginfox("%s: IA type %u IAID %s",
+ ifp->name, ia->ia_type,
+ hwaddr_ntoa(ia->iaid, sizeof(ia->iaid),
+ buf, sizeof(buf)));
+ warn_iaid_conflict(ifp, ia->ia_type, ia->iaid);
+ }
+ }
+#endif
+ }
+
+#ifdef INET6
+ if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) {
+ logerr("%s: ipv6_start", ifp->name);
+ ifo->options &= ~DHCPCD_IPV6;
+ }
+
+ if (ifo->options & DHCPCD_IPV6) {
+ if (ifp->active == IF_ACTIVE_USER) {
+ ipv6_startstatic(ifp);
+
+ if (ifo->options & DHCPCD_IPV6RS)
+ ipv6nd_startrs(ifp);
+ }
+
+#ifdef DHCP6
+ /* DHCPv6 could be turned off, but the interface
+ * is still delegated to. */
+ if (ifp->active)
+ dhcp6_find_delegates(ifp);
+
+ if (ifo->options & DHCPCD_DHCP6) {
+ if (ifp->active == IF_ACTIVE_USER) {
+ enum DH6S d6_state;
+
+ if (ifo->options & DHCPCD_IA_FORCED)
+ d6_state = DH6S_INIT;
+ else if (ifo->options & DHCPCD_INFORM6)
+ d6_state = DH6S_INFORM;
+ else
+ d6_state = DH6S_CONFIRM;
+ if (dhcp6_start(ifp, d6_state) == -1)
+ logerr("%s: dhcp6_start", ifp->name);
+ }
+ }
+#endif
+ }
+#endif
+
+#ifdef INET
+ if (ifo->options & DHCPCD_IPV4 && ifp->active == IF_ACTIVE_USER) {
+ /* Ensure we have an IPv4 state before starting DHCP */
+ if (ipv4_getstate(ifp) != NULL)
+ dhcp_start(ifp);
+ }
+#endif
+}
+
+static void
+dhcpcd_prestartinterface(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ bool randmac_down;
+
+ if (ifp->carrier <= LINK_DOWN &&
+ ifp->options->randomise_hwaddr &&
+ ifp->flags & IFF_UP)
+ {
+ if_down(ifp);
+ randmac_down = true;
+ } else
+ randmac_down = false;
+
+ if ((!(ctx->options & DHCPCD_MANAGER) ||
+ ifp->options->options & DHCPCD_IF_UP || randmac_down) &&
+ !(ifp->flags & IFF_UP))
+ {
+ if (ifp->options->randomise_hwaddr &&
+ if_randomisemac(ifp) == -1)
+ logerr(__func__);
+ if (if_up(ifp) == -1)
+ logerr(__func__);
+ }
+
+ dhcpcd_startinterface(ifp);
+}
+
+static void
+run_preinit(struct interface *ifp)
+{
+
+ if (ifp->ctx->options & DHCPCD_TEST)
+ return;
+
+ script_runreason(ifp, "PREINIT");
+ if (ifp->wireless && if_is_link_up(ifp))
+ dhcpcd_reportssid(ifp);
+ if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN)
+ script_runreason(ifp,
+ ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER");
+}
+
+void
+dhcpcd_activateinterface(struct interface *ifp, unsigned long long options)
+{
+
+ if (ifp->active)
+ return;
+
+ ifp->active = IF_ACTIVE;
+ dhcpcd_initstate2(ifp, options);
+
+ /* It's possible we might not have been able to load
+ * a config. */
+ if (!ifp->active)
+ return;
+
+ configure_interface1(ifp);
+ run_preinit(ifp);
+ dhcpcd_prestartinterface(ifp);
+}
+
+int
+dhcpcd_handleinterface(void *arg, int action, const char *ifname)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ struct ifaddrs *ifaddrs;
+ struct if_head *ifs;
+ struct interface *ifp, *iff;
+ const char * const argv[] = { ifname };
+ int e;
+
+ if (action == -1) {
+ ifp = if_find(ctx->ifaces, ifname);
+ if (ifp == NULL) {
+ errno = ESRCH;
+ return -1;
+ }
+ if (ifp->active) {
+ logdebugx("%s: interface departed", ifp->name);
+ stop_interface(ifp, "DEPARTED");
+ }
+ TAILQ_REMOVE(ctx->ifaces, ifp, next);
+ if_free(ifp);
+ return 0;
+ }
+
+ ifs = if_discover(ctx, &ifaddrs, -1, UNCONST(argv));
+ if (ifs == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+
+ ifp = if_find(ifs, ifname);
+ if (ifp == NULL) {
+ /* This can happen if an interface is quickly added
+ * and then removed. */
+ errno = ENOENT;
+ e = -1;
+ goto out;
+ }
+ e = 1;
+
+ /* Check if we already have the interface */
+ iff = if_find(ctx->ifaces, ifp->name);
+
+ if (iff != NULL) {
+ if (iff->active)
+ logdebugx("%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 {
+ TAILQ_REMOVE(ifs, ifp, next);
+ TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
+ if (ifp->active) {
+ logdebugx("%s: interface added", ifp->name);
+ dhcpcd_initstate(ifp, 0);
+ run_preinit(ifp);
+ }
+ iff = ifp;
+ }
+
+ if (action > 0) {
+ if_learnaddrs(ctx, ifs, &ifaddrs);
+ if (iff->active)
+ dhcpcd_prestartinterface(iff);
+ }
+
+out:
+ /* Free our discovered list */
+ while ((ifp = TAILQ_FIRST(ifs))) {
+ TAILQ_REMOVE(ifs, ifp, next);
+ if_free(ifp);
+ }
+ free(ifs);
+
+ return e;
+}
+
+static void
+dhcpcd_handlelink(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (if_handlelink(ctx) == -1) {
+ if (errno == ENOBUFS || errno == ENOMEM) {
+ dhcpcd_linkoverflow(ctx);
+ return;
+ }
+ if (errno != ENOTSUP)
+ logerr(__func__);
+ }
+}
+
+static void
+dhcpcd_checkcarrier(void *arg)
+{
+ struct interface *ifp0 = arg, *ifp;
+
+ ifp = if_find(ifp0->ctx->ifaces, ifp0->name);
+ if (ifp == NULL || ifp->carrier == ifp0->carrier)
+ return;
+
+ dhcpcd_handlecarrier(ifp, ifp0->carrier, ifp0->flags);
+ if_free(ifp0);
+}
+
+#ifndef SMALL
+static void
+dhcpcd_setlinkrcvbuf(struct dhcpcd_ctx *ctx)
+{
+ socklen_t socklen;
+
+ if (ctx->link_rcvbuf == 0)
+ return;
+
+ logdebugx("setting route socket receive buffer size to %d bytes",
+ ctx->link_rcvbuf);
+
+ socklen = sizeof(ctx->link_rcvbuf);
+ if (setsockopt(ctx->link_fd, SOL_SOCKET,
+ SO_RCVBUF, &ctx->link_rcvbuf, socklen) == -1)
+ logerr(__func__);
+}
+#endif
+
+static void
+dhcpcd_runprestartinterface(void *arg)
+{
+ struct interface *ifp = arg;
+
+ run_preinit(ifp);
+ dhcpcd_prestartinterface(ifp);
+}
+
+void
+dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx)
+{
+ socklen_t socklen;
+ int rcvbuflen;
+ char buf[2048];
+ ssize_t rlen;
+ size_t rcnt;
+ struct if_head *ifaces;
+ struct ifaddrs *ifaddrs;
+ struct interface *ifp, *ifn, *ifp1;
+
+ socklen = sizeof(rcvbuflen);
+ if (getsockopt(ctx->link_fd, SOL_SOCKET,
+ SO_RCVBUF, &rcvbuflen, &socklen) == -1) {
+ logerr("%s: getsockopt", __func__);
+ rcvbuflen = 0;
+ }
+#ifdef __linux__
+ else
+ rcvbuflen /= 2;
+#endif
+
+ logerrx("route socket overflowed (rcvbuflen %d)"
+ " - learning interface state", rcvbuflen);
+
+ /* Drain the socket.
+ * We cannot open a new one due to privsep. */
+ rcnt = 0;
+ do {
+ rlen = read(ctx->link_fd, buf, sizeof(buf));
+ if (++rcnt % 1000 == 0)
+ logwarnx("drained %zu messages", rcnt);
+ } while (rlen != -1 || errno == ENOBUFS || errno == ENOMEM);
+ if (rcnt % 1000 != 0)
+ logwarnx("drained %zu messages", rcnt);
+
+ /* Work out the current interfaces. */
+ ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv);
+ if (ifaces == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ /* Punt departed interfaces */
+ TAILQ_FOREACH_SAFE(ifp, ctx->ifaces, next, ifn) {
+ if (if_find(ifaces, ifp->name) != NULL)
+ continue;
+ dhcpcd_handleinterface(ctx, -1, ifp->name);
+ }
+
+ /* Add new interfaces */
+ while ((ifp = TAILQ_FIRST(ifaces)) != NULL ) {
+ TAILQ_REMOVE(ifaces, ifp, next);
+ ifp1 = if_find(ctx->ifaces, ifp->name);
+ if (ifp1 != NULL) {
+ /* If the interface already exists,
+ * check carrier state.
+ * dhcpcd_checkcarrier will free ifp. */
+ eloop_timeout_add_sec(ctx->eloop, 0,
+ dhcpcd_checkcarrier, ifp);
+ continue;
+ }
+ TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
+ if (ifp->active) {
+ dhcpcd_initstate(ifp, 0);
+ eloop_timeout_add_sec(ctx->eloop, 0,
+ dhcpcd_runprestartinterface, ifp);
+ }
+ }
+ free(ifaces);
+
+ /* Update address state. */
+ if_markaddrsstale(ctx->ifaces);
+ if_learnaddrs(ctx, ctx->ifaces, &ifaddrs);
+ if_deletestaleaddrs(ctx->ifaces);
+}
+
+void
+dhcpcd_handlehwaddr(struct interface *ifp,
+ uint16_t hwtype, const void *hwaddr, uint8_t hwlen)
+{
+ char buf[sizeof(ifp->hwaddr) * 3];
+
+ if (hwaddr == NULL || !if_valid_hwaddr(hwaddr, hwlen))
+ hwlen = 0;
+
+ if (hwlen > sizeof(ifp->hwaddr)) {
+ errno = ENOBUFS;
+ logerr("%s: %s", __func__, ifp->name);
+ return;
+ }
+
+ if (ifp->hwtype != hwtype) {
+ if (ifp->active)
+ loginfox("%s: hardware address type changed"
+ " from %d to %d", ifp->name, ifp->hwtype, hwtype);
+ ifp->hwtype = hwtype;
+ }
+
+ if (ifp->hwlen == hwlen &&
+ (hwlen == 0 || memcmp(ifp->hwaddr, hwaddr, hwlen) == 0))
+ return;
+
+ if (ifp->active) {
+ loginfox("%s: old hardware address: %s", ifp->name,
+ hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf)));
+ loginfox("%s: new hardware address: %s", ifp->name,
+ hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf)));
+ }
+ ifp->hwlen = hwlen;
+ if (hwaddr != NULL)
+ memcpy(ifp->hwaddr, hwaddr, hwlen);
+}
+
+static void
+if_reboot(struct interface *ifp, int argc, char **argv)
+{
+#ifdef INET
+ unsigned long long oldopts;
+
+ oldopts = ifp->options->options;
+#endif
+ script_runreason(ifp, "RECONFIGURE");
+ dhcpcd_initstate1(ifp, argc, argv, 0);
+#ifdef INET
+ dhcp_reboot_newopts(ifp, oldopts);
+#endif
+#ifdef DHCP6
+ dhcp6_reboot(ifp);
+#endif
+ dhcpcd_prestartinterface(ifp);
+}
+
+static void
+reload_config(struct dhcpcd_ctx *ctx)
+{
+ struct if_options *ifo;
+
+ free_globals(ctx);
+ if ((ifo = read_config(ctx, NULL, NULL, NULL)) == NULL)
+ return;
+ add_options(ctx, NULL, ifo, ctx->argc, ctx->argv);
+ /* We need to preserve these options. */
+ if (ctx->options & DHCPCD_STARTED)
+ ifo->options |= DHCPCD_STARTED;
+ if (ctx->options & DHCPCD_MANAGER)
+ ifo->options |= DHCPCD_MANAGER;
+ if (ctx->options & DHCPCD_DAEMONISED)
+ ifo->options |= DHCPCD_DAEMONISED;
+ if (ctx->options & DHCPCD_PRIVSEP)
+ ifo->options |= DHCPCD_PRIVSEP;
+ ctx->options = ifo->options;
+ free_options(ctx, ifo);
+}
+
+static void
+reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi)
+{
+ int i;
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ for (i = oi; i < argc; i++) {
+ if (strcmp(ifp->name, argv[i]) == 0)
+ break;
+ }
+ if (oi != argc && i == argc)
+ continue;
+ if (ifp->active == IF_ACTIVE_USER) {
+ if (action)
+ if_reboot(ifp, argc, argv);
+#ifdef INET
+ else
+ ipv4_applyaddr(ifp);
+#endif
+ } else if (i != argc) {
+ ifp->active = IF_ACTIVE_USER;
+ dhcpcd_initstate1(ifp, argc, argv, 0);
+ run_preinit(ifp);
+ dhcpcd_prestartinterface(ifp);
+ }
+ }
+}
+
+static void
+stop_all_interfaces(struct dhcpcd_ctx *ctx, unsigned long long opts)
+{
+ struct interface *ifp;
+
+ ctx->options |= DHCPCD_EXITING;
+ if (ctx->ifaces == NULL)
+ return;
+
+ /* Drop the last interface first */
+ TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) {
+ if (!ifp->active)
+ continue;
+ ifp->options->options |= opts;
+ if (ifp->options->options & DHCPCD_RELEASE)
+ ifp->options->options &= ~DHCPCD_PERSISTENT;
+ ifp->options->options |= DHCPCD_EXITING;
+ stop_interface(ifp, NULL);
+ }
+}
+
+static void
+dhcpcd_ifrenew(struct interface *ifp)
+{
+
+ if (!ifp->active)
+ return;
+
+ if (ifp->options->options & DHCPCD_LINK && !if_is_link_up(ifp))
+ return;
+
+#ifdef INET
+ dhcp_renew(ifp);
+#endif
+#ifdef INET6
+#define DHCPCD_RARENEW (DHCPCD_IPV6 | DHCPCD_IPV6RS)
+ if ((ifp->options->options & DHCPCD_RARENEW) == DHCPCD_RARENEW)
+ ipv6nd_startrs(ifp);
+#endif
+#ifdef DHCP6
+ dhcp6_renew(ifp);
+#endif
+}
+
+static void
+dhcpcd_renew(struct dhcpcd_ctx *ctx)
+{
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ dhcpcd_ifrenew(ifp);
+ }
+}
+
+#ifdef USE_SIGNALS
+#define sigmsg "received %s, %s"
+static void
+dhcpcd_signal_cb(int sig, void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ unsigned long long opts;
+ int exit_code;
+
+ if (ctx->options & DHCPCD_DUMPLEASE) {
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+
+ if (sig != SIGCHLD && ctx->options & DHCPCD_FORKED) {
+ if (sig != SIGHUP &&
+ write(ctx->fork_fd, &sig, sizeof(sig)) == -1)
+ logerr("%s: write", __func__);
+ return;
+ }
+
+ opts = 0;
+ exit_code = EXIT_FAILURE;
+ switch (sig) {
+ case SIGINT:
+ loginfox(sigmsg, "SIGINT", "stopping");
+ break;
+ case SIGTERM:
+ loginfox(sigmsg, "SIGTERM", "stopping");
+ exit_code = EXIT_SUCCESS;
+ break;
+ case SIGALRM:
+ loginfox(sigmsg, "SIGALRM", "releasing");
+ opts |= DHCPCD_RELEASE;
+ exit_code = EXIT_SUCCESS;
+ break;
+ case SIGHUP:
+ loginfox(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:
+ loginfox(sigmsg, "SIGUSR1", "renewing");
+ dhcpcd_renew(ctx);
+ return;
+ case SIGUSR2:
+ loginfox(sigmsg, "SIGUSR2", "reopening log");
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ctx)) {
+ if (ps_root_logreopen(ctx) == -1)
+ logerr("ps_root_logreopen");
+ return;
+ }
+#endif
+ if (logopen(ctx->logfile) == -1)
+ logerr("logopen");
+ return;
+ case SIGCHLD:
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+ return;
+ default:
+ logerrx("received signal %d but don't know what to do with it",
+ sig);
+ return;
+ }
+
+ if (!(ctx->options & DHCPCD_TEST))
+ stop_all_interfaces(ctx, opts);
+ eloop_exit(ctx->eloop, exit_code);
+}
+#endif
+
+int
+dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
+ int argc, char **argv)
+{
+ struct interface *ifp;
+ unsigned long long opts;
+ int opt, oi, do_reboot, do_renew, af = AF_UNSPEC;
+ size_t len, l, nifaces;
+ 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 */
+ /* Make any change here in privsep-control.c as well. */
+ if (strcmp(*argv, "--version") == 0) {
+ return control_queue(fd, UNCONST(VERSION),
+ strlen(VERSION) + 1);
+ } else if (strcmp(*argv, "--getconfigfile") == 0) {
+ return control_queue(fd, UNCONST(fd->ctx->cffile),
+ strlen(fd->ctx->cffile) + 1);
+ } else if (strcmp(*argv, "--getinterfaces") == 0) {
+ optind = argc = 0;
+ goto dumplease;
+ } else if (strcmp(*argv, "--listen") == 0) {
+ fd->flags |= FD_LISTEN;
+ return 0;
+ }
+
+ /* 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';
+ loginfox("control command: %s", tmp);
+ free(tmp);
+
+ optind = 0;
+ oi = 0;
+ opts = 0;
+ do_reboot = do_renew = 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':
+ opts |= DHCPCD_RELEASE;
+ break;
+ case 'n':
+ do_reboot = 1;
+ break;
+ case 'p':
+ opts |= DHCPCD_PERSISTENT;
+ break;
+ case 'x':
+ opts |= DHCPCD_EXITING;
+ break;
+ case 'N':
+ do_renew = 1;
+ break;
+ case 'U':
+ opts |= DHCPCD_DUMPLEASE;
+ break;
+ case '4':
+ af = AF_INET;
+ break;
+ case '6':
+ af = AF_INET6;
+ break;
+ }
+ }
+
+ if (opts & DHCPCD_DUMPLEASE) {
+ ctx->options |= DHCPCD_DUMPLEASE;
+dumplease:
+ nifaces = 0;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (!ifp->active)
+ continue;
+ for (oi = optind; oi < argc; oi++) {
+ if (strcmp(ifp->name, argv[oi]) == 0)
+ break;
+ }
+ if (optind == argc || oi < argc) {
+ opt = send_interface(NULL, ifp, af);
+ if (opt == -1)
+ goto dumperr;
+ nifaces += (size_t)opt;
+ }
+ }
+ if (write(fd->fd, &nifaces, sizeof(nifaces)) != sizeof(nifaces))
+ goto dumperr;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (!ifp->active)
+ continue;
+ for (oi = optind; oi < argc; oi++) {
+ if (strcmp(ifp->name, argv[oi]) == 0)
+ break;
+ }
+ if (optind == argc || oi < argc) {
+ if (send_interface(fd, ifp, af) == -1)
+ goto dumperr;
+ }
+ }
+ ctx->options &= ~DHCPCD_DUMPLEASE;
+ return 0;
+dumperr:
+ ctx->options &= ~DHCPCD_DUMPLEASE;
+ return -1;
+ }
+
+ /* Only privileged users can control dhcpcd via the socket. */
+ if (fd->flags & FD_UNPRIV) {
+ errno = EPERM;
+ return -1;
+ }
+
+ if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) {
+ if (optind == argc) {
+ stop_all_interfaces(ctx, opts);
+ 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 (!ifp->active)
+ continue;
+ ifp->options->options |= opts;
+ if (opts & DHCPCD_RELEASE)
+ ifp->options->options &= ~DHCPCD_PERSISTENT;
+ stop_interface(ifp, NULL);
+ }
+ return 0;
+ }
+
+ if (do_renew) {
+ if (optind == argc) {
+ dhcpcd_renew(ctx);
+ return 0;
+ }
+ for (oi = optind; oi < argc; oi++) {
+ if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL)
+ continue;
+ dhcpcd_ifrenew(ifp);
+ }
+ return 0;
+ }
+
+ reload_config(ctx);
+ /* XXX: Respect initial commandline options? */
+ reconf_reboot(ctx, do_reboot, argc, argv, optind - 1);
+ return 0;
+}
+
+static void dhcpcd_readdump1(void *);
+
+static void
+dhcpcd_readdump2(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ ssize_t len;
+ int exit_code = EXIT_FAILURE;
+
+ len = read(ctx->control_fd, ctx->ctl_buf + ctx->ctl_bufpos,
+ ctx->ctl_buflen - ctx->ctl_bufpos);
+ if (len == -1) {
+ logerr(__func__);
+ goto finished;
+ } else if (len == 0)
+ goto finished;
+ if ((size_t)len + ctx->ctl_bufpos != ctx->ctl_buflen) {
+ ctx->ctl_bufpos += (size_t)len;
+ return;
+ }
+
+ if (ctx->ctl_buf[ctx->ctl_buflen - 1] != '\0') /* unlikely */
+ ctx->ctl_buf[ctx->ctl_buflen - 1] = '\0';
+ script_dump(ctx->ctl_buf, ctx->ctl_buflen);
+ fflush(stdout);
+ if (--ctx->ctl_extra != 0) {
+ putchar('\n');
+ eloop_event_add(ctx->eloop, ctx->control_fd,
+ dhcpcd_readdump1, ctx);
+ return;
+ }
+ exit_code = EXIT_SUCCESS;
+
+finished:
+ shutdown(ctx->control_fd, SHUT_RDWR);
+ eloop_exit(ctx->eloop, exit_code);
+}
+
+static void
+dhcpcd_readdump1(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ ssize_t len;
+
+ len = read(ctx->control_fd, &ctx->ctl_buflen, sizeof(ctx->ctl_buflen));
+ if (len != sizeof(ctx->ctl_buflen)) {
+ if (len != -1)
+ errno = EINVAL;
+ goto err;
+ }
+ if (ctx->ctl_buflen > SSIZE_MAX) {
+ errno = ENOBUFS;
+ goto err;
+ }
+
+ free(ctx->ctl_buf);
+ ctx->ctl_buf = malloc(ctx->ctl_buflen);
+ if (ctx->ctl_buf == NULL)
+ goto err;
+
+ ctx->ctl_bufpos = 0;
+ eloop_event_add(ctx->eloop, ctx->control_fd,
+ dhcpcd_readdump2, ctx);
+ return;
+
+err:
+ logerr(__func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+}
+
+static void
+dhcpcd_readdump0(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ ssize_t len;
+
+ len = read(ctx->control_fd, &ctx->ctl_extra, sizeof(ctx->ctl_extra));
+ if (len != sizeof(ctx->ctl_extra)) {
+ if (len != -1)
+ errno = EINVAL;
+ logerr(__func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+
+ if (ctx->ctl_extra == 0) {
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+
+ eloop_event_add(ctx->eloop, ctx->control_fd,
+ dhcpcd_readdump1, ctx);
+}
+
+static void
+dhcpcd_readdumptimeout(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ logerrx(__func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+}
+
+static int
+dhcpcd_readdump(struct dhcpcd_ctx *ctx)
+{
+
+ ctx->options |= DHCPCD_FORKED;
+ if (eloop_timeout_add_sec(ctx->eloop, 5,
+ dhcpcd_readdumptimeout, ctx) == -1)
+ return -1;
+ return eloop_event_add(ctx->eloop, ctx->control_fd,
+ dhcpcd_readdump0, ctx);
+}
+
+static void
+dhcpcd_fork_cb(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ int exit_code;
+ ssize_t len;
+
+ len = read(ctx->fork_fd, &exit_code, sizeof(exit_code));
+ if (len == -1) {
+ logerr(__func__);
+ exit_code = EXIT_FAILURE;
+ } else if ((size_t)len < sizeof(exit_code)) {
+ logerrx("%s: truncated read %zd (expected %zu)",
+ __func__, len, sizeof(exit_code));
+ exit_code = EXIT_FAILURE;
+ }
+ if (ctx->options & DHCPCD_FORKED)
+ eloop_exit(ctx->eloop, exit_code);
+ else
+ dhcpcd_signal_cb(exit_code, ctx);
+}
+
+static void
+dhcpcd_stderr_cb(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ char log[BUFSIZ];
+ ssize_t len;
+
+ len = read(ctx->stderr_fd, log, sizeof(log));
+ if (len == -1) {
+ if (errno != ECONNRESET)
+ logerr(__func__);
+ return;
+ }
+
+ log[len] = '\0';
+ fprintf(stderr, "%s", log);
+}
+
+int
+main(int argc, char **argv, char **envp)
+{
+ struct dhcpcd_ctx ctx;
+ struct ifaddrs *ifaddrs = NULL;
+ struct if_options *ifo;
+ struct interface *ifp;
+ sa_family_t family = AF_UNSPEC;
+ int opt, oi = 0, i;
+ unsigned int logopts, t;
+ ssize_t len;
+#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK)
+ pid_t pid;
+ int fork_fd[2], stderr_fd[2];
+#endif
+#ifdef USE_SIGNALS
+ int sig = 0;
+ const char *siga = NULL;
+ size_t si;
+#endif
+
+#ifdef SETPROCTITLE_H
+ setproctitle_init(argc, argv, envp);
+#else
+ UNUSED(envp);
+#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);
+ printf("Compiled in features:"
+#ifdef INET
+ " INET"
+#endif
+#ifdef ARP
+ " ARP"
+#endif
+#ifdef ARPING
+ " ARPing"
+#endif
+#ifdef IPV4LL
+ " IPv4LL"
+#endif
+#ifdef INET6
+ " INET6"
+#endif
+#ifdef DHCP6
+ " DHCPv6"
+#endif
+#ifdef AUTH
+ " AUTH"
+#endif
+#ifdef PRIVSEP
+ " PRIVSEP"
+#endif
+ "\n");
+ return EXIT_SUCCESS;
+ }
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+
+ ifo = NULL;
+ ctx.cffile = CONFIG;
+ ctx.script = UNCONST(dhcpcd_default_script);
+ ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1;
+ ctx.pf_inet_fd = -1;
+#ifdef PF_LINK
+ ctx.pf_link_fd = -1;
+#endif
+
+ TAILQ_INIT(&ctx.control_fds);
+#ifdef USE_SIGNALS
+ ctx.fork_fd = -1;
+#endif
+#ifdef PLUGIN_DEV
+ ctx.dev_fd = -1;
+#endif
+#ifdef INET
+ ctx.udp_rfd = -1;
+ ctx.udp_wfd = -1;
+#endif
+#if defined(INET6) && !defined(__sun)
+ ctx.nd_fd = -1;
+#endif
+#ifdef DHCP6
+ ctx.dhcp6_rfd = -1;
+ ctx.dhcp6_wfd = -1;
+#endif
+#ifdef PRIVSEP
+ ctx.ps_root_fd = ctx.ps_log_fd = ctx.ps_data_fd = -1;
+ ctx.ps_inet_fd = ctx.ps_control_fd = -1;
+ TAILQ_INIT(&ctx.ps_processes);
+#endif
+
+ /* Check our streams for validity */
+ ctx.stdin_valid = fcntl(STDIN_FILENO, F_GETFD) != -1;
+ ctx.stdout_valid = fcntl(STDOUT_FILENO, F_GETFD) != -1;
+ ctx.stderr_valid = fcntl(STDERR_FILENO, F_GETFD) != -1;
+
+ logopts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID;
+ if (ctx.stderr_valid)
+ logopts |= LOGERR_ERR;
+
+ i = 0;
+
+ while ((opt = getopt_long(argc, argv,
+ ctx.options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : 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;
+ case 'j':
+ free(ctx.logfile);
+ ctx.logfile = strdup(optarg);
+ break;
+#ifdef USE_SIGNALS
+ case 'k':
+ sig = SIGALRM;
+ siga = "ALRM";
+ break;
+ case 'n':
+ sig = SIGHUP;
+ siga = "HUP";
+ break;
+ case 'g':
+ case 'p':
+ /* Force going via command socket as we're
+ * out of user definable signals. */
+ i = 4;
+ break;
+ case 'q':
+ /* -qq disables console output entirely.
+ * This is important for systemd because it logs
+ * both console AND syslog to the same log
+ * resulting in untold confusion. */
+ if (logopts & LOGERR_QUIET)
+ logopts &= ~LOGERR_ERR;
+ else
+ logopts |= LOGERR_QUIET;
+ break;
+ case 'x':
+ sig = SIGTERM;
+ siga = "TERM";
+ break;
+ case 'N':
+ sig = SIGUSR1;
+ siga = "USR1";
+ break;
+#endif
+ case 'P':
+ ctx.options |= DHCPCD_PRINT_PIDFILE;
+ logopts &= ~(LOGERR_LOG | LOGERR_ERR);
+ break;
+ case 'T':
+ i = 1;
+ logopts &= ~LOGERR_LOG;
+ break;
+ case 'U':
+ i = 3;
+ break;
+ case 'V':
+ i = 2;
+ break;
+ case '?':
+ if (ctx.options & DHCPCD_PRINT_PIDFILE)
+ continue;
+ usage();
+ goto exit_failure;
+ }
+ }
+
+ if (optind != argc - 1)
+ ctx.options |= DHCPCD_MANAGER;
+
+ logsetopts(logopts);
+ logopen(ctx.logfile);
+
+ ctx.argv = argv;
+ ctx.argc = argc;
+ ctx.ifc = argc - optind;
+ ctx.ifv = argv + optind;
+
+ rt_init(&ctx);
+
+ ifo = read_config(&ctx, NULL, NULL, NULL);
+ if (ifo == NULL) {
+ if (ctx.options & DHCPCD_PRINT_PIDFILE)
+ goto printpidfile;
+ goto exit_failure;
+ }
+
+ opt = add_options(&ctx, NULL, ifo, argc, argv);
+ if (opt != 1) {
+ if (ctx.options & DHCPCD_PRINT_PIDFILE)
+ goto printpidfile;
+ if (opt == 0)
+ usage();
+ goto exit_failure;
+ }
+ if (i == 2) {
+ printf("Interface options:\n");
+ if (optind == argc - 1) {
+ free_options(&ctx, 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);
+#ifdef DHCP6
+ printf("\nDHCPv6 options:\n");
+ dhcp6_printoptions(&ctx,
+ ifo->dhcp6_override, ifo->dhcp6_override_len);
+#endif
+ }
+#endif
+ goto exit_success;
+ }
+ ctx.options |= ifo->options;
+
+ if (i == 1 || i == 3) {
+ 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)
+ logsetopts(logopts | LOGERR_DEBUG);
+
+ if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) {
+printpidfile:
+ /* If we have any other args, we should run as a single dhcpcd
+ * instance for that interface. */
+ if (optind == argc - 1 && !(ctx.options & DHCPCD_MANAGER)) {
+ const char *per;
+ const char *ifname;
+
+ ifname = *ctx.ifv;
+ if (ifname == NULL || strlen(ifname) > IF_NAMESIZE) {
+ errno = ifname == NULL ? EINVAL : E2BIG;
+ logerr("%s: ", ifname);
+ 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, ifname, per, ".");
+ } else {
+ snprintf(ctx.pidfile, sizeof(ctx.pidfile),
+ PIDFILE, "", "", "");
+ ctx.options |= DHCPCD_MANAGER;
+ }
+ if (ctx.options & DHCPCD_PRINT_PIDFILE) {
+ printf("%s\n", ctx.pidfile);
+ goto exit_success;
+ }
+ }
+
+ if (chdir("/") == -1)
+ logerr("%s: chdir: /", __func__);
+
+ /* Freeing allocated addresses from dumping leases can trigger
+ * eloop removals as well, so init here. */
+ if ((ctx.eloop = eloop_new()) == NULL) {
+ logerr("%s: eloop_init", __func__);
+ goto exit_failure;
+ }
+
+#ifdef USE_SIGNALS
+ for (si = 0; si < dhcpcd_signals_ignore_len; si++)
+ signal(dhcpcd_signals_ignore[si], SIG_IGN);
+
+ /* Save signal mask, block and redirect signals to our handler */
+ eloop_signal_set_cb(ctx.eloop,
+ dhcpcd_signals, dhcpcd_signals_len,
+ dhcpcd_signal_cb, &ctx);
+ if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) {
+ logerr("%s: eloop_signal_mask", __func__);
+ goto exit_failure;
+ }
+
+ if (sig != 0) {
+ pid = pidfile_read(ctx.pidfile);
+ if (pid != 0 && pid != -1)
+ loginfox("sending signal %s to pid %d", siga, pid);
+ if (pid == 0 || pid == -1 || kill(pid, sig) != 0) {
+ if (sig != SIGHUP && sig != SIGUSR1 && errno != EPERM)
+ logerrx(PACKAGE" not running");
+ if (pid != 0 && pid != -1 && errno != ESRCH) {
+ logerr("kill");
+ goto exit_failure;
+ }
+ unlink(ctx.pidfile);
+ if (sig != SIGHUP && sig != SIGUSR1)
+ goto exit_failure;
+ } else {
+ struct timespec ts;
+
+ if (sig == SIGHUP || sig == SIGUSR1)
+ goto exit_success;
+ /* Spin until it exits */
+ loginfox("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 (pidfile_read(ctx.pidfile) == -1)
+ goto exit_success;
+ }
+ logerrx("pid %d failed to exit", pid);
+ goto exit_failure;
+ }
+ }
+#endif
+
+#ifdef PRIVSEP
+ ps_init(&ctx);
+#endif
+
+#ifndef SMALL
+ if (ctx.options & DHCPCD_DUMPLEASE &&
+ ioctl(fileno(stdin), FIONREAD, &i, sizeof(i)) == 0 &&
+ i > 0)
+ {
+ ctx.options |= DHCPCD_FORKED; /* pretend child process */
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1)
+ goto exit_failure;
+#endif
+ ifp = calloc(1, sizeof(*ifp));
+ if (ifp == NULL) {
+ logerr(__func__);
+ goto exit_failure;
+ }
+ ifp->ctx = &ctx;
+ ifp->options = ifo;
+ switch (family) {
+ case AF_INET:
+#ifdef INET
+ if (dhcp_dump(ifp) == -1)
+ goto exit_failure;
+ break;
+#else
+ logerrx("No DHCP support");
+ goto exit_failure;
+#endif
+ case AF_INET6:
+#ifdef DHCP6
+ if (dhcp6_dump(ifp) == -1)
+ goto exit_failure;
+ break;
+#else
+ logerrx("No DHCP6 support");
+ goto exit_failure;
+#endif
+ default:
+ logerrx("Family not specified. Please use -4 or -6.");
+ goto exit_failure;
+ }
+ goto exit_success;
+ }
+#endif
+
+ /* Test against siga instead of sig to avoid gcc
+ * warning about a bogus potential signed overflow.
+ * The end result will be the same. */
+ if ((siga == NULL || i == 4 || ctx.ifc != 0) &&
+ !(ctx.options & DHCPCD_TEST))
+ {
+ ctx.options |= DHCPCD_FORKED; /* avoid socket unlink */
+ if (!(ctx.options & DHCPCD_MANAGER))
+ ctx.control_fd = control_open(argv[optind], family,
+ ctx.options & DHCPCD_DUMPLEASE);
+ if (!(ctx.options & DHCPCD_MANAGER) && ctx.control_fd == -1)
+ ctx.control_fd = control_open(argv[optind], AF_UNSPEC,
+ ctx.options & DHCPCD_DUMPLEASE);
+ if (ctx.control_fd == -1)
+ ctx.control_fd = control_open(NULL, AF_UNSPEC,
+ ctx.options & DHCPCD_DUMPLEASE);
+ if (ctx.control_fd != -1) {
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(&ctx) &&
+ ps_managersandbox(&ctx, NULL) == -1)
+ goto exit_failure;
+#endif
+ if (!(ctx.options & DHCPCD_DUMPLEASE))
+ loginfox("sending commands to dhcpcd process");
+ len = control_send(&ctx, argc, argv);
+ if (len > 0)
+ logdebugx("send OK");
+ else {
+ logerr("%s: control_send", __func__);
+ goto exit_failure;
+ }
+ if (ctx.options & DHCPCD_DUMPLEASE) {
+ if (dhcpcd_readdump(&ctx) == -1) {
+ logerr("%s: dhcpcd_readdump", __func__);
+ goto exit_failure;
+ }
+ goto run_loop;
+ }
+ goto exit_success;
+ } else {
+ if (errno != ENOENT)
+ logerr("%s: control_open", __func__);
+ if (ctx.options & DHCPCD_DUMPLEASE) {
+ if (errno == ENOENT)
+ logerrx("dhcpcd is not running");
+ goto exit_failure;
+ }
+ if (errno == EPERM || errno == EACCES)
+ goto exit_failure;
+ }
+ ctx.options &= ~DHCPCD_FORKED;
+ }
+
+ if (!(ctx.options & DHCPCD_TEST)) {
+ /* Ensure we have the needed directories */
+ if (mkdir(DBDIR, 0750) == -1 && errno != EEXIST)
+ logerr("%s: mkdir: %s", __func__, DBDIR);
+ if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST)
+ logerr("%s: mkdir: %s", __func__, RUNDIR);
+ if ((pid = pidfile_lock(ctx.pidfile)) != 0) {
+ if (pid == -1)
+ logerr("%s: pidfile_lock: %s",
+ __func__, ctx.pidfile);
+ else
+ logerrx(PACKAGE
+ " already running on pid %d (%s)",
+ pid, ctx.pidfile);
+ goto exit_failure;
+ }
+ }
+
+ loginfox(PACKAGE "-" VERSION " starting");
+ if (ctx.stdin_valid && freopen(_PATH_DEVNULL, "w", stdin) == NULL)
+ logwarn("freopen stdin");
+
+#if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK)
+ if (!(ctx.options & DHCPCD_DAEMONISE))
+ goto start_manager;
+
+ if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fork_fd) == -1 ||
+ (ctx.stderr_valid &&
+ xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, stderr_fd) == -1))
+ {
+ logerr("socketpair");
+ goto exit_failure;
+ }
+ switch (pid = fork()) {
+ case -1:
+ logerr("fork");
+ goto exit_failure;
+ case 0:
+ ctx.fork_fd = fork_fd[1];
+ close(fork_fd[0]);
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fd(ctx.fork_fd) == -1) {
+ logerr("ps_rights_limit_fdpair");
+ goto exit_failure;
+ }
+#endif
+ eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx);
+
+ /*
+ * Redirect stderr to the stderr socketpair.
+ * Redirect stdout as well.
+ * dhcpcd doesn't output via stdout, but something in
+ * a called script might.
+ */
+ if (ctx.stderr_valid) {
+ if (dup2(stderr_fd[1], STDERR_FILENO) == -1 ||
+ (ctx.stdout_valid &&
+ dup2(stderr_fd[1], STDOUT_FILENO) == -1))
+ logerr("dup2");
+ close(stderr_fd[0]);
+ close(stderr_fd[1]);
+ } else if (ctx.stdout_valid) {
+ if (freopen(_PATH_DEVNULL, "w", stdout) == NULL)
+ logerr("freopen stdout");
+ }
+ if (setsid() == -1) {
+ logerr("%s: setsid", __func__);
+ goto exit_failure;
+ }
+ /* Ensure we can never get a controlling terminal */
+ switch (pid = fork()) {
+ case -1:
+ logerr("fork");
+ goto exit_failure;
+ case 0:
+ break;
+ default:
+ ctx.options |= DHCPCD_FORKED; /* A lie */
+ i = EXIT_SUCCESS;
+ goto exit1;
+ }
+ break;
+ default:
+ setproctitle("[launcher]");
+ ctx.options |= DHCPCD_FORKED | DHCPCD_LAUNCHER;
+ ctx.fork_fd = fork_fd[0];
+ close(fork_fd[1]);
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fd(ctx.fork_fd) == -1) {
+ logerr("ps_rights_limit_fd");
+ goto exit_failure;
+ }
+#endif
+ eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx);
+
+ if (ctx.stderr_valid) {
+ ctx.stderr_fd = stderr_fd[0];
+ close(stderr_fd[1]);
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fd(ctx.stderr_fd) == 1) {
+ logerr("ps_rights_limit_fd");
+ goto exit_failure;
+ }
+#endif
+ eloop_event_add(ctx.eloop, ctx.stderr_fd,
+ dhcpcd_stderr_cb, &ctx);
+ }
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1)
+ goto exit_failure;
+#endif
+ goto run_loop;
+ }
+
+ /* We have now forked, setsid, forked once more.
+ * From this point on, we are the controlling daemon. */
+ logdebugx("spawned manager process on PID %d", getpid());
+start_manager:
+ ctx.options |= DHCPCD_STARTED;
+ if ((pid = pidfile_lock(ctx.pidfile)) != 0) {
+ logerr("%s: pidfile_lock %d", __func__, pid);
+#ifdef PRIVSEP
+ /* privsep has not started ... */
+ ctx.options &= ~DHCPCD_PRIVSEP;
+#endif
+ goto exit_failure;
+ }
+#endif
+
+ os_init();
+
+#if defined(BSD) && defined(INET6)
+ /* Disable the kernel RTADV sysctl as early as possible. */
+ if (ctx.options & DHCPCD_IPV6 && ctx.options & DHCPCD_IPV6RS)
+ if_disable_rtadv();
+#endif
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(&ctx) && ps_start(&ctx) == -1) {
+ logerr("ps_start");
+ goto exit_failure;
+ }
+ if (ctx.options & DHCPCD_FORKED)
+ goto run_loop;
+#endif
+
+ if (!(ctx.options & DHCPCD_TEST)) {
+ if (control_start(&ctx,
+ ctx.options & DHCPCD_MANAGER ?
+ NULL : argv[optind], family) == -1)
+ {
+ logerr("%s: control_start", __func__);
+ goto exit_failure;
+ }
+ }
+
+#ifdef PLUGIN_DEV
+ /* Start any dev listening plugin which may want to
+ * change the interface name provided by the kernel */
+ if (!IN_PRIVSEP(&ctx) &&
+ (ctx.options & (DHCPCD_MANAGER | DHCPCD_DEV)) ==
+ (DHCPCD_MANAGER | DHCPCD_DEV))
+ dev_start(&ctx, dhcpcd_handleinterface);
+#endif
+
+ setproctitle("%s%s%s",
+ ctx.options & DHCPCD_MANAGER ? "[manager]" : argv[optind],
+ ctx.options & DHCPCD_IPV4 ? " [ip4]" : "",
+ ctx.options & DHCPCD_IPV6 ? " [ip6]" : "");
+
+ if (if_opensockets(&ctx) == -1) {
+ logerr("%s: if_opensockets", __func__);
+ goto exit_failure;
+ }
+#ifndef SMALL
+ dhcpcd_setlinkrcvbuf(&ctx);
+#endif
+
+ /* Try and create DUID from the machine UUID. */
+ dhcpcd_initduid(&ctx, NULL);
+
+ /* Cache the default vendor option. */
+ if (dhcp_vendor(ctx.vendor, sizeof(ctx.vendor)) == -1)
+ logerr("dhcp_vendor");
+
+ /* Start handling kernel messages for interfaces, addresses and
+ * routes. */
+ eloop_event_add(ctx.eloop, ctx.link_fd, dhcpcd_handlelink, &ctx);
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, "stdio route") == -1)
+ 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;
+
+ ctx.ifaces = if_discover(&ctx, &ifaddrs, ctx.ifc, ctx.ifv);
+ if (ctx.ifaces == NULL) {
+ logerr("%s: if_discover", __func__);
+ goto exit_failure;
+ }
+ for (i = 0; i < ctx.ifc; i++) {
+ if ((ifp = if_find(ctx.ifaces, ctx.ifv[i])) == NULL)
+ logerrx("%s: interface not found",
+ ctx.ifv[i]);
+ else if (!ifp->active)
+ logerrx("%s: interface has an invalid configuration",
+ ctx.ifv[i]);
+ }
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ if (ifp->active == IF_ACTIVE_USER)
+ break;
+ }
+ if (ifp == NULL) {
+ if (ctx.ifc == 0) {
+ int loglevel;
+
+ loglevel = ctx.options & DHCPCD_INACTIVE ?
+ LOG_DEBUG : LOG_ERR;
+ logmessage(loglevel, "no valid interfaces found");
+ dhcpcd_daemonise(&ctx);
+ } else
+ goto exit_failure;
+ if (!(ctx.options & DHCPCD_LINK)) {
+ logerrx("aborting as link detection is disabled");
+ goto exit_failure;
+ }
+ }
+
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ if (ifp->active)
+ dhcpcd_initstate1(ifp, argc, argv, 0);
+ }
+ if_learnaddrs(&ctx, ctx.ifaces, &ifaddrs);
+
+ if (ctx.options & DHCPCD_BACKGROUND)
+ dhcpcd_daemonise(&ctx);
+
+ opt = 0;
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ if (ifp->active) {
+ run_preinit(ifp);
+ if (if_is_link_up(ifp))
+ opt = 1;
+ }
+ }
+
+ if (!(ctx.options & DHCPCD_BACKGROUND)) {
+ if (ctx.options & DHCPCD_MANAGER)
+ t = ifo->timeout;
+ else {
+ t = 0;
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ if (ifp->active) {
+ t = ifp->options->timeout;
+ break;
+ }
+ }
+ }
+ if (opt == 0 &&
+ ctx.options & DHCPCD_LINK &&
+ !(ctx.options & DHCPCD_WAITIP))
+ {
+ int loglevel;
+
+ loglevel = ctx.options & DHCPCD_INACTIVE ?
+ LOG_DEBUG : LOG_WARNING;
+ logmessage(loglevel, "no interfaces have a carrier");
+ dhcpcd_daemonise(&ctx);
+ } 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(&ctx, ifo);
+ ifo = NULL;
+
+ TAILQ_FOREACH(ifp, ctx.ifaces, next) {
+ if (ifp->active)
+ eloop_timeout_add_sec(ctx.eloop, 0,
+ dhcpcd_prestartinterface, ifp);
+ }
+
+run_loop:
+ i = eloop_start(ctx.eloop, &ctx.sigset);
+ if (i < 0) {
+ logerr("%s: eloop_start", __func__);
+ goto exit_failure;
+ }
+ goto exit1;
+
+exit_success:
+ i = EXIT_SUCCESS;
+ goto exit1;
+
+exit_failure:
+ i = EXIT_FAILURE;
+
+exit1:
+ if (!(ctx.options & DHCPCD_TEST) && control_stop(&ctx) == -1)
+ logerr("%s: control_stop", __func__);
+ if (ifaddrs != NULL) {
+#ifdef PRIVSEP_GETIFADDRS
+ if (IN_PRIVSEP(&ctx))
+ free(ifaddrs);
+ else
+#endif
+ freeifaddrs(ifaddrs);
+ }
+ /* ps_stop will clear DHCPCD_PRIVSEP but we need to
+ * remember it to avoid attemping to remove the pidfile */
+ oi = ctx.options & DHCPCD_PRIVSEP ? 1 : 0;
+#ifdef PRIVSEP
+ ps_stop(&ctx);
+#endif
+ /* 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);
+ ctx.ifaces = NULL;
+ }
+ free_options(&ctx, ifo);
+#ifdef HAVE_OPEN_MEMSTREAM
+ if (ctx.script_fp)
+ fclose(ctx.script_fp);
+#endif
+ free(ctx.script_buf);
+ free(ctx.script_env);
+ rt_dispose(&ctx);
+ free(ctx.duid);
+ if (ctx.link_fd != -1) {
+ eloop_event_delete(ctx.eloop, ctx.link_fd);
+ close(ctx.link_fd);
+ }
+ if_closesockets(&ctx);
+ free_globals(&ctx);
+#ifdef INET6
+ ipv6_ctxfree(&ctx);
+#endif
+#ifdef PLUGIN_DEV
+ dev_stop(&ctx);
+#endif
+#ifdef PRIVSEP
+ eloop_free(ctx.ps_eloop);
+#endif
+ eloop_free(ctx.eloop);
+ if (ctx.script != dhcpcd_default_script)
+ free(ctx.script);
+ if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED))
+ loginfox(PACKAGE " exited");
+ logclose();
+ free(ctx.logfile);
+ free(ctx.ctl_buf);
+#ifdef SETPROCTITLE_H
+ setproctitle_fini();
+#endif
+#ifdef USE_SIGNALS
+ if (ctx.options & DHCPCD_STARTED) {
+ /* Try to detach from the launch process. */
+ if (ctx.fork_fd != -1 &&
+ write(ctx.fork_fd, &i, sizeof(i)) == -1)
+ logerr("%s: write", __func__);
+ }
+ if (ctx.options & DHCPCD_FORKED || oi != 0)
+ _exit(i); /* so atexit won't remove our pidfile */
+#endif
+ return i;
+}
diff --git a/src/dhcpcd.conf b/src/dhcpcd.conf
new file mode 100644
index 000000000000..916e82dae1e7
--- /dev/null
+++ b/src/dhcpcd.conf
@@ -0,0 +1,48 @@
+# 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
+
+# vendorclassid is set to blank to avoid sending the default of
+# dhcpcd-<version>:<os>:<machine>:<platform>
+vendorclassid
+
+# A list of options to request from the DHCP server.
+option domain_name_servers, domain_name, domain_search
+option classless_static_routes
+# Respect the network MTU. This is applied to DHCP routes.
+option interface_mtu
+
+# Request a hostname from the network
+option host_name
+
+# Most distributions have NTP support.
+#option ntp_servers
+
+# 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 ServerID is required by RFC2131.
+require dhcp_server_identifier
+
+# Generate SLAAC address using the Hardware Address of the interface
+#slaac hwaddr
+# OR generate Stable Private IPv6 Addresses based from the DUID
+slaac private
diff --git a/src/dhcpcd.conf.5.in b/src/dhcpcd.conf.5.in
new file mode 100644
index 000000000000..4d8fa38a3505
--- /dev/null
+++ b/src/dhcpcd.conf.5.in
@@ -0,0 +1,1004 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2006-2021 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 23, 2021
+.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.
+Comments can be prefixed with the # character.
+String values should be quoted with the " character.
+.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 anonymous
+Enables Anonymity Profiles for DHCP, RFC 7844.
+Any DUID is ignored and ClientID is set to LL only.
+All non essential options are then masked at this point,
+but they could be unmasked by explicitly requesting the option
+.Sy after
+the
+.Ic anonymous
+option is processed.
+As such, the
+.Ic anonymous
+option
+.Sy should
+be the last option in the configuration unless you really want to
+send something which could identify you.
+.Nm dhcpcd
+will not try and reboot an old lease, it will go straight into
+DISCOVER/SOLICIT.
+.It Ic randomise_hwaddr
+Forces a hardware address randomisation when the interface is brought up
+or when the carrier is lost.
+This is generally used in tandem with the anonymous option.
+.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 # My specific 192.168.0.1 network
+.D1 profile dd:ee:aa:dd:bb:ee
+.D1 static ip_address=192.168.0.10/24
+.Pp
+.D1 # A generic 192.168.0.1 network
+.D1 profile 192.168.0.1
+.D1 static ip_address=192.168.0.98/24
+.It Ic authprotocol Ar protocol Op Ar algorithm Op Ar rdm
+Authenticate DHCP messages.
+See the Supported Authentication Protocols section.
+If
+.Ar protocol
+is
+.Ar token
+then
+.Ar algorithm is
+snd_secretid/rcv_secretid so you can send and receive different tokens.
+.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
+protocol.
+.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
+Fork to the 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@/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, it 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
+.Va 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 Op ll | lt | uuid | value
+Use a DHCP Unique Identifier.
+If a system UUID is available, that will be used to create a DUID-UUID,
+otheriwse if persistent storage is available then a DUID-LLT
+(link local address + time) is generated,
+otherwise DUID-LL is generated (link local address).
+The DUID type can be hinted as an optional parameter if the file
+.Pa @DBDIR@/duid
+does not exist.
+If not
+.Va ll ,
+.Va lt
+or
+.Va uuid
+then
+.Va value
+will be converted from 00:11:22:33 format.
+This, plus the IAID will be used as the
+.Ic clientid .
+The DUID generated will be held in
+.Pa @DBDIR@/duid
+and should not be copied to other hosts.
+This file also takes precedence over the above rules except for setting a value.
+.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 VLANID (prefixed with 0xff) for the interface if set,
+otherwise 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 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 Ic inform Op Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address
+Behaves like
+.Ic 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 dhcpcd
+remains running and pretends it has an infinite lease.
+.Nm dhcpcd
+will not de-configure the interface when it exits.
+If
+.Nm dhcpcd
+fails to contact a DHCP server then it returns a failure instead of falling
+back on IPv4LL.
+.It Ic inform6
+Performs a DHCPv6 Information Request.
+No address is requested or specified, but all other DHCPv6 options are allowed.
+This is normally performed automatically when an IPv6 Router Advertisement
+indicates that the client should perform this operation.
+This option is only needed when
+.Nm dhcpcd
+is not processing IPv6 RA messages and the need for a DHCPv6 Information Request
+exists.
+.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
+Fall back 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 the hostname
+.Ar name
+to the DHCP server so it can be registered in DNS.
+If
+.Ar name
+is an empty string then the current system hostname is sent.
+If
+.Ar name
+is a FQDN (i.e., 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 Op / Ar suffix
+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 with the same resultant prefix length as the delegation,
+a reject route is installed for the Delegated Prefix to
+stop unallocated addresses being resolved upstream.
+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
+.Ar suffix ,
+defaulting to 1.
+If the
+.Ar suffix
+is 0 then a SLAAC address is assigned.
+You cannot assign a prefix to the requesting interface unless the
+DHCPv6 server supports the
+.Li RFC 6603
+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 which must be unique inside the
+.Ar iaid
+and is added to the prefix which must fit inside
+.Ar prefix_len
+less the length of the delegated prefix.
+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
+noipv6rs # disable routing solicitation
+denyinterfaces eth2 # Don't touch eth2 at all
+interface eth0
+ ipv6rs # enable routing solicitation for eth0
+ 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
+.Ed
+.It Ic ipv4only
+Only configure IPv4.
+.It Ic ipv6only
+Only configure IPv6.
+.It Ic fqdn Op disable | none | ptr | both
+.Ar none
+will not ask the DHCP server to update DNS.
+.Ar ptr
+just asks the DHCP server to update the PTR
+record of the host in DNS, whereas
+.Ar both
+also updates the A record.
+.Ar disable
+will disable the FQDN option.
+The default is
+.Ar both .
+.Nm dhcpcd
+itself never does any DNS updates.
+.Nm dhcpcd
+encodes the FQDN hostname as specified in
+.Li RFC 1035 .
+.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 an IPv6
+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 Router Advertisement,
+.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 a RA.
+.It Ic ipv6rs
+Enables IPv6 Router Advertisement 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 lease time of
+.Ar seconds .
+.Ar -1
+represents an infinite lease time.
+By default
+.Nm dhcpcd
+does not request any lease time and leaves it in the hands of the
+DHCP server.
+.It Ic link_rcvbuf Ar size
+Override the size of the link receive buffer from the kernel default.
+While
+.Nm dhcpcd
+will recover from link buffer overflows,
+this may not be desirable on heavily loaded systems.
+.It Ic logfile Ar logfile
+Writes to the specified
+.Ar logfile .
+.Nm dhcpcd
+still writes to
+.Xr syslog 3 .
+The
+.Ar logfile
+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 1000 +
+.Xr if_nametoindex 3 .
+This will be offset by 2000 for wireless interfaces, with additional offsets
+of 1000000 for IPv4LL and 2000000 for roaming interfaces.
+.It Ic mudurl Ar url
+Specifies the URL for a Manufacturer Usage Description (MUD).
+The description is used by upstream network devices to instantiate any
+desired access lists.
+See draft-ietf-opsawg-mud for more information.
+.It Ic noalias
+Any pre-existing IPv4 addresses 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 an IPv6 Router Advertisement 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 solicit or accept IPv6 Router Advertisements and DHCPv6.
+.It Ic noipv6rs
+Don't solicit or accept 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 manager mode.
+.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.
+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.
+.Pp
+To see a list of options you can use, call
+.Nm dhcpcd
+with the
+.Fl V , Fl Fl variables
+argument.
+.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.
+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 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
+authoritative which means
+.Nm dhcpcd
+will timeout before moving back to the DISCOVER phase.
+This has no effect on DHCPv6 other than skipping the reboot 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 Ar hwaddr | Ar private Op Ar temp | Ar temporary
+Selects the interface identifier used for SLAAC generated IPv6 addresses.
+If
+.Ar private
+is used, a RFC 7217 address is generated.
+The
+.Ar temporary
+directive will create a temporary address for the prefix as well.
+.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 will just use the value for the address
+with an infinite lease time.
+If you set
+.Ic ip6_address ,
+.Nm dhcpcd
+will continue auto-configuration as normal.
+.Pp
+Here is an example which configures two static address, overriding the default
+IPv4 broadcast address, an IPv4 router, DNS and disables IPv6 auto-configuration.
+You could also use the
+.Ic inform6
+command here if you wished to obtain more information via DHCPv6.
+For IPv4, you should use the
+.Ic inform Ar ipaddress
+option instead of setting a static address.
+.D1 interface eth0
+.D1 noipv6rs
+.D1 static ip_address=192.168.0.10/24
+.D1 static broadcast_address=192.168.0.63
+.D1 static ip6_address=fd51:42f8:caae:d92e::ff/64
+.D1 static routers=192.168.0.1
+.D1 static domain_name_servers=192.168.0.1 fd51:42f8:caae:d92e::1
+.Pp
+Here is an example for PPP which gives the destination a default route.
+It uses the special
+.Ar 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
+Time out 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 message with the userclass.
+You can specify more than one.
+.It Ic msuserclass Ar string
+Tag the DHCPv4 mesasge with the Microsoft userclass.
+Unlike the
+.Ic userclass
+option, this one can only be added once.
+It should only be used for Microsoft DHCP servers and the
+.Ic vendorclassid
+should be set to "MSFT 98" or "MSFT 5.0".
+This option is not RFC compliant.
+.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 its 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 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, and RFC 3925 vendor
+options for DHCP can also be supplied.
+Each option needs to be started with the
+.Ic define ,
+.Ic definend ,
+.Ic define6
+or
+.Ic vendopt
+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 uniquely 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 optional
+This option is optional.
+Only makes sense for embedded options like the client FQDN option, where
+the FQDN string itself is optional.
+.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 position is set.
+This is to allow reservation of the first bits while assigning 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
+An RFC 3397 encoded string.
+.It Ic dname
+An 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, RFC 4702
+.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, RFC 3925
+.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 a plain text token the server expects and matches a token sent by
+the server.
+The tokens do not have to be the same.
+If unspecified, the token with a
+.Ar secretid
+of 0 will be used in sending messages
+and validating received messages.
+.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 an unexpired token with a matching
+.Ar realm
+and
+.Ar secretid .
+This token is used to authenticate 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 client's 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 an 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
diff --git a/src/dhcpcd.h b/src/dhcpcd.h
new file mode 100644
index 000000000000..d7fb8164574d
--- /dev/null
+++ b/src/dhcpcd.h
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <stdio.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_SSIDLEN 32
+#define PROFILE_LEN 64
+#define SECRET_LEN 64
+
+#define IF_INACTIVE 0
+#define IF_ACTIVE 1
+#define IF_ACTIVE_USER 2
+
+#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
+
+#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];
+ unsigned int index;
+ unsigned int active;
+ unsigned int flags;
+ uint16_t hwtype; /* ARPHRD_ETHER for example */
+ unsigned char hwaddr[HWADDR_LEN];
+ uint8_t hwlen;
+ unsigned short vlanid;
+ unsigned int metric;
+ int carrier;
+ bool wireless;
+ uint8_t ssid[IF_SSIDLEN];
+ unsigned int ssid_len;
+
+ char profile[PROFILE_LEN];
+ struct if_options *options;
+ void *if_data[IF_DATA_MAX];
+};
+TAILQ_HEAD(if_head, interface);
+
+#include "privsep.h"
+
+/* dhcpcd requires CMSG_SPACE to evaluate to a compile time constant. */
+#if defined(__QNX) || \
+ (defined(__NetBSD_Version__) && __NetBSD_Version__ < 600000000)
+#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
+
+struct passwd;
+
+struct dhcpcd_ctx {
+ char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1];
+ char vendor[256];
+ bool stdin_valid; /* It's possible stdin, stdout and stderr */
+ bool stdout_valid; /* could be closed when dhcpcd starts. */
+ bool stderr_valid;
+ int stderr_fd; /* FD for logging to stderr */
+ int fork_fd; /* FD for the fork init signal pipe */
+ const char *cffile;
+ unsigned long long options;
+ char *logfile;
+ 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 */
+ uint8_t duid_type;
+ unsigned char *duid;
+ size_t duid_len;
+ struct if_head *ifaces;
+
+ char *ctl_buf;
+ size_t ctl_buflen;
+ size_t ctl_bufpos;
+ size_t ctl_extra;
+
+ rb_tree_t routes; /* our routes */
+#ifdef RT_FREE_ROUTE_TABLE
+ rb_tree_t froutes; /* free routes for re-use */
+#endif
+ size_t rt_order; /* route order storage */
+
+ int pf_inet_fd;
+#ifdef PF_LINK
+ int pf_link_fd;
+#endif
+ void *priv;
+ int link_fd;
+#ifndef SMALL
+ int link_rcvbuf;
+#endif
+ int seq; /* route message sequence no */
+ int sseq; /* successful seq no sent */
+
+#ifdef USE_SIGNALS
+ sigset_t sigset;
+#endif
+ struct eloop *eloop;
+
+ char *script;
+#ifdef HAVE_OPEN_MEMSTREAM
+ FILE *script_fp;
+#endif
+ char *script_buf;
+ size_t script_buflen;
+ char **script_env;
+ size_t script_envlen;
+
+ int control_fd;
+ int control_unpriv_fd;
+ struct fd_list_head control_fds;
+ char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE];
+ char control_sock_unpriv[sizeof(CONTROLSOCKET) + IF_NAMESIZE + 7];
+ gid_t control_group;
+
+ /* DHCP Enterprise options, RFC3925 */
+ struct dhcp_opt *vivso;
+ size_t vivso_len;
+
+ char *randomstate; /* original state */
+
+ /* For filtering RTM_MISS messages per router */
+#ifdef BSD
+ uint8_t *rt_missfilter;
+ size_t rt_missfilterlen;
+ size_t rt_missfiltersize;
+#endif
+
+#ifdef PRIVSEP
+ struct passwd *ps_user; /* struct passwd for privsep user */
+ pid_t ps_root_pid;
+ int ps_root_fd; /* Privileged Proxy commands */
+ int ps_log_fd; /* chroot logging */
+ int ps_data_fd; /* Data from root spawned processes */
+ struct eloop *ps_eloop; /* eloop for polling root data */
+ struct ps_process_head ps_processes; /* List of spawned processes */
+ pid_t ps_inet_pid;
+ int ps_inet_fd; /* Network Proxy commands and data */
+ pid_t ps_control_pid;
+ int ps_control_fd; /* Control Proxy - generic listener */
+ int ps_control_data_fd; /* Control Proxy - data query */
+ struct fd_list *ps_control; /* Queue for the above */
+ struct fd_list *ps_control_client; /* Queue for the above */
+#endif
+
+#ifdef INET
+ struct dhcp_opt *dhcp_opts;
+ size_t dhcp_opts_len;
+
+ int udp_rfd;
+ int udp_wfd;
+
+ /* 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;
+ size_t opt_buffer_len;
+#endif
+#ifdef INET6
+ uint8_t *secret;
+ size_t secret_len;
+
+#ifndef __sun
+ int nd_fd;
+#endif
+ struct ra_head *ra_routers;
+
+ struct dhcp_opt *nd_opts;
+ size_t nd_opts_len;
+#ifdef DHCP6
+ int dhcp6_rfd;
+ int dhcp6_wfd;
+ struct dhcp_opt *dhcp6_opts;
+ size_t dhcp6_opts_len;
+#endif
+
+#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;
+extern const int dhcpcd_signals_ignore[];
+extern const size_t dhcpcd_signals_ignore_len;
+#endif
+
+extern const char *dhcpcd_default_script;
+
+int dhcpcd_ifafwaiting(const struct interface *);
+int dhcpcd_afwaiting(const struct dhcpcd_ctx *);
+void dhcpcd_daemonise(struct dhcpcd_ctx *);
+
+void dhcpcd_linkoverflow(struct dhcpcd_ctx *);
+int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **);
+void dhcpcd_handlecarrier(struct interface *, int, unsigned int);
+int dhcpcd_handleinterface(void *, int, const char *);
+void dhcpcd_handlehwaddr(struct interface *, uint16_t, const void *, uint8_t);
+void dhcpcd_dropinterface(struct interface *, const char *);
+int dhcpcd_selectprofile(struct interface *, const char *);
+
+void dhcpcd_startinterface(void *);
+void dhcpcd_activateinterface(struct interface *, unsigned long long);
+
+#endif
diff --git a/src/duid.c b/src/duid.c
new file mode 100644
index 000000000000..be626f510082
--- /dev/null
+++ b/src/duid.c
@@ -0,0 +1,236 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 UUID_LEN 36
+#define DUID_TIME_EPOCH 946684800
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#ifdef BSD
+# include <sys/sysctl.h>
+#endif
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "dhcpcd.h"
+#include "duid.h"
+#include "logerr.h"
+
+static size_t
+duid_machineuuid(char *uuid, size_t uuid_len)
+{
+ int r;
+ size_t len = uuid_len;
+
+#if defined(HW_UUID) /* OpenBSD */
+ int mib[] = { CTL_HW, HW_UUID };
+
+ r = sysctl(mib, sizeof(mib)/sizeof(mib[0]), uuid, &len, NULL, 0);
+#elif defined(KERN_HOSTUUID) /* FreeBSD */
+ int mib[] = { CTL_KERN, KERN_HOSTUUID };
+
+ r = sysctl(mib, sizeof(mib)/sizeof(mib[0]), uuid, &len, NULL, 0);
+#elif defined(__NetBSD__)
+ r = sysctlbyname("machdep.dmi.system-uuid", uuid, &len, NULL, 0);
+#elif defined(__linux__)
+ FILE *fp;
+
+ fp = fopen("/sys/class/dmi/id/product_uuid", "r");
+ if (fp == NULL)
+ return 0;
+ if (fgets(uuid, (int)uuid_len, fp) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+ len = strlen(uuid) + 1;
+ fclose(fp);
+ r = len == 1 ? -1 : 0;
+#else
+ UNUSED(uuid);
+ r = -1;
+ errno = ENOSYS;
+#endif
+
+ if (r == -1)
+ return 0;
+ return len;
+}
+
+static size_t
+duid_make_uuid(uint8_t *d)
+{
+ uint16_t type = htons(DUID_UUID);
+ char uuid[UUID_LEN + 1];
+ size_t l;
+
+ if (duid_machineuuid(uuid, sizeof(uuid)) != sizeof(uuid))
+ return 0;
+
+ /* All zeros UUID is not valid */
+ if (strcmp("00000000-0000-0000-0000-000000000000", uuid) == 0)
+ return 0;
+
+ memcpy(d, &type, sizeof(type));
+ l = sizeof(type);
+ d += sizeof(type);
+ l += hwaddr_aton(d, uuid);
+ return l;
+}
+
+size_t
+duid_make(void *d, const struct interface *ifp, uint16_t type)
+{
+ uint8_t *p;
+ uint16_t u16;
+ time_t t;
+ uint32_t u32;
+
+ if (ifp->hwlen == 0)
+ return 0;
+
+ p = d;
+ u16 = htons(type);
+ memcpy(p, &u16, sizeof(u16));
+ p += sizeof(u16);
+ u16 = htons(ifp->hwtype);
+ memcpy(p, &u16, sizeof(u16));
+ p += sizeof(u16);
+ 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, sizeof(u32));
+ p += sizeof(u32);
+ }
+ /* Finally, add the MAC address of the interface */
+ memcpy(p, ifp->hwaddr, ifp->hwlen);
+ p += ifp->hwlen;
+ return (size_t)(p - (uint8_t *)d);
+}
+
+#define DUID_STRLEN DUID_LEN * 3
+static size_t
+duid_get(struct dhcpcd_ctx *ctx, const struct interface *ifp)
+{
+ uint8_t *data;
+ size_t len, slen;
+ 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 ((len = dhcp_read_hwaddr_aton(ctx, &data, DUID)) != 0) {
+ if (len <= DUID_LEN) {
+ ctx->duid = data;
+ return len;
+ }
+ logerrx("DUID too big (max %u): %s", DUID_LEN, DUID);
+ /* Keep the buffer, will assign below. */
+ } else {
+ if (errno != ENOENT)
+ logerr("%s", DUID);
+ if ((data = malloc(DUID_LEN)) == NULL) {
+ logerr(__func__);
+ return 0;
+ }
+ }
+
+ /* No file? OK, lets make one based the machines UUID */
+ if (ifp == NULL) {
+ if (ctx->duid_type != DUID_DEFAULT &&
+ ctx->duid_type != DUID_UUID)
+ len = 0;
+ else
+ len = duid_make_uuid(data);
+ if (len == 0)
+ free(data);
+ else
+ ctx->duid = data;
+ return len;
+ }
+
+ /* Regardless of what happens we will create a DUID to use. */
+ ctx->duid = data;
+
+ /* No UUID? OK, lets make one based on our interface */
+ if (ifp->hwlen == 0) {
+ logwarnx("%s: does not have hardware address", ifp->name);
+ TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+ if (ifp2->hwlen != 0)
+ break;
+ }
+ if (ifp2) {
+ ifp = ifp2;
+ logwarnx("picked interface %s to generate a DUID",
+ ifp->name);
+ } else {
+ if (ctx->duid_type != DUID_LL)
+ logwarnx("no interfaces have a fixed hardware "
+ "address");
+ return duid_make(data, ifp, DUID_LL);
+ }
+ }
+
+ len = duid_make(data, ifp,
+ ctx->duid_type == DUID_LL ? DUID_LL : DUID_LLT);
+ hwaddr_ntoa(data, len, line, sizeof(line));
+ slen = strlen(line);
+ if (slen < sizeof(line) - 2) {
+ line[slen++] = '\n';
+ line[slen] = '\0';
+ }
+ if (dhcp_writefile(ctx, DUID, 0640, line, slen) == -1) {
+ logerr("%s: cannot write duid", __func__);
+ if (ctx->duid_type != DUID_LL)
+ return duid_make(data, ifp, DUID_LL);
+ }
+ return len;
+}
+
+size_t
+duid_init(struct dhcpcd_ctx *ctx, const struct interface *ifp)
+{
+
+ if (ctx->duid == NULL)
+ ctx->duid_len = duid_get(ctx, ifp);
+ return ctx->duid_len;
+}
diff --git a/src/duid.h b/src/duid.h
new file mode 100644
index 000000000000..da3811d94123
--- /dev/null
+++ b/src/duid.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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
+#define DUID_DEFAULT 0
+#define DUID_LLT 1
+#define DUID_LL 3
+#define DUID_UUID 4
+
+size_t duid_make(void *, const struct interface *, uint16_t);
+size_t duid_init(struct dhcpcd_ctx *, const struct interface *);
+
+#endif
diff --git a/src/eloop.c b/src/eloop.c
new file mode 100644
index 000000000000..a6ab43fbaef7
--- /dev/null
+++ b/src/eloop.c
@@ -0,0 +1,787 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * eloop - portable event based main loop.
+ * Copyright (c) 2006-2021 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 <poll.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* config.h should define HAVE_PPOLL, etc. */
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H)
+#include "config.h"
+#endif
+
+#if defined(HAVE_PPOLL)
+#elif defined(HAVE_POLLTS)
+#define ppoll pollts
+#elif !defined(HAVE_PSELECT)
+#pragma message("Compiling eloop with pselect(2) support.")
+#define HAVE_PSELECT
+#define ppoll eloop_ppoll
+#endif
+
+#include "eloop.h"
+
+#ifndef UNUSED
+#define UNUSED(a) (void)((a))
+#endif
+#ifndef __unused
+#ifdef __GNUC__
+#define __unused __attribute__((__unused__))
+#else
+#define __unused
+#endif
+#endif
+
+#ifdef HAVE_PSELECT
+#include <sys/select.h>
+#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
+
+#ifdef ELOOP_DEBUG
+#include <stdio.h>
+#endif
+
+/*
+ * Allow a backlog of signals.
+ * If you use many eloops in the same process, they should all
+ * use the same signal handler or have the signal handler unset.
+ * Otherwise the signal might not behave as expected.
+ */
+#define ELOOP_NSIGNALS 5
+
+/*
+ * time_t is a signed integer of an unspecified size.
+ * To adjust for time_t wrapping, we need to work the maximum signed
+ * value and use that as a maximum.
+ */
+#ifndef TIME_MAX
+#define TIME_MAX ((1ULL << (sizeof(time_t) * NBBY - 1)) - 1)
+#endif
+/* The unsigned maximum is then simple - multiply by two and add one. */
+#ifndef UTIME_MAX
+#define UTIME_MAX (TIME_MAX * 2) + 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;
+ struct pollfd *pollfd;
+};
+
+struct eloop_timeout {
+ TAILQ_ENTRY(eloop_timeout) next;
+ unsigned int seconds;
+ unsigned int nseconds;
+ void (*callback)(void *);
+ void *arg;
+ int queue;
+};
+
+struct eloop {
+ TAILQ_HEAD (event_head, eloop_event) events;
+ size_t nevents;
+ struct event_head free_events;
+ bool events_need_setup;
+
+ struct timespec now;
+ TAILQ_HEAD (timeout_head, eloop_timeout) timeouts;
+ struct timeout_head free_timeouts;
+
+ const int *signals;
+ size_t signals_len;
+ void (*signal_cb)(int, void *);
+ void *signal_cb_ctx;
+
+ struct pollfd *fds;
+ size_t nfds;
+
+ int exitnow;
+ int exitcode;
+};
+
+#ifdef HAVE_REALLOCARRAY
+#define eloop_realloca reallocarray
+#else
+/* Handy routing to check for potential overflow.
+ * reallocarray(3) and reallocarr(3) are not portable. */
+#define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2))
+static void *
+eloop_realloca(void *ptr, size_t n, size_t size)
+{
+
+ if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return realloc(ptr, n * size);
+}
+#endif
+
+#ifdef HAVE_PSELECT
+/* Wrapper around pselect, to imitate the ppoll call. */
+static int
+eloop_ppoll(struct pollfd * fds, nfds_t nfds,
+ const struct timespec *ts, const sigset_t *sigmask)
+{
+ fd_set read_fds, write_fds;
+ nfds_t n;
+ int maxfd, r;
+
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_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 (fds[n].events & POLLOUT) {
+ FD_SET(fds[n].fd, &write_fds);
+ if (fds[n].fd > maxfd)
+ maxfd = fds[n].fd;
+ }
+ }
+
+ r = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask);
+ if (r > 0) {
+ for (n = 0; n < nfds; n++) {
+ fds[n].revents =
+ FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0;
+ if (FD_ISSET(fds[n].fd, &write_fds))
+ fds[n].revents |= POLLOUT;
+ }
+ }
+
+ return r;
+}
+#endif
+
+unsigned long long
+eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp,
+ unsigned int *nsp)
+{
+ unsigned long long tsecs, usecs, secs;
+ long nsecs;
+
+ if (tsp->tv_sec < 0) /* time wreapped */
+ tsecs = UTIME_MAX - (unsigned long long)(-tsp->tv_sec);
+ else
+ tsecs = (unsigned long long)tsp->tv_sec;
+ if (usp->tv_sec < 0) /* time wrapped */
+ usecs = UTIME_MAX - (unsigned long long)(-usp->tv_sec);
+ else
+ usecs = (unsigned long long)usp->tv_sec;
+
+ if (usecs > tsecs) /* time wrapped */
+ secs = (UTIME_MAX - usecs) + tsecs;
+ else
+ secs = tsecs - usecs;
+
+ nsecs = tsp->tv_nsec - usp->tv_nsec;
+ if (nsecs < 0) {
+ if (secs == 0)
+ nsecs = 0;
+ else {
+ secs--;
+ nsecs += NSEC_PER_SEC;
+ }
+ }
+ if (nsp != NULL)
+ *nsp = (unsigned int)nsecs;
+ return secs;
+}
+
+static void
+eloop_reduce_timers(struct eloop *eloop)
+{
+ struct timespec now;
+ unsigned long long secs;
+ unsigned int nsecs;
+ struct eloop_timeout *t;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ secs = eloop_timespec_diff(&now, &eloop->now, &nsecs);
+
+ TAILQ_FOREACH(t, &eloop->timeouts, next) {
+ if (secs > t->seconds) {
+ t->seconds = 0;
+ t->nseconds = 0;
+ } else {
+ t->seconds -= (unsigned int)secs;
+ if (nsecs > t->nseconds) {
+ if (t->seconds == 0)
+ t->nseconds = 0;
+ else {
+ t->seconds--;
+ t->nseconds = NSEC_PER_SEC
+ - (nsecs - t->nseconds);
+ }
+ } else
+ t->nseconds -= nsecs;
+ }
+ }
+
+ eloop->now = now;
+}
+
+static void
+eloop_event_setup_fds(struct eloop *eloop)
+{
+ struct eloop_event *e, *ne;
+ struct pollfd *pfd;
+
+ pfd = eloop->fds;
+ TAILQ_FOREACH_SAFE(e, &eloop->events, next, ne) {
+ if (e->fd == -1) {
+ TAILQ_REMOVE(&eloop->events, e, next);
+ TAILQ_INSERT_TAIL(&eloop->free_events, e, next);
+ continue;
+ }
+#ifdef ELOOP_DEBUG
+ fprintf(stderr, "%s(%d) fd=%d, rcb=%p, wcb=%p\n",
+ __func__, getpid(), e->fd, e->read_cb, e->write_cb);
+#endif
+ e->pollfd = pfd;
+ pfd->fd = e->fd;
+ pfd->events = 0;
+ if (e->read_cb != NULL)
+ pfd->events |= POLLIN;
+ if (e->write_cb != NULL)
+ pfd->events |= POLLOUT;
+ pfd->revents = 0;
+ pfd++;
+ }
+ eloop->events_need_setup = false;
+}
+
+size_t
+eloop_event_count(const struct eloop *eloop)
+{
+
+ return eloop->nevents;
+}
+
+int
+eloop_event_add_rw(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;
+ struct pollfd *pfd;
+
+ assert(eloop != NULL);
+ assert(read_cb != NULL || write_cb != NULL);
+ if (fd == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->fd == fd)
+ break;
+ }
+
+ if (e == NULL) {
+ if (eloop->nevents + 1 > eloop->nfds) {
+ pfd = eloop_realloca(eloop->fds, eloop->nevents + 1,
+ sizeof(*pfd));
+ if (pfd == NULL)
+ return -1;
+ eloop->fds = pfd;
+ eloop->nfds++;
+ }
+
+ e = TAILQ_FIRST(&eloop->free_events);
+ if (e != NULL)
+ TAILQ_REMOVE(&eloop->free_events, e, next);
+ else {
+ e = malloc(sizeof(*e));
+ if (e == NULL)
+ return -1;
+ }
+ TAILQ_INSERT_HEAD(&eloop->events, e, next);
+ eloop->nevents++;
+ 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;
+ goto setup;
+ }
+
+ 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;
+ }
+
+setup:
+ e->pollfd = NULL;
+ eloop->events_need_setup = true;
+ return 0;
+}
+
+int
+eloop_event_add(struct eloop *eloop, int fd,
+ void (*read_cb)(void *), void *read_cb_arg)
+{
+
+ return eloop_event_add_rw(eloop, fd, read_cb, read_cb_arg, NULL, NULL);
+}
+
+int
+eloop_event_add_w(struct eloop *eloop, int fd,
+ void (*write_cb)(void *), void *write_cb_arg)
+{
+
+ return eloop_event_add_rw(eloop, fd, NULL,NULL, write_cb, write_cb_arg);
+}
+
+int
+eloop_event_delete_write(struct eloop *eloop, int fd, int write_only)
+{
+ struct eloop_event *e;
+
+ assert(eloop != NULL);
+ if (fd == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->fd == fd)
+ break;
+ }
+ if (e == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (write_only) {
+ if (e->read_cb == NULL)
+ goto remove;
+ e->write_cb = NULL;
+ e->write_cb_arg = NULL;
+ if (e->pollfd != NULL) {
+ e->pollfd->events &= ~POLLOUT;
+ e->pollfd->revents &= ~POLLOUT;
+ }
+ return 1;
+ }
+
+remove:
+ e->fd = -1;
+ eloop->nevents--;
+ eloop->events_need_setup = true;
+ return 1;
+}
+
+/*
+ * This implementation should cope with UINT_MAX seconds on a system
+ * where time_t is INT32_MAX. It should also cope with the monotonic timer
+ * wrapping, although this is highly unlikely.
+ * unsigned int should match or be greater than any on wire specified timeout.
+ */
+static int
+eloop_q_timeout_add(struct eloop *eloop, int queue,
+ unsigned int seconds, unsigned int nseconds,
+ void (*callback)(void *), void *arg)
+{
+ struct eloop_timeout *t, *tt = NULL;
+
+ assert(eloop != NULL);
+ assert(callback != NULL);
+ assert(nseconds <= NSEC_PER_SEC);
+
+ /* 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;
+ }
+ }
+
+ eloop_reduce_timers(eloop);
+
+ t->seconds = seconds;
+ t->nseconds = nseconds;
+ 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 (t->seconds < tt->seconds ||
+ (t->seconds == tt->seconds && t->nseconds < tt->nseconds))
+ {
+ TAILQ_INSERT_BEFORE(tt, t, next);
+ return 0;
+ }
+ }
+ TAILQ_INSERT_TAIL(&eloop->timeouts, t, next);
+ return 0;
+}
+
+int
+eloop_q_timeout_add_tv(struct eloop *eloop, int queue,
+ const struct timespec *when, void (*callback)(void *), void *arg)
+{
+
+ if (when->tv_sec < 0 || (unsigned long)when->tv_sec > UINT_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (when->tv_nsec < 0 || when->tv_nsec > NSEC_PER_SEC) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return eloop_q_timeout_add(eloop, queue,
+ (unsigned int)when->tv_sec, (unsigned int)when->tv_sec,
+ callback, arg);
+}
+
+int
+eloop_q_timeout_add_sec(struct eloop *eloop, int queue, unsigned int seconds,
+ void (*callback)(void *), void *arg)
+{
+
+ return eloop_q_timeout_add(eloop, queue, seconds, 0, callback, arg);
+}
+
+int
+eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when,
+ void (*callback)(void *), void *arg)
+{
+ unsigned long seconds, nseconds;
+
+ seconds = when / MSEC_PER_SEC;
+ if (seconds > UINT_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ nseconds = (when % MSEC_PER_SEC) * NSEC_PER_MSEC;
+ return eloop_q_timeout_add(eloop, queue,
+ (unsigned int)seconds, (unsigned int)nseconds, callback, arg);
+}
+
+int
+eloop_q_timeout_delete(struct eloop *eloop, int queue,
+ void (*callback)(void *), void *arg)
+{
+ struct eloop_timeout *t, *tt;
+ int n;
+
+ assert(eloop != NULL);
+
+ n = 0;
+ 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);
+ n++;
+ }
+ }
+ return n;
+}
+
+void
+eloop_exit(struct eloop *eloop, int code)
+{
+
+ assert(eloop != NULL);
+
+ eloop->exitcode = code;
+ eloop->exitnow = 1;
+}
+
+void
+eloop_enter(struct eloop *eloop)
+{
+
+ eloop->exitnow = 0;
+}
+
+void
+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;
+}
+
+static volatile int _eloop_sig[ELOOP_NSIGNALS];
+static volatile size_t _eloop_nsig;
+
+static void
+eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg)
+{
+
+ if (_eloop_nsig == __arraycount(_eloop_sig)) {
+#ifdef ELOOP_DEBUG
+ fprintf(stderr, "%s: signal storm, discarding signal %d\n",
+ __func__, sig);
+#endif
+ return;
+ }
+
+ _eloop_sig[_eloop_nsig++] = sig;
+}
+
+int
+eloop_signal_mask(struct eloop *eloop, sigset_t *oldset)
+{
+ sigset_t newset;
+ size_t i;
+ struct sigaction sa = {
+ .sa_sigaction = eloop_signal3,
+ .sa_flags = SA_SIGINFO,
+ };
+
+ assert(eloop != NULL);
+
+ sigemptyset(&newset);
+ for (i = 0; i < eloop->signals_len; i++)
+ sigaddset(&newset, eloop->signals[i]);
+ if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1)
+ return -1;
+
+ sigemptyset(&sa.sa_mask);
+
+ for (i = 0; i < eloop->signals_len; i++) {
+ if (sigaction(eloop->signals[i], &sa, NULL) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+struct eloop *
+eloop_new(void)
+{
+ struct eloop *eloop;
+
+ eloop = calloc(1, sizeof(*eloop));
+ if (eloop == NULL)
+ return NULL;
+
+ /* Check we have a working monotonic clock. */
+ if (clock_gettime(CLOCK_MONOTONIC, &eloop->now) == -1) {
+ free(eloop);
+ return NULL;
+ }
+
+ TAILQ_INIT(&eloop->events);
+ TAILQ_INIT(&eloop->free_events);
+ TAILQ_INIT(&eloop->timeouts);
+ TAILQ_INIT(&eloop->free_timeouts);
+ eloop->exitcode = EXIT_FAILURE;
+
+ return eloop;
+}
+
+void
+eloop_clear(struct eloop *eloop)
+{
+ struct eloop_event *e;
+ struct eloop_timeout *t;
+
+ if (eloop == NULL)
+ return;
+
+ eloop->nevents = 0;
+ eloop->signals = NULL;
+ eloop->signals_len = 0;
+
+ 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);
+ }
+
+ free(eloop->fds);
+ eloop->fds = NULL;
+ eloop->nfds = 0;
+}
+
+void
+eloop_free(struct eloop *eloop)
+{
+
+ eloop_clear(eloop);
+ free(eloop);
+}
+
+int
+eloop_start(struct eloop *eloop, sigset_t *signals)
+{
+ int n;
+ struct eloop_event *e;
+ struct eloop_timeout *t;
+ struct timespec ts, *tsp;
+
+ assert(eloop != NULL);
+
+ for (;;) {
+ if (eloop->exitnow)
+ break;
+
+ if (_eloop_nsig != 0) {
+ n = _eloop_sig[--_eloop_nsig];
+ if (eloop->signal_cb != NULL)
+ eloop->signal_cb(n, eloop->signal_cb_ctx);
+ continue;
+ }
+
+ t = TAILQ_FIRST(&eloop->timeouts);
+ if (t == NULL && eloop->nevents == 0)
+ break;
+
+ if (t != NULL)
+ eloop_reduce_timers(eloop);
+
+ if (t != NULL && t->seconds == 0 && t->nseconds == 0) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ t->callback(t->arg);
+ TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next);
+ continue;
+ }
+
+ if (t != NULL) {
+ if (t->seconds > INT_MAX) {
+ ts.tv_sec = (time_t)INT_MAX;
+ ts.tv_nsec = 0;
+ } else {
+ ts.tv_sec = (time_t)t->seconds;
+ ts.tv_nsec = (long)t->nseconds;
+ }
+ tsp = &ts;
+ } else
+ tsp = NULL;
+
+ if (eloop->events_need_setup)
+ eloop_event_setup_fds(eloop);
+
+ n = ppoll(eloop->fds, (nfds_t)eloop->nevents, tsp, signals);
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+ if (n == 0)
+ continue;
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ /* Skip freshly added events */
+ if (e->pollfd == NULL)
+ continue;
+ if (e->pollfd->revents)
+ n--;
+ if (e->fd != -1 && e->pollfd->revents & POLLOUT) {
+ if (e->write_cb != NULL)
+ e->write_cb(e->write_cb_arg);
+ }
+ if (e->fd != -1 &&
+ e->pollfd != NULL && e->pollfd->revents)
+ {
+ if (e->read_cb != NULL)
+ e->read_cb(e->read_cb_arg);
+ }
+ if (n == 0)
+ break;
+ }
+ }
+
+ return eloop->exitcode;
+}
diff --git a/src/eloop.h b/src/eloop.h
new file mode 100644
index 000000000000..1813eaa64d24
--- /dev/null
+++ b/src/eloop.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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>
+
+/* Handy macros to create subsecond timeouts */
+#define CSEC_PER_SEC 100
+#define MSEC_PER_SEC 1000
+#define NSEC_PER_CSEC 10000000
+#define NSEC_PER_MSEC 1000000
+#define NSEC_PER_SEC 1000000000
+
+/* eloop queues are really only for deleting timeouts registered
+ * for a function or object.
+ * The idea being that one interface has different timeouts for
+ * say DHCP and DHCPv6. */
+#ifndef ELOOP_QUEUE
+ #define ELOOP_QUEUE 1
+#endif
+
+/* Used for deleting a timeout for all queues. */
+#define ELOOP_QUEUE_ALL 0
+
+/* Forward declare eloop - the content should be invisible to the outside */
+struct eloop;
+
+unsigned long long eloop_timespec_diff(const struct timespec *tsp,
+ const struct timespec *usp, unsigned int *nsp);
+size_t eloop_event_count(const struct eloop *);
+int eloop_event_add_rw(struct eloop *, int,
+ void (*)(void *), void *,
+ void (*)(void *), void *);
+int eloop_event_add(struct eloop *, int,
+ void (*)(void *), void *);
+int eloop_event_add_w(struct eloop *, int,
+ 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)
+int 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,
+ unsigned int, void (*)(void *), void *);
+int eloop_q_timeout_add_msec(struct eloop *, int,
+ unsigned long, void (*)(void *), void *);
+int eloop_q_timeout_delete(struct eloop *, int, void (*)(void *), void *);
+
+void 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);
+void eloop_clear(struct eloop *);
+void eloop_free(struct eloop *);
+void eloop_exit(struct eloop *, int);
+void eloop_enter(struct eloop *);
+int eloop_start(struct eloop *, sigset_t *);
+
+#endif
diff --git a/src/genembedc b/src/genembedc
new file mode 100755
index 000000000000..81545cd86b42
--- /dev/null
+++ b/src/genembedc
@@ -0,0 +1,30 @@
+#!/bin/sh
+set -e
+
+: ${TOOL_CAT:=cat}
+: ${TOOL_SED:=sed}
+CONF=${1:-dhcpcd-definitions.conf}
+CONF_SMALL=${2:-dhcpcd-definitions.conf}
+C=${3:-dhcpcd-embedded.c.in}
+
+$TOOL_CAT $C
+echo "#ifdef SMALL"
+$TOOL_SED \
+ -e 's/#.*$//' \
+ -e '/^$/d' \
+ -e 's/^/"/g' \
+ -e 's/$/\\n\"/g' \
+ -e 's/ [ ]*/ /g' \
+ -e 's/ [ ]*/ /g' \
+ $CONF_SMALL
+echo "#else"
+$TOOL_SED \
+ -e 's/#.*$//' \
+ -e '/^$/d' \
+ -e 's/^/"/g' \
+ -e 's/$/\\n"/g' \
+ -e 's/ [ ]*/ /g' \
+ -e 's/ [ ]*/ /g' \
+ $CONF
+echo "#endif"
+printf "%s\n%s\n" '"\0";'
diff --git a/src/genembedh b/src/genembedh
new file mode 100755
index 000000000000..1762a6042909
--- /dev/null
+++ b/src/genembedh
@@ -0,0 +1,24 @@
+#!/bin/sh
+set -e
+
+: ${TOOL_SED:=sed}
+: ${TOOL_GREP:=grep}
+: ${TOOL_WC:=wc}
+CONF=${1:-dhcpcd-definitions.conf}
+CONF_SMALL=${2:-dhcpcd-definitions-small.conf}
+H=${3:-dhcpcd-embedded.h.in}
+
+INITDEFINES=$($TOOL_GREP "^define " $CONF | $TOOL_WC -l)
+INITDEFINENDS=$($TOOL_GREP "^definend " $CONF | $TOOL_WC -l)
+INITDEFINE6S=$($TOOL_GREP "^define6 " $CONF | $TOOL_WC -l)
+INITDEFINES_SMALL=$($TOOL_GREP "^define " $CONF_SMALL | $TOOL_WC -l)
+INITDEFINENDS_SMALL=$($TOOL_GREP "^definend " $CONF_SMALL | $TOOL_WC -l)
+INITDEFINE6S_SMALL=$($TOOL_GREP "^define6 " $CONF_SMALL | $TOOL_WC -l)
+$TOOL_SED \
+ -e "s/@INITDEFINES@/$INITDEFINES/" \
+ -e "s/@INITDEFINENDS@/$INITDEFINENDS/" \
+ -e "s/@INITDEFINE6S@/$INITDEFINE6S/" \
+ -e "s/@INITDEFINES_SMALL@/$INITDEFINES_SMALL/" \
+ -e "s/@INITDEFINENDS_SMALL@/$INITDEFINENDS_SMALL/" \
+ -e "s/@INITDEFINE6S_SMALL@/$INITDEFINE6S_SMALL/" \
+ $H
diff --git a/src/if-bsd.c b/src/if-bsd.c
new file mode 100644
index 000000000000..e5ffe500655f
--- /dev/null
+++ b/src/if-bsd.c
@@ -0,0 +1,1917 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * BSD interface driver for dhcpcd
+ * Copyright (c) 2006-2021 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 "config.h"
+
+#include <arpa/inet.h>
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#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 __NetBSD__
+#include <net/if_vlanvar.h> /* Needs netinet/if_ether.h */
+#elif defined(__DragonFly__)
+#include <net/vlan/if_vlan_var.h>
+#else
+#include <net/if_vlan_var.h>
+#endif
+#ifdef __DragonFly__
+# include <netproto/802_11/ieee80211_ioctl.h>
+#else
+# include <net80211/ieee80211.h>
+# include <net80211/ieee80211_ioctl.h>
+#endif
+
+#include <assert.h>
+#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 "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 "logerr.h"
+#include "privsep.h"
+#include "route.h"
+#include "sa.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
+
+/* Ignore these interface names which look like ethernet but are virtual or
+ * just won't work without explicit configuration. */
+static const char * const ifnames_ignore[] = {
+ "bridge",
+ "fwe", /* Firewire */
+ "fwip", /* Firewire */
+ "tap",
+ "vether",
+ "xvif", /* XEN DOM0 -> guest interface */
+ NULL
+};
+
+struct priv {
+ int pf_inet6_fd;
+};
+
+struct rtm
+{
+ struct rt_msghdr hdr;
+ char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX];
+};
+
+int
+os_init(void)
+{
+ return 0;
+}
+
+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_opensockets_os(struct dhcpcd_ctx *ctx)
+{
+ struct priv *priv;
+ int n;
+#if defined(RO_MSGFILTER) || defined(ROUTE_MSGFILTER)
+ unsigned char msgfilter[] = {
+ RTM_IFINFO,
+#ifdef RTM_IFANNOUNCE
+ RTM_IFANNOUNCE,
+#endif
+ RTM_ADD, RTM_CHANGE, RTM_DELETE, RTM_MISS,
+#ifdef RTM_CHGADDR
+ RTM_CHGADDR,
+#endif
+ RTM_NEWADDR, RTM_DELADDR
+ };
+#ifdef ROUTE_MSGFILTER
+ unsigned int i, msgfilter_mask;
+#endif
+#endif
+
+ if ((priv = malloc(sizeof(*priv))) == NULL)
+ return -1;
+ ctx->priv = priv;
+
+#ifdef INET6
+ priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+#ifdef PRIVSEP_RIGHTS
+ if (IN_PRIVSEP(ctx))
+ ps_rights_limit_ioctl(priv->pf_inet6_fd);
+#endif
+ /* Don't return an error so we at least work on kernels witout INET6
+ * even though we expect INET6 support.
+ * We will fail noisily elsewhere anyway. */
+#else
+ priv->pf_inet6_fd = -1;
+#endif
+
+ ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_CXNB, AF_UNSPEC);
+ if (ctx->link_fd == -1)
+ return -1;
+
+#ifdef SO_RERROR
+ n = 1;
+ if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RERROR, &n,sizeof(n)) == -1)
+ logerr("%s: SO_RERROR", __func__);
+#endif
+
+ /* Ignore our own route(4) messages.
+ * Sadly there is no way of doing this for route(4) messages
+ * generated from addresses we add/delete. */
+ n = 0;
+ if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK,
+ &n, sizeof(n)) == -1)
+ logerr("%s: SO_USELOOPBACK", __func__);
+
+#if defined(RO_MSGFILTER)
+ if (setsockopt(ctx->link_fd, PF_ROUTE, RO_MSGFILTER,
+ &msgfilter, sizeof(msgfilter)) == -1)
+ logerr(__func__);
+#elif defined(ROUTE_MSGFILTER)
+ /* Convert the array into a bitmask. */
+ msgfilter_mask = 0;
+ for (i = 0; i < __arraycount(msgfilter); i++)
+ msgfilter_mask |= ROUTE_FILTER(msgfilter[i]);
+ if (setsockopt(ctx->link_fd, PF_ROUTE, ROUTE_MSGFILTER,
+ &msgfilter_mask, sizeof(msgfilter_mask)) == -1)
+ logerr(__func__);
+#else
+#warning kernel does not support route message filtering
+#endif
+
+#ifdef PRIVSEP_RIGHTS
+ /* We need to getsockopt for SO_RCVBUF and
+ * setsockopt for RO_MISSFILTER. */
+ if (IN_PRIVSEP(ctx))
+ ps_rights_limit_fd_sockopt(ctx->link_fd);
+#endif
+
+ return 0;
+}
+
+void
+if_closesockets_os(struct dhcpcd_ctx *ctx)
+{
+ struct priv *priv;
+
+ priv = (struct priv *)ctx->priv;
+ if (priv->pf_inet6_fd != -1)
+ close(priv->pf_inet6_fd);
+ free(priv);
+ ctx->priv = NULL;
+ free(ctx->rt_missfilter);
+}
+
+#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */
+static int
+if_ioctllink(struct dhcpcd_ctx *ctx, unsigned long req, void *data, size_t len)
+{
+ int s;
+ int retval;
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP)
+ return (int)ps_root_ioctllink(ctx, req, data, len);
+#else
+ UNUSED(ctx);
+#endif
+
+ s = socket(PF_LINK, SOCK_DGRAM, 0);
+ if (s == -1)
+ return -1;
+ retval = ioctl(s, req, data, len);
+ close(s);
+ return retval;
+}
+#endif
+
+int
+if_setmac(struct interface *ifp, void *mac, uint8_t maclen)
+{
+
+ if (ifp->hwlen != maclen) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */
+ struct if_laddrreq iflr = { .flags = IFLR_ACTIVE };
+ struct sockaddr_dl *sdl = satosdl(&iflr.addr);
+ int retval;
+
+ strlcpy(iflr.iflr_name, ifp->name, sizeof(iflr.iflr_name));
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_len = sizeof(*sdl);
+ sdl->sdl_alen = maclen;
+ memcpy(LLADDR(sdl), mac, maclen);
+ retval = if_ioctllink(ifp->ctx, SIOCALIFADDR, &iflr, sizeof(iflr));
+
+ /* Try and remove the old address */
+ memcpy(LLADDR(sdl), ifp->hwaddr, ifp->hwlen);
+ if_ioctllink(ifp->ctx, SIOCDLIFADDR, &iflr, sizeof(iflr));
+
+ return retval;
+#else
+ struct ifreq ifr = {
+ .ifr_addr.sa_family = AF_LINK,
+ .ifr_addr.sa_len = maclen,
+ };
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ memcpy(ifr.ifr_addr.sa_data, mac, maclen);
+ return if_ioctl(ifp->ctx, SIOCSIFLLADDR, &ifr, sizeof(ifr));
+#endif
+}
+
+static bool
+if_ignore1(const char *drvname)
+{
+ const char * const *p;
+
+ for (p = ifnames_ignore; *p; p++) {
+ if (strcmp(*p, drvname) == 0)
+ return true;
+ }
+ return false;
+}
+
+#ifdef SIOCGIFGROUP
+int
+if_ignoregroup(int s, const char *ifname)
+{
+ struct ifgroupreq ifgr = { .ifgr_len = 0 };
+ struct ifg_req *ifg;
+ size_t ifg_len;
+
+ /* Sadly it is possible to remove the device name
+ * from the interface groups, but hopefully this
+ * will be very unlikely.... */
+
+ strlcpy(ifgr.ifgr_name, ifname, sizeof(ifgr.ifgr_name));
+ if (ioctl(s, SIOCGIFGROUP, &ifgr) == -1 ||
+ (ifgr.ifgr_groups = malloc(ifgr.ifgr_len)) == NULL ||
+ ioctl(s, SIOCGIFGROUP, &ifgr) == -1)
+ {
+ logerr(__func__);
+ return -1;
+ }
+
+ for (ifg = ifgr.ifgr_groups, ifg_len = ifgr.ifgr_len;
+ ifg && ifg_len >= sizeof(*ifg);
+ ifg++, ifg_len -= sizeof(*ifg))
+ {
+ if (if_ignore1(ifg->ifgrq_group))
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+bool
+if_ignore(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ struct if_spec spec;
+
+ if (if_nametospec(ifname, &spec) != 0)
+ return false;
+
+ if (if_ignore1(spec.drvname))
+ return true;
+
+#ifdef SIOCGIFGROUP
+#if defined(PRIVSEP) && defined(HAVE_PLEDGE)
+ if (IN_PRIVSEP(ctx))
+ return ps_root_ifignoregroup(ctx, ifname) == 1 ? true : false;
+#endif
+ else
+ return if_ignoregroup(ctx->pf_inet_fd, ifname) == 1 ?
+ true : false;
+#else
+ UNUSED(ctx);
+ return false;
+#endif
+}
+
+static int if_indirect_ioctl(struct dhcpcd_ctx *ctx,
+ const char *ifname, unsigned long cmd, void *data, size_t len)
+{
+ struct ifreq ifr = { .ifr_flags = 0 };
+
+#if defined(PRIVSEP) && (defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE))
+ if (IN_PRIVSEP(ctx))
+ return (int)ps_root_indirectioctl(ctx, cmd, ifname, data, len);
+#else
+ UNUSED(len);
+#endif
+
+ strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+ ifr.ifr_data = data;
+ return ioctl(ctx->pf_inet_fd, cmd, &ifr);
+}
+
+int
+if_carrier(struct interface *ifp, const void *ifadata)
+{
+ const struct if_data *ifi = ifadata;
+
+ /*
+ * Every BSD returns this and it is the sole source of truth.
+ * Not all BSD's support SIOCGIFDATA and not all interfaces
+ * support SIOCGIFMEDIA.
+ */
+ assert(ifadata != NULL);
+
+ if (ifi->ifi_link_state >= LINK_STATE_UP)
+ return LINK_UP;
+ if (ifi->ifi_link_state == LINK_STATE_UNKNOWN) {
+ /*
+ * Work around net80211 issues in some BSDs.
+ * Wireless MUST support link state change.
+ */
+ if (ifp->wireless)
+ return LINK_DOWN;
+ return LINK_UNKNOWN;
+ }
+ return LINK_DOWN;
+}
+
+bool
+if_roaming(struct interface *ifp)
+{
+
+/* Check for NetBSD as a safety measure.
+ * If other BSD's gain IN_IFF_TENTATIVE check they re-do DAD
+ * when the carrier comes up again. */
+#if defined(IN_IFF_TENTATIVE) && defined(__NetBSD__)
+ return ifp->flags & IFF_UP && ifp->carrier == LINK_DOWN;
+#else
+ UNUSED(ifp);
+ return false;
+#endif
+}
+
+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;
+}
+
+static int
+if_getssid1(struct dhcpcd_ctx *ctx, const char *ifname, void *ssid)
+{
+ int retval = -1;
+#if defined(SIOCG80211NWID)
+ struct ieee80211_nwid nwid;
+#elif defined(IEEE80211_IOC_SSID)
+ struct ieee80211req ireq;
+ char nwid[IEEE80211_NWID_LEN];
+#endif
+
+#if defined(SIOCG80211NWID) /* NetBSD */
+ memset(&nwid, 0, sizeof(nwid));
+ if (if_indirect_ioctl(ctx, ifname, SIOCG80211NWID,
+ &nwid, sizeof(nwid)) == 0)
+ {
+ if (ssid == NULL)
+ retval = nwid.i_len;
+ else if (nwid.i_len > IF_SSIDLEN)
+ errno = ENOBUFS;
+ else {
+ retval = nwid.i_len;
+ memcpy(ssid, nwid.i_nwid, nwid.i_len);
+ }
+ }
+#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(ctx->pf_inet_fd, SIOCG80211, &ireq) == 0) {
+ if (ssid == NULL)
+ retval = ireq.i_len;
+ else if (ireq.i_len > IF_SSIDLEN)
+ errno = ENOBUFS;
+ else {
+ retval = ireq.i_len;
+ memcpy(ssid, nwid, ireq.i_len);
+ }
+ }
+#else
+ errno = ENOSYS;
+#endif
+
+ return retval;
+}
+
+int
+if_getssid(struct interface *ifp)
+{
+ int r;
+
+ r = if_getssid1(ifp->ctx, ifp->name, ifp->ssid);
+ if (r != -1)
+ ifp->ssid_len = (unsigned int)r;
+ else
+ ifp->ssid_len = 0;
+ ifp->ssid[ifp->ssid_len] = '\0';
+ 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(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ int r;
+ struct ifmediareq ifmr = { .ifm_active = 0 };
+
+ 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, ifname, NULL) == -1)
+ return 1;
+ }
+ return 0;
+}
+
+unsigned short
+if_vlanid(const struct interface *ifp)
+{
+#ifdef SIOCGETVLAN
+ struct vlanreq vlr = { .vlr_tag = 0 };
+
+ if (if_indirect_ioctl(ifp->ctx, ifp->name, SIOCGETVLAN,
+ &vlr, sizeof(vlr)) != 0)
+ return 0; /* 0 means no VLANID */
+ return vlr.vlr_tag;
+#elif defined(SIOCGVNETID)
+ struct ifreq ifr = { .ifr_vnetid = 0 };
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGVNETID, &ifr) != 0)
+ return 0; /* 0 means no VLANID */
+ return ifr.ifr_vnetid;
+#else
+ UNUSED(ifp);
+ return 0; /* 0 means no VLANID */
+#endif
+}
+
+static int
+get_addrs(int type, const void *data, size_t data_len,
+ const struct sockaddr **sa)
+{
+ const char *cp, *ep;
+ int i;
+
+ cp = data;
+ ep = cp + data_len;
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (type & (1 << i)) {
+ if (cp >= ep) {
+ errno = EINVAL;
+ return -1;
+ }
+ sa[i] = (const struct sockaddr *)cp;
+ RT_ADVANCE(cp, sa[i]);
+ } else
+ sa[i] = NULL;
+ }
+
+ return 0;
+}
+
+static struct interface *
+if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl)
+{
+
+ if (sdl->sdl_index)
+ return if_findindex(ctx->ifaces, sdl->sdl_index);
+
+ 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);
+ }
+ if (sdl->sdl_alen) {
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (ifp->hwlen == sdl->sdl_alen &&
+ memcmp(ifp->hwaddr,
+ sdl->sdl_data, sdl->sdl_alen) == 0)
+ return ifp;
+ }
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+static struct interface *
+if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa)
+{
+ if (sa == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ switch (sa->sa_family) {
+ case AF_LINK:
+ {
+ const struct sockaddr_dl *sdl;
+
+ sdl = (const void *)sa;
+ return if_findsdl(ctx, sdl);
+ }
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+ struct ipv4_addr *ia;
+
+ sin = (const void *)sa;
+ if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr)))
+ return ia->iface;
+ if ((ia = ipv4_findmaskbrd(ctx, &sin->sin_addr)))
+ return ia->iface;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin;
+ unsigned int scope;
+ struct ipv6_addr *ia;
+
+ sin = (const void *)sa;
+ scope = ipv6_getscope(sin);
+ if (scope != 0)
+ return if_findindex(ctx->ifaces, scope);
+ if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr)))
+ return ia->iface;
+ break;
+ }
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+static void
+if_copysa(struct sockaddr *dst, const struct sockaddr *src)
+{
+
+ assert(dst != NULL);
+ assert(src != NULL);
+
+ memcpy(dst, src, src->sa_len);
+#if defined(INET6) && defined(__KAME__)
+ if (dst->sa_family == AF_INET6) {
+ struct in6_addr *in6;
+
+ in6 = &satosin6(dst)->sin6_addr;
+ if (IN6_IS_ADDR_LINKLOCAL(in6))
+ in6->s6_addr[2] = in6->s6_addr[3] = '\0';
+ }
+#endif
+}
+
+int
+if_route(unsigned char cmd, const struct rt *rt)
+{
+ struct dhcpcd_ctx *ctx;
+ struct rtm rtmsg;
+ struct rt_msghdr *rtm = &rtmsg.hdr;
+ char *bp = rtmsg.buffer;
+ struct sockaddr_dl sdl;
+ bool gateway_unspec;
+
+ assert(rt != NULL);
+ assert(rt->rt_ifp != NULL);
+ assert(rt->rt_ifp->ctx != NULL);
+ ctx = rt->rt_ifp->ctx;
+
+#define ADDSA(sa) do { \
+ memcpy(bp, (sa), (sa)->sa_len); \
+ bp += RT_ROUNDUP((sa)->sa_len); \
+ } while (0 /* CONSTCOND */)
+
+ memset(&rtmsg, 0, sizeof(rtmsg));
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_type = cmd;
+#ifdef __OpenBSD__
+ rtm->rtm_pid = getpid();
+#endif
+ rtm->rtm_seq = ++ctx->seq;
+ rtm->rtm_flags = (int)rt->rt_flags;
+ rtm->rtm_addrs = RTA_DST;
+#ifdef RTF_PINNED
+ if (cmd != RTM_ADD)
+ rtm->rtm_flags |= RTF_PINNED;
+#endif
+
+ gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
+
+ if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
+ bool netmask_bcast = sa_is_allones(&rt->rt_netmask);
+
+ rtm->rtm_flags |= RTF_UP;
+ rtm->rtm_addrs |= RTA_GATEWAY;
+ if (!(rtm->rtm_flags & RTF_REJECT) &&
+ !sa_is_loopback(&rt->rt_gateway))
+ {
+ rtm->rtm_index = (unsigned short)rt->rt_ifp->index;
+/*
+ * OpenBSD rejects the message for on-link routes.
+ * FreeBSD-12 kernel apparently panics.
+ * I can't replicate the panic, but better safe than sorry!
+ * https://roy.marples.name/archives/dhcpcd-discuss/0002286.html
+ *
+ * Neither OS currently allows IPv6 address sharing anyway, so let's
+ * try to encourage someone to fix that by logging a waring during compile.
+ */
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#warning kernel does not allow IPv6 address sharing
+ if (!gateway_unspec || rt->rt_dest.sa_family!=AF_INET6)
+#endif
+ rtm->rtm_addrs |= RTA_IFP;
+ if (!sa_is_unspecified(&rt->rt_ifa))
+ rtm->rtm_addrs |= RTA_IFA;
+ }
+ if (netmask_bcast)
+ rtm->rtm_flags |= RTF_HOST;
+ /* Network routes are cloning or connected if supported.
+ * All other routes are static. */
+ if (gateway_unspec) {
+#ifdef RTF_CLONING
+ rtm->rtm_flags |= RTF_CLONING;
+#endif
+#ifdef RTF_CONNECTED
+ rtm->rtm_flags |= RTF_CONNECTED;
+#endif
+#ifdef RTP_CONNECTED
+ rtm->rtm_priority = RTP_CONNECTED;
+#endif
+#ifdef RTF_CLONING
+ if (netmask_bcast) {
+ /*
+ * 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->rtm_flags &= ~RTF_HOST;
+ }
+#endif
+ } else
+ rtm->rtm_flags |= RTF_GATEWAY;
+
+ if (rt->rt_dflags & RTDF_STATIC)
+ rtm->rtm_flags |= RTF_STATIC;
+
+ if (rt->rt_mtu != 0) {
+ rtm->rtm_inits |= RTV_MTU;
+ rtm->rtm_rmx.rmx_mtu = rt->rt_mtu;
+ }
+ }
+
+ if (!(rtm->rtm_flags & RTF_HOST))
+ rtm->rtm_addrs |= RTA_NETMASK;
+
+ if_linkaddr(&sdl, rt->rt_ifp);
+
+ ADDSA(&rt->rt_dest);
+
+ if (rtm->rtm_addrs & RTA_GATEWAY) {
+ if (gateway_unspec)
+ ADDSA((struct sockaddr *)&sdl);
+ else {
+ union sa_ss gateway;
+
+ if_copysa(&gateway.sa, &rt->rt_gateway);
+#ifdef INET6
+ if (gateway.sa.sa_family == AF_INET6)
+ ipv6_setscope(&gateway.sin6, rt->rt_ifp->index);
+#endif
+ ADDSA(&gateway.sa);
+ }
+ }
+
+ if (rtm->rtm_addrs & RTA_NETMASK)
+ ADDSA(&rt->rt_netmask);
+
+ if (rtm->rtm_addrs & RTA_IFP)
+ ADDSA((struct sockaddr *)&sdl);
+
+ if (rtm->rtm_addrs & RTA_IFA)
+ ADDSA(&rt->rt_ifa);
+
+#undef ADDSA
+
+ rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm);
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP) {
+ if (ps_root_route(ctx, rtm, rtm->rtm_msglen) == -1)
+ return -1;
+ return 0;
+ }
+#endif
+ if (write(ctx->link_fd, rtm, rtm->rtm_msglen) == -1)
+ return -1;
+ return 0;
+}
+
+static bool
+if_realroute(const struct rt_msghdr *rtm)
+{
+
+#ifdef RTF_CLONED
+ if (rtm->rtm_flags & RTF_CLONED)
+ return false;
+#endif
+#ifdef RTF_WASCLONED
+ if (rtm->rtm_flags & RTF_WASCLONED)
+ return false;
+#endif
+#ifdef RTF_LOCAL
+ if (rtm->rtm_flags & RTF_LOCAL)
+ return false;
+#endif
+#ifdef RTF_BROADCAST
+ if (rtm->rtm_flags & RTF_BROADCAST)
+ return false;
+#endif
+ return true;
+}
+
+static int
+if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm)
+{
+ const struct sockaddr *rti_info[RTAX_MAX];
+
+ if (!(rtm->rtm_addrs & RTA_DST)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (rtm->rtm_type != RTM_MISS && !(rtm->rtm_addrs & RTA_GATEWAY)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm),
+ rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1)
+ return -1;
+ memset(rt, 0, sizeof(*rt));
+
+ rt->rt_flags = (unsigned int)rtm->rtm_flags;
+ if_copysa(&rt->rt_dest, rti_info[RTAX_DST]);
+ if (rtm->rtm_addrs & RTA_NETMASK) {
+ if_copysa(&rt->rt_netmask, rti_info[RTAX_NETMASK]);
+ if (rt->rt_netmask.sa_family == 255) /* Why? */
+ rt->rt_netmask.sa_family = rt->rt_dest.sa_family;
+ }
+
+ /* dhcpcd likes an unspecified gateway to indicate via the link.
+ * However we need to know if gateway was a link with an address. */
+ if (rtm->rtm_addrs & RTA_GATEWAY) {
+ if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) {
+ const struct sockaddr_dl *sdl;
+
+ sdl = (const struct sockaddr_dl*)
+ (const void *)rti_info[RTAX_GATEWAY];
+ if (sdl->sdl_alen != 0)
+ rt->rt_dflags |= RTDF_GATELINK;
+ } else if (rtm->rtm_flags & RTF_GATEWAY)
+ if_copysa(&rt->rt_gateway, rti_info[RTAX_GATEWAY]);
+ }
+
+ if (rtm->rtm_addrs & RTA_IFA)
+ if_copysa(&rt->rt_ifa, rti_info[RTAX_IFA]);
+
+ rt->rt_mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;
+
+ if (rtm->rtm_index)
+ rt->rt_ifp = if_findindex(ctx->ifaces, rtm->rtm_index);
+ else if (rtm->rtm_addrs & RTA_IFP)
+ rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_IFP]);
+ else if (rtm->rtm_addrs & RTA_GATEWAY)
+ rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_GATEWAY]);
+ else
+ rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_DST]);
+
+ if (rt->rt_ifp == NULL && rtm->rtm_type == RTM_MISS)
+ rt->rt_ifp = if_find(ctx->ifaces, "lo0");
+
+ if (rt->rt_ifp == NULL) {
+ errno = ESRCH;
+ return -1;
+ }
+ return 0;
+}
+
+int
+if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af)
+{
+ struct rt_msghdr *rtm;
+ int mib[6];
+ size_t needed;
+ char *buf, *p, *end;
+ struct rt rt, *rtn;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = af;
+ 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) {
+ free(buf);
+ return -1;
+ }
+
+ end = buf + needed;
+ for (p = buf; p < end; p += rtm->rtm_msglen) {
+ rtm = (void *)p;
+ if (p + rtm->rtm_msglen >= end) {
+ errno = EINVAL;
+ break;
+ }
+ if (!if_realroute(rtm))
+ continue;
+ if (if_copyrt(ctx, &rt, rtm) != 0)
+ continue;
+ if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
+ logerr(__func__);
+ break;
+ }
+ memcpy(rtn, &rt, sizeof(*rtn));
+ if (rb_tree_insert_node(kroutes, rtn) != rtn)
+ rt_free(rtn);
+ }
+ free(buf);
+ return p == end ? 0 : -1;
+}
+
+#ifdef INET
+int
+if_address(unsigned char cmd, const struct ipv4_addr *ia)
+{
+ int r;
+ struct in_aliasreq ifra;
+ struct dhcpcd_ctx *ctx = ia->iface->ctx;
+
+ memset(&ifra, 0, sizeof(ifra));
+ strlcpy(ifra.ifra_name, ia->iface->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, &ia->addr);
+ ADDADDR(&ifra.ifra_mask, &ia->mask);
+ if (cmd == RTM_NEWADDR && ia->brd.s_addr != INADDR_ANY)
+ ADDADDR(&ifra.ifra_broadaddr, &ia->brd);
+#undef ADDADDR
+
+ r = if_ioctl(ctx,
+ cmd == RTM_DELADDR ? SIOCDIFADDR : SIOCAIFADDR, &ifra,sizeof(ifra));
+ return r;
+}
+
+#if !(defined(HAVE_IFADDRS_ADDRFLAGS) && defined(HAVE_IFAM_ADDRFLAGS))
+int
+if_addrflags(const struct interface *ifp, const struct in_addr *addr,
+ __unused const char *alias)
+{
+#ifdef SIOCGIFAFLAG_IN
+ struct ifreq ifr;
+ struct sockaddr_in *sin;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ sin = (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
+ UNUSED(ifp);
+ UNUSED(addr);
+ return 0;
+#endif
+}
+#endif
+#endif /* INET */
+
+#ifdef INET6
+static int
+if_ioctl6(struct dhcpcd_ctx *ctx, unsigned long req, void *data, size_t len)
+{
+ struct priv *priv;
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP)
+ return (int)ps_root_ioctl6(ctx, req, data, len);
+#endif
+
+ priv = ctx->priv;
+ return ioctl(priv->pf_inet6_fd, req, data, len);
+}
+
+int
+if_address6(unsigned char cmd, const struct ipv6_addr *ia)
+{
+ struct in6_aliasreq ifa = { .ifra_flags = 0 };
+ struct in6_addr mask;
+ struct dhcpcd_ctx *ctx = ia->iface->ctx;
+
+ strlcpy(ifa.ifra_name, ia->iface->name, sizeof(ifa.ifra_name));
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+ /* This is a bug - the kernel should work this out. */
+ if (ia->addr_flags & IN6_IFF_TENTATIVE)
+ ifa.ifra_flags |= IN6_IFF_TENTATIVE;
+#endif
+#if (defined(__NetBSD__) || defined(__OpenBSD__)) && \
+ (defined(IPV6CTL_ACCEPT_RTADV) || defined(ND6_IFF_ACCEPT_RTADV))
+ /* These kernels don't accept userland setting IN6_IFF_AUTOCONF */
+#else
+ if (ia->flags & IPV6_AF_AUTOCONF)
+ ifa.ifra_flags |= IN6_IFF_AUTOCONF;
+#endif
+#ifdef IPV6_MANAGETEMPADDR
+ 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);
+ ipv6_setscope(&ifa.ifra_addr, ia->iface->index);
+ ipv6_mask(&mask, ia->prefix_len);
+ ADDADDR(&ifa.ifra_prefixmask, &mask);
+
+#undef ADDADDR
+
+ /*
+ * Every BSD kernel wants to add the prefix of the address to it's
+ * list of RA received prefixes.
+ * THIS IS WRONG because there (as the comments in the kernel state)
+ * is no API for managing prefix lifetime and the kernel should not
+ * pretend it's from a RA either.
+ *
+ * The issue is that the very first assigned prefix will inherit the
+ * lifetime of the address, but any subsequent alteration of the
+ * address OR it's lifetime will not affect the prefix lifetime.
+ * As such, we cannot stop the prefix from timing out and then
+ * constantly removing the prefix route dhcpcd is capable of adding
+ * in it's absense.
+ *
+ * What we can do to mitigate the issue is to add the address with
+ * infinite lifetimes, so the prefix route will never time out.
+ * Once done, we can then set lifetimes on the address and all is good.
+ * The downside of this approach is that we need to manually remove
+ * the kernel route because it has no lifetime, but this is OK as
+ * dhcpcd will handle this too.
+ *
+ * This issue is discussed on the NetBSD mailing lists here:
+ * http://mail-index.netbsd.org/tech-net/2016/08/05/msg006044.html
+ *
+ * Fixed in NetBSD-7.99.36
+ * NOT fixed in FreeBSD - bug 195197
+ * Fixed in OpenBSD-5.9
+ */
+
+#if !((defined(__NetBSD_Version__) && __NetBSD_Version__ >= 799003600) || \
+ (defined(__OpenBSD__) && OpenBSD >= 201605))
+ if (cmd == RTM_NEWADDR && !(ia->flags & IPV6_AF_ADDED)) {
+ ifa.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+ ifa.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+ (void)if_ioctl6(ctx, SIOCAIFADDR_IN6, &ifa, sizeof(ifa));
+ }
+#endif
+
+#if defined(__OpenBSD__) && OpenBSD <= 201705
+ /* BUT OpenBSD older than 6.2 does not reset the address lifetime
+ * for subsequent calls...
+ * Luckily dhcpcd will remove the lease when it expires so
+ * just set an infinite lifetime, unless a temporary address. */
+ if (ifa.ifra_flags & IN6_IFF_PRIVACY) {
+ ifa.ifra_lifetime.ia6t_vltime = ia->prefix_vltime;
+ ifa.ifra_lifetime.ia6t_pltime = ia->prefix_pltime;
+ } else {
+ ifa.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+ ifa.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+ }
+#else
+ ifa.ifra_lifetime.ia6t_vltime = ia->prefix_vltime;
+ ifa.ifra_lifetime.ia6t_pltime = ia->prefix_pltime;
+#endif
+
+ return if_ioctl6(ctx,
+ cmd == RTM_DELADDR ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6,
+ &ifa, sizeof(ifa));
+}
+
+int
+if_addrflags6(const struct interface *ifp, const struct in6_addr *addr,
+ __unused const char *alias)
+{
+ int flags;
+ struct in6_ifreq ifr6;
+ struct priv *priv;
+
+ 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;
+ ipv6_setscope(&ifr6.ifr_addr, ifp->index);
+ priv = (struct priv *)ifp->ctx->priv;
+ if (ioctl(priv->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;
+ struct priv *priv;
+
+ 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;
+ ipv6_setscope(&ifr6.ifr_addr, ia->iface->index);
+ priv = (struct priv *)ia->iface->ctx->priv;
+ if (ioctl(priv->pf_inet6_fd, SIOCGIFALIFETIME_IN6, &ifr6) == -1)
+ return -1;
+ clock_gettime(CLOCK_MONOTONIC, &ia->created);
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+ t = ia->created.tv_sec;
+#else
+ t = time(NULL);
+#endif
+
+ 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 */
+ ia->created.tv_sec -= lifetime->ia6t_vltime - ia->prefix_vltime;
+ } else
+ ia->prefix_vltime = ND6_INFINITE_LIFETIME;
+ return 0;
+}
+#endif
+
+static int
+if_announce(struct dhcpcd_ctx *ctx, const struct if_announcemsghdr *ifan)
+{
+
+ if (ifan->ifan_msglen < sizeof(*ifan)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch(ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ return dhcpcd_handleinterface(ctx, 1, ifan->ifan_name);
+ case IFAN_DEPARTURE:
+ return dhcpcd_handleinterface(ctx, -1, ifan->ifan_name);
+ }
+
+ return 0;
+}
+
+static int
+if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm)
+{
+ struct interface *ifp;
+ int link_state;
+
+ if (ifm->ifm_msglen < sizeof(*ifm)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL)
+ return 0;
+
+ link_state = if_carrier(ifp, &ifm->ifm_data);
+ dhcpcd_handlecarrier(ifp, link_state, (unsigned int)ifm->ifm_flags);
+ return 0;
+}
+
+static int
+if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
+{
+ struct rt rt;
+
+ if (rtm->rtm_msglen < sizeof(*rtm)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Ignore errors. */
+ if (rtm->rtm_errno != 0)
+ return 0;
+
+ /* Ignore messages from ourself. */
+#ifdef PRIVSEP
+ if (ctx->ps_root_pid != 0) {
+ if (rtm->rtm_pid == ctx->ps_root_pid)
+ return 0;
+ }
+#endif
+
+ if (if_copyrt(ctx, &rt, rtm) == -1)
+ return errno == ENOTSUP ? 0 : -1;
+
+#ifdef INET6
+ /*
+ * BSD announces host routes.
+ * As such, we should be notified of reachability by its
+ * existance with a hardware address.
+ * Ensure we don't call this for a newly incomplete state.
+ */
+ if (rt.rt_dest.sa_family == AF_INET6 &&
+ (rt.rt_flags & RTF_HOST || rtm->rtm_type == RTM_MISS) &&
+ !(rtm->rtm_type == RTM_ADD && !(rt.rt_dflags & RTDF_GATELINK)))
+ {
+ bool reachable;
+
+ reachable = (rtm->rtm_type == RTM_ADD ||
+ rtm->rtm_type == RTM_CHANGE) &&
+ rt.rt_dflags & RTDF_GATELINK;
+ ipv6nd_neighbour(ctx, &rt.rt_ss_dest.sin6.sin6_addr, reachable);
+ }
+#endif
+
+ if (rtm->rtm_type != RTM_MISS && if_realroute(rtm))
+ rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid);
+ return 0;
+}
+
+static int
+if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam)
+{
+ struct interface *ifp;
+ const struct sockaddr *rti_info[RTAX_MAX];
+ int flags;
+ pid_t pid;
+
+ if (ifam->ifam_msglen < sizeof(*ifam)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#ifdef HAVE_IFAM_PID
+ /* Ignore address deletions from ourself.
+ * We need to process address flag changes though. */
+ if (ifam->ifam_type == RTM_DELADDR) {
+#ifdef PRIVSEP
+ if (ctx->ps_root_pid != 0) {
+ if (ifam->ifam_pid == ctx->ps_root_pid)
+ return 0;
+ } else
+#endif
+ /* address management is done via ioctl,
+ * so SO_USELOOPBACK has no effect,
+ * so we do need to check the pid. */
+ if (ifam->ifam_pid == getpid())
+ return 0;
+ }
+ pid = ifam->ifam_pid;
+#else
+ pid = 0;
+#endif
+
+ if (~ifam->ifam_addrs & RTA_IFA)
+ return 0;
+ if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL)
+ return 0;
+
+ if (get_addrs(ifam->ifam_addrs, (const char *)ifam + sizeof(*ifam),
+ ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1)
+ return -1;
+
+ switch (rti_info[RTAX_IFA]->sa_family) {
+ case AF_LINK:
+ {
+ struct sockaddr_dl sdl;
+
+#ifdef RTM_CHGADDR
+ if (ifam->ifam_type != RTM_CHGADDR)
+ break;
+#else
+ if (ifam->ifam_type != RTM_NEWADDR)
+ break;
+#endif
+ memcpy(&sdl, rti_info[RTAX_IFA], rti_info[RTAX_IFA]->sa_len);
+ dhcpcd_handlehwaddr(ifp, ifp->hwtype,
+ CLLADDR(&sdl), sdl.sdl_alen);
+ break;
+ }
+#ifdef INET
+ case AF_INET:
+ case 255: /* FIXME: Why 255? */
+ {
+ const struct sockaddr_in *sin;
+ struct in_addr addr, mask, bcast;
+
+ sin = (const void *)rti_info[RTAX_IFA];
+ addr.s_addr = sin != NULL && sin->sin_family == AF_INET ?
+ sin->sin_addr.s_addr : INADDR_ANY;
+ sin = (const void *)rti_info[RTAX_NETMASK];
+ mask.s_addr = sin != NULL && sin->sin_family == AF_INET ?
+ sin->sin_addr.s_addr : INADDR_ANY;
+ sin = (const void *)rti_info[RTAX_BRD];
+ bcast.s_addr = sin != NULL && sin->sin_family == AF_INET ?
+ sin->sin_addr.s_addr : INADDR_ANY;
+
+ /*
+ * NetBSD-7 and older send an invalid broadcast address.
+ * So we need to query the actual address to get
+ * the right one.
+ * We can also use this to test if the address
+ * has really been added or deleted.
+ */
+#ifdef SIOCGIFALIAS
+ struct in_aliasreq ifra;
+
+ memset(&ifra, 0, sizeof(ifra));
+ strlcpy(ifra.ifra_name, ifp->name, sizeof(ifra.ifra_name));
+ ifra.ifra_addr.sin_family = AF_INET;
+ ifra.ifra_addr.sin_len = sizeof(ifra.ifra_addr);
+ ifra.ifra_addr.sin_addr = addr;
+ if (ioctl(ctx->pf_inet_fd, SIOCGIFALIAS, &ifra) == -1) {
+ if (errno != ENXIO && errno != EADDRNOTAVAIL)
+ logerr("%s: SIOCGIFALIAS", __func__);
+ if (ifam->ifam_type != RTM_DELADDR)
+ break;
+ } else {
+ if (ifam->ifam_type == RTM_DELADDR)
+ break;
+#if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000
+ bcast = ifra.ifra_broadaddr.sin_addr;
+#endif
+ }
+#else
+#warning No SIOCGIFALIAS support
+ /*
+ * No SIOCGIFALIAS? That sucks!
+ * This makes this call very heavy weight, but we
+ * really need to know if the message is late or not.
+ */
+ const struct sockaddr *sa;
+ struct ifaddrs *ifaddrs = NULL, *ifa;
+
+ sa = rti_info[RTAX_IFA];
+#ifdef PRIVSEP_GETIFADDRS
+ if (IN_PRIVSEP(ctx)) {
+ if (ps_root_getifaddrs(ctx, &ifaddrs) == -1) {
+ logerr("ps_root_getifaddrs");
+ break;
+ }
+ } else
+#endif
+ if (getifaddrs(&ifaddrs) == -1) {
+ logerr("getifaddrs");
+ break;
+ }
+ for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
+ if (sa_cmp(ifa->ifa_addr, sa) == 0 &&
+ strcmp(ifa->ifa_name, ifp->name) == 0)
+ break;
+ }
+#ifdef PRIVSEP_GETIFADDRS
+ if (IN_PRIVSEP(ctx))
+ free(ifaddrs);
+ else
+#endif
+ freeifaddrs(ifaddrs);
+ if (ifam->ifam_type == RTM_DELADDR) {
+ if (ifa != NULL)
+ break;
+ } else {
+ if (ifa == NULL)
+ break;
+ }
+#endif
+
+#ifdef HAVE_IFAM_ADDRFLAGS
+ flags = ifam->ifam_addrflags;
+#else
+ flags = 0;
+#endif
+
+ ipv4_handleifa(ctx, ifam->ifam_type, NULL, ifp->name,
+ &addr, &mask, &bcast, flags, pid);
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct in6_addr addr6, mask6;
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = (const void *)rti_info[RTAX_IFA];
+ addr6 = sin6->sin6_addr;
+ sin6 = (const void *)rti_info[RTAX_NETMASK];
+ mask6 = sin6->sin6_addr;
+
+ /*
+ * If the address was deleted, lets check if it's
+ * a late message and it still exists (maybe modified).
+ * If so, ignore it as deleting an address causes
+ * dhcpcd to drop any lease to which it belongs.
+ * Also check an added address was really added.
+ */
+ flags = if_addrflags6(ifp, &addr6, NULL);
+ if (flags == -1) {
+ if (errno != ENXIO && errno != EADDRNOTAVAIL)
+ logerr("%s: if_addrflags6", __func__);
+ if (ifam->ifam_type != RTM_DELADDR)
+ break;
+ flags = 0;
+ } else if (ifam->ifam_type == RTM_DELADDR)
+ break;
+
+#ifdef __KAME__
+ if (IN6_IS_ADDR_LINKLOCAL(&addr6))
+ /* Remove the scope from the address */
+ addr6.s6_addr[2] = addr6.s6_addr[3] = '\0';
+#endif
+
+ ipv6_handleifa(ctx, ifam->ifam_type, NULL,
+ ifp->name, &addr6, ipv6_prefixlen(&mask6), flags, pid);
+ break;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+static int
+if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
+{
+
+ if (rtm->rtm_version != RTM_VERSION)
+ return 0;
+
+ switch(rtm->rtm_type) {
+#ifdef RTM_IFANNOUNCE
+ case RTM_IFANNOUNCE:
+ return if_announce(ctx, (const void *)rtm);
+#endif
+ case RTM_IFINFO:
+ return if_ifinfo(ctx, (const void *)rtm);
+ case RTM_ADD: /* FALLTHROUGH */
+ case RTM_CHANGE: /* FALLTHROUGH */
+ case RTM_DELETE: /* FALLTHROUGH */
+ case RTM_MISS:
+ return if_rtm(ctx, (const void *)rtm);
+#ifdef RTM_CHGADDR
+ case RTM_CHGADDR: /* FALLTHROUGH */
+#endif
+ case RTM_DELADDR: /* FALLTHROUGH */
+ case RTM_NEWADDR:
+ return if_ifa(ctx, (const void *)rtm);
+#ifdef RTM_DESYNC
+ case RTM_DESYNC:
+ dhcpcd_linkoverflow(ctx);
+#elif !defined(SO_RERROR)
+#warning cannot detect route socket overflow within kernel
+#endif
+ }
+
+ return 0;
+}
+
+static int
+if_missfilter0(struct dhcpcd_ctx *ctx, struct interface *ifp,
+ struct sockaddr *sa)
+{
+ size_t salen = (size_t)RT_ROUNDUP(sa->sa_len);
+ size_t newlen = ctx->rt_missfilterlen + salen;
+ size_t diff = salen - (sa->sa_len);
+ uint8_t *cp;
+
+ if (ctx->rt_missfiltersize < newlen) {
+ void *n = realloc(ctx->rt_missfilter, newlen);
+ if (n == NULL)
+ return -1;
+ ctx->rt_missfilter = n;
+ ctx->rt_missfiltersize = newlen;
+ }
+
+#ifdef INET6
+ if (sa->sa_family == AF_INET6)
+ ipv6_setscope(satosin6(sa), ifp->index);
+#else
+ UNUSED(ifp);
+#endif
+
+ cp = ctx->rt_missfilter + ctx->rt_missfilterlen;
+ memcpy(cp, sa, sa->sa_len);
+ if (diff != 0)
+ memset(cp + sa->sa_len, 0, diff);
+ ctx->rt_missfilterlen += salen;
+
+#ifdef INET6
+ if (sa->sa_family == AF_INET6)
+ ipv6_setscope(satosin6(sa), 0);
+#endif
+
+ return 0;
+}
+
+int
+if_missfilter(struct interface *ifp, struct sockaddr *sa)
+{
+
+ return if_missfilter0(ifp->ctx, ifp, sa);
+}
+
+int
+if_missfilter_apply(struct dhcpcd_ctx *ctx)
+{
+#ifdef RO_MISSFILTER
+ if (ctx->rt_missfilterlen == 0) {
+ struct sockaddr sa = {
+ .sa_family = AF_UNSPEC,
+ .sa_len = sizeof(sa),
+ };
+
+ if (if_missfilter0(ctx, NULL, &sa) == -1)
+ return -1;
+ }
+
+ return setsockopt(ctx->link_fd, PF_ROUTE, RO_MISSFILTER,
+ ctx->rt_missfilter, (socklen_t)ctx->rt_missfilterlen);
+#else
+#warning kernel does not support RTM_MISS DST filtering
+ UNUSED(ctx);
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
+__CTASSERT(offsetof(struct rt_msghdr, rtm_msglen) == 0);
+int
+if_handlelink(struct dhcpcd_ctx *ctx)
+{
+ struct rtm rtm;
+ ssize_t len;
+
+ len = read(ctx->link_fd, &rtm, sizeof(rtm));
+ if (len == -1)
+ return -1;
+ if (len == 0)
+ return 0;
+ if ((size_t)len < sizeof(rtm.hdr.rtm_msglen) ||
+ len != rtm.hdr.rtm_msglen)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ /*
+ * Coverity thinks that the data could be tainted from here.
+ * I have no idea how because the length of the data we read
+ * is guarded by len and checked to match rtm_msglen.
+ * The issue seems to be related to extracting the addresses
+ * at the end of the header, but seems to have no issues with the
+ * equivalent call in if_initrt.
+ */
+ /* coverity[tainted_data] */
+ return if_dispatch(ctx, &rtm.hdr);
+}
+
+#ifndef SYS_NMLN /* OSX */
+# define SYS_NMLN __SYS_NAMELEN
+#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 };
+
+ return sysctl(mib, sizeof(mib) / sizeof(mib[0]), str, &len, NULL, 0);
+}
+
+#ifdef INET6
+#if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \
+ defined(IPV6CTL_FORWARDING)
+#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
+
+int
+if_applyra(const struct ra *rap)
+{
+#ifdef SIOCSIFINFO_IN6
+ struct in6_ndireq nd = { .ndi.chlim = 0 };
+ struct dhcpcd_ctx *ctx = rap->iface->ctx;
+ int error;
+
+ strlcpy(nd.ifname, rap->iface->name, sizeof(nd.ifname));
+
+#ifdef IPV6CTL_ACCEPT_RTADV
+ struct priv *priv = ctx->priv;
+
+ /*
+ * NetBSD changed SIOCSIFINFO_IN6 to NOT set flags when kernel
+ * RA was removed, however both FreeBSD and DragonFlyBSD still do.
+ * linkmtu was also removed.
+ * Hopefully this guard will still work if either remove kernel RA.
+ */
+ if (ioctl(priv->pf_inet6_fd, SIOCGIFINFO_IN6, &nd, sizeof(nd)) == -1)
+ return -1;
+
+ nd.ndi.linkmtu = rap->mtu;
+#endif
+
+ nd.ndi.chlim = rap->hoplimit;
+ nd.ndi.retrans = rap->retrans;
+ nd.ndi.basereachable = rap->reachable;
+ error = if_ioctl6(ctx, SIOCSIFINFO_IN6, &nd, sizeof(nd));
+#ifdef IPV6CTL_ACCEPT_RTADV
+ if (error == -1 && errno == EINVAL) {
+ /*
+ * Very likely that this is caused by a dodgy MTU
+ * setting specific to the interface.
+ * Let's set it to "unspecified" and try again.
+ * Doesn't really matter as we fix the MTU against the
+ * routes we add as not all OS support SIOCSIFINFO_IN6.
+ */
+ nd.ndi.linkmtu = 0;
+ error = if_ioctl6(ctx, SIOCSIFINFO_IN6, &nd, sizeof(nd));
+ }
+#endif
+ return error;
+#else
+#warning OS does not allow setting of RA bits hoplimit, retrans or reachable
+ UNUSED(rap);
+ return 0;
+#endif
+}
+
+#ifndef IPV6CTL_FORWARDING
+#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_forwarding(__unused const char *ifname)
+{
+ int val;
+
+#ifdef IPV6CTL_FORWARDING
+ val = get_inet6_sysctl(IPV6CTL_FORWARDING);
+#else
+ val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding");
+#endif
+ return val < 0 ? 0 : val;
+}
+
+#ifdef SIOCIFAFATTACH
+static int
+if_af_attach(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 if_ioctl6(ifp->ctx, SIOCIFAFATTACH, &ifar, sizeof(ifar));
+}
+#endif
+
+#ifdef SIOCGIFXFLAGS
+static int
+if_set_ifxflags(const struct interface *ifp)
+{
+ struct ifreq ifr;
+ int flags;
+ struct priv *priv = ifp->ctx->priv;
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(priv->pf_inet6_fd, SIOCGIFXFLAGS, &ifr) == -1)
+ return -1;
+ flags = ifr.ifr_flags;
+#ifdef IFXF_NOINET6
+ flags &= ~IFXF_NOINET6;
+#endif
+ /*
+ * If not doing autoconf, don't disable the kernel from doing it.
+ * If we need to, we should have another option actively disable it.
+ *
+ * OpenBSD moved from kernel based SLAAC to userland via slaacd(8).
+ * It has a similar featureset to dhcpcd such as stable private
+ * addresses, but lacks the ability to handle DNS inside the RA
+ * which is a serious shortfall in this day and age.
+ * Appease their user base by working alongside slaacd(8) if
+ * dhcpcd is instructed not to do auto configuration of addresses.
+ */
+#if defined(ND6_IFF_ACCEPT_RTADV)
+#define BSD_AUTOCONF DHCPCD_IPV6RS
+#else
+#define BSD_AUTOCONF DHCPCD_IPV6RA_AUTOCONF
+#endif
+ if (ifp->options->options & BSD_AUTOCONF)
+ flags &= ~IFXF_AUTOCONF6;
+ if (ifr.ifr_flags == flags)
+ return 0;
+ ifr.ifr_flags = flags;
+ return if_ioctl6(ifp->ctx, SIOCSIFXFLAGS, &ifr, sizeof(ifr));
+}
+#endif
+
+/* OpenBSD removed ND6 flags entirely, so we need to check for their
+ * existance. */
+#if defined(ND6_IFF_AUTO_LINKLOCAL) || \
+ defined(ND6_IFF_PERFORMNUD) || \
+ defined(ND6_IFF_ACCEPT_RTADV) || \
+ defined(ND6_IFF_OVERRIDE_RTADV) || \
+ defined(ND6_IFF_IFDISABLED)
+#define ND6_NDI_FLAGS
+#endif
+
+void
+if_disable_rtadv(void)
+{
+#if defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)
+ int ra = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV);
+
+ if (ra == -1) {
+ if (errno != ENOENT)
+ logerr("IPV6CTL_ACCEPT_RTADV");
+ else if (ra != 0)
+ if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1)
+ logerr("IPV6CTL_ACCEPT_RTADV");
+ }
+#endif
+}
+
+void
+if_setup_inet6(const struct interface *ifp)
+{
+ struct priv *priv;
+ int s;
+#ifdef ND6_NDI_FLAGS
+ struct in6_ndireq nd;
+ int flags;
+#endif
+
+ priv = (struct priv *)ifp->ctx->priv;
+ s = priv->pf_inet6_fd;
+
+#ifdef ND6_NDI_FLAGS
+ memset(&nd, 0, sizeof(nd));
+ strlcpy(nd.ifname, ifp->name, sizeof(nd.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, &nd) == -1)
+ logerr("%s: SIOCGIFINFO_FLAGS", ifp->name);
+ flags = (int)nd.ndi.flags;
+#endif
+
+#ifdef ND6_IFF_AUTO_LINKLOCAL
+ /* Unlike the kernel, dhcpcd make make a stable private address. */
+ flags &= ~ND6_IFF_AUTO_LINKLOCAL;
+#endif
+
+#ifdef ND6_IFF_PERFORMNUD
+ /* NUD is kind of essential. */
+ flags |= ND6_IFF_PERFORMNUD;
+#endif
+
+#ifdef ND6_IFF_IFDISABLED
+ /* Ensure the interface is not disabled. */
+ flags &= ~ND6_IFF_IFDISABLED;
+#endif
+
+ /*
+ * If not doing autoconf, don't disable the kernel from doing it.
+ * If we need to, we should have another option actively disable it.
+ */
+#ifdef ND6_IFF_ACCEPT_RTADV
+ if (ifp->options->options & DHCPCD_IPV6RS)
+ flags &= ~ND6_IFF_ACCEPT_RTADV;
+#ifdef ND6_IFF_OVERRIDE_RTADV
+ if (ifp->options->options & DHCPCD_IPV6RS)
+ flags |= ND6_IFF_OVERRIDE_RTADV;
+#endif
+#endif
+
+#ifdef ND6_NDI_FLAGS
+ if (nd.ndi.flags != (uint32_t)flags) {
+ nd.ndi.flags = (uint32_t)flags;
+ if (if_ioctl6(ifp->ctx, SIOCSIFINFO_FLAGS,
+ &nd, sizeof(nd)) == -1)
+ logerr("%s: SIOCSIFINFO_FLAGS", ifp->name);
+ }
+#endif
+
+ /* Enabling IPv6 by whatever means must be the
+ * last action undertaken to ensure kernel RS and
+ * LLADDR auto configuration are disabled where applicable. */
+#ifdef SIOCIFAFATTACH
+ if (if_af_attach(ifp, AF_INET6) == -1)
+ logerr("%s: if_af_attach", ifp->name);
+#endif
+
+#ifdef SIOCGIFXFLAGS
+ if (if_set_ifxflags(ifp) == -1)
+ logerr("%s: set_ifxflags", ifp->name);
+#endif
+
+#ifdef SIOCSRTRFLUSH_IN6
+ /* 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 (ifp->options->options & DHCPCD_IPV6RS) {
+ struct in6_ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (if_ioctl6(ifp->ctx, SIOCSRTRFLUSH_IN6,
+ &ifr, sizeof(ifr)) == -1 &&
+ errno != ENOTSUP && errno != ENOTTY)
+ logwarn("SIOCSRTRFLUSH_IN6 %d", errno);
+#ifdef SIOCSPFXFLUSH_IN6
+ if (if_ioctl6(ifp->ctx, SIOCSPFXFLUSH_IN6,
+ &ifr, sizeof(ifr)) == -1 &&
+ errno != ENOTSUP && errno != ENOTTY)
+ logwarn("SIOCSPFXFLUSH_IN6");
+#endif
+ }
+#endif
+}
+#endif
diff --git a/src/if-linux-wext.c b/src/if-linux-wext.c
new file mode 100644
index 000000000000..a7a268926caa
--- /dev/null
+++ b/src/if-linux-wext.c
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2009-2021 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.
+ */
+
+/*
+ * THIS IS A NASTY HACK THAT SHOULD NEVER HAVE HAPPENED
+ * Basically we cannot include linux/if.h and net/if.h because
+ * they have conflicting structures.
+ * Sadly, linux/wireless.h includes linux/if.h all the time.
+ * Some kernel-header installs fix this and some do not.
+ * This file solely exists for those who do not.
+ *
+ * We *could* include wireless.h as that is designed for userspace,
+ * but that then depends on the correct version of wireless-tools being
+ * installed which isn't always the case.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <linux/types.h>
+#include <linux/rtnetlink.h>
+/* Support older kernels */
+#ifdef IFLA_WIRELESS
+# include <linux/if.h>
+# include <linux/wireless.h>
+#else
+# define IFLA_WIRELESS (IFLA_MASTER + 1)
+#endif
+
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+
+/* We can't include if.h or dhcpcd.h because
+ * they would pull in net/if.h, which defeats the purpose of this hack. */
+#define IF_SSIDLEN 32
+int if_getssid_wext(const char *ifname, uint8_t *ssid);
+
+int
+if_getssid_wext(const char *ifname, uint8_t *ssid)
+{
+#ifdef SIOCGIWESSID
+ int s, retval;
+ struct iwreq iwr;
+
+ if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
+ return -1;
+ memset(&iwr, 0, sizeof(iwr));
+ strlcpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name));
+ iwr.u.essid.pointer = ssid;
+ iwr.u.essid.length = IF_SSIDLEN;
+
+ if (ioctl(s, SIOCGIWESSID, &iwr) == 0)
+ retval = iwr.u.essid.length;
+ else
+ retval = -1;
+ close(s);
+ return retval;
+#else
+ /* Stop gcc warning about unused parameters */
+ ifname = ssid;
+ return -1;
+#endif
+}
diff --git a/src/if-linux.c b/src/if-linux.c
new file mode 100644
index 000000000000..fd05147bab57
--- /dev/null
+++ b/src/if-linux.c
@@ -0,0 +1,2175 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Linux interface driver for dhcpcd
+ * Copyright (c) 2006-2021 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 <asm/types.h> /* Needed for 2.4 kernels */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <linux/icmpv6.h>
+#include <linux/if_addr.h>
+#include <linux/if_link.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include <linux/if_vlan.h>
+#include <linux/filter.h>
+#include <linux/netlink.h>
+#include <linux/sockios.h>
+#include <linux/rtnetlink.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <net/route.h>
+
+/* musl has its own definition of struct ethhdr, so only include
+ * netinet/if_ether.h on systems with GLIBC. For the ARPHRD constants,
+ * we must include linux/if_arp.h instead. */
+#if defined(__GLIBC__)
+#include <netinet/if_ether.h>
+#else
+#include <linux/if_arp.h>
+#endif
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "bpf.h"
+#include "common.h"
+#include "dev.h"
+#include "dhcp.h"
+#include "if.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+#include "route.h"
+#include "sa.h"
+
+#ifdef HAVE_NL80211_H
+#include <linux/genetlink.h>
+#include <linux/nl80211.h>
+#else
+int if_getssid_wext(const char *ifname, uint8_t *ssid);
+#endif
+
+/* Support older kernels */
+#ifndef IFLA_WIRELESS
+#define IFLA_WIRELESS (IFLA_MASTER + 1)
+#endif
+
+/* For some reason, glibc doesn't include newer flags from linux/if.h
+ * However, we cannot include linux/if.h directly as it conflicts
+ * with the glibc version. D'oh! */
+#ifndef IFF_LOWER_UP
+#define IFF_LOWER_UP 0x10000 /* driver signals L1 up */
+#endif
+
+/* Buggy CentOS and RedHat */
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+/*
+ * Someone should fix kernel headers for clang alignment warnings.
+ * But this is unlikely.
+ * https://www.spinics.net/lists/netdev/msg646934.html
+ */
+
+#undef NLA_ALIGNTO
+#undef NLA_ALIGN
+#undef NLA_HDRLEN
+#define NLA_ALIGNTO 4U
+#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
+#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr)))
+
+#undef IFA_RTA
+#define IFA_RTA(r) ((struct rtattr *)(void *)(((char *)(r)) \
+ + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
+#undef IFLA_RTA
+#define IFLA_RTA(r) ((struct rtattr *)(void *)(((char *)(r)) \
+ + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
+#undef NLMSG_NEXT
+#define NLMSG_NEXT(nlh, len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
+ (struct nlmsghdr *)(void *)(((char *)(nlh)) \
+ + NLMSG_ALIGN((nlh)->nlmsg_len)))
+#undef RTM_RTA
+#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))
+#undef RTA_NEXT
+#define RTA_NEXT(rta, attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \
+ (struct rtattr *)(void *)(((char *)(rta)) \
+ + RTA_ALIGN((rta)->rta_len)))
+
+struct priv {
+ int route_fd;
+ int generic_fd;
+ uint32_t route_pid;
+};
+
+/* We need this to send a broadcast for InfiniBand.
+ * Our old code used sendto, but our new code writes to a raw BPF socket.
+ * What header structure does IPoIB use? */
+#if 0
+/* Broadcast address for IPoIB */
+static const uint8_t ipv4_bcast_addr[] = {
+ 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff
+};
+#endif
+
+static int if_addressexists(struct interface *, struct in_addr *);
+
+#define PROC_INET6 "/proc/net/if_inet6"
+#define PROC_PROMOTE "/proc/sys/net/ipv4/conf/%s/promote_secondaries"
+#define SYS_BRIDGE "/sys/class/net/%s/bridge/bridge_id"
+#define SYS_LAYER2 "/sys/class/net/%s/device/layer2"
+#define SYS_TUNTAP "/sys/class/net/%s/tun_flags"
+
+#if defined(__aarch64__)
+static const char *mproc = "AArch64";
+int
+if_machinearch(char *str, size_t len)
+{
+
+ return snprintf(str, len, "%s", mproc);
+}
+#else
+static const char *mproc =
+#if defined(__alpha__)
+ "system type"
+#elif defined(__arm__)
+ "Hardware"
+#elif defined(__avr32__)
+ "cpu family"
+#elif defined(__bfin__)
+ "BOARD Name"
+#elif defined(__cris__)
+ "cpu model"
+#elif defined(__frv__)
+ "System"
+#elif defined(__i386__) || defined(__x86_64__)
+ "vendor_id"
+#elif defined(__ia64__)
+ "vendor"
+#elif defined(__hppa__)
+ "model"
+#elif defined(__m68k__)
+ "MMU"
+#elif defined(__mips__)
+ "system type"
+#elif defined(__powerpc__) || defined(__powerpc64__)
+ "machine"
+#elif defined(__s390__) || defined(__s390x__)
+ "Manufacturer"
+#elif defined(__sh__)
+ "machine"
+#elif defined(sparc) || defined(__sparc__)
+ "cpu"
+#elif defined(__vax__)
+ "cpu"
+#else
+ NULL
+#endif
+ ;
+
+int
+if_machinearch(char *str, size_t len)
+{
+ FILE *fp;
+ char buf[256];
+
+ if (mproc == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fp = fopen("/proc/cpuinfo", "r");
+ if (fp == NULL)
+ return -1;
+
+ while (fscanf(fp, "%255s : ", buf) != EOF) {
+ if (strncmp(buf, mproc, strlen(mproc)) == 0 &&
+ fscanf(fp, "%255s", buf) == 1)
+ {
+ fclose(fp);
+ return snprintf(str, len, "%s", buf);
+ }
+ }
+ fclose(fp);
+ errno = ESRCH;
+ return -1;
+}
+#endif
+
+static int
+check_proc_int(struct dhcpcd_ctx *ctx, const char *path)
+{
+ char buf[64];
+ int error, i;
+
+ if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1)
+ return -1;
+ i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error);
+ if (error != 0 && error != ENOTSUP) {
+ errno = error;
+ return -1;
+ }
+ return i;
+}
+
+static int
+check_proc_uint(struct dhcpcd_ctx *ctx, const char *path, unsigned int *u)
+{
+ char buf[64];
+ int error;
+
+ if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1)
+ return -1;
+ *u = (unsigned int)strtou(buf, NULL, 0, 0, UINT_MAX, &error);
+ if (error != 0 && error != ENOTSUP) {
+ errno = error;
+ return error;
+ }
+ return 0;
+}
+
+static ssize_t
+if_writepathuint(struct dhcpcd_ctx *ctx, const char *path, unsigned int val)
+{
+ char buf[64];
+ int len;
+
+ len = snprintf(buf, sizeof(buf), "%u\n", val);
+ if (len == -1)
+ return -1;
+ return dhcp_writefile(ctx, path, 0664, buf, (size_t)len);
+}
+
+int
+if_init(struct interface *ifp)
+{
+ char path[sizeof(PROC_PROMOTE) + IF_NAMESIZE];
+ int n;
+
+ /* We enable promote_secondaries so that we can do this
+ * add 192.168.1.2/24
+ * add 192.168.1.3/24
+ * del 192.168.1.2/24
+ * and the subnet mask moves onto 192.168.1.3/24
+ * This matches the behaviour of BSD which makes coding dhcpcd
+ * a little easier as there's just one behaviour. */
+ snprintf(path, sizeof(path), PROC_PROMOTE, ifp->name);
+ n = check_proc_int(ifp->ctx, path);
+ if (n == -1)
+ return errno == ENOENT ? 0 : -1;
+ if (n == 1)
+ return 0;
+ return if_writepathuint(ifp->ctx, path, 1) == -1 ? -1 : 0;
+}
+
+int
+if_conf(struct interface *ifp)
+{
+ char path[sizeof(SYS_LAYER2) + IF_NAMESIZE];
+ int n;
+
+ /* Some qeth setups require the use of the broadcast flag. */
+ snprintf(path, sizeof(path), SYS_LAYER2, ifp->name);
+ n = check_proc_int(ifp->ctx, path);
+ if (n == -1)
+ return errno == ENOENT ? 0 : -1;
+ if (n == 0)
+ ifp->options->options |= DHCPCD_BROADCAST;
+ return 0;
+}
+
+static bool
+if_bridge(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ char path[sizeof(SYS_BRIDGE) + IF_NAMESIZE], buf[64];
+
+ snprintf(path, sizeof(path), SYS_BRIDGE, ifname);
+ if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1)
+ return false;
+ return true;
+}
+
+static bool
+if_tap(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+ char path[sizeof(SYS_TUNTAP) + IF_NAMESIZE];
+ unsigned int u;
+
+ snprintf(path, sizeof(path), SYS_TUNTAP, ifname);
+ if (check_proc_uint(ctx, path, &u) == -1)
+ return false;
+ return u & IFF_TAP;
+}
+
+bool
+if_ignore(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+ if (if_tap(ctx, ifname) || if_bridge(ctx, ifname))
+ return true;
+ return false;
+}
+
+/* XXX work out Virtal Interface Masters */
+int
+if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
+{
+
+ return 0;
+}
+
+unsigned short
+if_vlanid(const struct interface *ifp)
+{
+ struct vlan_ioctl_args v;
+
+ memset(&v, 0, sizeof(v));
+ strlcpy(v.device1, ifp->name, sizeof(v.device1));
+ v.cmd = GET_VLAN_VID_CMD;
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFVLAN, &v) != 0)
+ return 0; /* 0 means no VLANID */
+ return (unsigned short)v.u.VID;
+}
+
+int
+if_linksocket(struct sockaddr_nl *nl, int protocol, int flags)
+{
+ int fd;
+
+ fd = xsocket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | flags, protocol);
+ if (fd == -1)
+ return -1;
+ nl->nl_family = AF_NETLINK;
+ if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+char *
+if_getnetworknamespace(char *buf, size_t len)
+{
+ struct stat sb_self, sb_netns;
+ DIR *dir;
+ struct dirent *de;
+ char file[PATH_MAX], *bufp = NULL;
+
+ if (stat("/proc/self/ns/net", &sb_self) == -1)
+ return NULL;
+
+ dir = opendir("/var/run/netns");
+ if (dir == NULL)
+ return NULL;
+
+ while ((de = readdir(dir)) != NULL) {
+ snprintf(file, sizeof(file), "/var/run/netns/%s", de->d_name);
+ if (stat(file, &sb_netns) == -1)
+ continue;
+ if (sb_self.st_dev != sb_netns.st_dev ||
+ sb_self.st_ino != sb_netns.st_ino)
+ continue;
+ strlcpy(buf, de->d_name, len);
+ bufp = buf;
+ break;
+ }
+ closedir(dir);
+ return bufp;
+}
+
+int
+os_init(void)
+{
+ char netns[PATH_MAX], *p;
+
+ p = if_getnetworknamespace(netns, sizeof(netns));
+ if (p != NULL)
+ loginfox("network namespace: %s", p);
+
+ return 0;
+}
+
+int
+if_opensockets_os(struct dhcpcd_ctx *ctx)
+{
+ struct priv *priv;
+ struct sockaddr_nl snl;
+ socklen_t len;
+#ifdef NETLINK_BROADCAST_ERROR
+ int on = 1;
+#endif
+
+ /* Open the link socket first so it gets pid() for the socket.
+ * Then open our persistent route socket so we get a unique
+ * pid that doesn't clash with a process id for after we fork. */
+ memset(&snl, 0, sizeof(snl));
+ snl.nl_groups = RTMGRP_LINK;
+
+#ifdef INET
+ snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR;
+#endif
+#ifdef INET6
+ snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH;
+#endif
+
+ ctx->link_fd = if_linksocket(&snl, NETLINK_ROUTE, SOCK_NONBLOCK);
+ if (ctx->link_fd == -1)
+ return -1;
+#ifdef NETLINK_BROADCAST_ERROR
+ if (setsockopt(ctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR,
+ &on, sizeof(on)) == -1)
+ logerr("%s: NETLINK_BROADCAST_ERROR", __func__);
+#endif
+
+ if ((priv = calloc(1, sizeof(*priv))) == NULL)
+ return -1;
+
+ ctx->priv = priv;
+ memset(&snl, 0, sizeof(snl));
+ priv->route_fd = if_linksocket(&snl, NETLINK_ROUTE, 0);
+ if (priv->route_fd == -1)
+ return -1;
+ len = sizeof(snl);
+ if (getsockname(priv->route_fd, (struct sockaddr *)&snl, &len) == -1)
+ return -1;
+ priv->route_pid = snl.nl_pid;
+
+ memset(&snl, 0, sizeof(snl));
+ priv->generic_fd = if_linksocket(&snl, NETLINK_GENERIC, 0);
+ if (priv->generic_fd == -1)
+ return -1;
+
+ return 0;
+}
+
+void
+if_closesockets_os(struct dhcpcd_ctx *ctx)
+{
+ struct priv *priv;
+
+ if (ctx->priv != NULL) {
+ priv = (struct priv *)ctx->priv;
+ close(priv->route_fd);
+ close(priv->generic_fd);
+ }
+}
+
+int
+if_setmac(struct interface *ifp, void *mac, uint8_t maclen)
+{
+ struct ifreq ifr = {
+ .ifr_hwaddr.sa_family = ifp->hwtype,
+ };
+
+ if (ifp->hwlen != maclen || maclen > sizeof(ifr.ifr_hwaddr.sa_data)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ memcpy(ifr.ifr_hwaddr.sa_data, mac, maclen);
+ return if_ioctl(ifp->ctx, SIOCSIFHWADDR, &ifr, sizeof(ifr));
+}
+
+int
+if_carrier(struct interface *ifp, __unused const void *ifadata)
+{
+
+ return ifp->flags & IFF_RUNNING ? LINK_UP : LINK_DOWN;
+}
+
+bool
+if_roaming(struct interface *ifp)
+{
+
+#ifdef IFF_LOWER_UP
+ if (!ifp->wireless ||
+ ifp->flags & IFF_RUNNING ||
+ (ifp->flags & (IFF_UP | IFF_LOWER_UP)) != (IFF_UP | IFF_LOWER_UP))
+ return false;
+ return true;
+#else
+ return false;
+#endif
+}
+
+int
+if_getnetlink(struct dhcpcd_ctx *ctx, struct iovec *iov, int fd, int flags,
+ int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg)
+{
+ struct sockaddr_nl nladdr = { .nl_pid = 0 };
+ struct msghdr msg = {
+ .msg_name = &nladdr, .msg_namelen = sizeof(nladdr),
+ .msg_iov = iov, .msg_iovlen = 1,
+ };
+ ssize_t len;
+ struct nlmsghdr *nlm;
+ int r = 0;
+ unsigned int again;
+ bool terminated;
+
+recv_again:
+ len = recvmsg(fd, &msg, flags);
+ if (len == -1 || len == 0)
+ return (int)len;
+
+ /* Check sender */
+ if (msg.msg_namelen != sizeof(nladdr)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Ignore message if it is not from kernel */
+ if (nladdr.nl_pid != 0)
+ return 0;
+
+ again = 0;
+ terminated = false;
+ for (nlm = iov->iov_base;
+ nlm && NLMSG_OK(nlm, (size_t)len);
+ nlm = NLMSG_NEXT(nlm, len))
+ {
+ again = (nlm->nlmsg_flags & NLM_F_MULTI);
+ if (nlm->nlmsg_type == NLMSG_NOOP)
+ continue;
+
+ if (nlm->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *err;
+
+ if (nlm->nlmsg_len - sizeof(*nlm) < sizeof(*err)) {
+ errno = EBADMSG;
+ return -1;
+ }
+ err = (struct nlmsgerr *)NLMSG_DATA(nlm);
+ if (err->error != 0) {
+ errno = -err->error;
+ return -1;
+ }
+ again = 0;
+ terminated = true;
+ break;
+ }
+ if (nlm->nlmsg_type == NLMSG_DONE) {
+ again = 0;
+ terminated = true;
+ break;
+ }
+ if (cb == NULL)
+ continue;
+ if (nlm->nlmsg_seq != (uint32_t)ctx->seq && fd != ctx->link_fd)
+ logwarnx("%s: received sequence %u, expecting %d",
+ __func__, nlm->nlmsg_seq, ctx->seq);
+ else
+ r = cb(ctx, cbarg, nlm);
+ }
+
+ if ((again || !terminated) && (ctx != NULL && ctx->link_fd != fd))
+ goto recv_again;
+
+ return r;
+}
+
+static int
+if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct nlmsghdr *nlm)
+{
+ size_t len;
+ struct rtmsg *rtm;
+ struct rtattr *rta;
+ unsigned int ifindex;
+ struct sockaddr *sa;
+
+ len = nlm->nlmsg_len - sizeof(*nlm);
+ if (len < sizeof(*rtm)) {
+ errno = EBADMSG;
+ return -1;
+ }
+ rtm = (struct rtmsg *)NLMSG_DATA(nlm);
+ if (rtm->rtm_table != RT_TABLE_MAIN)
+ return -1;
+
+ memset(rt, 0, sizeof(*rt));
+ if (rtm->rtm_type == RTN_UNREACHABLE)
+ rt->rt_flags |= RTF_REJECT;
+
+ rta = RTM_RTA(rtm);
+ len = RTM_PAYLOAD(nlm);
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ sa = NULL;
+ switch (rta->rta_type) {
+ case RTA_DST:
+ sa = &rt->rt_dest;
+ break;
+ case RTA_GATEWAY:
+ sa = &rt->rt_gateway;
+ break;
+ case RTA_PREFSRC:
+ sa = &rt->rt_ifa;
+ break;
+ case RTA_OIF:
+ ifindex = *(unsigned int *)RTA_DATA(rta);
+ rt->rt_ifp = if_findindex(ctx->ifaces, ifindex);
+ break;
+ case RTA_PRIORITY:
+ rt->rt_metric = *(unsigned int *)RTA_DATA(rta);
+ break;
+ case RTA_METRICS:
+ {
+ struct rtattr *r2;
+ size_t l2;
+
+ l2 = rta->rta_len;
+ r2 = (struct rtattr *)RTA_DATA(rta);
+ for (; RTA_OK(r2, l2); r2 = RTA_NEXT(r2, l2)) {
+ switch (r2->rta_type) {
+ case RTAX_MTU:
+ rt->rt_mtu = *(unsigned int *)RTA_DATA(r2);
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (sa != NULL) {
+ socklen_t salen;
+
+ sa->sa_family = rtm->rtm_family;
+ salen = sa_addrlen(sa);
+ /* sa is a union where sockaddr_in6 is the biggest. */
+ /* coverity[overrun-buffer-arg] */
+ memcpy((char *)sa + sa_addroffset(sa), RTA_DATA(rta),
+ MIN(salen, RTA_PAYLOAD(rta)));
+ }
+ }
+
+ /* If no RTA_DST set the unspecified address for the family. */
+ if (rt->rt_dest.sa_family == AF_UNSPEC)
+ rt->rt_dest.sa_family = rtm->rtm_family;
+
+ rt->rt_netmask.sa_family = rtm->rtm_family;
+ sa_fromprefix(&rt->rt_netmask, rtm->rtm_dst_len);
+ if (sa_is_allones(&rt->rt_netmask))
+ rt->rt_flags |= RTF_HOST;
+
+ #if 0
+ if (rt->rtp_ifp == NULL && rt->src.s_addr != INADDR_ANY) {
+ struct ipv4_addr *ap;
+
+ /* For some reason the default route comes back with the
+ * loopback interface in RTA_OIF? Lets find it by
+ * preferred source address */
+ if ((ap = ipv4_findaddr(ctx, &rt->src)))
+ rt->iface = ap->iface;
+ }
+ #endif
+
+ if (rt->rt_ifp == NULL) {
+ errno = ESRCH;
+ return -1;
+ }
+ return 0;
+}
+
+static int
+link_route(struct dhcpcd_ctx *ctx, __unused struct interface *ifp,
+ struct nlmsghdr *nlm)
+{
+ size_t len;
+ int cmd;
+ struct priv *priv;
+ struct rt rt;
+
+ switch (nlm->nlmsg_type) {
+ case RTM_NEWROUTE:
+ cmd = RTM_ADD;
+ break;
+ case RTM_DELROUTE:
+ cmd = RTM_DELETE;
+ break;
+ default:
+ return 0;
+ }
+
+ len = nlm->nlmsg_len - sizeof(*nlm);
+ if (len < sizeof(struct rtmsg)) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ /* Ignore messages we sent. */
+#ifdef PRIVSEP
+ if (ctx->ps_root_pid != 0 &&
+ nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid)
+ return 0;
+#endif
+ priv = (struct priv *)ctx->priv;
+ if (nlm->nlmsg_pid == priv->route_pid)
+ return 0;
+
+ if (if_copyrt(ctx, &rt, nlm) == 0)
+ rt_recvrt(cmd, &rt, (pid_t)nlm->nlmsg_pid);
+
+ return 0;
+}
+
+static int
+link_addr(struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm)
+{
+ size_t len;
+ struct rtattr *rta;
+ struct ifaddrmsg *ifa;
+ struct priv *priv;
+#ifdef INET
+ struct in_addr addr, net, brd;
+ int ret;
+#endif
+#ifdef INET6
+ struct in6_addr addr6;
+ int flags;
+#endif
+
+ if (nlm->nlmsg_type != RTM_DELADDR && nlm->nlmsg_type != RTM_NEWADDR)
+ return 0;
+
+ len = nlm->nlmsg_len - sizeof(*nlm);
+ if (len < sizeof(*ifa)) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ /* Ignore address deletions from ourself.
+ * We need to process address flag changes though. */
+ if (nlm->nlmsg_type == RTM_DELADDR) {
+#ifdef PRIVSEP
+ if (ctx->ps_root_pid != 0 &&
+ nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid)
+ return 0;
+#endif
+ priv = (struct priv*)ctx->priv;
+ if (nlm->nlmsg_pid == priv->route_pid)
+ return 0;
+ }
+
+ ifa = NLMSG_DATA(nlm);
+ if ((ifp = if_findindex(ctx->ifaces, ifa->ifa_index)) == NULL) {
+ /* We don't know about the interface the address is for
+ * so it's not really an error */
+ return 1;
+ }
+ rta = IFA_RTA(ifa);
+ len = NLMSG_PAYLOAD(nlm, sizeof(*ifa));
+ switch (ifa->ifa_family) {
+#ifdef INET
+ case AF_INET:
+ addr.s_addr = brd.s_addr = INADDR_ANY;
+ inet_cidrtoaddr(ifa->ifa_prefixlen, &net);
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ switch (rta->rta_type) {
+ case IFA_ADDRESS:
+ if (ifp->flags & IFF_POINTOPOINT) {
+ memcpy(&brd.s_addr, RTA_DATA(rta),
+ sizeof(brd.s_addr));
+ }
+ break;
+ case IFA_BROADCAST:
+ memcpy(&brd.s_addr, RTA_DATA(rta),
+ sizeof(brd.s_addr));
+ break;
+ case IFA_LOCAL:
+ memcpy(&addr.s_addr, RTA_DATA(rta),
+ sizeof(addr.s_addr));
+ break;
+ }
+ }
+
+ /* Validate RTM_DELADDR really means address deleted
+ * and anything else really means address exists. */
+ ret = if_addressexists(ifp, &addr);
+ if (ret == -1) {
+ logerr("if_addressexists: %s", inet_ntoa(addr));
+ break;
+ } else if (ret == 1) {
+ if (nlm->nlmsg_type == RTM_DELADDR)
+ break;
+ } else {
+ if (nlm->nlmsg_type != RTM_DELADDR)
+ break;
+ }
+
+ ipv4_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name,
+ &addr, &net, &brd, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memset(&addr6, 0, sizeof(addr6));
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ switch (rta->rta_type) {
+ case IFA_ADDRESS:
+ memcpy(&addr6.s6_addr, RTA_DATA(rta),
+ sizeof(addr6.s6_addr));
+ break;
+ }
+ }
+
+ /* Validate RTM_DELADDR really means address deleted
+ * and anything else really means address exists. */
+ flags = if_addrflags6(ifp, &addr6, NULL);
+ if (nlm->nlmsg_type == RTM_DELADDR) {
+ if (flags != -1)
+ break;
+ } else {
+ if (flags == -1)
+ break;
+ }
+
+ ipv6_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name,
+ &addr6, ifa->ifa_prefixlen, ifa->ifa_flags,
+ (pid_t)nlm->nlmsg_pid);
+ break;
+#endif
+ }
+ return 0;
+}
+
+static uint8_t
+l2addr_len(unsigned short if_type)
+{
+
+ switch (if_type) {
+ case ARPHRD_ETHER: /* FALLTHROUGH */
+ case ARPHRD_IEEE802: /*FALLTHROUGH */
+ case ARPHRD_IEEE80211:
+ return 6;
+ case ARPHRD_IEEE1394:
+ return 8;
+ case ARPHRD_INFINIBAND:
+ return 20;
+ }
+
+ /* Impossible */
+ return 0;
+}
+
+#ifdef INET6
+static int
+link_neigh(struct dhcpcd_ctx *ctx, __unused struct interface *ifp,
+ struct nlmsghdr *nlm)
+{
+ struct ndmsg *r;
+ struct rtattr *rta;
+ size_t len;
+
+ if (nlm->nlmsg_type != RTM_NEWNEIGH && nlm->nlmsg_type != RTM_DELNEIGH)
+ return 0;
+ if (nlm->nlmsg_len < sizeof(*r))
+ return -1;
+
+ r = NLMSG_DATA(nlm);
+ rta = RTM_RTA(r);
+ len = RTM_PAYLOAD(nlm);
+ if (r->ndm_family == AF_INET6) {
+ bool unreachable;
+ struct in6_addr addr6;
+
+ unreachable = (nlm->nlmsg_type == RTM_NEWNEIGH &&
+ r->ndm_state & NUD_FAILED);
+ memset(&addr6, 0, sizeof(addr6));
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ switch (rta->rta_type) {
+ case NDA_DST:
+ memcpy(&addr6.s6_addr, RTA_DATA(rta),
+ sizeof(addr6.s6_addr));
+ break;
+ }
+ }
+ ipv6nd_neighbour(ctx, &addr6, !unreachable);
+ }
+
+ return 0;
+}
+#endif
+
+static int
+link_netlink(struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm)
+{
+ struct interface *ifp = arg;
+ int r;
+ size_t len;
+ struct rtattr *rta, *hwaddr;
+ struct ifinfomsg *ifi;
+ char ifn[IF_NAMESIZE + 1];
+
+ r = link_route(ctx, ifp, nlm);
+ if (r != 0)
+ return r;
+ r = link_addr(ctx, ifp, nlm);
+ if (r != 0)
+ return r;
+#ifdef INET6
+ r = link_neigh(ctx, ifp, nlm);
+ if (r != 0)
+ return r;
+#endif
+
+ if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK)
+ return 0;
+ len = nlm->nlmsg_len - sizeof(*nlm);
+ if ((size_t)len < sizeof(*ifi)) {
+ errno = EBADMSG;
+ return -1;
+ }
+ ifi = NLMSG_DATA(nlm);
+ if (ifi->ifi_flags & IFF_LOOPBACK)
+ return 0;
+ rta = (void *)((char *)ifi + NLMSG_ALIGN(sizeof(*ifi)));
+ len = NLMSG_PAYLOAD(nlm, sizeof(*ifi));
+ *ifn = '\0';
+ hwaddr = NULL;
+
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ switch (rta->rta_type) {
+ case IFLA_WIRELESS:
+ /* Ignore wireless messages */
+ if (nlm->nlmsg_type == RTM_NEWLINK &&
+ ifi->ifi_change == 0)
+ return 0;
+ break;
+ case IFLA_IFNAME:
+ strlcpy(ifn, (char *)RTA_DATA(rta), sizeof(ifn));
+ break;
+ case IFLA_ADDRESS:
+ hwaddr = rta;
+ break;
+ }
+ }
+
+ if (nlm->nlmsg_type == RTM_DELLINK) {
+#ifdef PLUGIN_DEV
+ /* If are listening to a dev manager, let that remove
+ * the interface rather than the kernel. */
+ if (dev_listening(ctx) < 1)
+#endif
+ dhcpcd_handleinterface(ctx, -1, ifn);
+ return 0;
+ }
+
+ /* Virtual interfaces may not get a valid hardware address
+ * at this point.
+ * To trigger a valid hardware address pickup we need to pretend
+ * that that don't exist until they have one. */
+ if (ifi->ifi_flags & IFF_MASTER && !hwaddr) {
+ dhcpcd_handleinterface(ctx, -1, ifn);
+ return 0;
+ }
+
+ /* Check for a new interface */
+ ifp = if_findindex(ctx->ifaces, (unsigned int)ifi->ifi_index);
+ if (ifp == NULL) {
+#ifdef PLUGIN_DEV
+ /* If are listening to a dev manager, let that announce
+ * the interface rather than the kernel. */
+ if (dev_listening(ctx) < 1)
+#endif
+ dhcpcd_handleinterface(ctx, 1, ifn);
+ return 0;
+ }
+
+ /* Handle interface being renamed */
+ if (strcmp(ifp->name, ifn) != 0) {
+ dhcpcd_handleinterface(ctx, -1, ifn);
+ dhcpcd_handleinterface(ctx, 1, ifn);
+ return 0;
+ }
+
+ /* Re-read hardware address and friends */
+ if (!(ifi->ifi_flags & IFF_UP)) {
+ void *hwa = hwaddr != NULL ? RTA_DATA(hwaddr) : NULL;
+ uint8_t hwl = l2addr_len(ifi->ifi_type);
+
+ if (hwaddr != NULL && hwaddr->rta_len != RTA_LENGTH(hwl))
+ hwa = NULL;
+ dhcpcd_handlehwaddr(ifp, ifi->ifi_type, hwa, hwl);
+ }
+
+ dhcpcd_handlecarrier(ifp,
+ ifi->ifi_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN,
+ ifi->ifi_flags);
+ return 0;
+}
+
+int
+if_handlelink(struct dhcpcd_ctx *ctx)
+{
+ unsigned char buf[16 * 1024];
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf),
+ };
+
+ return if_getnetlink(ctx, &iov, ctx->link_fd, MSG_DONTWAIT,
+ &link_netlink, NULL);
+}
+
+#ifdef PRIVSEP
+static bool
+if_netlinkpriv(int protocol, struct nlmsghdr *nlm)
+{
+
+ if (protocol != NETLINK_ROUTE)
+ return false;
+
+ switch(nlm->nlmsg_type) {
+ case RTM_NEWADDR: /* FALLTHROUGH */
+ case RTM_DELADDR: /* FALLTHROUGH */
+ case RTM_NEWROUTE: /* FALLTHROUGH */
+ case RTM_DELROUTE: /* FALLTHROUGH */
+ case RTM_NEWLINK:
+ return true;
+ default:
+ return false;
+ }
+}
+#endif
+
+static int
+if_sendnetlink(struct dhcpcd_ctx *ctx, int protocol, struct nlmsghdr *hdr,
+ int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg)
+{
+ int s;
+ struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
+ struct iovec iov = { .iov_base = hdr, .iov_len = hdr->nlmsg_len };
+ struct msghdr msg = {
+ .msg_name = &snl, .msg_namelen = sizeof(snl),
+ .msg_iov = &iov, .msg_iovlen = 1
+ };
+ struct priv *priv = (struct priv *)ctx->priv;
+ unsigned char buf[16 * 1024];
+ struct iovec riov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf),
+ };
+
+ /* Request a reply */
+ hdr->nlmsg_flags |= NLM_F_ACK;
+ hdr->nlmsg_seq = (uint32_t)++ctx->seq;
+ if ((unsigned int)ctx->seq > UINT32_MAX)
+ ctx->seq = 0;
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP && if_netlinkpriv(protocol, hdr))
+ return (int)ps_root_sendnetlink(ctx, protocol, &msg);
+#endif
+
+ switch (protocol) {
+ case NETLINK_ROUTE:
+ s = priv->route_fd;
+ break;
+ case NETLINK_GENERIC:
+ s = priv->generic_fd;
+#if 0
+#ifdef NETLINK_GET_STRICT_CHK
+ if (hdr->nlmsg_type == RTM_GETADDR) {
+ int on = 1;
+
+ if (setsockopt(s, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
+ &on, sizeof(on)) == -1 && errno != ENOPROTOOPT)
+ logerr("%s: NETLINK_GET_STRICT_CHK", __func__);
+ }
+#endif
+#endif
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (sendmsg(s, &msg, 0) == -1)
+ return -1;
+
+ return if_getnetlink(ctx, &riov, s, 0, cb, cbarg);
+}
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+static int
+add_attr_l(struct nlmsghdr *n, unsigned short maxlen, unsigned short type,
+ const void *data, unsigned short alen)
+{
+ unsigned short len = (unsigned short)RTA_LENGTH(alen);
+ struct rtattr *rta;
+
+ if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = len;
+ if (alen)
+ memcpy(RTA_DATA(rta), data, alen);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+
+ return 0;
+}
+
+static int
+add_attr_8(struct nlmsghdr *n, unsigned short maxlen, unsigned short type,
+ uint8_t data)
+{
+
+ return add_attr_l(n, maxlen, type, &data, sizeof(data));
+}
+
+static int
+add_attr_32(struct nlmsghdr *n, unsigned short maxlen, unsigned short type,
+ uint32_t data)
+{
+ unsigned short len = RTA_LENGTH(sizeof(data));
+ struct rtattr *rta;
+
+ if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = len;
+ memcpy(RTA_DATA(rta), &data, sizeof(data));
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+
+ return 0;
+}
+
+static int
+rta_add_attr_32(struct rtattr *rta, unsigned short maxlen,
+ unsigned short type, uint32_t data)
+{
+ unsigned short len = RTA_LENGTH(sizeof(data));
+ struct rtattr *subrta;
+
+ if (RTA_ALIGN(rta->rta_len) + len > maxlen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ subrta = (void *)((char*)rta + RTA_ALIGN(rta->rta_len));
+ subrta->rta_type = type;
+ subrta->rta_len = len;
+ memcpy(RTA_DATA(subrta), &data, sizeof(data));
+ rta->rta_len = (unsigned short)(NLMSG_ALIGN(rta->rta_len) + len);
+ return 0;
+}
+
+#ifdef HAVE_NL80211_H
+static struct nlattr *
+nla_next(struct nlattr *nla, size_t *rem)
+{
+
+ *rem -= (size_t)NLA_ALIGN(nla->nla_len);
+ return (void *)((char *)nla + NLA_ALIGN(nla->nla_len));
+}
+
+#define NLA_TYPE(nla) ((nla)->nla_type & NLA_TYPE_MASK)
+#define NLA_LEN(nla) (unsigned int)((nla)->nla_len - NLA_HDRLEN)
+#define NLA_OK(nla, rem) \
+ ((rem) >= sizeof(struct nlattr) && \
+ (nla)->nla_len >= sizeof(struct nlattr) && \
+ (nla)->nla_len <= rem)
+#define NLA_DATA(nla) (void *)((char *)(nla) + NLA_HDRLEN)
+#define NLA_FOR_EACH_ATTR(pos, head, len, rem) \
+ for (pos = head, rem = len; \
+ NLA_OK(pos, rem); \
+ pos = nla_next(pos, &(rem)))
+
+struct nlmg
+{
+ struct nlmsghdr hdr;
+ struct genlmsghdr ghdr;
+ char buffer[64];
+};
+
+static int
+nla_put_32(struct nlmsghdr *n, unsigned short maxlen,
+ unsigned short type, uint32_t data)
+{
+ unsigned short len;
+ struct nlattr *nla;
+
+ len = NLA_ALIGN(NLA_HDRLEN + sizeof(data));
+ if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ nla = (struct nlattr *)NLMSG_TAIL(n);
+ nla->nla_type = type;
+ nla->nla_len = len;
+ memcpy(NLA_DATA(nla), &data, sizeof(data));
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+
+ return 0;
+}
+
+static int
+nla_put_string(struct nlmsghdr *n, unsigned short maxlen,
+ unsigned short type, const char *data)
+{
+ struct nlattr *nla;
+ size_t len, sl;
+
+ sl = strlen(data) + 1;
+ len = NLA_ALIGN(NLA_HDRLEN + sl);
+ if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ nla = (struct nlattr *)NLMSG_TAIL(n);
+ nla->nla_type = type;
+ nla->nla_len = (unsigned short)len;
+ memcpy(NLA_DATA(nla), data, sl);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + (unsigned short)len;
+ return 0;
+}
+
+static int
+nla_parse(struct nlattr *tb[], struct nlattr *head, size_t len, int maxtype)
+{
+ struct nlattr *nla;
+ size_t rem;
+ int type;
+
+ memset(tb, 0, sizeof(*tb) * ((unsigned int)maxtype + 1));
+ NLA_FOR_EACH_ATTR(nla, head, len, rem) {
+ type = NLA_TYPE(nla);
+ if (type > maxtype)
+ continue;
+ tb[type] = nla;
+ }
+ return 0;
+}
+
+static int
+genl_parse(struct nlmsghdr *nlm, struct nlattr *tb[], int maxtype)
+{
+ struct genlmsghdr *ghdr;
+ struct nlattr *head;
+ size_t len;
+
+ ghdr = NLMSG_DATA(nlm);
+ head = (void *)((char *)ghdr + GENL_HDRLEN);
+ len = nlm->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN;
+ return nla_parse(tb, head, len, maxtype);
+}
+
+static int
+_gnl_getfamily(__unused struct dhcpcd_ctx *ctx, __unused void *arg,
+ struct nlmsghdr *nlm)
+{
+ struct nlattr *tb[CTRL_ATTR_FAMILY_ID + 1];
+ uint16_t family;
+
+ if (genl_parse(nlm, tb, CTRL_ATTR_FAMILY_ID) == -1)
+ return -1;
+ if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+ memcpy(&family, NLA_DATA(tb[CTRL_ATTR_FAMILY_ID]), sizeof(family));
+ return (int)family;
+}
+
+static int
+gnl_getfamily(struct dhcpcd_ctx *ctx, const char *name)
+{
+ struct nlmg nlm;
+
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
+ nlm.hdr.nlmsg_type = GENL_ID_CTRL;
+ nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
+ nlm.ghdr.cmd = CTRL_CMD_GETFAMILY;
+ nlm.ghdr.version = 1;
+ if (nla_put_string(&nlm.hdr, sizeof(nlm),
+ CTRL_ATTR_FAMILY_NAME, name) == -1)
+ return -1;
+ return if_sendnetlink(ctx, NETLINK_GENERIC, &nlm.hdr,
+ &_gnl_getfamily, NULL);
+}
+
+static int
+_if_getssid_nl80211(__unused struct dhcpcd_ctx *ctx, void *arg,
+ struct nlmsghdr *nlm)
+{
+ struct interface *ifp = arg;
+ struct nlattr *tb[NL80211_ATTR_BSS + 1];
+ struct nlattr *bss[NL80211_BSS_STATUS + 1];
+ uint32_t status;
+ unsigned char *ie;
+ int ie_len;
+
+ if (genl_parse(nlm, tb, NL80211_ATTR_BSS) == -1)
+ return 0;
+
+ if (tb[NL80211_ATTR_BSS] == NULL)
+ return 0;
+
+ if (nla_parse(bss,
+ NLA_DATA(tb[NL80211_ATTR_BSS]),
+ NLA_LEN(tb[NL80211_ATTR_BSS]),
+ NL80211_BSS_STATUS) == -1)
+ return 0;
+
+ if (bss[NL80211_BSS_BSSID] == NULL || bss[NL80211_BSS_STATUS] == NULL)
+ return 0;
+
+ memcpy(&status, NLA_DATA(bss[NL80211_BSS_STATUS]), sizeof(status));
+ if (status != NL80211_BSS_STATUS_ASSOCIATED)
+ return 0;
+
+ if (bss[NL80211_BSS_INFORMATION_ELEMENTS] == NULL)
+ return 0;
+
+ ie = NLA_DATA(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
+ ie_len = (int)NLA_LEN(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
+ /* ie[0] is type, ie[1] is lenth, ie[2..] is data */
+ while (ie_len >= 2 && ie_len >= ie[1]) {
+ if (ie[0] == 0) {
+ /* SSID */
+ if (ie[1] > IF_SSIDLEN) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ ifp->ssid_len = ie[1];
+ memcpy(ifp->ssid, ie + 2, ifp->ssid_len);
+ return (int)ifp->ssid_len;
+ }
+ ie_len -= ie[1] + 2;
+ ie += ie[1] + 2;
+ }
+
+ return 0;
+}
+
+static int
+if_getssid_nl80211(struct interface *ifp)
+{
+ int family;
+ struct nlmg nlm;
+
+ errno = 0;
+ family = gnl_getfamily(ifp->ctx, "nl80211");
+ if (family == -1)
+ return -1;
+
+ /* Is this a wireless interface? */
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
+ nlm.hdr.nlmsg_type = (unsigned short)family;
+ nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
+ nlm.ghdr.cmd = NL80211_CMD_GET_WIPHY;
+ nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index);
+ if (if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr,
+ NULL, NULL) == -1)
+ return -1;
+
+ /* We need to parse out the list of scan results and find the one
+ * we are connected to. */
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
+ nlm.hdr.nlmsg_type = (unsigned short)family;
+ nlm.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlm.ghdr.cmd = NL80211_CMD_GET_SCAN;
+ nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index);
+
+ return if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr,
+ &_if_getssid_nl80211, ifp);
+}
+#endif
+
+int
+if_getssid(struct interface *ifp)
+{
+ int r;
+
+#ifdef HAVE_NL80211_H
+ r = if_getssid_nl80211(ifp);
+ if (r == -1)
+ ifp->ssid_len = 0;
+#else
+ r = if_getssid_wext(ifp->name, ifp->ssid);
+ if (r != -1)
+ ifp->ssid_len = (unsigned int)r;
+#endif
+
+ ifp->ssid[ifp->ssid_len] = '\0';
+ return r;
+}
+
+struct nlma
+{
+ struct nlmsghdr hdr;
+ struct ifaddrmsg ifa;
+ char buffer[64];
+};
+
+#ifdef INET
+struct ifiaddr
+{
+ unsigned int ifa_ifindex;
+ struct in_addr ifa_addr;
+ bool ifa_found;
+};
+
+static int
+_if_addressexists(__unused struct dhcpcd_ctx *ctx,
+ void *arg, struct nlmsghdr *nlm)
+{
+ struct ifiaddr *ia = arg;
+ in_addr_t this_addr;
+ size_t len;
+ struct rtattr *rta;
+ struct ifaddrmsg *ifa;
+
+ ifa = NLMSG_DATA(nlm);
+ if (ifa->ifa_index != ia->ifa_ifindex || ifa->ifa_family != AF_INET)
+ return 0;
+
+ rta = IFA_RTA(ifa);
+ len = NLMSG_PAYLOAD(nlm, sizeof(*ifa));
+ for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ switch (rta->rta_type) {
+ case IFA_LOCAL:
+ memcpy(&this_addr, RTA_DATA(rta), sizeof(this_addr));
+ if (this_addr == ia->ifa_addr.s_addr) {
+ ia->ifa_found = true;
+ return 1;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+if_addressexists(struct interface *ifp, struct in_addr *addr)
+{
+ struct ifiaddr ia = {
+ .ifa_ifindex = ifp->index,
+ .ifa_addr = *addr,
+ .ifa_found = false,
+ };
+ struct nlma nlm = {
+ .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+ .hdr.nlmsg_type = RTM_GETADDR,
+ .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH,
+ .ifa.ifa_family = AF_INET,
+ .ifa.ifa_index = ifp->index,
+ };
+
+ int error = if_sendnetlink(ifp->ctx, NETLINK_ROUTE, &nlm.hdr,
+ &_if_addressexists, &ia);
+ if (error == -1)
+ return -1;
+ return ia.ifa_found ? 1 : 0;
+}
+#endif
+
+struct nlmr
+{
+ struct nlmsghdr hdr;
+ struct rtmsg rt;
+ char buffer[256];
+};
+
+int
+if_route(unsigned char cmd, const struct rt *rt)
+{
+ struct nlmr nlm;
+ bool gateway_unspec;
+
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ switch (cmd) {
+ case RTM_CHANGE:
+ nlm.hdr.nlmsg_type = RTM_NEWROUTE;
+ nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_REPLACE;
+ break;
+ case RTM_ADD:
+ nlm.hdr.nlmsg_type = RTM_NEWROUTE;
+ nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL;
+ break;
+ case RTM_DELETE:
+ nlm.hdr.nlmsg_type = RTM_DELROUTE;
+ break;
+ }
+ nlm.hdr.nlmsg_flags |= NLM_F_REQUEST;
+ nlm.rt.rtm_family = (unsigned char)rt->rt_dest.sa_family;
+ nlm.rt.rtm_table = RT_TABLE_MAIN;
+
+ gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
+
+ if (cmd == RTM_DELETE) {
+ nlm.rt.rtm_scope = RT_SCOPE_NOWHERE;
+ } else {
+ /* Address generated routes are RTPROT_KERNEL,
+ * otherwise RTPROT_BOOT */
+#ifdef RTPROT_RA
+ if (rt->rt_dflags & RTDF_RA)
+ nlm.rt.rtm_protocol = RTPROT_RA;
+ else
+#endif
+#ifdef RTPROT_DHCP
+ if (rt->rt_dflags & RTDF_DHCP)
+ nlm.rt.rtm_protocol = RTPROT_DHCP;
+ else
+#endif
+ if (rt->rt_dflags & RTDF_IFA_ROUTE)
+ nlm.rt.rtm_protocol = RTPROT_KERNEL;
+ else
+ nlm.rt.rtm_protocol = RTPROT_BOOT;
+ if (rt->rt_ifp->flags & IFF_LOOPBACK)
+ nlm.rt.rtm_scope = RT_SCOPE_HOST;
+ else if (gateway_unspec)
+ nlm.rt.rtm_scope = RT_SCOPE_LINK;
+ else
+ nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE;
+ if (rt->rt_flags & RTF_REJECT)
+ nlm.rt.rtm_type = RTN_UNREACHABLE;
+ else
+ nlm.rt.rtm_type = RTN_UNICAST;
+ }
+
+#define ADDSA(type, sa) \
+ add_attr_l(&nlm.hdr, sizeof(nlm), (type), \
+ (const char *)(sa) + sa_addroffset((sa)), \
+ (unsigned short)sa_addrlen((sa)));
+ nlm.rt.rtm_dst_len = (unsigned char)sa_toprefix(&rt->rt_netmask);
+ /* rt->rt_dest and rt->gateway are unions where sockaddr_in6
+ * is the biggest member. However, we access them as the
+ * generic sockaddr and coverity thinks this will overrun. */
+ /* coverity[overrun-buffer-arg] */
+ ADDSA(RTA_DST, &rt->rt_dest);
+ if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
+ if (!gateway_unspec) {
+ /* coverity[overrun-buffer-arg] */
+ ADDSA(RTA_GATEWAY, &rt->rt_gateway);
+ }
+ /* Cannot add tentative source addresses.
+ * We don't know this here, so just skip INET6 ifa's.*/
+ if (!sa_is_unspecified(&rt->rt_ifa) &&
+ rt->rt_ifa.sa_family != AF_INET6)
+ ADDSA(RTA_PREFSRC, &rt->rt_ifa);
+ if (rt->rt_mtu) {
+ char metricsbuf[32];
+ struct rtattr *metrics = (void *)metricsbuf;
+
+ metrics->rta_type = RTA_METRICS;
+ metrics->rta_len = RTA_LENGTH(0);
+ rta_add_attr_32(metrics, sizeof(metricsbuf),
+ RTAX_MTU, rt->rt_mtu);
+ add_attr_l(&nlm.hdr, sizeof(nlm), RTA_METRICS,
+ RTA_DATA(metrics),
+ (unsigned short)RTA_PAYLOAD(metrics));
+ }
+
+#ifdef HAVE_ROUTE_PREF
+ if (rt->rt_dflags & RTDF_RA) {
+ uint8_t pref;
+
+ switch(rt->rt_pref) {
+ case RTPREF_LOW:
+ pref = ICMPV6_ROUTER_PREF_LOW;
+ break;
+ case RTPREF_MEDIUM:
+ pref = ICMPV6_ROUTER_PREF_MEDIUM;
+ break;
+ case RTPREF_HIGH:
+ pref = ICMPV6_ROUTER_PREF_HIGH;
+ break;
+ default:
+ pref = ICMPV6_ROUTER_PREF_INVALID;
+ break;
+ }
+ add_attr_8(&nlm.hdr, sizeof(nlm), RTA_PREF, pref);
+ }
+#endif
+ }
+
+ if (!sa_is_loopback(&rt->rt_gateway))
+ add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->rt_ifp->index);
+
+ if (rt->rt_metric != 0)
+ add_attr_32(&nlm.hdr, sizeof(nlm), RTA_PRIORITY,
+ rt->rt_metric);
+
+ return if_sendnetlink(rt->rt_ifp->ctx, NETLINK_ROUTE, &nlm.hdr,
+ NULL, NULL);
+}
+
+static int
+_if_initrt(struct dhcpcd_ctx *ctx, void *arg,
+ struct nlmsghdr *nlm)
+{
+ struct rt rt, *rtn;
+ rb_tree_t *kroutes = arg;
+
+ if (if_copyrt(ctx, &rt, nlm) != 0)
+ return 0;
+ if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
+ logerr(__func__);
+ return 0;
+ }
+ memcpy(rtn, &rt, sizeof(*rtn));
+ if (rb_tree_insert_node(kroutes, rtn) != rtn)
+ rt_free(rtn);
+ return 0;
+}
+
+int
+if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af)
+{
+ struct nlmr nlm = {
+ .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
+ .hdr.nlmsg_type = RTM_GETROUTE,
+ .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH,
+ .rt.rtm_table = RT_TABLE_MAIN,
+ .rt.rtm_family = (unsigned char)af,
+ };
+
+ return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr,
+ &_if_initrt, kroutes);
+}
+
+#ifdef INET
+/* Linux is a special snowflake when it comes to BPF. */
+const char *bpf_name = "Packet Socket";
+
+/* Linux is a special snowflake for opening BPF. */
+struct bpf *
+bpf_open(const struct interface *ifp,
+ int (*filter)(const struct bpf *, const struct in_addr *),
+ const struct in_addr *ia)
+{
+ struct bpf *bpf;
+ union sockunion {
+ struct sockaddr sa;
+ struct sockaddr_ll sll;
+ struct sockaddr_storage ss;
+ } su = {
+ .sll = {
+ .sll_family = PF_PACKET,
+ .sll_protocol = htons(ETH_P_ALL),
+ .sll_ifindex = (int)ifp->index,
+ }
+ };
+#ifdef PACKET_AUXDATA
+ int n;
+#endif
+
+ bpf = calloc(1, sizeof(*bpf));
+ if (bpf == NULL)
+ return NULL;
+ bpf->bpf_ifp = ifp;
+
+ /* Allocate a suitably large buffer for a single packet. */
+ bpf->bpf_size = ETH_DATA_LEN;
+ bpf->bpf_buffer = malloc(bpf->bpf_size);
+ if (bpf->bpf_buffer == NULL)
+ goto eexit;
+
+ bpf->bpf_fd = xsocket(PF_PACKET, SOCK_RAW|SOCK_CXNB,htons(ETH_P_ALL));
+ if (bpf->bpf_fd == -1)
+ goto eexit;
+
+ /* We cannot validate the correct interface,
+ * so we MUST set this first. */
+ if (bind(bpf->bpf_fd, &su.sa, sizeof(su.sll)) == -1)
+ goto eexit;
+
+ if (filter(bpf, ia) != 0)
+ goto eexit;
+
+ /* In the ideal world, this would be set before the bind and filter. */
+#ifdef PACKET_AUXDATA
+ n = 1;
+ if (setsockopt(bpf->bpf_fd, SOL_PACKET, PACKET_AUXDATA,
+ &n, sizeof(n)) != 0) {
+ if (errno != ENOPROTOOPT)
+ goto eexit;
+ }
+#endif
+
+ /*
+ * At this point we could have received packets for the wrong
+ * interface or which don't pass the filter.
+ * Linux should flush upon setting the filter like every other OS.
+ * There is no way of flushing them from userland.
+ * As such, consumers need to inspect each packet to ensure it's valid.
+ * Or to put it another way, don't trust the Linux BPF filter.
+ */
+
+ return bpf;
+
+eexit:
+ if (bpf->bpf_fd != -1)
+ close(bpf->bpf_fd);
+ free(bpf->bpf_buffer);
+ free(bpf);
+ return NULL;
+}
+
+/* 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
+bpf_read(struct bpf *bpf, void *data, size_t len)
+{
+ ssize_t bytes;
+ struct iovec iov = {
+ .iov_base = bpf->bpf_buffer,
+ .iov_len = bpf->bpf_size,
+ };
+ struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
+#ifdef PACKET_AUXDATA
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct cmsghdr *cmsg;
+ struct tpacket_auxdata *aux;
+#endif
+
+#ifdef PACKET_AUXDATA
+ msg.msg_control = cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+#endif
+
+ bytes = recvmsg(bpf->bpf_fd, &msg, 0);
+ if (bytes == -1)
+ return -1;
+ bpf->bpf_flags |= BPF_EOF; /* We only ever read one packet. */
+ bpf->bpf_flags &= ~BPF_PARTIALCSUM;
+ if (bytes) {
+ if (bpf_frame_bcast(bpf->bpf_ifp, bpf->bpf_buffer) == 0)
+ bpf->bpf_flags |= BPF_BCAST;
+ else
+ bpf->bpf_flags &= ~BPF_BCAST;
+ if ((size_t)bytes > len)
+ bytes = (ssize_t)len;
+ memcpy(data, bpf->bpf_buffer, (size_t)bytes);
+#ifdef PACKET_AUXDATA
+ for (cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg;
+ cmsg = CMSG_NXTHDR(&msg, cmsg))
+ {
+ if (cmsg->cmsg_level == SOL_PACKET &&
+ cmsg->cmsg_type == PACKET_AUXDATA) {
+ aux = (void *)CMSG_DATA(cmsg);
+ if (aux->tp_status & TP_STATUS_CSUMNOTREADY)
+ bpf->bpf_flags |= BPF_PARTIALCSUM;
+ }
+ }
+#endif
+ }
+ return bytes;
+}
+
+int
+bpf_attach(int s, void *filter, unsigned int filter_len)
+{
+ struct sock_fprog pf = {
+ .filter = filter,
+ .len = (unsigned short)filter_len,
+ };
+
+ /* Install the filter. */
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) == -1)
+ return -1;
+
+#ifdef SO_LOCK_FILTER
+ int on = 1;
+
+ if (setsockopt(s, SOL_SOCKET, SO_LOCK_FILTER, &on, sizeof(on)) == -1)
+ return -1;
+#endif
+
+ return 0;
+}
+
+int
+if_address(unsigned char cmd, const struct ipv4_addr *ia)
+{
+ struct nlma nlm;
+ struct ifa_cacheinfo cinfo;
+ int retval = 0;
+#ifdef IFA_F_NOPREFIXROUTE
+ uint32_t flags = 0;
+#endif
+
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+ nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
+ nlm.hdr.nlmsg_type = cmd;
+ if (cmd == RTM_NEWADDR)
+ nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+ nlm.ifa.ifa_index = ia->iface->index;
+ nlm.ifa.ifa_family = AF_INET;
+
+ nlm.ifa.ifa_prefixlen = inet_ntocidr(ia->mask);
+
+#if 0
+ /* This creates the aliased interface */
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL,
+ ia->iface->alias,
+ (unsigned short)(strlen(ia->iface->alias) + 1));
+#endif
+
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL,
+ &ia->addr.s_addr, sizeof(ia->addr.s_addr));
+
+ if (cmd == RTM_NEWADDR) {
+#ifdef IFA_F_NOPREFIXROUTE
+ if (nlm.ifa.ifa_prefixlen < 32)
+ flags |= IFA_F_NOPREFIXROUTE;
+ add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags);
+#endif
+
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_BROADCAST,
+ &ia->brd.s_addr, sizeof(ia->brd.s_addr));
+
+ memset(&cinfo, 0, sizeof(cinfo));
+ cinfo.ifa_prefered = ia->pltime;
+ cinfo.ifa_valid = ia->vltime;
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO,
+ &cinfo, sizeof(cinfo));
+ }
+
+ if (if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr,
+ NULL, NULL) == -1)
+ retval = -1;
+ return retval;
+}
+
+int
+if_addrflags(__unused const struct interface *ifp,
+__unused const struct in_addr *addr, __unused const char *alias)
+{
+
+ /* Linux has no support for IPv4 address flags */
+ return 0;
+}
+#endif
+
+#ifdef INET6
+int
+if_address6(unsigned char cmd, const struct ipv6_addr *ia)
+{
+ struct nlma nlm;
+ struct ifa_cacheinfo cinfo;
+#if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE)
+ uint32_t flags = 0;
+#endif
+
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+ nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
+ nlm.hdr.nlmsg_type = cmd;
+ if (cmd == RTM_NEWADDR)
+ nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+ nlm.ifa.ifa_index = ia->iface->index;
+ nlm.ifa.ifa_family = AF_INET6;
+
+ /* Add as /128 if no IFA_F_NOPREFIXROUTE ? */
+ nlm.ifa.ifa_prefixlen = ia->prefix_len;
+
+#if 0
+ /* This creates the aliased interface */
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL,
+ ia->iface->alias, (unsigned short)(strlen(ia->iface->alias) + 1));
+#endif
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL,
+ &ia->addr.s6_addr, sizeof(ia->addr.s6_addr));
+
+ if (cmd == RTM_NEWADDR) {
+#ifdef IPV6_MANAGETEMPADDR
+ if (ia->flags & IPV6_AF_TEMPORARY) {
+ /* Currently the kernel filters out these flags */
+#ifdef IFA_F_NOPREFIXROUTE
+ flags |= IFA_F_TEMPORARY;
+#else
+ nlm.ifa.ifa_flags |= IFA_F_TEMPORARY;
+#endif
+ }
+#elif IFA_F_MANAGETEMPADDR
+ if (ia->flags & IPV6_AF_AUTOCONF && IA6_CANAUTOCONF(ia))
+ flags |= IFA_F_MANAGETEMPADDR;
+#endif
+#ifdef IFA_F_NOPREFIXROUTE
+ if (!IN6_IS_ADDR_LINKLOCAL(&ia->addr))
+ flags |= IFA_F_NOPREFIXROUTE;
+#endif
+#if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE)
+ add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags);
+#endif
+
+ memset(&cinfo, 0, sizeof(cinfo));
+ cinfo.ifa_prefered = ia->prefix_pltime;
+ cinfo.ifa_valid = ia->prefix_vltime;
+ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO,
+ &cinfo, sizeof(cinfo));
+ }
+
+ return if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr,
+ NULL, NULL);
+}
+
+int
+if_addrflags6(const struct interface *ifp, const struct in6_addr *addr,
+ __unused const char *alias)
+{
+ char buf[PS_BUFLEN], *bp = buf, *line;
+ ssize_t buflen;
+ char *p, ifaddress[33], address[33], name[IF_NAMESIZE + 1];
+ unsigned int ifindex;
+ int prefix, scope, flags, i;
+
+ buflen = dhcp_readfile(ifp->ctx, PROC_INET6, buf, sizeof(buf));
+ if (buflen == -1)
+ return -1;
+ if ((size_t)buflen == sizeof(buf)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ p = ifaddress;
+ for (i = 0; i < (int)sizeof(addr->s6_addr); i++) {
+ p += snprintf(p, 3, "%.2x", addr->s6_addr[i]);
+ }
+ *p = '\0';
+
+ while ((line = get_line(&bp, &buflen)) != NULL) {
+ if (sscanf(line,
+ "%32[a-f0-9] %x %x %x %x %"TOSTRING(IF_NAMESIZE)"s\n",
+ address, &ifindex, &prefix, &scope, &flags, name) != 6 ||
+ strlen(address) != 32)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (strcmp(name, ifp->name) == 0 &&
+ strcmp(ifaddress, address) == 0)
+ return flags;
+ }
+
+ errno = ESRCH;
+ return -1;
+}
+
+int
+if_getlifetime6(__unused struct ipv6_addr *ia)
+{
+
+ /* God knows how to work out address lifetimes on Linux */
+ errno = ENOTSUP;
+ return -1;
+}
+
+struct nlml
+{
+ struct nlmsghdr hdr;
+ struct ifinfomsg i;
+ char buffer[32];
+};
+
+#ifdef HAVE_IN6_ADDR_GEN_MODE_NONE
+static struct rtattr *
+add_attr_nest(struct nlmsghdr *n, unsigned short maxlen, unsigned short type)
+{
+ struct rtattr *nest;
+
+ nest = NLMSG_TAIL(n);
+ add_attr_l(n, maxlen, type, NULL, 0);
+ return nest;
+}
+
+static void
+add_attr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
+{
+
+ nest->rta_len = (unsigned short)((char *)NLMSG_TAIL(n) - (char *)nest);
+}
+#endif
+
+static int
+if_disable_autolinklocal(struct dhcpcd_ctx *ctx, unsigned int ifindex)
+{
+#ifdef HAVE_IN6_ADDR_GEN_MODE_NONE
+ struct nlml nlm;
+ struct rtattr *afs, *afs6;
+
+ memset(&nlm, 0, sizeof(nlm));
+ nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ nlm.hdr.nlmsg_type = RTM_NEWLINK;
+ nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
+ nlm.i.ifi_family = AF_INET6;
+ nlm.i.ifi_index = (int)ifindex;
+ afs = add_attr_nest(&nlm.hdr, sizeof(nlm), IFLA_AF_SPEC);
+ afs6 = add_attr_nest(&nlm.hdr, sizeof(nlm), AF_INET6);
+ add_attr_8(&nlm.hdr, sizeof(nlm), IFLA_INET6_ADDR_GEN_MODE,
+ IN6_ADDR_GEN_MODE_NONE);
+ add_attr_nest_end(&nlm.hdr, afs6);
+ add_attr_nest_end(&nlm.hdr, afs);
+
+ return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL);
+#else
+ UNUSED(ctx);
+ UNUSED(ifindex);
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
+static const char *p_conf = "/proc/sys/net/ipv6/conf";
+static const char *p_neigh = "/proc/sys/net/ipv6/neigh";
+
+void
+if_setup_inet6(const struct interface *ifp)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ int ra;
+ char path[256];
+
+ /* The kernel cannot make stable private addresses.
+ * However, a lot of distros ship newer kernel headers than
+ * the kernel itself so sweep that error under the table. */
+ if (if_disable_autolinklocal(ctx, ifp->index) == -1 &&
+ errno != ENODEV && errno != ENOTSUP && errno != EINVAL)
+ logdebug("%s: if_disable_autolinklocal", ifp->name);
+
+ /*
+ * If not doing autoconf, don't disable the kernel from doing it.
+ * If we need to, we should have another option actively disable it.
+ */
+ if (!(ifp->options->options & DHCPCD_IPV6RS))
+ return;
+
+ snprintf(path, sizeof(path), "%s/%s/autoconf", p_conf, ifp->name);
+ ra = check_proc_int(ctx, path);
+ if (ra != 1 && ra != -1) {
+ if (if_writepathuint(ctx, path, 0) == -1)
+ logerr("%s: %s", __func__, path);
+ }
+
+ snprintf(path, sizeof(path), "%s/%s/accept_ra", p_conf, ifp->name);
+ ra = check_proc_int(ctx, path);
+ if (ra == -1) {
+ /* The sysctl probably doesn't exist, but this isn't an
+ * error as such so just log it and continue */
+ if (errno != ENOENT)
+ logerr("%s: %s", __func__, path);
+ } else if (ra != 0) {
+ if (if_writepathuint(ctx, path, 0) == -1)
+ logerr("%s: %s", __func__, path);
+ }
+}
+
+int
+if_applyra(const struct ra *rap)
+{
+ char path[256];
+ const char *ifname = rap->iface->name;
+ struct dhcpcd_ctx *ctx = rap->iface->ctx;
+ int error = 0;
+
+ if (rap->hoplimit != 0) {
+ snprintf(path, sizeof(path), "%s/%s/hop_limit", p_conf, ifname);
+ if (if_writepathuint(ctx, path, rap->hoplimit) == -1)
+ error = -1;
+ }
+
+ if (rap->retrans != 0) {
+ snprintf(path, sizeof(path), "%s/%s/retrans_time_ms",
+ p_neigh, ifname);
+ if (if_writepathuint(ctx, path, rap->retrans) == -1)
+ error = -1;
+ }
+
+ if (rap->reachable != 0) {
+ snprintf(path, sizeof(path), "%s/%s/base_reachable_time_ms",
+ p_neigh, ifname);
+ if (if_writepathuint(ctx, path, rap->reachable) == -1)
+ error = -1;
+ }
+
+ return error;
+}
+
+int
+ip6_forwarding(const char *ifname)
+{
+ char path[256], buf[64];
+ int error, i;
+
+ if (ifname == NULL)
+ ifname = "all";
+ snprintf(path, sizeof(path), "%s/%s/forwarding", p_conf, ifname);
+ if (readfile(path, buf, sizeof(buf)) == -1)
+ return 0;
+ i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error);
+ if (error != 0 && error != ENOTSUP)
+ return 0;
+ return i;
+}
+
+#endif /* INET6 */
diff --git a/src/if-options.c b/src/if-options.c
new file mode 100644
index 000000000000..dd70c80656b2
--- /dev/null
+++ b/src/if-options.c
@@ -0,0 +1,2773 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <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 "duid.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "logerr.h"
+#include "sa.h"
+
+#define IN_CONFIG_BLOCK(ifo) ((ifo)->options & DHCPCD_FORKED)
+#define SET_CONFIG_BLOCK(ifo) ((ifo)->options |= DHCPCD_FORKED)
+#define CLEAR_CONFIG_BLOCK(ifo) ((ifo)->options &= ~DHCPCD_FORKED)
+
+static unsigned long long default_options;
+
+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'},
+ {"inform6", optional_argument, NULL, O_INFORM6},
+ {"timeout", required_argument, NULL, 't'},
+ {"userclass", required_argument, NULL, 'u'},
+#ifndef SMALL
+ {"msuserclass", required_argument, NULL, O_MSUSERCLASS},
+#endif
+ {"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", optional_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'},
+ {"manager", no_argument, NULL, 'M'},
+ {"renew", no_argument, NULL, 'N'},
+ {"nooption", required_argument, NULL, 'O'},
+ {"printpidfile", no_argument, NULL, 'P'},
+ {"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'},
+ {"oneshot", no_argument, NULL, '1'},
+ {"ipv4only", no_argument, NULL, '4'},
+ {"ipv6only", no_argument, NULL, '6'},
+ {"anonymous", no_argument, NULL, O_ANONYMOUS},
+ {"randomise_hwaddr",no_argument, NULL, O_RANDOMISE_HWADDR},
+ {"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},
+ {"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", optional_argument, NULL, O_IA_NA},
+ {"ia_ta", optional_argument, NULL, O_IA_TA},
+ {"ia_pd", optional_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},
+ {"lastleaseextend", no_argument, NULL, O_LASTLEASE_EXTEND},
+ {"inactive", no_argument, NULL, O_INACTIVE},
+ {"mudurl", required_argument, NULL, O_MUDURL},
+ {"link_rcvbuf", required_argument, NULL, O_LINK_RCVBUF},
+ {"configure", no_argument, NULL, O_CONFIGURE},
+ {"noconfigure", no_argument, NULL, O_NOCONFIGURE},
+ {NULL, 0, NULL, '\0'}
+};
+
+static char *
+add_environ(char ***array, const char *value, int uniq)
+{
+ char **newlist, **list = *array;
+ size_t i = 0, l, lv;
+ char *match = NULL, *p, *n;
+
+ match = strdup(value);
+ if (match == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ p = strchr(match, '=');
+ if (p == NULL) {
+ logerrx("%s: no assignment: %s", __func__, value);
+ free(match);
+ return NULL;
+ }
+ *p++ = '\0';
+ l = strlen(match);
+
+ while (list && list[i]) {
+ if (match && strncmp(list[i], match, l) == 0) {
+ if (uniq) {
+ n = strdup(value);
+ if (n == NULL) {
+ logerr(__func__);
+ free(match);
+ return NULL;
+ }
+ free(list[i]);
+ list[i] = n;
+ } else {
+ /* Append a space and the value to it */
+ l = strlen(list[i]);
+ lv = strlen(p);
+ n = realloc(list[i], l + lv + 2);
+ if (n == NULL) {
+ logerr(__func__);
+ free(match);
+ return NULL;
+ }
+ list[i] = n;
+ list[i][l] = ' ';
+ memcpy(list[i] + l + 1, p, lv);
+ list[i][l + lv + 1] = '\0';
+ }
+ free(match);
+ return list[i];
+ }
+ i++;
+ }
+
+ free(match);
+ n = strdup(value);
+ if (n == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ newlist = reallocarray(list, i + 2, sizeof(char *));
+ if (newlist == NULL) {
+ logerr(__func__);
+ free(n);
+ return NULL;
+ }
+ newlist[i] = n;
+ newlist[i + 1] = NULL;
+ *array = newlist;
+ return newlist[i];
+}
+
+#define PARSE_STRING 0
+#define PARSE_STRING_NULL 1
+#define PARSE_HWADDR 2
+#define parse_string(a, b, c) parse_str((a), (b), (c), PARSE_STRING)
+#define parse_nstring(a, b, c) parse_str((a), (b), (c), PARSE_STRING_NULL)
+#define parse_hwaddr(a, b, c) parse_str((a), (b), (c), PARSE_HWADDR)
+static ssize_t
+parse_str(char *sbuf, size_t slen, const char *str, int flags)
+{
+ size_t l;
+ const char *p, *end;
+ int i;
+ char c[4], cmd;
+
+ end = str + strlen(str);
+ /* If surrounded by quotes then it's a string */
+ if (*str == '"') {
+ p = end - 1;
+ if (*p == '"') {
+ str++;
+ end = p;
+ }
+ } 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 (flags == PARSE_HWADDR && *str) {
+ if (sbuf)
+ *sbuf++ = 0;
+ l++;
+ }
+ c[3] = '\0';
+ while (str < end) {
+ if (++l > slen && sbuf) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ if (*str == '\\') {
+ str++;
+ switch((cmd = *str++)) {
+ case '\0':
+ str--;
+ break;
+ case 'b':
+ if (sbuf)
+ *sbuf++ = '\b';
+ break;
+ case 'n':
+ if (sbuf)
+ *sbuf++ = '\n';
+ break;
+ case 'r':
+ if (sbuf)
+ *sbuf++ = '\r';
+ break;
+ case 't':
+ if (sbuf)
+ *sbuf++ = '\t';
+ 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') {
+ c[2] = '\0';
+ if (sbuf)
+ *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') {
+ i = (int)strtol(c, NULL, 8);
+ if (i > 255)
+ i = 255;
+ if (sbuf)
+ *sbuf++ = (char)i;
+ } else
+ l--;
+ break;
+ default:
+ if (sbuf)
+ *sbuf++ = cmd;
+ break;
+ }
+ } else {
+ if (sbuf)
+ *sbuf++ = *str;
+ str++;
+ }
+ }
+ if (flags == PARSE_STRING_NULL) {
+ l++;
+ if (sbuf != NULL) {
+ if (l > slen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ *sbuf = '\0';
+ }
+ }
+ 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);
+}
+
+#ifdef AUTH
+static int
+parse_uint32(uint32_t *i, const char *arg)
+{
+
+ return parse_iaid1((uint8_t *)i, arg, sizeof(uint32_t), 0);
+}
+#endif
+
+static char **
+splitv(int *argc, char **argv, const char *arg)
+{
+ char **n, **v = argv;
+ char *o = strdup(arg), *p, *t, *nt;
+
+ if (o == NULL) {
+ logerr(__func__);
+ return v;
+ }
+ p = o;
+ while ((t = strsep(&p, ", "))) {
+ nt = strdup(t);
+ if (nt == NULL) {
+ logerr(__func__);
+ free(o);
+ return v;
+ }
+ n = reallocarray(v, (size_t)(*argc) + 1, sizeof(char *));
+ if (n == NULL) {
+ logerr(__func__);
+ free(o);
+ free(nt);
+ return v;
+ }
+ v = n;
+ v[(*argc)++] = nt;
+ }
+ free(o);
+ return v;
+}
+
+#ifdef INET
+static int
+parse_addr(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))
+ {
+ logerrx("invalid CIDR: %s", p);
+ return -1;
+ }
+ }
+
+ if (addr != NULL && inet_aton(arg, addr) == 0) {
+ logerrx("invalid IP address: %s", 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(__unused struct in_addr *addr, __unused struct in_addr *net,
+ __unused const char *arg)
+{
+
+ logerrx("No IPv4 support");
+ return -1;
+}
+#endif
+
+static void
+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;
+ }
+
+#ifdef DHCP6
+ 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;
+ }
+#endif
+#else
+ UNUSED(arg);
+#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;
+}
+
+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 || *s == '\0')
+ return NULL;
+ while (*s == ' ' || *s == '\t') {
+ s++;
+ if (*s == '\0')
+ return NULL;
+ }
+ return UNCONST(s);
+}
+
+#ifdef AUTH
+/* 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);
+}
+#endif
+
+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;
+ 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 group *grp;
+#ifdef AUTH
+ struct token *token;
+#endif
+#ifdef _REENTRANT
+ struct group grpbuf;
+#endif
+#ifdef DHCP6
+ size_t sl;
+ struct if_ia *ia;
+ uint8_t iaid[4];
+#ifndef SMALL
+ struct if_sla *sla, *slap;
+#endif
+#endif
+
+ dop = NULL;
+ dop_len = NULL;
+#ifdef INET6
+ i = 0;
+#endif
+
+/* Add a guard for static analysers.
+ * This should not be needed really because of the argument_required option
+ * in the options declaration above. */
+#define ARG_REQUIRED if (arg == NULL) goto arg_required
+
+ switch(opt) {
+ case 'f': /* FALLTHROUGH */
+ case 'g': /* FALLTHROUGH */
+ case 'n': /* FALLTHROUGH */
+ case 'q': /* FALLTHROUGH */
+ case 'x': /* FALLTHROUGH */
+ case 'N': /* FALLTHROUGH */
+ case 'P': /* 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':
+ ARG_REQUIRED;
+ if (IN_CONFIG_BLOCK(ifo)) {
+ logerrx("%s: per interface scripts"
+ " are no longer supported",
+ ifname);
+ return -1;
+ }
+ if (ctx->script != dhcpcd_default_script)
+ free(ctx->script);
+ s = parse_nstring(NULL, 0, arg);
+ if (s == 0) {
+ ctx->script = NULL;
+ break;
+ }
+ dl = (size_t)s;
+ if (s == -1 || (ctx->script = malloc(dl)) == NULL) {
+ ctx->script = NULL;
+ logerr(__func__);
+ return -1;
+ }
+ s = parse_nstring(ctx->script, dl, arg);
+ if (s == -1 ||
+ ctx->script[0] == '\0' ||
+ strcmp(ctx->script, "/dev/null") == 0)
+ {
+ free(ctx->script);
+ ctx->script = NULL;
+ }
+ break;
+ case 'd':
+ ifo->options |= DHCPCD_DEBUG;
+ break;
+ case 'e':
+ ARG_REQUIRED;
+ add_environ(&ifo->environ, arg, 1);
+ break;
+ case 'h':
+ if (!arg) {
+ ifo->options |= DHCPCD_HOSTNAME;
+ break;
+ }
+ s = parse_nstring(ifo->hostname, sizeof(ifo->hostname), arg);
+ if (s == -1) {
+ logerr("%s: hostname", __func__);
+ return -1;
+ }
+ if (s != 0 && ifo->hostname[0] == '.') {
+ logerrx("hostname cannot begin with .");
+ return -1;
+ }
+ 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) {
+ logerr("vendorclassid");
+ return -1;
+ }
+ *ifo->vendorclassid = (uint8_t)s;
+ break;
+ case 'j':
+ ARG_REQUIRED;
+ /* per interface logging is not supported
+ * don't want to overide the commandline */
+ if (!IN_CONFIG_BLOCK(ifo) && ctx->logfile == NULL) {
+ logclose();
+ ctx->logfile = strdup(arg);
+ logopen(ctx->logfile);
+ }
+ break;
+ case 'k':
+ ifo->options |= DHCPCD_RELEASE;
+ break;
+ case 'l':
+ ARG_REQUIRED;
+ if (strcmp(arg, "-1") == 0) {
+ ifo->leasetime = DHCP_INFINITE_LIFETIME;
+ break;
+ }
+ ifo->leasetime = (uint32_t)strtou(arg, NULL,
+ 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logerrx("failed to convert leasetime %s", arg);
+ return -1;
+ }
+ break;
+ case 'm':
+ ARG_REQUIRED;
+ ifo->metric = (int)strtoi(arg, NULL, 0, 0, INT32_MAX, &e);
+ if (e) {
+ logerrx("failed to convert metric %s", arg);
+ return -1;
+ }
+ break;
+ case 'o':
+ ARG_REQUIRED;
+ if (ctx->options & DHCPCD_PRINT_PIDFILE)
+ break;
+ 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)
+ {
+ logerrx("unknown option: %s", arg);
+ return -1;
+ }
+ break;
+ case O_REJECT:
+ ARG_REQUIRED;
+ if (ctx->options & DHCPCD_PRINT_PIDFILE)
+ break;
+ 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)
+ {
+ logerrx("unknown option: %s", arg);
+ return -1;
+ }
+ break;
+ case 'p':
+ ifo->options |= DHCPCD_PERSISTENT;
+ break;
+ case 'r':
+ if (parse_addr(&ifo->req_addr, NULL, arg) != 0)
+ return -1;
+ ifo->options |= DHCPCD_REQUEST;
+ ifo->req_mask.s_addr = 0;
+ break;
+ case 's':
+ if (arg && *arg != '\0') {
+ /* Strip out a broadcast address */
+ p = strchr(arg, '/');
+ if (p != NULL) {
+ p = strchr(p + 1, '/');
+ if (p != NULL)
+ *p = '\0';
+ }
+ i = parse_addr(&ifo->req_addr, &ifo->req_mask, arg);
+ if (p != NULL) {
+ /* Ensure the original string is preserved */
+ *p++ = '/';
+ if (i == 0)
+ i = parse_addr(&ifo->req_brd, NULL, p);
+ }
+ if (i != 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 O_INFORM6:
+ ifo->options |= DHCPCD_INFORM6;
+ break;
+ case 't':
+ ARG_REQUIRED;
+ ifo->timeout = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logerrx("failed to convert timeout %s", arg);
+ return -1;
+ }
+ break;
+ case 'u':
+ dl = sizeof(ifo->userclass) - ifo->userclass[0] - 1;
+ s = parse_string((char *)ifo->userclass +
+ ifo->userclass[0] + 2, dl, arg);
+ if (s == -1) {
+ logerr("userclass");
+ 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;
+#ifndef SMALL
+ case O_MSUSERCLASS:
+ /* Some Microsoft DHCP servers expect userclass to be an
+ * opaque blob. This is not RFC 3004 compliant. */
+ s = parse_string((char *)ifo->userclass + 1,
+ sizeof(ifo->userclass) - 1, arg);
+ if (s == -1) {
+ logerr("msuserclass");
+ return -1;
+ }
+ ifo->userclass[0] = (uint8_t)s;
+ break;
+#endif
+ case 'v':
+ ARG_REQUIRED;
+ p = strchr(arg, ',');
+ if (!p || !p[1]) {
+ logerrx("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) {
+ logerr("vendor");
+ 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) {
+ logerrx("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) {
+ logerr("vendor");
+ 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':
+ ARG_REQUIRED;
+ ifo->reboot = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logerr("failed to convert reboot %s", arg);
+ return -1;
+ }
+ break;
+ case 'z':
+ ARG_REQUIRED;
+ if (!IN_CONFIG_BLOCK(ifo))
+ ctx->ifav = splitv(&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':
+ ARG_REQUIRED;
+ /* 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) {
+ logerr(__func__);
+ return -1;
+ }
+ snprintf(p, dl, "skip_hooks=%s", arg);
+ add_environ(&ifo->environ, p, 0);
+ free(p);
+ break;
+ case 'D':
+ ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID;
+ if (ifname != NULL) /* duid type only a global option */
+ break;
+ if (arg == NULL)
+ ctx->duid_type = DUID_DEFAULT;
+ else if (strcmp(arg, "ll") == 0)
+ ctx->duid_type = DUID_LL;
+ else if (strcmp(arg, "llt") == 0)
+ ctx->duid_type = DUID_LLT;
+ else if (strcmp(arg, "uuid") == 0)
+ ctx->duid_type = DUID_UUID;
+ else {
+ dl = hwaddr_aton(NULL, arg);
+ if (dl != 0) {
+ no = realloc(ctx->duid, dl);
+ if (no == NULL)
+ logerrx(__func__);
+ else {
+ ctx->duid = no;
+ ctx->duid_len = hwaddr_aton(no, arg);
+ }
+ }
+ }
+ 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 {
+ logerrx("invalid FQDN value: %s", 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_hwaddr((char *)ifo->clientid + 1,
+ CLIENTID_MAX_LEN, arg);
+ else
+ s = 0;
+ if (s == -1) {
+ logerr("clientid");
+ return -1;
+ }
+ ifo->options |= DHCPCD_CLIENTID;
+ ifo->clientid[0] = (uint8_t)s;
+ ifo->options &= ~DHCPCD_DUID;
+ 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_MANAGER;
+ break;
+ case 'O':
+ ARG_REQUIRED;
+ if (ctx->options & DHCPCD_PRINT_PIDFILE)
+ break;
+ 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)
+ {
+ logerrx("unknown option: %s", arg);
+ return -1;
+ }
+ break;
+ case 'Q':
+ ARG_REQUIRED;
+ if (ctx->options & DHCPCD_PRINT_PIDFILE)
+ break;
+ 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)
+ {
+ logerrx("unknown option: %s", arg);
+ return -1;
+ }
+ break;
+ case 'S':
+ ARG_REQUIRED;
+ p = strchr(arg, '=');
+ if (p == NULL) {
+ logerrx("static assignment required");
+ return -1;
+ }
+ p++;
+ if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) {
+ if (parse_addr(&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(&ifo->req_mask, NULL, p) != 0)
+ return -1;
+ } else if (strncmp(arg, "broadcast_address=",
+ strlen("broadcast_address=")) == 0)
+ {
+ if (parse_addr(&ifo->req_brd, 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)
+ {
+ struct in_addr addr3;
+
+ fp = np = strwhite(p);
+ if (np == NULL) {
+ logerrx("all routes need a gateway");
+ return -1;
+ }
+ *np++ = '\0';
+ np = strskipwhite(np);
+ if (parse_addr(&addr, &addr2, p) == -1 ||
+ parse_addr(&addr3, NULL, np) == -1)
+ {
+ *fp = ' ';
+ return -1;
+ }
+ *fp = ' ';
+ if ((rt = rt_new0(ctx)) == NULL)
+ return -1;
+ sa_in_init(&rt->rt_dest, &addr);
+ sa_in_init(&rt->rt_netmask, &addr2);
+ sa_in_init(&rt->rt_gateway, &addr3);
+ if (rt_proto_add_ctx(&ifo->routes, rt, ctx))
+ add_environ(&ifo->config, arg, 0);
+ } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) {
+ if (parse_addr(&addr, NULL, p) == -1)
+ return -1;
+ if ((rt = rt_new0(ctx)) == NULL)
+ return -1;
+ addr2.s_addr = INADDR_ANY;
+ sa_in_init(&rt->rt_dest, &addr2);
+ sa_in_init(&rt->rt_netmask, &addr2);
+ sa_in_init(&rt->rt_gateway, &addr);
+ if (rt_proto_add_ctx(&ifo->routes, rt, ctx))
+ add_environ(&ifo->config, arg, 0);
+ } 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) {
+ logerrx("invalid MTU %s", p);
+ return -1;
+ }
+ } else if (strncmp(arg, "ip6_address=", strlen("ip6_address=")) == 0) {
+ np = strchr(p, '/');
+ if (np)
+ *np++ = '\0';
+ if ((i = inet_pton(AF_INET6, p, &ifo->req_addr6)) == 1) {
+ if (np) {
+ ifo->req_prefix_len = (uint8_t)strtou(np,
+ NULL, 0, 0, 128, &e);
+ if (e) {
+ logerrx("%s: failed to "
+ "convert prefix len",
+ ifname);
+ return -1;
+ }
+ } else
+ ifo->req_prefix_len = 128;
+ }
+ if (np)
+ *(--np) = '\0';
+ if (i != 1) {
+ logerrx("invalid AF_INET6: %s", p);
+ memset(&ifo->req_addr6, 0,
+ sizeof(ifo->req_addr6));
+ return -1;
+ }
+ } else
+ add_environ(&ifo->config, arg, 1);
+ break;
+ case 'W':
+ if (parse_addr(&addr, &addr2, arg) != 0)
+ return -1;
+ if (strchr(arg, '/') == NULL)
+ addr2.s_addr = INADDR_BROADCAST;
+ naddr = reallocarray(ifo->whitelist,
+ ifo->whitelist_len + 2, sizeof(in_addr_t));
+ if (naddr == NULL) {
+ logerr(__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(&addr, &addr2, arg) != 0)
+ return -1;
+ if (strchr(arg, '/') == NULL)
+ addr2.s_addr = INADDR_BROADCAST;
+ naddr = reallocarray(ifo->blacklist,
+ ifo->blacklist_len + 2, sizeof(in_addr_t));
+ if (naddr == NULL) {
+ logerr(__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':
+ ARG_REQUIRED;
+ if (!IN_CONFIG_BLOCK(ifo))
+ ctx->ifdv = splitv(&ctx->ifdc, ctx->ifdv, arg);
+ break;
+ case '1':
+ ifo->options |= DHCPCD_ONESHOT;
+ break;
+ case '4':
+#ifdef INET
+ ifo->options &= ~DHCPCD_IPV6;
+ ifo->options |= DHCPCD_IPV4;
+ break;
+#else
+ logerrx("INET has been compiled out");
+ return -1;
+#endif
+ case '6':
+#ifdef INET6
+ ifo->options &= ~DHCPCD_IPV4;
+ ifo->options |= DHCPCD_IPV6;
+ break;
+#else
+ logerrx("INET6 has been compiled out");
+ return -1;
+#endif
+ 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;
+ case O_ANONYMOUS:
+ ifo->options |= DHCPCD_ANONYMOUS;
+ ifo->options &= ~DHCPCD_HOSTNAME;
+ ifo->fqdn = FQDN_DISABLE;
+
+ /* Block everything */
+ memset(ifo->nomask, 0xff, sizeof(ifo->nomask));
+ memset(ifo->nomask6, 0xff, sizeof(ifo->nomask6));
+
+ /* Allow the bare minimum through */
+#ifdef INET
+ del_option_mask(ifo->nomask, DHO_SUBNETMASK);
+ del_option_mask(ifo->nomask, DHO_CSR);
+ del_option_mask(ifo->nomask, DHO_ROUTER);
+ del_option_mask(ifo->nomask, DHO_DNSSERVER);
+ del_option_mask(ifo->nomask, DHO_DNSDOMAIN);
+ del_option_mask(ifo->nomask, DHO_BROADCAST);
+ del_option_mask(ifo->nomask, DHO_STATICROUTE);
+ del_option_mask(ifo->nomask, DHO_SERVERID);
+ del_option_mask(ifo->nomask, DHO_RENEWALTIME);
+ del_option_mask(ifo->nomask, DHO_REBINDTIME);
+ del_option_mask(ifo->nomask, DHO_DNSSEARCH);
+#endif
+
+#ifdef DHCP6
+ del_option_mask(ifo->nomask6, D6_OPTION_DNS_SERVERS);
+ del_option_mask(ifo->nomask6, D6_OPTION_DOMAIN_LIST);
+ del_option_mask(ifo->nomask6, D6_OPTION_SOL_MAX_RT);
+ del_option_mask(ifo->nomask6, D6_OPTION_INF_MAX_RT);
+#endif
+
+ break;
+ case O_RANDOMISE_HWADDR:
+ ifo->randomise_hwaddr = true;
+ break;
+#ifdef INET
+ case O_ARPING:
+ while (arg != NULL) {
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ if (parse_addr(&addr, NULL, arg) != 0)
+ return -1;
+ naddr = reallocarray(ifo->arping,
+ (size_t)ifo->arping_len + 1, sizeof(in_addr_t));
+ if (naddr == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ ifo->arping = naddr;
+ ifo->arping[ifo->arping_len++] = addr.s_addr;
+ arg = strskipwhite(fp);
+ }
+ break;
+ case O_DESTINATION:
+ ARG_REQUIRED;
+ if (ctx->options & DHCPCD_PRINT_PIDFILE)
+ break;
+ 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)
+ logerrx("option does not take"
+ " an IPv4 address: %s", arg);
+ else
+ logerrx("unknown option: %s", arg);
+ return -1;
+ }
+ break;
+ case O_FALLBACK:
+ ARG_REQUIRED;
+ free(ifo->fallback);
+ ifo->fallback = strdup(arg);
+ if (ifo->fallback == NULL) {
+ logerrx(__func__);
+ return -1;
+ }
+ break;
+#endif
+ case O_IAID:
+ ARG_REQUIRED;
+ if (ctx->options & DHCPCD_MANAGER && !IN_CONFIG_BLOCK(ifo)) {
+ logerrx("IAID must belong in an interface block");
+ return -1;
+ }
+ if (parse_iaid(ifo->iaid, arg, sizeof(ifo->iaid)) == -1) {
+ logerrx("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_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 DHCP6
+ 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) {
+#ifdef SMALL
+ logwarnx("%s: IA_PD not compiled in", ifname);
+ return -1;
+#else
+ if (ctx->options & DHCPCD_MANAGER &&
+ !IN_CONFIG_BLOCK(ifo))
+ {
+ logerrx("IA PD must belong in an "
+ "interface block");
+ return -1;
+ }
+ i = D6_OPTION_IA_PD;
+#endif
+ }
+ if (ctx->options & DHCPCD_MANAGER &&
+ !IN_CONFIG_BLOCK(ifo) && arg)
+ {
+ logerrx("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) {
+ logerr("invalid IAID: %s", arg);
+ return -1;
+ }
+ }
+ ia = NULL;
+ for (sl = 0; sl < ifo->ia_len; sl++) {
+ if ((arg == NULL && !ifo->ia[sl].iaid_set) ||
+ (arg != NULL && ifo->ia[sl].iaid_set &&
+ ifo->ia[sl].ia_type == (uint16_t)i &&
+ 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 == NULL) {
+ ia = reallocarray(ifo->ia,
+ ifo->ia_len + 1, sizeof(*ifo->ia));
+ if (ia == NULL) {
+ logerr(__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) {
+ logerrx("invalid AF_INET6: %s", arg);
+ memset(&ia->addr, 0, sizeof(ia->addr));
+ }
+ if (p && ia->ia_type == D6_OPTION_IA_PD) {
+ ia->prefix_len = (uint8_t)strtou(p,
+ NULL, 0, 8, 120, &e);
+ if (e) {
+ logerrx("%s: failed to convert"
+ " prefix len",
+ p);
+ ia->prefix_len = 0;
+ }
+ }
+ }
+#ifndef SMALL
+ ia->sla_max = 0;
+ ia->sla_len = 0;
+ ia->sla = NULL;
+#endif
+ }
+
+#ifdef SMALL
+ break;
+#else
+ 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 = reallocarray(ia->sla,
+ ia->sla_len + 1, sizeof(*ia->sla));
+ if (sla == NULL) {
+ logerr(__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))
+ {
+ logerrx("%s: interface name too long", arg);
+ goto err_sla;
+ }
+ sla->sla_set = false;
+ sla->prefix_len = 0;
+ sla->suffix = 1;
+ p = np;
+ if (p) {
+ np = strchr(p, '/');
+ if (np)
+ *np++ = '\0';
+ if (*p != '\0') {
+ sla->sla = (uint32_t)strtou(p, NULL,
+ 0, 0, UINT32_MAX, &e);
+ sla->sla_set = true;
+ if (e) {
+ logerrx("%s: failed to convert "
+ "sla",
+ ifname);
+ goto err_sla;
+ }
+ }
+ p = np;
+ }
+ if (p) {
+ np = strchr(p, '/');
+ if (np)
+ *np++ = '\0';
+ if (*p != '\0') {
+ sla->prefix_len = (uint8_t)strtou(p,
+ NULL, 0, 0, 120, &e);
+ if (e) {
+ logerrx("%s: failed to "
+ "convert prefix len",
+ ifname);
+ goto err_sla;
+ }
+ }
+ p = np;
+ }
+ if (p) {
+ np = strchr(p, '/');
+ if (np)
+ *np = '\0';
+ if (*p != '\0') {
+ sla->suffix = (uint64_t)strtou(p, NULL,
+ 0, 0, UINT64_MAX, &e);
+ if (e) {
+ logerrx("%s: failed to "
+ "convert suffix",
+ ifname);
+ goto err_sla;
+ }
+ }
+ }
+ /* Sanity check */
+ for (sl = 0; sl < ia->sla_len - 1; sl++) {
+ slap = &ia->sla[sl];
+ if (slap->sla_set != sla->sla_set) {
+ logerrx("%s: cannot mix automatic "
+ "and fixed SLA",
+ sla->ifname);
+ goto err_sla;
+ }
+ if (ia->prefix_len &&
+ (sla->prefix_len == ia->prefix_len ||
+ slap->prefix_len == ia->prefix_len))
+ {
+ logerrx("%s: cannot delegte the same"
+ "prefix length more than once",
+ sla->ifname);
+ goto err_sla;
+ }
+ if (!sla->sla_set &&
+ strcmp(slap->ifname, sla->ifname) == 0)
+ {
+ logwarnx("%s: cannot specify the "
+ "same interface twice with "
+ "an automatic SLA",
+ sla->ifname);
+ goto err_sla;
+ }
+ if (slap->sla_set && sla->sla_set &&
+ slap->sla == sla->sla)
+ {
+ logerrx("%s: cannot"
+ " assign the same SLA %u"
+ " more than once",
+ sla->ifname, sla->sla);
+ 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
+#endif
+ case O_HOSTNAME_SHORT:
+ ifo->options |= DHCPCD_HOSTNAME | DHCPCD_HOSTNAME_SHORT;
+ break;
+ case O_DEV:
+ ARG_REQUIRED;
+#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 {
+ logerrx("embed must be after a define "
+ "or encap");
+ return -1;
+ }
+ }
+ /* FALLTHROUGH */
+ case O_ENCAP:
+ ARG_REQUIRED;
+ if (dop == NULL) {
+ if (*ldop == NULL) {
+ logerrx("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) {
+ logerrx("invalid syntax: %s", arg);
+ return -1;
+ }
+ *fp++ = '\0';
+ u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logerrx("invalid code: %s", arg);
+ return -1;
+ }
+ arg = strskipwhite(fp);
+ if (arg == NULL) {
+ logerrx("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) {
+ logerrx("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 |= OT_REQUEST;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logerrx("incomplete request type");
+ return -1;
+ }
+ *fp++ = '\0';
+ } else if (strcasecmp(arg, "norequest") == 0) {
+ t |= OT_NOREQ;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logerrx("incomplete request type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "optional") == 0) {
+ t |= OT_OPTIONAL;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logerrx("incomplete optional type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "index") == 0) {
+ t |= OT_INDEX;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logerrx("incomplete index type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "array") == 0) {
+ t |= OT_ARRAY;
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logerrx("incomplete array type");
+ return -1;
+ }
+ *fp++ = '\0';
+ }
+ if (strcasecmp(arg, "ipaddress") == 0)
+ t |= OT_ADDRIPV4;
+ else if (strcasecmp(arg, "ip6address") == 0)
+ t |= OT_ADDRIPV6;
+ else if (strcasecmp(arg, "string") == 0)
+ t |= OT_STRING;
+ else if (strcasecmp(arg, "byte") == 0)
+ t |= OT_UINT8;
+ else if (strcasecmp(arg, "bitflags") == 0)
+ t |= OT_BITFLAG;
+ else if (strcasecmp(arg, "uint8") == 0)
+ t |= OT_UINT8;
+ else if (strcasecmp(arg, "int8") == 0)
+ t |= OT_INT8;
+ else if (strcasecmp(arg, "uint16") == 0)
+ t |= OT_UINT16;
+ else if (strcasecmp(arg, "int16") == 0)
+ t |= OT_INT16;
+ else if (strcasecmp(arg, "uint32") == 0)
+ t |= OT_UINT32;
+ else if (strcasecmp(arg, "int32") == 0)
+ t |= OT_INT32;
+ else if (strcasecmp(arg, "flag") == 0)
+ t |= OT_FLAG;
+ else if (strcasecmp(arg, "raw") == 0)
+ t |= OT_STRING | OT_RAW;
+ else if (strcasecmp(arg, "ascii") == 0)
+ t |= OT_STRING | OT_ASCII;
+ else if (strcasecmp(arg, "domain") == 0)
+ t |= OT_STRING | OT_DOMAIN | OT_RFC1035;
+ else if (strcasecmp(arg, "dname") == 0)
+ t |= OT_STRING | OT_DOMAIN;
+ else if (strcasecmp(arg, "binhex") == 0)
+ t |= OT_STRING | OT_BINHEX;
+ else if (strcasecmp(arg, "embed") == 0)
+ t |= OT_EMBED;
+ else if (strcasecmp(arg, "encap") == 0)
+ t |= OT_ENCAP;
+ else if (strcasecmp(arg, "rfc3361") ==0)
+ t |= OT_STRING | OT_RFC3361;
+ else if (strcasecmp(arg, "rfc3442") ==0)
+ t |= OT_STRING | OT_RFC3442;
+ else if (strcasecmp(arg, "option") == 0)
+ t |= OT_OPTION;
+ else {
+ logerrx("unknown type: %s", arg);
+ return -1;
+ }
+ if (l && !(t & (OT_STRING | OT_BINHEX))) {
+ logwarnx("ignoring length for type: %s", arg);
+ l = 0;
+ }
+ if (t & OT_ARRAY && t & (OT_STRING | OT_BINHEX) &&
+ !(t & (OT_RFC1035 | OT_DOMAIN)))
+ {
+ logwarnx("ignoring array for strings");
+ t &= ~OT_ARRAY;
+ }
+ if (t & OT_BITFLAG) {
+ if (bp == NULL)
+ logwarnx("missing bitflag assignment");
+ }
+ /* variable */
+ if (!fp) {
+ if (!(t & OT_OPTION)) {
+ logerrx("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) {
+ logerr(__func__);
+ return -1;
+ }
+ } else {
+ np = NULL;
+ t |= OT_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) {
+ ndop = reallocarray(*dop, *dop_len + 1, sizeof(**dop));
+ if (ndop == NULL) {
+ logerr(__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:
+ ARG_REQUIRED;
+ fp = strwhite(arg);
+ if (fp)
+ *fp++ = '\0';
+ u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+ if (e) {
+ logerrx("invalid code: %s", arg);
+ return -1;
+ }
+ fp = strskipwhite(fp);
+ if (fp) {
+ s = parse_string(NULL, 0, fp);
+ if (s == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ dl = (size_t)s;
+ if (dl + (sizeof(uint16_t) * 2) > UINT16_MAX) {
+ logerrx("vendor class is too big");
+ return -1;
+ }
+ np = malloc(dl);
+ if (np == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ parse_string(np, dl, fp);
+ } else {
+ dl = 0;
+ np = NULL;
+ }
+ vivco = reallocarray(ifo->vivco,
+ ifo->vivco_len + 1, sizeof(*ifo->vivco));
+ if (vivco == NULL) {
+ logerr( __func__);
+ free(np);
+ 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:
+ ARG_REQUIRED;
+#ifdef AUTH
+ 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 {
+ logerrx("%s: unsupported protocol", arg);
+ return -1;
+ }
+ arg = strskipwhite(fp);
+ fp = strwhite(arg);
+ if (arg == NULL) {
+ ifo->auth.options |= DHCPCD_AUTH_SEND;
+ if (ifo->auth.protocol == AUTH_PROTO_TOKEN)
+ ifo->auth.protocol = AUTH_ALG_NONE;
+ else
+ ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+ ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+ break;
+ }
+ if (fp)
+ *fp++ = '\0';
+ if (ifo->auth.protocol == AUTH_PROTO_TOKEN) {
+ np = strchr(arg, '/');
+ if (np) {
+ if (fp == NULL || np < fp)
+ *np++ = '\0';
+ else
+ np = NULL;
+ }
+ if (parse_uint32(&ifo->auth.token_snd_secretid,
+ arg) == -1)
+ logerrx("%s: not a number", arg);
+ else
+ ifo->auth.token_rcv_secretid =
+ ifo->auth.token_snd_secretid;
+ if (np &&
+ parse_uint32(&ifo->auth.token_rcv_secretid,
+ np) == -1)
+ logerrx("%s: not a number", arg);
+ } else {
+ if (strcasecmp(arg, "hmacmd5") == 0 ||
+ strcasecmp(arg, "hmac-md5") == 0)
+ ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+ else {
+ logerrx("%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 {
+ logerrx("%s: unsupported RDM", arg);
+ return -1;
+ }
+ ifo->auth.options |= DHCPCD_AUTH_SEND;
+ break;
+#else
+ logerrx("no authentication support");
+ return -1;
+#endif
+ case O_AUTHTOKEN:
+ ARG_REQUIRED;
+#ifdef AUTH
+ fp = strwhite(arg);
+ if (fp == NULL) {
+ logerrx("authtoken requires a realm");
+ return -1;
+ }
+ *fp++ = '\0';
+ token = calloc(1, sizeof(*token));
+ if (token == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ if (parse_uint32(&token->secretid, arg) == -1) {
+ logerrx("%s: not a number", arg);
+ goto invalid_token;
+ }
+ arg = fp;
+ fp = strend(arg);
+ if (fp == NULL) {
+ logerrx("authtoken requies an a key");
+ goto invalid_token;
+ }
+ *fp++ = '\0';
+ s = parse_string(NULL, 0, arg);
+ if (s == -1) {
+ logerr("realm_len");
+ goto invalid_token;
+ }
+ if (s != 0) {
+ token->realm_len = (size_t)s;
+ token->realm = malloc(token->realm_len);
+ if (token->realm == NULL) {
+ logerr(__func__);
+ goto invalid_token;
+ }
+ parse_string((char *)token->realm, token->realm_len,
+ arg);
+ }
+ arg = fp;
+ fp = strend(arg);
+ if (fp == NULL) {
+ logerrx("authtoken requies an expiry date");
+ goto invalid_token;
+ }
+ *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) {
+ logerrx("%s: invalid date time", arg);
+ goto invalid_token;
+ }
+ if ((token->expire = mktime(&tm)) == (time_t)-1) {
+ logerr("%s: mktime", __func__);
+ goto invalid_token;
+ }
+ }
+ arg = fp;
+ s = parse_string(NULL, 0, arg);
+ if (s == -1 || s == 0) {
+ if (s == -1)
+ logerr("token_len");
+ else
+ logerrx("authtoken needs a key");
+ goto invalid_token;
+ }
+ token->key_len = (size_t)s;
+ token->key = malloc(token->key_len);
+ if (token->key == NULL) {
+ logerr(__func__);
+ goto invalid_token;
+ }
+ parse_string((char *)token->key, token->key_len, arg);
+ TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next);
+ break;
+
+invalid_token:
+ free(token->realm);
+ free(token);
+#else
+ logerrx("no authentication support");
+#endif
+ return -1;
+ case O_AUTHNOTREQUIRED:
+ ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
+ break;
+ case O_DHCP:
+ ifo->options |= DHCPCD_DHCP | DHCPCD_WANTDHCP | 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:
+ ARG_REQUIRED;
+#ifdef PRIVSEP
+ /* Control group is already set by this point.
+ * We don't need to pledge getpw either with this. */
+ if (IN_PRIVSEP(ctx))
+ break;
+#endif
+#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) {
+ logerr(__func__);
+ return -1;
+ }
+ while ((i = getgrnam_r(arg, &grpbuf, p, dl, &grp)) ==
+ ERANGE)
+ {
+ size_t nl = dl * 2;
+ if (nl < dl) {
+ logerrx("control_group: out of buffer");
+ free(p);
+ return -1;
+ }
+ dl = nl;
+ np = realloc(p, dl);
+ if (np == NULL) {
+ logerr(__func__);
+ free(p);
+ return -1;
+ }
+ p = np;
+ }
+ if (i != 0) {
+ errno = i;
+ logerr("getgrnam_r");
+ free(p);
+ return -1;
+ }
+ if (grp == NULL) {
+ if (!ctx->control_group)
+ logerrx("controlgroup: %s: not found", arg);
+ free(p);
+ return -1;
+ }
+ ctx->control_group = grp->gr_gid;
+ free(p);
+#else
+ grp = getgrnam(arg);
+ if (grp == NULL) {
+ if (!ctx->control_group)
+ logerrx("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:
+ ARG_REQUIRED;
+ np = strwhite(arg);
+ if (np != NULL) {
+ *np++ = '\0';
+ np = strskipwhite(np);
+ }
+ if (strcmp(arg, "private") == 0 ||
+ strcmp(arg, "stableprivate") == 0 ||
+ strcmp(arg, "stable") == 0)
+ ifo->options |= DHCPCD_SLAACPRIVATE;
+ else
+ ifo->options &= ~DHCPCD_SLAACPRIVATE;
+ if (np != NULL &&
+ (strcmp(np, "temp") == 0 || strcmp(np, "temporary") == 0))
+ ifo->options |= DHCPCD_SLAACTEMP;
+ break;
+ case O_BOOTP:
+ ifo->options |= DHCPCD_BOOTP;
+ break;
+ case O_NODELAY:
+ ifo->options &= ~DHCPCD_INITIAL_DELAY;
+ break;
+ case O_LASTLEASE_EXTEND:
+ ifo->options |= DHCPCD_LASTLEASE | DHCPCD_LASTLEASE_EXTEND;
+ break;
+ case O_INACTIVE:
+ ifo->options |= DHCPCD_INACTIVE;
+ break;
+ case O_MUDURL:
+ ARG_REQUIRED;
+ s = parse_string((char *)ifo->mudurl + 1, MUDURL_MAX_LEN, arg);
+ if (s == -1) {
+ logerr("mudurl");
+ return -1;
+ }
+ *ifo->mudurl = (uint8_t)s;
+ break;
+ case O_LINK_RCVBUF:
+#ifndef SMALL
+ ARG_REQUIRED;
+ ctx->link_rcvbuf = (int)strtoi(arg, NULL, 0, 0, INT32_MAX, &e);
+ if (e) {
+ logerrx("failed to convert link_rcvbuf %s", arg);
+ return -1;
+ }
+#endif
+ break;
+ case O_CONFIGURE:
+ ifo->options |= DHCPCD_CONFIGURE;
+ break;
+ case O_NOCONFIGURE:
+ ifo->options &= ~DHCPCD_CONFIGURE;
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+
+#ifdef ARG_REQUIRED
+arg_required:
+ logerrx("option %d requires an argument", opt);
+ return -1;
+#undef ARG_REQUIRED
+#endif
+}
+
+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) {
+ logerrx("option requires an argument -- %s", opt);
+ return -1;
+ }
+
+ return parse_option(ctx, ifname, ifo, cf_options[i].val, line,
+ ldop, edop);
+ }
+
+ if (!(ctx->options & DHCPCD_PRINT_PIDFILE))
+ logerrx("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;
+ }
+
+ if (!(ifo->options & DHCPCD_ARP) ||
+ ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))
+ ifo->options &= ~DHCPCD_IPV4LL;
+
+ 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_IPV6RS))
+ ifo->options &=
+ ~(DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS);
+}
+
+struct if_options *
+default_config(struct dhcpcd_ctx *ctx)
+{
+ struct if_options *ifo;
+
+ /* Seed our default options */
+ if ((ifo = calloc(1, sizeof(*ifo))) == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ ifo->options |= DHCPCD_IF_UP | DHCPCD_LINK | DHCPCD_INITIAL_DELAY;
+ ifo->timeout = DEFAULT_TIMEOUT;
+ ifo->reboot = DEFAULT_REBOOT;
+ ifo->metric = -1;
+ ifo->auth.options |= DHCPCD_AUTH_REQUIRE;
+ rb_tree_init(&ifo->routes, &rt_compare_list_ops);
+#ifdef AUTH
+ TAILQ_INIT(&ifo->auth.tokens);
+#endif
+
+ /* Inherit some global defaults */
+ if (ctx->options & DHCPCD_CONFIGURE)
+ ifo->options |= DHCPCD_CONFIGURE;
+ if (ctx->options & DHCPCD_PERSISTENT)
+ ifo->options |= DHCPCD_PERSISTENT;
+ if (ctx->options & DHCPCD_SLAACPRIVATE)
+ ifo->options |= DHCPCD_SLAACPRIVATE;
+
+ return ifo;
+}
+
+struct if_options *
+read_config(struct dhcpcd_ctx *ctx,
+ const char *ifname, const char *ssid, const char *profile)
+{
+ struct if_options *ifo;
+ char buf[UDPLEN_MAX], *bp; /* 64k max config file size */
+ char *line, *option, *p;
+ ssize_t buflen;
+ size_t vlen;
+ int skip, have_profile, new_block, had_block;
+#if !defined(INET) || !defined(INET6)
+ size_t i;
+ struct dhcp_opt *opt;
+#endif
+ struct dhcp_opt *ldop, *edop;
+
+ /* Seed our default options */
+ if ((ifo = default_config(ctx)) == NULL)
+ return NULL;
+ if (default_options == 0) {
+ default_options |= DHCPCD_CONFIGURE | DHCPCD_DAEMONISE |
+ DHCPCD_GATEWAY;
+#ifdef INET
+ skip = socket(PF_INET, SOCK_DGRAM, 0);
+ if (skip != -1) {
+ close(skip);
+ default_options |= DHCPCD_IPV4 | DHCPCD_ARP |
+ DHCPCD_DHCP | DHCPCD_IPV4LL;
+ }
+#endif
+#ifdef INET6
+ skip = socket(PF_INET6, SOCK_DGRAM, 0);
+ if (skip != -1) {
+ close(skip);
+ default_options |= DHCPCD_IPV6 | DHCPCD_IPV6RS |
+ DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS |
+ DHCPCD_DHCP6;
+ }
+#endif
+#ifdef PLUGIN_DEV
+ default_options |= DHCPCD_DEV;
+#endif
+ }
+ ifo->options |= default_options;
+
+ CLEAR_CONFIG_BLOCK(ifo);
+
+ vlen = strlcpy((char *)ifo->vendorclassid + 1, ctx->vendor,
+ sizeof(ifo->vendorclassid) - 1);
+ ifo->vendorclassid[0] = (uint8_t)(vlen > 255 ? 0 : vlen);
+
+ /* Reset route order */
+ ctx->rt_order = 0;
+
+ /* Parse our embedded options file */
+ if (ifname == NULL && !(ctx->options & DHCPCD_PRINT_PIDFILE)) {
+ /* Space for initial estimates */
+#if defined(INET) && defined(INITDEFINES)
+ ifo->dhcp_override =
+ calloc(INITDEFINES, sizeof(*ifo->dhcp_override));
+ if (ifo->dhcp_override == NULL)
+ logerr(__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)
+ logerr(__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)
+ logerr(__func__);
+ else
+ ifo->dhcp6_override_len = INITDEFINE6S;
+#endif
+
+ /* Now load our embedded config */
+#ifdef EMBEDDED_CONFIG
+ buflen = dhcp_readfile(ctx, EMBEDDED_CONFIG, buf, sizeof(buf));
+ if (buflen == -1) {
+ logerr("%s: %s", __func__, EMBEDDED_CONFIG);
+ return ifo;
+ }
+ if (buf[buflen - 1] != '\0') {
+ if ((size_t)buflen < sizeof(buf) - 1)
+ buflen++;
+ buf[buflen - 1] = '\0';
+ }
+#else
+ buflen = (ssize_t)strlcpy(buf, dhcpcd_embedded_conf,
+ sizeof(buf));
+ if ((size_t)buflen >= sizeof(buf)) {
+ logerrx("%s: embedded config too big", __func__);
+ return ifo;
+ }
+ /* Our embedded config is NULL terminated */
+#endif
+ bp = buf;
+ while ((line = get_line(&bp, &buflen)) != NULL) {
+ option = strsep(&line, " \t");
+ if (line)
+ line = strskipwhite(line);
+ /* Trim trailing whitespace */
+ if (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 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;
+#ifdef DHCP6
+ ctx->dhcp6_opts = ifo->dhcp6_override;
+ ctx->dhcp6_opts_len = ifo->dhcp6_override_len;
+#endif
+#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 */
+ buflen = dhcp_readfile(ctx, ctx->cffile, buf, sizeof(buf));
+ if (buflen == -1) {
+ /* dhcpcd can continue without it, but no DNS options
+ * would be requested ... */
+ logerr("%s: %s", __func__, ctx->cffile);
+ return ifo;
+ }
+ if (buf[buflen - 1] != '\0') {
+ if ((size_t)buflen < sizeof(buf) - 1)
+ buflen++;
+ buf[buflen - 1] = '\0';
+ }
+ dhcp_filemtime(ctx, ctx->cffile, &ifo->mtime);
+
+ ldop = edop = NULL;
+ skip = have_profile = new_block = 0;
+ had_block = ifname == NULL ? 1 : 0;
+ bp = buf;
+ while ((line = get_line(&bp, &buflen)) != NULL) {
+ option = strsep(&line, " \t");
+ if (line)
+ line = strskipwhite(line);
+ /* Trim trailing whitespace */
+ if (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;
+ SET_CONFIG_BLOCK(ifo);
+ }
+
+ /* Start of an interface block, skip if not ours */
+ if (strcmp(option, "interface") == 0) {
+ char **n;
+
+ new_block = 1;
+ if (line == NULL) {
+ /* No interface given */
+ skip = 1;
+ continue;
+ }
+ if (ifname && strcmp(line, ifname) == 0)
+ skip = 0;
+ else
+ skip = 1;
+ if (ifname)
+ continue;
+
+ n = reallocarray(ctx->ifcv,
+ (size_t)ctx->ifcc + 1, sizeof(char *));
+ if (n == NULL) {
+ logerr(__func__);
+ continue;
+ }
+ ctx->ifcv = n;
+ ctx->ifcv[ctx->ifcc] = strdup(line);
+ if (ctx->ifcv[ctx->ifcc] == NULL) {
+ logerr(__func__);
+ continue;
+ }
+ ctx->ifcc++;
+ 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);
+ }
+
+ if (profile && !have_profile) {
+ free_options(ctx, ifo);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (!had_block)
+ ifo->options &= ~DHCPCD_WAITOPTS;
+ CLEAR_CONFIG_BLOCK(ifo);
+ 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,
+ ctx->options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : 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 dhcpcd_ctx *ctx, struct if_options *ifo)
+{
+ size_t i;
+#ifdef RT_FREE_ROUTE_TABLE
+ struct interface *ifp;
+ struct rt *rt;
+#endif
+ struct dhcp_opt *opt;
+ struct vivco *vo;
+#ifdef AUTH
+ struct token *token;
+#endif
+
+ if (ifo == NULL)
+ return;
+
+ 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);
+ }
+
+#ifdef RT_FREE_ROUTE_TABLE
+ /* Stupidly, we don't know the interface when creating the options.
+ * As such, make sure each route has one so they can goto the
+ * free list. */
+ ifp = ctx->ifaces != NULL ? TAILQ_FIRST(ctx->ifaces) : NULL;
+ if (ifp != NULL) {
+ RB_TREE_FOREACH(rt, &ifo->routes) {
+ if (rt->rt_ifp == NULL)
+ rt->rt_ifp = ifp;
+ }
+ }
+#endif
+ rt_headclear0(ctx, &ifo->routes, AF_UNSPEC);
+
+ 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);
+
+#if defined(INET6) && !defined(SMALL)
+ for (; ifo->ia_len > 0; ifo->ia_len--)
+ free(ifo->ia[ifo->ia_len - 1].sla);
+#endif
+ free(ifo->ia);
+
+#ifdef AUTH
+ 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);
+ }
+#endif
+ free(ifo);
+}
diff --git a/src/if-options.h b/src/if-options.h
new file mode 100644
index 000000000000..f80119d6c15f
--- /dev/null
+++ b/src/if-options.h
@@ -0,0 +1,293 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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"
+#include "route.h"
+
+/* Don't set any optional arguments here so we retain POSIX
+ * compatibility with getopt */
+#define IF_OPTS "146bc:de:f:gh:i:j:kl:m:no:pqr:s:t:u:v:wxy:z:" \
+ "ABC:DEF:GHI:JKLMNO:PQ:S:TUVW:X:Z:"
+#define NOERR_IF_OPTS ":" IF_OPTS
+
+#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 MUDURL_MAX_LEN 255
+
+#define DHCPCD_ARP (1ULL << 0)
+#define DHCPCD_RELEASE (1ULL << 1)
+#define DHCPCD_RTBUILD (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_MANAGER (1ULL << 17)
+#define DHCPCD_HOSTNAME (1ULL << 18)
+#define DHCPCD_CLIENTID (1ULL << 19)
+#define DHCPCD_LINK (1ULL << 20)
+#define DHCPCD_ANONYMOUS (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_PRIVSEP (1ULL << 33)
+#define DHCPCD_CONFIGURE (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_LAUNCHER (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)
+#define DHCPCD_INFORM6 (1ULL << 52)
+#define DHCPCD_WANTDHCP (1ULL << 53)
+#define DHCPCD_IPV6RA_AUTOCONF (1ULL << 54)
+#define DHCPCD_ROUTER_HOST_ROUTE_WARNED (1ULL << 55)
+#define DHCPCD_LASTLEASE_EXTEND (1ULL << 56)
+#define DHCPCD_BOOTP (1ULL << 57)
+#define DHCPCD_INITIAL_DELAY (1ULL << 58)
+#define DHCPCD_PRINT_PIDFILE (1ULL << 59)
+#define DHCPCD_ONESHOT (1ULL << 60)
+#define DHCPCD_INACTIVE (1ULL << 61)
+#define DHCPCD_SLAACTEMP (1ULL << 62)
+#define DHCPCD_PRIVSEPROOT (1ULL << 63)
+
+#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)
+
+/* 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_LINK_RCVBUF O_BASE + 7
+#define O_ANONYMOUS 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_BOOTP O_BASE + 42
+#define O_DEFINEND O_BASE + 43
+#define O_NODELAY O_BASE + 44
+#define O_INFORM6 O_BASE + 45
+#define O_LASTLEASE_EXTEND O_BASE + 46
+#define O_INACTIVE O_BASE + 47
+#define O_MUDURL O_BASE + 48
+#define O_MSUSERCLASS O_BASE + 49
+#define O_CONFIGURE O_BASE + 50
+#define O_NOCONFIGURE O_BASE + 51
+#define O_RANDOMISE_HWADDR O_BASE + 52
+
+extern const struct option cf_options[];
+
+struct if_sla {
+ char ifname[IF_NAMESIZE];
+ uint32_t sla;
+ uint8_t prefix_len;
+ uint64_t suffix;
+ bool 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;
+#ifndef SMALL
+ uint32_t sla_max;
+ size_t sla_len;
+ struct if_sla *sla;
+#endif
+#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;
+ uint32_t timeout;
+ uint32_t reboot;
+ unsigned long long options;
+ bool randomise_hwaddr;
+
+ struct in_addr req_addr;
+ struct in_addr req_mask;
+ struct in_addr req_brd;
+ rb_tree_t routes;
+ struct in6_addr req_addr6;
+ uint8_t req_prefix_len;
+ unsigned int mtu;
+ char **config;
+
+ char **environ;
+
+ 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];
+ uint8_t mudurl[MUDURL_MAX_LEN + 2];
+
+ size_t blacklist_len;
+ in_addr_t *blacklist;
+ size_t whitelist_len;
+ in_addr_t *whitelist;
+ ssize_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 *default_config(struct dhcpcd_ctx *);
+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 dhcpcd_ctx *, struct if_options *);
+
+#endif
diff --git a/src/if-sun.c b/src/if-sun.c
new file mode 100644
index 000000000000..d25bbc817a29
--- /dev/null
+++ b/src/if-sun.c
@@ -0,0 +1,1761 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Solaris interface driver for dhcpcd
+ * Copyright (c) 2016-2021 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <libdlpi.h>
+#include <kstat.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stropts.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <inet/ip.h>
+
+#include <net/if_dl.h>
+#include <net/if_types.h>
+
+#include <netinet/if_ether.h>
+#include <netinet/udp.h>
+
+#include <sys/ioctl.h>
+#include <sys/mac.h>
+#include <sys/pfmod.h>
+#include <sys/tihdr.h>
+#include <sys/utsname.h>
+
+/* Private libsocket interface we can hook into to get
+ * a better getifaddrs(3).
+ * From libsocket_priv.h, which is not always distributed so is here. */
+extern int getallifaddrs(sa_family_t, struct ifaddrs **, int64_t);
+
+#include "config.h"
+#include "bpf.h"
+#include "common.h"
+#include "dhcp.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "route.h"
+#include "sa.h"
+
+#ifndef ARP_MOD_NAME
+# define ARP_MOD_NAME "arp"
+#endif
+
+#ifndef RT_ROUNDUP
+#define RT_ROUNDUP(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(int32_t) - 1))) : sizeof(int32_t))
+#define RT_ADVANCE(x, n) ((x) += RT_ROUNDUP(sa_len((n))))
+#endif
+
+#define COPYOUT(sin, sa) do { \
+ if ((sa) && ((sa)->sa_family == AF_INET)) \
+ (sin) = ((const struct sockaddr_in *)(const void *) \
+ (sa))->sin_addr; \
+ } while (0)
+
+#define COPYOUT6(sin, sa) do { \
+ if ((sa) && ((sa)->sa_family == AF_INET6)) \
+ (sin) = ((const struct sockaddr_in6 *)(const void *) \
+ (sa))->sin6_addr; \
+ } while (0)
+
+#define COPYSA(dst, src) memcpy((dst), (src), sa_len((src)))
+
+struct priv {
+#ifdef INET6
+ int pf_inet6_fd;
+#endif
+};
+
+struct rtm
+{
+ struct rt_msghdr hdr;
+ char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX];
+};
+
+static int if_plumb(int, const struct dhcpcd_ctx *, int, const char *);
+
+int
+os_init(void)
+{
+ return 0;
+}
+
+int
+if_init(struct interface *ifp)
+{
+
+#ifdef INET
+ if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET, ifp->name) == -1 &&
+ errno != EEXIST)
+ return -1;
+#endif
+
+#ifdef INET6
+ if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET6, ifp->name) == -1 &&
+ errno != EEXIST)
+ return -1;
+#endif
+
+ if (ifp->index == 0)
+ ifp->index = if_nametoindex(ifp->name);
+
+ return 0;
+}
+
+int
+if_conf(__unused struct interface *ifp)
+{
+
+ return 0;
+}
+
+int
+if_opensockets_os(struct dhcpcd_ctx *ctx)
+{
+ struct priv *priv;
+ int n;
+
+ if ((priv = malloc(sizeof(*priv))) == NULL)
+ return -1;
+ ctx->priv = priv;
+
+#ifdef INET6
+ priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ /* Don't return an error so we at least work on kernels witout INET6
+ * even though we expect INET6 support.
+ * We will fail noisily elsewhere anyway. */
+#endif
+
+ ctx->link_fd = socket(PF_ROUTE,
+ SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+
+ if (ctx->link_fd == -1) {
+ free(ctx->priv);
+ return -1;
+ }
+
+ /* Ignore our own route(4) messages.
+ * Sadly there is no way of doing this for route(4) messages
+ * generated from addresses we add/delete. */
+ n = 0;
+ if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK,
+ &n, sizeof(n)) == -1)
+ logerr("%s: SO_USELOOPBACK", __func__);
+
+ return 0;
+}
+
+void
+if_closesockets_os(struct dhcpcd_ctx *ctx)
+{
+#ifdef INET6
+ struct priv *priv;
+
+ priv = (struct priv *)ctx->priv;
+ if (priv->pf_inet6_fd != -1)
+ close(priv->pf_inet6_fd);
+#endif
+
+ /* each interface should have closed itself */
+ free(ctx->priv);
+}
+
+int
+if_setmac(struct interface *ifp, void *mac, uint8_t maclen)
+{
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+int
+if_carrier(struct interface *ifp, __unused const void *ifadata)
+{
+ kstat_ctl_t *kcp;
+ kstat_t *ksp;
+ kstat_named_t *knp;
+ link_state_t linkstate;
+
+ if (if_getflags(ifp) == -1)
+ return LINK_UNKNOWN;
+
+ kcp = kstat_open();
+ if (kcp == NULL)
+ goto err;
+ ksp = kstat_lookup(kcp, UNCONST("link"), 0, ifp->name);
+ if (ksp == NULL)
+ goto err;
+ if (kstat_read(kcp, ksp, NULL) == -1)
+ goto err;
+ knp = kstat_data_lookup(ksp, UNCONST("link_state"));
+ if (knp == NULL)
+ goto err;
+ if (knp->data_type != KSTAT_DATA_UINT32)
+ goto err;
+ linkstate = (link_state_t)knp->value.ui32;
+ kstat_close(kcp);
+
+ switch (linkstate) {
+ case LINK_STATE_UP:
+ ifp->flags |= IFF_UP;
+ return LINK_UP;
+ case LINK_STATE_DOWN:
+ return LINK_DOWN;
+ default:
+ return LINK_UNKNOWN;
+ }
+
+err:
+ if (kcp != NULL)
+ kstat_close(kcp);
+ return LINK_UNKNOWN;
+}
+
+bool
+if_roaming(__unused struct interface *ifp)
+{
+
+ return false;
+}
+
+int
+if_mtu_os(const struct interface *ifp)
+{
+ dlpi_handle_t dh;
+ dlpi_info_t dlinfo;
+ int mtu;
+
+ if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
+ return -1;
+ if (dlpi_info(dh, &dlinfo, 0) == DLPI_SUCCESS)
+ mtu = dlinfo.di_max_sdu;
+ else
+ mtu = -1;
+ dlpi_close(dh);
+ return mtu;
+}
+
+int
+if_getssid(__unused struct interface *ifp)
+{
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+/* XXX work out TAP interfaces? */
+bool
+if_ignore(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
+{
+
+ return false;
+}
+
+unsigned short
+if_vlanid(__unused const struct interface *ifp)
+{
+
+ return 0;
+}
+
+int
+if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
+{
+
+ return 0;
+}
+
+int
+if_machinearch(__unused char *str, __unused size_t len)
+{
+
+ /* There is no extra data really.
+ * isainfo -v does return amd64, but also i386. */
+ return 0;
+}
+
+struct linkwalk {
+ struct ifaddrs *lw_ifa;
+ int lw_error;
+};
+
+static boolean_t
+if_newaddr(const char *ifname, void *arg)
+{
+ struct linkwalk *lw = arg;
+ int error;
+ struct ifaddrs *ifa;
+ dlpi_handle_t dh;
+ dlpi_info_t dlinfo;
+ uint8_t pa[DLPI_PHYSADDR_MAX];
+ size_t pa_len;
+ struct sockaddr_dl *sdl;
+
+ ifa = NULL;
+ error = dlpi_open(ifname, &dh, 0);
+ if (error == DLPI_ENOLINK) /* Just vanished or in global zone */
+ return B_FALSE;
+ if (error != DLPI_SUCCESS)
+ goto failed1;
+ if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS)
+ goto failed;
+
+ /* For some reason, dlpi_info won't return the
+ * physical address, it's all zero's.
+ * So cal dlpi_get_physaddr. */
+ pa_len = DLPI_PHYSADDR_MAX;
+ if (dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR,
+ pa, &pa_len) != DLPI_SUCCESS)
+ goto failed;
+
+ if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
+ goto failed;
+ if ((ifa->ifa_name = strdup(ifname)) == NULL)
+ goto failed;
+ if ((sdl = calloc(1, sizeof(*sdl))) == NULL)
+ goto failed;
+
+ ifa->ifa_addr = (struct sockaddr *)sdl;
+ sdl->sdl_index = if_nametoindex(ifname);
+ sdl->sdl_family = AF_LINK;
+ switch (dlinfo.di_mactype) {
+ case DL_ETHER:
+ sdl->sdl_type = IFT_ETHER;
+ break;
+ case DL_IB:
+ sdl->sdl_type = IFT_IB;
+ break;
+ default:
+ sdl->sdl_type = IFT_OTHER;
+ break;
+ }
+
+ sdl->sdl_alen = pa_len;
+ memcpy(sdl->sdl_data, pa, pa_len);
+
+ ifa->ifa_next = lw->lw_ifa;
+ lw->lw_ifa = ifa;
+ dlpi_close(dh);
+ return B_FALSE;
+
+failed:
+ dlpi_close(dh);
+ if (ifa != NULL) {
+ free(ifa->ifa_name);
+ free(ifa->ifa_addr);
+ free(ifa);
+ }
+failed1:
+ lw->lw_error = errno;
+ return B_TRUE;
+}
+
+/* Creates an empty sockaddr_dl for lo0. */
+static struct ifaddrs *
+if_ifa_lo0(void)
+{
+ struct ifaddrs *ifa;
+ struct sockaddr_dl *sdl;
+
+ if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
+ return NULL;
+ if ((sdl = calloc(1, sizeof(*sdl))) == NULL) {
+ free(ifa);
+ return NULL;
+ }
+ if ((ifa->ifa_name = strdup("lo0")) == NULL) {
+ free(ifa);
+ free(sdl);
+ return NULL;
+ }
+
+ ifa->ifa_addr = (struct sockaddr *)sdl;
+ ifa->ifa_flags = IFF_LOOPBACK;
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_index = if_nametoindex("lo0");
+
+ return ifa;
+}
+
+/* getifaddrs(3) does not support AF_LINK, strips aliases and won't
+ * report addresses that are not UP.
+ * As such it's just totally useless, so we need to roll our own. */
+int
+if_getifaddrs(struct ifaddrs **ifap)
+{
+ struct linkwalk lw;
+ struct ifaddrs *ifa;
+
+ /* Private libc function which we should not have to call
+ * to get non UP addresses. */
+ if (getallifaddrs(AF_UNSPEC, &lw.lw_ifa, 0) == -1)
+ return -1;
+
+ /* Start with some AF_LINK addresses. */
+ lw.lw_error = 0;
+ dlpi_walk(if_newaddr, &lw, 0);
+ if (lw.lw_error != 0) {
+ freeifaddrs(lw.lw_ifa);
+ errno = lw.lw_error;
+ return -1;
+ }
+
+ /* lo0 doesn't appear in dlpi_walk, so fudge it. */
+ if ((ifa = if_ifa_lo0()) == NULL) {
+ freeifaddrs(lw.lw_ifa);
+ return -1;
+ }
+ ifa->ifa_next = lw.lw_ifa;
+
+ *ifap = ifa;
+ return 0;
+}
+
+static void
+if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp)
+{
+
+ memset(sdl, 0, sizeof(*sdl));
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0;
+ sdl->sdl_index = (unsigned short)ifp->index;
+}
+
+static int
+get_addrs(int type, const void *data, size_t data_len,
+ const struct sockaddr **sa)
+{
+ const char *cp, *ep;
+ int i;
+
+ cp = data;
+ ep = cp + data_len;
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (type & (1 << i)) {
+ if (cp >= ep) {
+ errno = EINVAL;
+ return -1;
+ }
+ sa[i] = (const struct sockaddr *)cp;
+ RT_ADVANCE(cp, sa[i]);
+ } else
+ sa[i] = NULL;
+ }
+
+ return 0;
+}
+
+static struct interface *
+if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl)
+{
+
+ if (sdl->sdl_index)
+ return if_findindex(ctx->ifaces, sdl->sdl_index);
+
+ 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);
+ }
+ if (sdl->sdl_alen) {
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (ifp->hwlen == sdl->sdl_alen &&
+ memcmp(ifp->hwaddr,
+ sdl->sdl_data, sdl->sdl_alen) == 0)
+ return ifp;
+ }
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+static struct interface *
+if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa)
+{
+ if (sa == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ switch (sa->sa_family) {
+ case AF_LINK:
+ {
+ const struct sockaddr_dl *sdl;
+
+ sdl = (const void *)sa;
+ return if_findsdl(ctx, sdl);
+ }
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+ struct ipv4_addr *ia;
+
+ sin = (const void *)sa;
+ if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr)))
+ return ia->iface;
+ if ((ia = ipv4_findmaskbrd(ctx, &sin->sin_addr)))
+ return ia->iface;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin;
+ struct ipv6_addr *ia;
+
+ sin = (const void *)sa;
+ if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr)))
+ return ia->iface;
+ break;
+ }
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+static void
+if_route0(struct dhcpcd_ctx *ctx, struct rtm *rtmsg,
+ unsigned char cmd, const struct rt *rt)
+{
+ struct rt_msghdr *rtm;
+ char *bp = rtmsg->buffer;
+ socklen_t sl;
+ bool gateway_unspec;
+
+ /* WARNING: Solaris will not allow you to delete RTF_KERNEL routes.
+ * This includes subnet/prefix routes. */
+
+#define ADDSA(sa) do { \
+ sl = sa_len((sa)); \
+ memcpy(bp, (sa), sl); \
+ bp += RT_ROUNDUP(sl); \
+ } while (/* CONSTCOND */ 0)
+
+ memset(rtmsg, 0, sizeof(*rtmsg));
+ rtm = &rtmsg->hdr;
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_type = cmd;
+ rtm->rtm_seq = ++ctx->seq;
+ rtm->rtm_flags = rt->rt_flags;
+ rtm->rtm_addrs = RTA_DST | RTA_GATEWAY;
+
+ gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
+
+ if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
+ bool netmask_bcast = sa_is_allones(&rt->rt_netmask);
+
+ rtm->rtm_flags |= RTF_UP;
+ if (!(rtm->rtm_flags & RTF_REJECT) &&
+ !sa_is_loopback(&rt->rt_gateway))
+ {
+ rtm->rtm_addrs |= RTA_IFP;
+ /* RTA_IFA is currently ignored by the kernel.
+ * RTA_SRC and RTF_SETSRC look like what we want,
+ * but they don't work with RTF_GATEWAY.
+ * We set RTA_IFA just in the hope that the
+ * kernel will one day support this. */
+ if (!sa_is_unspecified(&rt->rt_ifa))
+ rtm->rtm_addrs |= RTA_IFA;
+ }
+
+ if (netmask_bcast)
+ rtm->rtm_flags |= RTF_HOST;
+ else if (!gateway_unspec)
+ rtm->rtm_flags |= RTF_GATEWAY;
+
+ if (rt->rt_dflags & RTDF_STATIC)
+ rtm->rtm_flags |= RTF_STATIC;
+
+ if (rt->rt_mtu != 0) {
+ rtm->rtm_inits |= RTV_MTU;
+ rtm->rtm_rmx.rmx_mtu = rt->rt_mtu;
+ }
+ }
+
+ if (!(rtm->rtm_flags & RTF_HOST))
+ rtm->rtm_addrs |= RTA_NETMASK;
+
+ ADDSA(&rt->rt_dest);
+
+ if (gateway_unspec)
+ ADDSA(&rt->rt_ifa);
+ else
+ ADDSA(&rt->rt_gateway);
+
+ if (rtm->rtm_addrs & RTA_NETMASK)
+ ADDSA(&rt->rt_netmask);
+
+ if (rtm->rtm_addrs & RTA_IFP) {
+ struct sockaddr_dl sdl;
+
+ if_linkaddr(&sdl, rt->rt_ifp);
+ ADDSA((struct sockaddr *)&sdl);
+ }
+
+ if (rtm->rtm_addrs & RTA_IFA)
+ ADDSA(&rt->rt_ifa);
+
+#if 0
+ if (rtm->rtm_addrs & RTA_SRC)
+ ADDSA(&rt->rt_ifa);
+#endif
+
+ rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm);
+}
+
+int
+if_route(unsigned char cmd, const struct rt *rt)
+{
+ struct rtm rtm;
+ struct dhcpcd_ctx *ctx = rt->rt_ifp->ctx;
+
+ if_route0(ctx, &rtm, cmd, rt);
+
+ if (write(ctx->link_fd, &rtm, rtm.hdr.rtm_msglen) == -1)
+ return -1;
+ return 0;
+}
+
+static int
+if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm)
+{
+ const struct sockaddr *rti_info[RTAX_MAX];
+
+ if (~rtm->rtm_addrs & RTA_DST) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm),
+ rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1)
+ return -1;
+
+ memset(rt, 0, sizeof(*rt));
+
+ rt->rt_flags = (unsigned int)rtm->rtm_flags;
+ COPYSA(&rt->rt_dest, rti_info[RTAX_DST]);
+ if (rtm->rtm_addrs & RTA_NETMASK)
+ COPYSA(&rt->rt_netmask, rti_info[RTAX_NETMASK]);
+
+ /* dhcpcd likes an unspecified gateway to indicate via the link.
+ * However we need to know if gateway was a link with an address. */
+ if (rtm->rtm_addrs & RTA_GATEWAY) {
+ if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) {
+ const struct sockaddr_dl *sdl;
+
+ sdl = (const struct sockaddr_dl*)
+ (const void *)rti_info[RTAX_GATEWAY];
+ if (sdl->sdl_alen != 0)
+ rt->rt_dflags |= RTDF_GATELINK;
+ } else if (rtm->rtm_flags & RTF_GATEWAY)
+ COPYSA(&rt->rt_gateway, rti_info[RTAX_GATEWAY]);
+ }
+
+ if (rtm->rtm_addrs & RTA_SRC)
+ COPYSA(&rt->rt_ifa, rti_info[RTAX_SRC]);
+ rt->rt_mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;
+
+ if (rtm->rtm_index)
+ rt->rt_ifp = if_findindex(ctx->ifaces, rtm->rtm_index);
+ else if (rtm->rtm_addrs & RTA_IFP)
+ rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_IFP]);
+ else if (rtm->rtm_addrs & RTA_GATEWAY)
+ rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_GATEWAY]);
+ else
+ rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_DST]);
+
+ if (rt->rt_ifp == NULL && rtm->rtm_type == RTM_MISS)
+ rt->rt_ifp = if_loopback(ctx);
+
+ if (rt->rt_ifp == NULL) {
+ errno = ESRCH;
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct rt *
+if_route_get(struct dhcpcd_ctx *ctx, struct rt *rt)
+{
+ struct rtm rtm;
+ int s;
+ struct iovec iov = { .iov_base = &rtm, .iov_len = sizeof(rtm) };
+ struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
+ ssize_t len;
+ struct rt *rtw = rt;
+
+ if_route0(ctx, &rtm, RTM_GET, rt);
+ rt = NULL;
+ s = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC, 0);
+ if (s == -1)
+ return NULL;
+ if (write(s, &rtm, rtm.hdr.rtm_msglen) == -1)
+ goto out;
+ if ((len = recvmsg(s, &msg, 0)) == -1)
+ goto out;
+ if ((size_t)len < sizeof(rtm.hdr) || len < rtm.hdr.rtm_msglen) {
+ errno = EINVAL;
+ goto out;
+ }
+ if (if_copyrt(ctx, rtw, &rtm.hdr) == -1)
+ goto out;
+ rt = rtw;
+
+out:
+ close(s);
+ return rt;
+}
+
+static int
+if_finishrt(struct dhcpcd_ctx *ctx, struct rt *rt)
+{
+ int mtu;
+
+ /* Solaris has a subnet route with the gateway
+ * of the owning address.
+ * dhcpcd has a blank gateway here to indicate a
+ * subnet route. */
+ if (!sa_is_unspecified(&rt->rt_dest) &&
+ !sa_is_unspecified(&rt->rt_gateway))
+ {
+ switch(rt->rt_gateway.sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct in_addr *in;
+
+ in = &satosin(&rt->rt_gateway)->sin_addr;
+ if (ipv4_findaddr(ctx, in))
+ in->s_addr = INADDR_ANY;
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct in6_addr *in6;
+
+ in6 = &satosin6(&rt->rt_gateway)->sin6_addr;
+ if (ipv6_findaddr(ctx, in6, 0))
+ *in6 = in6addr_any;
+ break;
+ }
+#endif
+ }
+ }
+
+ /* Solaris doesn't set interfaces for some routes.
+ * This sucks, so we need to call RTM_GET to
+ * work out the interface. */
+ if (rt->rt_ifp == NULL) {
+ if (if_route_get(ctx, rt) == NULL) {
+ rt->rt_ifp = if_loopback(ctx);
+ if (rt->rt_ifp == NULL)
+ return - 1;
+ }
+ }
+
+ /* Solaris likes to set route MTU to match
+ * interface MTU when adding routes.
+ * This confuses dhcpcd as it expects MTU to be 0
+ * when no explicit MTU has been set. */
+ mtu = if_getmtu(rt->rt_ifp);
+ if (mtu == -1)
+ return -1;
+ if (rt->rt_mtu == (unsigned int)mtu)
+ rt->rt_mtu = 0;
+
+ return 0;
+}
+
+static int
+if_addrflags0(int fd, int af, const char *ifname)
+{
+ struct lifreq lifr;
+ int flags;
+
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
+ return -1;
+
+ flags = 0;
+ if (lifr.lifr_flags & IFF_DUPLICATE)
+ flags |= af == AF_INET6 ? IN6_IFF_DUPLICATED:IN_IFF_DUPLICATED;
+ else if (!(lifr.lifr_flags & IFF_UP))
+ flags |= af == AF_INET6 ? IN6_IFF_TENTATIVE:IN_IFF_TENTATIVE;
+ return flags;
+}
+
+static int
+if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
+{
+ const struct sockaddr *sa;
+ struct rt rt;
+
+ if (rtm->rtm_msglen < sizeof(*rtm) + sizeof(*sa)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (if_copyrt(ctx, &rt, rtm) == -1 && errno != ESRCH)
+ return -1;
+
+#ifdef INET6
+ /*
+ * BSD announces host routes.
+ * As such, we should be notified of reachability by its
+ * existance with a hardware address.
+ * Ensure we don't call this for a newly incomplete state.
+ */
+ if (rt.rt_dest.sa_family == AF_INET6 &&
+ (rt.rt_flags & RTF_HOST || rtm->rtm_type == RTM_MISS) &&
+ !(rtm->rtm_type == RTM_ADD && !(rt.rt_dflags & RTDF_GATELINK)))
+ {
+ bool reachable;
+
+ reachable = (rtm->rtm_type == RTM_ADD ||
+ rtm->rtm_type == RTM_CHANGE) &&
+ rt.rt_dflags & RTDF_GATELINK;
+ ipv6nd_neighbour(ctx, &rt.rt_ss_dest.sin6.sin6_addr, reachable);
+ }
+#endif
+
+ if (if_finishrt(ctx, &rt) == -1)
+ return -1;
+ rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid);
+ return 0;
+}
+
+static bool
+if_getalias(struct interface *ifp, const struct sockaddr *sa, char *alias)
+{
+ struct ifaddrs *ifaddrs, *ifa;
+ struct interface *ifpx;
+ bool found;
+
+ ifaddrs = NULL;
+ if (getallifaddrs(sa->sa_family, &ifaddrs, 0) == -1)
+ return false;
+ found = false;
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
+ if (sa_cmp(sa, ifa->ifa_addr) != 0)
+ continue;
+ /* Check it's for the right interace. */
+ ifpx = if_find(ifp->ctx->ifaces, ifa->ifa_name);
+ if (ifp == ifpx) {
+ strlcpy(alias, ifa->ifa_name, IF_NAMESIZE);
+ found = true;
+ break;
+ }
+ }
+ freeifaddrs(ifaddrs);
+ return found;
+}
+
+static int
+if_getbrdaddr(struct dhcpcd_ctx *ctx, const char *ifname, struct in_addr *brd)
+{
+ struct lifreq lifr = { 0 };
+ int r;
+
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ errno = 0;
+ r = ioctl(ctx->pf_inet_fd, SIOCGLIFBRDADDR, &lifr, sizeof(lifr));
+ if (r != -1)
+ COPYOUT(*brd, (struct sockaddr *)&lifr.lifr_broadaddr);
+ return r;
+}
+
+static int
+if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam)
+{
+ struct interface *ifp;
+ const struct sockaddr *sa, *rti_info[RTAX_MAX];
+ int flags;
+ char ifalias[IF_NAMESIZE];
+
+ if (ifam->ifam_msglen < sizeof(*ifam)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (~ifam->ifam_addrs & RTA_IFA)
+ return 0;
+
+ if (get_addrs(ifam->ifam_addrs, (const char *)ifam + sizeof(*ifam),
+ ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1)
+ return -1;
+ sa = rti_info[RTAX_IFA];
+
+ /* XXX We have no way of knowing who generated these
+ * messages wich truely sucks because we want to
+ * avoid listening to our own delete messages. */
+ if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL)
+ return 0;
+
+ /*
+ * ifa_msghdr does not supply the alias, just the interface index.
+ * This is very bad, because it means we have to call getifaddrs
+ * and trawl the list of addresses to find the added address.
+ * To make life worse, you can have the same address on the same
+ * interface with different aliases.
+ * So this hack is not entirely accurate.
+ *
+ * IF anyone is going to fix Solaris, plesse consider adding the
+ * following fields to extend ifa_msghdr:
+ * ifam_alias
+ * ifam_pid
+ */
+ if (ifam->ifam_type != RTM_DELADDR && !if_getalias(ifp, sa, ifalias))
+ return 0;
+
+ switch (sa->sa_family) {
+ case AF_LINK:
+ {
+ struct sockaddr_dl sdl;
+
+ if (ifam->ifam_type != RTM_CHGADDR &&
+ ifam->ifam_type != RTM_NEWADDR)
+ break;
+ memcpy(&sdl, rti_info[RTAX_IFA], sizeof(sdl));
+ dhcpcd_handlehwaddr(ifp, ifp->hwtype,
+ CLLADDR(&sdl), sdl.sdl_alen);
+ break;
+ }
+#ifdef INET
+ case AF_INET:
+ {
+ struct in_addr addr, mask, bcast;
+
+ COPYOUT(addr, rti_info[RTAX_IFA]);
+ COPYOUT(mask, rti_info[RTAX_NETMASK]);
+ COPYOUT(bcast, rti_info[RTAX_BRD]);
+
+ if (ifam->ifam_type == RTM_DELADDR) {
+ struct ipv4_addr *ia;
+
+ ia = ipv4_iffindaddr(ifp, &addr, &mask);
+ if (ia == NULL)
+ return 0;
+ strlcpy(ifalias, ia->alias, sizeof(ifalias));
+ } else if (bcast.s_addr == INADDR_ANY) {
+ /* Work around a bug where broadcast
+ * address is not correctly reported. */
+ if (if_getbrdaddr(ctx, ifalias, &bcast) == -1)
+ return 0;
+ }
+ flags = if_addrflags(ifp, NULL, ifalias);
+ if (ifam->ifam_type == RTM_DELADDR) {
+ if (flags != -1)
+ return 0;
+ } else if (flags == -1)
+ return 0;
+
+ ipv4_handleifa(ctx,
+ ifam->ifam_type == RTM_CHGADDR ?
+ RTM_NEWADDR : ifam->ifam_type,
+ NULL, ifalias, &addr, &mask, &bcast, flags, 0);
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct in6_addr addr6, mask6;
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = (const void *)rti_info[RTAX_IFA];
+ addr6 = sin6->sin6_addr;
+ sin6 = (const void *)rti_info[RTAX_NETMASK];
+ mask6 = sin6->sin6_addr;
+
+ if (ifam->ifam_type == RTM_DELADDR) {
+ struct ipv6_addr *ia;
+
+ ia = ipv6_iffindaddr(ifp, &addr6, 0);
+ if (ia == NULL)
+ return 0;
+ strlcpy(ifalias, ia->alias, sizeof(ifalias));
+ }
+ flags = if_addrflags6(ifp, NULL, ifalias);
+ if (ifam->ifam_type == RTM_DELADDR) {
+ if (flags != -1)
+ return 0;
+ } else if (flags == -1)
+ return 0;
+
+ ipv6_handleifa(ctx,
+ ifam->ifam_type == RTM_CHGADDR ?
+ RTM_NEWADDR : ifam->ifam_type,
+ NULL, ifalias, &addr6, ipv6_prefixlen(&mask6), flags, 0);
+ break;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+static int
+if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm)
+{
+ struct interface *ifp;
+ int state;
+ unsigned int flags;
+
+ if (ifm->ifm_msglen < sizeof(*ifm)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL)
+ return 0;
+ flags = (unsigned int)ifm->ifm_flags;
+ if (ifm->ifm_flags & IFF_OFFLINE)
+ state = LINK_DOWN;
+ else {
+ state = LINK_UP;
+ flags |= IFF_UP;
+ }
+ dhcpcd_handlecarrier(ifp, state, flags);
+ return 0;
+}
+
+static int
+if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
+{
+
+ if (rtm->rtm_version != RTM_VERSION)
+ return 0;
+
+ switch(rtm->rtm_type) {
+ case RTM_IFINFO:
+ return if_ifinfo(ctx, (const void *)rtm);
+ case RTM_ADD: /* FALLTHROUGH */
+ case RTM_CHANGE: /* FALLTHROUGH */
+ case RTM_DELETE: /* FALLTHROUGH */
+ case RTM_MISS:
+ return if_rtm(ctx, (const void *)rtm);
+ case RTM_CHGADDR: /* FALLTHROUGH */
+ case RTM_DELADDR: /* FALLTHROUGH */
+ case RTM_NEWADDR:
+ return if_ifa(ctx, (const void *)rtm);
+ }
+
+ return 0;
+}
+
+int
+if_handlelink(struct dhcpcd_ctx *ctx)
+{
+ struct rtm rtm;
+ ssize_t len;
+
+ len = read(ctx->link_fd, &rtm, sizeof(rtm));
+ if (len == -1)
+ return -1;
+ if (len == 0)
+ return 0;
+ if ((size_t)len < sizeof(rtm.hdr.rtm_msglen) ||
+ len != rtm.hdr.rtm_msglen)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ /*
+ * Coverity thinks that the data could be tainted from here.
+ * I have no idea how because the length of the data we read
+ * is guarded by len and checked to match rtm_msglen.
+ * The issue seems to be related to extracting the addresses
+ * at the end of the header, but seems to have no issues with the
+ * equivalent call in if_initrt.
+ */
+ /* coverity[tainted_data] */
+ return if_dispatch(ctx, &rtm.hdr);
+}
+
+static void
+if_octetstr(char *buf, const Octet_t *o, ssize_t len)
+{
+ int i;
+ char *p;
+
+ p = buf;
+ for (i = 0; i < o->o_length; i++) {
+ if ((p + 1) - buf < len)
+ *p++ = o->o_bytes[i];
+ else
+ break;
+ }
+ *p = '\0';
+}
+
+static int
+if_setflags(int fd, const char *ifname, uint64_t flags)
+{
+ struct lifreq lifr = { .lifr_addrlen = 0 };
+
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
+ return -1;
+ if ((lifr.lifr_flags & flags) != flags) {
+ lifr.lifr_flags |= flags;
+ if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+if_addaddr(int fd, const char *ifname,
+ struct sockaddr_storage *addr, struct sockaddr_storage *mask,
+ struct sockaddr_storage *brd, uint8_t plen)
+{
+ struct lifreq lifr = { .lifr_addrlen = plen };
+
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+
+ /* First assign the netmask. */
+ lifr.lifr_addr = *mask;
+ if (addr == NULL) {
+ lifr.lifr_addrlen = plen;
+ if (ioctl(fd, SIOCSLIFSUBNET, &lifr) == -1)
+ return -1;
+ goto up;
+ } else {
+ if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1)
+ return -1;
+ }
+
+ /* Then assign the address. */
+ lifr.lifr_addr = *addr;
+ if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1)
+ return -1;
+
+ /* Then assign the broadcast address. */
+ if (brd != NULL) {
+ lifr.lifr_broadaddr = *brd;
+ if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1)
+ return -1;
+ }
+
+up:
+ return if_setflags(fd, ifname, IFF_UP);
+}
+
+static int
+if_getaf_fd(const struct dhcpcd_ctx *ctx, int af)
+{
+
+ if (af == AF_INET)
+ return ctx->pf_inet_fd;
+ if (af == AF_INET6) {
+ struct priv *priv;
+
+ priv = (struct priv *)ctx->priv;
+ return priv->pf_inet6_fd;
+ }
+
+ errno = EAFNOSUPPORT;
+ return -1;
+}
+
+int
+if_getsubnet(struct dhcpcd_ctx *ctx, const char *ifname, int af,
+ void *subnet, size_t subnet_len)
+{
+ struct lifreq lifr = { .lifr_addrlen = 0 };
+ int fd;
+
+ fd = if_getaf_fd(ctx, af);
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ if (ioctl(fd, SIOCGLIFSUBNET, &lifr) == -1)
+ return -1;
+ memcpy(subnet, &lifr.lifr_addr, MIN(subnet_len,sizeof(lifr.lifr_addr)));
+ return 0;
+}
+
+static int
+if_plumblif(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ struct lifreq lifr;
+ int s;
+
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ lifr.lifr_addr.ss_family = af;
+ s = if_getaf_fd(ctx, af);
+ return ioctl(s,
+ cmd == RTM_NEWADDR ? SIOCLIFADDIF : SIOCLIFREMOVEIF,
+ &lifr) == -1 && errno != EEXIST ? -1 : 0;
+}
+
+static int
+if_plumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ dlpi_handle_t dh, dh_arp = NULL;
+ int fd, af_fd, mux_fd, arp_fd = -1, mux_id, retval;
+ uint64_t flags;
+ struct lifreq lifr;
+ const char *udp_dev;
+ struct strioctl ioc;
+ struct if_spec spec;
+
+ if (if_nametospec(ifname, &spec) == -1)
+ return -1;
+
+ af_fd = if_getaf_fd(ctx, af);
+
+ switch (af) {
+ case AF_INET:
+ flags = IFF_IPV4;
+ udp_dev = UDP_DEV_NAME;
+ break;
+ case AF_INET6:
+ /* We will take care of setting the link local address. */
+ flags = IFF_IPV6 | IFF_NOLINKLOCAL;
+ udp_dev = UDP6_DEV_NAME;
+ break;
+ default:
+ errno = EPROTONOSUPPORT;
+ return -1;
+ }
+
+ if (dlpi_open(ifname, &dh, DLPI_NOATTACH) != DLPI_SUCCESS) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd = dlpi_fd(dh);
+ retval = -1;
+ mux_fd = -1;
+ if (ioctl(fd, I_PUSH, IP_MOD_NAME) == -1)
+ goto out;
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ lifr.lifr_ppa = spec.ppa;
+ lifr.lifr_flags = flags;
+ if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1)
+ goto out;
+
+ /* Get full flags. */
+ if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1)
+ goto out;
+ flags = lifr.lifr_flags;
+
+ /* Open UDP as a multiplexor to PLINK the interface stream.
+ * UDP is used because STREAMS will not let you PLINK a driver
+ * under itself and IP is generally at the bottom of the stream. */
+ if ((mux_fd = open(udp_dev, O_RDWR)) == -1)
+ goto out;
+ /* POP off all undesired modules. */
+ while (ioctl(mux_fd, I_POP, 0) != -1)
+ ;
+ if (errno != EINVAL)
+ goto out;
+ if(ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1)
+ goto out;
+
+ if (flags & (IFF_NOARP | IFF_IPV6)) {
+ /* PLINK the interface stream so it persists. */
+ if (ioctl(mux_fd, I_PLINK, fd) == -1)
+ goto out;
+ goto done;
+ }
+
+ if (dlpi_open(ifname, &dh_arp, DLPI_NOATTACH) != DLPI_SUCCESS)
+ goto out;
+ arp_fd = dlpi_fd(dh_arp);
+ if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1)
+ goto out;
+
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ lifr.lifr_ppa = spec.ppa;
+ lifr.lifr_flags = flags;
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.ic_cmd = SIOCSLIFNAME;
+ ioc.ic_dp = (char *)&lifr;
+ ioc.ic_len = sizeof(lifr);
+ if (ioctl(arp_fd, I_STR, &ioc) == -1)
+ goto out;
+
+ /* PLINK the interface stream so it persists. */
+ mux_id = ioctl(mux_fd, I_PLINK, fd);
+ if (mux_id == -1)
+ goto out;
+ if (ioctl(mux_fd, I_PLINK, arp_fd) == -1) {
+ ioctl(mux_fd, I_PUNLINK, mux_id);
+ goto out;
+ }
+
+done:
+ retval = 0;
+
+out:
+ dlpi_close(dh);
+ if (dh_arp != NULL)
+ dlpi_close(dh_arp);
+ if (mux_fd != -1)
+ close(mux_fd);
+ return retval;
+}
+
+static int
+if_unplumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ struct sockaddr_storage addr = { .ss_family = af };
+ int fd;
+
+ /* For the time being, don't unplumb the interface, just
+ * set the address to zero. */
+ fd = if_getaf_fd(ctx, af);
+ return if_addaddr(fd, ifname, &addr, &addr,
+ af == AF_INET ? &addr : NULL, 0);
+}
+
+static int
+if_plumb(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ struct if_spec spec;
+
+ if (if_nametospec(ifname, &spec) == -1)
+ return -1;
+ if (spec.lun != -1)
+ return if_plumblif(cmd, ctx, af, ifname);
+ if (cmd == RTM_NEWADDR)
+ return if_plumbif(ctx, af, ifname);
+ else
+ return if_unplumbif(ctx, af, ifname);
+}
+
+#ifdef INET
+static int
+if_walkrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len)
+{
+ mib2_ipRouteEntry_t *re, *e;
+ struct rt rt, *rtn;
+ char ifname[IF_NAMESIZE];
+ struct in_addr in;
+
+ if (len % sizeof(*re) != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ re = (mib2_ipRouteEntry_t *)data;
+ e = (mib2_ipRouteEntry_t *)(data + len);
+ do {
+ /* Skip route types we don't want. */
+ switch (re->ipRouteInfo.re_ire_type) {
+ case IRE_IF_CLONE:
+ case IRE_BROADCAST:
+ case IRE_MULTICAST:
+ case IRE_NOROUTE:
+ case IRE_LOCAL:
+ continue;
+ default:
+ break;
+ }
+
+ memset(&rt, 0, sizeof(rt));
+ in.s_addr = re->ipRouteDest;
+ sa_in_init(&rt.rt_dest, &in);
+ in.s_addr = re->ipRouteMask;
+ sa_in_init(&rt.rt_netmask, &in);
+ in.s_addr = re->ipRouteNextHop;
+ sa_in_init(&rt.rt_gateway, &in);
+ rt.rt_flags = re->ipRouteInfo.re_flags;
+ in.s_addr = re->ipRouteInfo.re_src_addr;
+ sa_in_init(&rt.rt_ifa, &in);
+ rt.rt_mtu = re->ipRouteInfo.re_max_frag;
+ if_octetstr(ifname, &re->ipRouteIfIndex, sizeof(ifname));
+ rt.rt_ifp = if_find(ctx->ifaces, ifname);
+ if (if_finishrt(ctx, &rt) == -1) {
+ logerr(__func__);
+ continue;
+ }
+ if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
+ logerr(__func__);
+ break;
+ }
+ memcpy(rtn, &rt, sizeof(*rtn));
+ if (rb_tree_insert_node(routes, rtn) != rtn)
+ rt_free(rtn);
+ } while (++re < e);
+ return 0;
+}
+#endif
+
+#ifdef INET6
+static int
+if_walkrt6(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len)
+{
+ mib2_ipv6RouteEntry_t *re, *e;
+ struct rt rt, *rtn;
+ char ifname[IF_NAMESIZE];
+ struct in6_addr in6;
+
+ if (len % sizeof(*re) != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ re = (mib2_ipv6RouteEntry_t *)data;
+ e = (mib2_ipv6RouteEntry_t *)(data + len);
+
+ do {
+ /* Skip route types we don't want. */
+ switch (re->ipv6RouteInfo.re_ire_type) {
+ case IRE_IF_CLONE:
+ case IRE_BROADCAST:
+ case IRE_MULTICAST:
+ case IRE_NOROUTE:
+ case IRE_LOCAL:
+ continue;
+ default:
+ break;
+ }
+
+ memset(&rt, 0, sizeof(rt));
+ sa_in6_init(&rt.rt_dest, &re->ipv6RouteDest);
+ ipv6_mask(&in6, re->ipv6RoutePfxLength);
+ sa_in6_init(&rt.rt_netmask, &in6);
+ sa_in6_init(&rt.rt_gateway, &re->ipv6RouteNextHop);
+ sa_in6_init(&rt.rt_ifa, &re->ipv6RouteInfo.re_src_addr);
+ rt.rt_mtu = re->ipv6RouteInfo.re_max_frag;
+ if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname));
+ rt.rt_ifp = if_find(ctx->ifaces, ifname);
+ if (if_finishrt(ctx, &rt) == -1) {
+ logerr(__func__);
+ continue;
+ }
+ if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
+ logerr(__func__);
+ break;
+ }
+ memcpy(rtn, &rt, sizeof(*rtn));
+ if (rb_tree_insert_node(routes, rtn) != rtn)
+ rt_free(rtn);
+ } while (++re < e);
+ return 0;
+}
+#endif
+
+static int
+if_parsert(struct dhcpcd_ctx *ctx, rb_tree_t *routes,
+ unsigned int level, unsigned int name,
+ int (*walkrt)(struct dhcpcd_ctx *, rb_tree_t *, char *, size_t))
+{
+ int s, retval, code, flags;
+ uintptr_t buf[512 / sizeof(uintptr_t)];
+ struct strbuf ctlbuf, databuf;
+ struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf;
+ struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf;
+ struct T_error_ack *tea = (struct T_error_ack *)buf;
+ struct opthdr *req;
+
+ if ((s = open("/dev/arp", O_RDWR)) == -1)
+ return -1;
+
+ /* Assume we are erroring. */
+ retval = -1;
+
+ tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
+ tor->OPT_offset = sizeof (struct T_optmgmt_req);
+ tor->OPT_length = sizeof (struct opthdr);
+ tor->MGMT_flags = T_CURRENT;
+
+ req = (struct opthdr *)&tor[1];
+ req->level = EXPER_IP_AND_ALL_IRES;
+ req->name = 0;
+ req->len = 1;
+
+ ctlbuf.buf = (char *)buf;
+ ctlbuf.len = tor->OPT_length + tor->OPT_offset;
+ if (putmsg(s, &ctlbuf, NULL, 0) == 1)
+ goto out;
+
+ req = (struct opthdr *)&toa[1];
+ ctlbuf.maxlen = sizeof(buf);
+
+ /* Create a reasonable buffer to start with */
+ databuf.maxlen = BUFSIZ * 2;
+ if ((databuf.buf = malloc(databuf.maxlen)) == NULL)
+ goto out;
+
+ for (;;) {
+ flags = 0;
+ if ((code = getmsg(s, &ctlbuf, 0, &flags)) == -1)
+ break;
+ if (code == 0 &&
+ toa->PRIM_type == T_OPTMGMT_ACK &&
+ toa->MGMT_flags == T_SUCCESS &&
+ (size_t)ctlbuf.len >= sizeof(struct T_optmgmt_ack))
+ {
+ /* End of messages, so return success! */
+ retval = 0;
+ break;
+ }
+ if (tea->PRIM_type == T_ERROR_ACK) {
+ errno = tea->TLI_error == TSYSERR ?
+ tea->UNIX_error : EPROTO;
+ break;
+ }
+ if (code != MOREDATA ||
+ toa->PRIM_type != T_OPTMGMT_ACK ||
+ toa->MGMT_flags != T_SUCCESS)
+ {
+ errno = ENOMSG;
+ break;
+ }
+
+ /* Try to ensure out buffer is big enough
+ * for future messages as well. */
+ if ((size_t)databuf.maxlen < req->len) {
+ size_t newlen;
+
+ free(databuf.buf);
+ newlen = roundup(req->len, BUFSIZ);
+ if ((databuf.buf = malloc(newlen)) == NULL)
+ break;
+ databuf.maxlen = newlen;
+ }
+
+ flags = 0;
+ if (getmsg(s, NULL, &databuf, &flags) == -1)
+ break;
+
+ /* We always have to get the data before moving onto
+ * the next item, so don't move this test higher up
+ * to avoid the buffer allocation and getmsg calls. */
+ if (req->level == level && req->name == name) {
+ if (walkrt(ctx, routes, databuf.buf, req->len) == -1)
+ break;
+ }
+ }
+
+ free(databuf.buf);
+out:
+ close(s);
+ return retval;
+}
+
+
+int
+if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, int af)
+{
+
+#ifdef INET
+ if ((af == AF_UNSPEC || af == AF_INET) &&
+ if_parsert(ctx, routes, MIB2_IP,MIB2_IP_ROUTE, if_walkrt) == -1)
+ return -1;
+#endif
+#ifdef INET6
+ if ((af == AF_UNSPEC || af == AF_INET6) &&
+ if_parsert(ctx, routes, MIB2_IP6, MIB2_IP6_ROUTE, if_walkrt6) == -1)
+ return -1;
+#endif
+ return 0;
+}
+
+
+#ifdef INET
+/* XXX We should fix this to write via the BPF interface. */
+ssize_t
+bpf_send(const struct bpf *bpf, uint16_t protocol, const void *data, size_t len)
+{
+ const struct interface *ifp = bpf->bpf_ifp;
+ dlpi_handle_t dh;
+ dlpi_info_t di;
+ int r;
+
+ if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
+ return -1;
+ if ((r = dlpi_info(dh, &di, 0)) == DLPI_SUCCESS &&
+ (r = dlpi_bind(dh, protocol, NULL)) == DLPI_SUCCESS)
+ r = dlpi_send(dh, di.di_bcastaddr, ifp->hwlen, data, len, NULL);
+ dlpi_close(dh);
+ return r == DLPI_SUCCESS ? (ssize_t)len : -1;
+}
+
+int
+if_address(unsigned char cmd, const struct ipv4_addr *ia)
+{
+ union {
+ struct sockaddr sa;
+ struct sockaddr_storage ss;
+ } addr, mask, brd;
+ int fd = ia->iface->ctx->pf_inet_fd;
+
+ /* Either remove the alias or ensure it exists. */
+ if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1 &&
+ errno != EEXIST)
+ return -1;
+
+ if (cmd == RTM_DELADDR)
+ return 0;
+
+ if (cmd != RTM_NEWADDR) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* We need to update the index now */
+ ia->iface->index = if_nametoindex(ia->alias);
+
+ sa_in_init(&addr.sa, &ia->addr);
+ sa_in_init(&mask.sa, &ia->mask);
+ sa_in_init(&brd.sa, &ia->brd);
+ return if_addaddr(fd, ia->alias, &addr.ss, &mask.ss, &brd.ss, 0);
+}
+
+int
+if_addrflags(const struct interface *ifp, __unused const struct in_addr * ia,
+ const char *alias)
+{
+
+ return if_addrflags0(ifp->ctx->pf_inet_fd, AF_INET, alias);
+}
+
+#endif
+
+#ifdef INET6
+int
+if_address6(unsigned char cmd, const struct ipv6_addr *ia)
+{
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_storage ss;
+ } addr, mask;
+ int fd, r;
+
+ /* Either remove the alias or ensure it exists. */
+ if (if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias) == -1 &&
+ errno != EEXIST)
+ return -1;
+
+ if (cmd == RTM_DELADDR)
+ return 0;
+
+ if (cmd != RTM_NEWADDR) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd = if_getaf_fd(ia->iface->ctx, AF_INET6);
+
+ if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) {
+ if (if_setflags(fd, ia->alias, IFF_NOLOCAL) ==-1)
+ return -1;
+ sa_in6_init(&mask.sa, &ia->prefix);
+ r = if_addaddr(fd, ia->alias,
+ NULL, &mask.ss, NULL, ia->prefix_len);
+ } else {
+ sa_in6_init(&addr.sa, &ia->addr);
+ mask.sin6.sin6_family = AF_INET6;
+ ipv6_mask(&mask.sin6.sin6_addr, ia->prefix_len);
+ r = if_addaddr(fd, ia->alias,
+ &addr.ss, &mask.ss, NULL, ia->prefix_len);
+ }
+ if (r == -1 && errno == EEXIST)
+ return 0;
+ return r;
+}
+
+int
+if_addrflags6(const struct interface *ifp, __unused const struct in6_addr *ia,
+ const char *alias)
+{
+ int fd;
+
+ fd = if_getaf_fd(ifp->ctx, AF_INET6);
+ return if_addrflags0(fd, AF_INET6, alias);
+}
+
+int
+if_getlifetime6(struct ipv6_addr *addr)
+{
+
+ UNUSED(addr);
+ errno = ENOTSUP;
+ return -1;
+}
+
+int
+if_applyra(const struct ra *rap)
+{
+ struct lifreq lifr = {
+ .lifr_ifinfo.lir_maxhops = rap->hoplimit,
+ .lifr_ifinfo.lir_reachtime = rap->reachable,
+ .lifr_ifinfo.lir_reachretrans = rap->retrans,
+ };
+
+ strlcpy(lifr.lifr_name, rap->iface->name, sizeof(lifr.lifr_name));
+ return ioctl(rap->iface->ctx->pf_inet_fd, SIOCSLIFLNKINFO, &lifr);
+}
+
+void
+if_setup_inet6(__unused const struct interface *ifp)
+{
+
+}
+
+int
+ip6_forwarding(__unused const char *ifname)
+{
+
+ return 1;
+}
+#endif
diff --git a/src/if.c b/src/if.c
new file mode 100644
index 000000000000..b00f5e6f384b
--- /dev/null
+++ b/src/if.c
@@ -0,0 +1,1042 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <fcntl.h> /* Needs to be here for old Linux */
+
+#include "config.h"
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#ifdef AF_LINK
+# include <net/if_dl.h>
+# include <net/if_types.h>
+# include <netinet/in_var.h>
+# undef AF_PACKET /* Newer Illumos defines this */
+#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 <inttypes.h>
+#include <fnmatch.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE ELOOP_IF
+#include "common.h"
+#include "eloop.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"
+#include "logerr.h"
+#include "privsep.h"
+
+void
+if_free(struct interface *ifp)
+{
+
+ if (ifp == NULL)
+ return;
+#ifdef IPV4LL
+ ipv4ll_free(ifp);
+#endif
+#ifdef INET
+ dhcp_free(ifp);
+ ipv4_free(ifp);
+#endif
+#ifdef DHCP6
+ dhcp6_free(ifp);
+#endif
+#ifdef INET6
+ ipv6nd_free(ifp);
+ ipv6_free(ifp);
+#endif
+ rt_freeif(ifp);
+ free_options(ifp->ctx, ifp->options);
+ free(ifp);
+}
+
+int
+if_opensockets(struct dhcpcd_ctx *ctx)
+{
+
+ if (if_opensockets_os(ctx) == -1)
+ return -1;
+
+#ifdef IFLR_ACTIVE
+ ctx->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (ctx->pf_link_fd == -1)
+ return -1;
+#ifdef HAVE_CAPSICUM
+ if (ps_rights_limit_ioctl(ctx->pf_link_fd) == -1)
+ return -1;
+#endif
+#endif
+
+ /* We use this socket for some operations without INET. */
+ ctx->pf_inet_fd = xsocket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (ctx->pf_inet_fd == -1)
+ return -1;
+
+ return 0;
+}
+
+void
+if_closesockets(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->pf_inet_fd != -1)
+ close(ctx->pf_inet_fd);
+#ifdef PF_LINK
+ if (ctx->pf_link_fd != -1)
+ close(ctx->pf_link_fd);
+#endif
+
+ if (ctx->priv) {
+ if_closesockets_os(ctx);
+ free(ctx->priv);
+ }
+}
+
+int
+if_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, size_t len)
+{
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP)
+ return (int)ps_root_ioctl(ctx, req, data, len);
+#endif
+ return ioctl(ctx->pf_inet_fd, req, data, len);
+}
+
+int
+if_getflags(struct interface *ifp)
+{
+ struct ifreq ifr = { .ifr_flags = 0 };
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1)
+ return -1;
+ ifp->flags = (unsigned int)ifr.ifr_flags;
+ return 0;
+}
+
+int
+if_setflag(struct interface *ifp, short setflag, short unsetflag)
+{
+ struct ifreq ifr = { .ifr_flags = 0 };
+ short oflags;
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1)
+ return -1;
+
+ oflags = ifr.ifr_flags;
+ ifr.ifr_flags |= setflag;
+ ifr.ifr_flags &= (short)~unsetflag;
+ if (ifr.ifr_flags != oflags &&
+ if_ioctl(ifp->ctx, SIOCSIFFLAGS, &ifr, sizeof(ifr)) == -1)
+ return -1;
+
+ /*
+ * Do NOT set ifp->flags here.
+ * We need to listen for flag updates from the kernel as they
+ * need to sync with carrier.
+ */
+ return 0;
+}
+
+bool
+if_is_link_up(const struct interface *ifp)
+{
+
+ return ifp->flags & IFF_UP &&
+ (ifp->carrier != LINK_DOWN ||
+ (ifp->options != NULL && !(ifp->options->options & DHCPCD_LINK)));
+}
+
+int
+if_randomisemac(struct interface *ifp)
+{
+ uint32_t randnum;
+ size_t hwlen = ifp->hwlen, rlen = 0;
+ uint8_t buf[HWADDR_LEN], *bp = buf, *rp = (uint8_t *)&randnum;
+ char sbuf[HWADDR_LEN * 3];
+ int retval;
+
+ if (hwlen == 0) {
+ errno = ENOTSUP;
+ return -1;
+ }
+ if (hwlen > sizeof(buf)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ for (; hwlen != 0; hwlen--) {
+ if (rlen == 0) {
+ randnum = arc4random();
+ rp = (uint8_t *)&randnum;
+ rlen = sizeof(randnum);
+ }
+ *bp++ = *rp++;
+ rlen--;
+ }
+
+ /* Unicast address and locally administered. */
+ buf[0] &= 0xFC;
+ buf[0] |= 0x02;
+
+ logdebugx("%s: hardware address randomised to %s",
+ ifp->name,
+ hwaddr_ntoa(buf, ifp->hwlen, sbuf, sizeof(sbuf)));
+ retval = if_setmac(ifp, buf, ifp->hwlen);
+ if (retval == 0)
+ memcpy(ifp->hwaddr, buf, ifp->hwlen);
+ return retval;
+}
+
+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;
+}
+
+void
+if_markaddrsstale(struct if_head *ifs)
+{
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ifs, next) {
+#ifdef INET
+ ipv4_markaddrsstale(ifp);
+#endif
+#ifdef INET6
+ ipv6_markaddrsstale(ifp, 0);
+#endif
+ }
+}
+
+void
+if_learnaddrs(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, *brd;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6, *net6;
+#endif
+ int addrflags;
+
+ for (ifa = *ifaddrs; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
+ if ((ifp = if_find(ifs, ifa->ifa_name)) == NULL)
+ continue;
+#ifdef HAVE_IFADDRS_ADDRFLAGS
+ addrflags = (int)ifa->ifa_addrflags;
+#endif
+ switch(ifa->ifa_addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ addr = (void *)ifa->ifa_addr;
+ net = (void *)ifa->ifa_netmask;
+ if (ifa->ifa_flags & IFF_POINTOPOINT)
+ brd = (void *)ifa->ifa_dstaddr;
+ else
+ brd = (void *)ifa->ifa_broadaddr;
+#ifndef HAVE_IFADDRS_ADDRFLAGS
+ addrflags = if_addrflags(ifp, &addr->sin_addr,
+ ifa->ifa_name);
+ if (addrflags == -1) {
+ if (errno != EEXIST && errno != EADDRNOTAVAIL) {
+ char dbuf[INET_ADDRSTRLEN];
+ const char *dbp;
+
+ dbp = inet_ntop(AF_INET, &addr->sin_addr,
+ dbuf, sizeof(dbuf));
+ logerr("%s: if_addrflags: %s%%%s",
+ __func__, dbp, ifp->name);
+ }
+ continue;
+ }
+#endif
+ ipv4_handleifa(ctx, RTM_NEWADDR, ifs, ifa->ifa_name,
+ &addr->sin_addr, &net->sin_addr,
+ brd ? &brd->sin_addr : NULL, addrflags, 0);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = (void *)ifa->ifa_addr;
+ net6 = (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
+#ifndef HAVE_IFADDRS_ADDRFLAGS
+ addrflags = if_addrflags6(ifp, &sin6->sin6_addr,
+ ifa->ifa_name);
+ if (addrflags == -1) {
+ if (errno != EEXIST && errno != EADDRNOTAVAIL) {
+ char dbuf[INET6_ADDRSTRLEN];
+ const char *dbp;
+
+ dbp = inet_ntop(AF_INET6, &sin6->sin6_addr,
+ dbuf, sizeof(dbuf));
+ logerr("%s: if_addrflags6: %s%%%s",
+ __func__, dbp, ifp->name);
+ }
+ continue;
+ }
+#endif
+ ipv6_handleifa(ctx, RTM_NEWADDR, ifs,
+ ifa->ifa_name, &sin6->sin6_addr,
+ ipv6_prefixlen(&net6->sin6_addr), addrflags, 0);
+ break;
+#endif
+ }
+ }
+
+#ifdef PRIVSEP_GETIFADDRS
+ if (IN_PRIVSEP(ctx))
+ free(*ifaddrs);
+ else
+#endif
+ freeifaddrs(*ifaddrs);
+ *ifaddrs = NULL;
+}
+
+void
+if_deletestaleaddrs(struct if_head *ifs)
+{
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ifs, next) {
+#ifdef INET
+ ipv4_deletestaleaddrs(ifp);
+#endif
+#ifdef INET6
+ ipv6_deletestaleaddrs(ifp);
+#endif
+ }
+}
+
+bool
+if_valid_hwaddr(const uint8_t *hwaddr, size_t hwlen)
+{
+ size_t i;
+ bool all_zeros, all_ones;
+
+ all_zeros = all_ones = true;
+ for (i = 0; i < hwlen; i++) {
+ if (hwaddr[i] != 0x00)
+ all_zeros = false;
+ if (hwaddr[i] != 0xff)
+ all_ones = false;
+ if (!all_zeros && !all_ones)
+ return true;
+ }
+ return false;
+}
+
+#if defined(AF_PACKET) && !defined(AF_LINK)
+static unsigned int
+if_check_arphrd(struct interface *ifp, unsigned int active, bool if_noconf)
+{
+
+ switch(ifp->hwtype) {
+ case ARPHRD_ETHER: /* FALLTHROUGH */
+ case ARPHRD_IEEE1394: /* FALLTHROUGH */
+ case ARPHRD_INFINIBAND: /* FALLTHROUGH */
+ case ARPHRD_NONE: /* FALLTHROUGH */
+ break;
+ case ARPHRD_LOOPBACK:
+ case ARPHRD_PPP:
+ if (if_noconf && active) {
+ logdebugx("%s: ignoring due to interface type and"
+ " no config",
+ ifp->name);
+ active = IF_INACTIVE;
+ }
+ break;
+ default:
+ if (active) {
+ int i;
+
+ if (if_noconf)
+ active = IF_INACTIVE;
+ i = active ? LOG_WARNING : LOG_DEBUG;
+ logmessage(i, "%s: unsupported"
+ " interface type 0x%.2x",
+ ifp->name, ifp->hwtype);
+ }
+ break;
+ }
+
+ return active;
+}
+#endif
+
+struct if_head *
+if_discover(struct dhcpcd_ctx *ctx, struct ifaddrs **ifaddrs,
+ int argc, char * const *argv)
+{
+ struct ifaddrs *ifa;
+ int i;
+ unsigned int active;
+ struct if_head *ifs;
+ struct interface *ifp;
+ struct if_spec spec;
+ bool if_noconf;
+#ifdef AF_LINK
+ const struct sockaddr_dl *sdl;
+#ifdef IFLR_ACTIVE
+ struct if_laddrreq iflr = { .flags = IFLR_PREFIX };
+#endif
+#elif defined(AF_PACKET)
+ const struct sockaddr_ll *sll;
+#endif
+#if defined(SIOCGIFPRIORITY)
+ struct ifreq ifr;
+#endif
+
+ if ((ifs = malloc(sizeof(*ifs))) == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ TAILQ_INIT(ifs);
+
+#ifdef PRIVSEP_GETIFADDRS
+ if (ctx->options & DHCPCD_PRIVSEP) {
+ if (ps_root_getifaddrs(ctx, ifaddrs) == -1) {
+ logerr("ps_root_getifaddrs");
+ free(ifs);
+ return NULL;
+ }
+ } else
+#endif
+ if (getifaddrs(ifaddrs) == -1) {
+ logerr("getifaddrs");
+ free(ifs);
+ return NULL;
+ }
+
+ 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 defined(AF_PACKET)
+ if (ifa->ifa_addr->sa_family != AF_PACKET)
+ continue;
+#endif
+ }
+ if (if_nametospec(ifa->ifa_name, &spec) != 0)
+ continue;
+
+ /* 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, spec.devname) == 0)
+ break;
+ }
+ if (ifp)
+ continue;
+
+ if (argc > 0) {
+ for (i = 0; i < argc; i++) {
+ if (strcmp(argv[i], spec.devname) == 0)
+ break;
+ }
+ active = (i == argc) ? IF_INACTIVE : IF_ACTIVE_USER;
+ } else {
+ /* -1 means we're discovering against a specific
+ * interface, but we still need the below rules
+ * to apply. */
+ if (argc == -1 && strcmp(argv[0], spec.devname) != 0)
+ continue;
+ active = ctx->options & DHCPCD_INACTIVE ?
+ IF_INACTIVE: IF_ACTIVE_USER;
+ }
+
+ for (i = 0; i < ctx->ifdc; i++)
+ if (fnmatch(ctx->ifdv[i], spec.devname, 0) == 0)
+ break;
+ if (i < ctx->ifdc)
+ active = IF_INACTIVE;
+ for (i = 0; i < ctx->ifc; i++)
+ if (fnmatch(ctx->ifv[i], spec.devname, 0) == 0)
+ break;
+ if (ctx->ifc && i == ctx->ifc)
+ active = IF_INACTIVE;
+ for (i = 0; i < ctx->ifac; i++)
+ if (fnmatch(ctx->ifav[i], spec.devname, 0) == 0)
+ break;
+ if (ctx->ifac && i == ctx->ifac)
+ active = IF_INACTIVE;
+
+#ifdef PLUGIN_DEV
+ /* Ensure that the interface name has settled */
+ if (!dev_initialised(ctx, spec.devname)) {
+ logdebugx("%s: waiting for interface to initialise",
+ spec.devname);
+ continue;
+ }
+#endif
+
+ if (if_vimaster(ctx, spec.devname) == 1) {
+ int loglevel = argc != 0 ? LOG_ERR : LOG_DEBUG;
+ logmessage(loglevel,
+ "%s: is a Virtual Interface Master, skipping",
+ spec.devname);
+ continue;
+ }
+
+ if_noconf = ((argc == 0 || argc == -1) && ctx->ifac == 0 &&
+ !if_hasconf(ctx, spec.devname));
+
+ /* Don't allow some reserved interface names unless explicit. */
+ if (if_noconf && if_ignore(ctx, spec.devname)) {
+ logdebugx("%s: ignoring due to interface type and"
+ " no config", spec.devname);
+ active = IF_INACTIVE;
+ }
+
+ ifp = calloc(1, sizeof(*ifp));
+ if (ifp == NULL) {
+ logerr(__func__);
+ break;
+ }
+ ifp->ctx = ctx;
+ strlcpy(ifp->name, spec.devname, sizeof(ifp->name));
+ ifp->flags = ifa->ifa_flags;
+
+ if (ifa->ifa_addr != NULL) {
+#ifdef AF_LINK
+ sdl = (const 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_PROPVIRTUAL
+ case IFT_PROPVIRTUAL: /* FALLTHROUGH */
+#endif
+#ifdef IFT_TUNNEL
+ case IFT_TUNNEL: /* FALLTHROUGH */
+#endif
+ case IFT_LOOP: /* FALLTHROUGH */
+ case IFT_PPP:
+ /* Don't allow unless explicit */
+ if (if_noconf && active) {
+ logdebugx("%s: ignoring due to"
+ " interface type and"
+ " no config",
+ ifp->name);
+ active = IF_INACTIVE;
+ }
+ __fallthrough; /* appease gcc */
+ /* FALLTHROUGH */
+#ifdef IFT_L2VLAN
+ case IFT_L2VLAN: /* FALLTHROUGH */
+#endif
+#ifdef IFT_L3IPVLAN
+ case IFT_L3IPVLAN: /* FALLTHROUGH */
+#endif
+ case IFT_ETHER:
+ ifp->hwtype = ARPHRD_ETHER;
+ break;
+#ifdef IFT_IEEE1394
+ case IFT_IEEE1394:
+ ifp->hwtype = ARPHRD_IEEE1394;
+ break;
+#endif
+#ifdef IFT_INFINIBAND
+ case IFT_INFINIBAND:
+ ifp->hwtype = ARPHRD_INFINIBAND;
+ break;
+#endif
+ default:
+ /* Don't allow unless explicit */
+ if (active) {
+ if (if_noconf)
+ active = IF_INACTIVE;
+ i = active ? LOG_WARNING : LOG_DEBUG;
+ logmessage(i, "%s: unsupported"
+ " interface type 0x%.2x",
+ ifp->name, sdl->sdl_type);
+ }
+ /* Pretend it's ethernet */
+ ifp->hwtype = ARPHRD_ETHER;
+ break;
+ }
+ ifp->hwlen = sdl->sdl_alen;
+ memcpy(ifp->hwaddr, CLLADDR(sdl), ifp->hwlen);
+#elif defined(AF_PACKET)
+ sll = (const void *)ifa->ifa_addr;
+ ifp->index = (unsigned int)sll->sll_ifindex;
+ ifp->hwtype = sll->sll_hatype;
+ ifp->hwlen = sll->sll_halen;
+ if (ifp->hwlen != 0)
+ memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen);
+ active = if_check_arphrd(ifp, active, if_noconf);
+#endif
+ }
+#ifdef __linux__
+ else {
+ struct ifreq ifr = { .ifr_flags = 0 };
+
+ /* This is a huge bug in getifaddrs(3) as there
+ * is no reason why this can't be returned in
+ * ifa_addr. */
+ strlcpy(ifr.ifr_name, ifa->ifa_name,
+ sizeof(ifr.ifr_name));
+ if (ioctl(ctx->pf_inet_fd, SIOCGIFHWADDR, &ifr) == -1)
+ logerr("%s: SIOCGIFHWADDR", ifa->ifa_name);
+ ifp->hwtype = ifr.ifr_hwaddr.sa_family;
+ if (ioctl(ctx->pf_inet_fd, SIOCGIFINDEX, &ifr) == -1)
+ logerr("%s: SIOCGIFINDEX", ifa->ifa_name);
+ ifp->index = (unsigned int)ifr.ifr_ifindex;
+ if_check_arphrd(ifp, active, if_noconf);
+ }
+#endif
+
+ if (!(ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) {
+ /* Handle any platform init for the interface */
+ if (active != IF_INACTIVE && if_init(ifp) == -1) {
+ logerr("%s: if_init", ifp->name);
+ if_free(ifp);
+ continue;
+ }
+ }
+
+ ifp->vlanid = if_vlanid(ifp);
+
+#ifdef SIOCGIFPRIORITY
+ /* Respect the interface priority */
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (pioctl(ctx, SIOCGIFPRIORITY, &ifr, sizeof(ifr)) == 0)
+ ifp->metric = (unsigned int)ifr.ifr_metric;
+ if_getssid(ifp);
+#else
+ /* Leave a low portion for user config */
+ ifp->metric = RTMETRIC_BASE + ifp->index;
+ if (if_getssid(ifp) != -1) {
+ ifp->wireless = true;
+ ifp->metric += RTMETRIC_WIRELESS;
+ }
+#endif
+
+ ifp->active = active;
+ ifp->carrier = if_carrier(ifp, ifa->ifa_data);
+ TAILQ_INSERT_TAIL(ifs, ifp, next);
+ }
+
+ return ifs;
+}
+
+/*
+ * eth0.100:2 OR eth0i100:2 (seems to be NetBSD xvif(4) only)
+ *
+ * drvname == eth
+ * devname == eth0.100 OR eth0i100
+ * ppa = 0
+ * lun = 2
+ */
+int
+if_nametospec(const char *ifname, struct if_spec *spec)
+{
+ char *ep, *pp;
+ int e;
+
+ if (ifname == NULL || *ifname == '\0' ||
+ strlcpy(spec->ifname, ifname, sizeof(spec->ifname)) >=
+ sizeof(spec->ifname) ||
+ strlcpy(spec->drvname, ifname, sizeof(spec->drvname)) >=
+ sizeof(spec->drvname))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* :N is an alias */
+ ep = strchr(spec->drvname, ':');
+ if (ep) {
+ spec->lun = (int)strtoi(ep + 1, NULL, 10, 0, INT_MAX, &e);
+ if (e != 0) {
+ errno = e;
+ return -1;
+ }
+ *ep = '\0';
+#ifdef __sun
+ ep--;
+#endif
+ } else {
+ spec->lun = -1;
+#ifdef __sun
+ ep = spec->drvname + strlen(spec->drvname) - 1;
+#endif
+ }
+
+ strlcpy(spec->devname, spec->drvname, sizeof(spec->devname));
+#ifdef __sun
+ /* Solaris has numbers in the driver name, such as e1000g */
+ while (ep > spec->drvname && isdigit((int)*ep))
+ ep--;
+ if (*ep++ == ':') {
+ errno = EINVAL;
+ return -1;
+ }
+#else
+ /* BSD and Linux no not have numbers in the driver name */
+ for (ep = spec->drvname; *ep != '\0' && !isdigit((int)*ep); ep++) {
+ if (*ep == ':') {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+#endif
+ spec->ppa = (int)strtoi(ep, &pp, 10, 0, INT_MAX, &e);
+ *ep = '\0';
+
+#ifndef __sun
+ /*
+ * . is used for VLAN style names
+ * i is used on NetBSD for xvif interfaces
+ */
+ if (pp != NULL && (*pp == '.' || *pp == 'i')) {
+ spec->vlid = (int)strtoi(pp + 1, NULL, 10, 0, INT_MAX, &e);
+ if (e)
+ spec->vlid = -1;
+ } else
+#endif
+ spec->vlid = -1;
+
+ return 0;
+}
+
+static struct interface *
+if_findindexname(struct if_head *ifaces, unsigned int idx, const char *name)
+{
+
+ if (ifaces != NULL) {
+ struct if_spec spec;
+ struct interface *ifp;
+
+ if (name && if_nametospec(name, &spec) == -1)
+ return NULL;
+
+ TAILQ_FOREACH(ifp, ifaces, next) {
+ if ((name && strcmp(ifp->name, spec.devname) == 0) ||
+ (!name && ifp->index == idx))
+ return ifp;
+ }
+ }
+
+ errno = ENXIO;
+ 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);
+}
+
+struct interface *
+if_loopback(struct dhcpcd_ctx *ctx)
+{
+ struct interface *ifp;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (ifp->flags & IFF_LOOPBACK)
+ return ifp;
+ }
+ return NULL;
+}
+
+int
+if_domtu(const struct interface *ifp, short int mtu)
+{
+ int r;
+ struct ifreq ifr;
+
+#ifdef __sun
+ if (mtu == 0)
+ return if_mtu_os(ifp);
+#endif
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ ifr.ifr_mtu = mtu;
+ if (mtu != 0)
+ r = if_ioctl(ifp->ctx, SIOCSIFMTU, &ifr, sizeof(ifr));
+ else
+ r = pioctl(ifp->ctx, SIOCGIFMTU, &ifr, sizeof(ifr));
+
+ if (r == -1)
+ return -1;
+ return ifr.ifr_mtu;
+}
+
+#ifdef ALIAS_ADDR
+int
+if_makealias(char *alias, size_t alias_len, const char *ifname, int lun)
+{
+
+ if (lun == 0)
+ return strlcpy(alias, ifname, alias_len);
+ return snprintf(alias, alias_len, "%s:%u", ifname, lun);
+}
+#endif
+
+struct interface *
+if_findifpfromcmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, int *hoplimit)
+{
+ struct cmsghdr *cm;
+ unsigned int ifindex = 0;
+ struct interface *ifp;
+#ifdef INET
+#ifdef IP_RECVIF
+ struct sockaddr_dl sdl;
+#else
+ struct in_pktinfo ipi;
+#endif
+#endif
+#ifdef INET6
+ struct in6_pktinfo ipi6;
+#else
+ UNUSED(hoplimit);
+#endif
+
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(msg);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(msg, cm))
+ {
+#ifdef INET
+ if (cm->cmsg_level == IPPROTO_IP) {
+ switch(cm->cmsg_type) {
+#ifdef IP_RECVIF
+ case IP_RECVIF:
+ if (cm->cmsg_len <
+ offsetof(struct sockaddr_dl, sdl_index) +
+ sizeof(sdl.sdl_index))
+ continue;
+ memcpy(&sdl, CMSG_DATA(cm),
+ MIN(sizeof(sdl), cm->cmsg_len));
+ ifindex = sdl.sdl_index;
+ break;
+#else
+ case IP_PKTINFO:
+ if (cm->cmsg_len != CMSG_LEN(sizeof(ipi)))
+ continue;
+ memcpy(&ipi, CMSG_DATA(cm), sizeof(ipi));
+ ifindex = (unsigned int)ipi.ipi_ifindex;
+ break;
+#endif
+ }
+ }
+#endif
+#ifdef INET6
+ if (cm->cmsg_level == IPPROTO_IPV6) {
+ switch(cm->cmsg_type) {
+ case IPV6_PKTINFO:
+ if (cm->cmsg_len != CMSG_LEN(sizeof(ipi6)))
+ continue;
+ memcpy(&ipi6, CMSG_DATA(cm), sizeof(ipi6));
+ ifindex = (unsigned int)ipi6.ipi6_ifindex;
+ break;
+ case IPV6_HOPLIMIT:
+ if (cm->cmsg_len != CMSG_LEN(sizeof(int)))
+ continue;
+ if (hoplimit == NULL)
+ break;
+ memcpy(hoplimit, CMSG_DATA(cm), sizeof(int));
+ break;
+ }
+ }
+#endif
+ }
+
+ /* Find the receiving interface */
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (ifp->index == ifindex)
+ break;
+ }
+ if (ifp == NULL)
+ errno = ESRCH;
+ return ifp;
+}
+
+int
+xsocket(int domain, int type, int protocol)
+{
+ int s;
+#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
+ int xflags, xtype = type;
+#endif
+
+#ifndef HAVE_SOCK_CLOEXEC
+ if (xtype & SOCK_CLOEXEC)
+ type &= ~SOCK_CLOEXEC;
+#endif
+#ifndef HAVE_SOCK_NONBLOCK
+ if (xtype & SOCK_NONBLOCK)
+ type &= ~SOCK_NONBLOCK;
+#endif
+
+ if ((s = socket(domain, type, protocol)) == -1)
+ return -1;
+
+#ifndef HAVE_SOCK_CLOEXEC
+ if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(s, F_GETFD)) == -1 ||
+ fcntl(s, F_SETFD, xflags | FD_CLOEXEC) == -1))
+ goto out;
+#endif
+#ifndef HAVE_SOCK_NONBLOCK
+ if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(s, F_GETFL)) == -1 ||
+ fcntl(s, F_SETFL, xflags | O_NONBLOCK) == -1))
+ goto out;
+#endif
+
+ return s;
+
+#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
+out:
+ close(s);
+ return -1;
+#endif
+}
+
+int
+xsocketpair(int domain, int type, int protocol, int fd[2])
+{
+ int s;
+#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
+ int xflags, xtype = type;
+#endif
+
+#ifndef HAVE_SOCK_CLOEXEC
+ if (xtype & SOCK_CLOEXEC)
+ type &= ~SOCK_CLOEXEC;
+#endif
+#ifndef HAVE_SOCK_NONBLOCK
+ if (xtype & SOCK_NONBLOCK)
+ type &= ~SOCK_NONBLOCK;
+#endif
+
+ if ((s = socketpair(domain, type, protocol, fd)) == -1)
+ return -1;
+
+#ifndef HAVE_SOCK_CLOEXEC
+ if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(fd[0], F_GETFD)) == -1 ||
+ fcntl(fd[0], F_SETFD, xflags | FD_CLOEXEC) == -1))
+ goto out;
+ if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(fd[1], F_GETFD)) == -1 ||
+ fcntl(fd[1], F_SETFD, xflags | FD_CLOEXEC) == -1))
+ goto out;
+#endif
+#ifndef HAVE_SOCK_NONBLOCK
+ if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(fd[0], F_GETFL)) == -1 ||
+ fcntl(fd[0], F_SETFL, xflags | O_NONBLOCK) == -1))
+ goto out;
+ if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(fd[1], F_GETFL)) == -1 ||
+ fcntl(fd[1], F_SETFL, xflags | O_NONBLOCK) == -1))
+ goto out;
+#endif
+
+ return s;
+
+#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
+out:
+ close(fd[0]);
+ close(fd[1]);
+ return -1;
+#endif
+}
diff --git a/src/if.h b/src/if.h
new file mode 100644
index 000000000000..d24fbc92cf48
--- /dev/null
+++ b/src/if.h
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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>
+#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
+
+#include <ifaddrs.h>
+
+/* 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 */
+
+/*
+ * Systems which handle 1 address per alias.
+ * Currenly this is just Solaris.
+ * While Linux can do aliased addresses, it is only useful for their
+ * legacy ifconfig(8) tool which cannot display >1 IPv4 address
+ * (it can display many IPv6 addresses which makes the limitation odd).
+ * Linux has ip(8) which is a more feature rich tool, without the above
+ * restriction.
+ */
+#ifndef ALIAS_ADDR
+# ifdef __sun
+# define ALIAS_ADDR
+# endif
+#endif
+
+#include "config.h"
+
+/* POSIX defines ioctl request as an int, which Solaris and musl use.
+ * Everyone else use an unsigned long, which happens to be the bigger one
+ * so we use that in our on wire API. */
+#ifdef IOCTL_REQUEST_TYPE
+typedef IOCTL_REQUEST_TYPE ioctl_request_t;
+#else
+typedef unsigned long ioctl_request_t;
+#endif
+
+#include "dhcpcd.h"
+#include "ipv4.h"
+#include "ipv6.h"
+#include "route.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
+
+/* Maximum frame length.
+ * Support jumbo frames and some extra. */
+#define FRAMEHDRLEN_MAX 14 /* only ethernet support */
+#define FRAMELEN_MAX (FRAMEHDRLEN_MAX + 9216)
+
+#define UDPLEN_MAX 64 * 1024
+
+/* 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
+
+#ifndef CLLADDR
+#ifdef AF_LINK
+# define CLLADDR(sdl) (const void *)((sdl)->sdl_data + (sdl)->sdl_nlen)
+#endif
+#endif
+
+#ifdef __sun
+/* Solaris stupidly defines this for compat with BSD
+ * but then ignores it. */
+#undef RTF_CLONING
+
+/* This interface is busted on DilOS at least.
+ * It used to work, but lukily Solaris can fall back to
+ * IP_PKTINFO. */
+#undef IP_RECVIF
+
+/* Solaris getifaddrs is very un-suitable for dhcpcd.
+ * See if-sun.c for details why. */
+struct ifaddrs;
+int if_getifaddrs(struct ifaddrs **);
+#define getifaddrs if_getifaddrs
+int if_getsubnet(struct dhcpcd_ctx *, const char *, int, void *, size_t);
+#endif
+
+int if_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t);
+#ifdef HAVE_PLEDGE
+#define pioctl(ctx, req, data, len) if_ioctl((ctx), (req), (data), (len))
+#else
+#define pioctl(ctx, req, data, len) ioctl((ctx)->pf_inet_fd, (req),(data),(len))
+#endif
+int if_getflags(struct interface *);
+int if_setflag(struct interface *, short, short);
+#define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING), 0)
+#define if_down(ifp) if_setflag((ifp), 0, IFF_UP);
+bool if_is_link_up(const struct interface *);
+bool if_valid_hwaddr(const uint8_t *, size_t);
+struct if_head *if_discover(struct dhcpcd_ctx *, struct ifaddrs **,
+ int, char * const *);
+void if_markaddrsstale(struct if_head *);
+void if_learnaddrs(struct dhcpcd_ctx *, struct if_head *, struct ifaddrs **);
+void if_deletestaleaddrs(struct if_head *);
+struct interface *if_find(struct if_head *, const char *);
+struct interface *if_findindex(struct if_head *, unsigned int);
+struct interface *if_loopback(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 *, const void *);
+bool if_roaming(struct interface *);
+
+#ifdef ALIAS_ADDR
+int if_makealias(char *, size_t, const char *, int);
+#endif
+
+int if_mtu_os(const struct interface *);
+
+/*
+ * Helper to decode an interface name of bge0:1 to
+ * devname = bge0, drvname = bge0, ppa = 0, lun = 1.
+ * If ppa or lun are invalid they are set to -1.
+ */
+struct if_spec {
+ char ifname[IF_NAMESIZE];
+ char devname[IF_NAMESIZE];
+ char drvname[IF_NAMESIZE];
+ int ppa;
+ int vlid;
+ int lun;
+};
+int if_nametospec(const char *, struct if_spec *);
+
+/* The below functions are provided by if-KERNEL.c */
+int os_init(void);
+int if_conf(struct interface *);
+int if_init(struct interface *);
+int if_getssid(struct interface *);
+int if_ignoregroup(int, const char *);
+bool if_ignore(struct dhcpcd_ctx *, const char *);
+int if_vimaster(struct dhcpcd_ctx *ctx, const char *);
+unsigned short if_vlanid(const struct interface *);
+char * if_getnetworknamespace(char *, size_t);
+int if_opensockets(struct dhcpcd_ctx *);
+int if_opensockets_os(struct dhcpcd_ctx *);
+void if_closesockets(struct dhcpcd_ctx *);
+void if_closesockets_os(struct dhcpcd_ctx *);
+int if_handlelink(struct dhcpcd_ctx *);
+int if_randomisemac(struct interface *);
+int if_setmac(struct interface *ifp, void *, uint8_t);
+
+/* 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
+
+/* Define SOCK_CLOEXEC and SOCK_NONBLOCK for systems that lack it.
+ * xsocket() in if.c will map them to fctnl FD_CLOEXEC and O_NONBLOCK. */
+#ifdef SOCK_CLOEXEC
+# define HAVE_SOCK_CLOEXEC
+#else
+# define SOCK_CLOEXEC 0x10000000
+#endif
+#ifdef SOCK_NONBLOCK
+# define HAVE_SOCK_NONBLOCK
+#else
+# define SOCK_NONBLOCK 0x20000000
+#endif
+#ifndef SOCK_CXNB
+#define SOCK_CXNB SOCK_CLOEXEC | SOCK_NONBLOCK
+#endif
+int xsocket(int, int, int);
+int xsocketpair(int, int, int, int[2]);
+
+int if_route(unsigned char, const struct rt *rt);
+int if_initrt(struct dhcpcd_ctx *, rb_tree_t *, int);
+
+int if_missfilter(struct interface *, struct sockaddr *);
+int if_missfilter_apply(struct dhcpcd_ctx *);
+
+#ifdef INET
+int if_address(unsigned char, const struct ipv4_addr *);
+int if_addrflags(const struct interface *, const struct in_addr *,
+ const char *);
+
+#endif
+
+#ifdef INET6
+void if_disable_rtadv(void);
+void if_setup_inet6(const struct interface *);
+int ip6_forwarding(const char *ifname);
+
+struct ra;
+struct ipv6_addr;
+
+int if_applyra(const struct ra *);
+int if_address6(unsigned char, const struct ipv6_addr *);
+int if_addrflags6(const struct interface *, const struct in6_addr *,
+ const char *);
+int if_getlifetime6(struct ipv6_addr *);
+
+#else
+#define if_checkipv6(a, b, c) (-1)
+#endif
+
+int if_machinearch(char *, size_t);
+struct interface *if_findifpfromcmsg(struct dhcpcd_ctx *,
+ struct msghdr *, int *);
+
+#ifdef __linux__
+int if_linksocket(struct sockaddr_nl *, int, int);
+int if_getnetlink(struct dhcpcd_ctx *, struct iovec *, int, int,
+ int (*)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *);
+#endif
+#endif
diff --git a/src/ipv4.c b/src/ipv4.c
new file mode 100644
index 000000000000..7bf7db38278a
--- /dev/null
+++ b/src/ipv4.c
@@ -0,0 +1,1002 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <stdbool.h>
+#include <stdio.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 "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "logerr.h"
+#include "route.h"
+#include "script.h"
+#include "sa.h"
+
+#define IPV4_LOOPBACK_ROUTE
+#if defined(__linux__) || defined(__sun) || (defined(BSD) && defined(RTF_LOCAL))
+/* Linux has had loopback routes in the local table since 2.2
+ * Solaris does not seem to support loopback routes. */
+#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 *mask)
+{
+ 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) &&
+ (mask == NULL || ap->mask.s_addr == mask->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(ntohl(ap->addr.s_addr)))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+static struct ipv4_addr *
+ipv4_iffindmaskaddr(struct interface *ifp, const struct in_addr *addr)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ state = IPV4_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH (ap, &state->addrs, next) {
+ if ((ap->addr.s_addr & ap->mask.s_addr) ==
+ (addr->s_addr & ap->mask.s_addr))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+static struct ipv4_addr *
+ipv4_iffindmaskbrd(struct interface *ifp, const struct in_addr *addr)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ state = IPV4_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH (ap, &state->addrs, next) {
+ if ((ap->brd.s_addr & ap->mask.s_addr) ==
+ (addr->s_addr & ap->mask.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;
+}
+
+struct ipv4_addr *
+ipv4_findmaskaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr)
+{
+ struct interface *ifp;
+ struct ipv4_addr *ap;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ ap = ipv4_iffindmaskaddr(ifp, addr);
+ if (ap)
+ return ap;
+ }
+ return NULL;
+}
+
+struct ipv4_addr *
+ipv4_findmaskbrd(struct dhcpcd_ctx *ctx, const struct in_addr *addr)
+{
+ struct interface *ifp;
+ struct ipv4_addr *ap;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ ap = ipv4_iffindmaskbrd(ifp, addr);
+ if (ap)
+ return ap;
+ }
+ return NULL;
+}
+
+int
+ipv4_hasaddr(const struct interface *ifp)
+{
+ const struct dhcp_state *dstate;
+
+#ifdef IPV4LL
+ if (IPV4LL_STATE_RUNNING(ifp))
+ return 1;
+#endif
+
+ dstate = D_CSTATE(ifp);
+ return (dstate &&
+ dstate->added == STATE_ADDED &&
+ dstate->addr != NULL);
+}
+
+/* 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) && (tis->added & STATE_FAKE))
+ return -1;
+ if ((sis->added & STATE_FAKE) && !(tis->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) {
+ if (IS_DHCP(sis->new) && !IS_DHCP(tis->new))
+ return -1;
+ if (!IS_DHCP(sis->new) && IS_DHCP(tis->new))
+ return 1;
+ }
+ return 0;
+}
+
+static int
+inet_dhcproutes(rb_tree_t *routes, struct interface *ifp, bool *have_default)
+{
+ const struct dhcp_state *state;
+ rb_tree_t nroutes;
+ struct rt *rt, *r = NULL;
+ struct in_addr in;
+ uint16_t mtu;
+ int n;
+
+ state = D_CSTATE(ifp);
+ if (state == NULL || state->state != DHS_BOUND || !state->added)
+ return 0;
+
+ /* An address does have to exist. */
+ assert(state->addr);
+
+ rb_tree_init(&nroutes, &rt_compare_proto_ops);
+
+ /* First, add a subnet route. */
+ if (state->addr->mask.s_addr != INADDR_ANY
+#ifndef BSD
+ /* BSD adds a route in this instance */
+ && state->addr->mask.s_addr != INADDR_BROADCAST
+#endif
+ ) {
+ if ((rt = rt_new(ifp)) == NULL)
+ return -1;
+ rt->rt_dflags |= RTDF_IFA_ROUTE;
+ in.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr;
+ sa_in_init(&rt->rt_dest, &in);
+ in.s_addr = state->addr->mask.s_addr;
+ sa_in_init(&rt->rt_netmask, &in);
+ //in.s_addr = INADDR_ANY;
+ //sa_in_init(&rt->rt_gateway, &in);
+ rt->rt_gateway.sa_family = AF_UNSPEC;
+ rt_proto_add(&nroutes, rt);
+ }
+
+ /* If any set routes, grab them, otherwise DHCP routes. */
+ if (RB_TREE_MIN(&ifp->options->routes)) {
+ RB_TREE_FOREACH(r, &ifp->options->routes) {
+ if (sa_is_unspecified(&r->rt_gateway))
+ break;
+ if ((rt = rt_new0(ifp->ctx)) == NULL)
+ return -1;
+ memcpy(rt, r, sizeof(*rt));
+ rt_setif(rt, ifp);
+ rt->rt_dflags = RTDF_STATIC;
+ rt_proto_add(&nroutes, rt);
+ }
+ } else {
+ if (dhcp_get_routes(&nroutes, ifp) == -1)
+ return -1;
+ }
+
+ /* If configured, install a gateway to the desintion
+ * for P2P interfaces. */
+ if (ifp->flags & IFF_POINTOPOINT &&
+ has_option_mask(ifp->options->dstmask, DHO_ROUTER))
+ {
+ if ((rt = rt_new(ifp)) == NULL)
+ return -1;
+ in.s_addr = INADDR_ANY;
+ sa_in_init(&rt->rt_dest, &in);
+ sa_in_init(&rt->rt_netmask, &in);
+ sa_in_init(&rt->rt_gateway, &state->addr->brd);
+ sa_in_init(&rt->rt_ifa, &state->addr->addr);
+ rt_proto_add(&nroutes, rt);
+ }
+
+ /* Copy our address as the source address and set mtu */
+ mtu = dhcp_get_mtu(ifp);
+ n = 0;
+ while ((rt = RB_TREE_MIN(&nroutes)) != NULL) {
+ rb_tree_remove_node(&nroutes, rt);
+ rt->rt_mtu = mtu;
+ if (!(rt->rt_dflags & RTDF_STATIC))
+ rt->rt_dflags |= RTDF_DHCP;
+ sa_in_init(&rt->rt_ifa, &state->addr->addr);
+ if (rb_tree_insert_node(routes, rt) != rt) {
+ rt_free(rt);
+ continue;
+ }
+ if (rt_is_default(rt))
+ *have_default = true;
+ n = 1;
+ }
+
+ return n;
+}
+
+/* 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 int
+inet_routerhostroute(rb_tree_t *routes, struct interface *ifp)
+{
+ struct rt *rt, *rth, *rtp;
+ struct sockaddr_in *dest, *netmask, *gateway;
+ const char *cp, *cp2, *cp3, *cplim;
+ struct if_options *ifo;
+ const struct dhcp_state *state;
+ struct in_addr in;
+ rb_tree_t troutes;
+
+ /* Don't add a host route for these interfaces. */
+ if (ifp->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
+ return 0;
+
+ rb_tree_init(&troutes, &rt_compare_proto_ops);
+
+ RB_TREE_FOREACH(rt, routes) {
+ if (rt->rt_dest.sa_family != AF_INET)
+ continue;
+ if (!sa_is_unspecified(&rt->rt_dest) ||
+ sa_is_unspecified(&rt->rt_gateway))
+ continue;
+ gateway = satosin(&rt->rt_gateway);
+ /* Scan for a route to match */
+ RB_TREE_FOREACH(rth, routes) {
+ if (rth == rt)
+ break;
+ /* match host */
+ if (sa_cmp(&rth->rt_dest, &rt->rt_gateway) == 0)
+ break;
+ /* match subnet */
+ /* XXX ADD TO RT_COMARE? XXX */
+ cp = (const char *)&gateway->sin_addr.s_addr;
+ dest = satosin(&rth->rt_dest);
+ cp2 = (const char *)&dest->sin_addr.s_addr;
+ netmask = satosin(&rth->rt_netmask);
+ cp3 = (const char *)&netmask->sin_addr.s_addr;
+ cplim = cp3 + sizeof(netmask->sin_addr.s_addr);
+ while (cp3 < cplim) {
+ if ((*cp++ ^ *cp2++) & *cp3++)
+ break;
+ }
+ if (cp3 == cplim)
+ break;
+ }
+ if (rth != rt)
+ 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))
+ {
+ char buf[INET_MAX_ADDRSTRLEN];
+
+ ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED;
+ logwarnx("%s: forcing router %s through "
+ "interface",
+ ifp->name,
+ sa_addrtop(&rt->rt_gateway,
+ buf, sizeof(buf)));
+ }
+ gateway->sin_addr.s_addr = INADDR_ANY;
+ continue;
+ }
+ if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) &&
+ !(state->added & STATE_FAKE))
+ {
+ char buf[INET_MAX_ADDRSTRLEN];
+
+ ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED;
+ logwarnx("%s: router %s requires a host route",
+ ifp->name,
+ sa_addrtop(&rt->rt_gateway, buf, sizeof(buf)));
+ }
+
+ if ((rth = rt_new(ifp)) == NULL)
+ return -1;
+ rth->rt_flags |= RTF_HOST;
+ sa_in_init(&rth->rt_dest, &gateway->sin_addr);
+ in.s_addr = INADDR_BROADCAST;
+ sa_in_init(&rth->rt_netmask, &in);
+ in.s_addr = INADDR_ANY;
+ sa_in_init(&rth->rt_gateway, &in);
+ rth->rt_mtu = dhcp_get_mtu(ifp);
+ if (state->addr != NULL)
+ sa_in_init(&rth->rt_ifa, &state->addr->addr);
+ else
+ rth->rt_ifa.sa_family = AF_UNSPEC;
+
+ /* We need to insert the host route just before the router. */
+ while ((rtp = RB_TREE_MAX(routes)) != NULL) {
+ rb_tree_remove_node(routes, rtp);
+ rt_proto_add(&troutes, rtp);
+ if (rtp == rt)
+ break;
+ }
+ rt_proto_add(routes, rth);
+ /* troutes is now reversed, so add backwards again. */
+ while ((rtp = RB_TREE_MAX(&troutes)) != NULL) {
+ rb_tree_remove_node(&troutes, rtp);
+ rt_proto_add(routes, rtp);
+ }
+ }
+ return 0;
+}
+
+bool
+inet_getroutes(struct dhcpcd_ctx *ctx, rb_tree_t *routes)
+{
+ struct interface *ifp;
+ bool have_default = false;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (!ifp->active)
+ continue;
+ if (inet_dhcproutes(routes, ifp, &have_default) == -1)
+ return false;
+#ifdef IPV4LL
+ if (ipv4ll_subnetroute(routes, ifp) == -1)
+ return false;
+#endif
+ if (inet_routerhostroute(routes, ifp) == -1)
+ return false;
+ }
+
+#ifdef IPV4LL
+ /* If there is no default route, see if we can use an IPv4LL one. */
+ if (have_default)
+ return true;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (ifp->active &&
+ ipv4ll_defaultroute(routes, ifp) == 1)
+ break;
+ }
+#endif
+
+ return true;
+}
+
+int
+ipv4_deladdr(struct ipv4_addr *addr, int keeparp)
+{
+ int r;
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ logdebugx("%s: deleting IP address %s",
+ addr->iface->name, addr->saddr);
+
+ r = if_address(RTM_DELADDR, addr);
+ if (r == -1 &&
+ errno != EADDRNOTAVAIL && errno != ESRCH &&
+ errno != ENXIO && errno != ENODEV)
+ logerr("%s: %s", addr->iface->name, __func__);
+
+#ifdef ARP
+ if (!keeparp)
+ arp_freeaddr(addr->iface, &addr->addr);
+#else
+ UNUSED(keeparp);
+#endif
+
+ state = IPV4_STATE(addr->iface);
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (IPV4_MASK_EQ(ap, addr)) {
+ struct dhcp_state *dstate;
+
+ dstate = D_STATE(ap->iface);
+ TAILQ_REMOVE(&state->addrs, ap, next);
+ free(ap);
+
+ if (dstate && dstate->addr == ap) {
+ dstate->added = 0;
+ dstate->addr = NULL;
+ }
+ break;
+ }
+ }
+
+ 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;
+ /* The lease could have been added, but the address deleted
+ * by a 3rd party. */
+ if (state->addr == NULL ||
+ ifo->options & DHCPCD_INFORM ||
+ (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0))
+ return 0;
+#ifdef ARP
+ arp_freeaddr(ifp, &state->addr->addr);
+#endif
+ r = ipv4_deladdr(state->addr, 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) {
+ logerr(__func__);
+ return NULL;
+ }
+ TAILQ_INIT(&state->addrs);
+ }
+ return state;
+}
+
+#ifdef ALIAS_ADDR
+/* Find the next logical aliase address we can use. */
+static int
+ipv4_aliasaddr(struct ipv4_addr *ia, struct ipv4_addr **repl)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *iap;
+ unsigned int lun;
+ char alias[IF_NAMESIZE];
+
+ if (ia->alias[0] != '\0')
+ return 0;
+
+ lun = 0;
+ state = IPV4_STATE(ia->iface);
+find_lun:
+ if (if_makealias(alias, IF_NAMESIZE, ia->iface->name, lun) >=
+ IF_NAMESIZE)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (iap->alias[0] != '\0' && iap->addr.s_addr == INADDR_ANY) {
+ /* No address assigned? Lets use it. */
+ strlcpy(ia->alias, iap->alias, sizeof(ia->alias));
+ if (repl)
+ *repl = iap;
+ return 1;
+ }
+ if (strcmp(iap->alias, alias) == 0)
+ break;
+ }
+
+ if (iap != NULL) {
+ if (lun == UINT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ lun++;
+ goto find_lun;
+ }
+
+ strlcpy(ia->alias, alias, sizeof(ia->alias));
+ return 0;
+}
+#endif
+
+struct ipv4_addr *
+ipv4_addaddr(struct interface *ifp, const struct in_addr *addr,
+ const struct in_addr *mask, const struct in_addr *bcast,
+ uint32_t vltime, uint32_t pltime)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ia;
+#ifdef ALIAS_ADDR
+ int replaced, blank;
+ struct ipv4_addr *replaced_ia;
+#endif
+
+ if ((state = ipv4_getstate(ifp)) == NULL) {
+ logerr(__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(ia, 0);
+ }
+ }
+
+ ia = ipv4_iffindaddr(ifp, addr, NULL);
+ if (ia == NULL) {
+ ia = malloc(sizeof(*ia));
+ if (ia == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ ia->iface = ifp;
+ ia->addr = *addr;
+#ifdef IN_IFF_TENTATIVE
+ ia->addr_flags = IN_IFF_TENTATIVE;
+#endif
+ ia->flags = IPV4_AF_NEW;
+ } else
+ ia->flags &= ~IPV4_AF_NEW;
+
+ ia->mask = *mask;
+ ia->brd = *bcast;
+#ifdef IP_LIFETIME
+ if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
+ /* We don't want the kernel to expire the address. */
+ ia->vltime = ia->pltime = DHCP_INFINITE_LIFETIME;
+ } else {
+ ia->vltime = vltime;
+ ia->pltime = pltime;
+ }
+#else
+ UNUSED(vltime);
+ UNUSED(pltime);
+#endif
+ snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d",
+ inet_ntoa(*addr), inet_ntocidr(*mask));
+
+#ifdef ALIAS_ADDR
+ blank = (ia->alias[0] == '\0');
+ if ((replaced = ipv4_aliasaddr(ia, &replaced_ia)) == -1) {
+ logerr("%s: ipv4_aliasaddr", ifp->name);
+ free(ia);
+ return NULL;
+ }
+ if (blank)
+ logdebugx("%s: aliased %s", ia->alias, ia->saddr);
+#endif
+
+ logdebugx("%s: adding IP address %s %s %s",
+ ifp->name, ia->saddr,
+ ifp->flags & IFF_POINTOPOINT ? "destination" : "broadcast",
+ inet_ntoa(*bcast));
+ if (if_address(RTM_NEWADDR, ia) == -1) {
+ if (errno != EEXIST)
+ logerr("%s: if_addaddress",
+ __func__);
+ if (ia->flags & IPV4_AF_NEW)
+ free(ia);
+ return NULL;
+ }
+
+#ifdef ALIAS_ADDR
+ if (replaced) {
+ TAILQ_REMOVE(&state->addrs, replaced_ia, next);
+ free(replaced_ia);
+ }
+#endif
+
+ if (ia->flags & IPV4_AF_NEW)
+ 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;
+ struct ipv4_addr *ia;
+
+ ia = ipv4_addaddr(ifp, &lease->addr, &lease->mask, &lease->brd,
+ lease->leasetime, lease->rebindtime);
+ if (ia == NULL)
+ return -1;
+
+ state = D_STATE(ifp);
+ state->added = STATE_ADDED;
+ state->addr = ia;
+ return 0;
+}
+
+struct ipv4_addr *
+ipv4_applyaddr(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct dhcp_lease *lease;
+ struct if_options *ifo = ifp->options;
+ struct ipv4_addr *ia;
+
+ if (state == NULL)
+ return NULL;
+
+ lease = &state->lease;
+ if (state->new == NULL) {
+ if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT))
+ {
+ if (state->added) {
+ delete_address(ifp);
+ rt_build(ifp->ctx, AF_INET);
+#ifdef ARP
+ /* Announce the preferred address to
+ * kick ARP caches. */
+ arp_announceaddr(ifp->ctx,&lease->addr);
+#endif
+ }
+ script_runreason(ifp, state->reason);
+ } else
+ rt_build(ifp->ctx, AF_INET);
+ return NULL;
+ }
+
+ ia = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ /* If the netmask or broadcast is different, re-add the addresss.
+ * If IP addresses do not have lifetimes, there is a very real chance
+ * that re-adding them will scrub the subnet route temporarily
+ * which is a bad thing, so avoid it. */
+ if (ia != NULL &&
+ ia->mask.s_addr == lease->mask.s_addr &&
+ ia->brd.s_addr == lease->brd.s_addr)
+ {
+#ifndef IP_LIFETIME
+ logdebugx("%s: IP address %s already exists",
+ ifp->name, ia->saddr);
+#endif
+ } else {
+#ifdef __linux__
+ /* Linux does not change netmask/broadcast address
+ * for re-added addresses, so we need to delete the old one
+ * first. */
+ if (ia != NULL)
+ ipv4_deladdr(ia, 0);
+#endif
+#ifndef IP_LIFETIME
+ if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST)
+ return NULL;
+#endif
+ }
+#ifdef IP_LIFETIME
+ if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST)
+ return NULL;
+#endif
+
+ ia = ipv4_iffindaddr(ifp, &lease->addr, NULL);
+ if (ia == NULL) {
+ logerrx("%s: added address vanished", ifp->name);
+ return NULL;
+ }
+#if defined(ARP) && defined(IN_IFF_NOTUSEABLE)
+ if (ia->addr_flags & IN_IFF_NOTUSEABLE)
+ return NULL;
+#endif
+
+ /* Delete the old address if different */
+ if (state->addr &&
+ state->addr->addr.s_addr != lease->addr.s_addr &&
+ ipv4_iffindaddr(ifp, &lease->addr, NULL))
+ delete_address(ifp);
+
+ state->addr = ia;
+ state->added = STATE_ADDED;
+
+ rt_build(ifp->ctx, AF_INET);
+
+#ifdef ARP
+ arp_announceaddr(ifp->ctx, &state->addr->addr);
+#endif
+
+ if (state->state == DHS_BOUND) {
+ script_runreason(ifp, state->reason);
+ dhcpcd_daemonise(ifp->ctx);
+ }
+ return ia;
+}
+
+void
+ipv4_markaddrsstale(struct interface *ifp)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ia;
+
+ state = IPV4_STATE(ifp);
+ if (state == NULL)
+ return;
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ ia->flags |= IPV4_AF_STALE;
+ }
+}
+
+void
+ipv4_deletestaleaddrs(struct interface *ifp)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ia, *ia1;
+
+ state = IPV4_STATE(ifp);
+ if (state == NULL)
+ return;
+
+ TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) {
+ if (!(ia->flags & IPV4_AF_STALE))
+ continue;
+ ipv4_handleifa(ifp->ctx, RTM_DELADDR,
+ ifp->ctx->ifaces, ifp->name,
+ &ia->addr, &ia->mask, &ia->brd, 0, getpid());
+ }
+}
+
+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 *mask,
+ const struct in_addr *brd, int addrflags, pid_t pid)
+{
+ struct interface *ifp;
+ struct ipv4_state *state;
+ struct ipv4_addr *ia;
+ bool ia_is_new;
+
+#if 0
+ char sbrdbuf[INET_ADDRSTRLEN];
+ const char *sbrd;
+
+ if (brd)
+ sbrd = inet_ntop(AF_INET, brd, sbrdbuf, sizeof(sbrdbuf));
+ else
+ sbrd = NULL;
+ logdebugx("%s: %s %s/%d %s %d", ifname,
+ cmd == RTM_NEWADDR ? "RTM_NEWADDR" :
+ cmd == RTM_DELADDR ? "RTM_DELADDR" : "???",
+ inet_ntoa(*addr), inet_ntocidr(*mask), sbrd, addrflags);
+#endif
+
+ if (ifs == NULL)
+ ifs = ctx->ifaces;
+ if (ifs == NULL) {
+ errno = ESRCH;
+ return;
+ }
+ if ((ifp = if_find(ifs, ifname)) == NULL)
+ return;
+ if ((state = ipv4_getstate(ifp)) == NULL) {
+ errno = ENOENT;
+ return;
+ }
+
+ ia = ipv4_iffindaddr(ifp, addr, NULL);
+ switch (cmd) {
+ case RTM_NEWADDR:
+ if (ia == NULL) {
+ if ((ia = malloc(sizeof(*ia))) == NULL) {
+ logerr(__func__);
+ return;
+ }
+ ia->iface = ifp;
+ ia->addr = *addr;
+ ia->mask = *mask;
+ ia->flags = 0;
+ ia_is_new = true;
+#ifdef ALIAS_ADDR
+ strlcpy(ia->alias, ifname, sizeof(ia->alias));
+#endif
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ } else
+ ia_is_new = false;
+ /* Mask could have changed */
+ if (ia_is_new ||
+ (mask->s_addr != INADDR_ANY &&
+ mask->s_addr != ia->mask.s_addr))
+ {
+ ia->mask = *mask;
+ snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d",
+ inet_ntoa(*addr), inet_ntocidr(*mask));
+ }
+ if (brd != NULL)
+ ia->brd = *brd;
+ else
+ ia->brd.s_addr = INADDR_ANY;
+ ia->addr_flags = addrflags;
+ ia->flags &= ~IPV4_AF_STALE;
+ break;
+ case RTM_DELADDR:
+ if (ia == NULL)
+ return;
+ if (mask->s_addr != INADDR_ANY &&
+ mask->s_addr != ia->mask.s_addr)
+ return;
+ TAILQ_REMOVE(&state->addrs, ia, next);
+ break;
+ default:
+ return;
+ }
+
+ if (addr->s_addr != INADDR_ANY && addr->s_addr != INADDR_BROADCAST) {
+ ia = dhcp_handleifa(cmd, ia, pid);
+#ifdef IPV4LL
+ if (ia != NULL)
+ ipv4ll_handleifa(cmd, ia, pid);
+#endif
+ }
+
+ if (cmd == RTM_DELADDR)
+ free(ia);
+}
+
+void
+ipv4_free(struct interface *ifp)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ia;
+
+ if (ifp == NULL || (state = IPV4_STATE(ifp)) == NULL)
+ return;
+
+ while ((ia = TAILQ_FIRST(&state->addrs))) {
+ TAILQ_REMOVE(&state->addrs, ia, next);
+ free(ia);
+ }
+ free(state);
+}
diff --git a/src/ipv4.h b/src/ipv4.h
new file mode 100644
index 000000000000..6c0ac8e889bc
--- /dev/null
+++ b/src/ipv4.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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"
+
+/* Prefer our macro */
+#ifdef HTONL
+#undef HTONL
+#endif
+
+#ifndef BYTE_ORDER
+#define BIG_ENDIAN 1234
+#define LITTLE_ENDIAN 4321
+#if defined(_BIG_ENDIAN)
+#define BYTE_ORDER BIG_ENDIAN
+#elif defined(_LITTLE_ENDIAN)
+#define BYTE_ORDER LITTLE_ENDIAN
+#else
+#error Endian unknown
+#endif
+#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))
+#endif /* BYTE_ORDER */
+
+#ifdef __sun
+ /* Solaris lacks these defines.
+ * While it supports DaD, to seems to only expose IFF_DUPLICATE
+ * so we have no way of knowing if it's tentative or not.
+ * I don't even know if Solaris has any special treatment for tentative. */
+# define IN_IFF_TENTATIVE 0x01
+# define IN_IFF_DUPLICATED 0x02
+# define IN_IFF_DETACHED 0x00
+#endif
+
+#ifdef IN_IFF_TENTATIVE
+#define IN_IFF_NOTUSEABLE \
+ (IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED)
+#endif
+
+#define IN_ARE_ADDR_EQUAL(a, b) ((a)->s_addr == (b)->s_addr)
+#define IN_IS_ADDR_UNSPECIFIED(a) ((a)->s_addr == INADDR_ANY)
+
+#ifdef __linux__
+#define IP_LIFETIME
+#endif
+
+struct ipv4_addr {
+ TAILQ_ENTRY(ipv4_addr) next;
+ struct in_addr addr;
+ struct in_addr mask;
+ struct in_addr brd;
+ struct interface *iface;
+ int addr_flags;
+ unsigned int flags;
+#ifdef IP_LIFETIME
+ uint32_t vltime;
+ uint32_t pltime;
+#endif
+ char saddr[INET_ADDRSTRLEN + 3];
+#ifdef ALIAS_ADDR
+ char alias[IF_NAMESIZE];
+#endif
+};
+TAILQ_HEAD(ipv4_addrhead, ipv4_addr);
+
+#define IPV4_AF_STALE (1U << 0)
+#define IPV4_AF_NEW (1U << 1)
+
+#define IPV4_ADDR_EQ(a1, a2) ((a1) && (a1)->addr.s_addr == (a2)->addr.s_addr)
+#define IPV4_MASK1_EQ(a1, a2) ((a1) && (a1)->mask.s_addr == (a2)->mask.s_addr)
+#define IPV4_MASK_EQ(a1, a2) (IPV4_ADDR_EQ(a1, a2) && IPV4_MASK1_EQ(a1, a2))
+#define IPV4_BRD1_EQ(a1, a2) ((a1) && (a1)->brd.s_addr == (a2)->brd.s_addr)
+#define IPV4_BRD_EQ(a1, a2) (IPV4_MASK_EQ(a1, a2) && IPV4_BRD1_EQ(a1, a2))
+
+struct ipv4_state {
+ struct ipv4_addrhead addrs;
+};
+
+#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_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 *);
+
+bool inet_getroutes(struct dhcpcd_ctx *, rb_tree_t *);
+
+#define STATE_ADDED 0x01
+#define STATE_FAKE 0x02
+#define STATE_EXPIRED 0x04
+
+int ipv4_deladdr(struct ipv4_addr *, int);
+struct ipv4_addr *ipv4_addaddr(struct interface *,
+ const struct in_addr *, const struct in_addr *, const struct in_addr *,
+ uint32_t, uint32_t);
+struct ipv4_addr *ipv4_applyaddr(void *);
+
+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 *);
+struct ipv4_addr *ipv4_findmaskaddr(struct dhcpcd_ctx *,
+ const struct in_addr *);
+struct ipv4_addr *ipv4_findmaskbrd(struct dhcpcd_ctx *,
+ const struct in_addr *);
+void ipv4_markaddrsstale(struct interface *);
+void ipv4_deletestaleaddrs(struct interface *);
+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, pid_t);
+
+void ipv4_free(struct interface *);
+#endif /* INET */
+
+#endif /* IPV4_H */
diff --git a/src/ipv4ll.c b/src/ipv4ll.c
new file mode 100644
index 000000000000..faaad70f6751
--- /dev/null
+++ b/src/ipv4ll.c
@@ -0,0 +1,554 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE IPV4LL
+#include "config.h"
+#include "arp.h"
+#include "common.h"
+#include "dhcp.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "logerr.h"
+#include "sa.h"
+#include "script.h"
+
+static const struct in_addr inaddr_llmask = {
+ .s_addr = HTONL(LINKLOCAL_MASK)
+};
+static const struct in_addr inaddr_llbcast = {
+ .s_addr = HTONL(LINKLOCAL_BCAST)
+};
+
+static void
+ipv4ll_pickaddr(struct interface *ifp)
+{
+ struct in_addr addr = { .s_addr = 0 };
+ struct ipv4ll_state *state;
+
+ state = IPV4LL_STATE(ifp);
+ setstate(state->randomstate);
+
+ do {
+ long r;
+
+again:
+ /* 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 arc4random. */
+ /* coverity[dont_call] */
+ r = random();
+ addr.s_addr = ntohl(LINKLOCAL_ADDR |
+ ((uint32_t)(r % 0xFD00) + 0x0100));
+
+ /* No point using a failed address */
+ if (IN_ARE_ADDR_EQUAL(&addr, &state->pickedaddr))
+ goto again;
+ /* Ensure we don't have the address on another interface */
+ } while (ipv4_findaddr(ifp->ctx, &addr) != NULL);
+
+ /* Restore the original random state */
+ setstate(ifp->ctx->randomstate);
+ state->pickedaddr = addr;
+}
+
+int
+ipv4ll_subnetroute(rb_tree_t *routes, struct interface *ifp)
+{
+ struct ipv4ll_state *state;
+ struct rt *rt;
+ struct in_addr in;
+
+ assert(ifp != NULL);
+ if ((state = IPV4LL_STATE(ifp)) == NULL ||
+ state->addr == NULL)
+ return 0;
+
+ if ((rt = rt_new(ifp)) == NULL)
+ return -1;
+
+ in.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr;
+ sa_in_init(&rt->rt_dest, &in);
+ in.s_addr = state->addr->mask.s_addr;
+ sa_in_init(&rt->rt_netmask, &in);
+ in.s_addr = INADDR_ANY;
+ sa_in_init(&rt->rt_gateway, &in);
+ sa_in_init(&rt->rt_ifa, &state->addr->addr);
+ rt->rt_dflags |= RTDF_IPV4LL;
+ return rt_proto_add(routes, rt) ? 1 : 0;
+}
+
+int
+ipv4ll_defaultroute(rb_tree_t *routes, struct interface *ifp)
+{
+ struct ipv4ll_state *state;
+ struct rt *rt;
+ struct in_addr in;
+
+ assert(ifp != NULL);
+ if ((state = IPV4LL_STATE(ifp)) == NULL ||
+ state->addr == NULL)
+ return 0;
+
+ if ((rt = rt_new(ifp)) == NULL)
+ return -1;
+
+ in.s_addr = INADDR_ANY;
+ sa_in_init(&rt->rt_dest, &in);
+ sa_in_init(&rt->rt_netmask, &in);
+ sa_in_init(&rt->rt_gateway, &in);
+ sa_in_init(&rt->rt_ifa, &state->addr->addr);
+ rt->rt_dflags |= RTDF_IPV4LL;
+#ifdef HAVE_ROUTE_METRIC
+ rt->rt_metric += RTMETRIC_IPV4LL;
+#endif
+ return rt_proto_add(routes, rt) ? 1 : 0;
+}
+
+ssize_t
+ipv4ll_env(FILE *fp, 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 || state->addr == NULL)
+ return 0;
+
+ /* Emulate a DHCP environment */
+ if (efprintf(fp, "%s%sip_address=%s",
+ prefix, pf, inet_ntoa(state->addr->addr)) == -1)
+ return -1;
+ if (efprintf(fp, "%s%ssubnet_mask=%s",
+ prefix, pf, inet_ntoa(state->addr->mask)) == -1)
+ return -1;
+ if (efprintf(fp, "%s%ssubnet_cidr=%d",
+ prefix, pf, inet_ntocidr(state->addr->mask)) == -1)
+ return -1;
+ if (efprintf(fp, "%s%sbroadcast_address=%s",
+ prefix, pf, inet_ntoa(state->addr->brd)) == -1)
+ return -1;
+ netnum.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr;
+ if (efprintf(fp, "%s%snetwork_number=%s",
+ prefix, pf, inet_ntoa(netnum)) == -1)
+ return -1;
+ return 5;
+}
+
+static void
+ipv4ll_announced_arp(struct arp_state *astate)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(astate->iface);
+
+ state->conflicts = 0;
+#ifdef KERNEL_RFC5227
+ arp_free(astate);
+#endif
+}
+
+#ifndef KERNEL_RFC5227
+/* This is the callback by ARP freeing */
+static void
+ipv4ll_free_arp(struct arp_state *astate)
+{
+ struct ipv4ll_state *state;
+
+ state = IPV4LL_STATE(astate->iface);
+ if (state != NULL && state->arp == astate)
+ state->arp = NULL;
+}
+
+/* This is us freeing any ARP state */
+static void
+ipv4ll_freearp(struct interface *ifp)
+{
+ struct ipv4ll_state *state;
+
+ state = IPV4LL_STATE(ifp);
+ if (state == NULL || state->arp == NULL)
+ return;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp);
+ arp_free(state->arp);
+ state->arp = NULL;
+}
+#else
+#define ipv4ll_freearp(ifp)
+#endif
+
+static void
+ipv4ll_not_found(struct interface *ifp)
+{
+ struct ipv4ll_state *state;
+ struct ipv4_addr *ia;
+ struct arp_state *astate;
+
+ state = IPV4LL_STATE(ifp);
+ ia = ipv4_iffindaddr(ifp, &state->pickedaddr, &inaddr_llmask);
+#ifdef IN_IFF_NOTREADY
+ if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY)
+#endif
+ loginfox("%s: using IPv4LL address %s",
+ ifp->name, inet_ntoa(state->pickedaddr));
+ if (!(ifp->options->options & DHCPCD_CONFIGURE))
+ goto run;
+ if (ia == NULL) {
+ if (ifp->ctx->options & DHCPCD_TEST)
+ goto test;
+ ia = ipv4_addaddr(ifp, &state->pickedaddr,
+ &inaddr_llmask, &inaddr_llbcast,
+ DHCP_INFINITE_LIFETIME, DHCP_INFINITE_LIFETIME);
+ }
+ if (ia == NULL)
+ return;
+#ifdef IN_IFF_NOTREADY
+ if (ia->addr_flags & IN_IFF_NOTREADY)
+ return;
+ logdebugx("%s: DAD completed for %s", ifp->name, ia->saddr);
+#endif
+
+test:
+ state->addr = ia;
+ state->down = false;
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ script_runreason(ifp, "TEST");
+ eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
+ rt_build(ifp->ctx, AF_INET);
+run:
+ astate = arp_announceaddr(ifp->ctx, &ia->addr);
+ if (astate != NULL)
+ astate->announced_cb = ipv4ll_announced_arp;
+ script_runreason(ifp, "IPV4LL");
+ dhcpcd_daemonise(ifp->ctx);
+}
+
+static void
+ipv4ll_found(struct interface *ifp)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(ifp);
+
+ ipv4ll_freearp(ifp);
+ if (++state->conflicts == MAX_CONFLICTS)
+ logerrx("%s: failed to acquire an IPv4LL address",
+ ifp->name);
+ ipv4ll_pickaddr(ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ state->conflicts >= MAX_CONFLICTS ?
+ RATE_LIMIT_INTERVAL : PROBE_WAIT,
+ ipv4ll_start, ifp);
+}
+
+static void
+ipv4ll_defend_failed(struct interface *ifp)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(ifp);
+
+ ipv4ll_freearp(ifp);
+ if (ifp->options->options & DHCPCD_CONFIGURE)
+ ipv4_deladdr(state->addr, 1);
+ state->addr = NULL;
+ rt_build(ifp->ctx, AF_INET);
+ script_runreason(ifp, "IPV4LL");
+ ipv4ll_pickaddr(ifp);
+ ipv4ll_start(ifp);
+}
+
+#ifndef KERNEL_RFC5227
+static void
+ipv4ll_not_found_arp(struct arp_state *astate)
+{
+
+ ipv4ll_not_found(astate->iface);
+}
+
+static void
+ipv4ll_found_arp(struct arp_state *astate, __unused const struct arp_msg *amsg)
+{
+
+ ipv4ll_found(astate->iface);
+}
+
+static void
+ipv4ll_defend_failed_arp(struct arp_state *astate)
+{
+
+ ipv4ll_defend_failed(astate->iface);
+}
+#endif
+
+void
+ipv4ll_start(void *arg)
+{
+ struct interface *ifp = arg;
+ struct ipv4ll_state *state;
+ struct ipv4_addr *ia;
+ bool repick;
+#ifndef KERNEL_RFC5227
+ struct arp_state *astate;
+#endif
+
+ if ((state = IPV4LL_STATE(ifp)) == NULL) {
+ ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state));
+ if ((state = IPV4LL_STATE(ifp)) == NULL) {
+ logerr(__func__);
+ 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->seeded) {
+ 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));
+ /* coverity[dont_call] */
+ 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);
+ state->seeded = true;
+ }
+
+ /* Find the previosuly used address. */
+ if (state->pickedaddr.s_addr != INADDR_ANY)
+ ia = ipv4_iffindaddr(ifp, &state->pickedaddr, NULL);
+ else
+ ia = NULL;
+
+ /* Find an existing IPv4LL address and ensure we can work with it. */
+ if (ia == NULL)
+ ia = ipv4_iffindlladdr(ifp);
+
+ repick = false;
+#ifdef IN_IFF_DUPLICATED
+ if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) {
+ state->pickedaddr = ia->addr; /* So it's not picked again. */
+ repick = true;
+ if (ifp->options->options & DHCPCD_CONFIGURE)
+ ipv4_deladdr(ia, 0);
+ ia = NULL;
+ }
+#endif
+
+ state->addr = ia;
+ state->down = true;
+ if (ia != NULL) {
+ state->pickedaddr = ia->addr;
+#ifdef IN_IFF_TENTATIVE
+ if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) {
+ loginfox("%s: waiting for DAD to complete on %s",
+ ifp->name, inet_ntoa(ia->addr));
+ return;
+ }
+#endif
+#ifdef IN_IFF_DUPLICATED
+ loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr);
+#endif
+ } else {
+ loginfox("%s: probing for an IPv4LL address", ifp->name);
+ if (repick || state->pickedaddr.s_addr == INADDR_ANY)
+ ipv4ll_pickaddr(ifp);
+ }
+
+#ifdef KERNEL_RFC5227
+ ipv4ll_not_found(ifp);
+#else
+ ipv4ll_freearp(ifp);
+ state->arp = astate = arp_new(ifp, &state->pickedaddr);
+ if (state->arp == NULL)
+ return;
+
+ astate->found_cb = ipv4ll_found_arp;
+ astate->not_found_cb = ipv4ll_not_found_arp;
+ astate->announced_cb = ipv4ll_announced_arp;
+ astate->defend_failed_cb = ipv4ll_defend_failed_arp;
+ astate->free_cb = ipv4ll_free_arp;
+ arp_probe(astate);
+#endif
+}
+
+void
+ipv4ll_drop(struct interface *ifp)
+{
+ struct ipv4ll_state *state;
+ bool dropped = false;
+ struct ipv4_state *istate;
+
+ assert(ifp != NULL);
+
+ ipv4ll_freearp(ifp);
+
+ if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP)
+ return;
+
+ state = IPV4LL_STATE(ifp);
+ if (state && state->addr != NULL) {
+ if (ifp->options->options & DHCPCD_CONFIGURE)
+ ipv4_deladdr(state->addr, 1);
+ state->addr = NULL;
+ dropped = true;
+ }
+
+ /* 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))) {
+ if (ifp->options->options & DHCPCD_CONFIGURE)
+ ipv4_deladdr(ia, 0);
+ dropped = true;
+ }
+ }
+ }
+
+ if (dropped) {
+ rt_build(ifp->ctx, AF_INET);
+ script_runreason(ifp, "IPV4LL");
+ }
+}
+
+void
+ipv4ll_reset(struct interface *ifp)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(ifp);
+
+ if (state == NULL)
+ return;
+ ipv4ll_freearp(ifp);
+ state->pickedaddr.s_addr = INADDR_ANY;
+ state->seeded = false;
+}
+
+void
+ipv4ll_free(struct interface *ifp)
+{
+
+ assert(ifp != NULL);
+
+ ipv4ll_freearp(ifp);
+ free(IPV4LL_STATE(ifp));
+ ifp->if_data[IF_DATA_IPV4LL] = NULL;
+}
+
+/* This may cause issues in BSD systems, where running as a single dhcpcd
+ * daemon would solve this issue easily. */
+#ifdef HAVE_ROUTE_METRIC
+int
+ipv4ll_recvrt(__unused int cmd, const struct rt *rt)
+{
+ struct dhcpcd_ctx *ctx;
+ struct interface *ifp;
+
+ /* Only interested in default route changes. */
+ if (sa_is_unspecified(&rt->rt_dest))
+ return 0;
+
+ /* If any interface is running IPv4LL, rebuild our routing table. */
+ ctx = rt->rt_ifp->ctx;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (IPV4LL_STATE_RUNNING(ifp)) {
+ rt_build(ctx, AF_INET);
+ break;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+struct ipv4_addr *
+ipv4ll_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid)
+{
+ struct interface *ifp;
+ struct ipv4ll_state *state;
+
+ ifp = ia->iface;
+ state = IPV4LL_STATE(ifp);
+ if (state == NULL)
+ return ia;
+
+ if (cmd == RTM_DELADDR &&
+ state->addr != NULL &&
+ IN_ARE_ADDR_EQUAL(&state->addr->addr, &ia->addr))
+ {
+ loginfox("%s: pid %d deleted IP address %s",
+ ifp->name, pid, ia->saddr);
+ ipv4ll_defend_failed(ifp);
+ return ia;
+ }
+
+#ifdef IN_IFF_DUPLICATED
+ if (cmd != RTM_NEWADDR)
+ return ia;
+ if (!IN_ARE_ADDR_EQUAL(&state->pickedaddr, &ia->addr))
+ return ia;
+ if (!(ia->addr_flags & IN_IFF_NOTUSEABLE))
+ ipv4ll_not_found(ifp);
+ else if (ia->addr_flags & IN_IFF_DUPLICATED) {
+ logerrx("%s: DAD detected %s", ifp->name, ia->saddr);
+ ipv4ll_freearp(ifp);
+ if (ifp->options->options & DHCPCD_CONFIGURE)
+ ipv4_deladdr(ia, 1);
+ state->addr = NULL;
+ rt_build(ifp->ctx, AF_INET);
+ ipv4ll_found(ifp);
+ return NULL;
+ }
+#endif
+
+ return ia;
+}
diff --git a/src/ipv4ll.h b/src/ipv4ll.h
new file mode 100644
index 000000000000..0dcf8572302d
--- /dev/null
+++ b/src/ipv4ll.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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
+
+#define LINKLOCAL_ADDR 0xa9fe0000
+#define LINKLOCAL_MASK IN_CLASSB_NET
+#define LINKLOCAL_BCAST (LINKLOCAL_ADDR | ~LINKLOCAL_MASK)
+
+#ifndef IN_LINKLOCAL
+# define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR)
+#endif
+
+#ifdef IPV4LL
+#include "arp.h"
+
+struct ipv4ll_state {
+ struct in_addr pickedaddr;
+ struct ipv4_addr *addr;
+ char randomstate[128];
+ bool seeded;
+ bool down;
+ size_t conflicts;
+#ifndef KERNEL_RFC5227
+ struct arp_state *arp;
+#endif
+};
+
+#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 && \
+ (IPV4LL_CSTATE((ifp))->addr != NULL))
+
+int ipv4ll_subnetroute(rb_tree_t *, struct interface *);
+int ipv4ll_defaultroute(rb_tree_t *,struct interface *);
+ssize_t ipv4ll_env(FILE *, const char *, const struct interface *);
+void ipv4ll_start(void *);
+void ipv4ll_claimed(void *);
+void ipv4ll_handle_failure(void *);
+struct ipv4_addr *ipv4ll_handleifa(int, struct ipv4_addr *, pid_t pid);
+#ifdef HAVE_ROUTE_METRIC
+int ipv4ll_recvrt(int, const struct rt *);
+#endif
+
+void ipv4ll_reset(struct interface *);
+void ipv4ll_drop(struct interface *);
+void ipv4ll_free(struct interface *);
+#endif
+
+#endif
diff --git a/src/ipv6.c b/src/ipv6.c
new file mode 100644
index 000000000000..04bf7746e080
--- /dev/null
+++ b/src/ipv6.c
@@ -0,0 +1,2389 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <arpa/inet.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include "config.h"
+
+#ifdef HAVE_SYS_BITOPS_H
+#include <sys/bitops.h>
+#else
+#include "compat/bitops.h"
+#endif
+
+#ifdef BSD
+/* Purely for the ND6_IFF_AUTO_LINKLOCAL #define which is solely used
+ * to generate our CAN_ADD_LLADDR #define. */
+# include <netinet6/in6_var.h>
+# include <netinet6/nd6.h>
+#endif
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE ELOOP_IPV6
+#include "common.h"
+#include "if.h"
+#include "dhcpcd.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+#include "sa.h"
+#include "script.h"
+
+#ifdef HAVE_MD5_H
+# ifndef DEPGEN
+# include <md5.h>
+# endif
+#endif
+
+#ifdef SHA2_H
+# include SHA2_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
+
+/* 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
+
+#if defined(HAVE_IN6_ADDR_GEN_MODE_NONE) || defined(ND6_IFF_AUTO_LINKLOCAL) || \
+ defined(IFF_NOLINKLOCAL)
+/* Only add the LL address if we have a carrier, so DaD works. */
+#define CAN_ADD_LLADDR(ifp) \
+ (!((ifp)->options->options & DHCPCD_LINK) || if_is_link_up((ifp)))
+#ifdef __sun
+/* Although we can add our own LL address, we cannot drop it
+ * without unplumbing the if which is a lot of code.
+ * So just keep it for the time being. */
+#define CAN_DROP_LLADDR(ifp) (0)
+#else
+#define CAN_DROP_LLADDR(ifp) (1)
+#endif
+#else
+/* We have no control over the OS adding the LLADDR, so just let it do it
+ * as we cannot force our own view on it. */
+#define CAN_ADD_LLADDR(ifp) (0)
+#define CAN_DROP_LLADDR(ifp) (0)
+#endif
+
+#ifdef IPV6_MANAGETEMPADDR
+static void ipv6_regentempaddr(void *);
+#endif
+
+int
+ipv6_init(struct dhcpcd_ctx *ctx)
+{
+
+ if (ctx->ra_routers != NULL)
+ return 0;
+
+ ctx->ra_routers = malloc(sizeof(*ctx->ra_routers));
+ if (ctx->ra_routers == NULL)
+ return -1;
+ TAILQ_INIT(ctx->ra_routers);
+
+#ifndef __sun
+ ctx->nd_fd = -1;
+#endif
+#ifdef DHCP6
+ ctx->dhcp6_rfd = -1;
+ ctx->dhcp6_wfd = -1;
+#endif
+ return 0;
+}
+
+static ssize_t
+ipv6_readsecret(struct dhcpcd_ctx *ctx)
+{
+ char line[1024];
+ unsigned char *p;
+ size_t len;
+ uint32_t r;
+
+ ctx->secret_len = dhcp_read_hwaddr_aton(ctx, &ctx->secret, SECRET);
+ if (ctx->secret_len != 0)
+ return (ssize_t)ctx->secret_len;
+
+ if (errno != ENOENT)
+ logerr("%s: cannot read secret", __func__);
+
+ /* 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). */
+ if (ctx->secret_len < 64) {
+ if ((ctx->secret = malloc(64)) == NULL) {
+ logerr(__func__);
+ return -1;
+ }
+ ctx->secret_len = 64;
+ }
+ p = ctx->secret;
+ for (len = 0; len < 512 / NBBY; len += sizeof(r)) {
+ r = arc4random();
+ memcpy(p, &r, sizeof(r));
+ p += sizeof(r);
+ }
+
+ hwaddr_ntoa(ctx->secret, ctx->secret_len, line, sizeof(line));
+ len = strlen(line);
+ if (len < sizeof(line) - 2) {
+ line[len++] = '\n';
+ line[len] = '\0';
+ }
+ if (dhcp_writefile(ctx, SECRET, S_IRUSR, line, len) == -1) {
+ logerr("%s: cannot write secret", __func__);
+ ctx->secret_len = 0;
+ return -1;
+ }
+ return (ssize_t)ctx->secret_len;
+}
+
+/* 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 bool
+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 < __arraycount(reslowhigh); i++) {
+ r = &reslowhigh[i];
+ low = be64dec(r->low);
+ high = be64dec(r->high);
+ if (id >= low && id <= high)
+ return true;
+ }
+ return false;
+}
+
+/* RFC7217 */
+static int
+ipv6_makestableprivate1(struct dhcpcd_ctx *ctx,
+ 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,
+ unsigned short vlanid,
+ uint32_t *dad_counter)
+{
+ unsigned char buf[2048], *p, digest[SHA256_DIGEST_LENGTH];
+ size_t len, l;
+ SHA256_CTX sha_ctx;
+
+ if (prefix_len < 0 || prefix_len > 120) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->secret_len == 0) {
+ if (ipv6_readsecret(ctx) == -1)
+ return -1;
+ }
+
+ l = (size_t)(ROUNDUP8(prefix_len) / NBBY);
+ len = l + netiface_len + netid_len + sizeof(*dad_counter) +
+ ctx->secret_len;
+ if (vlanid != 0)
+ len += sizeof(vlanid);
+ 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;
+ /* Don't use a vlanid if not set.
+ * This ensures prior versions have the same unique address. */
+ if (vlanid != 0) {
+ memcpy(p, &vlanid, sizeof(vlanid));
+ p += sizeof(vlanid);
+ }
+ memcpy(p, dad_counter, sizeof(*dad_counter));
+ p += sizeof(*dad_counter);
+ memcpy(p, ctx->secret, ctx->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(&sha_ctx);
+ SHA256_Update(&sha_ctx, buf, len);
+ SHA256_Final(digest, &sha_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(ifp->ctx, addr, prefix, prefix_len,
+ ifp->hwaddr, ifp->hwlen,
+ ifp->ssid, ifp->ssid_len,
+ ifp->vlanid, &dad);
+
+ if (r == 0)
+ *dad_counter = (int)dad;
+ return r;
+}
+
+#ifdef IPV6_AF_TEMPORARY
+static int
+ipv6_maketemporaryaddress(struct in6_addr *addr,
+ const struct in6_addr *prefix, int prefix_len,
+ const struct interface *ifp)
+{
+ struct in6_addr mask;
+ struct interface *ifpn;
+
+ if (ipv6_mask(&mask, prefix_len) == -1)
+ return -1;
+ *addr = *prefix;
+
+again:
+ addr->s6_addr32[2] |= (arc4random() & ~mask.s6_addr32[2]);
+ addr->s6_addr32[3] |= (arc4random() & ~mask.s6_addr32[3]);
+
+ TAILQ_FOREACH(ifpn, ifp->ctx->ifaces, next) {
+ if (ipv6_iffindaddr(ifpn, addr, 0) != NULL)
+ break;
+ }
+ if (ifpn != NULL)
+ goto again;
+ if (ipv6_reserved(addr))
+ goto again;
+ return 0;
+}
+#endif
+
+int
+ipv6_makeaddr(struct in6_addr *addr, struct interface *ifp,
+ const struct in6_addr *prefix, int prefix_len, unsigned int flags)
+{
+ const struct ipv6_addr *ap;
+ int dad;
+
+ if (prefix_len < 0 || prefix_len > 120) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#ifdef IPV6_AF_TEMPORARY
+ if (flags & IPV6_AF_TEMPORARY)
+ return ipv6_maketemporaryaddress(addr, prefix, prefix_len, ifp);
+#else
+ UNUSED(flags);
+#endif
+
+ if (ifp->options->options & DHCPCD_SLAACPRIVATE) {
+ 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;
+}
+
+static int
+ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len)
+{
+ struct in6_addr mask;
+ size_t i;
+
+ if (ipv6_mask(&mask, len) == -1)
+ return -1;
+ *prefix = *addr;
+ for (i = 0; i < sizeof(prefix->s6_addr); i++)
+ prefix->s6_addr[i] &= mask.s6_addr[i];
+ 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 != 0) {
+ /* Coverify false positive.
+ * bytelen cannot be 16 if bitlen is non zero */
+ /* coverity[overrun-local] */
+ 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 < 1 || prefix_len > 128 ||
+ result_len < 1 || result_len > 128)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check that the user_number fits inside result_len less prefix_len */
+ if (result_len < prefix_len ||
+ fls64(user_number) > result_len - prefix_len)
+ {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* If user_number is zero, just copy the prefix into the result. */
+ if (user_number == 0) {
+ *result = *prefix;
+ return 0;
+ }
+
+ /* Shift user_number so it fit's just inside result_len.
+ * Shifting by 0 or sizeof(user_number) is undefined,
+ * so we cater for that. */
+ if (result_len == 128) {
+ user_high = 0;
+ user_low = user_number;
+ } else if (result_len > 64) {
+ if (prefix_len >= 64)
+ user_high = 0;
+ else
+ user_high = user_number >> (result_len - prefix_len);
+ user_low = user_number << (128 - result_len);
+ } else if (result_len == 64) {
+ user_high = user_number;
+ user_low = 0;
+ } else {
+ user_high = user_number << (64 - result_len);
+ user_low = 0;
+ }
+
+ /* 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 *ia;
+ int flags;
+ const char *alias;
+
+ ia = arg;
+#ifdef ALIAS_ADDR
+ alias = ia->alias;
+#else
+ alias = NULL;
+#endif
+ if ((flags = if_addrflags6(ia->iface, &ia->addr, alias)) == -1) {
+ if (errno != EEXIST && errno != EADDRNOTAVAIL)
+ logerr("%s: if_addrflags6", __func__);
+ return;
+ }
+
+ if (!(flags & IN6_IFF_TENTATIVE)) {
+ /* Simulate the kernel announcing the new address. */
+ ipv6_handleifa(ia->iface->ctx, RTM_NEWADDR,
+ ia->iface->ctx->ifaces, ia->iface->name,
+ &ia->addr, ia->prefix_len, flags, 0);
+ } else {
+ /* Still tentative? Check again in a bit. */
+ eloop_timeout_add_msec(ia->iface->ctx->eloop,
+ RETRANS_TIMER / 2, ipv6_checkaddrflags, ia);
+ }
+}
+#endif
+
+static void
+ipv6_deletedaddr(struct ipv6_addr *ia)
+{
+
+#ifdef DHCP6
+#ifdef PRIVSEP
+ if (!(ia->iface->ctx->options & DHCPCD_MANAGER))
+ ps_inet_closedhcp6(ia);
+#elif defined(SMALL)
+ UNUSED(ia);
+#else
+ /* NOREJECT is set if we delegated exactly the prefix to another
+ * address.
+ * This can only be one address, so just clear the flag.
+ * This should ensure the reject route will be restored. */
+ if (ia->delegating_prefix != NULL)
+ ia->delegating_prefix->flags &= ~IPV6_AF_NOREJECT;
+#endif
+#else
+ UNUSED(ia);
+#endif
+}
+
+void
+ipv6_deleteaddr(struct ipv6_addr *ia)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ap;
+
+ loginfox("%s: deleting address %s", ia->iface->name, ia->saddr);
+ if (if_address6(RTM_DELADDR, ia) == -1 &&
+ errno != EADDRNOTAVAIL && errno != ESRCH &&
+ errno != ENXIO && errno != ENODEV)
+ logerr(__func__);
+
+ ipv6_deletedaddr(ia);
+
+ 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;
+ }
+ }
+
+#ifdef ND6_ADVERTISE
+ /* Advertise the address if it exists on another interface. */
+ ipv6nd_advertise(ia);
+#endif
+}
+
+static int
+ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
+{
+ struct interface *ifp;
+ uint32_t pltime, vltime;
+ int loglevel;
+#ifdef ND6_ADVERTISE
+ bool vltime_was_zero = ia->prefix_vltime == 0;
+#endif
+#ifdef __sun
+ struct ipv6_state *state;
+ struct ipv6_addr *ia2;
+
+ /* If we re-add then address on Solaris then the prefix
+ * route will be scrubbed and re-added. Something might
+ * be using it, so let's avoid it. */
+ if (ia->flags & IPV6_AF_DADCOMPLETED) {
+ logdebugx("%s: IP address %s already exists",
+ ia->iface->name, ia->saddr);
+#ifdef ND6_ADVERTISE
+ goto advertise;
+#else
+ return 0;
+#endif
+ }
+#endif
+
+ /* Remember the interface of the address. */
+ ifp = ia->iface;
+
+ if (!(ia->flags & IPV6_AF_DADCOMPLETED) &&
+ ipv6_iffindaddr(ifp, &ia->addr, IN6_IFF_NOTUSEABLE))
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+
+ /* Adjust plftime and vltime based on acquired time */
+ pltime = ia->prefix_pltime;
+ vltime = ia->prefix_vltime;
+
+ if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
+ /* We don't want the kernel to expire the address.
+ * The saved times will be re-applied to the ia
+ * before exiting this function. */
+ ia->prefix_vltime = ia->prefix_pltime = ND6_INFINITE_LIFETIME;
+ }
+
+ if (timespecisset(&ia->acquired) &&
+ (ia->prefix_pltime != ND6_INFINITE_LIFETIME ||
+ ia->prefix_vltime != ND6_INFINITE_LIFETIME))
+ {
+ uint32_t elapsed;
+ struct timespec n;
+
+ if (now == NULL) {
+ clock_gettime(CLOCK_MONOTONIC, &n);
+ now = &n;
+ }
+ elapsed = (uint32_t)eloop_timespec_diff(now, &ia->acquired,
+ NULL);
+ if (ia->prefix_pltime != ND6_INFINITE_LIFETIME) {
+ if (elapsed > ia->prefix_pltime)
+ ia->prefix_pltime = 0;
+ else
+ ia->prefix_pltime -= elapsed;
+ }
+ if (ia->prefix_vltime != ND6_INFINITE_LIFETIME) {
+ if (elapsed > ia->prefix_vltime)
+ ia->prefix_vltime = 0;
+ else
+ ia->prefix_vltime -= elapsed;
+ }
+ }
+
+ loglevel = ia->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG;
+ logmessage(loglevel, "%s: adding %saddress %s", ifp->name,
+#ifdef IPV6_AF_TEMPORARY
+ ia->flags & IPV6_AF_TEMPORARY ? "temporary " : "",
+#else
+ "",
+#endif
+ ia->saddr);
+ if (ia->prefix_pltime == ND6_INFINITE_LIFETIME &&
+ ia->prefix_vltime == ND6_INFINITE_LIFETIME)
+ logdebugx("%s: pltime infinity, vltime infinity",
+ ifp->name);
+ else if (ia->prefix_pltime == ND6_INFINITE_LIFETIME)
+ logdebugx("%s: pltime infinity, vltime %"PRIu32" seconds",
+ ifp->name, ia->prefix_vltime);
+ else if (ia->prefix_vltime == ND6_INFINITE_LIFETIME)
+ logdebugx("%s: pltime %"PRIu32"seconds, vltime infinity",
+ ifp->name, ia->prefix_pltime);
+ else
+ logdebugx("%s: pltime %"PRIu32" seconds, vltime %"PRIu32
+ " seconds",
+ ifp->name, ia->prefix_pltime, ia->prefix_vltime);
+
+ if (if_address6(RTM_NEWADDR, ia) == -1) {
+ logerr(__func__);
+ /* Restore real pltime and vltime */
+ ia->prefix_pltime = pltime;
+ ia->prefix_vltime = vltime;
+ return -1;
+ }
+
+#ifdef IPV6_MANAGETEMPADDR
+ /* RFC4941 Section 3.4 */
+ if (ia->flags & IPV6_AF_TEMPORARY &&
+ ia->prefix_pltime &&
+ ia->prefix_vltime &&
+ ifp->options->options & DHCPCD_SLAACTEMP)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ ia->prefix_pltime - REGEN_ADVANCE,
+ ipv6_regentempaddr, ia);
+#endif
+
+ /* Restore real pltime and vltime */
+ ia->prefix_pltime = pltime;
+ ia->prefix_vltime = vltime;
+
+ ia->flags &= ~IPV6_AF_NEW;
+ ia->flags |= IPV6_AF_ADDED;
+#ifndef SMALL
+ if (ia->delegating_prefix != NULL)
+ ia->flags |= IPV6_AF_DELEGATED;
+#endif
+
+#ifdef IPV6_POLLADDRFLAG
+ eloop_timeout_delete(ifp->ctx->eloop,
+ ipv6_checkaddrflags, ia);
+ if (!(ia->flags & IPV6_AF_DADCOMPLETED)) {
+ eloop_timeout_add_msec(ifp->ctx->eloop,
+ RETRANS_TIMER / 2, ipv6_checkaddrflags, ia);
+ }
+#endif
+
+#ifdef __sun
+ /* Solaris does not announce new addresses which need DaD
+ * so we need to take a copy and add it to our list.
+ * Otherwise aliasing gets confused if we add another
+ * address during DaD. */
+
+ state = IPV6_STATE(ifp);
+ TAILQ_FOREACH(ia2, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr))
+ break;
+ }
+ if (ia2 == NULL) {
+ if ((ia2 = malloc(sizeof(*ia2))) == NULL) {
+ logerr(__func__);
+ return 0; /* Well, we did add the address */
+ }
+ memcpy(ia2, ia, sizeof(*ia2));
+ TAILQ_INSERT_TAIL(&state->addrs, ia2, next);
+ }
+#endif
+
+#ifdef ND6_ADVERTISE
+#ifdef __sun
+advertise:
+#endif
+ /* Re-advertise the preferred address to be safe. */
+ if (!vltime_was_zero)
+ ipv6nd_advertise(ia);
+#endif
+
+ return 0;
+}
+
+#ifdef ALIAS_ADDR
+/* Find the next logical alias address we can use. */
+static int
+ipv6_aliasaddr(struct ipv6_addr *ia, struct ipv6_addr **repl)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *iap;
+ unsigned int lun;
+ char alias[IF_NAMESIZE];
+
+ if (ia->alias[0] != '\0')
+ return 0;
+ state = IPV6_STATE(ia->iface);
+
+ /* First find an existng address.
+ * This can happen when dhcpcd restarts as ND and DHCPv6
+ * maintain their own lists of addresses. */
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (iap->alias[0] != '\0' &&
+ IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr))
+ {
+ strlcpy(ia->alias, iap->alias, sizeof(ia->alias));
+ return 0;
+ }
+ }
+
+ lun = 0;
+find_unit:
+ if (if_makealias(alias, IF_NAMESIZE, ia->iface->name, lun) >=
+ IF_NAMESIZE)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (iap->alias[0] == '\0')
+ continue;
+ if (IN6_IS_ADDR_UNSPECIFIED(&iap->addr)) {
+ /* No address assigned? Lets use it. */
+ strlcpy(ia->alias, iap->alias, sizeof(ia->alias));
+ if (repl)
+ *repl = iap;
+ return 1;
+ }
+ if (strcmp(iap->alias, alias) == 0)
+ break;
+ }
+
+ if (iap != NULL) {
+ if (lun == UINT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ lun++;
+ goto find_unit;
+ }
+
+ strlcpy(ia->alias, alias, sizeof(ia->alias));
+ return 0;
+}
+#endif
+
+int
+ipv6_addaddr(struct ipv6_addr *ia, const struct timespec *now)
+{
+ int r;
+#ifdef ALIAS_ADDR
+ int replaced, blank;
+ struct ipv6_addr *replaced_ia;
+
+ blank = (ia->alias[0] == '\0');
+ if ((replaced = ipv6_aliasaddr(ia, &replaced_ia)) == -1)
+ return -1;
+ if (blank)
+ logdebugx("%s: aliased %s", ia->alias, ia->saddr);
+#endif
+
+ if ((r = ipv6_addaddr1(ia, now)) == 0) {
+#ifdef ALIAS_ADDR
+ if (replaced) {
+ struct ipv6_state *state;
+
+ state = IPV6_STATE(ia->iface);
+ TAILQ_REMOVE(&state->addrs, replaced_ia, next);
+ ipv6_freeaddr(replaced_ia);
+ }
+#endif
+ }
+ return r;
+}
+
+int
+ipv6_findaddrmatch(const struct ipv6_addr *addr, const struct in6_addr *match,
+ unsigned int 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, unsigned int flags)
+{
+ struct ipv6_addr *nap;
+#ifdef DHCP6
+ struct ipv6_addr *dap;
+#endif
+
+ nap = ipv6nd_findaddr(ctx, addr, flags);
+#ifdef DHCP6
+ dap = dhcp6_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;
+#else
+ return nap;
+#endif
+}
+
+int
+ipv6_doaddr(struct ipv6_addr *ia, struct timespec *now)
+{
+
+ /* A delegated prefix is not an address. */
+ if (ia->flags & IPV6_AF_DELEGATEDPFX)
+ return 0;
+
+ if (ia->prefix_vltime == 0) {
+ if (ia->flags & IPV6_AF_ADDED)
+ ipv6_deleteaddr(ia);
+ eloop_q_timeout_delete(ia->iface->ctx->eloop,
+ ELOOP_QUEUE_ALL, NULL, ia);
+ if (ia->flags & IPV6_AF_REQUEST) {
+ ia->flags &= ~IPV6_AF_ADDED;
+ return 0;
+ }
+ return -1;
+ }
+
+ if (ia->flags & IPV6_AF_STALE ||
+ IN6_IS_ADDR_UNSPECIFIED(&ia->addr))
+ return 0;
+
+ if (!timespecisset(now))
+ clock_gettime(CLOCK_MONOTONIC, now);
+ ipv6_addaddr(ia, now);
+ return ia->flags & IPV6_AF_NEW ? 1 : 0;
+}
+
+ssize_t
+ipv6_addaddrs(struct ipv6_addrhead *iaddrs)
+{
+ struct timespec now;
+ struct ipv6_addr *ia, *ian;
+ ssize_t i, r;
+
+ i = 0;
+ timespecclear(&now);
+ TAILQ_FOREACH_SAFE(ia, iaddrs, next, ian) {
+ r = ipv6_doaddr(ia, &now);
+ if (r != 0)
+ i++;
+ if (r == -1) {
+ TAILQ_REMOVE(iaddrs, ia, next);
+ ipv6_freeaddr(ia);
+ }
+ }
+ return i;
+}
+
+void
+ipv6_freeaddr(struct ipv6_addr *ia)
+{
+ struct eloop *eloop = ia->iface->ctx->eloop;
+#ifndef SMALL
+ struct ipv6_addr *iad;
+
+ /* Forget the reference */
+ if (ia->flags & IPV6_AF_DELEGATEDPFX) {
+ TAILQ_FOREACH(iad, &ia->pd_pfxs, pd_next) {
+ iad->delegating_prefix = NULL;
+ }
+ } else if (ia->delegating_prefix != NULL) {
+ TAILQ_REMOVE(&ia->delegating_prefix->pd_pfxs, ia, pd_next);
+ }
+#endif
+
+ if (ia->dhcp6_fd != -1) {
+ close(ia->dhcp6_fd);
+ eloop_event_delete(eloop, ia->dhcp6_fd);
+ }
+
+ eloop_q_timeout_delete(eloop, ELOOP_QUEUE_ALL, NULL, ia);
+ free(ia->na);
+ free(ia);
+}
+
+void
+ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop,
+ const struct interface *ifd)
+{
+ struct ipv6_addr *ap, *apn, *apf;
+ struct timespec now;
+
+#ifdef SMALL
+ UNUSED(ifd);
+#endif
+ timespecclear(&now);
+ TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
+#ifndef SMALL
+ if (ifd != NULL &&
+ (ap->delegating_prefix == NULL ||
+ ap->delegating_prefix->iface != ifd))
+ continue;
+#endif
+ 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))
+ {
+ /* Don't drop link-local addresses. */
+ if (!IN6_IS_ADDR_LINKLOCAL(&ap->addr) ||
+ CAN_DROP_LLADDR(ap->iface))
+ {
+ if (drop == 2)
+ TAILQ_REMOVE(addrs, ap, next);
+ /* 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) {
+ logerr(__func__);
+ return NULL;
+ }
+ TAILQ_INIT(&state->addrs);
+ TAILQ_INIT(&state->ll_callbacks);
+ }
+ return state;
+}
+
+struct ipv6_addr *
+ipv6_anyglobal(struct interface *sifp)
+{
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+ bool forwarding;
+
+ /* BSD forwarding is either on or off.
+ * Linux forwarding is technically the same as it's
+ * configured by the "all" interface.
+ * Per interface only affects IsRouter of NA messages. */
+#if defined(PRIVSEP) && (defined(HAVE_PLEDGE) || defined(__linux__))
+ if (IN_PRIVSEP(sifp->ctx))
+ forwarding = ps_root_ip6forwarding(sifp->ctx, NULL) != 0;
+ else
+#endif
+ forwarding = ip6_forwarding(NULL) != 0;
+
+ TAILQ_FOREACH(ifp, sifp->ctx->ifaces, next) {
+ if (ifp != sifp && !forwarding)
+ continue;
+
+ state = IPV6_STATE(ifp);
+ if (state == NULL)
+ continue;
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (IN6_IS_ADDR_LINKLOCAL(&ia->addr))
+ continue;
+ /* Let's be optimistic.
+ * Any decent OS won't forward or accept traffic
+ * from/to tentative or detached addresses. */
+ if (!(ia->addr_flags & IN6_IFF_DUPLICATED))
+ return ia;
+ }
+ }
+ return NULL;
+}
+
+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 addrflags, pid_t pid)
+{
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+ struct ll_callback *cb;
+ bool anyglobal;
+
+#ifdef __sun
+ struct sockaddr_in6 subnet;
+
+ /* Solaris on-link route is an unspecified address! */
+ if (IN6_IS_ADDR_UNSPECIFIED(addr)) {
+ if (if_getsubnet(ctx, ifname, AF_INET6,
+ &subnet, sizeof(subnet)) == -1)
+ {
+ logerr(__func__);
+ return;
+ }
+ addr = &subnet.sin6_addr;
+ }
+#endif
+
+#if 0
+ char dbuf[INET6_ADDRSTRLEN];
+ const char *dbp;
+
+ dbp = inet_ntop(AF_INET6, &addr->s6_addr,
+ dbuf, INET6_ADDRSTRLEN);
+ loginfox("%s: cmd %d addr %s addrflags %d",
+ ifname, cmd, dbp, addrflags);
+#endif
+
+ if (ifs == NULL)
+ ifs = ctx->ifaces;
+ if (ifs == NULL)
+ return;
+ if ((ifp = if_find(ifs, ifname)) == NULL)
+ return;
+ if ((state = ipv6_getstate(ifp)) == NULL)
+ return;
+ anyglobal = ipv6_anyglobal(ifp) != NULL;
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (IN6_ARE_ADDR_EQUAL(&ia->addr, addr))
+ break;
+ }
+
+ switch (cmd) {
+ case RTM_DELADDR:
+ if (ia != NULL) {
+ TAILQ_REMOVE(&state->addrs, ia, next);
+#ifdef ND6_ADVERTISE
+ /* Advertise the address if it exists on
+ * another interface. */
+ ipv6nd_advertise(ia);
+#endif
+ /* We'll free it at the end of the function. */
+ }
+ break;
+ case RTM_NEWADDR:
+ if (ia == NULL) {
+ ia = ipv6_newaddr(ifp, addr, prefix_len, 0);
+#ifdef ALIAS_ADDR
+ strlcpy(ia->alias, ifname, sizeof(ia->alias));
+#endif
+ if (if_getlifetime6(ia) == -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. */
+ ia->prefix_pltime = 0;
+ ia->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. */
+ ia->acquired = ia->created;
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ }
+ ia->addr_flags = addrflags;
+ ia->flags &= ~IPV6_AF_STALE;
+#ifdef IPV6_MANAGETEMPADDR
+ if (ia->addr_flags & IN6_IFF_TEMPORARY)
+ ia->flags |= IPV6_AF_TEMPORARY;
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) {
+#ifdef IPV6_POLLADDRFLAG
+ if (ia->addr_flags & IN6_IFF_TENTATIVE) {
+ eloop_timeout_add_msec(
+ ia->iface->ctx->eloop,
+ RETRANS_TIMER / 2, ipv6_checkaddrflags, ia);
+ break;
+ }
+#endif
+
+ if (ia->dadcallback)
+ ia->dadcallback(ia);
+
+ if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) &&
+ !(ia->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;
+ }
+
+ if (ia == NULL)
+ return;
+
+ ctx->options &= ~DHCPCD_RTBUILD;
+ ipv6nd_handleifa(cmd, ia, pid);
+#ifdef DHCP6
+ dhcp6_handleifa(cmd, ia, pid);
+#endif
+
+ /* Done with the ia now, so free it. */
+ if (cmd == RTM_DELADDR)
+ ipv6_freeaddr(ia);
+ else if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+
+ /* If we've not already called rt_build via the IPv6ND
+ * or DHCP6 handlers and the existance of any useable
+ * global address on the interface has changed,
+ * call rt_build to add/remove the default route. */
+ if (ifp->active &&
+ ((ifp->options != NULL && ifp->options->options & DHCPCD_IPV6) ||
+ (ifp->options == NULL && ctx->options & DHCPCD_IPV6)) &&
+ !(ctx->options & DHCPCD_RTBUILD) &&
+ (ipv6_anyglobal(ifp) != NULL) != anyglobal)
+ rt_build(ctx, AF_INET6);
+}
+
+int
+ipv6_hasaddr(const struct interface *ifp)
+{
+
+ if (ipv6nd_iffindaddr(ifp, NULL, 0) != NULL)
+ return 1;
+#ifdef DHCP6
+ if (dhcp6_iffindaddr(ifp, NULL, 0) != NULL)
+ return 1;
+#endif
+ return 0;
+}
+
+struct ipv6_addr *
+ipv6_iffindaddr(struct interface *ifp, const struct in6_addr *addr,
+ int revflags)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ap;
+
+ state = IPV6_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (addr == NULL) {
+ if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) &&
+ (!revflags || !(ap->addr_flags & revflags)))
+ return ap;
+ } else {
+ if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr) &&
+ (!revflags || !(ap->addr_flags & revflags)))
+ return ap;
+ }
+ }
+ }
+ return NULL;
+}
+
+static struct ipv6_addr *
+ipv6_iffindmaskaddr(const struct interface *ifp, const struct in6_addr *addr)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ap;
+ struct in6_addr mask;
+
+ state = IPV6_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (ipv6_mask(&mask, ap->prefix_len) == -1)
+ continue;
+ if (IN6_ARE_MASKED_ADDR_EQUAL(&ap->addr, addr, &mask))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+struct ipv6_addr *
+ipv6_findmaskaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr)
+{
+ struct interface *ifp;
+ struct ipv6_addr *ap;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ ap = ipv6_iffindmaskaddr(ifp, addr);
+ if (ap != NULL)
+ 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) {
+ logerr(__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 *ia;
+ struct in6_addr in6;
+
+ memset(&in6, 0, sizeof(in6));
+ in6.s6_addr32[0] = htonl(0xfe800000);
+ ia = ipv6_newaddr(ifp, &in6, 64, 0);
+ if (ia != NULL) {
+ ia->prefix_pltime = ND6_INFINITE_LIFETIME;
+ ia->prefix_vltime = ND6_INFINITE_LIFETIME;
+ }
+ return ia;
+}
+
+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;
+
+ if (!(ifp->options->options & DHCPCD_CONFIGURE))
+ return 0;
+
+ /* Check sanity before malloc */
+ if (!(ifp->options->options & DHCPCD_SLAACPRIVATE)) {
+ switch (ifp->hwtype) {
+ 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;
+
+ dadcounter = 0;
+ if (ifp->options->options & DHCPCD_SLAACPRIVATE) {
+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->hwtype) {
+ 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;
+ }
+
+ logwarnx("%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;
+}
+
+static int
+ipv6_tryaddlinklocal(struct interface *ifp)
+{
+ struct ipv6_addr *ia;
+
+ /* We can't assign a link-locak address to this,
+ * the ppp process has to. */
+ if (ifp->flags & IFF_POINTOPOINT)
+ return 0;
+
+ ia = ipv6_iffindaddr(ifp, NULL, IN6_IFF_DUPLICATED);
+ if (ia != NULL) {
+#ifdef IPV6_POLLADDRFLAG
+ if (ia->addr_flags & IN6_IFF_TENTATIVE) {
+ eloop_timeout_add_msec(
+ ia->iface->ctx->eloop,
+ RETRANS_TIMER / 2, ipv6_checkaddrflags, ia);
+ }
+#endif
+ return 0;
+ }
+ if (!CAN_ADD_LLADDR(ifp))
+ return 0;
+
+ return ipv6_addlinklocal(ifp);
+}
+
+void
+ipv6_setscope(struct sockaddr_in6 *sin, unsigned int ifindex)
+{
+
+#ifdef __KAME__
+ /* KAME based systems want to store the scope inside the sin6_addr
+ * for link local addresses */
+ 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
+}
+
+unsigned int
+ipv6_getscope(const struct sockaddr_in6 *sin)
+{
+#ifdef __KAME__
+ uint16_t scope;
+#endif
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr))
+ return 0;
+#ifdef __KAME__
+ memcpy(&scope, &sin->sin6_addr.s6_addr[2], sizeof(scope));
+ return (unsigned int)ntohs(scope);
+#else
+ return (unsigned int)sin->sin6_scope_id;
+#endif
+}
+
+struct ipv6_addr *
+ipv6_newaddr(struct interface *ifp, const struct in6_addr *addr,
+ uint8_t prefix_len, unsigned int flags)
+{
+ struct ipv6_addr *ia, *iaf;
+ char buf[INET6_ADDRSTRLEN];
+ const char *cbp;
+ bool tempaddr;
+ int addr_flags;
+
+#ifdef IPV6_AF_TEMPORARY
+ tempaddr = flags & IPV6_AF_TEMPORARY;
+#else
+ tempaddr = false;
+#endif
+
+ /* If adding a new DHCP / RA derived address, check current flags
+ * from an existing address. */
+ if (tempaddr)
+ iaf = NULL;
+ else if (flags & IPV6_AF_AUTOCONF)
+ iaf = ipv6nd_iffindprefix(ifp, addr, prefix_len);
+ else
+ iaf = ipv6_iffindaddr(ifp, addr, 0);
+ if (iaf != NULL) {
+ addr_flags = iaf->addr_flags;
+ flags |= IPV6_AF_ADDED;
+ } else
+ addr_flags = IN6_IFF_TENTATIVE;
+
+ ia = calloc(1, sizeof(*ia));
+ if (ia == NULL)
+ goto err;
+
+ ia->iface = ifp;
+ ia->addr_flags = addr_flags;
+ ia->flags = IPV6_AF_NEW | flags;
+ if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+ ia->prefix_len = prefix_len;
+ ia->dhcp6_fd = -1;
+
+#ifndef SMALL
+ TAILQ_INIT(&ia->pd_pfxs);
+#endif
+
+ if (prefix_len == 128)
+ goto makepfx;
+ else if (ia->flags & IPV6_AF_AUTOCONF) {
+ ia->prefix = *addr;
+ if (iaf != NULL)
+ memcpy(&ia->addr, &iaf->addr, sizeof(ia->addr));
+ else {
+ ia->dadcounter = ipv6_makeaddr(&ia->addr, ifp,
+ &ia->prefix,
+ ia->prefix_len,
+ ia->flags);
+ if (ia->dadcounter == -1)
+ goto err;
+ }
+ } else if (ia->flags & IPV6_AF_RAPFX) {
+ ia->prefix = *addr;
+#ifdef __sun
+ ia->addr = *addr;
+ cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
+ goto paddr;
+#else
+ return ia;
+#endif
+ } else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX)) {
+ ia->prefix = *addr;
+ cbp = inet_ntop(AF_INET6, &ia->prefix, buf, sizeof(buf));
+ goto paddr;
+ } else {
+makepfx:
+ ia->addr = *addr;
+ if (ipv6_makeprefix(&ia->prefix,
+ &ia->addr, ia->prefix_len) == -1)
+ goto err;
+ }
+
+ cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
+paddr:
+ if (cbp == NULL)
+ goto err;
+ snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", cbp, ia->prefix_len);
+
+ return ia;
+
+err:
+ logerr(__func__);
+ free(ia);
+ return NULL;
+}
+
+static void
+ipv6_staticdadcallback(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+ int wascompleted;
+
+ wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED);
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+ if (ia->addr_flags & IN6_IFF_DUPLICATED)
+ logwarnx("%s: DAD detected %s", ia->iface->name,
+ ia->saddr);
+ else if (!wascompleted) {
+ logdebugx("%s: IPv6 static DAD completed",
+ ia->iface->name);
+ }
+
+#define FINISHED (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)
+ if (!wascompleted) {
+ struct interface *ifp;
+ struct ipv6_state *state;
+
+ ifp = ia->iface;
+ state = IPV6_STATE(ifp);
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_STATIC &&
+ (ia->flags & FINISHED) != FINISHED)
+ {
+ wascompleted = 1;
+ break;
+ }
+ }
+ if (!wascompleted)
+ script_runreason(ifp, "STATIC6");
+ }
+#undef FINISHED
+}
+
+ssize_t
+ipv6_env(FILE *fp, const char *prefix, const struct interface *ifp)
+{
+ struct ipv6_addr *ia;
+
+ ia = ipv6_iffindaddr(UNCONST(ifp), &ifp->options->req_addr6,
+ IN6_IFF_NOTUSEABLE);
+ if (ia == NULL)
+ return 0;
+ if (efprintf(fp, "%s_ip6_address=%s", prefix, ia->saddr) == -1)
+ return -1;
+ return 1;
+}
+
+int
+ipv6_staticdadcompleted(const struct interface *ifp)
+{
+ const struct ipv6_state *state;
+ const struct ipv6_addr *ia;
+ int n;
+
+ if ((state = IPV6_CSTATE(ifp)) == NULL)
+ return 0;
+ n = 0;
+#define COMPLETED (IPV6_AF_STATIC | IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if ((ia->flags & COMPLETED) == COMPLETED &&
+ !(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+ n++;
+ }
+ return n;
+}
+
+int
+ipv6_startstatic(struct interface *ifp)
+{
+ struct ipv6_addr *ia;
+ int run_script;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&ifp->options->req_addr6))
+ return 0;
+
+ ia = ipv6_iffindaddr(ifp, &ifp->options->req_addr6, 0);
+ if (ia != NULL &&
+ (ia->prefix_len != ifp->options->req_prefix_len ||
+ ia->addr_flags & IN6_IFF_NOTUSEABLE))
+ {
+ ipv6_deleteaddr(ia);
+ ia = NULL;
+ }
+ if (ia == NULL) {
+ struct ipv6_state *state;
+
+ ia = ipv6_newaddr(ifp, &ifp->options->req_addr6,
+ ifp->options->req_prefix_len, 0);
+ if (ia == NULL)
+ return -1;
+ state = IPV6_STATE(ifp);
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ run_script = 0;
+ } else
+ run_script = 1;
+ ia->flags |= IPV6_AF_STATIC | IPV6_AF_ONLINK;
+ ia->prefix_vltime = ND6_INFINITE_LIFETIME;
+ ia->prefix_pltime = ND6_INFINITE_LIFETIME;
+ ia->dadcallback = ipv6_staticdadcallback;
+ ipv6_addaddr(ia, NULL);
+ rt_build(ifp->ctx, AF_INET6);
+ if (run_script)
+ script_runreason(ifp, "STATIC6");
+ return 1;
+}
+
+/* Ensure the interface has a link-local address */
+int
+ipv6_start(struct interface *ifp)
+{
+#ifdef IPV6_POLLADDRFLAG
+ struct ipv6_state *state;
+
+ /* We need to update the address flags. */
+ if ((state = IPV6_STATE(ifp)) != NULL) {
+ struct ipv6_addr *ia;
+ const char *alias;
+ int flags;
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+#ifdef ALIAS_ADDR
+ alias = ia->alias;
+#else
+ alias = NULL;
+#endif
+ flags = if_addrflags6(ia->iface, &ia->addr, alias);
+ if (flags != -1)
+ ia->addr_flags = flags;
+ }
+ }
+#endif
+
+ if (ipv6_tryaddlinklocal(ifp) == -1)
+ return -1;
+
+ 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;
+
+ /* If we got here, we can get rid of any LL callbacks. */
+ while ((cb = TAILQ_FIRST(&state->ll_callbacks))) {
+ TAILQ_REMOVE(&state->ll_callbacks, cb, next);
+ free(cb);
+ }
+
+ ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL);
+ if (drop) {
+ if (ifp->ctx->ra_routers != NULL)
+ rt_build(ifp->ctx, AF_INET6);
+ } else {
+ /* Because we need to cache the addresses we don't control,
+ * we only free the state on when NOT dropping addresses. */
+ free(state);
+ ifp->if_data[IF_DATA_IPV6] = NULL;
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ }
+}
+
+void
+ipv6_ctxfree(struct dhcpcd_ctx *ctx)
+{
+
+ free(ctx->ra_routers);
+ free(ctx->secret);
+}
+
+int
+ipv6_handleifa_addrs(int cmd,
+ struct ipv6_addrhead *addrs, const struct ipv6_addr *addr, pid_t pid)
+{
+ struct ipv6_addr *ia, *ian;
+ uint8_t found, alldadcompleted;
+
+ alldadcompleted = 1;
+ found = 0;
+ TAILQ_FOREACH_SAFE(ia, addrs, next, ian) {
+ if (!IN6_ARE_ADDR_EQUAL(&addr->addr, &ia->addr)) {
+ if (ia->flags & IPV6_AF_ADDED &&
+ !(ia->flags & IPV6_AF_DADCOMPLETED))
+ alldadcompleted = 0;
+ continue;
+ }
+ switch (cmd) {
+ case RTM_DELADDR:
+ if (ia->flags & IPV6_AF_ADDED) {
+ logwarnx("%s: pid %d deleted address %s",
+ ia->iface->name, pid, ia->saddr);
+ ia->flags &= ~IPV6_AF_ADDED;
+ }
+ ipv6_deletedaddr(ia);
+ if (ia->flags & IPV6_AF_DELEGATED) {
+ TAILQ_REMOVE(addrs, ia, next);
+ ipv6_freeaddr(ia);
+ }
+ break;
+ case RTM_NEWADDR:
+ ia->addr_flags = addr->addr_flags;
+ /* Safety - ignore tentative announcements */
+ if (ia->addr_flags &
+ (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE))
+ break;
+ if ((ia->flags & IPV6_AF_DADCOMPLETED) == 0) {
+ found++;
+ if (ia->dadcallback)
+ ia->dadcallback(ia);
+ /* We need to set this here in-case the
+ * dadcallback function checks it */
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+ }
+ break;
+ }
+ }
+
+ return alldadcompleted ? found : 0;
+}
+
+#ifdef IPV6_MANAGETEMPADDR
+static void
+ipv6_regen_desync(struct interface *ifp, bool force)
+{
+ struct ipv6_state *state;
+ unsigned int 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 greater than
+ * TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE. */
+ max = TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE;
+ if (state->desync_factor && !force && state->desync_factor < max)
+ return;
+ if (state->desync_factor == 0)
+ state->desync_factor =
+ arc4random_uniform(MIN(MAX_DESYNC_FACTOR, max));
+ max = TEMP_PREFERRED_LIFETIME - state->desync_factor - REGEN_ADVANCE;
+ eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempaddrs, ifp);
+}
+
+/* RFC4941 Section 3.3.7 */
+static void
+ipv6_tempdadcallback(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+
+ if (ia->addr_flags & IN6_IFF_DUPLICATED) {
+ struct ipv6_addr *ia1;
+ struct timespec tv;
+
+ if (++ia->dadcounter == TEMP_IDGEN_RETRIES) {
+ logerrx("%s: too many duplicate temporary addresses",
+ ia->iface->name);
+ return;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ if ((ia1 = ipv6_createtempaddr(ia, &tv)) == NULL)
+ logerr(__func__);
+ 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;
+ struct interface *ifp = ia0->iface;
+ struct ipv6_addr *ia;
+
+ ia = ipv6_newaddr(ifp, &ia0->prefix, ia0->prefix_len,
+ IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY);
+ if (ia == NULL)
+ return NULL;
+
+ ia->dadcallback = ipv6_tempdadcallback;
+ ia->created = ia->acquired = now ? *now : ia0->acquired;
+
+ /* Ensure desync is still valid */
+ ipv6_regen_desync(ifp, false);
+
+ /* RFC4941 Section 3.3.4 */
+ state = IPV6_STATE(ia->iface);
+ ia->prefix_pltime = MIN(ia0->prefix_pltime,
+ TEMP_PREFERRED_LIFETIME - state->desync_factor);
+ ia->prefix_vltime = MIN(ia0->prefix_vltime, TEMP_VALID_LIFETIME);
+ if (ia->prefix_pltime <= REGEN_ADVANCE ||
+ ia->prefix_pltime > ia0->prefix_vltime)
+ {
+ errno = EINVAL;
+ free(ia);
+ return NULL;
+ }
+
+ TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+ return ia;
+}
+
+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))
+ {
+ unsigned int 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, false);
+
+ /* RFC4941 Section 3.3.2
+ * Extend temporary times, but ensure that they
+ * never last beyond the system limit. */
+ ext = (unsigned int)ia->acquired.tv_sec
+ + ia->prefix_pltime;
+ max = (unsigned int)(ap->created.tv_sec +
+ TEMP_PREFERRED_LIFETIME -
+ 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 = (unsigned int)ia->acquired.tv_sec +
+ ia->prefix_vltime;
+ max = (unsigned int)(ap->created.tv_sec +
+ TEMP_VALID_LIFETIME);
+ 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_regentempaddr0(struct ipv6_addr *ia, struct timespec *tv)
+{
+ struct ipv6_addr *ia1;
+
+ logdebugx("%s: regen temp addr %s", ia->iface->name, ia->saddr);
+ ia1 = ipv6_createtempaddr(ia, tv);
+ if (ia1)
+ ipv6_addaddr(ia1, tv);
+ else
+ logerr(__func__);
+}
+
+static void
+ipv6_regentempaddr(void *arg)
+{
+ struct timespec tv;
+
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+ ipv6_regentempaddr0(arg, &tv);
+}
+
+void
+ipv6_regentempaddrs(void *arg)
+{
+ struct interface *ifp = arg;
+ struct timespec tv;
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+
+ state = IPV6_STATE(ifp);
+ if (state == NULL)
+ return;
+
+ ipv6_regen_desync(ifp, true);
+
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+
+ /* Mark addresses for regen so we don't infinite loop. */
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_TEMPORARY &&
+ ia->flags & IPV6_AF_ADDED &&
+ !(ia->flags & IPV6_AF_STALE))
+ ia->flags |= IPV6_AF_REGEN;
+ else
+ ia->flags &= ~IPV6_AF_REGEN;
+ }
+
+ /* Now regen temp addrs */
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (ia->flags & IPV6_AF_REGEN) {
+ ipv6_regentempaddr0(ia, &tv);
+ ia->flags &= ~IPV6_AF_REGEN;
+ }
+ }
+}
+#endif /* IPV6_MANAGETEMPADDR */
+
+void
+ipv6_markaddrsstale(struct interface *ifp, unsigned int flags)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+
+ state = IPV6_STATE(ifp);
+ if (state == NULL)
+ return;
+
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if (flags == 0 || ia->flags & flags)
+ ia->flags |= IPV6_AF_STALE;
+ }
+}
+
+void
+ipv6_deletestaleaddrs(struct interface *ifp)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *ia, *ia1;
+
+ state = IPV6_STATE(ifp);
+ if (state == NULL)
+ return;
+
+ TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) {
+ if (ia->flags & IPV6_AF_STALE)
+ ipv6_handleifa(ifp->ctx, RTM_DELADDR,
+ ifp->ctx->ifaces, ifp->name,
+ &ia->addr, ia->prefix_len, 0, getpid());
+ }
+}
+
+
+static struct rt *
+inet6_makeroute(struct interface *ifp, const struct ra *rap)
+{
+ struct rt *rt;
+
+ if ((rt = rt_new(ifp)) == NULL)
+ return NULL;
+
+#ifdef HAVE_ROUTE_METRIC
+ rt->rt_metric = ifp->metric;
+#endif
+ if (rap != NULL)
+ rt->rt_mtu = rap->mtu;
+ return rt;
+}
+
+static struct rt *
+inet6_makeprefix(struct interface *ifp, const struct ra *rap,
+ const struct ipv6_addr *addr)
+{
+ struct rt *rt;
+ struct in6_addr netmask;
+
+ 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. */
+ if (addr->prefix_len == 128 || addr->prefix_vltime == 0)
+ return NULL;
+
+ /* Don't install a reject route when not creating bigger prefixes. */
+ if (addr->flags & IPV6_AF_NOREJECT)
+ return NULL;
+
+ /* This address is the delegated prefix, so add a reject route for
+ * it via the loopback interface. */
+ if (addr->flags & IPV6_AF_DELEGATEDPFX) {
+ struct interface *lo0;
+
+ TAILQ_FOREACH(lo0, ifp->ctx->ifaces, next) {
+ if (lo0->flags & IFF_LOOPBACK)
+ break;
+ }
+ if (lo0 == NULL)
+ logwarnx("cannot find a loopback interface "
+ "to reject via");
+ else
+ ifp = lo0;
+ }
+
+ if ((rt = inet6_makeroute(ifp, rap)) == NULL)
+ return NULL;
+
+ sa_in6_init(&rt->rt_dest, &addr->prefix);
+ ipv6_mask(&netmask, addr->prefix_len);
+ sa_in6_init(&rt->rt_netmask, &netmask);
+ if (addr->flags & IPV6_AF_DELEGATEDPFX) {
+ rt->rt_flags |= RTF_REJECT;
+ /* Linux does not like a gateway for a reject route. */
+#ifndef __linux__
+ sa_in6_init(&rt->rt_gateway, &in6addr_loopback);
+#endif
+ } else if (!(addr->flags & IPV6_AF_ONLINK))
+ sa_in6_init(&rt->rt_gateway, &rap->from);
+ else
+ rt->rt_gateway.sa_family = AF_UNSPEC;
+ sa_in6_init(&rt->rt_ifa, &addr->addr);
+ return rt;
+}
+
+static struct rt *
+inet6_makerouter(struct ra *rap)
+{
+ struct rt *rt;
+
+ if ((rt = inet6_makeroute(rap->iface, rap)) == NULL)
+ return NULL;
+ sa_in6_init(&rt->rt_dest, &in6addr_any);
+ sa_in6_init(&rt->rt_netmask, &in6addr_any);
+ sa_in6_init(&rt->rt_gateway, &rap->from);
+ return rt;
+}
+
+#define RT_IS_DEFAULT(rtp) \
+ (IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) && \
+ IN6_ARE_ADDR_EQUAL(&((rtp)->mask), &in6addr_any))
+
+static int
+inet6_staticroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
+{
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *ia;
+ struct rt *rt;
+
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if ((state = IPV6_STATE(ifp)) == NULL)
+ continue;
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ if ((ia->flags & (IPV6_AF_ADDED | IPV6_AF_STATIC)) ==
+ (IPV6_AF_ADDED | IPV6_AF_STATIC))
+ {
+ rt = inet6_makeprefix(ifp, NULL, ia);
+ if (rt)
+ rt_proto_add(routes, rt);
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
+{
+ struct rt *rt;
+ struct ra *rap;
+ const struct ipv6_addr *addr;
+
+ if (ctx->ra_routers == NULL)
+ return 0;
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (rap->expired)
+ continue;
+ TAILQ_FOREACH(addr, &rap->addrs, next) {
+ if (addr->prefix_vltime == 0)
+ continue;
+ rt = inet6_makeprefix(rap->iface, rap, addr);
+ if (rt) {
+ rt->rt_dflags |= RTDF_RA;
+#ifdef HAVE_ROUTE_PREF
+ rt->rt_pref = ipv6nd_rtpref(rap);
+#endif
+ rt_proto_add(routes, rt);
+ }
+ }
+ if (rap->lifetime == 0)
+ continue;
+ if (ipv6_anyglobal(rap->iface) == NULL)
+ continue;
+ rt = inet6_makerouter(rap);
+ if (rt == NULL)
+ continue;
+ rt->rt_dflags |= RTDF_RA;
+#ifdef HAVE_ROUTE_PREF
+ rt->rt_pref = ipv6nd_rtpref(rap);
+#endif
+ rt_proto_add(routes, rt);
+ }
+ return 0;
+}
+
+#ifdef DHCP6
+static int
+inet6_dhcproutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx,
+ enum DH6S dstate)
+{
+ struct interface *ifp;
+ const struct dhcp6_state *d6_state;
+ const struct ipv6_addr *addr;
+ struct rt *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 = inet6_makeprefix(ifp, NULL, addr);
+ if (rt == NULL)
+ continue;
+ rt->rt_dflags |= RTDF_DHCP;
+ rt_proto_add(routes, rt);
+ }
+ }
+ }
+ return 0;
+}
+#endif
+
+bool
+inet6_getroutes(struct dhcpcd_ctx *ctx, rb_tree_t *routes)
+{
+
+ /* Should static take priority? */
+ if (inet6_staticroutes(routes, ctx) == -1)
+ return false;
+
+ /* First add reachable routers and their prefixes */
+ if (inet6_raroutes(routes, ctx) == -1)
+ return false;
+
+#ifdef DHCP6
+ /* 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. */
+ if (inet6_dhcproutes(routes, ctx, DH6S_BOUND) == -1)
+ return false;
+ if (inet6_dhcproutes(routes, ctx, DH6S_DELEGATED) == -1)
+ return false;
+#endif
+
+ return true;
+}
diff --git a/src/ipv6.h b/src/ipv6.h
new file mode 100644
index 000000000000..1fe1d5c224c7
--- /dev/null
+++ b/src/ipv6.h
@@ -0,0 +1,316 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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>
+
+#include "config.h"
+#include "if.h"
+
+#ifndef __linux__
+# if !defined(__QNX__) && !defined(__sun)
+# include <sys/endian.h>
+# endif
+# include <net/if.h>
+# ifndef __sun
+# include <netinet6/in6_var.h>
+# endif
+#endif
+
+#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
+
+/* RFC7217 constants */
+#define IDGEN_RETRIES 3
+#define IDGEN_DELAY 1 /* second */
+
+/* Interface identifier length. Prefix + this == 128 for autoconf */
+#define ipv6_ifidlen(ifp) 64
+#define IA6_CANAUTOCONF(ia) \
+ ((ia)->prefix_len + ipv6_ifidlen((ia)->iface) == 128)
+
+#ifndef IN6_ARE_MASKED_ADDR_EQUAL
+#define IN6_ARE_MASKED_ADDR_EQUAL(d, a, m) ( \
+ (((d)->s6_addr32[0] ^ (a)->s6_addr32[0]) & (m)->s6_addr32[0]) == 0 && \
+ (((d)->s6_addr32[1] ^ (a)->s6_addr32[1]) & (m)->s6_addr32[1]) == 0 && \
+ (((d)->s6_addr32[2] ^ (a)->s6_addr32[2]) & (m)->s6_addr32[2]) == 0 && \
+ (((d)->s6_addr32[3] ^ (a)->s6_addr32[3]) & (m)->s6_addr32[3]) == 0 )
+#endif
+
+#ifndef IN6ADDR_LINKLOCAL_ALLNODES_INIT
+#define IN6ADDR_LINKLOCAL_ALLNODES_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}}
+#endif
+#ifndef IN6ADDR_LINKLOCAL_ALLROUTERS_INIT
+#define IN6ADDR_LINKLOCAL_ALLROUTERS_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}}
+#endif
+
+/*
+ * 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(__DragonFly_version) && __DragonFly_version >= 500704) || \
+ (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 699002000)
+# undef IPV6_POLLADDRFLAG
+#endif
+
+/* Of course OpenBSD has their own special name. */
+#if !defined(IN6_IFF_TEMPORARY) && defined(IN6_IFF_PRIVACY)
+#define IN6_IFF_TEMPORARY IN6_IFF_PRIVACY
+#endif
+
+#ifdef __sun
+ /* Solaris lacks these defines.
+ * While it supports DaD, to seems to only expose IFF_DUPLICATE
+ * so we have no way of knowing if it's tentative or not.
+ * I don't even know if Solaris has any special treatment for tentative. */
+# define IN6_IFF_TENTATIVE 0x02
+# define IN6_IFF_DUPLICATED 0x04
+# define IN6_IFF_DETACHED 0x00
+#endif
+
+#define IN6_IFF_NOTUSEABLE \
+ (IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED)
+
+/*
+ * If dhcpcd handles RA processing instead of the kernel, the kernel needs
+ * to either allow userland to set temporary addresses or mark an address
+ * for the kernel to manage temporary addresses from.
+ * If the kernel allows the former, a global #define is needed, otherwise
+ * the address marking will be handled in the platform specific address handler.
+ *
+ * Some BSDs do not allow userland to set temporary addresses.
+ * Linux-3.18 allows the marking of addresses from which to manage temp addrs.
+ */
+#if defined(IN6_IFF_TEMPORARY) && !defined(__linux__)
+#define IPV6_MANAGETEMPADDR
+#endif
+
+#ifdef __linux__
+ /* Match Linux defines to BSD */
+# ifdef IFA_F_TEMPORARY
+# define IN6_IFF_TEMPORARY IFA_F_TEMPORARY
+# endif
+# 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
+
+/*
+ * ND6 Advertising is only used for IP address sharing to prefer
+ * the address on a specific interface.
+ * This just fails to work on OpenBSD and causes erroneous duplicate
+ * address messages on BSD's other then DragonFly and NetBSD.
+ */
+#if !defined(SMALL) && \
+ ((defined(__DragonFly_version) && __DragonFly_version >= 500703) || \
+ (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 899002800) || \
+ defined(__linux__) || defined(__sun))
+# define ND6_ADVERTISE
+#endif
+
+#ifdef INET6
+TAILQ_HEAD(ipv6_addrhead, ipv6_addr);
+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;
+ unsigned int flags;
+ char saddr[INET6_ADDRSTRLEN];
+ uint8_t iaid[4];
+ uint16_t ia_type;
+ int dhcp6_fd;
+
+#ifndef SMALL
+ struct ipv6_addr *delegating_prefix;
+ struct ipv6_addrhead pd_pfxs;
+ TAILQ_ENTRY(ipv6_addr) pd_next;
+
+ uint8_t prefix_exclude_len;
+ struct in6_addr prefix_exclude;
+#endif
+
+ void (*dadcallback)(void *);
+ int dadcounter;
+
+ struct nd_neighbor_advert *na;
+ size_t na_len;
+ int na_count;
+
+#ifdef ALIAS_ADDR
+ char alias[IF_NAMESIZE];
+#endif
+};
+
+#define IPV6_AF_ONLINK (1U << 0)
+#define IPV6_AF_NEW (1U << 1)
+#define IPV6_AF_STALE (1U << 2)
+#define IPV6_AF_ADDED (1U << 3)
+#define IPV6_AF_AUTOCONF (1U << 4)
+#define IPV6_AF_DADCOMPLETED (1U << 5)
+#define IPV6_AF_DELEGATED (1U << 6)
+#define IPV6_AF_DELEGATEDPFX (1U << 7)
+#define IPV6_AF_NOREJECT (1U << 8)
+#define IPV6_AF_REQUEST (1U << 9)
+#define IPV6_AF_STATIC (1U << 10)
+#define IPV6_AF_DELEGATEDLOG (1U << 11)
+#define IPV6_AF_RAPFX (1U << 12)
+#define IPV6_AF_EXTENDED (1U << 13)
+#define IPV6_AF_REGEN (1U << 14)
+#define IPV6_AF_ROUTER (1U << 15)
+#ifdef IPV6_MANAGETEMPADDR
+#define IPV6_AF_TEMPORARY (1U << 16)
+#endif
+
+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
+ uint32_t desync_factor;
+#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])
+#define IPV6_STATE_RUNNING(ifp) ipv6_staticdadcompleted((ifp))
+
+
+int ipv6_init(struct dhcpcd_ctx *);
+int ipv6_makestableprivate(struct in6_addr *,
+ const struct in6_addr *, int, const struct interface *, int *);
+int ipv6_makeaddr(struct in6_addr *, struct interface *,
+ const struct in6_addr *, int, unsigned 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 *);
+void ipv6_markaddrsstale(struct interface *, unsigned int);
+void ipv6_deletestaleaddrs(struct interface *);
+int ipv6_addaddr(struct ipv6_addr *, const struct timespec *);
+int ipv6_doaddr(struct ipv6_addr *, struct timespec *);
+ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs);
+void ipv6_deleteaddr(struct ipv6_addr *);
+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, pid_t);
+int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct ipv6_addr *,
+ pid_t);
+struct ipv6_addr *ipv6_iffindaddr(struct interface *,
+ const struct in6_addr *, int);
+int ipv6_hasaddr(const struct interface *);
+struct ipv6_addr *ipv6_anyglobal(struct interface *);
+int ipv6_findaddrmatch(const struct ipv6_addr *, const struct in6_addr *,
+ unsigned int);
+struct ipv6_addr *ipv6_findaddr(struct dhcpcd_ctx *,
+ const struct in6_addr *, unsigned int);
+struct ipv6_addr *ipv6_findmaskaddr(struct dhcpcd_ctx *,
+ const struct in6_addr *);
+#define ipv6_linklocal(ifp) ipv6_iffindaddr((ifp), NULL, IN6_IFF_NOTUSEABLE)
+int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *);
+void ipv6_setscope(struct sockaddr_in6 *, unsigned int);
+unsigned int ipv6_getscope(const struct sockaddr_in6 *);
+struct ipv6_addr *ipv6_newaddr(struct interface *, const struct in6_addr *,
+ uint8_t, unsigned int);
+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
+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 *);
+void ipv6_regentempaddrs(void *);
+#endif
+
+int ipv6_start(struct interface *);
+int ipv6_staticdadcompleted(const struct interface *);
+int ipv6_startstatic(struct interface *);
+ssize_t ipv6_env(FILE *, const char *, const struct interface *);
+void ipv6_ctxfree(struct dhcpcd_ctx *);
+bool inet6_getroutes(struct dhcpcd_ctx *, rb_tree_t *);
+#endif /* INET6 */
+
+#endif /* INET6_H */
diff --git a/src/ipv6nd.c b/src/ipv6nd.c
new file mode 100644
index 000000000000..b0174a775de6
--- /dev/null
+++ b/src/ipv6nd.c
@@ -0,0 +1,2075 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - IPv6 ND handling
+ * Copyright (c) 2006-2021 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define ELOOP_QUEUE ELOOP_IPV6ND
+#include "common.h"
+#include "dhcpcd.h"
+#include "dhcp-common.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "if.h"
+#include "ipv6.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+#include "route.h"
+#include "script.h"
+
+/* Debugging Router Solicitations is a lot of spam, so disable it */
+//#define DEBUG_RS
+
+#ifndef ND_RA_FLAG_HOME_AGENT
+#define ND_RA_FLAG_HOME_AGENT 0x20 /* Home Agent flag in RA */
+#endif
+#ifndef ND_RA_FLAG_PROXY
+#define ND_RA_FLAG_PROXY 0x04 /* Proxy */
+#endif
+#ifndef ND_OPT_PI_FLAG_ROUTER
+#define ND_OPT_PI_FLAG_ROUTER 0x20 /* Router flag in PI */
+#endif
+
+#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 */
+};
+__CTASSERT(sizeof(struct nd_opt_rdnss) == 8);
+#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 */
+};
+__CTASSERT(sizeof(struct nd_opt_rdnss) == 8);
+#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
+
+#define EXPIRED_MAX 5 /* Remember 5 expired routers to avoid
+ logspam. */
+
+#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
+
+/* Handy defines */
+#define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra), 0)
+#define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra), 1)
+
+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);
+ }
+}
+
+int
+ipv6nd_open(bool recv)
+{
+ int fd, on;
+ struct icmp6_filter filt;
+
+ fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6);
+ if (fd == -1)
+ return -1;
+
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+
+ /* RFC4861 4.1 */
+ on = 255;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+
+ if (recv) {
+ on = 1;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+
+ on = 1;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+
+#ifdef SO_RERROR
+ on = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_RERROR,
+ &on, sizeof(on)) == -1)
+ goto eexit;
+#endif
+ }
+
+ if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER,
+ &filt, sizeof(filt)) == -1)
+ goto eexit;
+
+ return fd;
+
+eexit:
+ close(fd);
+ return -1;
+}
+
+#ifdef __sun
+int
+ipv6nd_openif(struct interface *ifp)
+{
+ int fd;
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT,
+ .ipv6mr_interface = ifp->index
+ };
+ struct rs_state *state = RS_STATE(ifp);
+ uint_t ifindex = ifp->index;
+
+ if (state->nd_fd != -1)
+ return state->nd_fd;
+
+ fd = ipv6nd_open(true);
+ if (fd == -1)
+ return -1;
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF,
+ &ifindex, sizeof(ifindex)) == -1)
+ {
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) == -1)
+ {
+ close(fd);
+ return -1;
+ }
+
+ state->nd_fd = fd;
+ eloop_event_add(ifp->ctx->eloop, fd, ipv6nd_handledata, ifp);
+ return fd;
+}
+#endif
+
+static int
+ipv6nd_makersprobe(struct interface *ifp)
+{
+ struct rs_state *state;
+ struct nd_router_solicit *rs;
+
+ state = RS_STATE(ifp);
+ free(state->rs);
+ state->rslen = sizeof(*rs);
+ if (ifp->hwlen != 0)
+ state->rslen += (size_t)ROUNDUP8(ifp->hwlen + 2);
+ state->rs = calloc(1, state->rslen);
+ if (state->rs == NULL)
+ return -1;
+ rs = state->rs;
+ rs->nd_rs_type = ND_ROUTER_SOLICIT;
+ //rs->nd_rs_code = 0;
+ //rs->nd_rs_cksum = 0;
+ //rs->nd_rs_reserved = 0;
+
+ if (ifp->hwlen != 0) {
+ struct nd_opt_hdr *nd;
+
+ nd = (struct nd_opt_hdr *)(state->rs + 1);
+ 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 rs_state *state = RS_STATE(ifp);
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT,
+ .sin6_scope_id = ifp->index,
+ };
+ struct iovec iov = { .iov_base = state->rs, .iov_len = state->rslen };
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct msghdr msg = {
+ .msg_name = &dst, .msg_namelen = sizeof(dst),
+ .msg_iov = &iov, .msg_iovlen = 1,
+ .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
+ int s;
+#ifndef __sun
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+#endif
+
+ if (ipv6_linklocal(ifp) == NULL) {
+ logdebugx("%s: delaying Router Solicitation for LL address",
+ ifp->name);
+ ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp);
+ return;
+ }
+
+#ifdef HAVE_SA_LEN
+ dst.sin6_len = sizeof(dst);
+#endif
+
+ /* Set the outbound interface */
+ cm = CMSG_FIRSTHDR(&msg);
+ if (cm == NULL) /* unlikely */
+ return;
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+ logdebugx("%s: sending Router Solicitation", ifp->name);
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ifp->ctx)) {
+ if (ps_inet_sendnd(ifp, &msg) == -1)
+ logerr(__func__);
+ goto sent;
+ }
+#endif
+#ifdef __sun
+ if (state->nd_fd == -1) {
+ if (ipv6nd_openif(ifp) == -1) {
+ logerr(__func__);
+ return;
+ }
+ }
+ s = state->nd_fd;
+#else
+ if (ctx->nd_fd == -1) {
+ ctx->nd_fd = ipv6nd_open(true);
+ if (ctx->nd_fd == -1) {
+ logerr(__func__);
+ return;
+ }
+ eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx);
+ }
+ s = ifp->ctx->nd_fd;
+#endif
+ if (sendmsg(s, &msg, 0) == -1) {
+ logerr(__func__);
+ /* Allow IPv6ND to continue .... at most a few errors
+ * would be logged.
+ * Generally the error is ENOBUFS when struggling to
+ * associate with an access point. */
+ }
+
+#ifdef PRIVSEP
+sent:
+#endif
+ if (state->rsprobes++ < MAX_RTR_SOLICITATIONS)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp);
+ else
+ logwarnx("%s: no IPv6 Routers available", ifp->name);
+}
+
+#ifdef ND6_ADVERTISE
+static void
+ipv6nd_sendadvertisement(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+ struct interface *ifp = ia->iface;
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT,
+ .sin6_scope_id = ifp->index,
+ };
+ struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len };
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct msghdr msg = {
+ .msg_name = &dst, .msg_namelen = sizeof(dst),
+ .msg_iov = &iov, .msg_iovlen = 1,
+ .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
+ const struct rs_state *state = RS_CSTATE(ifp);
+ int s;
+
+ if (state == NULL || !if_is_link_up(ifp))
+ goto freeit;
+
+#ifdef SIN6_LEN
+ dst.sin6_len = sizeof(dst);
+#endif
+
+ /* Set the outbound interface. */
+ cm = CMSG_FIRSTHDR(&msg);
+ assert(cm != NULL);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+ logdebugx("%s: sending NA for %s", ifp->name, ia->saddr);
+
+#ifdef PRIVSEP
+ if (IN_PRIVSEP(ifp->ctx)) {
+ if (ps_inet_sendnd(ifp, &msg) == -1)
+ logerr(__func__);
+ goto sent;
+ }
+#endif
+#ifdef __sun
+ s = state->nd_fd;
+#else
+ s = ctx->nd_fd;
+#endif
+ if (sendmsg(s, &msg, 0) == -1)
+ logerr(__func__);
+
+#ifdef PRIVSEP
+sent:
+#endif
+ if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) {
+ eloop_timeout_add_sec(ctx->eloop,
+ state->retrans / 1000, ipv6nd_sendadvertisement, ia);
+ return;
+ }
+
+freeit:
+ free(ia->na);
+ ia->na = NULL;
+ ia->na_count = 0;
+}
+
+void
+ipv6nd_advertise(struct ipv6_addr *ia)
+{
+ struct dhcpcd_ctx *ctx;
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *iap, *iaf;
+ struct nd_neighbor_advert *na;
+
+ if (IN6_IS_ADDR_MULTICAST(&ia->addr))
+ return;
+
+#ifdef __sun
+ if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX)
+ return;
+#endif
+
+ ctx = ia->iface->ctx;
+ /* Find the most preferred address to advertise. */
+ iaf = NULL;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ state = IPV6_STATE(ifp);
+ if (state == NULL || !if_is_link_up(ifp))
+ continue;
+
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr))
+ continue;
+
+ /* Cancel any current advertisement. */
+ eloop_timeout_delete(ctx->eloop,
+ ipv6nd_sendadvertisement, iap);
+
+ /* Don't advertise what we can't use. */
+ if (iap->prefix_vltime == 0 ||
+ iap->addr_flags & IN6_IFF_NOTUSEABLE)
+ continue;
+
+ if (iaf == NULL ||
+ iaf->iface->metric > iap->iface->metric)
+ iaf = iap;
+ }
+ }
+ if (iaf == NULL)
+ return;
+
+ /* Make the packet. */
+ ifp = iaf->iface;
+ iaf->na_len = sizeof(*na);
+ if (ifp->hwlen != 0)
+ iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2);
+ na = calloc(1, iaf->na_len);
+ if (na == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ na->nd_na_type = ND_NEIGHBOR_ADVERT;
+ na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE;
+#if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE))
+ if (IN_PRIVSEP(ctx)) {
+ if (ps_root_ip6forwarding(ctx, ifp->name) != 0)
+ na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
+ } else
+#endif
+ if (ip6_forwarding(ifp->name) != 0)
+ na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
+ na->nd_na_target = ia->addr;
+
+ if (ifp->hwlen != 0) {
+ struct nd_opt_hdr *opt;
+
+ opt = (struct nd_opt_hdr *)(na + 1);
+ opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
+ opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
+ memcpy(opt + 1, ifp->hwaddr, ifp->hwlen);
+ }
+
+ iaf->na_count = 0;
+ free(iaf->na);
+ iaf->na = na;
+ eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf);
+ ipv6nd_sendadvertisement(iaf);
+}
+#elif !defined(SMALL)
+#warning kernel does not support userland sending ND6 advertisements
+#endif /* ND6_ADVERTISE */
+
+static void
+ipv6nd_expire(void *arg)
+{
+ struct interface *ifp = arg;
+ struct ra *rap;
+
+ if (ifp->ctx->ra_routers == NULL)
+ return;
+
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
+ if (rap->iface == ifp && rap->willexpire)
+ rap->doexpire = true;
+ }
+ ipv6nd_expirera(ifp);
+}
+
+void
+ipv6nd_startexpire(struct interface *ifp)
+{
+ struct ra *rap;
+
+ if (ifp->ctx->ra_routers == NULL)
+ return;
+
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
+ if (rap->iface == ifp)
+ rap->willexpire = true;
+ }
+ eloop_q_timeout_add_sec(ifp->ctx->eloop, ELOOP_IPV6RA_EXPIRE,
+ RTR_CARRIER_EXPIRE, ipv6nd_expire, ifp);
+}
+
+int
+ipv6nd_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:
+ logerrx("%s: impossible RA flag %x", __func__, rap->flags);
+ return RTPREF_INVALID;
+ }
+ /* NOTREACHED */
+}
+
+static void
+ipv6nd_sortrouters(struct dhcpcd_ctx *ctx)
+{
+ struct ra_head sorted_routers = TAILQ_HEAD_INITIALIZER(sorted_routers);
+ struct ra *ra1, *ra2;
+
+ while ((ra1 = TAILQ_FIRST(ctx->ra_routers)) != NULL) {
+ TAILQ_REMOVE(ctx->ra_routers, ra1, next);
+ TAILQ_FOREACH(ra2, &sorted_routers, next) {
+ if (ra1->iface->metric > ra2->iface->metric)
+ continue;
+ if (ra1->expired && !ra2->expired)
+ continue;
+ if (ra1->willexpire && !ra2->willexpire)
+ continue;
+ if (ra1->lifetime == 0 && ra2->lifetime != 0)
+ continue;
+ if (!ra1->isreachable && ra2->reachable)
+ continue;
+ if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2))
+ continue;
+ /* All things being equal, prefer older routers. */
+ /* We don't need to check time, becase newer
+ * routers are always added to the tail and then
+ * sorted. */
+ TAILQ_INSERT_BEFORE(ra2, ra1, next);
+ break;
+ }
+ if (ra2 == NULL)
+ TAILQ_INSERT_TAIL(&sorted_routers, ra1, next);
+ }
+
+ TAILQ_CONCAT(ctx->ra_routers, &sorted_routers, next);
+}
+
+static void
+ipv6nd_applyra(struct interface *ifp)
+{
+ struct ra *rap;
+ struct rs_state *state = RS_STATE(ifp);
+ struct ra defra = {
+ .iface = ifp,
+ .hoplimit = IPV6_DEFHLIM ,
+ .reachable = REACHABLE_TIME,
+ .retrans = RETRANS_TIMER,
+ };
+
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
+ if (rap->iface == ifp)
+ break;
+ }
+
+ /* If we have no Router Advertisement, then set default values. */
+ if (rap == NULL || rap->expired || rap->willexpire)
+ rap = &defra;
+
+ state->retrans = rap->retrans;
+ if (if_applyra(rap) == -1 && errno != ENOENT)
+ logerr(__func__);
+}
+
+/*
+ * Neighbour reachability.
+ *
+ * RFC 4681 6.2.5 says when a node is no longer a router it MUST
+ * send a RA with a zero lifetime.
+ * All OS's I know of set the NA router flag if they are a router
+ * or not and disregard that they are actively advertising or
+ * shutting down. If the interface is disabled, it cant't send a NA at all.
+ *
+ * As such we CANNOT rely on the NA Router flag and MUST use
+ * unreachability or receive a RA with a lifetime of zero to remove
+ * the node as a default router.
+ */
+void
+ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, bool reachable)
+{
+ struct ra *rap, *rapr;
+
+ if (ctx->ra_routers == NULL)
+ return;
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (IN6_ARE_ADDR_EQUAL(&rap->from, addr))
+ break;
+ }
+
+ if (rap == NULL || rap->expired || rap->isreachable == reachable)
+ return;
+
+ rap->isreachable = reachable;
+ loginfox("%s: %s is %s", rap->iface->name, rap->sfrom,
+ reachable ? "reachable again" : "unreachable");
+
+ /* See if we can install a reachable default router. */
+ ipv6nd_sortrouters(ctx);
+ ipv6nd_applyra(rap->iface);
+ rt_build(ctx, AF_INET6);
+
+ if (reachable)
+ return;
+
+ /* If we have no reachable default routers, try and solicit one. */
+ TAILQ_FOREACH(rapr, ctx->ra_routers, next) {
+ if (rap == rapr || rap->iface != rapr->iface)
+ continue;
+ if (rapr->isreachable && !rapr->expired && rapr->lifetime)
+ break;
+ }
+
+ if (rapr == NULL)
+ ipv6nd_startrs(rap->iface);
+}
+
+const struct ipv6_addr *
+ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr,
+ unsigned int flags)
+{
+ struct ra *rap;
+ struct ipv6_addr *ap;
+
+ if (ifp->ctx->ra_routers == NULL)
+ return NULL;
+
+ TAILQ_FOREACH(rap, ifp->ctx->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,
+ unsigned int flags)
+{
+ struct ra *rap;
+ struct ipv6_addr *ap;
+
+ if (ctx->ra_routers == NULL)
+ return NULL;
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if (ipv6_findaddrmatch(ap, addr, flags))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
+static struct ipv6_addr *
+ipv6nd_rapfindprefix(struct ra *rap,
+ const struct in6_addr *pfx, uint8_t pfxlen)
+{
+ struct ipv6_addr *ia;
+
+ TAILQ_FOREACH(ia, &rap->addrs, next) {
+ if (ia->prefix_vltime == 0)
+ continue;
+ if (ia->prefix_len == pfxlen &&
+ IN6_ARE_ADDR_EQUAL(&ia->prefix, pfx))
+ break;
+ }
+ return ia;
+}
+
+struct ipv6_addr *
+ipv6nd_iffindprefix(struct interface *ifp,
+ const struct in6_addr *pfx, uint8_t pfxlen)
+{
+ struct ra *rap;
+ struct ipv6_addr *ia;
+
+ ia = NULL;
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
+ if (rap->iface != ifp)
+ continue;
+ ia = ipv6nd_rapfindprefix(rap, pfx, pfxlen);
+ if (ia != NULL)
+ break;
+ }
+ return ia;
+}
+
+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)
+ TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next);
+ ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL);
+ free(rap->data);
+ free(rap);
+}
+
+static 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;
+
+ ctx = ifp->ctx;
+#ifdef __sun
+ eloop_event_delete(ctx->eloop, state->nd_fd);
+ close(state->nd_fd);
+#endif
+ free(state->rs);
+ free(state);
+ ifp->if_data[IF_DATA_IPV6ND] = NULL;
+ n = 0;
+ TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
+ if (rap->iface == ifp) {
+ ipv6nd_free_ra(rap);
+ n++;
+ }
+ }
+
+#ifndef __sun
+ /* If we don't have any more IPv6 enabled interfaces,
+ * close the global socket and release resources */
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ if (RS_STATE(ifp))
+ break;
+ }
+ if (ifp == NULL) {
+ if (ctx->nd_fd != -1) {
+ eloop_event_delete(ctx->eloop, ctx->nd_fd);
+ close(ctx->nd_fd);
+ ctx->nd_fd = -1;
+ }
+ }
+#endif
+
+ return n;
+}
+
+static void
+ipv6nd_scriptrun(struct ra *rap)
+{
+ int hasdns, hasaddress;
+ 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,
+ IN6_IFF_TENTATIVE))
+ ap->flags |= IPV6_AF_DADCOMPLETED;
+ if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) {
+ logdebugx("%s: waiting for Router Advertisement"
+ " DAD to complete",
+ rap->iface->name);
+ return;
+ }
+ }
+ }
+
+ /* 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");
+ if (hasdns && (hasaddress ||
+ !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))))
+ dhcpcd_daemonise(rap->iface->ctx);
+#if 0
+ else if (options & DHCPCD_DAEMONISE &&
+ !(options & DHCPCD_DAEMONISED) && new_data)
+ logwarnx("%s: did not fork due to an absent"
+ " RDNSS option in the RA",
+ ifp->name);
+#endif
+}
+
+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->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 *ia = arg, *rapap;
+ struct interface *ifp;
+ struct ra *rap;
+ int wascompleted, found;
+ char buf[INET6_ADDRSTRLEN];
+ const char *p;
+ int dadcounter;
+
+ ifp = ia->iface;
+ wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED);
+ ia->flags |= IPV6_AF_DADCOMPLETED;
+ if (ia->addr_flags & IN6_IFF_DUPLICATED) {
+ ia->dadcounter++;
+ logwarnx("%s: DAD detected %s", ifp->name, ia->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 &&
+ IA6_CANAUTOCONF(ia))
+ {
+ unsigned int delay;
+
+ if (ia->dadcounter >= IDGEN_RETRIES) {
+ logerrx("%s: unable to obtain a"
+ " stable private address",
+ ifp->name);
+ goto try_script;
+ }
+ loginfox("%s: deleting address %s",
+ ifp->name, ia->saddr);
+ if (if_address6(RTM_DELADDR, ia) == -1 &&
+ errno != EADDRNOTAVAIL && errno != ENXIO)
+ logerr(__func__);
+ dadcounter = ia->dadcounter;
+ if (ipv6_makestableprivate(&ia->addr,
+ &ia->prefix, ia->prefix_len,
+ ifp, &dadcounter) == -1)
+ {
+ logerr("ipv6_makestableprivate");
+ return;
+ }
+ ia->dadcounter = dadcounter;
+ ia->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
+ ia->flags |= IPV6_AF_NEW;
+ p = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
+ if (p)
+ snprintf(ia->saddr,
+ sizeof(ia->saddr),
+ "%s/%d",
+ p, ia->prefix_len);
+ else
+ ia->saddr[0] = '\0';
+ delay = arc4random_uniform(IDGEN_DELAY * MSEC_PER_SEC);
+ eloop_timeout_add_msec(ifp->ctx->eloop, delay,
+ ipv6nd_addaddr, ia);
+ return;
+ }
+ }
+
+try_script:
+ if (!wascompleted) {
+ TAILQ_FOREACH(rap, ifp->ctx->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 == ia)
+ found = 1;
+ }
+
+ if (wascompleted && found) {
+ logdebugx("%s: Router Advertisement DAD "
+ "completed",
+ rap->iface->name);
+ ipv6nd_scriptrun(rap);
+ }
+ }
+#ifdef ND6_ADVERTISE
+ ipv6nd_advertise(ia);
+#endif
+ }
+}
+
+static struct ipv6_addr *
+ipv6nd_findmarkstale(struct ra *rap, struct ipv6_addr *ia, bool mark)
+{
+ struct dhcpcd_ctx *ctx = ia->iface->ctx;
+ struct ra *rap2;
+ struct ipv6_addr *ia2;
+
+ TAILQ_FOREACH(rap2, ctx->ra_routers, next) {
+ if (rap2 == rap ||
+ rap2->iface != rap->iface ||
+ rap2->expired)
+ continue;
+ TAILQ_FOREACH(ia2, &rap2->addrs, next) {
+ if (!IN6_ARE_ADDR_EQUAL(&ia->prefix, &ia2->prefix))
+ continue;
+ if (!(ia2->flags & IPV6_AF_STALE))
+ return ia2;
+ if (mark)
+ ia2->prefix_pltime = 0;
+ }
+ }
+ return NULL;
+}
+
+#ifndef DHCP6
+/* If DHCPv6 is compiled out, supply a shim to provide an error message
+ * if IPv6RA requests DHCPv6. */
+enum DH6S {
+ DH6S_REQUEST,
+ DH6S_INFORM,
+};
+static int
+dhcp6_start(__unused struct interface *ifp, __unused enum DH6S init_state)
+{
+
+ errno = ENOTSUP;
+ return -1;
+}
+#endif
+
+static void
+ipv6nd_handlera(struct dhcpcd_ctx *ctx,
+ const struct sockaddr_in6 *from, const char *sfrom,
+ struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit)
+{
+ size_t i, olen;
+ struct nd_router_advert *nd_ra;
+ struct nd_opt_hdr ndo;
+ struct nd_opt_prefix_info pi;
+ struct nd_opt_mtu mtu;
+ struct nd_opt_rdnss rdnss;
+ uint8_t *p;
+ struct ra *rap;
+ struct in6_addr pi_prefix;
+ struct ipv6_addr *ia;
+ struct dhcp_opt *dho;
+ bool new_rap, new_data, has_address;
+ uint32_t old_lifetime;
+ int ifmtu;
+ int loglevel;
+ unsigned int flags;
+#ifdef IPV6_MANAGETEMPADDR
+ bool new_ia;
+#endif
+
+ if (ifp == NULL || RS_STATE(ifp) == NULL) {
+#ifdef DEBUG_RS
+ logdebugx("RA for unexpected interface from %s", sfrom);
+#endif
+ return;
+ }
+
+ if (len < sizeof(struct nd_router_advert)) {
+ logerrx("IPv6 RA packet too short from %s", sfrom);
+ return;
+ }
+
+ /* RFC 4861 7.1.2 */
+ if (hoplimit != 255) {
+ logerrx("invalid hoplimit(%d) in RA from %s", hoplimit, sfrom);
+ return;
+ }
+ if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) {
+ logerrx("RA from non local address %s", sfrom);
+ return;
+ }
+
+ if (!(ifp->options->options & DHCPCD_IPV6RS)) {
+#ifdef DEBUG_RS
+ logerrx("%s: unexpected RA from %s", ifp->name, sfrom);
+#endif
+ return;
+ }
+
+ /* We could receive a RA before we sent a RS*/
+ if (ipv6_linklocal(ifp) == NULL) {
+#ifdef DEBUG_RS
+ logdebugx("%s: received RA from %s (no link-local)",
+ ifp->name, sfrom);
+#endif
+ return;
+ }
+
+ if (ipv6_iffindaddr(ifp, &from->sin6_addr, IN6_IFF_TENTATIVE)) {
+ logdebugx("%s: ignoring RA from ourself %s",
+ ifp->name, sfrom);
+ return;
+ }
+
+ /*
+ * Because we preserve RA's and expire them quickly after
+ * carrier up, it's important to reset the kernels notion of
+ * reachable timers back to default values before applying
+ * new RA values.
+ */
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (ifp == rap->iface)
+ break;
+ }
+ if (rap != NULL && rap->willexpire)
+ ipv6nd_applyra(ifp);
+
+ TAILQ_FOREACH(rap, ctx->ra_routers, next) {
+ if (ifp == rap->iface &&
+ IN6_ARE_ADDR_EQUAL(&rap->from, &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;
+ }
+ new_data = true;
+ } else
+ new_data = false;
+ if (rap == NULL) {
+ rap = calloc(1, sizeof(*rap));
+ if (rap == NULL) {
+ logerr(__func__);
+ return;
+ }
+ rap->iface = ifp;
+ rap->from = from->sin6_addr;
+ strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
+ TAILQ_INIT(&rap->addrs);
+ new_rap = true;
+ rap->isreachable = true;
+ } else
+ new_rap = false;
+ if (rap->data_len == 0) {
+ rap->data = malloc(len);
+ if (rap->data == NULL) {
+ logerr(__func__);
+ if (new_rap)
+ free(rap);
+ return;
+ }
+ memcpy(rap->data, icp, len);
+ rap->data_len = len;
+ }
+
+ /* We could change the debug level based on new_data, but some
+ * routers like to decrease the advertised valid and preferred times
+ * in accordance with the own prefix times which would result in too
+ * much needless log spam. */
+ if (rap->willexpire)
+ new_data = true;
+ loglevel = new_rap || rap->willexpire || !rap->isreachable ?
+ LOG_INFO : LOG_DEBUG;
+ logmessage(loglevel, "%s: Router Advertisement from %s",
+ ifp->name, rap->sfrom);
+
+ clock_gettime(CLOCK_MONOTONIC, &rap->acquired);
+ rap->flags = nd_ra->nd_ra_flags_reserved;
+ old_lifetime = rap->lifetime;
+ rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
+ if (!new_rap && rap->lifetime == 0 && old_lifetime != 0)
+ logwarnx("%s: %s: no longer a default router",
+ ifp->name, rap->sfrom);
+ if (nd_ra->nd_ra_curhoplimit != 0)
+ rap->hoplimit = nd_ra->nd_ra_curhoplimit;
+ else
+ rap->hoplimit = IPV6_DEFHLIM;
+ if (nd_ra->nd_ra_reachable != 0) {
+ rap->reachable = ntohl(nd_ra->nd_ra_reachable);
+ if (rap->reachable > MAX_REACHABLE_TIME)
+ rap->reachable = 0;
+ } else
+ rap->reachable = REACHABLE_TIME;
+ if (nd_ra->nd_ra_retransmit != 0)
+ rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
+ else
+ rap->retrans = RETRANS_TIMER;
+ rap->expired = rap->willexpire = rap->doexpire = false;
+ rap->hasdns = false;
+ rap->isreachable = true;
+ has_address = false;
+ rap->mtu = 0;
+
+#ifdef IPV6_AF_TEMPORARY
+ ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY);
+#endif
+ TAILQ_FOREACH(ia, &rap->addrs, next) {
+ ia->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(ndo)) {
+ logerrx("%s: short option", ifp->name);
+ break;
+ }
+ memcpy(&ndo, p, sizeof(ndo));
+ olen = (size_t)ndo.nd_opt_len * 8;
+ if (olen == 0) {
+ logerrx("%s: zero length option", ifp->name);
+ break;
+ }
+ if (olen > len) {
+ logerrx("%s: option length exceeds message",
+ ifp->name);
+ break;
+ }
+
+ if (has_option_mask(ifp->options->rejectmasknd,
+ ndo.nd_opt_type))
+ {
+ for (i = 0, dho = ctx->nd_opts;
+ i < ctx->nd_opts_len;
+ i++, dho++)
+ {
+ if (dho->option == ndo.nd_opt_type)
+ break;
+ }
+ if (dho != NULL)
+ logwarnx("%s: reject RA (option %s) from %s",
+ ifp->name, dho->var, rap->sfrom);
+ else
+ logwarnx("%s: reject RA (option %d) from %s",
+ ifp->name, ndo.nd_opt_type, rap->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:
+ loglevel = new_data ? LOG_ERR : LOG_DEBUG;
+ if (ndo.nd_opt_len != 4) {
+ logmessage(loglevel,
+ "%s: invalid option len for prefix",
+ ifp->name);
+ continue;
+ }
+ memcpy(&pi, p, sizeof(pi));
+ if (pi.nd_opt_pi_prefix_len > 128) {
+ logmessage(loglevel, "%s: invalid prefix len",
+ ifp->name);
+ continue;
+ }
+ /* nd_opt_pi_prefix is not aligned. */
+ memcpy(&pi_prefix, &pi.nd_opt_pi_prefix,
+ sizeof(pi_prefix));
+ if (IN6_IS_ADDR_MULTICAST(&pi_prefix) ||
+ IN6_IS_ADDR_LINKLOCAL(&pi_prefix))
+ {
+ logmessage(loglevel, "%s: invalid prefix in RA",
+ ifp->name);
+ continue;
+ }
+ if (ntohl(pi.nd_opt_pi_preferred_time) >
+ ntohl(pi.nd_opt_pi_valid_time))
+ {
+ logmessage(loglevel, "%s: pltime > vltime",
+ ifp->name);
+ continue;
+ }
+
+ flags = IPV6_AF_RAPFX;
+ /* If no flags are set, that means the prefix is
+ * available via the router. */
+ if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)
+ flags |= IPV6_AF_ONLINK;
+ if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO &&
+ rap->iface->options->options &
+ DHCPCD_IPV6RA_AUTOCONF)
+ flags |= IPV6_AF_AUTOCONF;
+ if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ROUTER)
+ flags |= IPV6_AF_ROUTER;
+
+ ia = ipv6nd_rapfindprefix(rap,
+ &pi_prefix, pi.nd_opt_pi_prefix_len);
+ if (ia == NULL) {
+ ia = ipv6_newaddr(rap->iface,
+ &pi_prefix, pi.nd_opt_pi_prefix_len, flags);
+ if (ia == NULL)
+ break;
+ ia->prefix = pi_prefix;
+ if (flags & IPV6_AF_AUTOCONF)
+ ia->dadcallback = ipv6nd_dadcallback;
+ ia->created = ia->acquired = rap->acquired;
+ TAILQ_INSERT_TAIL(&rap->addrs, ia, 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 (flags & IPV6_AF_AUTOCONF &&
+ ipv6_iffindaddr(ifp, &ia->addr,
+ IN6_IFF_NOTUSEABLE) &&
+ ipv6_settemptime(ia, 0))
+ new_ia = false;
+ else
+ new_ia = true;
+#endif
+ } else {
+#ifdef IPV6_MANAGETEMPADDR
+ new_ia = false;
+#endif
+ ia->flags |= flags;
+ ia->flags &= ~IPV6_AF_STALE;
+ ia->acquired = rap->acquired;
+ }
+ ia->prefix_vltime =
+ ntohl(pi.nd_opt_pi_valid_time);
+ ia->prefix_pltime =
+ ntohl(pi.nd_opt_pi_preferred_time);
+ if (ia->prefix_vltime != 0 &&
+ ia->flags & IPV6_AF_AUTOCONF)
+ has_address = true;
+
+#ifdef IPV6_MANAGETEMPADDR
+ /* RFC4941 Section 3.3.3 */
+ if (ia->flags & IPV6_AF_AUTOCONF &&
+ ia->iface->options->options & DHCPCD_SLAACTEMP &&
+ IA6_CANAUTOCONF(ia))
+ {
+ if (!new_ia) {
+ if (ipv6_settemptime(ia, 1) == NULL)
+ new_ia = true;
+ }
+ if (new_ia && ia->prefix_pltime) {
+ if (ipv6_createtempaddr(ia,
+ &ia->acquired) == NULL)
+ logerr("ipv6_createtempaddr");
+ }
+ }
+#endif
+ break;
+
+ case ND_OPT_MTU:
+ if (len < sizeof(mtu)) {
+ logmessage(loglevel, "%s: short MTU option", ifp->name);
+ break;
+ }
+ memcpy(&mtu, p, sizeof(mtu));
+ mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu);
+ if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) {
+ logmessage(loglevel, "%s: invalid MTU %d",
+ ifp->name, mtu.nd_opt_mtu_mtu);
+ break;
+ }
+ ifmtu = if_getmtu(ifp);
+ if (ifmtu == -1)
+ logerr("if_getmtu");
+ else if (mtu.nd_opt_mtu_mtu > (uint32_t)ifmtu) {
+ logmessage(loglevel, "%s: advertised MTU %d"
+ " is greater than link MTU %d",
+ ifp->name, mtu.nd_opt_mtu_mtu, ifmtu);
+ rap->mtu = (uint32_t)ifmtu;
+ } else
+ rap->mtu = mtu.nd_opt_mtu_mtu;
+ break;
+ case ND_OPT_RDNSS:
+ if (len < sizeof(rdnss)) {
+ logmessage(loglevel, "%s: short RDNSS option", ifp->name);
+ break;
+ }
+ memcpy(&rdnss, p, sizeof(rdnss));
+ if (rdnss.nd_opt_rdnss_lifetime &&
+ rdnss.nd_opt_rdnss_len > 1)
+ rap->hasdns = 1;
+ break;
+ default:
+ continue;
+ }
+ }
+
+ for (i = 0, dho = ctx->nd_opts;
+ i < ctx->nd_opts_len;
+ i++, dho++)
+ {
+ if (has_option_mask(ifp->options->requiremasknd,
+ dho->option))
+ {
+ logwarnx("%s: reject RA (no option %s) from %s",
+ ifp->name, dho->var, rap->sfrom);
+ if (new_rap)
+ ipv6nd_removefreedrop_ra(rap, 0, 0);
+ else
+ ipv6nd_free_ra(rap);
+ return;
+ }
+ }
+
+ TAILQ_FOREACH(ia, &rap->addrs, next) {
+ if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0)
+ continue;
+ if (ipv6nd_findmarkstale(rap, ia, false) != NULL)
+ continue;
+ ipv6nd_findmarkstale(rap, ia, true);
+ logdebugx("%s: %s: became stale", ifp->name, ia->saddr);
+ /* Technically this violates RFC 4861 6.3.4,
+ * but we need a mechanism to tell the kernel to
+ * try and prefer other addresses. */
+ ia->prefix_pltime = 0;
+ }
+
+ if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp))
+ logwarnx("%s: no global addresses for default route",
+ ifp->name);
+
+ if (new_rap)
+ TAILQ_INSERT_TAIL(ctx->ra_routers, rap, next);
+ if (new_data)
+ ipv6nd_sortrouters(ifp->ctx);
+
+ if (ifp->ctx->options & DHCPCD_TEST) {
+ script_runreason(ifp, "TEST");
+ goto handle_flag;
+ }
+
+ if (!(ifp->options->options & DHCPCD_CONFIGURE))
+ goto run;
+
+ ipv6nd_applyra(ifp);
+ ipv6_addaddrs(&rap->addrs);
+#ifdef IPV6_MANAGETEMPADDR
+ ipv6_addtempaddrs(ifp, &rap->acquired);
+#endif
+ rt_build(ifp->ctx, AF_INET6);
+
+run:
+ ipv6nd_scriptrun(rap);
+
+ 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;
+/* Only log a DHCPv6 start error if compiled in or debugging is enabled. */
+#ifdef DHCP6
+#define LOG_DHCP6 logerr
+#else
+#define LOG_DHCP6 logdebug
+#endif
+ if (rap->flags & ND_RA_FLAG_MANAGED) {
+ if (new_data && dhcp6_start(ifp, DH6S_REQUEST) == -1)
+ LOG_DHCP6("dhcp6_start: %s", ifp->name);
+ } else if (rap->flags & ND_RA_FLAG_OTHER) {
+ if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1)
+ LOG_DHCP6("dhcp6_start: %s", ifp->name);
+ } else {
+#ifdef DHCP6
+ if (new_data)
+ logdebugx("%s: No DHCPv6 instruction in RA", ifp->name);
+#endif
+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);
+}
+
+bool
+ipv6nd_hasralifetime(const struct interface *ifp, bool lifetime)
+{
+ const struct ra *rap;
+
+ if (ifp->ctx->ra_routers) {
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next)
+ if (rap->iface == ifp &&
+ !rap->expired &&
+ (!lifetime ||rap->lifetime))
+ return true;
+ }
+ return false;
+}
+
+bool
+ipv6nd_hasradhcp(const struct interface *ifp, bool managed)
+{
+ const struct ra *rap;
+
+ if (ifp->ctx->ra_routers) {
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
+ if (rap->iface == ifp &&
+ !rap->expired && !rap->willexpire &&
+ ((managed && rap->flags & ND_RA_FLAG_MANAGED) ||
+ (!managed && rap->flags & ND_RA_FLAG_OTHER)))
+ return true;
+ }
+ }
+ return false;
+}
+
+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)
+{
+ struct nd_opt_hdr ndo;
+ size_t i;
+ struct dhcp_opt *opt;
+
+ if (od) {
+ *os = sizeof(ndo);
+ if (ol < *os) {
+ errno = EINVAL;
+ return NULL;
+ }
+ memcpy(&ndo, od, sizeof(ndo));
+ i = (size_t)(ndo.nd_opt_len * 8);
+ if (i > ol) {
+ errno = EINVAL;
+ return NULL;
+ }
+ *len = i;
+ *code = ndo.nd_opt_type;
+ }
+
+ for (i = 0, opt = ctx->nd_opts;
+ i < ctx->nd_opts_len; i++, opt++)
+ {
+ if (opt->option == *code) {
+ *oopt = opt;
+ break;
+ }
+ }
+
+ if (od)
+ return od + sizeof(ndo);
+ return NULL;
+}
+
+ssize_t
+ipv6nd_env(FILE *fp, const struct interface *ifp)
+{
+ size_t i, j, n, len, olen;
+ struct ra *rap;
+ char ndprefix[32];
+ struct dhcp_opt *opt;
+ uint8_t *p;
+ struct nd_opt_hdr ndo;
+ struct ipv6_addr *ia;
+ struct timespec now;
+ int pref;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ i = n = 0;
+ TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
+ if (rap->iface != ifp || rap->expired)
+ continue;
+ i++;
+ snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i);
+ if (efprintf(fp, "%s_from=%s", ndprefix, rap->sfrom) == -1)
+ return -1;
+ if (efprintf(fp, "%s_acquired=%lld", ndprefix,
+ (long long)rap->acquired.tv_sec) == -1)
+ return -1;
+ if (efprintf(fp, "%s_now=%lld", ndprefix,
+ (long long)now.tv_sec) == -1)
+ return -1;
+ if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1)
+ return -1;
+ pref = ipv6nd_rtpref(rap);
+ if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix,
+ rap->flags & ND_RA_FLAG_MANAGED ? "M" : "",
+ rap->flags & ND_RA_FLAG_OTHER ? "O" : "",
+ rap->flags & ND_RA_FLAG_HOME_AGENT ? "H" : "",
+ pref == RTPREF_HIGH ? "h" : pref == RTPREF_LOW ? "l" : "",
+ rap->flags & ND_RA_FLAG_PROXY ? "P" : "") == -1)
+ return -1;
+ if (efprintf(fp, "%s_lifetime=%u", ndprefix, rap->lifetime) == -1)
+ return -1;
+
+ /* Zero our indexes */
+ 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 - sizeof(struct nd_router_advert);
+ for (p = rap->data + sizeof(struct nd_router_advert);
+ len >= sizeof(ndo);
+ p += olen, len -= olen)
+ {
+ memcpy(&ndo, p, sizeof(ndo));
+ olen = (size_t)(ndo.nd_opt_len * 8);
+ if (olen > len) {
+ errno = EINVAL;
+ break;
+ }
+ if (has_option_mask(rap->iface->options->nomasknd,
+ ndo.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 == ndo.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 == ndo.nd_opt_type)
+ break;
+ if (j == rap->iface->ctx->nd_opts_len)
+ opt = NULL;
+ }
+ if (opt == NULL)
+ continue;
+ dhcp_envoption(rap->iface->ctx, fp,
+ ndprefix, rap->iface->name,
+ opt, ipv6nd_getoption,
+ p + sizeof(ndo), olen - sizeof(ndo));
+ }
+
+ /* 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
+ !(ia->flags & IPV6_AF_ADDED) ||
+ ia->prefix_vltime == 0)
+ continue;
+ if (efprintf(fp, "%s_addr%zu=%s",
+ ndprefix, ++j, ia->saddr) == -1)
+ return -1;
+ }
+ }
+ return 1;
+}
+
+void
+ipv6nd_handleifa(int cmd, struct ipv6_addr *addr, pid_t pid)
+{
+ struct ra *rap;
+
+ /* IPv6 init may not have happened yet if we are learning
+ * existing addresses when dhcpcd starts. */
+ if (addr->iface->ctx->ra_routers == NULL)
+ return;
+
+ TAILQ_FOREACH(rap, addr->iface->ctx->ra_routers, next) {
+ if (rap->iface != addr->iface)
+ continue;
+ ipv6_handleifa_addrs(cmd, &rap->addrs, addr, pid);
+ }
+}
+
+void
+ipv6nd_expirera(void *arg)
+{
+ struct interface *ifp;
+ struct ra *rap, *ran;
+ struct timespec now;
+ uint32_t elapsed;
+ bool expired, valid;
+ struct ipv6_addr *ia;
+ size_t len, olen;
+ uint8_t *p;
+ struct nd_opt_hdr ndo;
+#if 0
+ struct nd_opt_prefix_info pi;
+#endif
+ struct nd_opt_dnssl dnssl;
+ struct nd_opt_rdnss rdnss;
+ unsigned int next = 0, ltime;
+ size_t nexpired = 0;
+
+ ifp = arg;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ expired = false;
+
+ TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
+ if (rap->iface != ifp || rap->expired)
+ continue;
+ valid = false;
+ if (rap->lifetime) {
+ elapsed = (uint32_t)eloop_timespec_diff(&now,
+ &rap->acquired, NULL);
+ if (elapsed >= rap->lifetime || rap->doexpire) {
+ if (!rap->expired) {
+ logwarnx("%s: %s: router expired",
+ ifp->name, rap->sfrom);
+ rap->lifetime = 0;
+ expired = true;
+ }
+ } else {
+ valid = true;
+ ltime = rap->lifetime - elapsed;
+ if (next == 0 || ltime < next)
+ next = ltime;
+ }
+ }
+
+ /* Not every prefix is tied to an address which
+ * the kernel can expire, so we need to handle it ourself.
+ * Also, some OS don't support address lifetimes (Solaris). */
+ TAILQ_FOREACH(ia, &rap->addrs, next) {
+ if (ia->prefix_vltime == 0)
+ continue;
+ if (ia->prefix_vltime == ND6_INFINITE_LIFETIME &&
+ !rap->doexpire)
+ {
+ valid = true;
+ continue;
+ }
+ elapsed = (uint32_t)eloop_timespec_diff(&now,
+ &ia->acquired, NULL);
+ if (elapsed >= ia->prefix_vltime || rap->doexpire) {
+ if (ia->flags & IPV6_AF_ADDED) {
+ logwarnx("%s: expired %s %s",
+ ia->iface->name,
+ ia->flags & IPV6_AF_AUTOCONF ?
+ "address" : "prefix",
+ ia->saddr);
+ if (if_address6(RTM_DELADDR, ia)== -1 &&
+ errno != EADDRNOTAVAIL &&
+ errno != ENXIO)
+ logerr(__func__);
+ }
+ ia->prefix_vltime = ia->prefix_pltime = 0;
+ ia->flags &=
+ ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
+ expired = true;
+ } else {
+ valid = true;
+ ltime = ia->prefix_vltime - elapsed;
+ if (next == 0 || ltime < next)
+ next = ltime;
+ }
+ }
+
+ /* Work out expiry for ND options */
+ elapsed = (uint32_t)eloop_timespec_diff(&now,
+ &rap->acquired, NULL);
+ len = rap->data_len - sizeof(struct nd_router_advert);
+ for (p = rap->data + sizeof(struct nd_router_advert);
+ len >= sizeof(ndo);
+ p += olen, len -= olen)
+ {
+ memcpy(&ndo, p, sizeof(ndo));
+ olen = (size_t)(ndo.nd_opt_len * 8);
+ if (olen > len) {
+ errno = EINVAL;
+ break;
+ }
+
+ if (has_option_mask(rap->iface->options->nomasknd,
+ ndo.nd_opt_type))
+ continue;
+
+ switch (ndo.nd_opt_type) {
+ /* Prefix info is already checked in the above loop. */
+#if 0
+ case ND_OPT_PREFIX_INFORMATION:
+ if (len < sizeof(pi))
+ break;
+ memcpy(&pi, p, sizeof(pi));
+ ltime = pi.nd_opt_pi_valid_time;
+ break;
+#endif
+ case ND_OPT_DNSSL:
+ if (len < sizeof(dnssl))
+ continue;
+ memcpy(&dnssl, p, sizeof(dnssl));
+ ltime = dnssl.nd_opt_dnssl_lifetime;
+ break;
+ case ND_OPT_RDNSS:
+ if (len < sizeof(rdnss))
+ continue;
+ memcpy(&rdnss, p, sizeof(rdnss));
+ ltime = rdnss.nd_opt_rdnss_lifetime;
+ break;
+ default:
+ continue;
+ }
+
+ if (ltime == 0)
+ continue;
+ if (rap->doexpire) {
+ expired = true;
+ continue;
+ }
+ if (ltime == ND6_INFINITE_LIFETIME) {
+ valid = true;
+ continue;
+ }
+
+ ltime = ntohl(ltime);
+ if (elapsed >= ltime) {
+ expired = true;
+ continue;
+ }
+
+ valid = true;
+ ltime -= elapsed;
+ if (next == 0 || ltime < next)
+ next = ltime;
+ }
+
+ if (valid)
+ continue;
+
+ /* Router has expired. Let's not keep a lot of them. */
+ rap->expired = true;
+ if (++nexpired > EXPIRED_MAX)
+ ipv6nd_free_ra(rap);
+ }
+
+ if (next != 0)
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ next, ipv6nd_expirera, ifp);
+ if (expired) {
+ logwarnx("%s: part of a Router Advertisement expired",
+ ifp->name);
+ ipv6nd_sortrouters(ifp->ctx);
+ ipv6nd_applyra(ifp);
+ rt_build(ifp->ctx, AF_INET6);
+ script_runreason(ifp, "ROUTERADVERT");
+ }
+}
+
+void
+ipv6nd_drop(struct interface *ifp)
+{
+ struct ra *rap, *ran;
+ bool expired = false;
+
+ if (ifp->ctx->ra_routers == NULL)
+ return;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
+ if (rap->iface == ifp) {
+ rap->expired = expired = true;
+ ipv6nd_drop_ra(rap);
+ }
+ }
+ if (expired) {
+ ipv6nd_applyra(ifp);
+ rt_build(ifp->ctx, AF_INET6);
+ if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP)
+ script_runreason(ifp, "ROUTERADVERT");
+ }
+}
+
+void
+ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg)
+{
+ struct sockaddr_in6 *from = (struct sockaddr_in6 *)msg->msg_name;
+ char sfrom[INET6_ADDRSTRLEN];
+ int hoplimit = 0;
+ struct icmp6_hdr *icp;
+ struct interface *ifp;
+ size_t len = msg->msg_iov[0].iov_len;
+
+ inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom));
+ if ((size_t)len < sizeof(struct icmp6_hdr)) {
+ logerrx("IPv6 ICMP packet too short from %s", sfrom);
+ return;
+ }
+
+ ifp = if_findifpfromcmsg(ctx, msg, &hoplimit);
+ if (ifp == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ /* Don't do anything if the user hasn't configured it. */
+ if (ifp->active != IF_ACTIVE_USER ||
+ !(ifp->options->options & DHCPCD_IPV6))
+ return;
+
+ icp = (struct icmp6_hdr *)msg->msg_iov[0].iov_base;
+ if (icp->icmp6_code == 0) {
+ switch(icp->icmp6_type) {
+ case ND_ROUTER_ADVERT:
+ ipv6nd_handlera(ctx, from, sfrom,
+ ifp, icp, (size_t)len, hoplimit);
+ return;
+ }
+ }
+
+ logerrx("invalid IPv6 type %d or code %d from %s",
+ icp->icmp6_type, icp->icmp6_code, sfrom);
+}
+
+static void
+ipv6nd_handledata(void *arg)
+{
+ struct dhcpcd_ctx *ctx;
+ int fd;
+ struct sockaddr_in6 from;
+ union {
+ struct icmp6_hdr hdr;
+ uint8_t buf[64 * 1024]; /* Maximum ICMPv6 size */
+ } iovbuf;
+ struct iovec iov = {
+ .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf),
+ };
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct msghdr msg = {
+ .msg_name = &from, .msg_namelen = sizeof(from),
+ .msg_iov = &iov, .msg_iovlen = 1,
+ .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ ssize_t len;
+
+#ifdef __sun
+ struct interface *ifp;
+ struct rs_state *state;
+
+ ifp = arg;
+ state = RS_STATE(ifp);
+ ctx = ifp->ctx;
+ fd = state->nd_fd;
+#else
+ ctx = arg;
+ fd = ctx->nd_fd;
+#endif
+ len = recvmsg(fd, &msg, 0);
+ if (len == -1) {
+ logerr(__func__);
+ return;
+ }
+
+ iov.iov_len = (size_t)len;
+ ipv6nd_recvmsg(ctx, &msg);
+}
+
+static void
+ipv6nd_startrs1(void *arg)
+{
+ struct interface *ifp = arg;
+ struct rs_state *state;
+
+ loginfox("%s: soliciting an IPv6 router", ifp->name);
+ state = RS_STATE(ifp);
+ if (state == NULL) {
+ ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state));
+ state = RS_STATE(ifp);
+ if (state == NULL) {
+ logerr(__func__);
+ return;
+ }
+#ifdef __sun
+ state->nd_fd = -1;
+#endif
+ }
+
+ /* Always make a new probe as the underlying hardware
+ * address could have changed. */
+ ipv6nd_makersprobe(ifp);
+ if (state->rs == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ state->retrans = RETRANS_TIMER;
+ state->rsprobes = 0;
+ ipv6nd_sendrsprobe(ifp);
+}
+
+void
+ipv6nd_startrs(struct interface *ifp)
+{
+ unsigned int delay;
+
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) {
+ ipv6nd_startrs1(ifp);
+ return;
+ }
+
+ delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC);
+ logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds",
+ ifp->name, (float)delay / MSEC_PER_SEC);
+ eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp);
+ return;
+}
diff --git a/src/ipv6nd.h b/src/ipv6nd.h
new file mode 100644
index 000000000000..fd5990cfea97
--- /dev/null
+++ b/src/ipv6nd.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - IPv6 ND handling
+ * Copyright (c) 2006-2021 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
+
+#ifdef INET6
+
+#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];
+ uint8_t *data;
+ size_t data_len;
+ struct timespec acquired;
+ unsigned char flags;
+ uint32_t lifetime;
+ uint32_t reachable;
+ uint32_t retrans;
+ uint32_t mtu;
+ uint8_t hoplimit;
+ struct ipv6_addrhead addrs;
+ bool hasdns;
+ bool expired;
+ bool willexpire;
+ bool doexpire;
+ bool isreachable;
+};
+
+TAILQ_HEAD(ra_head, ra);
+
+struct rs_state {
+ struct nd_router_solicit *rs;
+ size_t rslen;
+ int rsprobes;
+ uint32_t retrans;
+#ifdef __sun
+ int nd_fd;
+#endif
+};
+
+#define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
+#define RS_CSTATE(a) ((const struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
+#define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a)))
+
+#ifndef MAX_RTR_SOLICITATION_DELAY
+#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 */
+#define MAX_NEIGHBOR_ADVERTISEMENT 3 /* 3 transmissions */
+
+#ifndef IPV6_DEFHLIM
+#define IPV6_DEFHLIM 64
+#endif
+#endif
+
+/* 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 */
+
+int ipv6nd_open(bool);
+#ifdef __sun
+int ipv6nd_openif(struct interface *);
+#endif
+void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *);
+int ipv6nd_rtpref(struct ra *);
+void ipv6nd_printoptions(const struct dhcpcd_ctx *,
+ const struct dhcp_opt *, size_t);
+void ipv6nd_startrs(struct interface *);
+ssize_t ipv6nd_env(FILE *, const struct interface *);
+const struct ipv6_addr *ipv6nd_iffindaddr(const struct interface *ifp,
+ const struct in6_addr *addr, unsigned int flags);
+struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *,
+ const struct in6_addr *, unsigned int);
+struct ipv6_addr *ipv6nd_iffindprefix(struct interface *,
+ const struct in6_addr *, uint8_t);
+ssize_t ipv6nd_free(struct interface *);
+void ipv6nd_expirera(void *arg);
+bool ipv6nd_hasralifetime(const struct interface *, bool);
+#define ipv6nd_hasra(i) ipv6nd_hasralifetime((i), false)
+bool ipv6nd_hasradhcp(const struct interface *, bool);
+void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t);
+int ipv6nd_dadcompleted(const struct interface *);
+void ipv6nd_advertise(struct ipv6_addr *);
+void ipv6nd_startexpire(struct interface *);
+void ipv6nd_drop(struct interface *);
+void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, bool);
+#endif /* INET6 */
+
+#endif /* IPV6ND_H */
diff --git a/src/logerr.c b/src/logerr.c
new file mode 100644
index 000000000000..7a650e87f2c7
--- /dev/null
+++ b/src/logerr.c
@@ -0,0 +1,497 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * logerr: errx with logging
+ * Copyright (c) 2006-2021 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 <errno.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "logerr.h"
+
+#ifndef LOGERR_SYSLOG_FACILITY
+#define LOGERR_SYSLOG_FACILITY LOG_DAEMON
+#endif
+
+#ifdef SMALL
+#undef LOGERR_TAG
+#endif
+
+/* syslog protocol is 1k message max, RFC 3164 section 4.1 */
+#define LOGERR_SYSLOGBUF 1024 + sizeof(int) + sizeof(pid_t)
+
+#define UNUSED(a) (void)(a)
+
+struct logctx {
+ char log_buf[BUFSIZ];
+ unsigned int log_opts;
+ int log_fd;
+ pid_t log_pid;
+#ifndef SMALL
+ FILE *log_file;
+#ifdef LOGERR_TAG
+ const char *log_tag;
+#endif
+#endif
+};
+
+static struct logctx _logctx = {
+ /* syslog style, but without the hostname or tag. */
+ .log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID,
+ .log_fd = -1,
+ .log_pid = 0,
+};
+
+#if defined(__linux__)
+/* Poor man's getprogname(3). */
+static char *_logprog;
+static const char *
+getprogname(void)
+{
+ const char *p;
+
+ /* Use PATH_MAX + 1 to avoid truncation. */
+ if (_logprog == NULL) {
+ /* readlink(2) does not append a NULL byte,
+ * so zero the buffer. */
+ if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL)
+ return NULL;
+ if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) {
+ free(_logprog);
+ _logprog = NULL;
+ return NULL;
+ }
+ }
+ if (_logprog[0] == '[')
+ return NULL;
+ p = strrchr(_logprog, '/');
+ if (p == NULL)
+ return _logprog;
+ return p + 1;
+}
+#endif
+
+#ifndef SMALL
+/* Write the time, syslog style. month day time - */
+static int
+logprintdate(FILE *stream)
+{
+ struct timeval tv;
+ time_t now;
+ struct tm tmnow;
+ char buf[32];
+
+ if (gettimeofday(&tv, NULL) == -1)
+ return -1;
+
+ now = tv.tv_sec;
+ if (localtime_r(&now, &tmnow) == NULL)
+ return -1;
+ if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0)
+ return -1;
+ return fprintf(stream, "%s", buf);
+}
+#endif
+
+__printflike(3, 0) static int
+vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args)
+{
+ int len = 0, e;
+ va_list a;
+#ifndef SMALL
+ bool log_pid;
+#ifdef LOGERR_TAG
+ bool log_tag;
+#endif
+
+ if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) ||
+ (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE))
+ {
+ if ((e = logprintdate(stream)) == -1)
+ return -1;
+ len += e;
+ }
+
+#ifdef LOGERR_TAG
+ log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) ||
+ (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG));
+ if (log_tag) {
+ if (ctx->log_tag == NULL)
+ ctx->log_tag = getprogname();
+ if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1)
+ return -1;
+ len += e;
+ }
+#endif
+
+ log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) ||
+ (stream != stderr && ctx->log_opts & LOGERR_LOG_PID));
+ if (log_pid) {
+ pid_t pid;
+
+ if (ctx->log_pid == 0)
+ pid = getpid();
+ else
+ pid = ctx->log_pid;
+ if ((e = fprintf(stream, "[%d]", pid)) == -1)
+ return -1;
+ len += e;
+ }
+
+#ifdef LOGERR_TAG
+ if (log_tag || log_pid)
+#else
+ if (log_pid)
+#endif
+ {
+ if ((e = fprintf(stream, ": ")) == -1)
+ return -1;
+ len += e;
+ }
+#else
+ UNUSED(ctx);
+#endif
+
+ va_copy(a, args);
+ e = vfprintf(stream, fmt, a);
+ if (fputc('\n', stream) == EOF)
+ e = -1;
+ else if (e != -1)
+ e++;
+ va_end(a);
+
+ return e == -1 ? -1 : len + e;
+}
+
+/*
+ * NetBSD's gcc has been modified to check for the non standard %m in printf
+ * like functions and warn noisily about it that they should be marked as
+ * syslog like instead.
+ * This is all well and good, but our logger also goes via vfprintf and
+ * when marked as a sysloglike funcion, gcc will then warn us that the
+ * function should be printflike instead!
+ * This creates an infinte loop of gcc warnings.
+ * Until NetBSD solves this issue, we have to disable a gcc diagnostic
+ * for our fully standards compliant code in the logger function.
+ */
+#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-format-attribute"
+#endif
+__printflike(2, 0) static int
+vlogmessage(int pri, const char *fmt, va_list args)
+{
+ struct logctx *ctx = &_logctx;
+ int len = 0;
+
+ if (ctx->log_fd != -1) {
+ char buf[LOGERR_SYSLOGBUF];
+ pid_t pid;
+
+ memcpy(buf, &pri, sizeof(pri));
+ pid = getpid();
+ memcpy(buf + sizeof(pri), &pid, sizeof(pid));
+ len = vsnprintf(buf + sizeof(pri) + sizeof(pid),
+ sizeof(buf) - sizeof(pri) - sizeof(pid),
+ fmt, args);
+ if (len != -1)
+ len = (int)write(ctx->log_fd, buf,
+ ((size_t)++len) + sizeof(pri) + sizeof(pid));
+ return len;
+ }
+
+ if (ctx->log_opts & LOGERR_ERR &&
+ (pri <= LOG_ERR ||
+ (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) ||
+ (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG)))
+ len = vlogprintf_r(ctx, stderr, fmt, args);
+
+#ifndef SMALL
+ if (ctx->log_file != NULL &&
+ (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG)))
+ len = vlogprintf_r(ctx, ctx->log_file, fmt, args);
+#endif
+
+ if (ctx->log_opts & LOGERR_LOG)
+ vsyslog(pri, fmt, args);
+
+ return len;
+}
+#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
+#pragma GCC diagnostic pop
+#endif
+
+__printflike(2, 3) void
+logmessage(int pri, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogmessage(pri, fmt, args);
+ va_end(args);
+}
+
+__printflike(2, 0) static void
+vlogerrmessage(int pri, const char *fmt, va_list args)
+{
+ int _errno = errno;
+ char buf[1024];
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ logmessage(pri, "%s: %s", buf, strerror(_errno));
+ errno = _errno;
+}
+
+__printflike(2, 3) void
+logerrmessage(int pri, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogerrmessage(pri, fmt, args);
+ va_end(args);
+}
+
+void
+log_debug(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogerrmessage(LOG_DEBUG, fmt, args);
+ va_end(args);
+}
+
+void
+log_debugx(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogmessage(LOG_DEBUG, fmt, args);
+ va_end(args);
+}
+
+void
+log_info(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogerrmessage(LOG_INFO, fmt, args);
+ va_end(args);
+}
+
+void
+log_infox(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogmessage(LOG_INFO, fmt, args);
+ va_end(args);
+}
+
+void
+log_warn(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogerrmessage(LOG_WARNING, fmt, args);
+ va_end(args);
+}
+
+void
+log_warnx(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogmessage(LOG_WARNING, fmt, args);
+ va_end(args);
+}
+
+void
+log_err(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogerrmessage(LOG_ERR, fmt, args);
+ va_end(args);
+}
+
+void
+log_errx(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vlogmessage(LOG_ERR, fmt, args);
+ va_end(args);
+}
+
+int
+loggetfd(void)
+{
+ struct logctx *ctx = &_logctx;
+
+ return ctx->log_fd;
+}
+
+void
+logsetfd(int fd)
+{
+ struct logctx *ctx = &_logctx;
+
+ ctx->log_fd = fd;
+#ifndef SMALL
+ if (fd != -1 && ctx->log_file != NULL) {
+ fclose(ctx->log_file);
+ ctx->log_file = NULL;
+ }
+#endif
+}
+
+int
+logreadfd(int fd)
+{
+ struct logctx *ctx = &_logctx;
+ char buf[LOGERR_SYSLOGBUF];
+ int len, pri;
+
+ len = (int)read(fd, buf, sizeof(buf));
+ if (len == -1)
+ return -1;
+
+ /* Ensure we have pri, pid and a terminator */
+ if (len < (int)(sizeof(pri) + sizeof(pid_t) + 1) ||
+ buf[len - 1] != '\0')
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memcpy(&pri, buf, sizeof(pri));
+ memcpy(&ctx->log_pid, buf + sizeof(pri), sizeof(ctx->log_pid));
+ logmessage(pri, "%s", buf + sizeof(pri) + sizeof(ctx->log_pid));
+ ctx->log_pid = 0;
+ return len;
+}
+
+unsigned int
+loggetopts(void)
+{
+ struct logctx *ctx = &_logctx;
+
+ return ctx->log_opts;
+}
+
+void
+logsetopts(unsigned int opts)
+{
+ struct logctx *ctx = &_logctx;
+
+ ctx->log_opts = opts;
+ setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO));
+}
+
+#ifdef LOGERR_TAG
+void
+logsettag(const char *tag)
+{
+#if !defined(SMALL)
+ struct logctx *ctx = &_logctx;
+
+ ctx->log_tag = tag;
+#else
+ UNUSED(tag);
+#endif
+}
+#endif
+
+int
+logopen(const char *path)
+{
+ struct logctx *ctx = &_logctx;
+ int opts = 0;
+
+ /* Cache timezone */
+ tzset();
+
+ (void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf));
+
+#ifndef SMALL
+ if (ctx->log_file != NULL) {
+ fclose(ctx->log_file);
+ ctx->log_file = NULL;
+ }
+#endif
+
+ if (ctx->log_opts & LOGERR_LOG_PID)
+ opts |= LOG_PID;
+ openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY);
+ if (path == NULL)
+ return 1;
+
+#ifndef SMALL
+ if ((ctx->log_file = fopen(path, "ae")) == NULL)
+ return -1;
+ setlinebuf(ctx->log_file);
+ return fileno(ctx->log_file);
+#else
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
+void
+logclose(void)
+{
+#ifndef SMALL
+ struct logctx *ctx = &_logctx;
+#endif
+
+ closelog();
+#if defined(__linux__)
+ free(_logprog);
+ _logprog = NULL;
+#endif
+#ifndef SMALL
+ if (ctx->log_file == NULL)
+ return;
+ fclose(ctx->log_file);
+ ctx->log_file = NULL;
+#endif
+}
diff --git a/src/logerr.h b/src/logerr.h
new file mode 100644
index 000000000000..ba7e4f6a6a01
--- /dev/null
+++ b/src/logerr.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * logerr: errx with logging
+ * Copyright (c) 2006-2021 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 LOGERR_H
+#define LOGERR_H
+
+#include <sys/param.h>
+
+#ifndef __printflike
+#if __GNUC__ > 2 || defined(__INTEL_COMPILER)
+#define __printflike(a, b) __attribute__((format(printf, a, b)))
+#else
+#define __printflike(a, b)
+#endif
+#endif /* !__printflike */
+
+/* Please do not call log_* functions directly, use macros below */
+__printflike(1, 2) void log_debug(const char *, ...);
+__printflike(1, 2) void log_debugx(const char *, ...);
+__printflike(1, 2) void log_info(const char *, ...);
+__printflike(1, 2) void log_infox(const char *, ...);
+__printflike(1, 2) void log_warn(const char *, ...);
+__printflike(1, 2) void log_warnx(const char *, ...);
+__printflike(1, 2) void log_err(const char *, ...);
+__printflike(1, 2) void log_errx(const char *, ...);
+#define LOGERROR logerr("%s: %d", __FILE__, __LINE__)
+
+__printflike(2, 3) void logmessage(int pri, const char *fmt, ...);
+__printflike(2, 3) void logerrmessage(int pri, const char *fmt, ...);
+
+/*
+ * These are macros to prevent taking address of them so
+ * __FILE__, __LINE__, etc can easily be added.
+ *
+ * We should be using
+ * #define loginfox(fmt, __VA_OPT__(,) __VA_ARGS__)
+ * but that requires gcc-8 or clang-6 and we still have a need to support
+ * old OS's without modern compilers.
+ *
+ * Likewise, ##__VA_ARGS__ can't be used as that's a gcc only extension.
+ *
+ * The solution is to put fmt into __VA_ARGS__.
+ * It's not pretty but it's 100% portable.
+ */
+#define logdebug(...) log_debug(__VA_ARGS__)
+#define logdebugx(...) log_debugx(__VA_ARGS__)
+#define loginfo(...) log_info(__VA_ARGS__)
+#define loginfox(...) log_infox(__VA_ARGS__)
+#define logwarn(...) log_warn(__VA_ARGS__)
+#define logwarnx(...) log_warnx(__VA_ARGS__)
+#define logerr(...) log_err(__VA_ARGS__)
+#define logerrx(...) log_errx(__VA_ARGS__)
+
+/* For logging in a chroot */
+int loggetfd(void);
+void logsetfd(int);
+int logreadfd(int);
+
+unsigned int loggetopts(void);
+void logsetopts(unsigned int);
+#define LOGERR_DEBUG (1U << 6)
+#define LOGERR_QUIET (1U << 7)
+#define LOGERR_LOG (1U << 11)
+#define LOGERR_LOG_DATE (1U << 12)
+#define LOGERR_LOG_HOST (1U << 13)
+#define LOGERR_LOG_TAG (1U << 14)
+#define LOGERR_LOG_PID (1U << 15)
+#define LOGERR_ERR (1U << 21)
+#define LOGERR_ERR_DATE (1U << 22)
+#define LOGERR_ERR_HOST (1U << 23)
+#define LOGERR_ERR_TAG (1U << 24)
+#define LOGERR_ERR_PID (1U << 25)
+
+/* To build tag support or not. */
+//#define LOGERR_TAG
+#if defined(LOGERR_TAG)
+void logsettag(const char *);
+#endif
+
+/* Can be called more than once. */
+int logopen(const char *);
+
+/* Should only be called at program exit. */
+void logclose(void);
+
+#endif
diff --git a/src/privsep-bpf.c b/src/privsep-bpf.c
new file mode 100644
index 000000000000..f402ea183071
--- /dev/null
+++ b/src/privsep-bpf.c
@@ -0,0 +1,372 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation BPF Initiator
+ * Copyright (c) 2006-2021 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>
+
+/* Need these headers just for if_ether on some OS. */
+#ifndef __NetBSD__
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#endif
+#include <netinet/if_ether.h>
+
+#include <assert.h>
+#include <pwd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "bpf.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+
+static void
+ps_bpf_recvbpf(void *arg)
+{
+ struct ps_process *psp = arg;
+ struct bpf *bpf = psp->psp_bpf;
+ uint8_t buf[FRAMELEN_MAX];
+ ssize_t len;
+ struct ps_msghdr psm = {
+ .ps_id = psp->psp_id,
+ .ps_cmd = psp->psp_id.psi_cmd,
+ };
+
+ bpf->bpf_flags &= ~BPF_EOF;
+ /* A BPF read can read more than one filtered packet at time.
+ * This mechanism allows us to read each packet from the buffer. */
+ while (!(bpf->bpf_flags & BPF_EOF)) {
+ len = bpf_read(bpf, buf, sizeof(buf));
+ if (len == -1) {
+ int error = errno;
+
+ if (errno != ENETDOWN)
+ logerr("%s: %s", psp->psp_ifname, __func__);
+ if (error != ENXIO)
+ break;
+ /* If the interface has departed, close the BPF
+ * socket. This stops log spam if RTM_IFANNOUNCE is
+ * delayed in announcing the departing interface. */
+ eloop_event_delete(psp->psp_ctx->eloop, bpf->bpf_fd);
+ bpf_close(bpf);
+ psp->psp_bpf = NULL;
+ break;
+ }
+ if (len == 0)
+ break;
+ psm.ps_flags = bpf->bpf_flags;
+ len = ps_sendpsmdata(psp->psp_ctx, psp->psp_ctx->ps_data_fd,
+ &psm, buf, (size_t)len);
+ if (len == -1)
+ logerr(__func__);
+ if (len == -1 || len == 0)
+ break;
+ }
+}
+
+static ssize_t
+ps_bpf_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct ps_process *psp = arg;
+ struct iovec *iov = msg->msg_iov;
+
+#ifdef PRIVSEP_DEBUG
+ logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
+#endif
+
+ switch(psm->ps_cmd) {
+#ifdef ARP
+ case PS_BPF_ARP: /* FALLTHROUGH */
+#endif
+ case PS_BPF_BOOTP:
+ break;
+ default:
+ /* IPC failure, we should not be processing any commands
+ * at this point!/ */
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* We might have had an earlier ENXIO error. */
+ if (psp->psp_bpf == NULL) {
+ errno = ENXIO;
+ return -1;
+ }
+
+ return bpf_send(psp->psp_bpf, psp->psp_proto,
+ iov->iov_base, iov->iov_len);
+}
+
+static void
+ps_bpf_recvmsg(void *arg)
+{
+ struct ps_process *psp = arg;
+
+ if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd,
+ ps_bpf_recvmsgcb, arg) == -1)
+ logerr(__func__);
+}
+
+static int
+ps_bpf_start_bpf(void *arg)
+{
+ struct ps_process *psp = arg;
+ struct dhcpcd_ctx *ctx = psp->psp_ctx;
+ char *addr;
+ struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr;
+
+ if (ia->s_addr == INADDR_ANY) {
+ ia = NULL;
+ addr = NULL;
+ } else
+ addr = inet_ntoa(*ia);
+ setproctitle("[BPF %s] %s%s%s", psp->psp_protostr, psp->psp_ifname,
+ addr != NULL ? " " : "", addr != NULL ? addr : "");
+ ps_freeprocesses(ctx, psp);
+
+ psp->psp_bpf = bpf_open(&psp->psp_ifp, psp->psp_filter, ia);
+ if (psp->psp_bpf == NULL)
+ logerr("%s: bpf_open",__func__);
+#ifdef PRIVSEP_RIGHTS
+ else if (ps_rights_limit_fd(psp->psp_bpf->bpf_fd) == -1)
+ logerr("%s: ps_rights_limit_fd", __func__);
+#endif
+ else if (eloop_event_add(ctx->eloop,
+ psp->psp_bpf->bpf_fd, ps_bpf_recvbpf, psp) == -1)
+ logerr("%s: eloop_event_add", __func__);
+ else {
+ psp->psp_work_fd = psp->psp_bpf->bpf_fd;
+ return 0;
+ }
+
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return -1;
+}
+
+ssize_t
+ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ uint16_t cmd;
+ struct ps_process *psp;
+ pid_t start;
+ struct iovec *iov = msg->msg_iov;
+ struct interface *ifp;
+
+ cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
+ psp = ps_findprocess(ctx, &psm->ps_id);
+
+#ifdef PRIVSEP_DEBUG
+ logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
+#endif
+
+ switch (cmd) {
+#ifdef ARP
+ case PS_BPF_ARP: /* FALLTHROUGH */
+#endif
+ case PS_BPF_BOOTP:
+ break;
+ default:
+ logerrx("%s: unknown command %x", __func__, psm->ps_cmd);
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (!(psm->ps_cmd & PS_START)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (psp != NULL)
+ return 1;
+
+ psp = ps_newprocess(ctx, &psm->ps_id);
+ if (psp == NULL)
+ return -1;
+
+ ifp = &psp->psp_ifp;
+ assert(msg->msg_iovlen == 1);
+ assert(iov->iov_len == sizeof(*ifp));
+ memcpy(ifp, iov->iov_base, sizeof(*ifp));
+ ifp->ctx = psp->psp_ctx;
+ ifp->options = NULL;
+ memset(ifp->if_data, 0, sizeof(ifp->if_data));
+
+ memcpy(psp->psp_ifname, ifp->name, sizeof(psp->psp_ifname));
+
+ switch (cmd) {
+#ifdef ARP
+ case PS_BPF_ARP:
+ psp->psp_proto = ETHERTYPE_ARP;
+ psp->psp_protostr = "ARP";
+ psp->psp_filter = bpf_arp;
+ break;
+#endif
+ case PS_BPF_BOOTP:
+ psp->psp_proto = ETHERTYPE_IP;
+ psp->psp_protostr = "BOOTP";
+ psp->psp_filter = bpf_bootp;
+ break;
+ }
+
+ start = ps_dostart(ctx,
+ &psp->psp_pid, &psp->psp_fd,
+ ps_bpf_recvmsg, NULL, psp,
+ ps_bpf_start_bpf, NULL,
+ PSF_DROPPRIVS);
+ switch (start) {
+ case -1:
+ ps_freeprocess(psp);
+ return -1;
+ case 0:
+ ps_entersandbox("stdio", NULL);
+ break;
+ default:
+ logdebugx("%s: spawned BPF %s on PID %d",
+ psp->psp_ifname, psp->psp_protostr, start);
+ break;
+ }
+ return start;
+}
+
+ssize_t
+ps_bpf_dispatch(struct dhcpcd_ctx *ctx,
+ struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct iovec *iov = msg->msg_iov;
+ struct interface *ifp;
+ uint8_t *bpf;
+ size_t bpf_len;
+
+ switch (psm->ps_cmd) {
+#ifdef ARP
+ case PS_BPF_ARP:
+#endif
+ case PS_BPF_BOOTP:
+ break;
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ ifp = if_findindex(ctx->ifaces, psm->ps_id.psi_ifindex);
+ /* interface may have departed .... */
+ if (ifp == NULL)
+ return -1;
+
+ bpf = iov->iov_base;
+ bpf_len = iov->iov_len;
+
+ switch (psm->ps_cmd) {
+#ifdef ARP
+ case PS_BPF_ARP:
+ arp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags);
+ break;
+#endif
+ case PS_BPF_BOOTP:
+ dhcp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags);
+ break;
+ }
+
+ return 1;
+}
+
+static ssize_t
+ps_bpf_send(const struct interface *ifp, const struct in_addr *ia,
+ uint16_t cmd, const void *data, size_t len)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct ps_msghdr psm = {
+ .ps_cmd = cmd,
+ .ps_id = {
+ .psi_ifindex = ifp->index,
+ .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)),
+ },
+ };
+
+ if (ia != NULL)
+ psm.ps_id.psi_addr.psa_in_addr = *ia;
+
+ return ps_sendpsmdata(ctx, ctx->ps_root_fd, &psm, data, len);
+}
+
+#ifdef ARP
+ssize_t
+ps_bpf_openarp(const struct interface *ifp, const struct in_addr *ia)
+{
+
+ assert(ia != NULL);
+ return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_START,
+ ifp, sizeof(*ifp));
+}
+
+ssize_t
+ps_bpf_closearp(const struct interface *ifp, const struct in_addr *ia)
+{
+
+ return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_STOP, NULL, 0);
+}
+
+ssize_t
+ps_bpf_sendarp(const struct interface *ifp, const struct in_addr *ia,
+ const void *data, size_t len)
+{
+
+ assert(ia != NULL);
+ return ps_bpf_send(ifp, ia, PS_BPF_ARP, data, len);
+}
+#endif
+
+ssize_t
+ps_bpf_openbootp(const struct interface *ifp)
+{
+
+ return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_START,
+ ifp, sizeof(*ifp));
+}
+
+ssize_t
+ps_bpf_closebootp(const struct interface *ifp)
+{
+
+ return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_STOP, NULL, 0);
+}
+
+ssize_t
+ps_bpf_sendbootp(const struct interface *ifp, const void *data, size_t len)
+{
+
+ return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP, data, len);
+}
diff --git a/src/privsep-bpf.h b/src/privsep-bpf.h
new file mode 100644
index 000000000000..50c132379d24
--- /dev/null
+++ b/src/privsep-bpf.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2021 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 PRIVSEP_BPF_H
+#define PRIVSEP_BPF_H
+
+ssize_t ps_bpf_cmd(struct dhcpcd_ctx *,
+ struct ps_msghdr *, struct msghdr *);
+ssize_t ps_bpf_dispatch(struct dhcpcd_ctx *,
+ struct ps_msghdr *, struct msghdr *);
+
+#ifdef ARP
+ssize_t ps_bpf_openarp(const struct interface *, const struct in_addr *);
+ssize_t ps_bpf_closearp(const struct interface *, const struct in_addr *);
+ssize_t ps_bpf_sendarp(const struct interface *, const struct in_addr *,
+ const void *, size_t);
+#endif
+
+ssize_t ps_bpf_openbootp(const struct interface *);
+ssize_t ps_bpf_closebootp(const struct interface *);
+ssize_t ps_bpf_sendbootp(const struct interface *, const void *, size_t);
+ssize_t ps_bpf_openbootpudp(const struct interface *);
+ssize_t ps_bpf_closebootpudp(const struct interface *);
+ssize_t ps_bpf_sendbootpudp(const struct interface *, const void *, size_t);
+#endif
diff --git a/src/privsep-bsd.c b/src/privsep-bsd.c
new file mode 100644
index 000000000000..22472625af52
--- /dev/null
+++ b/src/privsep-bsd.c
@@ -0,0 +1,278 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, BSD driver
+ * Copyright (c) 2006-2021 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>
+
+/* Need these for filtering the ioctls */
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+#ifdef __NetBSD__
+#include <netinet/if_ether.h>
+#include <net/if_vlanvar.h> /* Needs netinet/if_ether.h */
+#elif defined(__DragonFly__)
+#include <net/vlan/if_vlan_var.h>
+#else
+#include <net/if_vlan_var.h>
+#endif
+#ifdef __DragonFly__
+# include <netproto/802_11/ieee80211_ioctl.h>
+#else
+# include <net80211/ieee80211.h>
+# include <net80211/ieee80211_ioctl.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dhcpcd.h"
+#include "logerr.h"
+#include "privsep.h"
+
+static ssize_t
+ps_root_doioctldom(int domain, unsigned long req, void *data, size_t len)
+{
+ int s, err;
+
+ /* Only allow these ioctls */
+ switch(req) {
+#ifdef SIOCGIFDATA
+ case SIOCGIFDATA: /* FALLTHROUGH */
+#endif
+#ifdef SIOCG80211NWID
+ case SIOCG80211NWID: /* FALLTHROUGH */
+#endif
+#ifdef SIOCGETVLAN
+ case SIOCGETVLAN: /* FALLTHROUGH */
+#endif
+#ifdef SIOCIFAFATTACH
+ case SIOCIFAFATTACH: /* FALLTHROUGH */
+#endif
+#ifdef SIOCSIFXFLAGS
+ case SIOCSIFXFLAGS: /* FALLTHROUGH */
+#endif
+#ifdef SIOCSIFINFO_FLAGS
+ case SIOCSIFINFO_FLAGS: /* FALLTHROUGH */
+#endif
+#ifdef SIOCSRTRFLUSH_IN6
+ case SIOCSRTRFLUSH_IN6: /* FALLTHROUGH */
+ case SIOCSPFXFLUSH_IN6: /* FALLTHROUGH */
+#endif
+#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE)
+ case SIOCALIFADDR: /* FALLTHROUGH */
+ case SIOCDLIFADDR: /* FALLTHROUGH */
+#else
+ case SIOCSIFLLADDR: /* FALLTHROUGH */
+#endif
+#ifdef SIOCSIFINFO_IN6
+ case SIOCSIFINFO_IN6: /* FALLTHROUGH */
+#endif
+ case SIOCAIFADDR_IN6: /* FALLTHROUGH */
+ case SIOCDIFADDR_IN6:
+ break;
+ default:
+ errno = EPERM;
+ return -1;
+ }
+
+ s = socket(domain, SOCK_DGRAM, 0);
+ if (s == -1)
+ return -1;
+ err = ioctl(s, req, data, len);
+ close(s);
+ return err;
+}
+
+static ssize_t
+ps_root_doroute(void *data, size_t len)
+{
+ int s;
+ ssize_t err;
+
+ s = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s != -1)
+ err = write(s, data, len);
+ else
+ err = -1;
+ if (s != -1)
+ close(s);
+ return err;
+}
+
+#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)
+static ssize_t
+ps_root_doindirectioctl(unsigned long req, void *data, size_t len)
+{
+ char *p = data;
+ struct ifreq ifr = { .ifr_flags = 0 };
+
+ /* ioctl filtering is done in ps_root_doioctldom */
+
+ if (len < IFNAMSIZ + 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ strlcpy(ifr.ifr_name, p, IFNAMSIZ);
+ len -= IFNAMSIZ;
+ memmove(data, p + IFNAMSIZ, len);
+ ifr.ifr_data = data;
+
+ return ps_root_doioctldom(PF_INET, req, &ifr, sizeof(ifr));
+}
+#endif
+
+#ifdef HAVE_PLEDGE
+static ssize_t
+ps_root_doifignoregroup(void *data, size_t len)
+{
+ int s, err;
+
+ if (len == 0 || ((const char *)data)[len - 1] != '\0') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ return -1;
+ err = if_ignoregroup(s, data);
+ close(s);
+ return err;
+}
+#endif
+
+ssize_t
+ps_root_os(struct ps_msghdr *psm, struct msghdr *msg,
+ void **rdata, size_t *rlen)
+{
+ struct iovec *iov = msg->msg_iov;
+ void *data = iov->iov_base;
+ size_t len = iov->iov_len;
+ ssize_t err;
+
+ switch (psm->ps_cmd) {
+ case PS_IOCTLLINK:
+ err = ps_root_doioctldom(PF_LINK, psm->ps_flags, data, len);
+ break;
+ case PS_IOCTL6:
+ err = ps_root_doioctldom(PF_INET6, psm->ps_flags, data, len);
+ break;
+ case PS_ROUTE:
+ return ps_root_doroute(data, len);
+#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)
+ case PS_IOCTLINDIRECT:
+ err = ps_root_doindirectioctl(psm->ps_flags, data, len);
+ break;
+#endif
+#ifdef HAVE_PLEDGE
+ case PS_IFIGNOREGRP:
+ return ps_root_doifignoregroup(data, len);
+#endif
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (err != -1) {
+ *rdata = data;
+ *rlen = len;
+ }
+ return err;
+}
+
+static ssize_t
+ps_root_ioctldom(struct dhcpcd_ctx *ctx, uint16_t domain, unsigned long request,
+ void *data, size_t len)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, domain,
+ request, data, len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_ioctllink(struct dhcpcd_ctx *ctx, unsigned long request,
+ void *data, size_t len)
+{
+
+ return ps_root_ioctldom(ctx, PS_IOCTLLINK, request, data, len);
+}
+
+ssize_t
+ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request,
+ void *data, size_t len)
+{
+
+ return ps_root_ioctldom(ctx, PS_IOCTL6, request, data, len);
+}
+
+ssize_t
+ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_ROUTE, 0, data, len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
+
+#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)
+ssize_t
+ps_root_indirectioctl(struct dhcpcd_ctx *ctx, unsigned long request,
+ const char *ifname, void *data, size_t len)
+{
+ char buf[PS_BUFLEN];
+
+ if (IFNAMSIZ + len > sizeof(buf)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+
+ strlcpy(buf, ifname, IFNAMSIZ);
+ memcpy(buf + IFNAMSIZ, data, len);
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTLINDIRECT,
+ request, buf, IFNAMSIZ + len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_ifignoregroup(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IFIGNOREGRP, 0,
+ ifname, strlen(ifname) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+#endif
diff --git a/src/privsep-control.c b/src/privsep-control.c
new file mode 100644
index 000000000000..fe9bbbf22732
--- /dev/null
+++ b/src/privsep-control.c
@@ -0,0 +1,296 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, control proxy
+ * Copyright (c) 2006-2021 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 <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dhcpcd.h"
+#include "control.h"
+#include "eloop.h"
+#include "logerr.h"
+#include "privsep.h"
+
+static int
+ps_ctl_startcb(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ sa_family_t af;
+
+ if (ctx->options & DHCPCD_MANAGER) {
+ setproctitle("[control proxy]");
+ af = AF_UNSPEC;
+ } else {
+ setproctitle("[control proxy] %s%s%s",
+ ctx->ifv[0],
+ ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
+ ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
+ if ((ctx->options &
+ (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4)
+ af = AF_INET;
+ else if ((ctx->options &
+ (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6)
+ af = AF_INET6;
+ else
+ af = AF_UNSPEC;
+ }
+
+ ctx->ps_control_pid = getpid();
+
+ return control_start(ctx,
+ ctx->options & DHCPCD_MANAGER ? NULL : *ctx->ifv, af);
+}
+
+static ssize_t
+ps_ctl_recvmsgcb(void *arg, struct ps_msghdr *psm, __unused struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (psm->ps_cmd != PS_CTL_EOF) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (ctx->ps_control_client != NULL)
+ ctx->ps_control_client = NULL;
+ return 0;
+}
+
+static void
+ps_ctl_recvmsg(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_recvmsgcb, ctx) == -1)
+ logerr(__func__);
+}
+
+ssize_t
+ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len)
+{
+
+ /* Make any change here in dhcpcd.c as well. */
+ if (strncmp(data, "--version",
+ MIN(strlen("--version"), len)) == 0) {
+ return control_queue(fd, UNCONST(VERSION),
+ strlen(VERSION) + 1);
+ } else if (strncmp(data, "--getconfigfile",
+ MIN(strlen("--getconfigfile"), len)) == 0) {
+ return control_queue(fd, UNCONST(fd->ctx->cffile),
+ strlen(fd->ctx->cffile) + 1);
+ } else if (strncmp(data, "--listen",
+ MIN(strlen("--listen"), len)) == 0) {
+ fd->flags |= FD_LISTEN;
+ return 0;
+ }
+
+ if (fd->ctx->ps_control_client != NULL &&
+ fd->ctx->ps_control_client != fd)
+ {
+ logerrx("%s: cannot handle another client", __func__);
+ return 0;
+ }
+ return 1;
+}
+
+static ssize_t
+ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ struct iovec *iov = msg->msg_iov;
+ struct fd_list *fd;
+ unsigned int fd_flags = FD_SENDLEN;
+
+ switch (psm->ps_flags) {
+ case PS_CTL_PRIV:
+ break;
+ case PS_CTL_UNPRIV:
+ fd_flags |= FD_UNPRIV;
+ break;
+ }
+
+ switch (psm->ps_cmd) {
+ case PS_CTL:
+ if (msg->msg_iovlen != 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (ctx->ps_control_client != NULL) {
+ logerrx("%s: cannot handle another client", __func__);
+ return 0;
+ }
+ fd = control_new(ctx, ctx->ps_control_data_fd, fd_flags);
+ if (fd == NULL)
+ return -1;
+ ctx->ps_control_client = fd;
+ control_recvdata(fd, iov->iov_base, iov->iov_len);
+ break;
+ case PS_CTL_EOF:
+ control_free(ctx->ps_control_client);
+ break;
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+ return 0;
+}
+
+static void
+ps_ctl_dodispatch(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_dispatch, ctx) == -1)
+ logerr(__func__);
+}
+
+static void
+ps_ctl_recv(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ char buf[BUFSIZ];
+ ssize_t len;
+
+ errno = 0;
+ len = read(ctx->ps_control_data_fd, buf, sizeof(buf));
+ if (len == -1 || len == 0) {
+ logerr("%s: read", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+ if (ctx->ps_control_client == NULL) /* client disconnected */
+ return;
+ errno = 0;
+ if (control_queue(ctx->ps_control_client, buf, (size_t)len) == -1)
+ logerr("%s: control_queue", __func__);
+}
+
+static void
+ps_ctl_listen(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ char buf[BUFSIZ];
+ ssize_t len;
+ struct fd_list *fd;
+
+ errno = 0;
+ len = read(ctx->ps_control->fd, buf, sizeof(buf));
+ if (len == -1 || len == 0) {
+ logerr("%s: read", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+
+ /* Send to our listeners */
+ TAILQ_FOREACH(fd, &ctx->control_fds, next) {
+ if (!(fd->flags & FD_LISTEN))
+ continue;
+ if (control_queue(fd, buf, (size_t)len)== -1)
+ logerr("%s: control_queue", __func__);
+ }
+}
+
+pid_t
+ps_ctl_start(struct dhcpcd_ctx *ctx)
+{
+ int data_fd[2], listen_fd[2];
+ pid_t pid;
+
+ if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, data_fd) == -1 ||
+ xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1)
+ return -1;
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fdpair(data_fd) == -1 ||
+ ps_rights_limit_fdpair(listen_fd) == -1)
+ return -1;
+#endif
+
+ pid = ps_dostart(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd,
+ ps_ctl_recvmsg, ps_ctl_dodispatch, ctx,
+ ps_ctl_startcb, NULL,
+ PSF_DROPPRIVS);
+
+ if (pid == -1)
+ return -1;
+ else if (pid != 0) {
+ ctx->ps_control_data_fd = data_fd[1];
+ close(data_fd[0]);
+ ctx->ps_control = control_new(ctx,
+ listen_fd[1], FD_SENDLEN | FD_LISTEN);
+ if (ctx->ps_control == NULL)
+ return -1;
+ close(listen_fd[0]);
+ return pid;
+ }
+
+ ctx->ps_control_data_fd = data_fd[0];
+ close(data_fd[1]);
+ if (eloop_event_add(ctx->eloop, ctx->ps_control_data_fd,
+ ps_ctl_recv, ctx) == -1)
+ return -1;
+
+ ctx->ps_control = control_new(ctx,
+ listen_fd[0], 0);
+ close(listen_fd[1]);
+ if (ctx->ps_control == NULL)
+ return -1;
+ if (eloop_event_add(ctx->eloop, ctx->ps_control->fd,
+ ps_ctl_listen, ctx) == -1)
+ return -1;
+
+ ps_entersandbox("stdio inet", NULL);
+ return 0;
+}
+
+int
+ps_ctl_stop(struct dhcpcd_ctx *ctx)
+{
+
+ return ps_dostop(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd);
+}
+
+ssize_t
+ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len)
+{
+ struct dhcpcd_ctx *ctx = fd->ctx;
+
+ if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd)
+ logerrx("%s: cannot deal with another client", __func__);
+ ctx->ps_control_client = fd;
+ return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL,
+ fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV,
+ data, len);
+}
+
+ssize_t
+ps_ctl_sendeof(struct fd_list *fd)
+{
+ struct dhcpcd_ctx *ctx = fd->ctx;
+
+ return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL_EOF, 0, NULL, 0);
+}
diff --git a/src/privsep-control.h b/src/privsep-control.h
new file mode 100644
index 000000000000..8f01b536cf85
--- /dev/null
+++ b/src/privsep-control.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2021 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 PRIVSEP_CTL_H
+#define PRIVSEP_CTL_H
+
+#define IN_PRIVSEP_CONTROLLER(ctx) \
+ (IN_PRIVSEP((ctx)) && (ctx)->ps_control_pid == getpid())
+
+pid_t ps_ctl_start(struct dhcpcd_ctx *);
+int ps_ctl_stop(struct dhcpcd_ctx *);
+ssize_t ps_ctl_handleargs(struct fd_list *, char *, size_t);
+ssize_t ps_ctl_sendargs(struct fd_list *, void *, size_t);
+ssize_t ps_ctl_sendeof(struct fd_list *fd);
+
+#endif
diff --git a/src/privsep-inet.c b/src/privsep-inet.c
new file mode 100644
index 000000000000..3a192ee0c461
--- /dev/null
+++ b/src/privsep-inet.c
@@ -0,0 +1,717 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, network proxy
+ * Copyright (c) 2006-2021 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 <netinet/in.h>
+#include <netinet/icmp6.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "bpf.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+
+#ifdef INET
+static void
+ps_inet_recvbootp(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvmsg(ctx, ctx->udp_rfd, PS_BOOTP, ctx->ps_inet_fd) == -1)
+ logerr(__func__);
+}
+#endif
+
+#ifdef INET6
+static void
+ps_inet_recvra(void *arg)
+{
+#ifdef __sun
+ struct interface *ifp = arg;
+ struct rs_state *state = RS_STATE(ifp);
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+
+ if (ps_recvmsg(ctx, state->nd_fd, PS_ND, ctx->ps_inet_fd) == -1)
+ logerr(__func__);
+#else
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvmsg(ctx, ctx->nd_fd, PS_ND, ctx->ps_inet_fd) == -1)
+ logerr(__func__);
+#endif
+}
+#endif
+
+#ifdef DHCP6
+static void
+ps_inet_recvdhcp6(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvmsg(ctx, ctx->dhcp6_rfd, PS_DHCP6, ctx->ps_inet_fd) == -1)
+ logerr(__func__);
+}
+#endif
+
+bool
+ps_inet_canstart(const struct dhcpcd_ctx *ctx)
+{
+
+#ifdef INET
+ if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MANAGER)) ==
+ (DHCPCD_IPV4 | DHCPCD_MANAGER))
+ return true;
+#endif
+#if defined(INET6) && !defined(__sun)
+ if (ctx->options & DHCPCD_IPV6)
+ return true;
+#endif
+#ifdef DHCP6
+ if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MANAGER)) ==
+ (DHCPCD_IPV6 | DHCPCD_MANAGER))
+ return true;
+#endif
+
+ return false;
+}
+
+static int
+ps_inet_startcb(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ int ret = 0;
+
+ if (ctx->options & DHCPCD_MANAGER)
+ setproctitle("[network proxy]");
+ else
+ setproctitle("[network proxy] %s%s%s",
+ ctx->ifv[0],
+ ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
+ ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
+
+ /* This end is the main engine, so it's useless for us. */
+ close(ctx->ps_data_fd);
+ ctx->ps_data_fd = -1;
+
+ errno = 0;
+
+#ifdef INET
+ if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MANAGER)) ==
+ (DHCPCD_IPV4 | DHCPCD_MANAGER))
+ {
+ ctx->udp_rfd = dhcp_openudp(NULL);
+ if (ctx->udp_rfd == -1)
+ logerr("%s: dhcp_open", __func__);
+#ifdef PRIVSEP_RIGHTS
+ else if (ps_rights_limit_fd_rdonly(ctx->udp_rfd) == -1) {
+ logerr("%s: ps_rights_limit_fd_rdonly", __func__);
+ close(ctx->udp_rfd);
+ ctx->udp_rfd = -1;
+ }
+#endif
+ else if (eloop_event_add(ctx->eloop, ctx->udp_rfd,
+ ps_inet_recvbootp, ctx) == -1)
+ {
+ logerr("%s: eloop_event_add DHCP", __func__);
+ close(ctx->udp_rfd);
+ ctx->udp_rfd = -1;
+ } else
+ ret++;
+ }
+#endif
+#if defined(INET6) && !defined(__sun)
+ if (ctx->options & DHCPCD_IPV6) {
+ ctx->nd_fd = ipv6nd_open(true);
+ if (ctx->nd_fd == -1)
+ logerr("%s: ipv6nd_open", __func__);
+#ifdef PRIVSEP_RIGHTS
+ else if (ps_rights_limit_fd_rdonly(ctx->nd_fd) == -1) {
+ logerr("%s: ps_rights_limit_fd_rdonly", __func__);
+ close(ctx->nd_fd);
+ ctx->nd_fd = -1;
+ }
+#endif
+ else if (eloop_event_add(ctx->eloop, ctx->nd_fd,
+ ps_inet_recvra, ctx) == -1)
+ {
+ logerr("%s: eloop_event_add RA", __func__);
+ close(ctx->nd_fd);
+ ctx->nd_fd = -1;
+ } else
+ ret++;
+ }
+#endif
+#ifdef DHCP6
+ if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MANAGER)) ==
+ (DHCPCD_IPV6 | DHCPCD_MANAGER))
+ {
+ ctx->dhcp6_rfd = dhcp6_openudp(0, NULL);
+ if (ctx->dhcp6_rfd == -1)
+ logerr("%s: dhcp6_open", __func__);
+#ifdef PRIVSEP_RIGHTS
+ else if (ps_rights_limit_fd_rdonly(ctx->dhcp6_rfd) == -1) {
+ logerr("%s: ps_rights_limit_fd_rdonly", __func__);
+ close(ctx->dhcp6_rfd);
+ ctx->dhcp6_rfd = -1;
+ }
+#endif
+ else if (eloop_event_add(ctx->eloop, ctx->dhcp6_rfd,
+ ps_inet_recvdhcp6, ctx) == -1)
+ {
+ logerr("%s: eloop_event_add DHCP6", __func__);
+ close(ctx->dhcp6_rfd);
+ ctx->dhcp6_rfd = -1;
+ } else
+ ret++;
+ }
+#endif
+
+ if (ret == 0 && errno == 0) {
+ errno = ENXIO;
+ return -1;
+ }
+ return ret;
+}
+
+static bool
+ps_inet_validudp(struct msghdr *msg, uint16_t sport, uint16_t dport)
+{
+ struct udphdr udp;
+ struct iovec *iov = msg->msg_iov;
+
+ if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(udp)) {
+ errno = EINVAL;
+ return false;
+ }
+
+ memcpy(&udp, iov->iov_base, sizeof(udp));
+ if (udp.uh_sport != htons(sport) || udp.uh_dport != htons(dport)) {
+ errno = EPERM;
+ return false;
+ }
+ return true;
+}
+
+#ifdef INET6
+static bool
+ps_inet_validnd(struct msghdr *msg)
+{
+ struct icmp6_hdr icmp6;
+ struct iovec *iov = msg->msg_iov;
+
+ if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(icmp6)) {
+ errno = EINVAL;
+ return false;
+ }
+
+ memcpy(&icmp6, iov->iov_base, sizeof(icmp6));
+ switch(icmp6.icmp6_type) {
+ case ND_ROUTER_SOLICIT:
+ case ND_NEIGHBOR_ADVERT:
+ break;
+ default:
+ errno = EPERM;
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+static ssize_t
+ps_inet_sendmsg(struct dhcpcd_ctx *ctx,
+ struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct ps_process *psp;
+ int s;
+
+ psp = ps_findprocess(ctx, &psm->ps_id);
+ if (psp != NULL) {
+ s = psp->psp_work_fd;
+ goto dosend;
+ }
+
+ switch (psm->ps_cmd) {
+#ifdef INET
+ case PS_BOOTP:
+ if (!ps_inet_validudp(msg, BOOTPC, BOOTPS))
+ return -1;
+ s = ctx->udp_wfd;
+ break;
+#endif
+#if defined(INET6) && !defined(__sun)
+ case PS_ND:
+ if (!ps_inet_validnd(msg))
+ return -1;
+ s = ctx->nd_fd;
+ break;
+#endif
+#ifdef DHCP6
+ case PS_DHCP6:
+ if (!ps_inet_validudp(msg, DHCP6_CLIENT_PORT,DHCP6_SERVER_PORT))
+ return -1;
+ s = ctx->dhcp6_wfd;
+ break;
+#endif
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+dosend:
+ return sendmsg(s, msg, 0);
+}
+
+static void
+ps_inet_recvmsg(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ /* Receive shutdown */
+ if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, NULL, NULL) == -1)
+ logerr(__func__);
+}
+
+ssize_t
+ps_inet_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ switch (psm->ps_cmd) {
+#ifdef INET
+ case PS_BOOTP:
+ dhcp_recvmsg(ctx, msg);
+ break;
+#endif
+#ifdef INET6
+ case PS_ND:
+ ipv6nd_recvmsg(ctx, msg);
+ break;
+#endif
+#ifdef DHCP6
+ case PS_DHCP6:
+ dhcp6_recvmsg(ctx, msg, NULL);
+ break;
+#endif
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+ return 1;
+}
+
+static void
+ps_inet_dodispatch(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, ps_inet_dispatch, ctx) == -1)
+ logerr(__func__);
+}
+
+pid_t
+ps_inet_start(struct dhcpcd_ctx *ctx)
+{
+ pid_t pid;
+
+ pid = ps_dostart(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd,
+ ps_inet_recvmsg, ps_inet_dodispatch, ctx,
+ ps_inet_startcb, NULL,
+ PSF_DROPPRIVS);
+
+ if (pid == 0)
+ ps_entersandbox("stdio", NULL);
+
+ return pid;
+}
+
+int
+ps_inet_stop(struct dhcpcd_ctx *ctx)
+{
+
+ return ps_dostop(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd);
+}
+
+#ifdef INET
+static void
+ps_inet_recvinbootp(void *arg)
+{
+ struct ps_process *psp = arg;
+
+ if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd,
+ PS_BOOTP, psp->psp_ctx->ps_data_fd) == -1)
+ logerr(__func__);
+}
+
+static int
+ps_inet_listenin(void *arg)
+{
+ struct ps_process *psp = arg;
+ struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr;
+ char buf[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, ia, buf, sizeof(buf));
+ setproctitle("[network proxy] %s", buf);
+
+ psp->psp_work_fd = dhcp_openudp(ia);
+ if (psp->psp_work_fd == -1) {
+ logerr(__func__);
+ return -1;
+ }
+
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) {
+ logerr("%s: ps_rights_limit_fd_rdonly", __func__);
+ return -1;
+ }
+#endif
+
+ if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd,
+ ps_inet_recvinbootp, psp) == -1)
+ {
+ logerr("%s: eloop_event_add DHCP", __func__);
+ return -1;
+ }
+
+ logdebugx("spawned listener %s on PID %d", buf, getpid());
+ return 0;
+}
+#endif
+
+#if defined(INET6) && defined(__sun)
+static void
+ps_inet_recvin6nd(void *arg)
+{
+ struct ps_process *psp = arg;
+
+ if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd,
+ PS_ND, psp->psp_ctx->ps_data_fd) == -1)
+ logerr(__func__);
+}
+
+static int
+ps_inet_listennd(void *arg)
+{
+ struct ps_process *psp = arg;
+
+ setproctitle("[ND network proxy]");
+
+ psp->psp_work_fd = ipv6nd_open(&psp->psp_ifp);
+ if (psp->psp_work_fd == -1) {
+ logerr(__func__);
+ return -1;
+ }
+
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) {
+ logerr("%s: ps_rights_limit_fd_rdonly", __func__);
+ return -1;
+ }
+#endif
+
+ if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd,
+ ps_inet_recvin6nd, psp) == -1)
+ {
+ logerr(__func__);
+ return -1;
+ }
+
+ logdebugx("spawned ND listener on PID %d", getpid());
+ return 0;
+}
+#endif
+
+#ifdef DHCP6
+static void
+ps_inet_recvin6dhcp6(void *arg)
+{
+ struct ps_process *psp = arg;
+
+ if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd,
+ PS_DHCP6, psp->psp_ctx->ps_data_fd) == -1)
+ logerr(__func__);
+}
+
+static int
+ps_inet_listenin6(void *arg)
+{
+ struct ps_process *psp = arg;
+ struct in6_addr *ia = &psp->psp_id.psi_addr.psa_in6_addr;
+ char buf[INET6_ADDRSTRLEN];
+
+ inet_ntop(AF_INET6, ia, buf, sizeof(buf));
+ setproctitle("[network proxy] %s", buf);
+
+ psp->psp_work_fd = dhcp6_openudp(psp->psp_id.psi_ifindex, ia);
+ if (psp->psp_work_fd == -1) {
+ logerr(__func__);
+ return -1;
+ }
+
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) {
+ logerr("%s: ps_rights_limit_fd_rdonly", __func__);
+ return -1;
+ }
+#endif
+
+ if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd,
+ ps_inet_recvin6dhcp6, psp) == -1)
+ {
+ logerr("%s: eloop_event_add DHCP", __func__);
+ return -1;
+ }
+
+ logdebugx("spawned listener %s on PID %d", buf, getpid());
+ return 0;
+}
+#endif
+
+static void
+ps_inet_recvmsgpsp(void *arg)
+{
+ struct ps_process *psp = arg;
+
+ /* Receive shutdown. */
+ if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, NULL, NULL) == -1)
+ logerr(__func__);
+}
+
+ssize_t
+ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ uint16_t cmd;
+ struct ps_process *psp;
+ int (*start_func)(void *);
+ pid_t start;
+
+ cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
+ if (cmd == psm->ps_cmd)
+ return ps_inet_sendmsg(ctx, psm, msg);
+
+ psp = ps_findprocess(ctx, &psm->ps_id);
+
+#ifdef PRIVSEP_DEBUG
+ logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
+#endif
+
+ if (psm->ps_cmd & PS_STOP) {
+ assert(psp == NULL);
+ return 0;
+ }
+
+ switch (cmd) {
+#ifdef INET
+ case PS_BOOTP:
+ start_func = ps_inet_listenin;
+ break;
+#endif
+#ifdef INET6
+#ifdef __sun
+ case PS_ND:
+ start_func = ps_inet_listennd;
+ break;
+#endif
+#ifdef DHCP6
+ case PS_DHCP6:
+ start_func = ps_inet_listenin6;
+ break;
+#endif
+#endif
+ default:
+ logerrx("%s: unknown command %x", __func__, psm->ps_cmd);
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (!(psm->ps_cmd & PS_START)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (psp != NULL)
+ return 1;
+
+ psp = ps_newprocess(ctx, &psm->ps_id);
+ if (psp == NULL)
+ return -1;
+
+ start = ps_dostart(ctx,
+ &psp->psp_pid, &psp->psp_fd,
+ ps_inet_recvmsgpsp, NULL, psp,
+ start_func, NULL,
+ PSF_DROPPRIVS);
+ switch (start) {
+ case -1:
+ ps_freeprocess(psp);
+ return -1;
+ case 0:
+ ps_entersandbox("stdio", NULL);
+ break;
+ default:
+ break;
+ }
+ return start;
+}
+
+#ifdef INET
+static ssize_t
+ps_inet_in_docmd(struct ipv4_addr *ia, uint16_t cmd, const struct msghdr *msg)
+{
+ assert(ia != NULL);
+ struct dhcpcd_ctx *ctx = ia->iface->ctx;
+ struct ps_msghdr psm = {
+ .ps_cmd = cmd,
+ .ps_id = {
+ .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)),
+ .psi_ifindex = ia->iface->index,
+ .psi_addr.psa_in_addr = ia->addr,
+ },
+ };
+
+ return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg);
+}
+
+ssize_t
+ps_inet_openbootp(struct ipv4_addr *ia)
+{
+
+ return ps_inet_in_docmd(ia, PS_START | PS_BOOTP, NULL);
+}
+
+ssize_t
+ps_inet_closebootp(struct ipv4_addr *ia)
+{
+
+ return ps_inet_in_docmd(ia, PS_STOP | PS_BOOTP, NULL);
+}
+
+ssize_t
+ps_inet_sendbootp(struct interface *ifp, const struct msghdr *msg)
+{
+
+ return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_BOOTP, 0, msg);
+}
+#endif /* INET */
+
+#ifdef INET6
+#ifdef __sun
+static ssize_t
+ps_inet_ifp_docmd(struct interface *ifp, uint16_t cmd, const struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct ps_msghdr psm = {
+ .ps_cmd = cmd,
+ .ps_id = {
+ .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)),
+ .psi_ifindex = ifp->index,
+ },
+ };
+
+ return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg);
+}
+
+ssize_t
+ps_inet_opennd(struct interface *ifp)
+{
+
+ return ps_inet_ifp_docmd(ifp, PS_ND | PS_START, NULL);
+}
+
+ssize_t
+ps_inet_closend(struct interface *ifp)
+{
+
+ return ps_inet_ifp_docmd(ifp, PS_ND | PS_STOP, NULL);
+}
+
+ssize_t
+ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg)
+{
+
+ return ps_inet_ifp_docmd(ifp, PS_ND, msg);
+}
+#else
+ssize_t
+ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg)
+{
+
+ return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_ND, 0, msg);
+}
+#endif
+
+#ifdef DHCP6
+static ssize_t
+ps_inet_in6_docmd(struct ipv6_addr *ia, uint16_t cmd, const struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = ia->iface->ctx;
+ struct ps_msghdr psm = {
+ .ps_cmd = cmd,
+ .ps_id = {
+ .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)),
+ .psi_ifindex = ia->iface->index,
+ .psi_addr.psa_in6_addr = ia->addr,
+ },
+ };
+
+ return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg);
+}
+
+ssize_t
+ps_inet_opendhcp6(struct ipv6_addr *ia)
+{
+
+ return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_START, NULL);
+}
+
+ssize_t
+ps_inet_closedhcp6(struct ipv6_addr *ia)
+{
+
+ return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_STOP, NULL);
+}
+
+ssize_t
+ps_inet_senddhcp6(struct interface *ifp, const struct msghdr *msg)
+{
+
+ return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_DHCP6, 0, msg);
+}
+#endif /* DHCP6 */
+#endif /* INET6 */
diff --git a/src/privsep-inet.h b/src/privsep-inet.h
new file mode 100644
index 000000000000..dc4a072d8d72
--- /dev/null
+++ b/src/privsep-inet.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2021 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 PRIVSEP_INET_H
+#define PRIVSEP_INET_H
+
+bool ps_inet_canstart(const struct dhcpcd_ctx *);
+pid_t ps_inet_start(struct dhcpcd_ctx *);
+int ps_inet_stop(struct dhcpcd_ctx *);
+ssize_t ps_inet_cmd(struct dhcpcd_ctx *, struct ps_msghdr *, struct msghdr *);
+ssize_t ps_inet_dispatch(void *, struct ps_msghdr *, struct msghdr *);
+
+#ifdef INET
+struct ipv4_addr;
+ssize_t ps_inet_openbootp(struct ipv4_addr *);
+ssize_t ps_inet_closebootp(struct ipv4_addr *);
+ssize_t ps_inet_sendbootp(struct interface *, const struct msghdr *);
+#endif
+
+#ifdef INET6
+struct ipv6_addr;
+#ifdef __sun
+ssize_t ps_inet_opennd(struct interface *);
+ssize_t ps_inet_closend(struct interface *);
+#endif
+ssize_t ps_inet_sendnd(struct interface *, const struct msghdr *);
+#ifdef DHCP6
+ssize_t ps_inet_opendhcp6(struct ipv6_addr *);
+ssize_t ps_inet_closedhcp6(struct ipv6_addr *);
+ssize_t ps_inet_senddhcp6(struct interface *, const struct msghdr *);
+#endif /* DHCP6 */
+#endif /* INET6 */
+#endif
diff --git a/src/privsep-linux.c b/src/privsep-linux.c
new file mode 100644
index 000000000000..ee2e22d2e95d
--- /dev/null
+++ b/src/privsep-linux.c
@@ -0,0 +1,455 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, Linux driver
+ * Copyright (c) 2006-2021 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/prctl.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/termios.h> /* For TCGETS */
+
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/net.h>
+#include <linux/seccomp.h>
+#include <linux/sockios.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "if.h"
+#include "logerr.h"
+#include "privsep.h"
+
+/*
+ * Set this to debug SECCOMP.
+ * Then run dhcpcd with strace -f and strace will even translate
+ * the failing syscall into the __NR_name define we need to use below.
+ * DO NOT ENABLE THIS FOR PRODUCTION BUILDS!
+ */
+//#define SECCOMP_FILTER_DEBUG
+
+static ssize_t
+ps_root_dosendnetlink(int protocol, struct msghdr *msg)
+{
+ struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
+ int s;
+ unsigned char buf[16 * 1024];
+ struct iovec riov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf),
+ };
+ ssize_t retval;
+
+ if ((s = if_linksocket(&snl, protocol, 0)) == -1)
+ return -1;
+
+ if (sendmsg(s, msg, 0) == -1) {
+ retval = -1;
+ goto out;
+ }
+
+ retval = if_getnetlink(NULL, &riov, s, 0, NULL, NULL);
+out:
+ close(s);
+ return retval;
+}
+
+ssize_t
+ps_root_os(struct ps_msghdr *psm, struct msghdr *msg,
+ __unused void **rdata, __unused size_t *rlen)
+{
+
+ switch (psm->ps_cmd) {
+ case PS_ROUTE:
+ return ps_root_dosendnetlink((int)psm->ps_flags, msg);
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+}
+
+ssize_t
+ps_root_sendnetlink(struct dhcpcd_ctx *ctx, int protocol, struct msghdr *msg)
+{
+
+ if (ps_sendmsg(ctx, ctx->ps_root_fd, PS_ROUTE,
+ (unsigned long)protocol, msg) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_ARG_LO 0
+# define SECCOMP_ARG_HI sizeof(uint32_t)
+#elif (BYTE_ORDER == BIG_ENDIAN)
+# define SECCOMP_ARG_LO sizeof(uint32_t)
+# define SECCOMP_ARG_HI 0
+#else
+# error "Uknown endian"
+#endif
+
+#define SECCOMP_ALLOW(_nr) \
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (_nr), 0, 1), \
+ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
+
+#define SECCOMP_ALLOW_ARG(_nr, _arg, _val) \
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (_nr), 0, 6), \
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, \
+ offsetof(struct seccomp_data, args[(_arg)]) + SECCOMP_ARG_LO), \
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, \
+ ((_val) & 0xffffffff), 0, 3), \
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, \
+ offsetof(struct seccomp_data, args[(_arg)]) + SECCOMP_ARG_HI), \
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, \
+ (((uint32_t)((uint64_t)(_val) >> 32)) & 0xffffffff), 0, 1), \
+ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), \
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, \
+ offsetof(struct seccomp_data, nr))
+
+#ifdef SECCOMP_FILTER_DEBUG
+#define SECCOMP_FILTER_FAIL SECCOMP_RET_TRAP
+#else
+#define SECCOMP_FILTER_FAIL SECCOMP_RET_KILL
+#endif
+
+/* I personally find this quite nutty.
+ * Why can a system header not define a default for this? */
+#if defined(__i386__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386
+#elif defined(__x86_64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64
+#elif defined(__arc__)
+# if defined(__A7__)
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCOMPACT
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCOMPACTBE
+# endif
+# elif defined(__HS__)
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCV2
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCV2BE
+# endif
+# else
+# error "Platform does not support seccomp filter yet"
+# endif
+#elif defined(__arm__)
+# ifndef EM_ARM
+# define EM_ARM 40
+# endif
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARM
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARMEB
+# endif
+#elif defined(__aarch64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_AARCH64
+#elif defined(__alpha__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ALPHA
+#elif defined(__hppa__)
+# if defined(__LP64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PARISC64
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PARISC
+# endif
+#elif defined(__ia64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_IA64
+#elif defined(__microblaze__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MICROBLAZE
+#elif defined(__m68k__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_M68K
+#elif defined(__mips__)
+# if defined(__MIPSEL__)
+# if defined(__LP64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPSEL64
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPSEL
+# endif
+# elif defined(__LP64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPS64
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPS
+# endif
+#elif defined(__nds32__)
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_NDS32
+#else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_NDS32BE
+#endif
+#elif defined(__nios2__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_NIOS2
+#elif defined(__or1k__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_OPENRISC
+#elif defined(__powerpc64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PPC64
+#elif defined(__powerpc__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PPC
+#elif defined(__riscv)
+# if defined(__LP64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_RISCV64
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_RISCV32
+# endif
+#elif defined(__s390x__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_S390X
+#elif defined(__s390__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_S390
+#elif defined(__sh__)
+# if defined(__LP64__)
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SHEL64
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SH64
+# endif
+# else
+# if (BYTE_ORDER == LITTLE_ENDIAN)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SHEL
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SH
+# endif
+# endif
+#elif defined(__sparc__)
+# if defined(__arch64__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SPARC64
+# else
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SPARC
+# endif
+#elif defined(__xtensa__)
+# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_XTENSA
+#else
+# error "Platform does not support seccomp filter yet"
+#endif
+
+static struct sock_filter ps_seccomp_filter[] = {
+ /* Check syscall arch */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
+ offsetof(struct seccomp_data, arch)),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_AUDIT_ARCH, 1, 0),
+ BPF_STMT(BPF_RET + BPF_K, SECCOMP_FILTER_FAIL),
+ /* Allow syscalls */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
+ offsetof(struct seccomp_data, nr)),
+#ifdef __NR_accept
+ SECCOMP_ALLOW(__NR_accept),
+#endif
+#ifdef __NR_brk
+ SECCOMP_ALLOW(__NR_brk),
+#endif
+#ifdef __NR_clock_gettime
+ SECCOMP_ALLOW(__NR_clock_gettime),
+#endif
+#if defined(__x86_64__) && defined(__ILP32__) && defined(__X32_SYSCALL_BIT)
+ SECCOMP_ALLOW(__NR_clock_gettime & ~__X32_SYSCALL_BIT),
+#endif
+#ifdef __NR_clock_gettime64
+ SECCOMP_ALLOW(__NR_clock_gettime64),
+#endif
+#ifdef __NR_close
+ SECCOMP_ALLOW(__NR_close),
+#endif
+#ifdef __NR_exit_group
+ SECCOMP_ALLOW(__NR_exit_group),
+#endif
+#ifdef __NR_fcntl
+ SECCOMP_ALLOW(__NR_fcntl),
+#endif
+#ifdef __NR_fcntl64
+ SECCOMP_ALLOW(__NR_fcntl64),
+#endif
+#ifdef __NR_fstat
+ SECCOMP_ALLOW(__NR_fstat),
+#endif
+#ifdef __NR_fstat64
+ SECCOMP_ALLOW(__NR_fstat64),
+#endif
+#ifdef __NR_gettimeofday
+ SECCOMP_ALLOW(__NR_gettimeofday),
+#endif
+#ifdef __NR_getpid
+ SECCOMP_ALLOW(__NR_getpid),
+#endif
+#ifdef __NR_getsockopt
+ /* For route socket overflow */
+ SECCOMP_ALLOW_ARG(__NR_getsockopt, 1, SOL_SOCKET),
+ SECCOMP_ALLOW_ARG(__NR_getsockopt, 2, SO_RCVBUF),
+#endif
+#ifdef __NR_ioctl
+ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFFLAGS),
+ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFHWADDR),
+ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFINDEX),
+ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFMTU),
+ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFVLAN),
+ /* printf over serial terminal requires this */
+ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, TCGETS),
+ /* SECCOMP BPF is newer than nl80211 so we don't need SIOCGIWESSID
+ * which lives in the impossible to include linux/wireless.h header */
+#endif
+#ifdef __NR_mmap
+ SECCOMP_ALLOW(__NR_mmap),
+#endif
+#ifdef __NR_munmap
+ SECCOMP_ALLOW(__NR_munmap),
+#endif
+#ifdef __NR_nanosleep
+ SECCOMP_ALLOW(__NR_nanosleep), /* XXX should use ppoll instead */
+#endif
+#ifdef __NR_ppoll
+ SECCOMP_ALLOW(__NR_ppoll),
+#endif
+#ifdef __NR_ppoll_time64
+ SECCOMP_ALLOW(__NR_ppoll_time64),
+#endif
+#ifdef __NR_read
+ SECCOMP_ALLOW(__NR_read),
+#endif
+#ifdef __NR_readv
+ SECCOMP_ALLOW(__NR_readv),
+#endif
+#ifdef __NR_recv
+ SECCOMP_ALLOW(__NR_recv),
+#endif
+#ifdef __NR_recvfrom
+ SECCOMP_ALLOW(__NR_recvfrom),
+#endif
+#ifdef __NR_recvmsg
+ SECCOMP_ALLOW(__NR_recvmsg),
+#endif
+#ifdef __NR_rt_sigreturn
+ SECCOMP_ALLOW(__NR_rt_sigreturn),
+#endif
+#ifdef __NR_send
+ SECCOMP_ALLOW(__NR_send),
+#endif
+#ifdef __NR_sendmsg
+ SECCOMP_ALLOW(__NR_sendmsg),
+#endif
+#ifdef __NR_sendto
+ SECCOMP_ALLOW(__NR_sendto),
+#endif
+#ifdef __NR_socketcall
+ /* i386 needs this and demonstrates why SECCOMP
+ * is poor compared to OpenBSD pledge(2) and FreeBSD capsicum(4)
+ * as this is soooo tied to the kernel API which changes per arch
+ * and likely libc as well. */
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_ACCEPT),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_ACCEPT4),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_LISTEN),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_GETSOCKOPT), /* overflow */
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_RECV),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_RECVFROM),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_RECVMSG),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SEND),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SENDMSG),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SENDTO),
+ SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SHUTDOWN),
+#endif
+#ifdef __NR_shutdown
+ SECCOMP_ALLOW(__NR_shutdown),
+#endif
+#ifdef __NR_time
+ SECCOMP_ALLOW(__NR_time),
+#endif
+#ifdef __NR_wait4
+ SECCOMP_ALLOW(__NR_wait4),
+#endif
+#ifdef __NR_waitpid
+ SECCOMP_ALLOW(__NR_waitpid),
+#endif
+#ifdef __NR_write
+ SECCOMP_ALLOW(__NR_write),
+#endif
+#ifdef __NR_writev
+ SECCOMP_ALLOW(__NR_writev),
+#endif
+#ifdef __NR_uname
+ SECCOMP_ALLOW(__NR_uname),
+#endif
+
+ /* Deny everything else */
+ BPF_STMT(BPF_RET + BPF_K, SECCOMP_FILTER_FAIL),
+};
+
+static struct sock_fprog ps_seccomp_prog = {
+ .len = (unsigned short)__arraycount(ps_seccomp_filter),
+ .filter = ps_seccomp_filter,
+};
+
+#ifdef SECCOMP_FILTER_DEBUG
+static void
+ps_seccomp_violation(__unused int signum, siginfo_t *si, __unused void *context)
+{
+
+ logerrx("%s: unexpected syscall %d (arch=0x%x)",
+ __func__, si->si_syscall, si->si_arch);
+ _exit(EXIT_FAILURE);
+}
+
+static int
+ps_seccomp_debug(void)
+{
+ struct sigaction sa = {
+ .sa_flags = SA_SIGINFO,
+ .sa_sigaction = &ps_seccomp_violation,
+ };
+ sigset_t mask;
+
+ /* Install a signal handler to catch any issues with our filter. */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGSYS);
+ if (sigaction(SIGSYS, &sa, NULL) == -1 ||
+ sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1)
+ return -1;
+
+ return 0;
+}
+#endif
+
+int
+ps_seccomp_enter(void)
+{
+
+#ifdef SECCOMP_FILTER_DEBUG
+ ps_seccomp_debug();
+#endif
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 ||
+ prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &ps_seccomp_prog) == -1)
+ {
+ if (errno == EINVAL)
+ errno = ENOSYS;
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/privsep-root.c b/src/privsep-root.c
new file mode 100644
index 000000000000..45af3910feed
--- /dev/null
+++ b/src/privsep-root.c
@@ -0,0 +1,1069 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, privileged proxy
+ * Copyright (c) 2006-2021 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/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "auth.h"
+#include "common.h"
+#include "dev.h"
+#include "dhcpcd.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "if.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+#include "sa.h"
+#include "script.h"
+
+__CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long));
+
+struct psr_error
+{
+ ssize_t psr_result;
+ int psr_errno;
+ char psr_pad[sizeof(ssize_t) - sizeof(int)];
+ size_t psr_datalen;
+};
+
+struct psr_ctx {
+ struct dhcpcd_ctx *psr_ctx;
+ struct psr_error psr_error;
+ size_t psr_datalen;
+ void *psr_data;
+};
+
+static void
+ps_root_readerrorcb(void *arg)
+{
+ struct psr_ctx *psr_ctx = arg;
+ struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
+ struct psr_error *psr_error = &psr_ctx->psr_error;
+ struct iovec iov[] = {
+ { .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
+ { .iov_base = psr_ctx->psr_data,
+ .iov_len = psr_ctx->psr_datalen },
+ };
+ ssize_t len;
+ int exit_code = EXIT_FAILURE;
+
+#define PSR_ERROR(e) \
+ do { \
+ psr_error->psr_result = -1; \
+ psr_error->psr_errno = (e); \
+ goto out; \
+ } while (0 /* CONSTCOND */)
+
+ len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
+ if (len == -1)
+ PSR_ERROR(errno);
+ else if ((size_t)len < sizeof(*psr_error))
+ PSR_ERROR(EINVAL);
+ exit_code = EXIT_SUCCESS;
+
+out:
+ eloop_exit(ctx->ps_eloop, exit_code);
+}
+
+ssize_t
+ps_root_readerror(struct dhcpcd_ctx *ctx, void *data, size_t len)
+{
+ struct psr_ctx psr_ctx = {
+ .psr_ctx = ctx,
+ .psr_data = data, .psr_datalen = len,
+ };
+
+ if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
+ ps_root_readerrorcb, &psr_ctx) == -1)
+ return -1;
+
+ eloop_enter(ctx->ps_eloop);
+ eloop_start(ctx->ps_eloop, &ctx->sigset);
+
+ errno = psr_ctx.psr_error.psr_errno;
+ return psr_ctx.psr_error.psr_result;
+}
+
+#ifdef PRIVSEP_GETIFADDRS
+static void
+ps_root_mreaderrorcb(void *arg)
+{
+ struct psr_ctx *psr_ctx = arg;
+ struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
+ struct psr_error *psr_error = &psr_ctx->psr_error;
+ struct iovec iov[] = {
+ { .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
+ { .iov_base = NULL, .iov_len = 0 },
+ };
+ ssize_t len;
+ int exit_code = EXIT_FAILURE;
+
+ len = recv(ctx->ps_root_fd, psr_error, sizeof(*psr_error), MSG_PEEK);
+ if (len == -1)
+ PSR_ERROR(errno);
+ else if ((size_t)len < sizeof(*psr_error))
+ PSR_ERROR(EINVAL);
+
+ if (psr_error->psr_datalen > SSIZE_MAX)
+ PSR_ERROR(ENOBUFS);
+ else if (psr_error->psr_datalen != 0) {
+ psr_ctx->psr_data = malloc(psr_error->psr_datalen);
+ if (psr_ctx->psr_data == NULL)
+ PSR_ERROR(errno);
+ psr_ctx->psr_datalen = psr_error->psr_datalen;
+ iov[1].iov_base = psr_ctx->psr_data;
+ iov[1].iov_len = psr_ctx->psr_datalen;
+ }
+
+ len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
+ if (len == -1)
+ PSR_ERROR(errno);
+ else if ((size_t)len != sizeof(*psr_error) + psr_ctx->psr_datalen)
+ PSR_ERROR(EINVAL);
+ exit_code = EXIT_SUCCESS;
+
+out:
+ eloop_exit(ctx->ps_eloop, exit_code);
+}
+
+ssize_t
+ps_root_mreaderror(struct dhcpcd_ctx *ctx, void **data, size_t *len)
+{
+ struct psr_ctx psr_ctx = {
+ .psr_ctx = ctx,
+ };
+
+ if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
+ ps_root_mreaderrorcb, &psr_ctx) == -1)
+ return -1;
+
+ eloop_enter(ctx->ps_eloop);
+ eloop_start(ctx->ps_eloop, &ctx->sigset);
+
+ errno = psr_ctx.psr_error.psr_errno;
+ *data = psr_ctx.psr_data;
+ *len = psr_ctx.psr_datalen;
+ return psr_ctx.psr_error.psr_result;
+}
+#endif
+
+static ssize_t
+ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result,
+ void *data, size_t len)
+{
+ struct psr_error psr = {
+ .psr_result = result,
+ .psr_errno = errno,
+ .psr_datalen = len,
+ };
+ struct iovec iov[] = {
+ { .iov_base = &psr, .iov_len = sizeof(psr) },
+ { .iov_base = data, .iov_len = len },
+ };
+
+#ifdef PRIVSEP_DEBUG
+ logdebugx("%s: result %zd errno %d", __func__, result, errno);
+#endif
+
+ return writev(ctx->ps_root_fd, iov, __arraycount(iov));
+}
+
+static ssize_t
+ps_root_doioctl(unsigned long req, void *data, size_t len)
+{
+ int s, err;
+
+ /* Only allow these ioctls */
+ switch(req) {
+#ifdef SIOCAIFADDR
+ case SIOCAIFADDR: /* FALLTHROUGH */
+ case SIOCDIFADDR: /* FALLTHROUGH */
+#endif
+#ifdef SIOCSIFHWADDR
+ case SIOCSIFHWADDR: /* FALLTHROUGH */
+#endif
+#ifdef SIOCGIFPRIORITY
+ case SIOCGIFPRIORITY: /* FALLTHROUGH */
+#endif
+ case SIOCSIFFLAGS: /* FALLTHROUGH */
+ case SIOCGIFMTU: /* FALLTHROUGH */
+ case SIOCSIFMTU:
+ break;
+ default:
+ errno = EPERM;
+ return -1;
+ }
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ if (s != -1)
+#ifdef IOCTL_REQUEST_TYPE
+ {
+ ioctl_request_t reqt;
+
+ memcpy(&reqt, &req, sizeof(reqt));
+ err = ioctl(s, reqt, data, len);
+ }
+#else
+ err = ioctl(s, req, data, len);
+#endif
+ else
+ err = -1;
+ if (s != -1)
+ close(s);
+ return err;
+}
+
+static ssize_t
+ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len)
+{
+ const char *envbuf = data;
+ char * const argv[] = { ctx->script, NULL };
+ pid_t pid;
+ int status;
+
+ if (len == 0)
+ return 0;
+
+ if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL)
+ return -1;
+
+ pid = script_exec(argv, ctx->script_env);
+ if (pid == -1)
+ return -1;
+ /* Wait for the script to finish */
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ logerr(__func__);
+ status = 0;
+ break;
+ }
+ }
+ return status;
+}
+
+static bool
+ps_root_validpath(const struct dhcpcd_ctx *ctx, uint16_t cmd, const char *path)
+{
+
+ /* Avoid a previous directory attack to avoid /proc/../
+ * dhcpcd should never use a path with double dots. */
+ if (strstr(path, "..") != NULL)
+ return false;
+
+ if (cmd == PS_READFILE) {
+#ifdef EMBEDDED_CONFIG
+ if (strcmp(ctx->cffile, EMBEDDED_CONFIG) == 0)
+ return true;
+#endif
+ if (strcmp(ctx->cffile, path) == 0)
+ return true;
+ }
+ if (strncmp(DBDIR, path, strlen(DBDIR)) == 0)
+ return true;
+ if (strncmp(RUNDIR, path, strlen(RUNDIR)) == 0)
+ return true;
+
+#ifdef __linux__
+ if (strncmp("/proc/net/", path, strlen("/proc/net/")) == 0 ||
+ strncmp("/proc/sys/net/", path, strlen("/proc/sys/net/")) == 0 ||
+ strncmp("/sys/class/net/", path, strlen("/sys/class/net/")) == 0)
+ return true;
+#endif
+
+ errno = EPERM;
+ return false;
+}
+
+static ssize_t
+ps_root_dowritefile(const struct dhcpcd_ctx *ctx,
+ mode_t mode, void *data, size_t len)
+{
+ char *file = data, *nc;
+
+ nc = memchr(file, '\0', len);
+ if (nc == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!ps_root_validpath(ctx, PS_WRITEFILE, file))
+ return -1;
+ nc++;
+ return writefile(file, mode, nc, len - (size_t)(nc - file));
+}
+
+#ifdef AUTH
+static ssize_t
+ps_root_monordm(uint64_t *rdm, size_t len)
+{
+
+ if (len != sizeof(*rdm)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return auth_get_rdm_monotonic(rdm);
+}
+#endif
+
+#ifdef PRIVSEP_GETIFADDRS
+#define IFA_NADDRS 4
+static ssize_t
+ps_root_dogetifaddrs(void **rdata, size_t *rlen)
+{
+ struct ifaddrs *ifaddrs, *ifa;
+ size_t len;
+ uint8_t *buf, *sap;
+ socklen_t salen;
+
+ if (getifaddrs(&ifaddrs) == -1)
+ return -1;
+ if (ifaddrs == NULL) {
+ *rdata = NULL;
+ *rlen = 0;
+ return 0;
+ }
+
+ /* Work out the buffer length required.
+ * Ensure everything is aligned correctly, which does
+ * create a larger buffer than what is needed to send,
+ * but makes creating the same structure in the client
+ * much easier. */
+ len = 0;
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ len += ALIGN(sizeof(*ifa));
+ len += ALIGN(IFNAMSIZ);
+ len += ALIGN(sizeof(salen) * IFA_NADDRS);
+ if (ifa->ifa_addr != NULL)
+ len += ALIGN(sa_len(ifa->ifa_addr));
+ if (ifa->ifa_netmask != NULL)
+ len += ALIGN(sa_len(ifa->ifa_netmask));
+ if (ifa->ifa_broadaddr != NULL)
+ len += ALIGN(sa_len(ifa->ifa_broadaddr));
+#ifdef BSD
+ /*
+ * On BSD we need to carry ifa_data so we can access
+ * if_data->ifi_link_state
+ */
+ if (ifa->ifa_addr != NULL &&
+ ifa->ifa_addr->sa_family == AF_LINK)
+ len += ALIGN(sizeof(struct if_data));
+#endif
+ }
+
+ /* Use calloc to set everything to zero.
+ * This satisfies memory sanitizers because don't write
+ * where we don't need to. */
+ buf = calloc(1, len);
+ if (buf == NULL) {
+ freeifaddrs(ifaddrs);
+ return -1;
+ }
+ *rdata = buf;
+ *rlen = len;
+
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ memcpy(buf, ifa, sizeof(*ifa));
+ buf += ALIGN(sizeof(*ifa));
+
+ strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ);
+ buf += ALIGN(IFNAMSIZ);
+ sap = buf;
+ buf += ALIGN(sizeof(salen) * IFA_NADDRS);
+
+#define COPYINSA(addr) \
+ do { \
+ if ((addr) != NULL) \
+ salen = sa_len((addr)); \
+ else \
+ salen = 0; \
+ if (salen != 0) { \
+ memcpy(sap, &salen, sizeof(salen)); \
+ memcpy(buf, (addr), salen); \
+ buf += ALIGN(salen); \
+ } \
+ sap += sizeof(salen); \
+ } while (0 /*CONSTCOND */)
+
+ COPYINSA(ifa->ifa_addr);
+ COPYINSA(ifa->ifa_netmask);
+ COPYINSA(ifa->ifa_broadaddr);
+
+#ifdef BSD
+ if (ifa->ifa_addr != NULL &&
+ ifa->ifa_addr->sa_family == AF_LINK)
+ {
+ salen = (socklen_t)sizeof(struct if_data);
+ memcpy(buf, ifa->ifa_data, salen);
+ buf += ALIGN(salen);
+ } else
+#endif
+ salen = 0;
+ memcpy(sap, &salen, sizeof(salen));
+ }
+
+ freeifaddrs(ifaddrs);
+ return 0;
+}
+#endif
+
+static ssize_t
+ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ uint16_t cmd;
+ struct ps_process *psp;
+ struct iovec *iov = msg->msg_iov;
+ void *data = iov->iov_base, *rdata = NULL;
+ size_t len = iov->iov_len, rlen = 0;
+ uint8_t buf[PS_BUFLEN];
+ time_t mtime;
+ ssize_t err;
+ bool free_rdata = false;
+
+ cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
+ psp = ps_findprocess(ctx, &psm->ps_id);
+
+#ifdef PRIVSEP_DEBUG
+ logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
+#endif
+
+ if (psp != NULL) {
+ if (psm->ps_cmd & PS_STOP) {
+ int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
+
+ ps_freeprocess(psp);
+ return ret;
+ } else if (psm->ps_cmd & PS_START) {
+ /* Process has already started .... */
+ return 0;
+ }
+
+ err = ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg);
+ if (err == -1) {
+ logerr("%s: failed to send message to pid %d",
+ __func__, psp->psp_pid);
+ shutdown(psp->psp_fd, SHUT_RDWR);
+ close(psp->psp_fd);
+ psp->psp_fd = -1;
+ ps_freeprocess(psp);
+ }
+ return 0;
+ }
+
+ if (psm->ps_cmd & PS_STOP && psp == NULL)
+ return 0;
+
+ switch (cmd) {
+#ifdef INET
+#ifdef ARP
+ case PS_BPF_ARP: /* FALLTHROUGH */
+#endif
+ case PS_BPF_BOOTP:
+ return ps_bpf_cmd(ctx, psm, msg);
+#endif
+#ifdef INET
+ case PS_BOOTP:
+ return ps_inet_cmd(ctx, psm, msg);
+#endif
+#ifdef INET6
+#ifdef DHCP6
+ case PS_DHCP6: /* FALLTHROUGH */
+#endif
+ case PS_ND:
+ return ps_inet_cmd(ctx, psm, msg);
+#endif
+ default:
+ break;
+ }
+
+ assert(msg->msg_iovlen == 0 || msg->msg_iovlen == 1);
+
+ /* Reset errno */
+ errno = 0;
+
+ switch (psm->ps_cmd) {
+ case PS_IOCTL:
+ err = ps_root_doioctl(psm->ps_flags, data, len);
+ if (err != -1) {
+ rdata = data;
+ rlen = len;
+ }
+ break;
+ case PS_SCRIPT:
+ err = ps_root_run_script(ctx, data, len);
+ break;
+ case PS_UNLINK:
+ if (!ps_root_validpath(ctx, psm->ps_cmd, data)) {
+ err = -1;
+ break;
+ }
+ err = unlink(data);
+ break;
+ case PS_READFILE:
+ if (!ps_root_validpath(ctx, psm->ps_cmd, data)) {
+ err = -1;
+ break;
+ }
+ err = readfile(data, buf, sizeof(buf));
+ if (err != -1) {
+ rdata = buf;
+ rlen = (size_t)err;
+ }
+ break;
+ case PS_WRITEFILE:
+ err = ps_root_dowritefile(ctx, (mode_t)psm->ps_flags,
+ data, len);
+ break;
+ case PS_FILEMTIME:
+ err = filemtime(data, &mtime);
+ if (err != -1) {
+ rdata = &mtime;
+ rlen = sizeof(mtime);
+ }
+ break;
+ case PS_LOGREOPEN:
+ err = logopen(ctx->logfile);
+ break;
+#ifdef AUTH
+ case PS_AUTH_MONORDM:
+ err = ps_root_monordm(data, len);
+ if (err != -1) {
+ rdata = data;
+ rlen = len;
+ }
+ break;
+#endif
+#ifdef PRIVSEP_GETIFADDRS
+ case PS_GETIFADDRS:
+ err = ps_root_dogetifaddrs(&rdata, &rlen);
+ free_rdata = true;
+ break;
+#endif
+#if defined(INET6) && (defined(__linux__) || defined(HAVE_PLEDGE))
+ case PS_IP6FORWARDING:
+ err = ip6_forwarding(data);
+ break;
+#endif
+#ifdef PLUGIN_DEV
+ case PS_DEV_INITTED:
+ err = dev_initialised(ctx, data);
+ break;
+ case PS_DEV_LISTENING:
+ err = dev_listening(ctx);
+ break;
+#endif
+ default:
+ err = ps_root_os(psm, msg, &rdata, &rlen);
+ break;
+ }
+
+ err = ps_root_writeerror(ctx, err, rlen != 0 ? rdata : 0, rlen);
+ if (free_rdata)
+ free(rdata);
+ return err;
+}
+
+/* Receive from state engine, do an action. */
+static void
+ps_root_recvmsg(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1)
+ logerr(__func__);
+}
+
+#ifdef PLUGIN_DEV
+static int
+ps_root_handleinterface(void *arg, int action, const char *ifname)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ unsigned long flag;
+
+ if (action == 1)
+ flag = PS_DEV_IFADDED;
+ else if (action == -1)
+ flag = PS_DEV_IFREMOVED;
+ else if (action == 0)
+ flag = PS_DEV_IFUPDATED;
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return (int)ps_sendcmd(ctx, ctx->ps_data_fd, PS_DEV_IFCMD, flag,
+ ifname, strlen(ifname) + 1);
+}
+#endif
+
+static int
+ps_root_startcb(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ctx->options & DHCPCD_MANAGER)
+ setproctitle("[privileged proxy]");
+ else
+ setproctitle("[privileged proxy] %s%s%s",
+ ctx->ifv[0],
+ ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
+ ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
+ ctx->ps_root_pid = getpid();
+ ctx->options |= DHCPCD_PRIVSEPROOT;
+
+ /* Open network sockets for sending.
+ * This is a small bit wasteful for non sandboxed OS's
+ * but makes life very easy for unicasting DHCPv6 in non manager
+ * mode as we no longer care about address selection.
+ * We can't call shutdown SHUT_RD on the socket because it's
+ * not connectd. All we can do is try and set a zero sized
+ * receive buffer and just let it overflow.
+ * Reading from it just to drain it is a waste of CPU time. */
+#ifdef INET
+ if (ctx->options & DHCPCD_IPV4) {
+ int buflen = 1;
+
+ ctx->udp_wfd = xsocket(PF_INET,
+ SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
+ if (ctx->udp_wfd == -1)
+ logerr("%s: dhcp_openraw", __func__);
+ else if (setsockopt(ctx->udp_wfd, SOL_SOCKET, SO_RCVBUF,
+ &buflen, sizeof(buflen)) == -1)
+ logerr("%s: setsockopt SO_RCVBUF DHCP", __func__);
+ }
+#endif
+#ifdef INET6
+ if (ctx->options & DHCPCD_IPV6) {
+ int buflen = 1;
+
+ ctx->nd_fd = ipv6nd_open(false);
+ if (ctx->nd_fd == -1)
+ logerr("%s: ipv6nd_open", __func__);
+ else if (setsockopt(ctx->nd_fd, SOL_SOCKET, SO_RCVBUF,
+ &buflen, sizeof(buflen)) == -1)
+ logerr("%s: setsockopt SO_RCVBUF ND", __func__);
+ }
+#endif
+#ifdef DHCP6
+ if (ctx->options & DHCPCD_IPV6) {
+ int buflen = 1;
+
+ ctx->dhcp6_wfd = dhcp6_openraw();
+ if (ctx->dhcp6_wfd == -1)
+ logerr("%s: dhcp6_openraw", __func__);
+ else if (setsockopt(ctx->dhcp6_wfd, SOL_SOCKET, SO_RCVBUF,
+ &buflen, sizeof(buflen)) == -1)
+ logerr("%s: setsockopt SO_RCVBUF DHCP6", __func__);
+ }
+#endif
+
+#ifdef PLUGIN_DEV
+ /* Start any dev listening plugin which may want to
+ * change the interface name provided by the kernel */
+ if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_DEV)) ==
+ (DHCPCD_MANAGER | DHCPCD_DEV))
+ dev_start(ctx, ps_root_handleinterface);
+#endif
+
+ return 0;
+}
+
+static void
+ps_root_signalcb(int sig, __unused void *arg)
+{
+
+ if (sig == SIGCHLD) {
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+ return;
+ }
+}
+
+int (*handle_interface)(void *, int, const char *);
+
+#ifdef PLUGIN_DEV
+static ssize_t
+ps_root_devcb(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ int action;
+ struct iovec *iov = msg->msg_iov;
+
+ if (msg->msg_iovlen != 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch(psm->ps_flags) {
+ case PS_DEV_IFADDED:
+ action = 1;
+ break;
+ case PS_DEV_IFREMOVED:
+ action = -1;
+ break;
+ case PS_DEV_IFUPDATED:
+ action = 0;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ return dhcpcd_handleinterface(ctx, action, iov->iov_base);
+}
+#endif
+
+static ssize_t
+ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ ssize_t err;
+
+ switch(psm->ps_cmd) {
+#ifdef PLUGIN_DEV
+ case PS_DEV_IFCMD:
+ err = ps_root_devcb(ctx, psm, msg);
+ break;
+#endif
+ default:
+#ifdef INET
+ err = ps_bpf_dispatch(ctx, psm, msg);
+ if (err == -1 && errno == ENOTSUP)
+#endif
+ err = ps_inet_dispatch(ctx, psm, msg);
+ }
+ return err;
+}
+
+static void
+ps_root_dispatch(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1)
+ logerr(__func__);
+}
+
+static void
+ps_root_log(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (logreadfd(ctx->ps_log_fd) == -1)
+ logerr(__func__);
+}
+
+pid_t
+ps_root_start(struct dhcpcd_ctx *ctx)
+{
+ int logfd[2], datafd[2];
+ pid_t pid;
+
+ if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, logfd) == -1)
+ return -1;
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fdpair(logfd) == -1)
+ return -1;
+#endif
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, datafd) == -1)
+ return -1;
+ if (ps_setbuf_fdpair(datafd) == -1)
+ return -1;
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fdpair(datafd) == -1)
+ return -1;
+#endif
+
+ pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd,
+ ps_root_recvmsg, NULL, ctx,
+ ps_root_startcb, ps_root_signalcb, 0);
+
+ if (pid == 0) {
+ ctx->ps_log_fd = logfd[1];
+ if (eloop_event_add(ctx->eloop, ctx->ps_log_fd,
+ ps_root_log, ctx) == -1)
+ return -1;
+ close(logfd[0]);
+ ctx->ps_data_fd = datafd[1];
+ close(datafd[0]);
+ return 0;
+ } else if (pid == -1)
+ return -1;
+
+ logsetfd(logfd[0]);
+ close(logfd[1]);
+
+ ctx->ps_data_fd = datafd[0];
+ close(datafd[1]);
+ if (eloop_event_add(ctx->eloop, ctx->ps_data_fd,
+ ps_root_dispatch, ctx) == -1)
+ return -1;
+
+ if ((ctx->ps_eloop = eloop_new()) == NULL)
+ return -1;
+
+ eloop_signal_set_cb(ctx->ps_eloop,
+ dhcpcd_signals, dhcpcd_signals_len,
+ ps_root_signalcb, ctx);
+
+ return pid;
+}
+
+int
+ps_root_stop(struct dhcpcd_ctx *ctx)
+{
+
+ return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd);
+}
+
+ssize_t
+ps_root_script(struct dhcpcd_ctx *ctx, const void *data, size_t len)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_SCRIPT, 0, data, len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+ssize_t
+ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data,
+ size_t len)
+{
+#ifdef IOCTL_REQUEST_TYPE
+ unsigned long ulreq = 0;
+
+ memcpy(&ulreq, &req, sizeof(req));
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1)
+ return -1;
+#else
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1)
+ return -1;
+#endif
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_unlink(struct dhcpcd_ctx *ctx, const char *file)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_UNLINK, 0,
+ file, strlen(file) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+ssize_t
+ps_root_readfile(struct dhcpcd_ctx *ctx, const char *file,
+ void *data, size_t len)
+{
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_READFILE, 0,
+ file, strlen(file) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode,
+ const void *data, size_t len)
+{
+ char buf[PS_BUFLEN];
+ size_t flen;
+
+ flen = strlcpy(buf, file, sizeof(buf));
+ flen += 1;
+ if (flen > sizeof(buf) || flen + len > sizeof(buf)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(buf + flen, data, len);
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_WRITEFILE, mode,
+ buf, flen + len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+ssize_t
+ps_root_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_FILEMTIME, 0,
+ file, strlen(file) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, time, sizeof(*time));
+}
+
+ssize_t
+ps_root_logreopen(struct dhcpcd_ctx *ctx)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_LOGREOPEN, 0, NULL, 0) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+#ifdef PRIVSEP_GETIFADDRS
+int
+ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead)
+{
+ struct ifaddrs *ifa;
+ void *buf = NULL;
+ char *bp, *sap;
+ socklen_t salen;
+ size_t len;
+ ssize_t err;
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd,
+ PS_GETIFADDRS, 0, NULL, 0) == -1)
+ return -1;
+ err = ps_root_mreaderror(ctx, &buf, &len);
+
+ if (err == -1)
+ return -1;
+
+ /* Should be impossible - lo0 will always exist. */
+ if (len == 0) {
+ *ifahead = NULL;
+ return 0;
+ }
+
+ bp = buf;
+ *ifahead = (struct ifaddrs *)(void *)bp;
+ for (ifa = *ifahead; ifa != NULL; ifa = ifa->ifa_next) {
+ if (len < ALIGN(sizeof(*ifa)) +
+ ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS))
+ goto err;
+ bp += ALIGN(sizeof(*ifa));
+ ifa->ifa_name = bp;
+ bp += ALIGN(IFNAMSIZ);
+ sap = bp;
+ bp += ALIGN(sizeof(salen) * IFA_NADDRS);
+ len -= ALIGN(sizeof(*ifa)) +
+ ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS);
+
+#define COPYOUTSA(addr) \
+ do { \
+ memcpy(&salen, sap, sizeof(salen)); \
+ if (len < salen) \
+ goto err; \
+ if (salen != 0) { \
+ (addr) = (struct sockaddr *)bp; \
+ bp += ALIGN(salen); \
+ len -= ALIGN(salen); \
+ } \
+ sap += sizeof(salen); \
+ } while (0 /* CONSTCOND */)
+
+ COPYOUTSA(ifa->ifa_addr);
+ COPYOUTSA(ifa->ifa_netmask);
+ COPYOUTSA(ifa->ifa_broadaddr);
+
+ memcpy(&salen, sap, sizeof(salen));
+ if (len < salen)
+ goto err;
+ if (salen != 0) {
+ ifa->ifa_data = bp;
+ bp += ALIGN(salen);
+ len -= ALIGN(salen);
+ } else
+ ifa->ifa_data = NULL;
+
+ if (len != 0)
+ ifa->ifa_next = (struct ifaddrs *)(void *)bp;
+ else
+ ifa->ifa_next = NULL;
+ }
+ return 0;
+
+err:
+ free(buf);
+ *ifahead = NULL;
+ errno = EINVAL;
+ return -1;
+}
+#endif
+
+#if defined(__linux__) || defined(HAVE_PLEDGE)
+ssize_t
+ps_root_ip6forwarding(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IP6FORWARDING, 0,
+ ifname, ifname != NULL ? strlen(ifname) + 1 : 0) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+#endif
+
+#ifdef AUTH
+int
+ps_root_getauthrdm(struct dhcpcd_ctx *ctx, uint64_t *rdm)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_AUTH_MONORDM, 0,
+ rdm, sizeof(*rdm))== -1)
+ return -1;
+ return (int)ps_root_readerror(ctx, rdm, sizeof(*rdm));
+}
+#endif
+
+#ifdef PLUGIN_DEV
+int
+ps_root_dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_INITTED, 0,
+ ifname, strlen(ifname) + 1)== -1)
+ return -1;
+ return (int)ps_root_readerror(ctx, NULL, 0);
+}
+
+int
+ps_root_dev_listening(struct dhcpcd_ctx * ctx)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_LISTENING, 0, NULL, 0)== -1)
+ return -1;
+ return (int)ps_root_readerror(ctx, NULL, 0);
+}
+#endif
diff --git a/src/privsep-root.h b/src/privsep-root.h
new file mode 100644
index 000000000000..7fdd9f69f212
--- /dev/null
+++ b/src/privsep-root.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2021 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 PRIVSEP_ROOT_H
+#define PRIVSEP_ROOT_H
+
+#include "if.h"
+
+#if defined(PRIVSEP) && (defined(HAVE_CAPSICUM) || defined(__linux__))
+#define PRIVSEP_GETIFADDRS
+#endif
+
+pid_t ps_root_start(struct dhcpcd_ctx *ctx);
+int ps_root_stop(struct dhcpcd_ctx *ctx);
+
+ssize_t ps_root_readerror(struct dhcpcd_ctx *, void *, size_t);
+ssize_t ps_root_mreaderror(struct dhcpcd_ctx *, void **, size_t *);
+ssize_t ps_root_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t);
+ssize_t ps_root_ip6forwarding(struct dhcpcd_ctx *, const char *);
+ssize_t ps_root_unlink(struct dhcpcd_ctx *, const char *);
+ssize_t ps_root_filemtime(struct dhcpcd_ctx *, const char *, time_t *);
+ssize_t ps_root_readfile(struct dhcpcd_ctx *, const char *, void *, size_t);
+ssize_t ps_root_writefile(struct dhcpcd_ctx *, const char *, mode_t,
+ const void *, size_t);
+ssize_t ps_root_logreopen(struct dhcpcd_ctx *);
+ssize_t ps_root_script(struct dhcpcd_ctx *, const void *, size_t);
+int ps_root_getauthrdm(struct dhcpcd_ctx *, uint64_t *);
+#ifdef PRIVSEP_GETIFADDRS
+int ps_root_getifaddrs(struct dhcpcd_ctx *, struct ifaddrs **);
+#endif
+
+ssize_t ps_root_os(struct ps_msghdr *, struct msghdr *, void **, size_t *);
+#if defined(BSD) || defined(__sun)
+ssize_t ps_root_route(struct dhcpcd_ctx *, void *, size_t);
+ssize_t ps_root_ioctllink(struct dhcpcd_ctx *, unsigned long, void *, size_t);
+ssize_t ps_root_ioctl6(struct dhcpcd_ctx *, unsigned long, void *, size_t);
+ssize_t ps_root_indirectioctl(struct dhcpcd_ctx *, unsigned long, const char *,
+ void *, size_t);
+ssize_t ps_root_ifignoregroup(struct dhcpcd_ctx *, const char *);
+#endif
+#ifdef __linux__
+ssize_t ps_root_sendnetlink(struct dhcpcd_ctx *, int, struct msghdr *);
+#endif
+
+#ifdef PLUGIN_DEV
+int ps_root_dev_initialised(struct dhcpcd_ctx *, const char *);
+int ps_root_dev_listening(struct dhcpcd_ctx *);
+#endif
+
+#endif
diff --git a/src/privsep-sun.c b/src/privsep-sun.c
new file mode 100644
index 000000000000..8043eed6ef65
--- /dev/null
+++ b/src/privsep-sun.c
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, Solaris driver
+ * Copyright (c) 2006-2021 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 <errno.h>
+#include <unistd.h>
+
+#include "dhcpcd.h"
+#include "logerr.h"
+#include "privsep.h"
+
+#warning Solaris privsep should compile but wont work,
+#warning no DLPI support, ioctl support need rework
+/* We should implement privileges(5) as well.
+ * https://illumos.org/man/5/privileges */
+
+static ssize_t
+ps_root_doioctl6(unsigned long req, void *data, size_t len)
+{
+ int s, err;
+
+ s = socket(PF_INET6, SOCK_DGRAM, 0);
+ if (s != -1)
+ err = ioctl(s, req, data, len);
+ else
+ err = -1;
+ if (err == -1)
+ logerr(__func__);
+ if (s != -1)
+ close(s);
+ return err;
+}
+
+static ssize_t
+ps_root_doroute(void *data, size_t len)
+{
+ int s;
+ ssize_t err;
+
+ s = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s != -1)
+ err = write(s, data, len);
+ else
+ err = -1;
+ if (err == -1)
+ logerr(__func__);
+ if (s != -1)
+ close(s);
+ return err;
+}
+
+ssize_t
+ps_root_os(struct ps_msghdr *psm, struct msghdr *msg,
+ void **rdata, size_t *rlen)
+{
+ struct iovec *iov = msg->msg_iov;
+ void *data = iov->iov_base;
+ size_t len = iov->iov_len;
+ ssize_t err;
+
+ switch (psm->ps_cmd) {
+ case PS_IOCTL6:
+ err = ps_root_doioctl6(psm->ps_flags, data, len);
+ case PS_ROUTE:
+ return ps_root_doroute(data, len);
+ default:
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (err != -1) {
+ *rdata = data;
+ *rlen = len;
+ }
+ return err;
+}
+
+ssize_t
+ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request, void *data, size_t len)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL6,
+ request, data, len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_ROUTE, 0, data, len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
diff --git a/src/privsep.c b/src/privsep.c
new file mode 100644
index 000000000000..d574a2bcaee7
--- /dev/null
+++ b/src/privsep.c
@@ -0,0 +1,1029 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2021 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.
+ */
+
+/*
+ * The current design is this:
+ * Spawn a priv process to carry out privileged actions and
+ * spawning unpriv process to initate network connections such as BPF
+ * or address specific listener.
+ * Spawn an unpriv process to send/receive common network data.
+ * Then drop all privs and start running.
+ * Every process aside from the privileged proxy is chrooted.
+ * All privsep processes ignore signals - only the manager process accepts them.
+ *
+ * dhcpcd will maintain the config file in the chroot, no need to handle
+ * this in a script or something.
+ */
+
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#ifdef AF_LINK
+#include <net/if_dl.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stddef.h> /* For offsetof, struct padding debug */
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "common.h"
+#include "control.h"
+#include "dev.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+
+#ifdef HAVE_CAPSICUM
+#include <sys/capsicum.h>
+#include <capsicum_helpers.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+/* CMSG_ALIGN is a Linux extension */
+#ifndef CMSG_ALIGN
+#define CMSG_ALIGN(n) (CMSG_SPACE((n)) - CMSG_SPACE(0))
+#endif
+
+/* Calculate number of padding bytes to achieve 'struct cmsghdr' alignment */
+#define CALC_CMSG_PADLEN(has_cmsg, pos) \
+ ((has_cmsg) ? (socklen_t)(CMSG_ALIGN((pos)) - (pos)) : 0)
+
+int
+ps_init(struct dhcpcd_ctx *ctx)
+{
+ struct passwd *pw;
+ struct stat st;
+
+ errno = 0;
+ if ((ctx->ps_user = pw = getpwnam(PRIVSEP_USER)) == NULL) {
+ ctx->options &= ~DHCPCD_PRIVSEP;
+ if (errno == 0) {
+ logerrx("no such user %s", PRIVSEP_USER);
+ /* Just incase logerrx caused an error... */
+ errno = 0;
+ } else
+ logerr("getpwnam");
+ return -1;
+ }
+
+ if (stat(pw->pw_dir, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ ctx->options &= ~DHCPCD_PRIVSEP;
+ logerrx("refusing chroot: %s: %s",
+ PRIVSEP_USER, pw->pw_dir);
+ errno = 0;
+ return -1;
+ }
+
+ ctx->options |= DHCPCD_PRIVSEP;
+ return 0;
+}
+
+static int
+ps_dropprivs(struct dhcpcd_ctx *ctx)
+{
+ struct passwd *pw = ctx->ps_user;
+
+ if (ctx->options & DHCPCD_LAUNCHER)
+ logdebugx("chrooting as %s to %s", pw->pw_name, pw->pw_dir);
+ if (chroot(pw->pw_dir) == -1 &&
+ (errno != EPERM || ctx->options & DHCPCD_FORKED))
+ logerr("%s: chroot: %s", __func__, pw->pw_dir);
+ if (chdir("/") == -1)
+ logerr("%s: chdir: /", __func__);
+
+ if ((setgroups(1, &pw->pw_gid) == -1 ||
+ setgid(pw->pw_gid) == -1 ||
+ setuid(pw->pw_uid) == -1) &&
+ (errno != EPERM || ctx->options & DHCPCD_FORKED))
+ {
+ logerr("failed to drop privileges");
+ return -1;
+ }
+
+ struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 };
+
+ if (ctx->ps_control_pid != getpid()) {
+ /* Prohibit new files, sockets, etc */
+#if defined(__linux__) || defined(__sun) || defined(__OpenBSD__)
+ /*
+ * If poll(2) is called with nfds > RLIMIT_NOFILE
+ * then it returns EINVAL.
+ * This blows.
+ * Do the best we can and limit to what we need.
+ * An attacker could potentially close a file and
+ * open a new one still, but that cannot be helped.
+ */
+ unsigned long maxfd;
+ maxfd = (unsigned long)eloop_event_count(ctx->eloop);
+ if (IN_PRIVSEP_SE(ctx))
+ maxfd++; /* XXX why? */
+
+ struct rlimit rmaxfd = {
+ .rlim_cur = maxfd,
+ .rlim_max = maxfd
+ };
+ if (setrlimit(RLIMIT_NOFILE, &rmaxfd) == -1)
+ logerr("setrlimit RLIMIT_NOFILE");
+#else
+ if (setrlimit(RLIMIT_NOFILE, &rzero) == -1)
+ logerr("setrlimit RLIMIT_NOFILE");
+#endif
+ }
+
+#define DHC_NOCHKIO (DHCPCD_STARTED | DHCPCD_DAEMONISE)
+ /* Prohibit writing to files.
+ * Obviously this won't work if we are using a logfile
+ * or redirecting stderr to a file. */
+ if ((ctx->options & DHC_NOCHKIO) == DHC_NOCHKIO ||
+ (ctx->logfile == NULL &&
+ (!ctx->stderr_valid || isatty(STDERR_FILENO) == 1)))
+ {
+ if (setrlimit(RLIMIT_FSIZE, &rzero) == -1)
+ logerr("setrlimit RLIMIT_FSIZE");
+ }
+
+#ifdef RLIMIT_NPROC
+ /* Prohibit forks */
+ if (setrlimit(RLIMIT_NPROC, &rzero) == -1)
+ logerr("setrlimit RLIMIT_NPROC");
+#endif
+
+ return 0;
+}
+
+static int
+ps_setbuf0(int fd, int ctl, int minlen)
+{
+ int len;
+ socklen_t slen;
+
+ slen = sizeof(len);
+ if (getsockopt(fd, SOL_SOCKET, ctl, &len, &slen) == -1)
+ return -1;
+
+#ifdef __linux__
+ len /= 2;
+#endif
+ if (len >= minlen)
+ return 0;
+
+ return setsockopt(fd, SOL_SOCKET, ctl, &minlen, sizeof(minlen));
+}
+
+static int
+ps_setbuf(int fd)
+{
+ /* Ensure we can receive a fully sized privsep message.
+ * Double the send buffer. */
+ int minlen = (int)sizeof(struct ps_msg);
+
+ if (ps_setbuf0(fd, SO_RCVBUF, minlen) == -1 ||
+ ps_setbuf0(fd, SO_SNDBUF, minlen * 2) == -1)
+ {
+ logerr(__func__);
+ return -1;
+ }
+ return 0;
+}
+
+int
+ps_setbuf_fdpair(int fd[])
+{
+
+ if (ps_setbuf(fd[0]) == -1 || ps_setbuf(fd[1]) == -1)
+ return -1;
+ return 0;
+}
+
+#ifdef PRIVSEP_RIGHTS
+int
+ps_rights_limit_ioctl(int fd)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_IOCTL);
+ if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
+ return -1;
+ return 0;
+}
+
+int
+ps_rights_limit_fd_fctnl(int fd)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT,
+ CAP_ACCEPT, CAP_FCNTL);
+ if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
+ return -1;
+ return 0;
+}
+
+int
+ps_rights_limit_fd(int fd)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_SHUTDOWN);
+ if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
+ return -1;
+ return 0;
+}
+
+int
+ps_rights_limit_fd_sockopt(int fd)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT,
+ CAP_GETSOCKOPT, CAP_SETSOCKOPT);
+ if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
+ return -1;
+ return 0;
+}
+
+int
+ps_rights_limit_fd_rdonly(int fd)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_READ, CAP_EVENT);
+ if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
+ return -1;
+ return 0;
+}
+
+int
+ps_rights_limit_fdpair(int fd[])
+{
+
+ if (ps_rights_limit_fd(fd[0]) == -1 || ps_rights_limit_fd(fd[1]) == -1)
+ return -1;
+ return 0;
+}
+
+static int
+ps_rights_limit_stdio(struct dhcpcd_ctx *ctx)
+{
+ const int iebadf = CAPH_IGNORE_EBADF;
+ int error = 0;
+
+ if (ctx->stdin_valid &&
+ caph_limit_stream(STDIN_FILENO, CAPH_READ | iebadf) == -1)
+ error = -1;
+ if (ctx->stdout_valid &&
+ caph_limit_stream(STDOUT_FILENO, CAPH_WRITE | iebadf) == -1)
+ error = -1;
+ if (ctx->stderr_valid &&
+ caph_limit_stream(STDERR_FILENO, CAPH_WRITE | iebadf) == -1)
+ error = -1;
+
+ return error;
+}
+#endif
+
+pid_t
+ps_dostart(struct dhcpcd_ctx *ctx,
+ pid_t *priv_pid, int *priv_fd,
+ void (*recv_msg)(void *), void (*recv_unpriv_msg),
+ void *recv_ctx, int (*callback)(void *), void (*signal_cb)(int, void *),
+ unsigned int flags)
+{
+ int fd[2];
+ pid_t pid;
+
+ if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1) {
+ logerr("%s: socketpair", __func__);
+ return -1;
+ }
+ if (ps_setbuf_fdpair(fd) == -1) {
+ logerr("%s: ps_setbuf_fdpair", __func__);
+ return -1;
+ }
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fdpair(fd) == -1) {
+ logerr("%s: ps_rights_limit_fdpair", __func__);
+ return -1;
+ }
+#endif
+
+ switch (pid = fork()) {
+ case -1:
+ logerr("fork");
+ return -1;
+ case 0:
+ *priv_fd = fd[1];
+ close(fd[0]);
+ break;
+ default:
+ *priv_pid = pid;
+ *priv_fd = fd[0];
+ close(fd[1]);
+ if (recv_unpriv_msg == NULL)
+ ;
+ else if (eloop_event_add(ctx->eloop, *priv_fd,
+ recv_unpriv_msg, recv_ctx) == -1)
+ {
+ logerr("%s: eloop_event_add", __func__);
+ return -1;
+ }
+ return pid;
+ }
+
+ ctx->options |= DHCPCD_FORKED;
+ if (ctx->fork_fd != -1) {
+ close(ctx->fork_fd);
+ ctx->fork_fd = -1;
+ }
+ pidfile_clean();
+
+ eloop_clear(ctx->eloop);
+ eloop_signal_set_cb(ctx->eloop,
+ dhcpcd_signals, dhcpcd_signals_len, signal_cb, ctx);
+ /* ctx->sigset aready has the initial sigmask set in main() */
+ if (eloop_signal_mask(ctx->eloop, NULL) == -1) {
+ logerr("%s: eloop_signal_mask", __func__);
+ goto errexit;
+ }
+
+ /* We are not root */
+ if (priv_fd != &ctx->ps_root_fd) {
+ ps_freeprocesses(ctx, recv_ctx);
+ if (ctx->ps_root_fd != -1) {
+ close(ctx->ps_root_fd);
+ ctx->ps_root_fd = -1;
+ }
+
+#ifdef PRIVSEP_RIGHTS
+ /* We cannot limit the root process in any way. */
+ if (ps_rights_limit_stdio(ctx) == -1) {
+ logerr("ps_rights_limit_stdio");
+ goto errexit;
+ }
+#endif
+ }
+
+ if (priv_fd != &ctx->ps_inet_fd && ctx->ps_inet_fd != -1) {
+ close(ctx->ps_inet_fd);
+ ctx->ps_inet_fd = -1;
+ }
+
+ if (eloop_event_add(ctx->eloop, *priv_fd, recv_msg, recv_ctx) == -1)
+ {
+ logerr("%s: eloop_event_add", __func__);
+ goto errexit;
+ }
+
+ if (callback(recv_ctx) == -1)
+ goto errexit;
+
+ if (flags & PSF_DROPPRIVS)
+ ps_dropprivs(ctx);
+
+ return 0;
+
+errexit:
+ /* Failure to start root or inet processes is fatal. */
+ if (priv_fd == &ctx->ps_root_fd || priv_fd == &ctx->ps_inet_fd)
+ (void)ps_sendcmd(ctx, *priv_fd, PS_STOP, 0, NULL, 0);
+ shutdown(*priv_fd, SHUT_RDWR);
+ *priv_fd = -1;
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return -1;
+}
+
+int
+ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd)
+{
+ int err = 0;
+
+#ifdef PRIVSEP_DEBUG
+ logdebugx("%s: pid=%d fd=%d", __func__, *pid, *fd);
+#endif
+
+ if (*fd != -1) {
+ eloop_event_delete(ctx->eloop, *fd);
+ if (ps_sendcmd(ctx, *fd, PS_STOP, 0, NULL, 0) == -1) {
+ logerr(__func__);
+ err = -1;
+ }
+ (void)shutdown(*fd, SHUT_RDWR);
+ close(*fd);
+ *fd = -1;
+ }
+
+ /* Don't wait for the process as it may not respond to the shutdown
+ * request. We'll reap the process on receipt of SIGCHLD. */
+ *pid = 0;
+ return err;
+}
+
+int
+ps_start(struct dhcpcd_ctx *ctx)
+{
+ pid_t pid;
+
+ TAILQ_INIT(&ctx->ps_processes);
+
+ switch (pid = ps_root_start(ctx)) {
+ case -1:
+ logerr("ps_root_start");
+ return -1;
+ case 0:
+ return 0;
+ default:
+ logdebugx("spawned privileged proxy on PID %d", pid);
+ }
+
+ /* No point in spawning the generic network listener if we're
+ * not going to use it. */
+ if (!ps_inet_canstart(ctx))
+ goto started_net;
+
+ switch (pid = ps_inet_start(ctx)) {
+ case -1:
+ return -1;
+ case 0:
+ return 0;
+ default:
+ logdebugx("spawned network proxy on PID %d", pid);
+ }
+
+started_net:
+ if (!(ctx->options & DHCPCD_TEST)) {
+ switch (pid = ps_ctl_start(ctx)) {
+ case -1:
+ return -1;
+ case 0:
+ return 0;
+ default:
+ logdebugx("spawned controller proxy on PID %d", pid);
+ }
+ }
+
+#ifdef ARC4RANDOM_H
+ /* Seed the random number generator early incase it needs /dev/urandom
+ * which won't be available in the chroot. */
+ arc4random();
+#endif
+
+ return 1;
+}
+
+int
+ps_entersandbox(const char *_pledge, const char **sandbox)
+{
+
+#if !defined(HAVE_PLEDGE)
+ UNUSED(_pledge);
+#endif
+
+#if defined(HAVE_CAPSICUM)
+ if (sandbox != NULL)
+ *sandbox = "capsicum";
+ return cap_enter();
+#elif defined(HAVE_PLEDGE)
+ if (sandbox != NULL)
+ *sandbox = "pledge";
+ return pledge(_pledge, NULL);
+#elif defined(HAVE_SECCOMP)
+ if (sandbox != NULL)
+ *sandbox = "seccomp";
+ return ps_seccomp_enter();
+#else
+ if (sandbox != NULL)
+ *sandbox = "posix resource limited";
+ return 0;
+#endif
+}
+
+int
+ps_managersandbox(struct dhcpcd_ctx *ctx, const char *_pledge)
+{
+ const char *sandbox = NULL;
+ bool forked;
+ int dropped;
+
+ forked = ctx->options & DHCPCD_FORKED;
+ ctx->options &= ~DHCPCD_FORKED;
+ dropped = ps_dropprivs(ctx);
+ if (forked)
+ ctx->options |= DHCPCD_FORKED;
+
+ /*
+ * If we don't have a root process, we cannot use syslog.
+ * If it cannot be opened before chrooting then syslog(3) will fail.
+ * openlog(3) does not return an error which doubly sucks.
+ */
+ if (ctx->ps_root_fd == -1) {
+ unsigned int logopts = loggetopts();
+
+ logopts &= ~LOGERR_LOG;
+ logsetopts(logopts);
+ }
+
+ if (dropped == -1) {
+ logerr("%s: ps_dropprivs", __func__);
+ return -1;
+ }
+
+#ifdef PRIVSEP_RIGHTS
+ if ((ctx->pf_inet_fd != -1 &&
+ ps_rights_limit_ioctl(ctx->pf_inet_fd) == -1) ||
+ ps_rights_limit_stdio(ctx) == -1)
+ {
+ logerr("%s: cap_rights_limit", __func__);
+ return -1;
+ }
+#endif
+
+ if (_pledge == NULL)
+ _pledge = "stdio";
+ if (ps_entersandbox(_pledge, &sandbox) == -1) {
+ if (errno == ENOSYS) {
+ if (sandbox != NULL)
+ logwarnx("sandbox unavailable: %s", sandbox);
+ return 0;
+ }
+ logerr("%s: %s", __func__, sandbox);
+ return -1;
+ } else if (ctx->options & DHCPCD_LAUNCHER ||
+ ((!(ctx->options & DHCPCD_DAEMONISE)) &&
+ ctx->options & DHCPCD_MANAGER))
+ logdebugx("sandbox: %s", sandbox);
+ return 0;
+}
+
+int
+ps_stop(struct dhcpcd_ctx *ctx)
+{
+ int r, ret = 0;
+
+ if (!(ctx->options & DHCPCD_PRIVSEP) ||
+ ctx->options & DHCPCD_FORKED ||
+ ctx->eloop == NULL)
+ return 0;
+
+ r = ps_ctl_stop(ctx);
+ if (r != 0)
+ ret = r;
+
+ r = ps_inet_stop(ctx);
+ if (r != 0)
+ ret = r;
+
+ /* We've been chrooted, so we need to tell the
+ * privileged proxy to remove the pidfile. */
+ ps_root_unlink(ctx, ctx->pidfile);
+
+ r = ps_root_stop(ctx);
+ if (r != 0)
+ ret = r;
+
+ ctx->options &= ~DHCPCD_PRIVSEP;
+ return ret;
+}
+
+void
+ps_freeprocess(struct ps_process *psp)
+{
+
+ TAILQ_REMOVE(&psp->psp_ctx->ps_processes, psp, next);
+ if (psp->psp_fd != -1) {
+ eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd);
+ close(psp->psp_fd);
+ }
+ if (psp->psp_work_fd != -1) {
+ eloop_event_delete(psp->psp_ctx->eloop, psp->psp_work_fd);
+ close(psp->psp_work_fd);
+ }
+#ifdef INET
+ if (psp->psp_bpf != NULL)
+ bpf_close(psp->psp_bpf);
+#endif
+ free(psp);
+}
+
+static void
+ps_free(struct dhcpcd_ctx *ctx)
+{
+ struct ps_process *psp;
+ bool stop = ctx->ps_root_pid == getpid();
+
+ while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) {
+ if (stop)
+ ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
+ ps_freeprocess(psp);
+ }
+}
+
+int
+ps_unrollmsg(struct msghdr *msg, struct ps_msghdr *psm,
+ const void *data, size_t len)
+{
+ uint8_t *datap, *namep, *controlp;
+ socklen_t cmsg_padlen =
+ CALC_CMSG_PADLEN(psm->ps_controllen, psm->ps_namelen);
+
+ namep = UNCONST(data);
+ controlp = namep + psm->ps_namelen + cmsg_padlen;
+ datap = controlp + psm->ps_controllen;
+
+ if (psm->ps_namelen != 0) {
+ if (psm->ps_namelen > len) {
+ errno = EINVAL;
+ return -1;
+ }
+ msg->msg_name = namep;
+ len -= psm->ps_namelen;
+ } else
+ msg->msg_name = NULL;
+ msg->msg_namelen = psm->ps_namelen;
+
+ if (psm->ps_controllen != 0) {
+ if (psm->ps_controllen > len) {
+ errno = EINVAL;
+ return -1;
+ }
+ msg->msg_control = controlp;
+ len -= psm->ps_controllen + cmsg_padlen;
+ } else
+ msg->msg_control = NULL;
+ msg->msg_controllen = psm->ps_controllen;
+
+ if (len != 0) {
+ msg->msg_iovlen = 1;
+ msg->msg_iov[0].iov_base = datap;
+ msg->msg_iov[0].iov_len = len;
+ } else {
+ msg->msg_iovlen = 0;
+ msg->msg_iov[0].iov_base = NULL;
+ msg->msg_iov[0].iov_len = 0;
+ }
+ return 0;
+}
+
+ssize_t
+ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd,
+ struct ps_msghdr *psm, const struct msghdr *msg)
+{
+ long padding[1] = { 0 };
+ struct iovec iov[] = {
+ { .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) },
+ { .iov_base = NULL, }, /* name */
+ { .iov_base = NULL, }, /* control padding */
+ { .iov_base = NULL, }, /* control */
+ { .iov_base = NULL, }, /* payload 1 */
+ { .iov_base = NULL, }, /* payload 2 */
+ { .iov_base = NULL, }, /* payload 3 */
+ };
+ int iovlen;
+ ssize_t len;
+
+ if (msg != NULL) {
+ struct iovec *iovp = &iov[1];
+ int i;
+ socklen_t cmsg_padlen;
+
+ psm->ps_namelen = msg->msg_namelen;
+ psm->ps_controllen = (socklen_t)msg->msg_controllen;
+
+ iovp->iov_base = msg->msg_name;
+ iovp->iov_len = msg->msg_namelen;
+ iovp++;
+
+ cmsg_padlen =
+ CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen);
+ assert(cmsg_padlen <= sizeof(padding));
+ iovp->iov_len = cmsg_padlen;
+ iovp->iov_base = cmsg_padlen != 0 ? padding : NULL;
+ iovp++;
+
+ iovp->iov_base = msg->msg_control;
+ iovp->iov_len = msg->msg_controllen;
+ iovlen = 4;
+
+ for (i = 0; i < (int)msg->msg_iovlen; i++) {
+ if ((size_t)(iovlen + i) > __arraycount(iov)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ iovp++;
+ iovp->iov_base = msg->msg_iov[i].iov_base;
+ iovp->iov_len = msg->msg_iov[i].iov_len;
+ }
+ iovlen += i;
+ } else
+ iovlen = 1;
+
+ len = writev(fd, iov, iovlen);
+ if (len == -1) {
+ logerr(__func__);
+ if (ctx->options & DHCPCD_FORKED &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ }
+ return len;
+}
+
+ssize_t
+ps_sendpsmdata(struct dhcpcd_ctx *ctx, int fd,
+ struct ps_msghdr *psm, const void *data, size_t len)
+{
+ struct iovec iov[] = {
+ { .iov_base = UNCONST(data), .iov_len = len },
+ };
+ struct msghdr msg = {
+ .msg_iov = iov, .msg_iovlen = 1,
+ };
+
+ return ps_sendpsmmsg(ctx, fd, psm, &msg);
+}
+
+
+ssize_t
+ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
+ const struct msghdr *msg)
+{
+ struct ps_msghdr psm = {
+ .ps_cmd = cmd,
+ .ps_flags = flags,
+ .ps_namelen = msg->msg_namelen,
+ .ps_controllen = (socklen_t)msg->msg_controllen,
+ };
+ size_t i;
+
+ for (i = 0; i < (size_t)msg->msg_iovlen; i++)
+ psm.ps_datalen += msg->msg_iov[i].iov_len;
+
+#if 0 /* For debugging structure padding. */
+ logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family));
+ logerrx("psa.pad %lu %zu", offsetof(struct ps_addr, psa_pad), sizeof(psm.ps_id.psi_addr.psa_pad));
+ logerrx("psa.psa_u %lu %zu", offsetof(struct ps_addr, psa_u), sizeof(psm.ps_id.psi_addr.psa_u));
+ logerrx("psa %zu", sizeof(psm.ps_id.psi_addr));
+
+ logerrx("psi.addr %lu %zu", offsetof(struct ps_id, psi_addr), sizeof(psm.ps_id.psi_addr));
+ logerrx("psi.index %lu %zu", offsetof(struct ps_id, psi_ifindex), sizeof(psm.ps_id.psi_ifindex));
+ logerrx("psi.cmd %lu %zu", offsetof(struct ps_id, psi_cmd), sizeof(psm.ps_id.psi_cmd));
+ logerrx("psi.pad %lu %zu", offsetof(struct ps_id, psi_pad), sizeof(psm.ps_id.psi_pad));
+ logerrx("psi %zu", sizeof(struct ps_id));
+
+ logerrx("ps_cmd %lu", offsetof(struct ps_msghdr, ps_cmd));
+ logerrx("ps_pad %lu %zu", offsetof(struct ps_msghdr, ps_pad), sizeof(psm.ps_pad));
+ logerrx("ps_flags %lu %zu", offsetof(struct ps_msghdr, ps_flags), sizeof(psm.ps_flags));
+
+ logerrx("ps_id %lu %zu", offsetof(struct ps_msghdr, ps_id), sizeof(psm.ps_id));
+
+ logerrx("ps_namelen %lu %zu", offsetof(struct ps_msghdr, ps_namelen), sizeof(psm.ps_namelen));
+ logerrx("ps_controllen %lu %zu", offsetof(struct ps_msghdr, ps_controllen), sizeof(psm.ps_controllen));
+ logerrx("ps_pad2 %lu %zu", offsetof(struct ps_msghdr, ps_pad2), sizeof(psm.ps_pad2));
+ logerrx("ps_datalen %lu %zu", offsetof(struct ps_msghdr, ps_datalen), sizeof(psm.ps_datalen));
+ logerrx("psm %zu", sizeof(psm));
+#endif
+
+ return ps_sendpsmmsg(ctx, fd, &psm, msg);
+}
+
+ssize_t
+ps_sendcmd(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
+ const void *data, size_t len)
+{
+ struct ps_msghdr psm = {
+ .ps_cmd = cmd,
+ .ps_flags = flags,
+ };
+ struct iovec iov[] = {
+ { .iov_base = UNCONST(data), .iov_len = len }
+ };
+ struct msghdr msg = {
+ .msg_iov = iov, .msg_iovlen = 1,
+ };
+
+ return ps_sendpsmmsg(ctx, fd, &psm, &msg);
+}
+
+static ssize_t
+ps_sendcmdmsg(int fd, uint16_t cmd, const struct msghdr *msg)
+{
+ struct ps_msghdr psm = { .ps_cmd = cmd };
+ uint8_t data[PS_BUFLEN], *p = data;
+ struct iovec iov[] = {
+ { .iov_base = &psm, .iov_len = sizeof(psm) },
+ { .iov_base = data, .iov_len = 0 },
+ };
+ size_t dl = sizeof(data);
+ socklen_t cmsg_padlen =
+ CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen);
+
+ if (msg->msg_namelen != 0) {
+ if (msg->msg_namelen > dl)
+ goto nobufs;
+ psm.ps_namelen = msg->msg_namelen;
+ memcpy(p, msg->msg_name, msg->msg_namelen);
+ p += msg->msg_namelen;
+ dl -= msg->msg_namelen;
+ }
+
+ if (msg->msg_controllen != 0) {
+ if (msg->msg_controllen + cmsg_padlen > dl)
+ goto nobufs;
+ if (cmsg_padlen != 0) {
+ memset(p, 0, cmsg_padlen);
+ p += cmsg_padlen;
+ dl -= cmsg_padlen;
+ }
+ psm.ps_controllen = (socklen_t)msg->msg_controllen;
+ memcpy(p, msg->msg_control, msg->msg_controllen);
+ p += msg->msg_controllen;
+ dl -= msg->msg_controllen;
+ }
+
+ psm.ps_datalen = msg->msg_iov[0].iov_len;
+ if (psm.ps_datalen > dl)
+ goto nobufs;
+
+ iov[1].iov_len =
+ psm.ps_namelen + psm.ps_controllen + psm.ps_datalen + cmsg_padlen;
+ if (psm.ps_datalen != 0)
+ memcpy(p, msg->msg_iov[0].iov_base, psm.ps_datalen);
+ return writev(fd, iov, __arraycount(iov));
+
+nobufs:
+ errno = ENOBUFS;
+ return -1;
+}
+
+ssize_t
+ps_recvmsg(struct dhcpcd_ctx *ctx, int rfd, uint16_t cmd, int wfd)
+{
+ struct sockaddr_storage ss = { .ss_family = AF_UNSPEC };
+ uint8_t controlbuf[sizeof(struct sockaddr_storage)] = { 0 };
+ uint8_t databuf[64 * 1024];
+ struct iovec iov[] = {
+ { .iov_base = databuf, .iov_len = sizeof(databuf) }
+ };
+ struct msghdr msg = {
+ .msg_name = &ss, .msg_namelen = sizeof(ss),
+ .msg_control = controlbuf, .msg_controllen = sizeof(controlbuf),
+ .msg_iov = iov, .msg_iovlen = 1,
+ };
+
+ ssize_t len = recvmsg(rfd, &msg, 0);
+
+ if (len == -1)
+ logerr("%s: recvmsg", __func__);
+ if (len == -1 || len == 0) {
+ if (ctx->options & DHCPCD_FORKED &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ eloop_exit(ctx->eloop,
+ len == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ return len;
+ }
+
+ iov[0].iov_len = (size_t)len;
+ len = ps_sendcmdmsg(wfd, cmd, &msg);
+ if (len == -1) {
+ logerr("ps_sendcmdmsg");
+ if (ctx->options & DHCPCD_FORKED &&
+ !(ctx->options & DHCPCD_PRIVSEPROOT))
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ }
+ return len;
+}
+
+ssize_t
+ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd,
+ ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *),
+ void *cbctx)
+{
+ struct ps_msg psm;
+ ssize_t len;
+ size_t dlen;
+ struct iovec iov[1];
+ struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 };
+ bool stop = false;
+
+ len = read(fd, &psm, sizeof(psm));
+#ifdef PRIVSEP_DEBUG
+ logdebugx("%s: %zd", __func__, len);
+#endif
+
+ if (len == -1 || len == 0)
+ stop = true;
+ else {
+ dlen = (size_t)len;
+ if (dlen < sizeof(psm.psm_hdr)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (psm.psm_hdr.ps_cmd == PS_STOP) {
+ stop = true;
+ len = 0;
+ }
+ }
+
+ if (stop) {
+#ifdef PRIVSEP_DEBUG
+ logdebugx("process %d stopping", getpid());
+#endif
+ ps_free(ctx);
+#ifdef PLUGIN_DEV
+ dev_stop(ctx);
+#endif
+ eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE);
+ return len;
+ }
+ dlen -= sizeof(psm.psm_hdr);
+
+ if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1)
+ return -1;
+
+ if (callback == NULL)
+ return 0;
+
+ errno = 0;
+ return callback(cbctx, &psm.psm_hdr, &msg);
+}
+
+struct ps_process *
+ps_findprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
+{
+ struct ps_process *psp;
+
+ TAILQ_FOREACH(psp, &ctx->ps_processes, next) {
+ if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0)
+ return psp;
+ }
+ errno = ESRCH;
+ return NULL;
+}
+
+struct ps_process *
+ps_newprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
+{
+ struct ps_process *psp;
+
+ psp = calloc(1, sizeof(*psp));
+ if (psp == NULL)
+ return NULL;
+ psp->psp_ctx = ctx;
+ memcpy(&psp->psp_id, psid, sizeof(psp->psp_id));
+ psp->psp_work_fd = -1;
+ TAILQ_INSERT_TAIL(&ctx->ps_processes, psp, next);
+ return psp;
+}
+
+void
+ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis)
+{
+ struct ps_process *psp, *psn;
+
+ TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) {
+ if (psp == notthis)
+ continue;
+ ps_freeprocess(psp);
+ }
+}
diff --git a/src/privsep.h b/src/privsep.h
new file mode 100644
index 000000000000..d843dda8fdf0
--- /dev/null
+++ b/src/privsep.h
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2021 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 PRIVSEP_H
+#define PRIVSEP_H
+
+//#define PRIVSEP_DEBUG
+
+/* Start flags */
+#define PSF_DROPPRIVS 0x01
+
+/* Protocols */
+#define PS_BOOTP 0x0001
+#define PS_ND 0x0002
+#define PS_DHCP6 0x0003
+#define PS_BPF_BOOTP 0x0004
+#define PS_BPF_ARP 0x0005
+
+/* Generic commands */
+#define PS_IOCTL 0x0010
+#define PS_ROUTE 0x0011 /* Also used for NETLINK */
+#define PS_SCRIPT 0x0012
+#define PS_UNLINK 0x0013
+#define PS_READFILE 0x0014
+#define PS_WRITEFILE 0x0015
+#define PS_FILEMTIME 0x0016
+#define PS_AUTH_MONORDM 0x0017
+#define PS_CTL 0x0018
+#define PS_CTL_EOF 0x0019
+#define PS_LOGREOPEN 0x0020
+
+/* BSD Commands */
+#define PS_IOCTLLINK 0x0101
+#define PS_IOCTL6 0x0102
+#define PS_IOCTLINDIRECT 0x0103
+#define PS_IP6FORWARDING 0x0104
+#define PS_GETIFADDRS 0x0105
+#define PS_IFIGNOREGRP 0x0106
+
+/* Dev Commands */
+#define PS_DEV_LISTENING 0x1001
+#define PS_DEV_INITTED 0x1002
+#define PS_DEV_IFCMD 0x1003
+
+/* Dev Interface Commands (via flags) */
+#define PS_DEV_IFADDED 0x0001
+#define PS_DEV_IFREMOVED 0x0002
+#define PS_DEV_IFUPDATED 0x0003
+
+/* Control Type (via flags) */
+#define PS_CTL_PRIV 0x0004
+#define PS_CTL_UNPRIV 0x0005
+
+/* Process commands */
+#define PS_START 0x4000
+#define PS_STOP 0x8000
+
+/* Max INET message size + meta data for IPC */
+#define PS_BUFLEN ((64 * 1024) + \
+ sizeof(struct ps_msghdr) + \
+ sizeof(struct msghdr) + \
+ CMSG_SPACE(sizeof(struct in6_pktinfo) + \
+ sizeof(int)))
+
+/* Handy macro to work out if in the privsep engine or not. */
+#define IN_PRIVSEP(ctx) \
+ ((ctx)->options & DHCPCD_PRIVSEP)
+#define IN_PRIVSEP_SE(ctx) \
+ (((ctx)->options & (DHCPCD_PRIVSEP | DHCPCD_FORKED)) == DHCPCD_PRIVSEP)
+
+#if defined(PRIVSEP) && defined(HAVE_CAPSICUM)
+#define PRIVSEP_RIGHTS
+#endif
+
+#ifdef __linux__
+# include <linux/version.h>
+# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
+# define HAVE_SECCOMP
+# endif
+#endif
+
+#include "config.h"
+#include "arp.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
+
+struct ps_addr {
+ sa_family_t psa_family;
+ uint8_t psa_pad[4 - sizeof(sa_family_t)];
+ union {
+ struct in_addr psau_in_addr;
+ struct in6_addr psau_in6_addr;
+ } psa_u;
+#define psa_in_addr psa_u.psau_in_addr
+#define psa_in6_addr psa_u.psau_in6_addr
+};
+
+/* Uniquely identify a process */
+struct ps_id {
+ struct ps_addr psi_addr;
+ unsigned int psi_ifindex;
+ uint16_t psi_cmd;
+ uint8_t psi_pad[2];
+};
+
+struct ps_msghdr {
+ uint16_t ps_cmd;
+ uint8_t ps_pad[sizeof(unsigned long) - sizeof(uint16_t)];
+ unsigned long ps_flags;
+ struct ps_id ps_id;
+ socklen_t ps_namelen;
+ socklen_t ps_controllen;
+ uint8_t ps_pad2[sizeof(size_t) - sizeof(socklen_t)];
+ size_t ps_datalen;
+};
+
+struct ps_msg {
+ struct ps_msghdr psm_hdr;
+ uint8_t psm_data[PS_BUFLEN];
+};
+
+struct bpf;
+struct ps_process {
+ TAILQ_ENTRY(ps_process) next;
+ struct dhcpcd_ctx *psp_ctx;
+ struct ps_id psp_id;
+ pid_t psp_pid;
+ int psp_fd;
+ int psp_work_fd;
+ unsigned int psp_ifindex;
+ char psp_ifname[IF_NAMESIZE];
+ uint16_t psp_proto;
+ const char *psp_protostr;
+
+#ifdef INET
+ int (*psp_filter)(const struct bpf *, const struct in_addr *);
+ struct interface psp_ifp; /* Move BPF gubbins elsewhere */
+ struct bpf *psp_bpf;
+#endif
+};
+TAILQ_HEAD(ps_process_head, ps_process);
+
+#include "privsep-control.h"
+#include "privsep-inet.h"
+#include "privsep-root.h"
+#ifdef INET
+#include "privsep-bpf.h"
+#endif
+
+int ps_init(struct dhcpcd_ctx *);
+int ps_start(struct dhcpcd_ctx *);
+int ps_stop(struct dhcpcd_ctx *);
+int ps_entersandbox(const char *, const char **);
+int ps_managersandbox(struct dhcpcd_ctx *, const char *);
+
+int ps_unrollmsg(struct msghdr *, struct ps_msghdr *, const void *, size_t);
+ssize_t ps_sendpsmmsg(struct dhcpcd_ctx *, int,
+ struct ps_msghdr *, const struct msghdr *);
+ssize_t ps_sendpsmdata(struct dhcpcd_ctx *, int,
+ struct ps_msghdr *, const void *, size_t);
+ssize_t ps_sendmsg(struct dhcpcd_ctx *, int, uint16_t, unsigned long,
+ const struct msghdr *);
+ssize_t ps_sendcmd(struct dhcpcd_ctx *, int, uint16_t, unsigned long,
+ const void *data, size_t len);
+ssize_t ps_recvmsg(struct dhcpcd_ctx *, int, uint16_t, int);
+ssize_t ps_recvpsmsg(struct dhcpcd_ctx *, int,
+ ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *), void *);
+
+/* Internal privsep functions. */
+int ps_setbuf_fdpair(int []);
+
+#ifdef PRIVSEP_RIGHTS
+int ps_rights_limit_ioctl(int);
+int ps_rights_limit_fd_fctnl(int);
+int ps_rights_limit_fd_rdonly(int);
+int ps_rights_limit_fd_sockopt(int);
+int ps_rights_limit_fd(int);
+int ps_rights_limit_fdpair(int []);
+#endif
+
+#ifdef HAVE_SECCOMP
+int ps_seccomp_enter(void);
+#endif
+
+pid_t ps_dostart(struct dhcpcd_ctx * ctx,
+ pid_t *priv_pid, int *priv_fd,
+ void (*recv_msg)(void *), void (*recv_unpriv_msg),
+ void *recv_ctx, int (*callback)(void *), void (*)(int, void *),
+ unsigned int);
+int ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd);
+
+struct ps_process *ps_findprocess(struct dhcpcd_ctx *, struct ps_id *);
+struct ps_process *ps_newprocess(struct dhcpcd_ctx *, struct ps_id *);
+void ps_freeprocess(struct ps_process *);
+void ps_freeprocesses(struct dhcpcd_ctx *, struct ps_process *);
+#endif
diff --git a/src/route.c b/src/route.c
new file mode 100644
index 000000000000..ef9c41258000
--- /dev/null
+++ b/src/route.c
@@ -0,0 +1,799 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - route management
+ * Copyright (c) 2006-2021 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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4.h"
+#include "ipv4ll.h"
+#include "ipv6.h"
+#include "logerr.h"
+#include "route.h"
+#include "sa.h"
+
+/* Needed for NetBSD-6, 7 and 8. */
+#ifndef RB_TREE_FOREACH_SAFE
+#ifndef RB_TREE_PREV
+#define RB_TREE_NEXT(T, N) rb_tree_iterate((T), (N), RB_DIR_RIGHT)
+#define RB_TREE_PREV(T, N) rb_tree_iterate((T), (N), RB_DIR_LEFT)
+#endif
+#define RB_TREE_FOREACH_SAFE(N, T, S) \
+ for ((N) = RB_TREE_MIN(T); \
+ (N) && ((S) = RB_TREE_NEXT((T), (N)), 1); \
+ (N) = (S))
+#define RB_TREE_FOREACH_REVERSE_SAFE(N, T, S) \
+ for ((N) = RB_TREE_MAX(T); \
+ (N) && ((S) = RB_TREE_PREV((T), (N)), 1); \
+ (N) = (S))
+#endif
+
+#ifdef RT_FREE_ROUTE_TABLE_STATS
+static size_t croutes;
+static size_t nroutes;
+static size_t froutes;
+static size_t mroutes;
+#endif
+
+static void
+rt_maskedaddr(struct sockaddr *dst,
+ const struct sockaddr *addr, const struct sockaddr *netmask)
+{
+ const char *addrp = addr->sa_data, *netmaskp = netmask->sa_data;
+ char *dstp = dst->sa_data;
+ const char *addre = (char *)dst + sa_len(addr);
+ const char *netmaske = (char *)dst + MIN(sa_len(addr), sa_len(netmask));
+
+ dst->sa_family = addr->sa_family;
+#ifdef HAVE_SA_LEN
+ dst->sa_len = addr->sa_len;
+#endif
+
+ if (sa_is_unspecified(netmask)) {
+ if (addre > dstp)
+ memcpy(dstp, addrp, (size_t)(addre - dstp));
+ return;
+ }
+
+ while (dstp < netmaske)
+ *dstp++ = *addrp++ & *netmaskp++;
+ if (dstp < addre)
+ memset(dstp, 0, (size_t)(addre - dstp));
+}
+
+/*
+ * On some systems, host routes have no need for a netmask.
+ * However DHCP specifies host routes using an all-ones netmask.
+ * This handy function allows easy comparison when the two
+ * differ.
+ */
+static int
+rt_cmp_netmask(const struct rt *rt1, const struct rt *rt2)
+{
+
+ if (rt1->rt_flags & RTF_HOST && rt2->rt_flags & RTF_HOST)
+ return 0;
+ return sa_cmp(&rt1->rt_netmask, &rt2->rt_netmask);
+}
+
+int
+rt_cmp_dest(const struct rt *rt1, const struct rt *rt2)
+{
+ union sa_ss ma1 = { .sa.sa_family = AF_UNSPEC };
+ union sa_ss ma2 = { .sa.sa_family = AF_UNSPEC };
+ int c;
+
+ rt_maskedaddr(&ma1.sa, &rt1->rt_dest, &rt1->rt_netmask);
+ rt_maskedaddr(&ma2.sa, &rt2->rt_dest, &rt2->rt_netmask);
+ c = sa_cmp(&ma1.sa, &ma2.sa);
+ if (c != 0)
+ return c;
+
+ return rt_cmp_netmask(rt1, rt2);
+}
+
+static int
+rt_compare_os(__unused void *context, const void *node1, const void *node2)
+{
+ const struct rt *rt1 = node1, *rt2 = node2;
+ int c;
+
+ /* Sort by masked destination. */
+ c = rt_cmp_dest(rt1, rt2);
+ if (c != 0)
+ return c;
+
+#ifdef HAVE_ROUTE_METRIC
+ c = (int)(rt1->rt_ifp->metric - rt2->rt_ifp->metric);
+#endif
+ return c;
+}
+
+static int
+rt_compare_list(__unused void *context, const void *node1, const void *node2)
+{
+ const struct rt *rt1 = node1, *rt2 = node2;
+
+ if (rt1->rt_order > rt2->rt_order)
+ return 1;
+ if (rt1->rt_order < rt2->rt_order)
+ return -1;
+ return 0;
+}
+
+static int
+rt_compare_proto(void *context, const void *node1, const void *node2)
+{
+ const struct rt *rt1 = node1, *rt2 = node2;
+ int c;
+ struct interface *ifp1, *ifp2;
+
+ assert(rt1->rt_ifp != NULL);
+ assert(rt2->rt_ifp != NULL);
+ ifp1 = rt1->rt_ifp;
+ ifp2 = rt2->rt_ifp;
+
+ /* Prefer interfaces with a carrier. */
+ c = ifp1->carrier - ifp2->carrier;
+ if (c != 0)
+ return -c;
+
+ /* Prefer roaming over non roaming if both carriers are down. */
+ if (ifp1->carrier == LINK_DOWN && ifp2->carrier == LINK_DOWN) {
+ bool roam1 = if_roaming(ifp1);
+ bool roam2 = if_roaming(ifp2);
+
+ if (roam1 != roam2)
+ return roam1 ? 1 : -1;
+ }
+
+#ifdef INET
+ /* IPv4LL routes always come last */
+ if (rt1->rt_dflags & RTDF_IPV4LL && !(rt2->rt_dflags & RTDF_IPV4LL))
+ return -1;
+ else if (!(rt1->rt_dflags & RTDF_IPV4LL) && rt2->rt_dflags & RTDF_IPV4LL)
+ return 1;
+#endif
+
+ /* Lower metric interfaces come first. */
+ c = (int)(ifp1->metric - ifp2->metric);
+ if (c != 0)
+ return c;
+
+ /* Finally the order in which the route was given to us. */
+ return rt_compare_list(context, rt1, rt2);
+}
+
+static const rb_tree_ops_t rt_compare_os_ops = {
+ .rbto_compare_nodes = rt_compare_os,
+ .rbto_compare_key = rt_compare_os,
+ .rbto_node_offset = offsetof(struct rt, rt_tree),
+ .rbto_context = NULL
+};
+
+const rb_tree_ops_t rt_compare_list_ops = {
+ .rbto_compare_nodes = rt_compare_list,
+ .rbto_compare_key = rt_compare_list,
+ .rbto_node_offset = offsetof(struct rt, rt_tree),
+ .rbto_context = NULL
+};
+
+const rb_tree_ops_t rt_compare_proto_ops = {
+ .rbto_compare_nodes = rt_compare_proto,
+ .rbto_compare_key = rt_compare_proto,
+ .rbto_node_offset = offsetof(struct rt, rt_tree),
+ .rbto_context = NULL
+};
+
+#ifdef RT_FREE_ROUTE_TABLE
+static int
+rt_compare_free(__unused void *context, const void *node1, const void *node2)
+{
+
+ return node1 == node2 ? 0 : node1 < node2 ? -1 : 1;
+}
+
+static const rb_tree_ops_t rt_compare_free_ops = {
+ .rbto_compare_nodes = rt_compare_free,
+ .rbto_compare_key = rt_compare_free,
+ .rbto_node_offset = offsetof(struct rt, rt_tree),
+ .rbto_context = NULL
+};
+#endif
+
+void
+rt_init(struct dhcpcd_ctx *ctx)
+{
+
+ rb_tree_init(&ctx->routes, &rt_compare_os_ops);
+#ifdef RT_FREE_ROUTE_TABLE
+ rb_tree_init(&ctx->froutes, &rt_compare_free_ops);
+#endif
+}
+
+bool
+rt_is_default(const struct rt *rt)
+{
+
+ return sa_is_unspecified(&rt->rt_dest) &&
+ sa_is_unspecified(&rt->rt_netmask);
+}
+
+static void
+rt_desc(const char *cmd, const struct rt *rt)
+{
+ char dest[INET_MAX_ADDRSTRLEN], gateway[INET_MAX_ADDRSTRLEN];
+ int prefix;
+ const char *ifname;
+ bool gateway_unspec;
+
+ assert(cmd != NULL);
+ assert(rt != NULL);
+
+ sa_addrtop(&rt->rt_dest, dest, sizeof(dest));
+ prefix = sa_toprefix(&rt->rt_netmask);
+ sa_addrtop(&rt->rt_gateway, gateway, sizeof(gateway));
+ gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
+ ifname = rt->rt_ifp == NULL ? "(null)" : rt->rt_ifp->name;
+
+ if (rt->rt_flags & RTF_HOST) {
+ if (gateway_unspec)
+ loginfox("%s: %s host route to %s",
+ ifname, cmd, dest);
+ else
+ loginfox("%s: %s host route to %s via %s",
+ ifname, cmd, dest, gateway);
+ } else if (rt_is_default(rt)) {
+ if (gateway_unspec)
+ loginfox("%s: %s default route",
+ ifname, cmd);
+ else
+ loginfox("%s: %s default route via %s",
+ ifname, cmd, gateway);
+ } else if (gateway_unspec)
+ loginfox("%s: %s%s route to %s/%d",
+ ifname, cmd,
+ rt->rt_flags & RTF_REJECT ? " reject" : "",
+ dest, prefix);
+ else
+ loginfox("%s: %s%s route to %s/%d via %s",
+ ifname, cmd,
+ rt->rt_flags & RTF_REJECT ? " reject" : "",
+ dest, prefix, gateway);
+}
+
+void
+rt_headclear0(struct dhcpcd_ctx *ctx, rb_tree_t *rts, int af)
+{
+ struct rt *rt, *rtn;
+
+ if (rts == NULL)
+ return;
+ assert(ctx != NULL);
+#ifdef RT_FREE_ROUTE_TABLE
+ assert(&ctx->froutes != rts);
+#endif
+
+ RB_TREE_FOREACH_SAFE(rt, rts, rtn) {
+ if (af != AF_UNSPEC &&
+ rt->rt_dest.sa_family != af &&
+ rt->rt_gateway.sa_family != af)
+ continue;
+ rb_tree_remove_node(rts, rt);
+ rt_free(rt);
+ }
+}
+
+void
+rt_headclear(rb_tree_t *rts, int af)
+{
+ struct rt *rt;
+
+ if (rts == NULL || (rt = RB_TREE_MIN(rts)) == NULL)
+ return;
+ rt_headclear0(rt->rt_ifp->ctx, rts, af);
+}
+
+static void
+rt_headfree(rb_tree_t *rts)
+{
+ struct rt *rt;
+
+ while ((rt = RB_TREE_MIN(rts)) != NULL) {
+ rb_tree_remove_node(rts, rt);
+ free(rt);
+ }
+}
+
+void
+rt_dispose(struct dhcpcd_ctx *ctx)
+{
+
+ assert(ctx != NULL);
+ rt_headfree(&ctx->routes);
+#ifdef RT_FREE_ROUTE_TABLE
+ rt_headfree(&ctx->froutes);
+#ifdef RT_FREE_ROUTE_TABLE_STATS
+ logdebugx("free route list used %zu times", froutes);
+ logdebugx("new routes from route free list %zu", nroutes);
+ logdebugx("maximum route free list size %zu", mroutes);
+#endif
+#endif
+}
+
+struct rt *
+rt_new0(struct dhcpcd_ctx *ctx)
+{
+ struct rt *rt;
+
+ assert(ctx != NULL);
+#ifdef RT_FREE_ROUTE_TABLE
+ if ((rt = RB_TREE_MIN(&ctx->froutes)) != NULL) {
+ rb_tree_remove_node(&ctx->froutes, rt);
+#ifdef RT_FREE_ROUTE_TABLE_STATS
+ croutes--;
+ nroutes++;
+#endif
+ } else
+#endif
+ if ((rt = malloc(sizeof(*rt))) == NULL) {
+ logerr(__func__);
+ return NULL;
+ }
+ memset(rt, 0, sizeof(*rt));
+ return rt;
+}
+
+void
+rt_setif(struct rt *rt, struct interface *ifp)
+{
+
+ assert(rt != NULL);
+ assert(ifp != NULL);
+ rt->rt_ifp = ifp;
+#ifdef HAVE_ROUTE_METRIC
+ rt->rt_metric = ifp->metric;
+ if (if_roaming(ifp))
+ rt->rt_metric += RTMETRIC_ROAM;
+#endif
+}
+
+struct rt *
+rt_new(struct interface *ifp)
+{
+ struct rt *rt;
+
+ assert(ifp != NULL);
+ if ((rt = rt_new0(ifp->ctx)) == NULL)
+ return NULL;
+ rt_setif(rt, ifp);
+ return rt;
+}
+
+struct rt *
+rt_proto_add_ctx(rb_tree_t *tree, struct rt *rt, struct dhcpcd_ctx *ctx)
+{
+
+ rt->rt_order = ctx->rt_order++;
+ if (rb_tree_insert_node(tree, rt) == rt)
+ return rt;
+
+ rt_free(rt);
+ errno = EEXIST;
+ return NULL;
+}
+
+struct rt *
+rt_proto_add(rb_tree_t *tree, struct rt *rt)
+{
+
+ assert (rt->rt_ifp != NULL);
+ return rt_proto_add_ctx(tree, rt, rt->rt_ifp->ctx);
+}
+
+void
+rt_free(struct rt *rt)
+{
+#ifdef RT_FREE_ROUTE_TABLE
+ struct dhcpcd_ctx *ctx;
+
+ assert(rt != NULL);
+ if (rt->rt_ifp == NULL) {
+ free(rt);
+ return;
+ }
+
+ ctx = rt->rt_ifp->ctx;
+ rb_tree_insert_node(&ctx->froutes, rt);
+#ifdef RT_FREE_ROUTE_TABLE_STATS
+ croutes++;
+ froutes++;
+ if (croutes > mroutes)
+ mroutes = croutes;
+#endif
+#else
+ free(rt);
+#endif
+}
+
+void
+rt_freeif(struct interface *ifp)
+{
+ struct dhcpcd_ctx *ctx;
+ struct rt *rt, *rtn;
+
+ if (ifp == NULL)
+ return;
+ ctx = ifp->ctx;
+ RB_TREE_FOREACH_SAFE(rt, &ctx->routes, rtn) {
+ if (rt->rt_ifp == ifp) {
+ rb_tree_remove_node(&ctx->routes, rt);
+ rt_free(rt);
+ }
+ }
+}
+
+/* If something other than dhcpcd removes a route,
+ * we need to remove it from our internal table. */
+void
+rt_recvrt(int cmd, const struct rt *rt, pid_t pid)
+{
+ struct dhcpcd_ctx *ctx;
+ struct rt *f;
+
+ assert(rt != NULL);
+ assert(rt->rt_ifp != NULL);
+ assert(rt->rt_ifp->ctx != NULL);
+
+ ctx = rt->rt_ifp->ctx;
+
+ switch(cmd) {
+ case RTM_DELETE:
+ f = rb_tree_find_node(&ctx->routes, rt);
+ if (f != NULL) {
+ char buf[32];
+
+ rb_tree_remove_node(&ctx->routes, f);
+ snprintf(buf, sizeof(buf), "pid %d deleted", pid);
+ rt_desc(buf, f);
+ rt_free(f);
+ }
+ break;
+ }
+
+#if defined(IPV4LL) && defined(HAVE_ROUTE_METRIC)
+ if (rt->rt_dest.sa_family == AF_INET)
+ ipv4ll_recvrt(cmd, rt);
+#endif
+}
+
+static bool
+rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort)
+{
+ struct dhcpcd_ctx *ctx;
+ bool change, kroute, result;
+
+ assert(nrt != NULL);
+ ctx = nrt->rt_ifp->ctx;
+
+ /*
+ * Don't install a gateway if not asked to.
+ * This option is mainly for VPN users who want their VPN to be the
+ * default route.
+ * Because VPN's generally don't care about route management
+ * beyond their own, a longer term solution would be to remove this
+ * and get the VPN to inject the default route into dhcpcd somehow.
+ */
+ if (((nrt->rt_ifp->active &&
+ !(nrt->rt_ifp->options->options & DHCPCD_GATEWAY)) ||
+ (!nrt->rt_ifp->active && !(ctx->options & DHCPCD_GATEWAY))) &&
+ sa_is_unspecified(&nrt->rt_dest) &&
+ sa_is_unspecified(&nrt->rt_netmask))
+ return false;
+
+ rt_desc(ort == NULL ? "adding" : "changing", nrt);
+
+ change = kroute = result = false;
+ if (ort == NULL) {
+ ort = rb_tree_find_node(kroutes, nrt);
+ if (ort != NULL &&
+ ((ort->rt_flags & RTF_REJECT &&
+ nrt->rt_flags & RTF_REJECT) ||
+ (ort->rt_ifp == nrt->rt_ifp &&
+#ifdef HAVE_ROUTE_METRIC
+ ort->rt_metric == nrt->rt_metric &&
+#endif
+ sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)))
+ {
+ if (ort->rt_mtu == nrt->rt_mtu)
+ return true;
+ change = true;
+ kroute = true;
+ }
+ } else if (ort->rt_dflags & RTDF_FAKE &&
+ !(nrt->rt_dflags & RTDF_FAKE) &&
+ ort->rt_ifp == nrt->rt_ifp &&
+#ifdef HAVE_ROUTE_METRIC
+ ort->rt_metric == nrt->rt_metric &&
+#endif
+ sa_cmp(&ort->rt_dest, &nrt->rt_dest) == 0 &&
+ rt_cmp_netmask(ort, nrt) == 0 &&
+ sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)
+ {
+ if (ort->rt_mtu == nrt->rt_mtu)
+ return true;
+ change = true;
+ }
+
+#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->rt_flags & RTF_CLONING)
+ change = false;
+#endif
+
+ if (change) {
+ if (if_route(RTM_CHANGE, nrt) != -1) {
+ result = true;
+ goto out;
+ }
+ if (errno != ESRCH)
+ logerr("if_route (CHG)");
+ }
+
+#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) != -1) {
+ if (ort != NULL) {
+ if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
+ logerr("if_route (DEL)");
+ }
+ result = true;
+ goto out;
+ }
+
+ /* 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. */
+#ifdef ROUTE_PER_GATEWAY
+ errno = 0;
+#endif
+ if (ort != NULL) {
+ if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
+ logerr("if_route (DEL)");
+ else
+ kroute = false;
+ }
+#ifdef ROUTE_PER_GATEWAY
+ /* The OS allows many routes to the same dest with different gateways.
+ * dhcpcd does not support this yet, so for the time being just keep on
+ * deleting the route until there is an error. */
+ if (ort != NULL && errno == 0) {
+ for (;;) {
+ if (if_route(RTM_DELETE, ort) == -1)
+ break;
+ }
+ }
+#endif
+
+ /* Shouldn't need to check for EEXIST, but some kernels don't
+ * dump the subnet route just after we added the address. */
+ if (if_route(RTM_ADD, nrt) != -1 || errno == EEXIST) {
+ result = true;
+ goto out;
+ }
+
+#ifdef HAVE_ROUTE_METRIC
+logerr:
+#endif
+ logerr("if_route (ADD)");
+
+out:
+ if (kroute) {
+ rb_tree_remove_node(kroutes, ort);
+ rt_free(ort);
+ }
+ return result;
+}
+
+static bool
+rt_delete(struct rt *rt)
+{
+ int retval;
+
+ rt_desc("deleting", rt);
+ retval = if_route(RTM_DELETE, rt) == -1 ? false : true;
+ if (!retval && errno != ENOENT && errno != ESRCH)
+ logerr(__func__);
+ return retval;
+}
+
+static bool
+rt_cmp(const struct rt *r1, const struct rt *r2)
+{
+
+ return (r1->rt_ifp == r2->rt_ifp &&
+#ifdef HAVE_ROUTE_METRIC
+ r1->rt_metric == r2->rt_metric &&
+#endif
+ sa_cmp(&r1->rt_gateway, &r2->rt_gateway) == 0);
+}
+
+static bool
+rt_doroute(rb_tree_t *kroutes, struct rt *rt)
+{
+ struct dhcpcd_ctx *ctx;
+ struct rt *or;
+
+ ctx = rt->rt_ifp->ctx;
+ /* Do we already manage it? */
+ or = rb_tree_find_node(&ctx->routes, rt);
+ if (or != NULL) {
+ if (rt->rt_dflags & RTDF_FAKE)
+ return true;
+ if (or->rt_dflags & RTDF_FAKE ||
+ !rt_cmp(rt, or) ||
+ (rt->rt_ifa.sa_family != AF_UNSPEC &&
+ sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) ||
+ or->rt_mtu != rt->rt_mtu)
+ {
+ if (!rt_add(kroutes, rt, or))
+ return false;
+ }
+ rb_tree_remove_node(&ctx->routes, or);
+ rt_free(or);
+ } else {
+ if (rt->rt_dflags & RTDF_FAKE) {
+ or = rb_tree_find_node(kroutes, rt);
+ if (or == NULL)
+ return false;
+ if (!rt_cmp(rt, or))
+ return false;
+ } else {
+ if (!rt_add(kroutes, rt, NULL))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+rt_build(struct dhcpcd_ctx *ctx, int af)
+{
+ rb_tree_t routes, added, kroutes;
+ struct rt *rt, *rtn;
+ unsigned long long o;
+
+ rb_tree_init(&routes, &rt_compare_proto_ops);
+ rb_tree_init(&added, &rt_compare_os_ops);
+ rb_tree_init(&kroutes, &rt_compare_os_ops);
+ if_initrt(ctx, &kroutes, af);
+ ctx->rt_order = 0;
+ ctx->options |= DHCPCD_RTBUILD;
+
+#ifdef INET
+ if (!inet_getroutes(ctx, &routes))
+ goto getfail;
+#endif
+#ifdef INET6
+ if (!inet6_getroutes(ctx, &routes))
+ goto getfail;
+#endif
+
+#ifdef BSD
+ /* Rewind the miss filter */
+ ctx->rt_missfilterlen = 0;
+#endif
+
+ RB_TREE_FOREACH_SAFE(rt, &routes, rtn) {
+ if (rt->rt_ifp->active) {
+ if (!(rt->rt_ifp->options->options & DHCPCD_CONFIGURE))
+ continue;
+ } else if (!(ctx->options & DHCPCD_CONFIGURE))
+ continue;
+#ifdef BSD
+ if (rt_is_default(rt) &&
+ if_missfilter(rt->rt_ifp, &rt->rt_gateway) == -1)
+ logerr("if_missfilter");
+#endif
+ if ((rt->rt_dest.sa_family != af &&
+ rt->rt_dest.sa_family != AF_UNSPEC) ||
+ (rt->rt_gateway.sa_family != af &&
+ rt->rt_gateway.sa_family != AF_UNSPEC))
+ continue;
+ /* Is this route already in our table? */
+ if (rb_tree_find_node(&added, rt) != NULL)
+ continue;
+ if (rt_doroute(&kroutes, rt)) {
+ rb_tree_remove_node(&routes, rt);
+ if (rb_tree_insert_node(&added, rt) != rt) {
+ errno = EEXIST;
+ logerr(__func__);
+ rt_free(rt);
+ }
+ }
+ }
+
+#ifdef BSD
+ if (if_missfilter_apply(ctx) == -1 && errno != ENOTSUP)
+ logerr("if_missfilter_apply");
+#endif
+
+ /* Remove old routes we used to manage. */
+ RB_TREE_FOREACH_REVERSE_SAFE(rt, &ctx->routes, rtn) {
+ if ((rt->rt_dest.sa_family != af &&
+ rt->rt_dest.sa_family != AF_UNSPEC) ||
+ (rt->rt_gateway.sa_family != af &&
+ rt->rt_gateway.sa_family != AF_UNSPEC))
+ continue;
+ rb_tree_remove_node(&ctx->routes, rt);
+ if (rb_tree_find_node(&added, rt) == NULL) {
+ o = rt->rt_ifp->options ?
+ rt->rt_ifp->options->options :
+ ctx->options;
+ if ((o &
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
+ (DHCPCD_EXITING | DHCPCD_PERSISTENT))
+ rt_delete(rt);
+ }
+ rt_free(rt);
+ }
+
+ /* XXX This needs to be optimised. */
+ while ((rt = RB_TREE_MIN(&added)) != NULL) {
+ rb_tree_remove_node(&added, rt);
+ if (rb_tree_insert_node(&ctx->routes, rt) != rt) {
+ errno = EEXIST;
+ logerr(__func__);
+ rt_free(rt);
+ }
+ }
+
+getfail:
+ rt_headclear(&routes, AF_UNSPEC);
+ rt_headclear(&kroutes, AF_UNSPEC);
+}
diff --git a/src/route.h b/src/route.h
new file mode 100644
index 000000000000..45f0e1a7dc96
--- /dev/null
+++ b/src/route.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - route management
+ * Copyright (c) 2006-2021 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 ROUTE_H
+#define ROUTE_H
+
+#ifdef HAVE_SYS_RBTREE_H
+#include <sys/rbtree.h>
+#endif
+
+#include <sys/socket.h>
+#include <net/route.h>
+
+#include <stdbool.h>
+
+#include "dhcpcd.h"
+#include "sa.h"
+
+/*
+ * Enable the route free list by default as
+ * memory usage is still reported as low/unchanged even
+ * when dealing with millions of routes.
+ */
+#if !defined(RT_FREE_ROUTE_TABLE)
+#define RT_FREE_ROUTE_TABLE 1
+#elif RT_FREE_ROUTE_TABLE == 0
+#undef RT_FREE_ROUTE_TABLE
+#endif
+
+/* 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
+
+#ifdef __linux__
+# include <linux/version.h> /* RTA_PREF is only an enum.... */
+# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
+# define HAVE_ROUTE_PREF
+# endif
+#endif
+
+#if defined(__OpenBSD__) || defined (__sun)
+# define ROUTE_PER_GATEWAY
+/* XXX dhcpcd doesn't really support this yet.
+ * But that's generally OK if only dhcpcd is managing routes. */
+#endif
+
+/* OpenBSD defines this as a "convienience" ..... we work around it. */
+#ifdef __OpenBSD__
+#undef rt_mtu
+#endif
+
+struct rt {
+ union sa_ss rt_ss_dest;
+#define rt_dest rt_ss_dest.sa
+ union sa_ss rt_ss_netmask;
+#define rt_netmask rt_ss_netmask.sa
+ union sa_ss rt_ss_gateway;
+#define rt_gateway rt_ss_gateway.sa
+ struct interface *rt_ifp;
+ union sa_ss rt_ss_ifa;
+#define rt_ifa rt_ss_ifa.sa
+ unsigned int rt_flags;
+ unsigned int rt_mtu;
+#ifdef HAVE_ROUTE_METRIC
+ unsigned int rt_metric;
+#endif
+/* Maximum interface index is generally USHORT_MAX or 65535.
+ * Add some padding for other stuff and we get offsets for the
+ * below that should work automatically.
+ * This is only an issue if the user defines higher metrics in
+ * their configuration, but then they might wish to override also. */
+#define RTMETRIC_BASE 1000U
+#define RTMETRIC_WIRELESS 2000U
+#define RTMETRIC_IPV4LL 1000000U
+#define RTMETRIC_ROAM 2000000U
+#ifdef HAVE_ROUTE_PREF
+ int rt_pref;
+#endif
+#define RTPREF_HIGH 1
+#define RTPREF_MEDIUM 0 /* has to be zero */
+#define RTPREF_LOW (-1)
+#define RTPREF_RESERVED (-2)
+#define RTPREF_INVALID (-3) /* internal */
+ unsigned int rt_dflags;
+#define RTDF_IFA_ROUTE 0x01 /* Address generated route */
+#define RTDF_FAKE 0x02 /* Maybe us on lease reboot */
+#define RTDF_IPV4LL 0x04 /* IPv4LL route */
+#define RTDF_RA 0x08 /* Router Advertisement */
+#define RTDF_DHCP 0x10 /* DHCP route */
+#define RTDF_STATIC 0x20 /* Configured in dhcpcd */
+#define RTDF_GATELINK 0x40 /* Gateway is on link */
+ size_t rt_order;
+ rb_node_t rt_tree;
+};
+
+extern const rb_tree_ops_t rt_compare_list_ops;
+extern const rb_tree_ops_t rt_compare_proto_ops;
+
+void rt_init(struct dhcpcd_ctx *);
+void rt_dispose(struct dhcpcd_ctx *);
+void rt_free(struct rt *);
+void rt_freeif(struct interface *);
+bool rt_is_default(const struct rt *);
+void rt_headclear0(struct dhcpcd_ctx *, rb_tree_t *, int);
+void rt_headclear(rb_tree_t *, int);
+void rt_headfreeif(rb_tree_t *);
+struct rt * rt_new0(struct dhcpcd_ctx *);
+void rt_setif(struct rt *, struct interface *);
+struct rt * rt_new(struct interface *);
+struct rt * rt_proto_add_ctx(rb_tree_t *, struct rt *, struct dhcpcd_ctx *);
+struct rt * rt_proto_add(rb_tree_t *, struct rt *);
+int rt_cmp_dest(const struct rt *, const struct rt *);
+void rt_recvrt(int, const struct rt *, pid_t);
+void rt_build(struct dhcpcd_ctx *, int);
+
+#endif
diff --git a/src/sa.c b/src/sa.c
new file mode 100644
index 000000000000..c6a19d1bef7a
--- /dev/null
+++ b/src/sa.c
@@ -0,0 +1,489 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Socket Address handling for dhcpcd
+ * Copyright (c) 2015-2021 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>
+#ifdef AF_LINK
+#include <net/if_dl.h>
+#elif defined(AF_PACKET)
+#include <linux/if_packet.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "config.h"
+#include "common.h"
+#include "sa.h"
+
+#ifndef NDEBUG
+static bool sa_inprefix;
+#endif
+
+socklen_t
+sa_addroffset(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ return offsetof(struct sockaddr_in, sin_addr) +
+ offsetof(struct in_addr, s_addr);
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ return offsetof(struct sockaddr_in6, sin6_addr) +
+ offsetof(struct in6_addr, s6_addr);
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return 0;
+ }
+}
+
+socklen_t
+sa_addrlen(const struct sockaddr *sa)
+{
+#define membersize(type, member) sizeof(((type *)0)->member)
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ return membersize(struct in_addr, s_addr);
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ return membersize(struct in6_addr, s6_addr);
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return 0;
+ }
+}
+
+#ifndef HAVE_SA_LEN
+socklen_t
+sa_len(const struct sockaddr *sa)
+{
+
+ switch (sa->sa_family) {
+#ifdef AF_LINK
+ case AF_LINK:
+ return sizeof(struct sockaddr_dl);
+#endif
+#ifdef AF_PACKET
+ case AF_PACKET:
+ return sizeof(struct sockaddr_ll);
+#endif
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ default:
+ return sizeof(struct sockaddr);
+ }
+}
+#endif
+
+bool
+sa_is_unspecified(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+ case AF_UNSPEC:
+ return true;
+#ifdef INET
+ case AF_INET:
+ return satocsin(sa)->sin_addr.s_addr == INADDR_ANY;
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ return IN6_IS_ADDR_UNSPECIFIED(&satocsin6(sa)->sin6_addr);
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return false;
+ }
+}
+
+#ifdef INET6
+#ifndef IN6MASK128
+#define IN6MASK128 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}
+#endif
+static const struct in6_addr in6allones = IN6MASK128;
+#endif
+
+bool
+sa_is_allones(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+ case AF_UNSPEC:
+ return false;
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+
+ sin = satocsin(sa);
+ return sin->sin_addr.s_addr == INADDR_BROADCAST;
+ }
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = satocsin6(sa);
+ return IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &in6allones);
+ }
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return false;
+ }
+}
+
+bool
+sa_is_loopback(const struct sockaddr *sa)
+{
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+ case AF_UNSPEC:
+ return false;
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+
+ sin = satocsin(sa);
+ return sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK);
+ }
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = satocsin6(sa);
+ return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr);
+ }
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return false;
+ }
+}
+
+int
+sa_toprefix(const struct sockaddr *sa)
+{
+ int prefix;
+
+ assert(sa != NULL);
+ switch(sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+ uint32_t mask;
+
+ sin = satocsin(sa);
+ if (sin->sin_addr.s_addr == INADDR_ANY) {
+ prefix = 0;
+ break;
+ }
+ mask = ntohl(sin->sin_addr.s_addr);
+ prefix = 33 - ffs((int)mask); /* 33 - (1 .. 32) -> 32 .. 1 */
+ if (prefix < 32) { /* more than 1 bit in mask */
+ /* check for non-contig netmask */
+ if ((mask^(((1U << prefix)-1) << (32 - prefix))) != 0) {
+ errno = EINVAL;
+ return -1; /* noncontig, no pfxlen */
+ }
+ }
+ break;
+ }
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+ int x, y;
+ const uint8_t *lim, *p;
+
+ sin6 = satocsin6(sa);
+ p = (const uint8_t *)sin6->sin6_addr.s6_addr;
+ lim = p + sizeof(sin6->sin6_addr.s6_addr);
+ for (x = 0; 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;
+ }
+
+ prefix = x * NBBY + y;
+ break;
+ }
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+#ifndef NDEBUG
+ /* Ensure the calculation is correct */
+ if (!sa_inprefix) {
+ union sa_ss ss = { .sa = { .sa_family = sa->sa_family } };
+
+ sa_inprefix = true;
+ sa_fromprefix(&ss.sa, prefix);
+ assert(sa_cmp(sa, &ss.sa) == 0);
+ sa_inprefix = false;
+ }
+#endif
+
+ return prefix;
+}
+
+int
+sa_fromprefix(struct sockaddr *sa, int prefix)
+{
+ uint8_t *ap;
+ int max_prefix, bytes, bits, i;
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ max_prefix = 32;
+#ifdef HAVE_SA_LEN
+ sa->sa_len = sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ max_prefix = 128;
+#ifdef HAVE_SA_LEN
+ sa->sa_len = sizeof(struct sockaddr_in6);
+#endif
+ break;
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ bytes = prefix / NBBY;
+ bits = prefix % NBBY;
+
+ ap = (uint8_t *)sa + sa_addroffset(sa);
+ for (i = 0; i < bytes; i++)
+ *ap++ = 0xff;
+ if (bits) {
+ uint8_t a;
+
+ a = 0xff;
+ a = (uint8_t)(a << (8 - bits));
+ *ap++ = a;
+ }
+ bytes = (max_prefix - prefix) / NBBY;
+ for (i = 0; i < bytes; i++)
+ *ap++ = 0x00;
+
+#ifndef NDEBUG
+ /* Ensure the calculation is correct */
+ if (!sa_inprefix) {
+ sa_inprefix = true;
+ assert(sa_toprefix(sa) == prefix);
+ sa_inprefix = false;
+ }
+#endif
+ return 0;
+}
+
+/* inet_ntop, but for sockaddr. */
+const char *
+sa_addrtop(const struct sockaddr *sa, char *buf, socklen_t len)
+{
+ const void *addr;
+
+ assert(buf != NULL);
+ assert(len > 0);
+
+ if (sa->sa_family == 0) {
+ *buf = '\0';
+ return NULL;
+ }
+
+#ifdef AF_LINK
+#ifndef CLLADDR
+#define CLLADDR(sdl) (const void *)((sdl)->sdl_data + (sdl)->sdl_nlen)
+#endif
+ if (sa->sa_family == AF_LINK) {
+ const struct sockaddr_dl *sdl;
+
+ sdl = (const void *)sa;
+ if (sdl->sdl_alen == 0) {
+ if (snprintf(buf, len, "link#%d", sdl->sdl_index) == -1)
+ return NULL;
+ return buf;
+ }
+ return hwaddr_ntoa(CLLADDR(sdl), sdl->sdl_alen, buf, len);
+ }
+#elif defined(AF_PACKET)
+ if (sa->sa_family == AF_PACKET) {
+ const struct sockaddr_ll *sll;
+
+ sll = (const void *)sa;
+ return hwaddr_ntoa(sll->sll_addr, sll->sll_halen, buf, len);
+ }
+#endif
+ addr = (const char *)sa + sa_addroffset(sa);
+ return inet_ntop(sa->sa_family, addr, buf, len);
+}
+
+int
+sa_cmp(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+ socklen_t offset, len;
+
+ assert(sa1 != NULL);
+ assert(sa2 != NULL);
+
+ /* Treat AF_UNSPEC as the unspecified address. */
+ if ((sa1->sa_family == AF_UNSPEC || sa2->sa_family == AF_UNSPEC) &&
+ sa_is_unspecified(sa1) && sa_is_unspecified(sa2))
+ return 0;
+
+ if (sa1->sa_family != sa2->sa_family)
+ return sa1->sa_family - sa2->sa_family;
+
+#ifdef HAVE_SA_LEN
+ len = MIN(sa1->sa_len, sa2->sa_len);
+#endif
+
+ switch (sa1->sa_family) {
+#ifdef INET
+ case AF_INET:
+ offset = offsetof(struct sockaddr_in, sin_addr);
+#ifdef HAVE_SA_LEN
+ len -= offset;
+ len = MIN(len, sizeof(struct in_addr));
+#else
+ len = sizeof(struct in_addr);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ offset = offsetof(struct sockaddr_in6, sin6_addr);
+#ifdef HAVE_SA_LEN
+ len -= offset;
+ len = MIN(len, sizeof(struct in6_addr));
+#else
+ len = sizeof(struct in6_addr);
+#endif
+ break;
+#endif
+ default:
+ offset = 0;
+#ifndef HAVE_SA_LEN
+ len = sizeof(struct sockaddr);
+#endif
+ break;
+ }
+
+ return memcmp((const char *)sa1 + offset,
+ (const char *)sa2 + offset,
+ len);
+}
+
+#ifdef INET
+void
+sa_in_init(struct sockaddr *sa, const struct in_addr *addr)
+{
+ struct sockaddr_in *sin;
+
+ assert(sa != NULL);
+ assert(addr != NULL);
+ sin = satosin(sa);
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+ sin->sin_addr.s_addr = addr->s_addr;
+}
+#endif
+
+#ifdef INET6
+void
+sa_in6_init(struct sockaddr *sa, const struct in6_addr *addr)
+{
+ struct sockaddr_in6 *sin6;
+
+ assert(sa != NULL);
+ assert(addr != NULL);
+ sin6 = satosin6(sa);
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+ memcpy(&sin6->sin6_addr.s6_addr, &addr->s6_addr,
+ sizeof(sin6->sin6_addr.s6_addr));
+}
+#endif
diff --git a/src/sa.h b/src/sa.h
new file mode 100644
index 000000000000..69724cd8570c
--- /dev/null
+++ b/src/sa.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Socket Address handling for dhcpcd
+ * Copyright (c) 2015-2021 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 SA_H
+#define SA_H
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+union sa_ss {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+};
+
+#ifdef BSD
+#define HAVE_SA_LEN
+#endif
+
+/* Allow for a sockaddr_dl being printed too. */
+#define INET_MAX_ADDRSTRLEN (20 * 3)
+
+#ifdef INET
+#define satosin(sa) ((struct sockaddr_in *)(void *)(sa))
+#define satocsin(sa) ((const struct sockaddr_in *)(const void *)(sa))
+#endif
+#ifdef INET6
+#define satosin6(sa) ((struct sockaddr_in6 *)(void *)(sa))
+#define satocsin6(sa) ((const struct sockaddr_in6 *)(const void *)(sa))
+#endif
+
+socklen_t sa_addroffset(const struct sockaddr *sa);
+socklen_t sa_addrlen(const struct sockaddr *sa);
+#ifdef HAVE_SA_LEN
+#define sa_len(sa) ((sa)->sa_len)
+#else
+socklen_t sa_len(const struct sockaddr *sa);
+#endif
+bool sa_is_unspecified(const struct sockaddr *);
+bool sa_is_allones(const struct sockaddr *);
+bool sa_is_loopback(const struct sockaddr *);
+void *sa_toaddr(struct sockaddr *);
+int sa_toprefix(const struct sockaddr *);
+int sa_fromprefix(struct sockaddr *, int);
+const char *sa_addrtop(const struct sockaddr *, char *, socklen_t);
+int sa_cmp(const struct sockaddr *, const struct sockaddr *);
+void sa_in_init(struct sockaddr *, const struct in_addr *);
+void sa_in6_init(struct sockaddr *, const struct in6_addr *);
+
+#endif
diff --git a/src/script.c b/src/script.c
new file mode 100644
index 000000000000..6173b4020f38
--- /dev/null
+++ b/src/script.c
@@ -0,0 +1,788 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "if.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+#include "script.h"
+
+#define DEFAULT_PATH "/usr/bin:/usr/sbin:/bin:/sbin"
+
+static const char * const if_params[] = {
+ "interface",
+ "protocol",
+ "reason",
+ "pid",
+ "ifcarrier",
+ "ifmetric",
+ "ifwireless",
+ "ifflags",
+ "ssid",
+ "profile",
+ "interface_order",
+ NULL
+};
+
+static const char * true_str = "true";
+static const char * false_str = "false";
+
+void
+if_printoptions(void)
+{
+ const char * const *p;
+
+ for (p = if_params; *p; p++)
+ printf(" - %s\n", *p);
+}
+
+pid_t
+script_exec(char *const *argv, char *const *env)
+{
+ pid_t pid = 0;
+ 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);
+ posix_spawnattr_setsigmask(&attr, &defsigs);
+ for (i = 0; i < dhcpcd_signals_len; i++)
+ sigaddset(&defsigs, dhcpcd_signals[i]);
+ for (i = 0; i < dhcpcd_signals_ignore_len; i++)
+ sigaddset(&defsigs, dhcpcd_signals_ignore[i]);
+ posix_spawnattr_setsigdefault(&attr, &defsigs);
+#endif
+ errno = 0;
+ r = posix_spawn(&pid, argv[0], NULL, &attr, argv, env);
+ posix_spawnattr_destroy(&attr);
+ if (r) {
+ errno = r;
+ return -1;
+ }
+ return pid;
+}
+
+#ifdef INET
+static int
+append_config(FILE *fp, const char *prefix, const char *const *config)
+{
+ size_t i;
+
+ if (config == NULL)
+ return 0;
+
+ /* Do we need to replace existing config rather than append? */
+ for (i = 0; config[i] != NULL; i++) {
+ if (efprintf(fp, "%s_%s", prefix, config[i]) == -1)
+ return -1;
+ }
+ return 1;
+}
+
+#endif
+
+#define PROTO_LINK 0
+#define PROTO_DHCP 1
+#define PROTO_IPV4LL 2
+#define PROTO_RA 3
+#define PROTO_DHCP6 4
+#define PROTO_STATIC6 5
+static const char *protocols[] = {
+ "link",
+ "dhcp",
+ "ipv4ll",
+ "ra",
+ "dhcp6",
+ "static6"
+};
+
+int
+efprintf(FILE *fp, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+
+ va_start(args, fmt);
+ r = vfprintf(fp, fmt, args);
+ va_end(args);
+ if (r == -1)
+ return -1;
+ /* Write a trailing NULL so we can easily create env strings. */
+ if (fputc('\0', fp) == EOF)
+ return -1;
+ return r;
+}
+
+char **
+script_buftoenv(struct dhcpcd_ctx *ctx, char *buf, size_t len)
+{
+ char **env, **envp, *bufp, *endp;
+ size_t nenv;
+
+ /* Count the terminated env strings.
+ * Assert that the terminations are correct. */
+ nenv = 0;
+ endp = buf + len;
+ for (bufp = buf; bufp < endp; bufp++) {
+ if (*bufp == '\0') {
+#ifndef NDEBUG
+ if (bufp + 1 < endp)
+ assert(*(bufp + 1) != '\0');
+#endif
+ nenv++;
+ }
+ }
+ assert(*(bufp - 1) == '\0');
+ if (nenv == 0)
+ return NULL;
+
+ if (ctx->script_envlen < nenv) {
+ env = reallocarray(ctx->script_env, nenv + 1, sizeof(*env));
+ if (env == NULL)
+ return NULL;
+ ctx->script_env = env;
+ ctx->script_envlen = nenv;
+ }
+
+ bufp = buf;
+ envp = ctx->script_env;
+ *envp++ = bufp++;
+ endp--; /* Avoid setting the last \0 to an invalid pointer */
+ for (; bufp < endp; bufp++) {
+ if (*bufp == '\0')
+ *envp++ = bufp + 1;
+ }
+ *envp = NULL;
+
+ return ctx->script_env;
+}
+
+static long
+make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp,
+ const char *reason)
+{
+ FILE *fp;
+ long buf_pos, i;
+ char *path;
+ int protocol = PROTO_LINK;
+ const struct if_options *ifo;
+ const struct interface *ifp2;
+ int af;
+ bool is_stdin = ifp->name[0] == '\0';
+ const char *if_up, *if_down;
+ rb_tree_t ifaces;
+ struct rt *rt;
+#ifdef INET
+ const struct dhcp_state *state;
+#ifdef IPV4LL
+ const struct ipv4ll_state *istate;
+#endif
+#endif
+#ifdef DHCP6
+ const struct dhcp6_state *d6_state;
+#endif
+
+#ifdef HAVE_OPEN_MEMSTREAM
+ if (ctx->script_fp == NULL) {
+ fp = open_memstream(&ctx->script_buf, &ctx->script_buflen);
+ if (fp == NULL)
+ goto eexit;
+ ctx->script_fp = fp;
+ } else {
+ fp = ctx->script_fp;
+ rewind(fp);
+ }
+#else
+ char tmpfile[] = "/tmp/dhcpcd-script-env-XXXXXX";
+ int tmpfd;
+
+ fp = NULL;
+ tmpfd = mkstemp(tmpfile);
+ if (tmpfd == -1) {
+ logerr("%s: mkstemp", __func__);
+ return -1;
+ }
+ unlink(tmpfile);
+ fp = fdopen(tmpfd, "w+");
+ if (fp == NULL) {
+ close(tmpfd);
+ goto eexit;
+ }
+#endif
+
+ if (!(ifp->ctx->options & DHCPCD_DUMPLEASE)) {
+ /* Needed for scripts */
+ path = getenv("PATH");
+ if (efprintf(fp, "PATH=%s",
+ path == NULL ? DEFAULT_PATH : path) == -1)
+ goto eexit;
+ if (efprintf(fp, "pid=%d", getpid()) == -1)
+ goto eexit;
+ }
+
+ if (!is_stdin) {
+ if (efprintf(fp, "reason=%s", reason) == -1)
+ goto eexit;
+ }
+
+ ifo = ifp->options;
+#ifdef INET
+ state = D_STATE(ifp);
+#ifdef IPV4LL
+ istate = IPV4LL_CSTATE(ifp);
+#endif
+#endif
+#ifdef DHCP6
+ d6_state = D6_CSTATE(ifp);
+#endif
+ if (strcmp(reason, "TEST") == 0) {
+ if (1 == 2) {
+ /* This space left intentionally blank
+ * as all the below statements are optional. */
+ }
+#ifdef INET6
+#ifdef DHCP6
+ else if (d6_state && d6_state->new)
+ protocol = PROTO_DHCP6;
+#endif
+ else if (ipv6nd_hasra(ifp))
+ protocol = PROTO_RA;
+#endif
+#ifdef INET
+#ifdef IPV4LL
+ else if (istate && istate->addr != NULL)
+ protocol = PROTO_IPV4LL;
+#endif
+ else
+ protocol = PROTO_DHCP;
+#endif
+ }
+#ifdef INET6
+ else if (strcmp(reason, "STATIC6") == 0)
+ protocol = PROTO_STATIC6;
+#ifdef DHCP6
+ else if (reason[strlen(reason) - 1] == '6')
+ protocol = PROTO_DHCP6;
+#endif
+ else if (strcmp(reason, "ROUTERADVERT") == 0)
+ protocol = PROTO_RA;
+#endif
+ else if (strcmp(reason, "PREINIT") == 0 ||
+ strcmp(reason, "CARRIER") == 0 ||
+ strcmp(reason, "NOCARRIER") == 0 ||
+ strcmp(reason, "NOCARRIER_ROAMING") == 0 ||
+ strcmp(reason, "UNKNOWN") == 0 ||
+ strcmp(reason, "DEPARTED") == 0 ||
+ strcmp(reason, "STOPPED") == 0)
+ protocol = PROTO_LINK;
+#ifdef INET
+#ifdef IPV4LL
+ else if (strcmp(reason, "IPV4LL") == 0)
+ protocol = PROTO_IPV4LL;
+#endif
+ else
+ protocol = PROTO_DHCP;
+#endif
+
+ if (!is_stdin) {
+ if (efprintf(fp, "interface=%s", ifp->name) == -1)
+ goto eexit;
+ if (protocols[protocol] != NULL) {
+ if (efprintf(fp, "protocol=%s",
+ protocols[protocol]) == -1)
+ goto eexit;
+ }
+ }
+ if (ifp->ctx->options & DHCPCD_DUMPLEASE && protocol != PROTO_LINK)
+ goto dumplease;
+ if (efprintf(fp, "if_configured=%s",
+ ifo->options & DHCPCD_CONFIGURE ? "true" : "false") == -1)
+ goto eexit;
+ if (efprintf(fp, "ifcarrier=%s",
+ ifp->carrier == LINK_UNKNOWN ? "unknown" :
+ ifp->carrier == LINK_UP ? "up" : "down") == -1)
+ goto eexit;
+ if (efprintf(fp, "ifmetric=%d", ifp->metric) == -1)
+ goto eexit;
+ if (efprintf(fp, "ifwireless=%d", ifp->wireless) == -1)
+ goto eexit;
+ if (efprintf(fp, "ifflags=%u", ifp->flags) == -1)
+ goto eexit;
+ if (efprintf(fp, "ifmtu=%d", if_getmtu(ifp)) == -1)
+ goto eexit;
+ if (ifp->wireless) {
+ char pssid[IF_SSIDLEN * 4];
+
+ if (print_string(pssid, sizeof(pssid), OT_ESCSTRING,
+ ifp->ssid, ifp->ssid_len) != -1)
+ {
+ if (efprintf(fp, "ifssid=%s", pssid) == -1)
+ goto eexit;
+ }
+ }
+ if (*ifp->profile != '\0') {
+ if (efprintf(fp, "profile=%s", ifp->profile) == -1)
+ goto eexit;
+ }
+ if (ifp->ctx->options & DHCPCD_DUMPLEASE)
+ goto dumplease;
+
+ ifp->ctx->rt_order = 0;
+ rb_tree_init(&ifaces, &rt_compare_proto_ops);
+ TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+ if (!ifp2->active)
+ continue;
+ rt = rt_new(UNCONST(ifp2));
+ if (rt == NULL)
+ goto eexit;
+ if (rt_proto_add(&ifaces, rt) != rt)
+ goto eexit;
+ }
+ if (fprintf(fp, "interface_order=") == -1)
+ goto eexit;
+ RB_TREE_FOREACH(rt, &ifaces) {
+ if (rt != RB_TREE_MIN(&ifaces) &&
+ fprintf(fp, "%s", " ") == -1)
+ goto eexit;
+ if (fprintf(fp, "%s", rt->rt_ifp->name) == -1)
+ goto eexit;
+ }
+ rt_headclear(&ifaces, AF_UNSPEC);
+ if (fputc('\0', fp) == EOF)
+ goto eexit;
+
+ if (strcmp(reason, "STOPPED") == 0) {
+ if_up = false_str;
+ if_down = ifo->options & DHCPCD_RELEASE ? true_str : false_str;
+ } else if (strcmp(reason, "TEST") == 0 ||
+ strcmp(reason, "PREINIT") == 0 ||
+ strcmp(reason, "CARRIER") == 0 ||
+ strcmp(reason, "UNKNOWN") == 0)
+ {
+ if_up = false_str;
+ if_down = false_str;
+ } else if (strcmp(reason, "NOCARRIER") == 0) {
+ if_up = false_str;
+ if_down = true_str;
+ } else if (strcmp(reason, "NOCARRIER_ROAMING") == 0) {
+ if_up = true_str;
+ if_down = false_str;
+ } else if (1 == 2 /* appease ifdefs */
+#ifdef INET
+ || (protocol == PROTO_DHCP && state && state->new)
+#ifdef IPV4LL
+ || (protocol == PROTO_IPV4LL && IPV4LL_STATE_RUNNING(ifp))
+#endif
+#endif
+#ifdef INET6
+ || (protocol == PROTO_STATIC6 && IPV6_STATE_RUNNING(ifp))
+#ifdef DHCP6
+ || (protocol == PROTO_DHCP6 && d6_state && d6_state->new)
+#endif
+ || (protocol == PROTO_RA && ipv6nd_hasra(ifp))
+#endif
+ )
+ {
+ if_up = true_str;
+ if_down = false_str;
+ } else {
+ if_up = false_str;
+ if_down = true_str;
+ }
+ if (efprintf(fp, "if_up=%s", if_up) == -1)
+ goto eexit;
+ if (efprintf(fp, "if_down=%s", if_down) == -1)
+ goto eexit;
+
+ if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) {
+ if (efprintf(fp, "if_afwaiting=%d", af) == -1)
+ goto eexit;
+ }
+ 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) {
+ if (efprintf(fp, "af_waiting=%d", af) == -1)
+ goto eexit;
+ }
+ if (ifo->options & DHCPCD_DEBUG) {
+ if (efprintf(fp, "syslog_debug=true") == -1)
+ goto eexit;
+ }
+#ifdef INET
+ if (protocol == PROTO_DHCP && state && state->old) {
+ if (dhcp_env(fp, "old", ifp,
+ state->old, state->old_len) == -1)
+ goto eexit;
+ if (append_config(fp, "old",
+ (const char *const *)ifo->config) == -1)
+ goto eexit;
+ }
+#endif
+#ifdef DHCP6
+ if (protocol == PROTO_DHCP6 && d6_state && d6_state->old) {
+ if (dhcp6_env(fp, "old", ifp,
+ d6_state->old, d6_state->old_len) == -1)
+ goto eexit;
+ }
+#endif
+
+dumplease:
+#ifdef INET
+#ifdef IPV4LL
+ if (protocol == PROTO_IPV4LL && istate) {
+ if (ipv4ll_env(fp, istate->down ? "old" : "new", ifp) == -1)
+ goto eexit;
+ }
+#endif
+ if (protocol == PROTO_DHCP && state && state->new) {
+ if (dhcp_env(fp, "new", ifp,
+ state->new, state->new_len) == -1)
+ goto eexit;
+ if (append_config(fp, "new",
+ (const char *const *)ifo->config) == -1)
+ goto eexit;
+ }
+#endif
+#ifdef INET6
+ if (protocol == PROTO_STATIC6) {
+ if (ipv6_env(fp, "new", ifp) == -1)
+ goto eexit;
+ }
+#ifdef DHCP6
+ if (protocol == PROTO_DHCP6 && D6_STATE_RUNNING(ifp)) {
+ if (dhcp6_env(fp, "new", ifp,
+ d6_state->new, d6_state->new_len) == -1)
+ goto eexit;
+ }
+#endif
+ if (protocol == PROTO_RA) {
+ if (ipv6nd_env(fp, ifp) == -1)
+ goto eexit;
+ }
+#endif
+
+ /* Add our base environment */
+ if (ifo->environ) {
+ for (i = 0; ifo->environ[i] != NULL; i++)
+ if (efprintf(fp, "%s", ifo->environ[i]) == -1)
+ goto eexit;
+ }
+
+ /* Convert buffer to argv */
+ fflush(fp);
+
+ buf_pos = ftell(fp);
+ if (buf_pos == -1) {
+ logerr(__func__);
+ goto eexit;
+ }
+
+#ifndef HAVE_OPEN_MEMSTREAM
+ size_t buf_len = (size_t)buf_pos;
+ if (ctx->script_buflen < buf_len) {
+ char *buf = realloc(ctx->script_buf, buf_len);
+ if (buf == NULL)
+ goto eexit;
+ ctx->script_buf = buf;
+ ctx->script_buflen = buf_len;
+ }
+ rewind(fp);
+ if (fread(ctx->script_buf, sizeof(char), buf_len, fp) != buf_len)
+ goto eexit;
+ fclose(fp);
+ fp = NULL;
+#endif
+
+ if (is_stdin)
+ return buf_pos;
+
+ if (script_buftoenv(ctx, ctx->script_buf, (size_t)buf_pos) == NULL)
+ goto eexit;
+
+ return buf_pos;
+
+eexit:
+ logerr(__func__);
+#ifndef HAVE_OPEN_MEMSTREAM
+ if (fp != NULL)
+ fclose(fp);
+#endif
+ return -1;
+}
+
+static int
+send_interface1(struct fd_list *fd, const struct interface *ifp,
+ const char *reason)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ long len;
+
+ len = make_env(ifp->ctx, ifp, reason);
+ if (len == -1)
+ return -1;
+ return control_queue(fd, ctx->script_buf, (size_t)len);
+}
+
+int
+send_interface(struct fd_list *fd, const struct interface *ifp, int af)
+{
+ int retval = 0;
+#ifdef INET
+ const struct dhcp_state *d;
+#endif
+#ifdef DHCP6
+ const struct dhcp6_state *d6;
+#endif
+
+#ifndef AF_LINK
+#define AF_LINK AF_PACKET
+#endif
+
+ if (af == AF_UNSPEC || af == AF_LINK) {
+ const char *reason;
+
+ switch (ifp->carrier) {
+ case LINK_UP:
+ reason = "CARRIER";
+ break;
+ case LINK_DOWN:
+ reason = "NOCARRIER";
+ break;
+ default:
+ reason = "UNKNOWN";
+ break;
+ }
+ if (fd != NULL) {
+ if (send_interface1(fd, ifp, reason) == -1)
+ retval = -1;
+ } else
+ retval++;
+ }
+
+#ifdef INET
+ if (af == AF_UNSPEC || af == AF_INET) {
+ if (D_STATE_RUNNING(ifp)) {
+ d = D_CSTATE(ifp);
+ if (fd != NULL) {
+ if (send_interface1(fd, ifp, d->reason) == -1)
+ retval = -1;
+ } else
+ retval++;
+ }
+#ifdef IPV4LL
+ if (IPV4LL_STATE_RUNNING(ifp)) {
+ if (fd != NULL) {
+ if (send_interface1(fd, ifp, "IPV4LL") == -1)
+ retval = -1;
+ } else
+ retval++;
+ }
+#endif
+ }
+#endif
+
+#ifdef INET6
+ if (af == AF_UNSPEC || af == AF_INET6) {
+ if (IPV6_STATE_RUNNING(ifp)) {
+ if (fd != NULL) {
+ if (send_interface1(fd, ifp, "STATIC6") == -1)
+ retval = -1;
+ } else
+ retval++;
+ }
+ if (RS_STATE_RUNNING(ifp)) {
+ if (fd != NULL) {
+ if (send_interface1(fd, ifp,
+ "ROUTERADVERT") == -1)
+ retval = -1;
+ } else
+ retval++;
+ }
+#ifdef DHCP6
+ if (D6_STATE_RUNNING(ifp)) {
+ d6 = D6_CSTATE(ifp);
+ if (fd != NULL) {
+ if (send_interface1(fd, ifp, d6->reason) == -1)
+ retval = -1;
+ } else
+ retval++;
+ }
+#endif
+ }
+#endif
+
+ return retval;
+}
+
+static int
+script_run(struct dhcpcd_ctx *ctx, char **argv)
+{
+ pid_t pid;
+ int status = 0;
+
+ pid = script_exec(argv, ctx->script_env);
+ if (pid == -1)
+ logerr("%s: %s", __func__, argv[0]);
+ else if (pid != 0) {
+ /* Wait for the script to finish */
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ logerr("%s: waitpid", __func__);
+ status = 0;
+ break;
+ }
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status))
+ logerrx("%s: %s: WEXITSTATUS %d",
+ __func__, argv[0], WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status))
+ logerrx("%s: %s: %s",
+ __func__, argv[0], strsignal(WTERMSIG(status)));
+ }
+
+ return WEXITSTATUS(status);
+}
+
+int
+script_dump(const char *env, size_t len)
+{
+ const char *ep = env + len;
+
+ if (len == 0)
+ return 0;
+
+ if (*(ep - 1) != '\0') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (; env < ep; env += strlen(env) + 1) {
+ if (strncmp(env, "new_", 4) == 0)
+ env += 4;
+ printf("%s\n", env);
+ }
+ return 0;
+}
+
+int
+script_runreason(const struct interface *ifp, const char *reason)
+{
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ char *argv[2];
+ int status = 0;
+ struct fd_list *fd;
+ long buflen;
+
+ if (ctx->script == NULL &&
+ TAILQ_FIRST(&ifp->ctx->control_fds) == NULL)
+ return 0;
+
+ /* Make our env */
+ if ((buflen = make_env(ifp->ctx, ifp, reason)) == -1) {
+ logerr(__func__);
+ return -1;
+ }
+
+ if (strncmp(reason, "DUMP", 4) == 0)
+ return script_dump(ctx->script_buf, (size_t)buflen);
+
+ if (ctx->script == NULL)
+ goto send_listeners;
+
+ argv[0] = ctx->script;
+ argv[1] = NULL;
+ logdebugx("%s: executing: %s %s", ifp->name, argv[0], reason);
+
+#ifdef PRIVSEP
+ if (ctx->options & DHCPCD_PRIVSEP) {
+ if (ps_root_script(ctx,
+ ctx->script_buf, ctx->script_buflen) == -1)
+ logerr(__func__);
+ goto send_listeners;
+ }
+#endif
+
+ script_run(ctx, argv);
+
+send_listeners:
+ /* Send to our listeners */
+ status = 0;
+ TAILQ_FOREACH(fd, &ctx->control_fds, next) {
+ if (!(fd->flags & FD_LISTEN))
+ continue;
+ if (control_queue(fd, ctx->script_buf, ctx->script_buflen)== -1)
+ logerr("%s: control_queue", __func__);
+ else
+ status = 1;
+ }
+
+ return status;
+}
diff --git a/src/script.h b/src/script.h
new file mode 100644
index 000000000000..feb8574d5912
--- /dev/null
+++ b/src/script.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2021 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"
+
+__printflike(2, 3) int efprintf(FILE *, const char *, ...);
+void if_printoptions(void);
+char ** script_buftoenv(struct dhcpcd_ctx *, char *, size_t);
+pid_t script_exec(char *const *, char *const *);
+int send_interface(struct fd_list *, const struct interface *, int);
+int script_dump(const char *, size_t);
+int script_runreason(const struct interface *, const char *);
+#endif
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 000000000000..16a229ff47af
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,20 @@
+SUBDIRS= crypt eloop-bench
+
+all:
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+install:
+
+proginstall:
+
+clean:
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+distclean: clean
+ rm -f *.diff *.patch *.orig *.rej
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+test:
+ for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
+
+tests: test
diff --git a/tests/crypt/.gitignore b/tests/crypt/.gitignore
new file mode 100644
index 000000000000..4390dc850728
--- /dev/null
+++ b/tests/crypt/.gitignore
@@ -0,0 +1 @@
+run-test
diff --git a/tests/crypt/GNUmakefile b/tests/crypt/GNUmakefile
new file mode 100644
index 000000000000..2e838d5e7283
--- /dev/null
+++ b/tests/crypt/GNUmakefile
@@ -0,0 +1,7 @@
+# GNU Make does not automagically include .depend
+# Luckily it does read GNUmakefile over Makefile so we can work around it
+
+include Makefile
+ifneq ($(wildcard .depend), )
+include .depend
+endif
diff --git a/tests/crypt/Makefile b/tests/crypt/Makefile
new file mode 100644
index 000000000000..dd676f4b2aa9
--- /dev/null
+++ b/tests/crypt/Makefile
@@ -0,0 +1,36 @@
+TOP= ../..
+include ${TOP}/iconfig.mk
+
+PROG= run-test
+SRCS= run-test.c
+SRCS+= test_hmac_md5.c
+
+CFLAGS?= -O2
+CSTD?= c99
+CFLAGS+= -std=${CSTD}
+
+CPPFLAGS+= -I${TOP} -I${TOP}/src
+
+PCRYPT_SRCS= ${CRYPT_SRCS:compat/%=${TOP}/compat/%}
+OBJS+= ${SRCS:.c=.o} ${PCRYPT_SRCS:.c=.o}
+
+.c.o:
+ ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@
+
+all: ${PROG}
+
+clean:
+ rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
+
+distclean: clean
+ rm -f .depend
+ rm -f *.diff *.patch *.orig *.rej
+
+.depend: ${SRCS} ${PCRYPT_SRCS}
+ ${CC} ${CPPFLAGS} -MM ${SRCS} ${PCRYPT_SRCS}
+
+${PROG}: ${DEPEND} ${OBJS}
+ ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
+
+test: ${PROG}
+ ./${PROG}
diff --git a/tests/crypt/README.md b/tests/crypt/README.md
new file mode 100644
index 000000000000..8269e51fee56
--- /dev/null
+++ b/tests/crypt/README.md
@@ -0,0 +1,8 @@
+# dhcpcd Test Suite
+
+Currently this just tests the RFC2202 MD5 implementation in dhcpcd.
+This is important, because dhcpcd will either use the system MD5
+implementation if found, otherwise some compat code.
+
+This test suit ensures that it works in accordance with known standards
+on your platform.
diff --git a/tests/crypt/run-test.c b/tests/crypt/run-test.c
new file mode 100644
index 000000000000..42f52d1fce2d
--- /dev/null
+++ b/tests/crypt/run-test.c
@@ -0,0 +1,38 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2018 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 "test.h"
+
+int main(void)
+{
+ int r = 0;
+
+ if (test_hmac_md5())
+ r = -1;
+
+ return r;
+}
diff --git a/tests/crypt/test.h b/tests/crypt/test.h
new file mode 100644
index 000000000000..4f716f1220f4
--- /dev/null
+++ b/tests/crypt/test.h
@@ -0,0 +1,32 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2018 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 TEST_H
+
+int test_hmac_md5(void);
+
+#endif
diff --git a/tests/crypt/test_hmac_md5.c b/tests/crypt/test_hmac_md5.c
new file mode 100644
index 000000000000..aed6345d5707
--- /dev/null
+++ b/tests/crypt/test_hmac_md5.c
@@ -0,0 +1,209 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2018 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "test.h"
+
+#ifdef HAVE_HMAC_H
+#include <hmac.h>
+#endif
+
+static void
+print_hmac(FILE *stream, const uint8_t *hmac)
+{
+ int i;
+
+ fprintf(stream, "digest = 0x");
+ for (i = 0; i < 16; i++)
+ fprintf(stream, "%02x", *hmac++);
+ fprintf(stream, "\n");
+}
+
+static void
+test_hmac(const uint8_t *hmac, const uint8_t *tst)
+{
+ print_hmac(stdout, hmac);
+ if (memcmp(hmac, tst, 16) == 0)
+ return;
+ fprintf(stderr, "FAILED!\nExpected\t\t\t");
+ print_hmac(stderr, tst);
+ exit(EXIT_FAILURE);
+}
+
+static void
+hmac_md5_test1(void)
+{
+ const uint8_t text[] = "Hi There";
+ uint8_t key[16];
+ const uint8_t expect[16] = {
+ 0x92, 0x94, 0x72, 0x7a, 0x36, 0x38, 0xbb, 0x1c,
+ 0x13, 0xf4, 0x8e, 0xf8, 0x15, 0x8b, 0xfc, 0x9d,
+ };
+ uint8_t digest[16];
+ int i;
+
+ printf ("HMAC MD5 Test 1:\t\t");
+ for (i = 0; i < 16; i++)
+ key[i] = 0x0b;
+ hmac("md5", key, 16, text, 8, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+static void
+hmac_md5_test2(void)
+{
+ const uint8_t text[] = "what do ya want for nothing?";
+ const uint8_t key[] = "Jefe";
+ const uint8_t expect[16] = {
+ 0x75, 0x0c, 0x78, 0x3e, 0x6a, 0xb0, 0xb5, 0x03,
+ 0xea, 0xa8, 0x6e, 0x31, 0x0a, 0x5d, 0xb7, 0x38,
+ };
+ uint8_t digest[16];
+
+ printf("HMAC MD5 Test 2:\t\t");
+ hmac("md5", key, 4, text, 28, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+static void
+hmac_md5_test3(void)
+{
+ const uint8_t expect[16] = {
+ 0x56, 0xbe, 0x34, 0x52, 0x1d, 0x14, 0x4c, 0x88,
+ 0xdb, 0xb8, 0xc7, 0x33, 0xf0, 0xe8, 0xb3, 0xf6,
+ };
+ uint8_t digest[16];
+ uint8_t text[50];
+ uint8_t key[16];
+ int i;
+
+ printf ("HMAC MD5 Test 3:\t\t");
+ for (i = 0; i < 50; i++)
+ text[i] = 0xdd;
+ for (i = 0; i < 16; i++)
+ key[i] = 0xaa;
+ hmac("md5", key, 16, text, 50, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+static void
+hmac_md5_test4(void)
+{
+ const uint8_t expect[16] = {
+ 0x69, 0x7e, 0xaf, 0x0a, 0xca, 0x3a, 0x3a, 0xea,
+ 0x3a, 0x75, 0x16, 0x47, 0x46, 0xff, 0xaa, 0x79,
+ };
+ uint8_t digest[16];
+ uint8_t text[50];
+ uint8_t key[25];
+ uint8_t i;
+
+ printf ("HMAC MD5 Test 4:\t\t");
+ for (i = 0; i < 50; i++)
+ text[i] = 0xcd;
+ for (i = 0; i < 25; i++)
+ key[i] = (uint8_t)(i + 1);
+ hmac("md5", key, 25, text, 50, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+static void
+hmac_md5_test5(void)
+{
+ const uint8_t text[] = "Test With Truncation";
+ const uint8_t expect[] = {
+ 0x56, 0x46, 0x1e, 0xf2, 0x34, 0x2e, 0xdc, 0x00,
+ 0xf9, 0xba, 0xb9, 0x95, 0x69, 0x0e, 0xfd, 0x4c,
+ };
+ uint8_t digest[16];
+ uint8_t key[16];
+ int i;
+
+ printf ("HMAC MD5 Test 5:\t\t");
+ for (i = 0; i < 16; i++)
+ key[i] = 0x0c;
+ hmac("md5", key, 16, text, 20, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+static void
+hmac_md5_test6(void)
+{
+ const uint8_t text[] = "Test Using Larger Than Block-Size Key - Hash Key First";
+ const uint8_t expect[] = {
+ 0x6b, 0x1a, 0xb7, 0xfe, 0x4b, 0xd7, 0xbf, 0x8f,
+ 0x0b, 0x62, 0xe6, 0xce, 0x61, 0xb9, 0xd0, 0xcd,
+ };
+ uint8_t digest[16];
+ uint8_t key[80];
+ int i;
+
+ printf ("HMAC MD5 Test 6:\t\t");
+ for (i = 0; i < 80; i++)
+ key[i] = 0xaa;
+ hmac("md5", key, 80, text, 54, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+static void
+hmac_md5_test7(void)
+{
+ const uint8_t text[] = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data";
+ const uint8_t expect[] = {
+ 0x6f, 0x63, 0x0f, 0xad, 0x67, 0xcd, 0xa0, 0xee,
+ 0x1f, 0xb1, 0xf5, 0x62, 0xdb, 0x3a, 0xa5, 0x3e,
+ };
+ uint8_t digest[16];
+ uint8_t key[80];
+ int i;
+
+ printf ("HMAC MD5 Test 7:\t\t");
+ for (i = 0; i < 80; i++)
+ key[i] = 0xaa;
+ hmac("md5", key, 80, text, 73, digest, sizeof(digest));
+ test_hmac(digest, expect);
+}
+
+int test_hmac_md5(void)
+{
+
+ printf ("Starting RFC2202 HMAC MD5 tests...\n\n");
+ hmac_md5_test1();
+ hmac_md5_test2();
+ hmac_md5_test3();
+ hmac_md5_test4();
+ hmac_md5_test5();
+ hmac_md5_test6();
+ hmac_md5_test7();
+ printf("\nAll tests pass.\n");
+ return 0;
+}
diff --git a/tests/eloop-bench/.gitignore b/tests/eloop-bench/.gitignore
new file mode 100644
index 000000000000..346bbde39b13
--- /dev/null
+++ b/tests/eloop-bench/.gitignore
@@ -0,0 +1 @@
+eloop-bench
diff --git a/tests/eloop-bench/Makefile b/tests/eloop-bench/Makefile
new file mode 100644
index 000000000000..2827c6070422
--- /dev/null
+++ b/tests/eloop-bench/Makefile
@@ -0,0 +1,45 @@
+TOP= ../..
+include ${TOP}/iconfig.mk
+
+PROG= eloop-bench
+SRCS= eloop-bench.c
+SRCS+= ${TOP}/src/eloop.c
+
+CFLAGS?= -O2
+CSTD?= c99
+CFLAGS+= -std=${CSTD}
+
+#CPPFLAGS+= -DNO_CONFIG_H
+#CPPFLAGS+= -DQUEUE_H=../compat/queue.h
+CPPFLAGS+= -I${TOP} -I${TOP}/src
+
+# Default is to let eloop decide
+#CPPFLAGS+= -DHAVE_KQUEUE
+#CPPFLAGS+= -DHAVE_POLLTS
+#CPPFLAGS+= -DHAVE_PSELECT
+#CPPFLAGS+= -DHAVE_EPOLL
+#CPPFLAGS+= -DHAVE_PPOLL
+CPPFLAGS+= -DWARN_SELECT
+
+PCOMPAT_SRCS= ${COMPAT_SRCS:compat/%=${TOP}/compat/%}
+OBJS+= ${SRCS:.c=.o} ${PCOMPAT_SRCS:.c=.o}
+
+.c.o: Makefile
+ ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@
+
+all: ${PROG}
+
+clean:
+ rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
+
+distclean: clean
+ rm -f .depend
+ rm -f *.diff *.patch *.orig *.rej
+
+depend:
+
+${PROG}: ${DEPEND} ${OBJS}
+ ${CC} ${LDFLAGS} -o $@ ${OBJS}
+
+test: ${PROG}
+ ./${PROG}
diff --git a/tests/eloop-bench/README.md b/tests/eloop-bench/README.md
new file mode 100644
index 000000000000..6f4a20c7ea81
--- /dev/null
+++ b/tests/eloop-bench/README.md
@@ -0,0 +1,53 @@
+# eloop-bench
+
+eloop is a portable event loop designed to be dropped into the code of a
+program. It is not in any library to date.
+The basic requirement of eloop is a descriptor polling mechanism which
+allows the safe delivery of signals.
+As such, select(2) and poll(2) are not suitable.
+
+This is an eloop benchmark to test the performance of the various
+polling mechanisms. It's inspired by libevent/bench.
+
+eloop needs to be compiled for a specific polling mechanism.
+eloop will try and work out which one to use, but you can influence which one
+by giving one of these CPPFLAGS to the Makefile:
+ * `HAVE_KQUEUE`
+ * `HAVE_EPOLL`
+ * `HAVE_PSELECT`
+ * `HAVE_POLLTS`
+ * `HAVE_PPOLL`
+
+kqueue(2) is found on modern BSD kernels.
+epoll(7) is found on modern Linux and Solaris kernels.
+These two *should* be the best performers.
+
+pselect(2) *should* be found on any POSIX libc.
+This *should* be the worst performer.
+
+pollts(2) and ppoll(2) are NetBSD and Linux specific variants on poll(2),
+but allow safe signal delivery like pselect(2).
+Aside from the function name, the arguments and functionality are identical.
+They are of little use as both platforms have kqueue(2) and epoll(2),
+but there is an edge case where system doesn't have epoll(2) compiled hence
+it's inclusion here.
+
+## using eloop-bench
+
+The benchmark runs by setting up npipes to read/write to and attaching
+an eloop callback for each pipe reader.
+Once setup, it will perform a run by writing to nactive pipes.
+For each successful pipe read, if nwrites >0 then the reader will reduce
+nwrites by one on successful write back to itself.
+Once nwrites is 0, the timed run will end once the last write has been read.
+At the end of run, the time taken in seconds and nanoseconds is printed.
+
+The following arguments can influence the benchmark:
+ * `-a active`
+ The number of active pipes, default 1.
+ * `-n pipes`
+ The number of pipes to create and attach an eloop callback to, defalt 100.
+ * `-r runs`
+ The number of timed runs to make, default 25.
+ * `-w writes`
+ The number of writes to make by the read callback, default 100.
diff --git a/tests/eloop-bench/eloop-bench.c b/tests/eloop-bench/eloop-bench.c
new file mode 100644
index 000000000000..0f90e824c3ad
--- /dev/null
+++ b/tests/eloop-bench/eloop-bench.c
@@ -0,0 +1,184 @@
+/*
+ * eloop benchmark
+ * Copyright (c) 2006-2018 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/resource.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "eloop.h"
+
+#ifndef timespecsub
+#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
+
+struct pipe {
+ int fd[2];
+};
+
+static size_t good, bad, writes, fired;
+static size_t npipes = 100, nwrites = 100, nactive = 1;
+static struct pipe *pipes;
+static struct eloop *e;
+
+static void
+read_cb(void *arg)
+{
+ struct pipe *p = arg;
+ unsigned char buf[1];
+
+ if (read(p->fd[0], buf, 1) != 1) {
+ warn("%s: read", __func__);
+ bad++;
+ } else
+ good++;
+
+ if (writes != 0) {
+ writes--;
+ if (write(p->fd[1], "e", 1) != 1) {
+ warn("%s: write", __func__);
+ bad++;
+ } else
+ fired++;
+ }
+
+ if (writes == 0 && fired == good) {
+ //printf("fired %zu, good %zu, bad %zu\n", fired, good, bad);
+ eloop_exit(e, good == fired && bad == 0 ?
+ EXIT_SUCCESS : EXIT_FAILURE);
+ }
+}
+
+static int
+runone(struct timespec *t)
+{
+ size_t i;
+ struct pipe *p;
+ struct timespec ts, te;
+ int result;
+
+ writes = nwrites;
+ fired = good = 0;
+
+ for (i = 0, p = pipes; i < nactive; i++, p++) {
+ if (write(p->fd[1], "e", 1) != 1)
+ err(EXIT_FAILURE, "send");
+ writes--;
+ fired++;
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
+ err(EXIT_FAILURE, "clock_gettime");
+ result = eloop_start(e, NULL);
+ if (clock_gettime(CLOCK_MONOTONIC, &te) == -1)
+ err(EXIT_FAILURE, "clock_gettime");
+
+ timespecsub(&te, &ts, t);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, result, exit_code;
+ size_t i, nruns = 25;
+ struct pipe *p;
+ struct timespec ts, te, t;
+
+ while ((c = getopt(argc, argv, "a:n:r:w:")) != -1) {
+ switch (c) {
+ case 'a':
+ nactive = (size_t)atoi(optarg);
+ break;
+ case 'n':
+ npipes = (size_t)atoi(optarg);
+ break;
+ case 'r':
+ nruns = (size_t)atoi(optarg);
+ break;
+ case 'w':
+ nwrites = (size_t)atoi(optarg);
+ break;
+ default:
+ errx(EXIT_FAILURE, "illegal argument `%c'", c);
+ }
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
+ err(EXIT_FAILURE, "clock_gettime");
+
+ if ((e = eloop_new()) == NULL)
+ err(EXIT_FAILURE, "eloop_init");
+
+ if (nactive > npipes)
+ nactive = npipes;
+
+ pipes = calloc(npipes, sizeof(*p));
+ if (pipes == NULL)
+ err(EXIT_FAILURE, "malloc");
+
+ for (i = 0, p = pipes; i < npipes; i++, p++) {
+ if (pipe2(p->fd, O_CLOEXEC | O_NONBLOCK) == -1)
+ err(EXIT_FAILURE, "pipe");
+ if (eloop_event_add(e, p->fd[0], read_cb, p) == -1)
+ err(EXIT_FAILURE, "eloop_event_add");
+ }
+
+ printf("active = %zu, pipes = %zu, runs = %zu, writes = %zu\n",
+ nactive, npipes, nruns, nwrites);
+
+ exit_code = EXIT_SUCCESS;
+ for (i = 0; i < nruns; i++) {
+ result = runone(&t);
+ if (result != EXIT_SUCCESS)
+ exit_code = result;
+ printf("run %zu took %lld.%.9ld seconds, result %d\n",
+ i + 1, (long long)t.tv_sec, t.tv_nsec, result);
+ }
+
+ eloop_free(e);
+ free(pipes);
+
+ if (clock_gettime(CLOCK_MONOTONIC, &te) == -1)
+ err(EXIT_FAILURE, "clock_gettime");
+ timespecsub(&te, &ts, &t);
+ printf("total %lld.%.9ld seconds, result %d\n",
+ (long long)t.tv_sec, t.tv_nsec, exit_code);
+ exit(exit_code);
+}