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-96dba636abec6d5451820add99300bda2ca6d86a.tar.gz
src-96dba636abec6d5451820add99300bda2ca6d86a.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)UIN