Index: .gitignore =================================================================== --- /dev/null +++ .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 Index: BUILDING.md =================================================================== --- /dev/null +++ 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. Index: LICENSE =================================================================== --- /dev/null +++ LICENSE @@ -0,0 +1,23 @@ +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. Index: Makefile =================================================================== --- /dev/null +++ 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 Index: Makefile.inc =================================================================== --- /dev/null +++ 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' Index: README.md =================================================================== --- /dev/null +++ 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/). Index: compat/_strtoi.h =================================================================== --- /dev/null +++ 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 Index: compat/arc4random.h =================================================================== --- /dev/null +++ compat/arc4random.h @@ -0,0 +1,16 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres . + * + * 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 + +uint32_t arc4random(void); +#endif Index: compat/arc4random.c =================================================================== --- /dev/null +++ compat/arc4random.c @@ -0,0 +1,173 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres . + * + * 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 + +#include +#include +#include +#include + +#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); +} Index: compat/arc4random_uniform.h =================================================================== --- /dev/null +++ compat/arc4random_uniform.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2008, Damien Miller + * + * 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 + +uint32_t arc4random_uniform(uint32_t); +#endif Index: compat/arc4random_uniform.c =================================================================== --- /dev/null +++ compat/arc4random_uniform.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2008, Damien Miller + * + * 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 +#include + +/* 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; +} Index: compat/bitops.h =================================================================== --- /dev/null +++ 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 +#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_ */ Index: compat/consttime_memequal.h =================================================================== --- /dev/null +++ compat/consttime_memequal.h @@ -0,0 +1,28 @@ +/* + * Written by Matthias Drochner . + * 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 */ Index: compat/crypt/hmac.h =================================================================== --- /dev/null +++ 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 + +ssize_t hmac(const char *, const void *, size_t, const void *, size_t, void *, + size_t); + +#endif Index: compat/crypt/hmac.c =================================================================== --- /dev/null +++ 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 +#include + +#include "config.h" + +#if defined(HAVE_MD5_H) && !defined(DEPGEN) +#include +#endif + +#ifdef SHA2_H +# include SHA2_H +#endif + +#ifndef __arraycount +#define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) +#endif + +#if 0 +#include +#include +#include +#include +#include +#include +#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; +} Index: compat/crypt/md5.h =================================================================== --- /dev/null +++ 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 Index: compat/crypt/md5.c =================================================================== --- /dev/null +++ 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 +#include + +#include + +#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<>(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 */ +} + + Index: compat/crypt/sha256.h =================================================================== --- /dev/null +++ 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 + +#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 Index: compat/crypt/sha256.c =================================================================== --- /dev/null +++ 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 + +#include + +#ifdef __GLIBC__ +# include +#endif +#ifdef BSD +# ifndef __QNX__ +# include +# 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)); +} Index: compat/dprintf.h =================================================================== --- /dev/null +++ compat/dprintf.h @@ -0,0 +1,43 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2017 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. + */ + +#ifndef DPRINTF_H +#define DPRINTF_H + +#include + +#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 Index: compat/dprintf.c =================================================================== --- /dev/null +++ compat/dprintf.c @@ -0,0 +1,64 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2017 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. + */ + +#include +#include +#include +#include + +#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; +} Index: compat/endian.h =================================================================== --- /dev/null +++ compat/endian.h @@ -0,0 +1,71 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 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. + */ + +#ifndef ENDIAN_H +#define ENDIAN_H + +#include + +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 Index: compat/pidfile.h =================================================================== --- /dev/null +++ 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 + +int pidfile_clean(void); +pid_t pidfile_lock(const char *); +pid_t pidfile_read(const char *); + +#endif Index: compat/pidfile.c =================================================================== --- /dev/null +++ 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* 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; +} Index: compat/queue.h =================================================================== --- /dev/null +++ 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 */ Index: compat/rb.c =================================================================== --- /dev/null +++ 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 . + * + * 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 +#include +#include +#include +#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 +__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 +#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 */ Index: compat/rbtree.h =================================================================== --- /dev/null +++ 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 . + * + * 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 +#else +#include +#include +#endif +#ifdef HAVE_SYS_QUEUE_H +#include +#else +#include "queue.h" +#endif +#if !defined(__linux__) && !defined(__QNX__) && !defined(__sun) +#include +#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_*/ Index: compat/reallocarray.h =================================================================== --- /dev/null +++ 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 . + * 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 Index: compat/reallocarray.c =================================================================== --- /dev/null +++ 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 . + * 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 +#include +#include +#include +#include + +/* + * 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); +} Index: compat/setproctitle.h =================================================================== --- /dev/null +++ compat/setproctitle.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2010 William Ahern + * Copyright © 2012-2013 Guillem Jover + * + * 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 Index: compat/setproctitle.c =================================================================== --- /dev/null +++ compat/setproctitle.c @@ -0,0 +1,331 @@ +/* + * Copyright © 2010 William Ahern + * Copyright © 2012-2013 Guillem Jover + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +//#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); Index: compat/strlcpy.h =================================================================== --- /dev/null +++ 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 + * + * 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 Index: compat/strlcpy.c =================================================================== --- /dev/null +++ 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 + * + * 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 + +#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 */ +} Index: compat/strtoi.h =================================================================== --- /dev/null +++ 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 + +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 Index: compat/strtoi.c =================================================================== --- /dev/null +++ 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 +#include +#include +#elif defined(_STANDALONE) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#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 Index: compat/strtou.c =================================================================== --- /dev/null +++ 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 +#include +#include +#elif defined(_STANDALONE) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#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 Index: config-null.mk =================================================================== --- /dev/null +++ config-null.mk @@ -0,0 +1,3 @@ +# This space left intentionally blank + +DHCPCD_SRCS+= dhcpcd-embedded.c Index: configure =================================================================== --- /dev/null +++ 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 < if you have libraries in a + nonstandard directory + CPPFLAGS C/C++ preprocessor flags, e.g. -I if you have + headers in a nonstandard directory + 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 <>$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 <_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 " >>$CONFIG_H; + esac + echo "#include " >>$CONFIG_H + echo "#include " >>$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 /* fix broken headers */" >>$CONFIG_H + echo "#include /* fix broken headers */" >>$CONFIG_H + echo "#include " >>$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 <_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 <_capsicum.c +#include +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 <_pledge.c +#include +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 <_nl80211.c +#include +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 <_IN6_ADDR_GEN_MODE_NONE.c +#include +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 <_ifam_pid.c +#include +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 <_ifam_addrflags.c +#include +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 <_getifaddrs.c +#include +#include +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 <_getifaddrs_addrflags.c +#include +#include +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 <_clock_gettime.c +#include +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 <_ioctl.c +#include +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 <_inet_ntoa.c +#include +#include +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 <_arc4random.c +#include +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 <_arc4random_uniform.c +#include +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 <_open_memstream.c +#include +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 <_pidfile.c +#include +#include +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 +#include +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 <_strlcpy.c +#include +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 <_strtoi.c +#include +#include +#include +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 <_consttime_memequal.c +#include +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 <_dprintf.c +#include +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 <_queue.c +#include +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 <_queue.c +#include +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 <_queue.c +#include +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 <_queue.c +#include +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 ">>$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 <_rbtree.c +#include +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 <_reallocarray.c +#include + +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 <_ppoll.c +#include +#include +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 <_pollts.c +#include +#include +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 <_pselect.c +#include +#include +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 <_be64enc.c +#include +#include +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 <_fls64.c +#include +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 " >>$CONFIG_H + echo "#else" >>$CONFIG_H +fi + +if [ -z "$MD5" ]; then + MD5_LIB= + printf "Testing for MD5Init ... " + cat <_md5.c +#include +#include +#include +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 <_sha256.c +#include +EOF + [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c + cat <>_sha256.c +#include +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 <_sha256.c +#include +EOF + [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c + cat <>_sha256.c +#include +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 <_hmac.c +#include +#include +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 <_hmac.c +#include +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 <_udev.c +#include +#include +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 <_udev.c +#include +#include +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 <_dlopen.c +#include +#include +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 Index: hooks/01-test =================================================================== --- /dev/null +++ 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 Index: hooks/10-wpa_supplicant =================================================================== --- /dev/null +++ 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 Index: hooks/15-timezone =================================================================== --- /dev/null +++ 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 Index: hooks/20-resolv.conf =================================================================== --- /dev/null +++ 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 Index: hooks/29-lookup-hostname =================================================================== --- /dev/null +++ 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 Index: hooks/30-hostname.in =================================================================== --- /dev/null +++ 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 /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 Index: hooks/50-dhcpcd-compat =================================================================== --- /dev/null +++ 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" Index: hooks/50-ntp.conf =================================================================== --- /dev/null +++ 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 Index: hooks/50-yp.conf =================================================================== --- /dev/null +++ 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 Index: hooks/50-ypbind.in =================================================================== --- /dev/null +++ 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 Index: hooks/Makefile =================================================================== --- /dev/null +++ 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 Index: hooks/dhcpcd-run-hooks.8.in =================================================================== --- /dev/null +++ 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. Index: hooks/dhcpcd-run-hooks.in =================================================================== --- /dev/null +++ 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 Index: iconfig.mk =================================================================== --- /dev/null +++ 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} Index: src/GNUmakefile =================================================================== --- /dev/null +++ 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 Index: src/Makefile =================================================================== --- /dev/null +++ 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 Index: src/arp.h =================================================================== --- /dev/null +++ src/arp.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#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 */ Index: src/arp.c =================================================================== --- /dev/null +++ src/arp.c @@ -0,0 +1,634 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - ARP handler + * 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. + */ + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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); +} Index: src/auth.h =================================================================== --- /dev/null +++ src/auth.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef AUTH_H +#define AUTH_H + +#include "config.h" + +#ifdef HAVE_SYS_QUEUE_H +#include +#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 Index: src/auth.c =================================================================== --- /dev/null +++ src/auth.c @@ -0,0 +1,738 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "auth.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "dhcpcd.h" +#include "privsep-root.h" + +#ifdef HAVE_HMAC_H +#include +#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 */ +} Index: src/bpf.h =================================================================== --- /dev/null +++ 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 + * 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 Index: src/bpf.c =================================================================== --- /dev/null +++ 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 + * 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 +#include + +#include + +#include +#include +#include + +#ifdef __linux__ +/* Special BPF snowflake. */ +#include +#define bpf_insn sock_filter +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#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 +} Index: src/common.h =================================================================== --- /dev/null +++ src/common.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include + +/* 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 Index: src/common.c =================================================================== --- /dev/null +++ src/common.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 +} Index: src/control.h =================================================================== --- /dev/null +++ src/control.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include + +#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 Index: src/control.c =================================================================== --- /dev/null +++ src/control.c @@ -0,0 +1,594 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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; +} Index: src/defs.h =================================================================== --- /dev/null +++ src/defs.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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 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 Index: src/dev.h =================================================================== --- /dev/null +++ src/dev.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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 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 Index: src/dev.c =================================================================== --- /dev/null +++ src/dev.c @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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 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 +#include +#include +#include +#include + +#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; +} Index: src/dev/Makefile =================================================================== --- /dev/null +++ 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} Index: src/dev/udev.c =================================================================== --- /dev/null +++ src/dev/udev.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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 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 +#include + +#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; +} Index: src/dhcp-common.h =================================================================== --- /dev/null +++ src/dhcp-common.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef DHCPCOMMON_H +#define DHCPCOMMON_H + +#include +#include + +#include + +#include /* 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 Index: src/dhcp-common.c =================================================================== --- /dev/null +++ src/dhcp-common.c @@ -0,0 +1,1029 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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; +} Index: src/dhcp.h =================================================================== --- /dev/null +++ src/dhcp.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include +#include + +#include +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include +#undef __FAVOR_BSD + +#include +#include + +#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 */ Index: src/dhcp.c =================================================================== --- /dev/null +++ src/dhcp.c @@ -0,0 +1,4314 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include +#undef __FAVOR_BSD + +#ifdef AF_LINK +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 Index: src/dhcp6.h =================================================================== --- /dev/null +++ src/dhcp6.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef DHCP6_H +#define DHCP6_H + +#include "dhcpcd.h" + +#define IN6ADDR_LINKLOCAL_ALLDHCP_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 }}} + +/* UDP port numbers for DHCP */ +#define DHCP6_CLIENT_PORT 546 +#define DHCP6_SERVER_PORT 547 + +/* DHCP message type */ +#define DHCP6_SOLICIT 1 +#define DHCP6_ADVERTISE 2 +#define DHCP6_REQUEST 3 +#define DHCP6_CONFIRM 4 +#define DHCP6_RENEW 5 +#define DHCP6_REBIND 6 +#define DHCP6_REPLY 7 +#define DHCP6_RELEASE 8 +#define DHCP6_DECLINE 9 +#define DHCP6_RECONFIGURE 10 +#define DHCP6_INFORMATION_REQ 11 +#define DHCP6_RELAY_FLOW 12 +#define DHCP6_RELAY_REPL 13 +#define DHCP6_RECONFIGURE_REQ 18 +#define DHCP6_RECONFIGURE_REPLY 19 + +#ifdef DHCP6 + +#define D6_OPTION_CLIENTID 1 +#define D6_OPTION_SERVERID 2 +#define D6_OPTION_IA_NA 3 +#define D6_OPTION_IA_TA 4 +#define D6_OPTION_ORO 6 +#define D6_OPTION_IA_ADDR 5 +#define D6_OPTION_PREFERENCE 7 +#define D6_OPTION_ELAPSED 8 +#define D6_OPTION_AUTH 11 +#define D6_OPTION_UNICAST 12 +#define D6_OPTION_STATUS_CODE 13 +#define D6_OPTION_RAPID_COMMIT 14 +#define D6_OPTION_USER_CLASS 15 +#define D6_OPTION_VENDOR_CLASS 16 +#define D6_OPTION_VENDOR_OPTS 17 +#define D6_OPTION_INTERFACE_ID 18 +#define D6_OPTION_RECONF_MSG 19 +#define D6_OPTION_RECONF_ACCEPT 20 +#define D6_OPTION_SIP_SERVERS_NAME 21 +#define D6_OPTION_SIP_SERVERS_ADDRESS 22 +#define D6_OPTION_DNS_SERVERS 23 +#define D6_OPTION_DOMAIN_LIST 24 +#define D6_OPTION_IA_PD 25 +#define D6_OPTION_IAPREFIX 26 +#define D6_OPTION_NIS_SERVERS 27 +#define D6_OPTION_NISP_SERVERS 28 +#define D6_OPTION_NIS_DOMAIN_NAME 29 +#define D6_OPTION_NISP_DOMAIN_NAME 30 +#define D6_OPTION_SNTP_SERVERS 31 +#define D6_OPTION_INFO_REFRESH_TIME 32 +#define D6_OPTION_BCMS_SERVER_D 33 +#define D6_OPTION_BCMS_SERVER_A 34 +#define D6_OPTION_FQDN 39 +#define D6_OPTION_POSIX_TIMEZONE 41 +#define D6_OPTION_TZDB_TIMEZONE 42 +#define D6_OPTION_PD_EXCLUDE 67 +#define D6_OPTION_SOL_MAX_RT 82 +#define D6_OPTION_INF_MAX_RT 83 +#define D6_OPTION_MUDURL 112 + +#define D6_FQDN_PTR 0x00 +#define D6_FQDN_BOTH 0x01 +#define D6_FQDN_NONE 0x04 + +#include "dhcp.h" +#include "ipv6.h" + +#define D6_STATUS_OK 0 +#define D6_STATUS_FAIL 1 +#define D6_STATUS_NOADDR 2 +#define D6_STATUS_NOBINDING 3 +#define D6_STATUS_NOTONLINK 4 +#define D6_STATUS_USEMULTICAST 5 + +#define SOL_MAX_DELAY 1 +#define SOL_TIMEOUT 1 +#define SOL_MAX_RT 3600 /* RFC7083 */ +#define SOL_MAX_RC 0 +#define REQ_MAX_DELAY 0 +#define REQ_TIMEOUT 1 +#define REQ_MAX_RT 30 +#define REQ_MAX_RC 10 +#define CNF_MAX_DELAY 1 +#define CNF_TIMEOUT 1 +#define CNF_MAX_RT 4 +#define CNF_MAX_RC 0 +#define CNF_MAX_RD 10 +#define REN_MAX_DELAY 0 +#define REN_TIMEOUT 10 +#define REN_MAX_RT 600 +#define REB_MAX_DELAY 0 +#define REB_TIMEOUT 10 +#define REB_MAX_RT 600 +#define INF_MAX_DELAY 1 +#define INF_TIMEOUT 1 +#define INF_MAX_RD CNF_MAX_RD /* NOT RFC defined */ +#define INF_MAX_RT 3600 /* RFC7083 */ +#define REL_MAX_DELAY 0 +#define REL_TIMEOUT 1 +#define REL_MAX_RT 0 +#define REL_MAX_RC 5 +#define DEC_MAX_DELAY 0 +#define DEC_TIMEOUT 1 +#define DEC_MAX_RC 5 +#define REC_MAX_DELAY 0 +#define REC_TIMEOUT 2 +#define REC_MAX_RC 8 +#define HOP_COUNT_LIMIT 32 + +/* RFC4242 3.1 */ +#define IRT_DEFAULT 86400 +#define IRT_MINIMUM 600 + +/* These should give -.1 to .1 randomness */ +#define DHCP6_RAND_MIN -100 +#define DHCP6_RAND_MAX 100 +#define DHCP6_RAND_DIV 1000.0f + +enum DH6S { + DH6S_INIT, + DH6S_DISCOVER, + DH6S_REQUEST, + DH6S_BOUND, + DH6S_RENEW, + DH6S_REBIND, + DH6S_CONFIRM, + DH6S_INFORM, + DH6S_INFORMED, + DH6S_RENEW_REQUESTED, + DH6S_PROBE, + DH6S_DECLINE, + DH6S_DELEGATED, + DH6S_RELEASE, + DH6S_RELEASED, +}; + +struct dhcp6_state { + enum DH6S state; + struct timespec started; + + /* Message retransmission timings in seconds */ + unsigned int IMD; + unsigned int RTC; + unsigned int IRT; + unsigned int MRC; + unsigned int MRT; + void (*MRCcallback)(void *); + unsigned int sol_max_rt; + unsigned int inf_max_rt; + unsigned int RT; /* retransmission timer in milliseconds + * maximal RT is 1 day + RAND, + * so should be enough */ + + struct dhcp6_message *send; + size_t send_len; + struct dhcp6_message *recv; + size_t recv_len; + struct dhcp6_message *new; + size_t new_len; + struct dhcp6_message *old; + size_t old_len; + + struct timespec acquired; + uint32_t renew; + uint32_t rebind; + uint32_t expire; + struct in6_addr unicast; + struct ipv6_addrhead addrs; + uint32_t lowpl; + /* The +3 is for the possible .pd extension for prefix delegation */ + char leasefile[sizeof(LEASEFILE6) + IF_NAMESIZE + (IF_SSIDLEN * 4) +3]; + const char *reason; + uint16_t lerror; /* Last error received from DHCPv6 reply. */ + bool has_no_binding; + bool failed; /* Entered the failed state - used to rate limit log. */ + bool new_start; /* New external start, to determine log type. */ +#ifdef AUTH + struct authstate auth; +#endif +}; + +#define D6_STATE(ifp) \ + ((struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) +#define D6_CSTATE(ifp) \ + ((const struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) +#define D6_STATE_RUNNING(ifp) \ + (D6_CSTATE((ifp)) && \ + D6_CSTATE((ifp))->reason && dhcp6_dadcompleted((ifp))) + +int dhcp6_openraw(void); +int dhcp6_openudp(unsigned int, struct in6_addr *); +void dhcp6_recvmsg(struct dhcpcd_ctx *, struct msghdr *, struct ipv6_addr *); +void dhcp6_printoptions(const struct dhcpcd_ctx *, + const struct dhcp_opt *, size_t); +const struct ipv6_addr *dhcp6_iffindaddr(const struct interface *ifp, + const struct in6_addr *addr, unsigned int flags); +struct ipv6_addr *dhcp6_findaddr(struct dhcpcd_ctx *, const struct in6_addr *, + unsigned int); +size_t dhcp6_find_delegates(struct interface *); +int dhcp6_start(struct interface *, enum DH6S); +void dhcp6_reboot(struct interface *); +void dhcp6_renew(struct interface *); +ssize_t dhcp6_env(FILE *, const char *, const struct interface *, + const struct dhcp6_message *, size_t); +void dhcp6_free(struct interface *); +void dhcp6_handleifa(int, struct ipv6_addr *, pid_t); +bool dhcp6_dadcompleted(const struct interface *); +void dhcp6_abort(struct interface *); +void dhcp6_drop(struct interface *, const char *); +int dhcp6_dump(struct interface *); +#endif /* DHCP6 */ + +#endif /* DHCP6_H */ Index: src/dhcp6.c =================================================================== --- /dev/null +++ src/dhcp6.c @@ -0,0 +1,4364 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#else +#include "compat/bitops.h" +#endif + +/* DHCPCD Project has been assigned an IANA PEN of 40712 */ +#define DHCPCD_IANA_PEN 40712 + +/* Unsure if I want this */ +//#define VENDOR_SPLIT + +/* Support older systems with different defines */ +#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#endif + +#ifdef DHCP6 + +/* Assert the correct structure size for on wire */ +struct dhcp6_message { + uint8_t type; + uint8_t xid[3]; + /* followed by options */ +}; +__CTASSERT(sizeof(struct dhcp6_message) == 4); + +struct dhcp6_option { + uint16_t code; + uint16_t len; + /* followed by data */ +}; +__CTASSERT(sizeof(struct dhcp6_option) == 4); + +struct dhcp6_ia_na { + uint8_t iaid[4]; + uint32_t t1; + uint32_t t2; +}; +__CTASSERT(sizeof(struct dhcp6_ia_na) == 12); + +struct dhcp6_ia_ta { + uint8_t iaid[4]; +}; +__CTASSERT(sizeof(struct dhcp6_ia_ta) == 4); + +struct dhcp6_ia_addr { + struct in6_addr addr; + uint32_t pltime; + uint32_t vltime; +}; +__CTASSERT(sizeof(struct dhcp6_ia_addr) == 16 + 8); + +/* XXX FIXME: This is the only packed structure and it does not align. + * Maybe manually decode it? */ +struct dhcp6_pd_addr { + uint32_t pltime; + uint32_t vltime; + uint8_t prefix_len; + struct in6_addr prefix; +} __packed; +__CTASSERT(sizeof(struct dhcp6_pd_addr) == 8 + 1 + 16); + +struct dhcp6_op { + uint16_t type; + const char *name; +}; + +static const struct dhcp6_op dhcp6_ops[] = { + { DHCP6_SOLICIT, "SOLICIT6" }, + { DHCP6_ADVERTISE, "ADVERTISE6" }, + { DHCP6_REQUEST, "REQUEST6" }, + { DHCP6_REPLY, "REPLY6" }, + { DHCP6_RENEW, "RENEW6" }, + { DHCP6_REBIND, "REBIND6" }, + { DHCP6_CONFIRM, "CONFIRM6" }, + { DHCP6_INFORMATION_REQ, "INFORM6" }, + { DHCP6_RELEASE, "RELEASE6" }, + { DHCP6_RECONFIGURE, "RECONFIGURE6" }, + { DHCP6_DECLINE, "DECLINE6" }, + { 0, NULL } +}; + +struct dhcp_compat { + uint8_t dhcp_opt; + uint16_t dhcp6_opt; +}; + +static const struct dhcp_compat dhcp_compats[] = { + { DHO_DNSSERVER, D6_OPTION_DNS_SERVERS }, + { DHO_HOSTNAME, D6_OPTION_FQDN }, + { DHO_DNSDOMAIN, D6_OPTION_FQDN }, + { DHO_NISSERVER, D6_OPTION_NIS_SERVERS }, + { DHO_NTPSERVER, D6_OPTION_SNTP_SERVERS }, + { DHO_RAPIDCOMMIT, D6_OPTION_RAPID_COMMIT }, + { DHO_FQDN, D6_OPTION_FQDN }, + { DHO_VIVCO, D6_OPTION_VENDOR_CLASS }, + { DHO_VIVSO, D6_OPTION_VENDOR_OPTS }, + { DHO_DNSSEARCH, D6_OPTION_DOMAIN_LIST }, + { 0, 0 } +}; + +static const char * const dhcp6_statuses[] = { + "Success", + "Unspecified Failure", + "No Addresses Available", + "No Binding", + "Not On Link", + "Use Multicast", + "No Prefix Available" +}; + +static void dhcp6_bind(struct interface *, const char *, const char *); +static void dhcp6_failinform(void *); +static void dhcp6_recvaddr(void *); +static void dhcp6_startdecline(struct interface *); + +#ifdef SMALL +#define dhcp6_hasprefixdelegation(a) (0) +#else +static int dhcp6_hasprefixdelegation(struct interface *); +#endif + +#define DECLINE_IA(ia) \ + ((ia)->addr_flags & IN6_IFF_DUPLICATED && \ + (ia)->ia_type != 0 && (ia)->ia_type != D6_OPTION_IA_PD && \ + !((ia)->flags & IPV6_AF_STALE) && \ + (ia)->prefix_vltime != 0) + +void +dhcp6_printoptions(const struct dhcpcd_ctx *ctx, + const struct dhcp_opt *opts, size_t opts_len) +{ + size_t i, j; + const struct dhcp_opt *opt, *opt2; + int cols; + + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; i++, opt++) + { + for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) + if (opt2->option == opt->option) + break; + if (j == opts_len) { + cols = printf("%05d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } + } + for (i = 0, opt = opts; i < opts_len; i++, opt++) { + cols = printf("%05d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } +} + +static size_t +dhcp6_makeuser(void *data, const struct interface *ifp) +{ + const struct if_options *ifo = ifp->options; + struct dhcp6_option o; + uint8_t *p; + const uint8_t *up, *ue; + uint16_t ulen, unlen; + size_t olen; + + /* Convert the DHCPv4 user class option to DHCPv6 */ + up = ifo->userclass; + ulen = *up++; + if (ulen == 0) + return 0; + + p = data; + olen = 0; + if (p != NULL) + p += sizeof(o); + + ue = up + ulen; + for (; up < ue; up += ulen) { + ulen = *up++; + olen += sizeof(ulen) + ulen; + if (data == NULL) + continue; + unlen = htons(ulen); + memcpy(p, &unlen, sizeof(unlen)); + p += sizeof(unlen); + memcpy(p, up, ulen); + p += ulen; + } + if (data != NULL) { + o.code = htons(D6_OPTION_USER_CLASS); + o.len = htons((uint16_t)olen); + memcpy(data, &o, sizeof(o)); + } + + return sizeof(o) + olen; +} + +static size_t +dhcp6_makevendor(void *data, const struct interface *ifp) +{ + const struct if_options *ifo; + size_t len, vlen, i; + uint8_t *p; + const struct vivco *vivco; + struct dhcp6_option o; + + ifo = ifp->options; + len = sizeof(uint32_t); /* IANA PEN */ + if (ifo->vivco_en) { + vlen = 0; + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + vlen += sizeof(uint16_t) + vivco->len; + len += vlen; + } else if (ifo->vendorclassid[0] != '\0') { + /* dhcpcd owns DHCPCD_IANA_PEN. + * If you need your own string, get your own IANA PEN. */ + vlen = strlen(ifp->ctx->vendor); + len += sizeof(uint16_t) + vlen; + } else + return 0; + + if (len > UINT16_MAX) { + logerrx("%s: DHCPv6 Vendor Class too big", ifp->name); + return 0; + } + + if (data != NULL) { + uint32_t pen; + uint16_t hvlen; + + p = data; + o.code = htons(D6_OPTION_VENDOR_CLASS); + o.len = htons((uint16_t)len); + memcpy(p, &o, sizeof(o)); + p += sizeof(o); + pen = htonl(ifo->vivco_en ? ifo->vivco_en : DHCPCD_IANA_PEN); + memcpy(p, &pen, sizeof(pen)); + p += sizeof(pen); + + if (ifo->vivco_en) { + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + { + hvlen = htons((uint16_t)vivco->len); + memcpy(p, &hvlen, sizeof(hvlen)); + p += sizeof(hvlen); + memcpy(p, vivco->data, vivco->len); + p += vivco->len; + } + } else if (ifo->vendorclassid[0] != '\0') { + hvlen = htons((uint16_t)vlen); + memcpy(p, &hvlen, sizeof(hvlen)); + p += sizeof(hvlen); + memcpy(p, ifp->ctx->vendor, vlen); + } + } + + return sizeof(o) + len; +} + +static void * +dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len) +{ + uint8_t *d; + struct dhcp6_option o; + + code = htons(code); + for (d = data; data_len != 0; d += o.len, data_len -= o.len) { + if (data_len < sizeof(o)) { + errno = EINVAL; + return NULL; + } + memcpy(&o, d, sizeof(o)); + d += sizeof(o); + data_len -= sizeof(o); + o.len = htons(o.len); + if (data_len < o.len) { + errno = EINVAL; + return NULL; + } + if (o.code == code) { + if (len != NULL) + *len = o.len; + return d; + } + } + + errno = ENOENT; + return NULL; +} + +static void * +dhcp6_findmoption(void *data, size_t data_len, uint16_t code, + uint16_t *len) +{ + uint8_t *d; + + if (data_len < sizeof(struct dhcp6_message)) { + errno = EINVAL; + return false; + } + d = data; + d += sizeof(struct dhcp6_message); + data_len -= sizeof(struct dhcp6_message); + return dhcp6_findoption(d, data_len, code, len); +} + +static const uint8_t * +dhcp6_getoption(struct dhcpcd_ctx *ctx, + size_t *os, unsigned int *code, size_t *len, + const uint8_t *od, size_t ol, struct dhcp_opt **oopt) +{ + struct dhcp6_option o; + size_t i; + struct dhcp_opt *opt; + + if (od != NULL) { + *os = sizeof(o); + if (ol < *os) { + errno = EINVAL; + return NULL; + } + memcpy(&o, od, sizeof(o)); + *len = ntohs(o.len); + if (*len > ol - *os) { + errno = ERANGE; + return NULL; + } + *code = ntohs(o.code); + } + + *oopt = NULL; + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; i++, opt++) + { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + if (od != NULL) + return od + sizeof(o); + return NULL; +} + +static bool +dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, size_t len) +{ + uint8_t *opt; + uint16_t opt_len; + struct dhcp6_state *state; + struct timespec tv; + unsigned long long hsec; + uint16_t sec; + + opt = dhcp6_findmoption(m, len, D6_OPTION_ELAPSED, &opt_len); + if (opt == NULL) + return false; + if (opt_len != sizeof(sec)) { + errno = EINVAL; + return false; + } + + state = D6_STATE(ifp); + clock_gettime(CLOCK_MONOTONIC, &tv); + if (state->RTC == 0) { + /* An RTC of zero means we're the first message + * out of the door, so the elapsed time is zero. */ + state->started = tv; + hsec = 0; + } else { + unsigned long long secs; + unsigned int nsecs; + + secs = eloop_timespec_diff(&tv, &state->started, &nsecs); + /* Elapsed time is measured in centiseconds. + * We need to be sure it will not potentially overflow. */ + if (secs >= (UINT16_MAX / CSEC_PER_SEC) + 1) + hsec = UINT16_MAX; + else { + hsec = (secs * CSEC_PER_SEC) + + (nsecs / NSEC_PER_CSEC); + if (hsec > UINT16_MAX) + hsec = UINT16_MAX; + } + } + sec = htons((uint16_t)hsec); + memcpy(opt, &sec, sizeof(sec)); + return true; +} + +static void +dhcp6_newxid(const struct interface *ifp, struct dhcp6_message *m) +{ + const struct interface *ifp1; + const struct dhcp6_state *state1; + uint32_t xid; + + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid), + sizeof(xid)); + else { +again: + xid = arc4random(); + } + + m->xid[0] = (xid >> 16) & 0xff; + m->xid[1] = (xid >> 8) & 0xff; + m->xid[2] = xid & 0xff; + + /* Ensure it's unique */ + TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) { + if (ifp == ifp1) + continue; + if ((state1 = D6_CSTATE(ifp1)) == NULL) + continue; + if (state1->send != NULL && + state1->send->xid[0] == m->xid[0] && + state1->send->xid[1] == m->xid[1] && + state1->send->xid[2] == m->xid[2]) + break; + } + + if (ifp1 != NULL) { + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(xid)) + { + logerrx("%s: duplicate xid on %s", + ifp->name, ifp1->name); + return; + } + goto again; + } +} + +#ifndef SMALL +static const struct if_sla * +dhcp6_findselfsla(struct interface *ifp) +{ + size_t i, j; + struct if_ia *ia; + + for (i = 0; i < ifp->options->ia_len; i++) { + ia = &ifp->options->ia[i]; + if (ia->ia_type != D6_OPTION_IA_PD) + continue; + for (j = 0; j < ia->sla_len; j++) { + if (strcmp(ia->sla[j].ifname, ifp->name) == 0) + return &ia->sla[j]; + } + } + return NULL; +} + +static int +dhcp6_delegateaddr(struct in6_addr *addr, struct interface *ifp, + const struct ipv6_addr *prefix, const struct if_sla *sla, struct if_ia *ia) +{ + struct dhcp6_state *state; + struct if_sla asla; + char sabuf[INET6_ADDRSTRLEN]; + const char *sa; + + state = D6_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state)); + state = D6_STATE(ifp); + if (state == NULL) { + logerr(__func__); + return -1; + } + + TAILQ_INIT(&state->addrs); + state->state = DH6S_DELEGATED; + state->reason = "DELEGATED6"; + } + + if (sla == NULL || !sla->sla_set) { + /* No SLA set, so make an assumption of + * desired SLA and prefix length. */ + asla.sla = ifp->index; + asla.prefix_len = 0; + asla.sla_set = false; + sla = &asla; + } else if (sla->prefix_len == 0) { + /* An SLA was given, but prefix length was not. + * We need to work out a suitable prefix length for + * potentially more than one interface. */ + asla.sla = sla->sla; + asla.prefix_len = 0; + asla.sla_set = sla->sla_set; + sla = &asla; + } + + if (sla->prefix_len == 0) { + uint32_t sla_max; + int bits; + + sla_max = ia->sla_max; + if (sla_max == 0 && (sla == NULL || !sla->sla_set)) { + const struct interface *ifi; + + TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) { + if (ifi->index > sla_max) + sla_max = ifi->index; + } + } + + bits = fls32(sla_max); + + if (prefix->prefix_len + bits > (int)UINT8_MAX) + asla.prefix_len = UINT8_MAX; + else { + asla.prefix_len = (uint8_t)(prefix->prefix_len + bits); + + /* Make a 64 prefix by default, as this makes SLAAC + * possible. + * Otherwise round up to the nearest 4 bits. */ + if (asla.prefix_len <= 64) + asla.prefix_len = 64; + else + asla.prefix_len = + (uint8_t)ROUNDUP4(asla.prefix_len); + } + +#define BIT(n) (1UL << (n)) +#define BIT_MASK(len) (BIT(len) - 1) + if (ia->sla_max == 0) { + /* Work out the real sla_max from our bits used */ + bits = asla.prefix_len - prefix->prefix_len; + /* Make static analysis happy. + * Bits cannot be bigger than 32 thanks to fls32. */ + assert(bits <= 32); + ia->sla_max = (uint32_t)BIT_MASK(bits); + } + } + + if (ipv6_userprefix(&prefix->prefix, prefix->prefix_len, + sla->sla, addr, sla->prefix_len) == -1) + { + sa = inet_ntop(AF_INET6, &prefix->prefix, + sabuf, sizeof(sabuf)); + logerr("%s: invalid prefix %s/%d + %d/%d", + ifp->name, sa, prefix->prefix_len, + sla->sla, sla->prefix_len); + return -1; + } + + if (prefix->prefix_exclude_len && + IN6_ARE_ADDR_EQUAL(addr, &prefix->prefix_exclude)) + { + sa = inet_ntop(AF_INET6, &prefix->prefix_exclude, + sabuf, sizeof(sabuf)); + logerrx("%s: cannot delegate excluded prefix %s/%d", + ifp->name, sa, prefix->prefix_exclude_len); + return -1; + } + + return sla->prefix_len; +} +#endif + +static int +dhcp6_makemessage(struct interface *ifp) +{ + struct dhcp6_state *state; + struct dhcp6_message *m; + struct dhcp6_option o; + uint8_t *p, *si, *unicast, IA; + size_t n, l, len, ml, hl; + uint8_t type; + uint16_t si_len, uni_len, n_options; + uint8_t *o_lenp; + struct if_options *ifo = ifp->options; + const struct dhcp_opt *opt, *opt2; + const struct ipv6_addr *ap; + char hbuf[HOSTNAME_MAX_LEN + 1]; + const char *hostname; + int fqdn; + struct dhcp6_ia_na ia_na; + uint16_t ia_na_len; + struct if_ia *ifia; +#ifdef AUTH + uint16_t auth_len; +#endif + uint8_t duid[DUID_LEN]; + size_t duid_len = 0; + + state = D6_STATE(ifp); + if (state->send) { + free(state->send); + state->send = NULL; + } + + switch(state->state) { + case DH6S_INIT: /* FALLTHROUGH */ + case DH6S_DISCOVER: + type = DHCP6_SOLICIT; + break; + case DH6S_REQUEST: + type = DHCP6_REQUEST; + break; + case DH6S_CONFIRM: + type = DHCP6_CONFIRM; + break; + case DH6S_REBIND: + type = DHCP6_REBIND; + break; + case DH6S_RENEW: + type = DHCP6_RENEW; + break; + case DH6S_INFORM: + type = DHCP6_INFORMATION_REQ; + break; + case DH6S_RELEASE: + type = DHCP6_RELEASE; + break; + case DH6S_DECLINE: + type = DHCP6_DECLINE; + break; + default: + errno = EINVAL; + return -1; + } + + /* RFC 4704 Section 5 says we can only send FQDN for these + * message types. */ + switch(type) { + case DHCP6_SOLICIT: + case DHCP6_REQUEST: + case DHCP6_RENEW: + case DHCP6_REBIND: + fqdn = ifo->fqdn; + break; + default: + fqdn = FQDN_DISABLE; + break; + } + + if (fqdn == FQDN_DISABLE && ifo->options & DHCPCD_HOSTNAME) { + /* We're sending the DHCPv4 hostname option, so send FQDN as + * DHCPv6 has no FQDN option and DHCPv4 must not send + * hostname and FQDN according to RFC4702 */ + fqdn = FQDN_BOTH; + } + if (fqdn != FQDN_DISABLE) + hostname = dhcp_get_hostname(hbuf, sizeof(hbuf), ifo); + else + hostname = NULL; /* appearse gcc */ + + /* Work out option size first */ + n_options = 0; + len = 0; + si = NULL; + hl = 0; /* Appease gcc */ + if (state->state != DH6S_RELEASE && state->state != DH6S_DECLINE) { + for (l = 0, opt = ifp->ctx->dhcp6_opts; + l < ifp->ctx->dhcp6_opts_len; + l++, opt++) + { + for (n = 0, opt2 = ifo->dhcp6_override; + n < ifo->dhcp6_override_len; + n++, opt2++) + { + if (opt->option == opt2->option) + break; + } + if (n < ifo->dhcp6_override_len) + continue; + if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + continue; + n_options++; + len += sizeof(o.len); + } +#ifndef SMALL + for (l = 0, opt = ifo->dhcp6_override; + l < ifo->dhcp6_override_len; + l++, opt++) + { + if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + continue; + n_options++; + len += sizeof(o.len); + } + if (dhcp6_findselfsla(ifp)) { + n_options++; + len += sizeof(o.len); + } +#endif + if (len) + len += sizeof(o); + + if (fqdn != FQDN_DISABLE) { + hl = encode_rfc1035(hostname, NULL); + len += sizeof(o) + 1 + hl; + } + + if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) && + ifo->mudurl[0]) + len += sizeof(o) + ifo->mudurl[0]; + +#ifdef AUTH + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE && + DHC_REQ(ifo->requestmask6, ifo->nomask6, + D6_OPTION_RECONF_ACCEPT)) + len += sizeof(o); /* Reconfigure Accept */ +#endif + } + + len += sizeof(*state->send); + len += sizeof(o) + sizeof(uint16_t); /* elapsed */ + + if (ifo->options & DHCPCD_ANONYMOUS) { + duid_len = duid_make(duid, ifp, DUID_LL); + len += sizeof(o) + duid_len; + } else { + len += sizeof(o) + ifp->ctx->duid_len; + } + + if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) + len += dhcp6_makeuser(NULL, ifp); + if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) + len += dhcp6_makevendor(NULL, ifp); + + /* IA */ + m = NULL; + ml = 0; + switch(state->state) { + case DH6S_REQUEST: + m = state->recv; + ml = state->recv_len; + /* FALLTHROUGH */ + case DH6S_DECLINE: + /* FALLTHROUGH */ + case DH6S_RELEASE: + /* FALLTHROUGH */ + case DH6S_RENEW: + if (m == NULL) { + m = state->new; + ml = state->new_len; + } + si = dhcp6_findmoption(m, ml, D6_OPTION_SERVERID, &si_len); + if (si == NULL) + return -1; + len += sizeof(o) + si_len; + /* FALLTHROUGH */ + case DH6S_REBIND: + /* FALLTHROUGH */ + case DH6S_CONFIRM: + /* FALLTHROUGH */ + case DH6S_DISCOVER: + if (m == NULL) { + m = state->new; + ml = state->new_len; + } + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->flags & IPV6_AF_STALE) + continue; + if (!(ap->flags & IPV6_AF_REQUEST) && + (ap->prefix_vltime == 0 || + state->state == DH6S_DISCOVER)) + continue; + if (DECLINE_IA(ap) && state->state != DH6S_DECLINE) + continue; + if (ap->ia_type == D6_OPTION_IA_PD) { +#ifndef SMALL + len += sizeof(o) + sizeof(struct dhcp6_pd_addr); + if (ap->prefix_exclude_len) + len += sizeof(o) + 1 + + (uint8_t)((ap->prefix_exclude_len - + ap->prefix_len - 1) / NBBY) + 1; +#endif + } else + len += sizeof(o) + sizeof(struct dhcp6_ia_addr); + } + /* FALLTHROUGH */ + case DH6S_INIT: + for (l = 0; l < ifo->ia_len; l++) { + len += sizeof(o) + sizeof(uint32_t); /* IAID */ + /* IA_TA does not have T1 or T2 timers */ + if (ifo->ia[l].ia_type != D6_OPTION_IA_TA) + len += sizeof(uint32_t) + sizeof(uint32_t); + } + IA = 1; + break; + default: + IA = 0; + } + + if (state->state == DH6S_DISCOVER && + !(ifp->ctx->options & DHCPCD_TEST) && + DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT)) + len += sizeof(o); + + if (m == NULL) { + m = state->new; + ml = state->new_len; + } + + switch(state->state) { + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_RENEW: /* FALLTHROUGH */ + case DH6S_RELEASE: + if (has_option_mask(ifo->nomask6, D6_OPTION_UNICAST)) { + unicast = NULL; + break; + } + unicast = dhcp6_findmoption(m, ml, D6_OPTION_UNICAST, &uni_len); + break; + default: + unicast = NULL; + break; + } + + /* In non manager mode we listen and send from fixed addresses. + * We should try and match an address we have to unicast to, + * but for now this is the safest policy. */ + if (unicast != NULL && !(ifp->ctx->options & DHCPCD_MANAGER)) { + logdebugx("%s: ignoring unicast option as not manager", + ifp->name); + unicast = NULL; + } + +#ifdef AUTH + auth_len = 0; + if (ifo->auth.options & DHCPCD_AUTH_SEND) { + ssize_t alen = dhcp_auth_encode(ifp->ctx, &ifo->auth, + state->auth.token, NULL, 0, 6, type, NULL, 0); + if (alen != -1 && alen > UINT16_MAX) { + errno = ERANGE; + alen = -1; + } + if (alen == -1) + logerr("%s: %s: dhcp_auth_encode", __func__, ifp->name); + else if (alen != 0) { + auth_len = (uint16_t)alen; + len += sizeof(o) + auth_len; + } + } +#endif + + state->send = malloc(len); + if (state->send == NULL) + return -1; + + state->send_len = len; + state->send->type = type; + + /* If we found a unicast option, copy it to our state for sending */ + if (unicast && uni_len == sizeof(state->unicast)) + memcpy(&state->unicast, unicast, sizeof(state->unicast)); + else + state->unicast = in6addr_any; + + dhcp6_newxid(ifp, state->send); + +#define COPYIN1(_code, _len) { \ + o.code = htons((_code)); \ + o.len = htons((_len)); \ + memcpy(p, &o, sizeof(o)); \ + p += sizeof(o); \ +} +#define COPYIN(_code, _data, _len) do { \ + COPYIN1((_code), (_len)); \ + if ((_len) != 0) { \ + memcpy(p, (_data), (_len)); \ + p += (_len); \ + } \ +} while (0 /* CONSTCOND */) +#define NEXTLEN (p + offsetof(struct dhcp6_option, len)) + + /* Options are listed in numerical order as per RFC 7844 Section 4.1 + * XXX: They should be randomised. */ + + p = (uint8_t *)state->send + sizeof(*state->send); + if (ifo->options & DHCPCD_ANONYMOUS) + COPYIN(D6_OPTION_CLIENTID, duid, + (uint16_t)duid_len); + else + COPYIN(D6_OPTION_CLIENTID, ifp->ctx->duid, + (uint16_t)ifp->ctx->duid_len); + + if (si != NULL) + COPYIN(D6_OPTION_SERVERID, si, si_len); + + for (l = 0; IA && l < ifo->ia_len; l++) { + ifia = &ifo->ia[l]; + o_lenp = NEXTLEN; + /* TA structure is the same as the others, + * it just lacks the T1 and T2 timers. + * These happen to be at the end of the struct, + * so we just don't copy them in. */ + if (ifia->ia_type == D6_OPTION_IA_TA) + ia_na_len = sizeof(struct dhcp6_ia_ta); + else + ia_na_len = sizeof(ia_na); + memcpy(ia_na.iaid, ifia->iaid, sizeof(ia_na.iaid)); + ia_na.t1 = 0; + ia_na.t2 = 0; + COPYIN(ifia->ia_type, &ia_na, ia_na_len); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->flags & IPV6_AF_STALE) + continue; + if (!(ap->flags & IPV6_AF_REQUEST) && + (ap->prefix_vltime == 0 || + state->state == DH6S_DISCOVER)) + continue; + if (DECLINE_IA(ap) && state->state != DH6S_DECLINE) + continue; + if (ap->ia_type != ifia->ia_type) + continue; + if (memcmp(ap->iaid, ifia->iaid, sizeof(ap->iaid))) + continue; + if (ap->ia_type == D6_OPTION_IA_PD) { +#ifndef SMALL + struct dhcp6_pd_addr pdp; + + pdp.pltime = htonl(ap->prefix_pltime); + pdp.vltime = htonl(ap->prefix_vltime); + pdp.prefix_len = ap->prefix_len; + /* pdp.prefix is not aligned, so copy it in. */ + memcpy(&pdp.prefix, &ap->prefix, sizeof(pdp.prefix)); + COPYIN(D6_OPTION_IAPREFIX, &pdp, sizeof(pdp)); + ia_na_len = (uint16_t) + (ia_na_len + sizeof(o) + sizeof(pdp)); + + /* RFC6603 Section 4.2 */ + if (ap->prefix_exclude_len) { + uint8_t exb[16], *ep, u8; + const uint8_t *pp; + + n = (size_t)((ap->prefix_exclude_len - + ap->prefix_len - 1) / NBBY) + 1; + ep = exb; + *ep++ = (uint8_t)ap->prefix_exclude_len; + pp = ap->prefix_exclude.s6_addr; + pp += (size_t) + ((ap->prefix_len - 1) / NBBY) + + (n - 1); + u8 = ap->prefix_len % NBBY; + if (u8) + n--; + while (n-- > 0) + *ep++ = *pp--; + if (u8) + *ep = (uint8_t)(*pp << u8); + n++; + COPYIN(D6_OPTION_PD_EXCLUDE, exb, + (uint16_t)n); + ia_na_len = (uint16_t) + (ia_na_len + sizeof(o) + n); + } +#endif + } else { + struct dhcp6_ia_addr ia; + + ia.addr = ap->addr; + ia.pltime = htonl(ap->prefix_pltime); + ia.vltime = htonl(ap->prefix_vltime); + COPYIN(D6_OPTION_IA_ADDR, &ia, sizeof(ia)); + ia_na_len = (uint16_t) + (ia_na_len + sizeof(o) + sizeof(ia)); + } + } + + /* Update the total option lenth. */ + ia_na_len = htons(ia_na_len); + memcpy(o_lenp, &ia_na_len, sizeof(ia_na_len)); + } + + if (state->send->type != DHCP6_RELEASE && + state->send->type != DHCP6_DECLINE && + n_options) + { + o_lenp = NEXTLEN; + o.len = 0; + COPYIN1(D6_OPTION_ORO, 0); + for (l = 0, opt = ifp->ctx->dhcp6_opts; + l < ifp->ctx->dhcp6_opts_len; + l++, opt++) + { +#ifndef SMALL + for (n = 0, opt2 = ifo->dhcp6_override; + n < ifo->dhcp6_override_len; + n++, opt2++) + { + if (opt->option == opt2->option) + break; + } + if (n < ifo->dhcp6_override_len) + continue; +#endif + if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + continue; + o.code = htons((uint16_t)opt->option); + memcpy(p, &o.code, sizeof(o.code)); + p += sizeof(o.code); + o.len = (uint16_t)(o.len + sizeof(o.code)); + } +#ifndef SMALL + for (l = 0, opt = ifo->dhcp6_override; + l < ifo->dhcp6_override_len; + l++, opt++) + { + if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + continue; + o.code = htons((uint16_t)opt->option); + memcpy(p, &o.code, sizeof(o.code)); + p += sizeof(o.code); + o.len = (uint16_t)(o.len + sizeof(o.code)); + } + if (dhcp6_findselfsla(ifp)) { + o.code = htons(D6_OPTION_PD_EXCLUDE); + memcpy(p, &o.code, sizeof(o.code)); + p += sizeof(o.code); + o.len = (uint16_t)(o.len + sizeof(o.code)); + } +#endif + o.len = htons(o.len); + memcpy(o_lenp, &o.len, sizeof(o.len)); + } + + si_len = 0; + COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len)); + + if (state->state == DH6S_DISCOVER && + !(ifp->ctx->options & DHCPCD_TEST) && + DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT)) + COPYIN1(D6_OPTION_RAPID_COMMIT, 0); + + if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) + p += dhcp6_makeuser(p, ifp); + if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) + p += dhcp6_makevendor(p, ifp); + + if (state->send->type != DHCP6_RELEASE && + state->send->type != DHCP6_DECLINE) + { + if (fqdn != FQDN_DISABLE) { + o_lenp = NEXTLEN; + COPYIN1(D6_OPTION_FQDN, 0); + if (hl == 0) + *p = D6_FQDN_NONE; + else { + switch (fqdn) { + case FQDN_BOTH: + *p = D6_FQDN_BOTH; + break; + case FQDN_PTR: + *p = D6_FQDN_PTR; + break; + default: + *p = D6_FQDN_NONE; + break; + } + } + p++; + encode_rfc1035(hostname, p); + p += hl; + o.len = htons((uint16_t)(hl + 1)); + memcpy(o_lenp, &o.len, sizeof(o.len)); + } + + if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) && + ifo->mudurl[0]) + COPYIN(D6_OPTION_MUDURL, + ifo->mudurl + 1, ifo->mudurl[0]); + +#ifdef AUTH + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE && + DHC_REQ(ifo->requestmask6, ifo->nomask6, + D6_OPTION_RECONF_ACCEPT)) + COPYIN1(D6_OPTION_RECONF_ACCEPT, 0); +#endif + + } + +#ifdef AUTH + /* This has to be the last option */ + if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) { + COPYIN1(D6_OPTION_AUTH, auth_len); + /* data will be filled at send message time */ + } +#endif + + return 0; +} + +static const char * +dhcp6_get_op(uint16_t type) +{ + const struct dhcp6_op *d; + + for (d = dhcp6_ops; d->name; d++) + if (d->type == type) + return d->name; + return NULL; +} + +static void +dhcp6_freedrop_addrs(struct interface *ifp, int drop, + const struct interface *ifd) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + if (state) { + ipv6_freedrop_addrs(&state->addrs, drop, ifd); + if (drop) + rt_build(ifp->ctx, AF_INET6); + } +} + +#ifndef SMALL +static void dhcp6_delete_delegates(struct interface *ifp) +{ + struct interface *ifp0; + + if (ifp->ctx->ifaces) { + TAILQ_FOREACH(ifp0, ifp->ctx->ifaces, next) { + if (ifp0 != ifp) + dhcp6_freedrop_addrs(ifp0, 1, ifp); + } + } +} +#endif + +#ifdef AUTH +static ssize_t +dhcp6_update_auth(struct interface *ifp, struct dhcp6_message *m, size_t len) +{ + struct dhcp6_state *state; + uint8_t *opt; + uint16_t opt_len; + + opt = dhcp6_findmoption(m, len, D6_OPTION_AUTH, &opt_len); + if (opt == NULL) + return -1; + + state = D6_STATE(ifp); + return dhcp_auth_encode(ifp->ctx, &ifp->options->auth, + state->auth.token, (uint8_t *)state->send, state->send_len, 6, + state->send->type, opt, opt_len); +} +#endif + +static const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT; +static int +dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) +{ + struct dhcp6_state *state = D6_STATE(ifp); + struct dhcpcd_ctx *ctx = ifp->ctx; + unsigned int RT; + bool broadcast = true; + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + /* Setting the port on Linux gives EINVAL when sending. + * This looks like a kernel bug as the equivalent works + * fine with the DHCP counterpart. */ +#ifndef __linux__ + .sin6_port = htons(DHCP6_SERVER_PORT), +#endif + }; + struct udphdr udp = { + .uh_sport = htons(DHCP6_CLIENT_PORT), + .uh_dport = htons(DHCP6_SERVER_PORT), + .uh_ulen = htons((uint16_t)(sizeof(udp) + state->send_len)), + }; + struct iovec iov[] = { + { .iov_base = &udp, .iov_len = sizeof(udp), }, + { .iov_base = state->send, .iov_len = state->send_len, }, + }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &dst, .msg_namelen = sizeof(dst), + .msg_iov = iov, .msg_iovlen = __arraycount(iov), + }; + char uaddr[INET6_ADDRSTRLEN]; + + if (!callback && !if_is_link_up(ifp)) + return 0; + + if (!IN6_IS_ADDR_UNSPECIFIED(&state->unicast)) { + switch (state->send->type) { + case DHCP6_SOLICIT: /* FALLTHROUGH */ + case DHCP6_CONFIRM: /* FALLTHROUGH */ + case DHCP6_REBIND: + /* Unicasting is denied for these types. */ + break; + default: + broadcast = false; + inet_ntop(AF_INET6, &state->unicast, uaddr, + sizeof(uaddr)); + break; + } + } + dst.sin6_addr = broadcast ? alldhcp : state->unicast; + + if (!callback) { + logdebugx("%s: %s %s with xid 0x%02x%02x%02x%s%s", + ifp->name, + broadcast ? "broadcasting" : "unicasting", + dhcp6_get_op(state->send->type), + state->send->xid[0], + state->send->xid[1], + state->send->xid[2], + !broadcast ? " " : "", + !broadcast ? uaddr : ""); + RT = 0; + } else { + if (state->IMD && + !(ifp->options->options & DHCPCD_INITIAL_DELAY)) + state->IMD = 0; + if (state->IMD) { + state->RT = state->IMD * MSEC_PER_SEC; + /* Some buggy PPP servers close the link too early + * after sending an invalid status in their reply + * which means this host won't see it. + * 1 second grace seems to be the sweet spot. */ + if (ifp->flags & IFF_POINTOPOINT) + state->RT += MSEC_PER_SEC; + } else if (state->RTC == 0) + state->RT = state->IRT * MSEC_PER_SEC; + + if (state->MRT != 0) { + unsigned int mrt = state->MRT * MSEC_PER_SEC; + + if (state->RT > mrt) + state->RT = mrt; + } + + /* Add -.1 to .1 * RT randomness as per RFC8415 section 15 */ + uint32_t lru = arc4random_uniform( + state->RTC == 0 ? DHCP6_RAND_MAX + : DHCP6_RAND_MAX - DHCP6_RAND_MIN); + int lr = (int)lru - (state->RTC == 0 ? 0 : DHCP6_RAND_MAX); + RT = state->RT + + (unsigned int)((float)state->RT + * ((float)lr / DHCP6_RAND_DIV)); + + if (if_is_link_up(ifp)) + logdebugx("%s: %s %s (xid 0x%02x%02x%02x)%s%s," + " next in %0.1f seconds", + ifp->name, + state->IMD != 0 ? "delaying" : + broadcast ? "broadcasting" : "unicasting", + dhcp6_get_op(state->send->type), + state->send->xid[0], + state->send->xid[1], + state->send->xid[2], + state->IMD == 0 && !broadcast ? " " : "", + state->IMD == 0 && !broadcast ? uaddr : "", + (float)RT / MSEC_PER_SEC); + + /* Wait the initial delay */ + if (state->IMD != 0) { + state->IMD = 0; + eloop_timeout_add_msec(ctx->eloop, RT, callback, ifp); + return 0; + } + } + + if (!if_is_link_up(ifp)) + return 0; + + /* Update the elapsed time */ + dhcp6_updateelapsed(ifp, state->send, state->send_len); +#ifdef AUTH + if (ifp->options->auth.options & DHCPCD_AUTH_SEND && + dhcp6_update_auth(ifp, state->send, state->send_len) == -1) + { + logerr("%s: %s: dhcp6_updateauth", __func__, ifp->name); + if (errno != ESRCH) + return -1; + } +#endif + + /* Set the outbound interface */ + if (broadcast) { + struct cmsghdr *cm; + struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; + + dst.sin6_scope_id = ifp->index; + msg.msg_control = cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cm = CMSG_FIRSTHDR(&msg); + if (cm == NULL) /* unlikely */ + return -1; + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(pi)); + memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); + } + +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_inet_senddhcp6(ifp, &msg) == -1) + logerr(__func__); + goto sent; + } +#endif + + if (sendmsg(ctx->dhcp6_wfd, &msg, 0) == -1) { + logerr("%s: %s: sendmsg", __func__, ifp->name); + /* Allow DHCPv6 to continue .... the errors + * would be rate limited by the protocol. + * Generally the error is ENOBUFS when struggling to + * associate with an access point. */ + } + +#ifdef PRIVSEP +sent: +#endif + state->RTC++; + if (callback) { + state->RT = RT * 2; + if (state->RT < RT) /* Check overflow */ + state->RT = RT; + if (state->MRC == 0 || state->RTC < state->MRC) + eloop_timeout_add_msec(ctx->eloop, + RT, callback, ifp); + else if (state->MRC != 0 && state->MRCcallback) + eloop_timeout_add_msec(ctx->eloop, + RT, state->MRCcallback, ifp); + else + logwarnx("%s: sent %d times with no reply", + ifp->name, state->RTC); + } + return 0; +} + +static void +dhcp6_sendinform(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendinform); +} + +static void +dhcp6_senddiscover(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_senddiscover); +} + +static void +dhcp6_sendrequest(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrequest); +} + +static void +dhcp6_sendrebind(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrebind); +} + +static void +dhcp6_sendrenew(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrenew); +} + +static void +dhcp6_sendconfirm(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendconfirm); +} + +static void +dhcp6_senddecline(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_senddecline); +} + +static void +dhcp6_sendrelease(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrelease); +} + +static void +dhcp6_startrenew(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + + ifp = arg; + if ((state = D6_STATE(ifp)) == NULL) + return; + + /* Only renew in the bound or renew states */ + if (state->state != DH6S_BOUND && + state->state != DH6S_RENEW) + return; + + /* Remove the timeout as the renew may have been forced. */ + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startrenew, ifp); + + state->state = DH6S_RENEW; + state->RTC = 0; + state->IMD = REN_MAX_DELAY; + state->IRT = REN_TIMEOUT; + state->MRT = REN_MAX_RT; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) + logerr("%s: %s", __func__, ifp->name); + else + dhcp6_sendrenew(ifp); +} + +void dhcp6_renew(struct interface *ifp) +{ + + dhcp6_startrenew(ifp); +} + +bool +dhcp6_dadcompleted(const struct interface *ifp) +{ + const struct dhcp6_state *state; + const struct ipv6_addr *ap; + + state = D6_CSTATE(ifp); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->flags & IPV6_AF_ADDED && + !(ap->flags & IPV6_AF_DADCOMPLETED)) + return false; + } + return true; +} + +static void +dhcp6_dadcallback(void *arg) +{ + struct ipv6_addr *ia = arg; + struct interface *ifp; + struct dhcp6_state *state; + struct ipv6_addr *ia2; + bool completed, valid, oneduplicated; + + completed = (ia->flags & IPV6_AF_DADCOMPLETED); + ia->flags |= IPV6_AF_DADCOMPLETED; + if (ia->addr_flags & IN6_IFF_DUPLICATED) + logwarnx("%s: DAD detected %s", ia->iface->name, ia->saddr); + +#ifdef ND6_ADVERTISE + else + ipv6nd_advertise(ia); +#endif + if (completed) + return; + + ifp = ia->iface; + state = D6_STATE(ifp); + if (state->state != DH6S_BOUND && state->state != DH6S_DELEGATED) + return; + +#ifdef SMALL + valid = true; +#else + valid = (ia->delegating_prefix == NULL); +#endif + completed = true; + oneduplicated = false; + TAILQ_FOREACH(ia2, &state->addrs, next) { + if (ia2->flags & IPV6_AF_ADDED && + !(ia2->flags & IPV6_AF_DADCOMPLETED)) + { + completed = false; + break; + } + if (DECLINE_IA(ia)) + oneduplicated = true; + } + if (!completed) + return; + + logdebugx("%s: DHCPv6 DAD completed", ifp->name); + + if (oneduplicated && state->state == DH6S_BOUND) { + dhcp6_startdecline(ifp); + return; + } + + script_runreason(ifp, +#ifndef SMALL + ia->delegating_prefix ? "DELEGATED6" : +#endif + state->reason); + if (valid) + dhcpcd_daemonise(ifp->ctx); +} + +static void +dhcp6_addrequestedaddrs(struct interface *ifp) +{ + struct dhcp6_state *state; + size_t i; + struct if_ia *ia; + struct ipv6_addr *a; + + state = D6_STATE(ifp); + /* Add any requested prefixes / addresses */ + for (i = 0; i < ifp->options->ia_len; i++) { + ia = &ifp->options->ia[i]; + if (!((ia->ia_type == D6_OPTION_IA_PD && ia->prefix_len) || + !IN6_IS_ADDR_UNSPECIFIED(&ia->addr))) + continue; + a = ipv6_newaddr(ifp, &ia->addr, + /* + * RFC 5942 Section 5 + * We cannot assume any prefix length, nor tie the + * address to an existing one as it could expire + * before the address. + * As such we just give it a 128 prefix. + */ + ia->ia_type == D6_OPTION_IA_PD ? ia->prefix_len : 128, + IPV6_AF_REQUEST); + if (a == NULL) + continue; + a->dadcallback = dhcp6_dadcallback; + memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid)); + a->ia_type = ia->ia_type; + TAILQ_INSERT_TAIL(&state->addrs, a, next); + } +} + +static void +dhcp6_startdiscover(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + int llevel; + + ifp = arg; + state = D6_STATE(ifp); +#ifndef SMALL + if (state->reason == NULL || strcmp(state->reason, "TIMEOUT6") != 0) + dhcp6_delete_delegates(ifp); +#endif + if (state->new == NULL && !state->failed) + llevel = LOG_INFO; + else + llevel = LOG_DEBUG; + logmessage(llevel, "%s: soliciting a DHCPv6 lease", ifp->name); + state->state = DH6S_DISCOVER; + state->RTC = 0; + state->IMD = SOL_MAX_DELAY; + state->IRT = SOL_TIMEOUT; + state->MRT = state->sol_max_rt; + state->MRC = SOL_MAX_RC; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + free(state->new); + state->new = NULL; + state->new_len = 0; + + if (dhcp6_makemessage(ifp) == -1) + logerr("%s: %s", __func__, ifp->name); + else + dhcp6_senddiscover(ifp); +} + +static void +dhcp6_startinform(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + int llevel; + + ifp = arg; + state = D6_STATE(ifp); + if (state->new_start || (state->new == NULL && !state->failed)) + llevel = LOG_INFO; + else + llevel = LOG_DEBUG; + logmessage(llevel, "%s: requesting DHCPv6 information", ifp->name); + state->state = DH6S_INFORM; + state->RTC = 0; + state->IMD = INF_MAX_DELAY; + state->IRT = INF_TIMEOUT; + state->MRT = state->inf_max_rt; + state->MRC = 0; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + if (dhcp6_makemessage(ifp) == -1) { + logerr("%s: %s", __func__, ifp->name); + return; + } + dhcp6_sendinform(ifp); + /* RFC3315 18.1.2 says that if CONFIRM failed then the prior addresses + * SHOULD be used. The wording here is poor, because the addresses are + * merely one facet of the lease as a whole. + * This poor wording might explain the lack of similar text for INFORM + * in 18.1.5 because there are no addresses in the INFORM message. */ + eloop_timeout_add_sec(ifp->ctx->eloop, + INF_MAX_RD, dhcp6_failinform, ifp); +} + +static bool +dhcp6_startdiscoinform(struct interface *ifp) +{ + unsigned long long opts = ifp->options->options; + + if (opts & DHCPCD_IA_FORCED || ipv6nd_hasradhcp(ifp, true)) + dhcp6_startdiscover(ifp); + else if (opts & DHCPCD_INFORM6 || ipv6nd_hasradhcp(ifp, false)) + dhcp6_startinform(ifp); + else + return false; + return true; +} + +static void +dhcp6_leaseextend(struct interface *ifp) +{ + struct dhcp6_state *state = D6_STATE(ifp); + struct ipv6_addr *ia; + + logwarnx("%s: extending DHCPv6 lease", ifp->name); + TAILQ_FOREACH(ia, &state->addrs, next) { + ia->flags |= IPV6_AF_EXTENDED; + /* Set infinite lifetimes. */ + ia->prefix_pltime = ND6_INFINITE_LIFETIME; + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + } +} + +static void +dhcp6_fail(struct interface *ifp) +{ + struct dhcp6_state *state = D6_STATE(ifp); + + state->failed = true; + + /* RFC3315 18.1.2 says that prior addresses SHOULD be used on failure. + * RFC2131 3.2.3 says that MAY chose to use the prior address. + * Because dhcpcd was written first for RFC2131, we have the LASTLEASE + * option which defaults to off as that makes the most sense for + * mobile clients. + * dhcpcd also has LASTLEASE_EXTEND to extend this lease past it's + * expiry, but this is strictly not RFC compliant in any way or form. */ + if (state->new != NULL && + ifp->options->options & DHCPCD_LASTLEASE_EXTEND) + { + dhcp6_leaseextend(ifp); + dhcp6_bind(ifp, NULL, NULL); + } else { + dhcp6_freedrop_addrs(ifp, 1, NULL); +#ifndef SMALL + dhcp6_delete_delegates(ifp); +#endif + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = NULL; + state->new_len = 0; + if (state->old != NULL) + script_runreason(ifp, "EXPIRE6"); + dhcp_unlink(ifp->ctx, state->leasefile); + dhcp6_addrequestedaddrs(ifp); + } + + if (!dhcp6_startdiscoinform(ifp)) { + logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name); + state->state = DH6S_INIT; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + } +} + +static int +dhcp6_failloglevel(struct interface *ifp) +{ + const struct dhcp6_state *state = D6_CSTATE(ifp); + + return state->failed ? LOG_DEBUG : LOG_ERR; +} + +static void +dhcp6_failconfirm(void *arg) +{ + struct interface *ifp = arg; + int llevel = dhcp6_failloglevel(ifp); + + logmessage(llevel, "%s: failed to confirm prior DHCPv6 address", + ifp->name); + dhcp6_fail(ifp); +} + +static void +dhcp6_failrequest(void *arg) +{ + struct interface *ifp = arg; + int llevel = dhcp6_failloglevel(ifp); + + logmessage(llevel, "%s: failed to request DHCPv6 address", ifp->name); + dhcp6_fail(ifp); +} + +static void +dhcp6_failinform(void *arg) +{ + struct interface *ifp = arg; + int llevel = dhcp6_failloglevel(ifp); + + logmessage(llevel, "%s: failed to request DHCPv6 information", + ifp->name); + dhcp6_fail(ifp); +} + +#ifndef SMALL +static void +dhcp6_failrebind(void *arg) +{ + struct interface *ifp = arg; + + logerrx("%s: failed to rebind prior DHCPv6 delegation", ifp->name); + dhcp6_fail(ifp); +} + +static int +dhcp6_hasprefixdelegation(struct interface *ifp) +{ + size_t i; + uint16_t t; + + t = 0; + for (i = 0; i < ifp->options->ia_len; i++) { + if (t && t != ifp->options->ia[i].ia_type) { + if (t == D6_OPTION_IA_PD || + ifp->options->ia[i].ia_type == D6_OPTION_IA_PD) + return 2; + } + t = ifp->options->ia[i].ia_type; + } + return t == D6_OPTION_IA_PD ? 1 : 0; +} +#endif + +static void +dhcp6_startrebind(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; +#ifndef SMALL + int pd; +#endif + + ifp = arg; + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp); + state = D6_STATE(ifp); + if (state->state == DH6S_RENEW) + logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name); + else + loginfox("%s: rebinding prior DHCPv6 lease", ifp->name); + state->state = DH6S_REBIND; + state->RTC = 0; + state->MRC = 0; + +#ifndef SMALL + /* RFC 3633 section 12.1 */ + pd = dhcp6_hasprefixdelegation(ifp); + if (pd) { + state->IMD = CNF_MAX_DELAY; + state->IRT = CNF_TIMEOUT; + state->MRT = CNF_MAX_RT; + } else +#endif + { + state->IMD = REB_MAX_DELAY; + state->IRT = REB_TIMEOUT; + state->MRT = REB_MAX_RT; + } + + if (dhcp6_makemessage(ifp) == -1) + logerr("%s: %s", __func__, ifp->name); + else + dhcp6_sendrebind(ifp); + +#ifndef SMALL + /* RFC 3633 section 12.1 */ + if (pd) + eloop_timeout_add_sec(ifp->ctx->eloop, + CNF_MAX_RD, dhcp6_failrebind, ifp); +#endif +} + + +static void +dhcp6_startrequest(struct interface *ifp) +{ + struct dhcp6_state *state; + + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp); + state = D6_STATE(ifp); + state->state = DH6S_REQUEST; + state->RTC = 0; + state->IMD = 0; + state->IRT = REQ_TIMEOUT; + state->MRT = REQ_MAX_RT; + state->MRC = REQ_MAX_RC; + state->MRCcallback = dhcp6_failrequest; + + if (dhcp6_makemessage(ifp) == -1) { + logerr("%s: %s", __func__, ifp->name); + return; + } + + dhcp6_sendrequest(ifp); +} + +static void +dhcp6_startconfirm(struct interface *ifp) +{ + struct dhcp6_state *state; + struct ipv6_addr *ia; + + state = D6_STATE(ifp); + + TAILQ_FOREACH(ia, &state->addrs, next) { + if (!DECLINE_IA(ia)) + continue; + logerrx("%s: prior DHCPv6 has a duplicated address", ifp->name); + dhcp6_startdecline(ifp); + return; + } + + state->state = DH6S_CONFIRM; + state->RTC = 0; + state->IMD = CNF_MAX_DELAY; + state->IRT = CNF_TIMEOUT; + state->MRT = CNF_MAX_RT; + state->MRC = CNF_MAX_RC; + + loginfox("%s: confirming prior DHCPv6 lease", ifp->name); + + if (dhcp6_makemessage(ifp) == -1) { + logerr("%s: %s", __func__, ifp->name); + return; + } + dhcp6_sendconfirm(ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + CNF_MAX_RD, dhcp6_failconfirm, ifp); +} + +static void +dhcp6_startexpire(void *arg) +{ + struct interface *ifp; + + ifp = arg; + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp); + + logerrx("%s: DHCPv6 lease expired", ifp->name); + dhcp6_fail(ifp); +} + +static void +dhcp6_faildecline(void *arg) +{ + struct interface *ifp = arg; + + logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name); + dhcp6_fail(ifp); +} + +static void +dhcp6_startdecline(struct interface *ifp) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + loginfox("%s: declining failed DHCPv6 addresses", ifp->name); + state->state = DH6S_DECLINE; + state->RTC = 0; + state->IMD = 0; + state->IRT = DEC_TIMEOUT; + state->MRT = 0; + state->MRC = DEC_MAX_RC; + state->MRCcallback = dhcp6_faildecline; + + if (dhcp6_makemessage(ifp) == -1) + logerr("%s: %s", __func__, ifp->name); + else + dhcp6_senddecline(ifp); +} + +static void +dhcp6_finishrelease(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + + ifp = (struct interface *)arg; + if ((state = D6_STATE(ifp)) != NULL) { + state->state = DH6S_RELEASED; + dhcp6_drop(ifp, "RELEASE6"); + } +} + +static void +dhcp6_startrelease(struct interface *ifp) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + if (state->state != DH6S_BOUND) + return; + + state->state = DH6S_RELEASE; + state->RTC = 0; + state->IMD = REL_MAX_DELAY; + state->IRT = REL_TIMEOUT; + state->MRT = REL_MAX_RT; + /* MRC of REL_MAX_RC is optional in RFC 3315 18.1.6 */ +#if 0 + state->MRC = REL_MAX_RC; + state->MRCcallback = dhcp6_finishrelease; +#else + state->MRC = 0; + state->MRCcallback = NULL; +#endif + + if (dhcp6_makemessage(ifp) == -1) + logerr("%s: %s", __func__, ifp->name); + else { + dhcp6_sendrelease(ifp); + dhcp6_finishrelease(ifp); + } +} + +static int +dhcp6_checkstatusok(const struct interface *ifp, + struct dhcp6_message *m, uint8_t *p, size_t len) +{ + struct dhcp6_state *state; + uint8_t *opt; + uint16_t opt_len, code; + size_t mlen; + void * (*f)(void *, size_t, uint16_t, uint16_t *), *farg; + char buf[32], *sbuf; + const char *status; + int loglevel; + + state = D6_STATE(ifp); + f = p ? dhcp6_findoption : dhcp6_findmoption; + if (p) + farg = p; + else + farg = m; + if ((opt = f(farg, len, D6_OPTION_STATUS_CODE, &opt_len)) == NULL) { + //logdebugx("%s: no status", ifp->name); + state->lerror = 0; + errno = ESRCH; + return 0; + } + + if (opt_len < sizeof(code)) { + logerrx("%s: status truncated", ifp->name); + return -1; + } + memcpy(&code, opt, sizeof(code)); + code = ntohs(code); + if (code == D6_STATUS_OK) { + state->lerror = 0; + errno = 0; + return 0; + } + + /* Anything after the code is a message. */ + opt += sizeof(code); + mlen = opt_len - sizeof(code); + if (mlen == 0) { + sbuf = NULL; + if (code < sizeof(dhcp6_statuses) / sizeof(char *)) + status = dhcp6_statuses[code]; + else { + snprintf(buf, sizeof(buf), "Unknown Status (%d)", code); + status = buf; + } + } else { + if ((sbuf = malloc(mlen + 1)) == NULL) { + logerr(__func__); + return -1; + } + memcpy(sbuf, opt, mlen); + sbuf[mlen] = '\0'; + status = sbuf; + } + + if (state->lerror == code || state->state == DH6S_INIT) + loglevel = LOG_DEBUG; + else + loglevel = LOG_ERR; + logmessage(loglevel, "%s: DHCPv6 REPLY: %s", ifp->name, status); + free(sbuf); + state->lerror = code; + errno = 0; + + /* code cannot be D6_STATUS_OK, so there is a failure */ + if (ifp->ctx->options & DHCPCD_TEST) + eloop_exit(ifp->ctx->eloop, EXIT_FAILURE); + + return (int)code; +} + +const struct ipv6_addr * +dhcp6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr, + unsigned int flags) +{ + const struct dhcp6_state *state; + const struct ipv6_addr *ap; + + if ((state = D6_STATE(ifp)) != NULL) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ipv6_findaddrmatch(ap, addr, flags)) + return ap; + } + } + return NULL; +} + +struct ipv6_addr * +dhcp6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, + unsigned int flags) +{ + struct interface *ifp; + struct ipv6_addr *ap; + struct dhcp6_state *state; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if ((state = D6_STATE(ifp)) != NULL) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ipv6_findaddrmatch(ap, addr, flags)) + return ap; + } + } + } + return NULL; +} + +static int +dhcp6_findna(struct interface *ifp, uint16_t ot, const uint8_t *iaid, + uint8_t *d, size_t l, const struct timespec *acquired) +{ + struct dhcp6_state *state; + uint8_t *o, *nd; + uint16_t ol; + struct ipv6_addr *a; + int i; + struct dhcp6_ia_addr ia; + + i = 0; + state = D6_STATE(ifp); + while ((o = dhcp6_findoption(d, l, D6_OPTION_IA_ADDR, &ol))) { + /* Set d and l first to ensure we find the next option. */ + nd = o + ol; + l -= (size_t)(nd - d); + d = nd; + if (ol < sizeof(ia)) { + errno = EINVAL; + logerrx("%s: IA Address option truncated", ifp->name); + continue; + } + memcpy(&ia, o, sizeof(ia)); + ia.pltime = ntohl(ia.pltime); + ia.vltime = ntohl(ia.vltime); + /* RFC 3315 22.6 */ + if (ia.pltime > ia.vltime) { + errno = EINVAL; + logerr("%s: IA Address pltime %"PRIu32 + " > vltime %"PRIu32, + ifp->name, ia.pltime, ia.vltime); + continue; + } + TAILQ_FOREACH(a, &state->addrs, next) { + if (ipv6_findaddrmatch(a, &ia.addr, 0)) + break; + } + if (a == NULL) { + /* + * RFC 5942 Section 5 + * We cannot assume any prefix length, nor tie the + * address to an existing one as it could expire + * before the address. + * As such we just give it a 128 prefix. + */ + a = ipv6_newaddr(ifp, &ia.addr, 128, IPV6_AF_ONLINK); + a->dadcallback = dhcp6_dadcallback; + a->ia_type = ot; + memcpy(a->iaid, iaid, sizeof(a->iaid)); + a->created = *acquired; + + TAILQ_INSERT_TAIL(&state->addrs, a, next); + } else { + if (!(a->flags & IPV6_AF_ONLINK)) + a->flags |= IPV6_AF_ONLINK | IPV6_AF_NEW; + a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED); + } + a->acquired = *acquired; + a->prefix_pltime = ia.pltime; + if (a->prefix_vltime != ia.vltime) { + a->flags |= IPV6_AF_NEW; + a->prefix_vltime = ia.vltime; + } + if (a->prefix_pltime && a->prefix_pltime < state->lowpl) + state->lowpl = a->prefix_pltime; + if (a->prefix_vltime && a->prefix_vltime > state->expire) + state->expire = a->prefix_vltime; + i++; + } + return i; +} + +#ifndef SMALL +static int +dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, + uint8_t *d, size_t l, const struct timespec *acquired) +{ + struct dhcp6_state *state; + uint8_t *o, *nd; + struct ipv6_addr *a; + int i; + uint8_t nb, *pw; + uint16_t ol; + struct dhcp6_pd_addr pdp; + struct in6_addr pdp_prefix; + + i = 0; + state = D6_STATE(ifp); + while ((o = dhcp6_findoption(d, l, D6_OPTION_IAPREFIX, &ol))) { + /* Set d and l first to ensure we find the next option. */ + nd = o + ol; + l -= (size_t)(nd - d); + d = nd; + if (ol < sizeof(pdp)) { + errno = EINVAL; + logerrx("%s: IA Prefix option truncated", ifp->name); + continue; + } + + memcpy(&pdp, o, sizeof(pdp)); + pdp.pltime = ntohl(pdp.pltime); + pdp.vltime = ntohl(pdp.vltime); + /* RFC 3315 22.6 */ + if (pdp.pltime > pdp.vltime) { + errno = EINVAL; + logerrx("%s: IA Prefix pltime %"PRIu32 + " > vltime %"PRIu32, + ifp->name, pdp.pltime, pdp.vltime); + continue; + } + + o += sizeof(pdp); + ol = (uint16_t)(ol - sizeof(pdp)); + + /* pdp.prefix is not aligned so copy it out. */ + memcpy(&pdp_prefix, &pdp.prefix, sizeof(pdp_prefix)); + TAILQ_FOREACH(a, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&a->prefix, &pdp_prefix)) + break; + } + + if (a == NULL) { + a = ipv6_newaddr(ifp, &pdp_prefix, pdp.prefix_len, + IPV6_AF_DELEGATEDPFX); + if (a == NULL) + break; + a->created = *acquired; + a->dadcallback = dhcp6_dadcallback; + a->ia_type = D6_OPTION_IA_PD; + memcpy(a->iaid, iaid, sizeof(a->iaid)); + TAILQ_INSERT_TAIL(&state->addrs, a, next); + } else { + if (!(a->flags & IPV6_AF_DELEGATEDPFX)) + a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; + a->flags &= ~(IPV6_AF_STALE | + IPV6_AF_EXTENDED | + IPV6_AF_REQUEST); + if (a->prefix_vltime != pdp.vltime) + a->flags |= IPV6_AF_NEW; + } + + a->acquired = *acquired; + a->prefix_pltime = pdp.pltime; + a->prefix_vltime = pdp.vltime; + + if (a->prefix_pltime && a->prefix_pltime < state->lowpl) + state->lowpl = a->prefix_pltime; + if (a->prefix_vltime && a->prefix_vltime > state->expire) + state->expire = a->prefix_vltime; + i++; + + a->prefix_exclude_len = 0; + memset(&a->prefix_exclude, 0, sizeof(a->prefix_exclude)); + o = dhcp6_findoption(o, ol, D6_OPTION_PD_EXCLUDE, &ol); + if (o == NULL) + continue; + + /* RFC 6603 4.2 says option length MUST be between 2 and 17. + * This allows 1 octet for prefix length and 16 for the + * subnet ID. */ + if (ol < 2 || ol > 17) { + logerrx("%s: invalid PD Exclude option", ifp->name); + continue; + } + + /* RFC 6603 4.2 says prefix length MUST be between the + * length of the IAPREFIX prefix length + 1 and 128. */ + if (*o < a->prefix_len + 1 || *o > 128) { + logerrx("%s: invalid PD Exclude length", ifp->name); + continue; + } + + ol--; + /* Check option length matches prefix length. */ + if (((*o - a->prefix_len - 1) / NBBY) + 1 != ol) { + logerrx("%s: PD Exclude length mismatch", ifp->name); + continue; + } + a->prefix_exclude_len = *o++; + + memcpy(&a->prefix_exclude, &a->prefix, + sizeof(a->prefix_exclude)); + nb = a->prefix_len % NBBY; + if (nb) + ol--; + pw = a->prefix_exclude.s6_addr + + (a->prefix_exclude_len / NBBY) - 1; + while (ol-- > 0) + *pw-- = *o++; + if (nb) + *pw = (uint8_t)(*pw | (*o >> nb)); + } + return i; +} +#endif + +static int +dhcp6_findia(struct interface *ifp, struct dhcp6_message *m, size_t l, + const char *sfrom, const struct timespec *acquired) +{ + struct dhcp6_state *state; + const struct if_options *ifo; + struct dhcp6_option o; + uint8_t *d, *p; + struct dhcp6_ia_na ia; + int i, e, error; + size_t j; + uint16_t nl; + uint8_t iaid[4]; + char buf[sizeof(iaid) * 3]; + struct ipv6_addr *ap; + struct if_ia *ifia; + + if (l < sizeof(*m)) { + /* Should be impossible with guards at packet in + * and reading leases */ + errno = EINVAL; + return -1; + } + + ifo = ifp->options; + i = e = 0; + state = D6_STATE(ifp); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (!(ap->flags & IPV6_AF_DELEGATED)) + ap->flags |= IPV6_AF_STALE; + } + + d = (uint8_t *)m + sizeof(*m); + l -= sizeof(*m); + while (l > sizeof(o)) { + memcpy(&o, d, sizeof(o)); + o.len = ntohs(o.len); + if (o.len > l || sizeof(o) + o.len > l) { + errno = EINVAL; + logerrx("%s: option overflow", ifp->name); + break; + } + p = d + sizeof(o); + d = p + o.len; + l -= sizeof(o) + o.len; + + o.code = ntohs(o.code); + switch(o.code) { + case D6_OPTION_IA_TA: + nl = 4; + break; + case D6_OPTION_IA_NA: + case D6_OPTION_IA_PD: + nl = 12; + break; + default: + continue; + } + if (o.len < nl) { + errno = EINVAL; + logerrx("%s: IA option truncated", ifp->name); + continue; + } + + memcpy(&ia, p, nl); + p += nl; + o.len = (uint16_t)(o.len - nl); + + for (j = 0; j < ifo->ia_len; j++) { + ifia = &ifo->ia[j]; + if (ifia->ia_type == o.code && + memcmp(ifia->iaid, ia.iaid, sizeof(ia.iaid)) == 0) + break; + } + if (j == ifo->ia_len && + !(ifo->ia_len == 0 && ifp->ctx->options & DHCPCD_DUMPLEASE)) + { + logdebugx("%s: ignoring unrequested IAID %s", + ifp->name, + hwaddr_ntoa(ia.iaid, sizeof(ia.iaid), + buf, sizeof(buf))); + continue; + } + + if (o.code != D6_OPTION_IA_TA) { + ia.t1 = ntohl(ia.t1); + ia.t2 = ntohl(ia.t2); + /* RFC 3315 22.4 */ + if (ia.t2 > 0 && ia.t1 > ia.t2) { + logwarnx("%s: IAID %s T1(%d) > T2(%d) from %s", + ifp->name, + hwaddr_ntoa(iaid, sizeof(iaid), buf, + sizeof(buf)), + ia.t1, ia.t2, sfrom); + continue; + } + } else + ia.t1 = ia.t2 = 0; /* appease gcc */ + if ((error = dhcp6_checkstatusok(ifp, NULL, p, o.len)) != 0) { + if (error == D6_STATUS_NOBINDING) + state->has_no_binding = true; + e = 1; + continue; + } + if (o.code == D6_OPTION_IA_PD) { +#ifndef SMALL + if (dhcp6_findpd(ifp, ia.iaid, p, o.len, + acquired) == 0) + { + logwarnx("%s: %s: DHCPv6 REPLY missing Prefix", + ifp->name, sfrom); + continue; + } +#endif + } else { + if (dhcp6_findna(ifp, o.code, ia.iaid, p, o.len, + acquired) == 0) + { + logwarnx("%s: %s: DHCPv6 REPLY missing " + "IA Address", + ifp->name, sfrom); + continue; + } + } + if (o.code != D6_OPTION_IA_TA) { + if (ia.t1 != 0 && + (ia.t1 < state->renew || state->renew == 0)) + state->renew = ia.t1; + if (ia.t2 != 0 && + (ia.t2 < state->rebind || state->rebind == 0)) + state->rebind = ia.t2; + } + i++; + } + + if (i == 0 && e) + return -1; + return i; +} + +#ifndef SMALL +static void +dhcp6_deprecatedele(struct ipv6_addr *ia) +{ + struct ipv6_addr *da, *dan, *dda; + struct timespec now; + struct dhcp6_state *state; + + timespecclear(&now); + TAILQ_FOREACH_SAFE(da, &ia->pd_pfxs, pd_next, dan) { + if (ia->prefix_vltime == 0) { + if (da->prefix_vltime != 0) + da->prefix_vltime = 0; + else + continue; + } else if (da->prefix_pltime != 0) + da->prefix_pltime = 0; + else + continue; + + if (ipv6_doaddr(da, &now) != -1) + continue; + + /* Delegation deleted, forget it. */ + TAILQ_REMOVE(&ia->pd_pfxs, da, pd_next); + + /* Delete it from the interface. */ + state = D6_STATE(da->iface); + TAILQ_FOREACH(dda, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&dda->addr, &da->addr)) + break; + } + if (dda != NULL) { + TAILQ_REMOVE(&state->addrs, dda, next); + ipv6_freeaddr(dda); + } + } +} +#endif + +static void +dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs) +{ + struct ipv6_addr *ia, *ian; + + TAILQ_FOREACH_SAFE(ia, addrs, next, ian) { + if (ia->flags & IPV6_AF_EXTENDED) + ; + else if (ia->flags & IPV6_AF_STALE) { + if (ia->prefix_vltime != 0) + logdebugx("%s: %s: became stale", + ia->iface->name, ia->saddr); + /* Technically this violates RFC 8415 18.2.10.1, + * but we need a mechanism to tell the kernel to + * try and prefer other addresses. */ + ia->prefix_pltime = 0; + } else if (ia->prefix_vltime == 0) + loginfox("%s: %s: no valid lifetime", + ia->iface->name, ia->saddr); + else + continue; + +#ifndef SMALL + /* If we delegated from this prefix, deprecate or remove + * the delegations. */ + if (ia->flags & IPV6_AF_DELEGATEDPFX) + dhcp6_deprecatedele(ia); +#endif + + if (ia->flags & IPV6_AF_REQUEST) { + ia->prefix_vltime = ia->prefix_pltime = 0; + eloop_q_timeout_delete(ia->iface->ctx->eloop, + ELOOP_QUEUE_ALL, NULL, ia); + continue; + } + TAILQ_REMOVE(addrs, ia, next); + if (ia->flags & IPV6_AF_EXTENDED) + ipv6_deleteaddr(ia); + ipv6_freeaddr(ia); + } +} + +static int +dhcp6_validatelease(struct interface *ifp, + struct dhcp6_message *m, size_t len, + const char *sfrom, const struct timespec *acquired) +{ + struct dhcp6_state *state; + int nia, ok_errno; + struct timespec aq; + + if (len <= sizeof(*m)) { + logerrx("%s: DHCPv6 lease truncated", ifp->name); + return -1; + } + + state = D6_STATE(ifp); + errno = 0; + if (dhcp6_checkstatusok(ifp, m, NULL, len) != 0) + return -1; + ok_errno = errno; + + state->renew = state->rebind = state->expire = 0; + state->lowpl = ND6_INFINITE_LIFETIME; + if (!acquired) { + clock_gettime(CLOCK_MONOTONIC, &aq); + acquired = &aq; + } + state->has_no_binding = false; + nia = dhcp6_findia(ifp, m, len, sfrom, acquired); + if (nia == 0) { + if (state->state != DH6S_CONFIRM && ok_errno != 0) { + logerrx("%s: no useable IA found in lease", ifp->name); + return -1; + } + + /* We are confirming and have an OK, + * so look for ia's in our old lease. + * IA's must have existed here otherwise we would + * have rejected it earlier. */ + assert(state->new != NULL && state->new_len != 0); + state->has_no_binding = false; + nia = dhcp6_findia(ifp, state->new, state->new_len, + sfrom, acquired); + } + return nia; +} + +static ssize_t +dhcp6_readlease(struct interface *ifp, int validate) +{ + union { + struct dhcp6_message dhcp6; + uint8_t buf[UDPLEN_MAX]; + } buf; + struct dhcp6_state *state; + ssize_t bytes; + int fd; + time_t mtime, now; +#ifdef AUTH + uint8_t *o; + uint16_t ol; +#endif + + state = D6_STATE(ifp); + if (state->leasefile[0] == '\0') { + logdebugx("reading standard input"); + bytes = read(fileno(stdin), buf.buf, sizeof(buf.buf)); + } else { + logdebugx("%s: reading lease: %s", + ifp->name, state->leasefile); + bytes = dhcp_readfile(ifp->ctx, state->leasefile, + buf.buf, sizeof(buf.buf)); + } + if (bytes == -1) + goto ex; + + if (ifp->ctx->options & DHCPCD_DUMPLEASE || state->leasefile[0] == '\0') + goto out; + + if (bytes == 0) + goto ex; + + /* If not validating IA's and if they have expired, + * skip to the auth check. */ + if (!validate) + goto auth; + + if (dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == -1) + goto ex; + clock_gettime(CLOCK_MONOTONIC, &state->acquired); + if ((now = time(NULL)) == -1) + goto ex; + state->acquired.tv_sec -= now - mtime; + + /* Check to see if the lease is still valid */ + fd = dhcp6_validatelease(ifp, &buf.dhcp6, (size_t)bytes, NULL, + &state->acquired); + if (fd == -1) + goto ex; + + if (state->expire != ND6_INFINITE_LIFETIME && + (time_t)state->expire < now - mtime && + !(ifp->options->options & DHCPCD_LASTLEASE_EXTEND)) + { + logdebugx("%s: discarding expired lease", ifp->name); + bytes = 0; + goto ex; + } + +auth: +#ifdef AUTH + /* Authenticate the message */ + o = dhcp6_findmoption(&buf.dhcp6, (size_t)bytes, D6_OPTION_AUTH, &ol); + if (o) { + if (dhcp_auth_validate(&state->auth, &ifp->options->auth, + buf.buf, (size_t)bytes, 6, buf.dhcp6.type, o, ol) == NULL) + { + logerr("%s: authentication failed", ifp->name); + bytes = 0; + goto ex; + } + if (state->auth.token) + logdebugx("%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + loginfox("%s: accepted reconfigure key", ifp->name); + } else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) == + DHCPCD_AUTH_SENDREQUIRE) + { + logerrx("%s: authentication now required", ifp->name); + goto ex; + } +#endif + +out: + free(state->new); + state->new = malloc((size_t)bytes); + if (state->new == NULL) { + logerr(__func__); + goto ex; + } + + memcpy(state->new, buf.buf, (size_t)bytes); + state->new_len = (size_t)bytes; + return bytes; + +ex: + dhcp6_freedrop_addrs(ifp, 0, NULL); + dhcp_unlink(ifp->ctx, state->leasefile); + free(state->new); + state->new = NULL; + state->new_len = 0; + dhcp6_addrequestedaddrs(ifp); + return bytes == 0 ? 0 : -1; +} + +static void +dhcp6_startinit(struct interface *ifp) +{ + struct dhcp6_state *state; + ssize_t r; + uint8_t has_ta, has_non_ta; + size_t i; + + state = D6_STATE(ifp); + state->state = DH6S_INIT; + state->expire = ND6_INFINITE_LIFETIME; + state->lowpl = ND6_INFINITE_LIFETIME; + + dhcp6_addrequestedaddrs(ifp); + has_ta = has_non_ta = 0; + for (i = 0; i < ifp->options->ia_len; i++) { + switch (ifp->options->ia[i].ia_type) { + case D6_OPTION_IA_TA: + has_ta = 1; + break; + default: + has_non_ta = 1; + } + } + + if (!(ifp->ctx->options & DHCPCD_TEST) && + !(has_ta && !has_non_ta) && + ifp->options->reboot != 0) + { + r = dhcp6_readlease(ifp, 1); + if (r == -1) { + if (errno != ENOENT && errno != ESRCH) + logerr("%s: %s", __func__, state->leasefile); + } else if (r != 0 && + !(ifp->options->options & DHCPCD_ANONYMOUS)) + { + /* RFC 3633 section 12.1 */ +#ifndef SMALL + if (dhcp6_hasprefixdelegation(ifp)) + dhcp6_startrebind(ifp); + else +#endif + dhcp6_startconfirm(ifp); + return; + } + } + dhcp6_startdiscoinform(ifp); +} + +#ifndef SMALL +static struct ipv6_addr * +dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix, + const struct if_sla *sla, struct if_ia *if_ia) +{ + struct dhcp6_state *state; + struct in6_addr addr, daddr; + struct ipv6_addr *ia; + int pfxlen, dadcounter; + uint64_t vl; + + /* RFC6603 Section 4.2 */ + if (strcmp(ifp->name, prefix->iface->name) == 0) { + if (prefix->prefix_exclude_len == 0) { + /* Don't spam the log automatically */ + if (sla != NULL) + logwarnx("%s: DHCPv6 server does not support " + "OPTION_PD_EXCLUDE", + ifp->name); + return NULL; + } + pfxlen = prefix->prefix_exclude_len; + memcpy(&addr, &prefix->prefix_exclude, sizeof(addr)); + } else if ((pfxlen = dhcp6_delegateaddr(&addr, ifp, prefix, + sla, if_ia)) == -1) + return NULL; + + if (sla != NULL && fls64(sla->suffix) > 128 - pfxlen) { + logerrx("%s: suffix %" PRIu64 " + prefix_len %d > 128", + ifp->name, sla->suffix, pfxlen); + return NULL; + } + + /* Add our suffix */ + if (sla != NULL && sla->suffix != 0) { + daddr = addr; + vl = be64dec(addr.s6_addr + 8); + vl |= sla->suffix; + be64enc(daddr.s6_addr + 8, vl); + } else { + dadcounter = ipv6_makeaddr(&daddr, ifp, &addr, pfxlen, 0); + if (dadcounter == -1) { + logerrx("%s: error adding slaac to prefix_len %d", + ifp->name, pfxlen); + return NULL; + } + } + + /* Find an existing address */ + state = D6_STATE(ifp); + TAILQ_FOREACH(ia, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ia->addr, &daddr)) + break; + } + if (ia == NULL) { + ia = ipv6_newaddr(ifp, &daddr, (uint8_t)pfxlen, IPV6_AF_ONLINK); + if (ia == NULL) + return NULL; + ia->dadcallback = dhcp6_dadcallback; + memcpy(&ia->iaid, &prefix->iaid, sizeof(ia->iaid)); + ia->created = prefix->acquired; + + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + TAILQ_INSERT_TAIL(&prefix->pd_pfxs, ia, pd_next); + } + ia->delegating_prefix = prefix; + ia->prefix = addr; + ia->prefix_len = (uint8_t)pfxlen; + ia->acquired = prefix->acquired; + ia->prefix_pltime = prefix->prefix_pltime; + ia->prefix_vltime = prefix->prefix_vltime; + + /* If the prefix length hasn't changed, + * don't install a reject route. */ + if (prefix->prefix_len == pfxlen) + prefix->flags |= IPV6_AF_NOREJECT; + else + prefix->flags &= ~IPV6_AF_NOREJECT; + + return ia; +} +#endif + +static void +dhcp6_script_try_run(struct interface *ifp, int delegated) +{ + struct dhcp6_state *state; + struct ipv6_addr *ap; + int completed; + + state = D6_STATE(ifp); + completed = 1; + /* If all addresses have completed DAD run the script */ + TAILQ_FOREACH(ap, &state->addrs, next) { + if (!(ap->flags & IPV6_AF_ADDED)) + continue; + if (ap->flags & IPV6_AF_ONLINK) { + if (!(ap->flags & IPV6_AF_DADCOMPLETED) && + ipv6_iffindaddr(ap->iface, &ap->addr, + IN6_IFF_TENTATIVE)) + ap->flags |= IPV6_AF_DADCOMPLETED; + if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0 +#ifndef SMALL + && ((delegated && ap->delegating_prefix) || + (!delegated && !ap->delegating_prefix)) +#endif + ) + { + completed = 0; + break; + } + } + } + if (completed) { + script_runreason(ifp, delegated ? "DELEGATED6" : state->reason); + if (!delegated) + dhcpcd_daemonise(ifp->ctx); + } else + logdebugx("%s: waiting for DHCPv6 DAD to complete", ifp->name); +} + +#ifdef SMALL +size_t +dhcp6_find_delegates(__unused struct interface *ifp) +{ + + return 0; +} +#else +static void +dhcp6_delegate_prefix(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp6_state *state; + struct ipv6_addr *ap; + size_t i, j, k; + struct if_ia *ia; + struct if_sla *sla; + struct interface *ifd; + bool carrier_warned; + + ifo = ifp->options; + state = D6_STATE(ifp); + + /* Clear the logged flag. */ + TAILQ_FOREACH(ap, &state->addrs, next) { + ap->flags &= ~IPV6_AF_DELEGATEDLOG; + } + + TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) { + if (!ifd->active) + continue; + if (!(ifd->options->options & DHCPCD_CONFIGURE)) + continue; + k = 0; + carrier_warned = false; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (!(ap->flags & IPV6_AF_DELEGATEDPFX)) + continue; + if (!(ap->flags & IPV6_AF_DELEGATEDLOG)) { + int loglevel; + + if (ap->flags & IPV6_AF_NEW) + loglevel = LOG_INFO; + else + loglevel = LOG_DEBUG; + /* We only want to log this the once as we loop + * through many interfaces first. */ + ap->flags |= IPV6_AF_DELEGATEDLOG; + logmessage(loglevel, "%s: delegated prefix %s", + ifp->name, ap->saddr); + ap->flags &= ~IPV6_AF_NEW; + } + for (i = 0; i < ifo->ia_len; i++) { + ia = &ifo->ia[i]; + if (ia->ia_type != D6_OPTION_IA_PD) + continue; + if (memcmp(ia->iaid, ap->iaid, + sizeof(ia->iaid))) + continue; + if (ia->sla_len == 0) { + /* no SLA configured, so lets + * automate it */ + if (!if_is_link_up(ifd)) { + logdebugx( + "%s: has no carrier, cannot" + " delegate addresses", + ifd->name); + carrier_warned = true; + break; + } + if (dhcp6_ifdelegateaddr(ifd, ap, + NULL, ia)) + k++; + } + for (j = 0; j < ia->sla_len; j++) { + sla = &ia->sla[j]; + if (strcmp(ifd->name, sla->ifname)) + continue; + if (!if_is_link_up(ifd)) { + logdebugx( + "%s: has no carrier, cannot" + " delegate addresses", + ifd->name); + carrier_warned = true; + break; + } + if (dhcp6_ifdelegateaddr(ifd, ap, + sla, ia)) + k++; + } + if (carrier_warned) + break; + } + if (carrier_warned) + break; + } + if (k && !carrier_warned) { + struct dhcp6_state *s = D6_STATE(ifd); + + ipv6_addaddrs(&s->addrs); + dhcp6_script_try_run(ifd, 1); + } + } + + /* Now all addresses have been added, rebuild the routing table. */ + rt_build(ifp->ctx, AF_INET6); +} + +static void +dhcp6_find_delegates1(void *arg) +{ + + dhcp6_find_delegates(arg); +} + +size_t +dhcp6_find_delegates(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp6_state *state; + struct ipv6_addr *ap; + size_t i, j, k; + struct if_ia *ia; + struct if_sla *sla; + struct interface *ifd; + + if (ifp->options != NULL && + !(ifp->options->options & DHCPCD_CONFIGURE)) + return 0; + + k = 0; + TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) { + ifo = ifd->options; + state = D6_STATE(ifd); + if (state == NULL || state->state != DH6S_BOUND) + continue; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (!(ap->flags & IPV6_AF_DELEGATEDPFX)) + continue; + for (i = 0; i < ifo->ia_len; i++) { + ia = &ifo->ia[i]; + if (ia->ia_type != D6_OPTION_IA_PD) + continue; + if (memcmp(ia->iaid, ap->iaid, + sizeof(ia->iaid))) + continue; + for (j = 0; j < ia->sla_len; j++) { + sla = &ia->sla[j]; + if (strcmp(ifp->name, sla->ifname)) + continue; + if (ipv6_linklocal(ifp) == NULL) { + logdebugx( + "%s: delaying adding" + " delegated addresses for" + " LL address", + ifp->name); + ipv6_addlinklocalcallback(ifp, + dhcp6_find_delegates1, ifp); + return 1; + } + if (dhcp6_ifdelegateaddr(ifp, ap, + sla, ia)) + k++; + } + } + } + } + + if (k) { + loginfox("%s: adding delegated prefixes", ifp->name); + state = D6_STATE(ifp); + state->state = DH6S_DELEGATED; + ipv6_addaddrs(&state->addrs); + rt_build(ifp->ctx, AF_INET6); + dhcp6_script_try_run(ifp, 1); + } + return k; +} +#endif + +static void +dhcp6_bind(struct interface *ifp, const char *op, const char *sfrom) +{ + struct dhcp6_state *state = D6_STATE(ifp); + bool timedout = (op == NULL), confirmed; + struct ipv6_addr *ia; + int loglevel; + struct timespec now; + + if (state->state == DH6S_RENEW && !state->new_start) { + loglevel = LOG_DEBUG; + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_NEW) { + loglevel = LOG_INFO; + break; + } + } + } else if (state->state == DH6S_INFORM) + loglevel = state->new_start ? LOG_INFO : LOG_DEBUG; + else + loglevel = LOG_INFO; + state->new_start = false; + + if (!timedout) { + logmessage(loglevel, "%s: %s received from %s", + ifp->name, op, sfrom); +#ifndef SMALL + /* If we delegated from an unconfirmed lease we MUST drop + * them now. Hopefully we have new delegations. */ + if (state->reason != NULL && + strcmp(state->reason, "TIMEOUT6") == 0) + dhcp6_delete_delegates(ifp); +#endif + state->reason = NULL; + } else + state->reason = "TIMEOUT6"; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + clock_gettime(CLOCK_MONOTONIC, &now); + + switch(state->state) { + case DH6S_INFORM: + { + struct dhcp6_option *o; + uint16_t ol; + + if (state->reason == NULL) + state->reason = "INFORM6"; + o = dhcp6_findmoption(state->new, state->new_len, + D6_OPTION_INFO_REFRESH_TIME, &ol); + if (o == NULL || ol != sizeof(uint32_t)) + state->renew = IRT_DEFAULT; + else { + memcpy(&state->renew, o, ol); + state->renew = ntohl(state->renew); + if (state->renew < IRT_MINIMUM) + state->renew = IRT_MINIMUM; + } + state->rebind = 0; + state->expire = ND6_INFINITE_LIFETIME; + state->lowpl = ND6_INFINITE_LIFETIME; + } + break; + + case DH6S_REQUEST: + if (state->reason == NULL) + state->reason = "BOUND6"; + /* FALLTHROUGH */ + case DH6S_RENEW: + if (state->reason == NULL) + state->reason = "RENEW6"; + /* FALLTHROUGH */ + case DH6S_REBIND: + if (state->reason == NULL) + state->reason = "REBIND6"; + /* FALLTHROUGH */ + case DH6S_CONFIRM: + if (state->reason == NULL) + state->reason = "REBOOT6"; + if (state->renew != 0) { + bool all_expired = true; + + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_STALE) + continue; + if (!(state->renew == ND6_INFINITE_LIFETIME + && ia->prefix_vltime == ND6_INFINITE_LIFETIME) + && ia->prefix_vltime != 0 + && ia->prefix_vltime <= state->renew) + logwarnx( + "%s: %s will expire before renewal", + ifp->name, ia->saddr); + else + all_expired = false; + } + if (all_expired) { + /* All address's vltime happens at or before + * the configured T1 in the IA. + * This is a badly configured server and we + * have to use our own notion of what + * T1 and T2 should be as a result. + * + * Doing this violates RFC 3315 22.4: + * In a message sent by a server to a client, + * the client MUST use the values in the T1 + * and T2 fields for the T1 and T2 parameters, + * unless those values in those fields are 0. + */ + logwarnx("%s: ignoring T1 %"PRIu32 + " due to address expiry", + ifp->name, state->renew); + state->renew = state->rebind = 0; + } + } + if (state->renew == 0 && state->lowpl != ND6_INFINITE_LIFETIME) + state->renew = (uint32_t)(state->lowpl * 0.5); + if (state->rebind == 0 && state->lowpl != ND6_INFINITE_LIFETIME) + state->rebind = (uint32_t)(state->lowpl * 0.8); + break; + default: + state->reason = "UNKNOWN6"; + break; + } + + if (state->state != DH6S_CONFIRM && !timedout) { + state->acquired = now; + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = state->recv; + state->new_len = state->recv_len; + state->recv = NULL; + state->recv_len = 0; + confirmed = false; + } else { + /* Reduce timers based on when we got the lease. */ + uint32_t elapsed; + + elapsed = (uint32_t)eloop_timespec_diff(&now, + &state->acquired, NULL); + if (state->renew && state->renew != ND6_INFINITE_LIFETIME) { + if (state->renew > elapsed) + state->renew -= elapsed; + else + state->renew = 0; + } + if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) { + if (state->rebind > elapsed) + state->rebind -= elapsed; + else + state->rebind = 0; + } + if (state->expire && state->expire != ND6_INFINITE_LIFETIME) { + if (state->expire > elapsed) + state->expire -= elapsed; + else + state->expire = 0; + } + confirmed = true; + } + + if (ifp->ctx->options & DHCPCD_TEST) + script_runreason(ifp, "TEST"); + else { + if (state->state == DH6S_INFORM) + state->state = DH6S_INFORMED; + else + state->state = DH6S_BOUND; + state->failed = false; + + if (state->renew && state->renew != ND6_INFINITE_LIFETIME) + eloop_timeout_add_sec(ifp->ctx->eloop, + state->renew, + state->state == DH6S_INFORMED ? + dhcp6_startinform : dhcp6_startrenew, ifp); + if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) + eloop_timeout_add_sec(ifp->ctx->eloop, + state->rebind, dhcp6_startrebind, ifp); + if (state->expire != ND6_INFINITE_LIFETIME) + eloop_timeout_add_sec(ifp->ctx->eloop, + state->expire, dhcp6_startexpire, ifp); + + if (ifp->options->options & DHCPCD_CONFIGURE) { + ipv6_addaddrs(&state->addrs); + if (!timedout) + dhcp6_deprecateaddrs(&state->addrs); + } + + if (state->state == DH6S_INFORMED) + logmessage(loglevel, "%s: refresh in %"PRIu32" seconds", + ifp->name, state->renew); + else if (state->renew == ND6_INFINITE_LIFETIME) + logmessage(loglevel, "%s: leased for infinity", + ifp->name); + else if (state->renew || state->rebind) + logmessage(loglevel, "%s: renew in %"PRIu32", " + "rebind in %"PRIu32", " + "expire in %"PRIu32" seconds", + ifp->name, + state->renew, state->rebind, state->expire); + else if (state->expire == 0) + logmessage(loglevel, "%s: will expire", ifp->name); + else + logmessage(loglevel, "%s: expire in %"PRIu32" seconds", + ifp->name, state->expire); + rt_build(ifp->ctx, AF_INET6); + if (!confirmed && !timedout) { + logdebugx("%s: writing lease: %s", + ifp->name, state->leasefile); + if (dhcp_writefile(ifp->ctx, state->leasefile, 0640, + state->new, state->new_len) == -1) + logerr("dhcp_writefile: %s",state->leasefile); + } +#ifndef SMALL + dhcp6_delegate_prefix(ifp); +#endif + dhcp6_script_try_run(ifp, 0); + } + + if (ifp->ctx->options & DHCPCD_TEST || + (ifp->options->options & DHCPCD_INFORM && + !(ifp->ctx->options & DHCPCD_MANAGER))) + { + eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); + } +} + +static void +dhcp6_recvif(struct interface *ifp, const char *sfrom, + struct dhcp6_message *r, size_t len) +{ + struct dhcpcd_ctx *ctx; + size_t i; + const char *op; + struct dhcp6_state *state; + uint8_t *o; + uint16_t ol; + const struct dhcp_opt *opt; + const struct if_options *ifo; + bool valid_op; +#ifdef AUTH + uint8_t *auth; + uint16_t auth_len; +#endif + + ctx = ifp->ctx; + state = D6_STATE(ifp); + if (state == NULL || state->send == NULL) { + logdebugx("%s: DHCPv6 reply received but not running", + ifp->name); + return; + } + + /* We're already bound and this message is for another machine */ + /* XXX DELEGATED? */ + if (r->type != DHCP6_RECONFIGURE && + (state->state == DH6S_BOUND || state->state == DH6S_INFORMED)) + { + logdebugx("%s: DHCPv6 reply received but already bound", + ifp->name); + return; + } + + if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) { + logdebugx("%s: no DHCPv6 server ID from %s", ifp->name, sfrom); + return; + } + + ifo = ifp->options; + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; + i++, opt++) + { + if (has_option_mask(ifo->requiremask6, opt->option) && + !dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL)) + { + logwarnx("%s: reject DHCPv6 (no option %s) from %s", + ifp->name, opt->var, sfrom); + return; + } + if (has_option_mask(ifo->rejectmask6, opt->option) && + dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL)) + { + logwarnx("%s: reject DHCPv6 (option %s) from %s", + ifp->name, opt->var, sfrom); + return; + } + } + +#ifdef AUTH + /* Authenticate the message */ + auth = dhcp6_findmoption(r, len, D6_OPTION_AUTH, &auth_len); + if (auth != NULL) { + if (dhcp_auth_validate(&state->auth, &ifo->auth, + (uint8_t *)r, len, 6, r->type, auth, auth_len) == NULL) + { + logerr("%s: authentication failed from %s", + ifp->name, sfrom); + return; + } + if (state->auth.token) + logdebugx("%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + loginfox("%s: accepted reconfigure key", ifp->name); + } else if (ifo->auth.options & DHCPCD_AUTH_SEND) { + if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { + logerrx("%s: no authentication from %s", + ifp->name, sfrom); + return; + } + logwarnx("%s: no authentication from %s", ifp->name, sfrom); + } +#endif + + op = dhcp6_get_op(r->type); + valid_op = op != NULL; + switch(r->type) { + case DHCP6_REPLY: + switch(state->state) { + case DH6S_INFORM: + if (dhcp6_checkstatusok(ifp, r, NULL, len) != 0) + return; + break; + case DH6S_CONFIRM: + if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1) + { + dhcp6_startdiscoinform(ifp); + return; + } + break; + case DH6S_DISCOVER: + /* Only accept REPLY in DISCOVER for RAPID_COMMIT. + * Normally we get an ADVERTISE for a DISCOVER. */ + if (!has_option_mask(ifo->requestmask6, + D6_OPTION_RAPID_COMMIT) || + !dhcp6_findmoption(r, len, D6_OPTION_RAPID_COMMIT, + NULL)) + { + valid_op = false; + break; + } + /* Validate lease before setting state to REQUEST. */ + /* FALLTHROUGH */ + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_RENEW: /* FALLTHROUGH */ + case DH6S_REBIND: + if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1) + { + /* + * If we can't use the lease, fallback to + * DISCOVER and try and get a new one. + * + * This is needed become some servers + * renumber the prefix or address + * and deny the current one before it expires + * rather than sending it back with a zero + * lifetime along with the new prefix or + * address to use. + * This behavior is wrong, but moving to the + * DISCOVER phase works around it. + * + * The currently held lease is still valid + * until a new one is found. + */ + if (state->state != DH6S_DISCOVER) + dhcp6_startdiscoinform(ifp); + return; + } + /* RFC8415 18.2.10.1 */ + if ((state->state == DH6S_RENEW || + state->state == DH6S_REBIND) && + state->has_no_binding) + { + dhcp6_startrequest(ifp); + return; + } + if (state->state == DH6S_DISCOVER) + state->state = DH6S_REQUEST; + break; + case DH6S_DECLINE: + /* This isnt really a failure, but an + * acknowledgement of one. */ + loginfox("%s: %s acknowledged DECLINE6", + ifp->name, sfrom); + dhcp6_fail(ifp); + return; + default: + valid_op = false; + break; + } + break; + case DHCP6_ADVERTISE: + if (state->state != DH6S_DISCOVER) { + valid_op = false; + break; + } + /* RFC7083 */ + o = dhcp6_findmoption(r, len, D6_OPTION_SOL_MAX_RT, &ol); + if (o && ol == sizeof(uint32_t)) { + uint32_t max_rt; + + memcpy(&max_rt, o, sizeof(max_rt)); + max_rt = ntohl(max_rt); + if (max_rt >= 60 && max_rt <= 86400) { + logdebugx("%s: SOL_MAX_RT %llu -> %u", + ifp->name, + (unsigned long long)state->sol_max_rt, + max_rt); + state->sol_max_rt = max_rt; + } else + logerr("%s: invalid SOL_MAX_RT %u", + ifp->name, max_rt); + } + o = dhcp6_findmoption(r, len, D6_OPTION_INF_MAX_RT, &ol); + if (o && ol == sizeof(uint32_t)) { + uint32_t max_rt; + + memcpy(&max_rt, o, sizeof(max_rt)); + max_rt = ntohl(max_rt); + if (max_rt >= 60 && max_rt <= 86400) { + logdebugx("%s: INF_MAX_RT %llu -> %u", + ifp->name, + (unsigned long long)state->inf_max_rt, + max_rt); + state->inf_max_rt = max_rt; + } else + logerrx("%s: invalid INF_MAX_RT %u", + ifp->name, max_rt); + } + if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1) + return; + break; + case DHCP6_RECONFIGURE: +#ifdef AUTH + if (auth == NULL) { +#endif + logerrx("%s: unauthenticated %s from %s", + ifp->name, op, sfrom); + if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) + return; +#ifdef AUTH + } + loginfox("%s: %s from %s", ifp->name, op, sfrom); + o = dhcp6_findmoption(r, len, D6_OPTION_RECONF_MSG, &ol); + if (o == NULL) { + logerrx("%s: missing Reconfigure Message option", + ifp->name); + return; + } + if (ol != 1) { + logerrx("%s: missing Reconfigure Message type", + ifp->name); + return; + } + switch(*o) { + case DHCP6_RENEW: + if (state->state != DH6S_BOUND) { + logerrx("%s: not bound, ignoring %s", + ifp->name, op); + return; + } + dhcp6_startrenew(ifp); + break; + case DHCP6_INFORMATION_REQ: + if (state->state != DH6S_INFORMED) { + logerrx("%s: not informed, ignoring %s", + ifp->name, op); + return; + } + eloop_timeout_delete(ifp->ctx->eloop, + dhcp6_sendinform, ifp); + dhcp6_startinform(ifp); + break; + default: + logerr("%s: unsupported %s type %d", + ifp->name, op, *o); + break; + } + return; +#else + break; +#endif + default: + logerrx("%s: invalid DHCP6 type %s (%d)", + ifp->name, op, r->type); + return; + } + if (!valid_op) { + logwarnx("%s: invalid state for DHCP6 type %s (%d)", + ifp->name, op, r->type); + return; + } + + if (state->recv_len < (size_t)len) { + free(state->recv); + state->recv = malloc(len); + if (state->recv == NULL) { + logerr(__func__); + return; + } + } + memcpy(state->recv, r, len); + state->recv_len = len; + + if (r->type == DHCP6_ADVERTISE) { + struct ipv6_addr *ia; + + if (state->state == DH6S_REQUEST) /* rapid commit */ + goto bind; + TAILQ_FOREACH(ia, &state->addrs, next) { + if (!(ia->flags & (IPV6_AF_STALE | IPV6_AF_REQUEST))) + break; + } + if (ia == NULL) + ia = TAILQ_FIRST(&state->addrs); + if (ia == NULL) + loginfox("%s: ADV (no address) from %s", + ifp->name, sfrom); + else + loginfox("%s: ADV %s from %s", + ifp->name, ia->saddr, sfrom); + dhcp6_startrequest(ifp); + return; + } + +bind: + dhcp6_bind(ifp, op, sfrom); +} + +void +dhcp6_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, struct ipv6_addr *ia) +{ + struct sockaddr_in6 *from = msg->msg_name; + size_t len = msg->msg_iov[0].iov_len; + char sfrom[INET6_ADDRSTRLEN]; + struct interface *ifp; + struct dhcp6_message *r; + const struct dhcp6_state *state; + uint8_t *o; + uint16_t ol; + + inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom)); + if (len < sizeof(struct dhcp6_message)) { + logerrx("DHCPv6 packet too short from %s", sfrom); + return; + } + + if (ia != NULL) + ifp = ia->iface; + else { + ifp = if_findifpfromcmsg(ctx, msg, NULL); + if (ifp == NULL) { + logerr(__func__); + return; + } + } + + r = (struct dhcp6_message *)msg->msg_iov[0].iov_base; + + uint8_t duid[DUID_LEN], *dp; + size_t duid_len; + o = dhcp6_findmoption(r, len, D6_OPTION_CLIENTID, &ol); + if (ifp->options->options & DHCPCD_ANONYMOUS) { + duid_len = duid_make(duid, ifp, DUID_LL); + dp = duid; + } else { + duid_len = ctx->duid_len; + dp = ctx->duid; + } + if (o == NULL || ol != duid_len || memcmp(o, dp, ol) != 0) { + logdebugx("%s: incorrect client ID from %s", + ifp->name, sfrom); + return; + } + + if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) { + logdebugx("%s: no DHCPv6 server ID from %s", + ifp->name, sfrom); + return; + } + + if (r->type == DHCP6_RECONFIGURE) { + if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { + logerrx("%s: RECONFIGURE6 recv from %s, not LL", + ifp->name, sfrom); + return; + } + goto recvif; + } + + state = D6_CSTATE(ifp); + if (state == NULL || + r->xid[0] != state->send->xid[0] || + r->xid[1] != state->send->xid[1] || + r->xid[2] != state->send->xid[2]) + { + struct interface *ifp1; + const struct dhcp6_state *state1; + + /* Find an interface with a matching xid. */ + TAILQ_FOREACH(ifp1, ctx->ifaces, next) { + state1 = D6_CSTATE(ifp1); + if (state1 == NULL || state1->send == NULL) + continue; + if (r->xid[0] == state1->send->xid[0] && + r->xid[1] == state1->send->xid[1] && + r->xid[2] == state1->send->xid[2]) + break; + } + + if (ifp1 == NULL) { + if (state != NULL) + logdebugx("%s: wrong xid 0x%02x%02x%02x" + " (expecting 0x%02x%02x%02x) from %s", + ifp->name, + r->xid[0], r->xid[1], r->xid[2], + state->send->xid[0], + state->send->xid[1], + state->send->xid[2], + sfrom); + return; + } + logdebugx("%s: redirecting DHCP6 message to %s", + ifp->name, ifp1->name); + ifp = ifp1; + } + +#if 0 + /* + * Handy code to inject raw DHCPv6 packets over responses + * from our server. + * This allows me to take a 3rd party wireshark trace and + * replay it in my code. + */ + static int replyn = 0; + char fname[PATH_MAX], tbuf[UDPLEN_MAX]; + int fd; + ssize_t tlen; + uint8_t *si1, *si2; + uint16_t si_len1, si_len2; + + snprintf(fname, sizeof(fname), + "/tmp/dhcp6.reply%d.raw", replyn++); + fd = open(fname, O_RDONLY, 0); + if (fd == -1) { + logerr("%s: open: %s", __func__, fname); + return; + } + tlen = read(fd, tbuf, sizeof(tbuf)); + if (tlen == -1) + logerr("%s: read: %s", __func__, fname); + close(fd); + + /* Copy across ServerID so we can work with our own server. */ + si1 = dhcp6_findmoption(r, len, D6_OPTION_SERVERID, &si_len1); + si2 = dhcp6_findmoption(tbuf, (size_t)tlen, + D6_OPTION_SERVERID, &si_len2); + if (si1 != NULL && si2 != NULL && si_len1 == si_len2) + memcpy(si2, si1, si_len2); + r = (struct dhcp6_message *)tbuf; + len = (size_t)tlen; +#endif + +recvif: + dhcp6_recvif(ifp, sfrom, r, len); +} + +static void +dhcp6_recv(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia) +{ + struct sockaddr_in6 from; + union { + struct dhcp6_message dhcp6; + uint8_t buf[UDPLEN_MAX]; /* Maximum UDP message size */ + } iovbuf; + struct iovec iov = { + .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf), + }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &from, .msg_namelen = sizeof(from), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + int s; + ssize_t bytes; + + s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_rfd; + bytes = recvmsg(s, &msg, 0); + if (bytes == -1) { + logerr(__func__); + return; + } + + iov.iov_len = (size_t)bytes; + dhcp6_recvmsg(ctx, &msg, ia); +} + +static void +dhcp6_recvaddr(void *arg) +{ + struct ipv6_addr *ia = arg; + + dhcp6_recv(ia->iface->ctx, ia); +} + +static void +dhcp6_recvctx(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + dhcp6_recv(ctx, NULL); +} + +int +dhcp6_openraw(void) +{ + int fd, v; + + fd = socket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP); + if (fd == -1) + return -1; + + v = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &v, sizeof(v)) == -1) + goto errexit; + + v = offsetof(struct udphdr, uh_sum); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &v, sizeof(v)) == -1) + goto errexit; + + return fd; + +errexit: + close(fd); + return -1; +} + +int +dhcp6_openudp(unsigned int ifindex, struct in6_addr *ia) +{ + struct sockaddr_in6 sa; + int n, s; + + s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP); + if (s == -1) + goto errexit; + + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(DHCP6_CLIENT_PORT); +#ifdef BSD + sa.sin6_len = sizeof(sa); +#endif + + if (ia != NULL) { + memcpy(&sa.sin6_addr, ia, sizeof(sa.sin6_addr)); + ipv6_setscope(&sa, ifindex); + } + + if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) + goto errexit; + + n = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &n, sizeof(n)) == -1) + goto errexit; + +#ifdef SO_RERROR + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1) + goto errexit; +#endif + + return s; + +errexit: + logerr(__func__); + if (s != -1) + close(s); + return -1; +} + +#ifndef SMALL +static void +dhcp6_activateinterfaces(struct interface *ifp) +{ + struct interface *ifd; + size_t i, j; + struct if_ia *ia; + struct if_sla *sla; + + for (i = 0; i < ifp->options->ia_len; i++) { + ia = &ifp->options->ia[i]; + if (ia->ia_type != D6_OPTION_IA_PD) + continue; + for (j = 0; j < ia->sla_len; j++) { + sla = &ia->sla[j]; + ifd = if_find(ifp->ctx->ifaces, sla->ifname); + if (ifd == NULL) { + logwarn("%s: cannot delegate to %s", + ifp->name, sla->ifname); + continue; + } + if (!ifd->active) { + loginfox("%s: activating for delegation", + sla->ifname); + dhcpcd_activateinterface(ifd, + DHCPCD_IPV6 | DHCPCD_DHCP6); + } + } + } +} +#endif + +static void +dhcp6_start1(void *arg) +{ + struct interface *ifp = arg; + struct dhcpcd_ctx *ctx = ifp->ctx; + struct if_options *ifo = ifp->options; + struct dhcp6_state *state; + size_t i; + const struct dhcp_compat *dhc; + + if ((ctx->options & (DHCPCD_MANAGER|DHCPCD_PRIVSEP)) == DHCPCD_MANAGER && + ctx->dhcp6_rfd == -1) + { + ctx->dhcp6_rfd = dhcp6_openudp(0, NULL); + if (ctx->dhcp6_rfd == -1) { + logerr(__func__); + return; + } + eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, dhcp6_recvctx, ctx); + } + + if (!IN_PRIVSEP(ctx) && ctx->dhcp6_wfd == -1) { + ctx->dhcp6_wfd = dhcp6_openraw(); + if (ctx->dhcp6_wfd == -1) { + logerr(__func__); + return; + } + } + + state = D6_STATE(ifp); + /* If no DHCPv6 options are configured, + match configured DHCPv4 options to DHCPv6 equivalents. */ + for (i = 0; i < sizeof(ifo->requestmask6); i++) { + if (ifo->requestmask6[i] != '\0') + break; + } + if (i == sizeof(ifo->requestmask6)) { + for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) { + if (DHC_REQ(ifo->requestmask, ifo->nomask, dhc->dhcp_opt)) + add_option_mask(ifo->requestmask6, + dhc->dhcp6_opt); + } + if (ifo->fqdn != FQDN_DISABLE || ifo->options & DHCPCD_HOSTNAME) + add_option_mask(ifo->requestmask6, D6_OPTION_FQDN); + } + +#ifndef SMALL + /* Rapid commit won't work with Prefix Delegation Exclusion */ + if (dhcp6_findselfsla(ifp)) + del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT); +#endif + + if (state->state == DH6S_INFORM) { + add_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME); + dhcp6_startinform(ifp); + } else { + del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME); + dhcp6_startinit(ifp); + } + +#ifndef SMALL + dhcp6_activateinterfaces(ifp); +#endif +} + +int +dhcp6_start(struct interface *ifp, enum DH6S init_state) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + if (state != NULL) { + switch (init_state) { + case DH6S_INIT: + goto gogogo; + case DH6S_INFORM: + if (state->state == DH6S_INIT || + state->state == DH6S_INFORMED || + (state->state == DH6S_DISCOVER && + !(ifp->options->options & DHCPCD_IA_FORCED) && + !ipv6nd_hasradhcp(ifp, true))) + { + /* We don't want log spam when the RA + * has just adjusted it's prefix times. */ + if (state->state != DH6S_INFORMED) + state->new_start = true; + dhcp6_startinform(ifp); + } + break; + case DH6S_REQUEST: + if (ifp->options->options & DHCPCD_DHCP6 && + (state->state == DH6S_INIT || + state->state == DH6S_INFORM || + state->state == DH6S_INFORMED || + state->state == DH6S_DELEGATED)) + { + /* Change from stateless to stateful */ + init_state = DH6S_INIT; + goto gogogo; + } + break; + case DH6S_CONFIRM: + init_state = DH6S_INIT; + goto gogogo; + default: + /* Not possible, but sushes some compiler warnings. */ + break; + } + return 0; + } else { + switch (init_state) { + case DH6S_CONFIRM: + /* No DHCPv6 config, no existing state + * so nothing to do. */ + return 0; + case DH6S_INFORM: + break; + default: + init_state = DH6S_INIT; + break; + } + } + + if (!(ifp->options->options & DHCPCD_DHCP6)) + return 0; + + ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state)); + state = D6_STATE(ifp); + if (state == NULL) + return -1; + + state->sol_max_rt = SOL_MAX_RT; + state->inf_max_rt = INF_MAX_RT; + TAILQ_INIT(&state->addrs); + +gogogo: + state->new_start = true; + state->state = init_state; + state->lerror = 0; + state->failed = false; + dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), + AF_INET6, ifp); + if (ipv6_linklocal(ifp) == NULL) { + logdebugx("%s: delaying DHCPv6 for LL address", ifp->name); + ipv6_addlinklocalcallback(ifp, dhcp6_start1, ifp); + return 0; + } + + dhcp6_start1(ifp); + return 0; +} + +void +dhcp6_reboot(struct interface *ifp) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + if (state == NULL) + return; + + state->lerror = 0; + switch (state->state) { + case DH6S_BOUND: + dhcp6_startrebind(ifp); + break; + default: + dhcp6_startdiscoinform(ifp); + break; + } +} + +static void +dhcp6_freedrop(struct interface *ifp, int drop, const char *reason) +{ + struct dhcp6_state *state; + struct dhcpcd_ctx *ctx; + unsigned long long options; + + if (ifp->options) + options = ifp->options->options; + else + options = ifp->ctx->options; + + if (ifp->ctx->eloop) + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + +#ifndef SMALL + /* If we're dropping the lease, drop delegated addresses. + * If, for whatever reason, we don't drop them in the future + * then they should at least be marked as deprecated (pltime 0). */ + if (drop && (options & DHCPCD_NODROP) != DHCPCD_NODROP) + dhcp6_delete_delegates(ifp); +#endif + + state = D6_STATE(ifp); + if (state) { + /* Failure to send the release may cause this function to + * re-enter */ + if (state->state == DH6S_RELEASE) { + dhcp6_finishrelease(ifp); + return; + } + + if (drop && options & DHCPCD_RELEASE && + state->state != DH6S_DELEGATED) + { + if (if_is_link_up(ifp) && + state->state != DH6S_RELEASED && + state->state != DH6S_INFORMED) + { + dhcp6_startrelease(ifp); + return; + } + dhcp_unlink(ifp->ctx, state->leasefile); + } +#ifdef AUTH + else if (state->auth.reconf != NULL) { + /* + * Drop the lease as the token may only be present + * in the initial reply message and not subsequent + * renewals. + * If dhcpcd is restarted, the token is lost. + * XXX persist this in another file? + */ + dhcp_unlink(ifp->ctx, state->leasefile); + } +#endif + + dhcp6_freedrop_addrs(ifp, drop, NULL); + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = NULL; + state->new_len = 0; + if (drop && state->old && + (options & DHCPCD_NODROP) != DHCPCD_NODROP) + { + if (reason == NULL) + reason = "STOP6"; + script_runreason(ifp, reason); + } + free(state->old); + free(state->send); + free(state->recv); + free(state); + ifp->if_data[IF_DATA_DHCP6] = NULL; + } + + /* If we don't have any more DHCP6 enabled interfaces, + * close the global socket and release resources */ + ctx = ifp->ctx; + if (ctx->ifaces) { + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (D6_STATE(ifp)) + break; + } + } + if (ifp == NULL && ctx->dhcp6_rfd != -1) { + eloop_event_delete(ctx->eloop, ctx->dhcp6_rfd); + close(ctx->dhcp6_rfd); + ctx->dhcp6_rfd = -1; + } +} + +void +dhcp6_drop(struct interface *ifp, const char *reason) +{ + + dhcp6_freedrop(ifp, 1, reason); +} + +void +dhcp6_free(struct interface *ifp) +{ + + dhcp6_freedrop(ifp, 0, NULL); +} + +void +dhcp6_abort(struct interface *ifp) +{ + struct dhcp6_state *state; +#ifdef ND6_ADVERTISE + struct ipv6_addr *ia; +#endif + + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp); + state = D6_STATE(ifp); + if (state == NULL) + return; + +#ifdef ND6_ADVERTISE + TAILQ_FOREACH(ia, &state->addrs, next) { + ipv6nd_advertise(ia); + } +#endif + + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startdiscover, ifp); + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp); + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startinform, ifp); + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendinform, ifp); + + switch (state->state) { + case DH6S_DISCOVER: /* FALLTHROUGH */ + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_INFORM: + state->state = DH6S_INIT; + break; + default: + break; + } +} + +void +dhcp6_handleifa(int cmd, struct ipv6_addr *ia, pid_t pid) +{ + struct dhcp6_state *state; + struct interface *ifp = ia->iface; + + /* If not running in manager mode, listen to this address */ + if (cmd == RTM_NEWADDR && + !(ia->addr_flags & IN6_IFF_NOTUSEABLE) && + ifp->active == IF_ACTIVE_USER && + !(ifp->ctx->options & DHCPCD_MANAGER) && + ifp->options->options & DHCPCD_DHCP6) + { +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ifp->ctx)) { + if (ps_inet_opendhcp6(ia) == -1) + logerr(__func__); + } else +#endif + { + if (ia->dhcp6_fd == -1) + ia->dhcp6_fd = dhcp6_openudp(ia->iface->index, + &ia->addr); + if (ia->dhcp6_fd != -1) + eloop_event_add(ia->iface->ctx->eloop, + ia->dhcp6_fd, dhcp6_recvaddr, ia); + } + } + + + if ((state = D6_STATE(ifp)) != NULL) + ipv6_handleifa_addrs(cmd, &state->addrs, ia, pid); +} + +ssize_t +dhcp6_env(FILE *fp, const char *prefix, const struct interface *ifp, + const struct dhcp6_message *m, size_t len) +{ + const struct if_options *ifo; + struct dhcp_opt *opt, *vo; + const uint8_t *p; + struct dhcp6_option o; + size_t i; + char *pfx; + uint32_t en; + const struct dhcpcd_ctx *ctx; +#ifndef SMALL + const struct dhcp6_state *state; + const struct ipv6_addr *ap; +#endif + + if (m == NULL) + goto delegated; + + if (len < sizeof(*m)) { + /* Should be impossible with guards at packet in + * and reading leases */ + errno = EINVAL; + return -1; + } + + ifo = ifp->options; + ctx = ifp->ctx; + + /* Zero our indexes */ + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; + i++, opt++) + dhcp_zero_index(opt); + for (i = 0, opt = ifp->options->dhcp6_override; + i < ifp->options->dhcp6_override_len; + i++, opt++) + dhcp_zero_index(opt); + for (i = 0, opt = ctx->vivso; + i < ctx->vivso_len; + i++, opt++) + dhcp_zero_index(opt); + if (asprintf(&pfx, "%s_dhcp6", prefix) == -1) + return -1; + + /* Unlike DHCP, DHCPv6 options *may* occur more than once. + * There is also no provision for option concatenation unlike DHCP. */ + p = (const uint8_t *)m + sizeof(*m); + len -= sizeof(*m); + for (; len != 0; p += o.len, len -= o.len) { + if (len < sizeof(o)) { + errno = EINVAL; + break; + } + memcpy(&o, p, sizeof(o)); + p += sizeof(o); + len -= sizeof(o); + o.len = ntohs(o.len); + if (len < o.len) { + errno = EINVAL; + break; + } + o.code = ntohs(o.code); + if (has_option_mask(ifo->nomask6, o.code)) + continue; + for (i = 0, opt = ifo->dhcp6_override; + i < ifo->dhcp6_override_len; + i++, opt++) + if (opt->option == o.code) + break; + if (i == ifo->dhcp6_override_len && + o.code == D6_OPTION_VENDOR_OPTS && + o.len > sizeof(en)) + { + memcpy(&en, p, sizeof(en)); + en = ntohl(en); + vo = vivso_find(en, ifp); + } else + vo = NULL; + if (i == ifo->dhcp6_override_len) { + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; + i++, opt++) + if (opt->option == o.code) + break; + if (i == ctx->dhcp6_opts_len) + opt = NULL; + } + if (opt) { + dhcp_envoption(ifp->ctx, + fp, pfx, ifp->name, + opt, dhcp6_getoption, p, o.len); + } + if (vo) { + dhcp_envoption(ifp->ctx, + fp, pfx, ifp->name, + vo, dhcp6_getoption, + p + sizeof(en), + o.len - sizeof(en)); + } + } + free(pfx); + +delegated: +#ifndef SMALL + /* Needed for Delegated Prefixes */ + state = D6_CSTATE(ifp); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->delegating_prefix) + break; + } + if (ap == NULL) + return 1; + if (fprintf(fp, "%s_delegated_dhcp6_prefix=", prefix) == -1) + return -1; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->delegating_prefix == NULL) + continue; + if (ap != TAILQ_FIRST(&state->addrs)) { + if (fputc(' ', fp) == EOF) + return -1; + } + if (fprintf(fp, "%s", ap->saddr) == -1) + return -1; + } + if (fputc('\0', fp) == EOF) + return -1; +#endif + + return 1; +} +#endif + +#ifndef SMALL +int +dhcp6_dump(struct interface *ifp) +{ + struct dhcp6_state *state; + + ifp->if_data[IF_DATA_DHCP6] = state = calloc(1, sizeof(*state)); + if (state == NULL) { + logerr(__func__); + return -1; + } + TAILQ_INIT(&state->addrs); + if (dhcp6_readlease(ifp, 0) == -1) { + logerr("dhcp6_readlease"); + return -1; + } + state->reason = "DUMP6"; + return script_runreason(ifp, state->reason); +} +#endif Index: src/dhcpcd-definitions-small.conf =================================================================== --- /dev/null +++ src/dhcpcd-definitions-small.conf @@ -0,0 +1,127 @@ +# Copyright (c) 2006-2021 Roy Marples +# All rights reserved + +# Bare essentials for automatic IP configuration + +############################################################################## +# DHCP RFC2132 options unless otheriwse stated +define 1 request ipaddress subnet_mask +# RFC3442 states that the CSR has to come before all other routes +# For completeness we also specify static routes then routers +define 121 rfc3442 classless_static_routes +define 3 request array ipaddress routers +define 6 array ipaddress domain_name_servers +define 12 dname host_name +define 15 array dname domain_name +define 26 uint16 interface_mtu +define 28 request ipaddress broadcast_address +define 33 request array ipaddress static_routes +define 50 ipaddress dhcp_requested_address +define 51 request uint32 dhcp_lease_time +define 52 byte dhcp_option_overload +define 53 byte dhcp_message_type +define 54 ipaddress dhcp_server_identifier +define 55 array byte dhcp_parameter_request_list +define 56 string dhcp_message +define 57 uint16 dhcp_max_message_size +define 58 request uint32 dhcp_renewal_time +define 59 request uint32 dhcp_rebinding_time +define 60 string vendor_class_identifier +define 61 binhex dhcp_client_identifier + +# DHCP Rapid Commit, RFC4039 +define 80 norequest flag rapid_commit + +# DHCP Fully Qualified Domain Name, RFC4702 +define 81 embed fqdn +embed bitflags=0000NEOS flags +embed byte rcode1 +embed byte rcode2 +# dhcpcd always sets the E bit which means the fqdn itself is always +# RFC1035 encoded. +# The server MUST use the encoding as specified by the client as noted +# in RFC4702 Section 2.1. +embed optional domain fqdn + +# DHCP Domain Search, RFC3397 +define 119 array domain domain_search + +# Option 249 is an IANA assigned private number used by Windows DHCP servers +# to provide the exact same information as option 121, classless static routes +define 249 rfc3442 ms_classless_static_routes + +############################################################################## +# ND6 options, RFC4861 +definend 1 binhex source_address +definend 2 binhex target_address + +definend 3 index embed prefix_information +embed byte length +embed bitflags=LAH flags +embed uint32 vltime +embed uint32 pltime +embed uint32 reserved +embed array ip6address prefix + +# option 4 is only for Redirect messages + +definend 5 embed mtu +embed uint16 reserved +embed uint32 mtu + +# ND6 options, RFC6101 +definend 25 index embed rdnss +embed uint16 reserved +embed uint32 lifetime +embed array ip6address servers + +definend 31 index embed dnssl +embed uint16 reserved +embed uint32 lifetime +embed domain search + +############################################################################## +# DHCPv6 options, RFC3315 +define6 1 binhex client_id +define6 2 binhex server_id + +define6 3 norequest index embed ia_na +embed binhex:4 iaid +embed uint32 t1 +embed uint32 t2 +encap 5 option +encap 13 option + +define6 4 norequest index embed ia_ta +embed uint32 iaid +encap 5 option +encap 13 option + +define6 5 norequest index embed ia_addr +embed ip6address ia_addr +embed uint32 pltime +embed uint32 vltime +encap 13 option + +define6 12 ip6address unicast + +define6 13 norequest embed status_code +embed uint16 status_code +embed optional string message + +define6 18 binhex interface_id +define6 19 byte reconfigure_msg +define6 20 flag reconfigure_accept + +# DHCPv6 DNS Configuration Options, RFC3646 +define6 23 array ip6address name_servers +define6 24 array domain domain_search + +# DHCPv6 Fully Qualified Domain Name, RFC4704 +define6 39 embed fqdn +embed bitflags=00000NOS flags +embed optional domain fqdn + +# DHCPv6 SOL_MAX_RT, RFC7083 +define6 82 request uint32 sol_max_rt +define6 83 request uint32 inf_max_rt Index: src/dhcpcd-definitions.conf =================================================================== --- /dev/null +++ src/dhcpcd-definitions.conf @@ -0,0 +1,651 @@ +# Copyright (c) 2006-2021 Roy Marples +# All rights reserved + +# DHCP option definitions for dhcpcd(8) +# These are used to translate DHCP options into shell variables +# for use in dhcpcd-run-hooks(8) +# See dhcpcd.conf(5) for details + +############################################################################## +# DHCP RFC2132 options unless otheriwse stated +define 1 request ipaddress subnet_mask +# RFC3442 states that the CSR has to come before all other routes +# For completeness we also specify static routes then routers +define 121 rfc3442 classless_static_routes +define 2 uint32 time_offset +define 3 request array ipaddress routers +define 4 array ipaddress time_servers +define 5 array ipaddress ien116_name_servers +define 6 array ipaddress domain_name_servers +define 7 array ipaddress log_servers +define 8 array ipaddress cookie_servers +define 9 array ipaddress lpr_servers +define 10 array ipaddress impress_servers +define 11 array ipaddress resource_location_servers +define 12 dname host_name +define 13 uint16 boot_size +define 14 string merit_dump +# Technically domain_name is not an array, but many servers expect clients +# to treat it as one. +define 15 array dname domain_name +define 16 ipaddress swap_server +define 17 string root_path +define 18 string extensions_path +define 19 byte ip_forwarding +define 20 byte non_local_source_routing +define 21 array ipaddress policy_filter +define 22 uint16 max_dgram_reassembly +define 23 byte default_ip_ttl +define 24 uint32 path_mtu_aging_timeout +define 25 array uint16 path_mtu_plateau_table +define 26 uint16 interface_mtu +define 27 byte all_subnets_local +define 28 request ipaddress broadcast_address +define 29 byte perform_mask_discovery +define 30 byte mask_supplier +define 31 byte router_discovery +define 32 ipaddress router_solicitation_address +define 33 request array ipaddress static_routes +define 34 byte trailer_encapsulation +define 35 uint32 arp_cache_timeout +define 36 uint16 ieee802_3_encapsulation +define 37 byte default_tcp_ttl +define 38 uint32 tcp_keepalive_interval +define 39 byte tcp_keepalive_garbage +define 40 string nis_domain +define 41 array ipaddress nis_servers +define 42 array ipaddress ntp_servers +define 43 binhex vendor_encapsulated_options +define 44 array ipaddress netbios_name_servers +define 45 ipaddress netbios_dd_server +define 46 byte netbios_node_type +define 47 string netbios_scope +define 48 array ipaddress font_servers +define 49 array ipaddress x_display_manager +define 50 ipaddress dhcp_requested_address +define 51 request uint32 dhcp_lease_time +define 52 byte dhcp_option_overload +define 53 byte dhcp_message_type +define 54 ipaddress dhcp_server_identifier +define 55 array byte dhcp_parameter_request_list +define 56 string dhcp_message +define 57 uint16 dhcp_max_message_size +define 58 request uint32 dhcp_renewal_time +define 59 request uint32 dhcp_rebinding_time +define 60 string vendor_class_identifier +define 61 binhex dhcp_client_identifier +define 64 string nisplus_domain +define 65 array ipaddress nisplus_servers +define 66 dname tftp_server_name +define 67 string bootfile_name +define 68 array ipaddress mobile_ip_home_agent +define 69 array ipaddress smtp_server +define 70 array ipaddress pop_server +define 71 array ipaddress nntp_server +define 72 array ipaddress www_server +define 73 array ipaddress finger_server +define 74 array ipaddress irc_server +define 75 array ipaddress streettalk_server +define 76 array ipaddress streettalk_directory_assistance_server + +# DHCP User Class, RFC3004 +define 77 binhex user_class + +# DHCP SLP Directory Agent, RFC2610 +define 78 embed slp_agent +embed byte mandatory +embed array ipaddress address +define 79 embed slp_service +embed byte mandatory +embed ascii scope_list + +# DHCP Rapid Commit, RFC4039 +define 80 norequest flag rapid_commit + +# DHCP Fully Qualified Domain Name, RFC4702 +define 81 embed fqdn +embed bitflags=0000NEOS flags +embed byte rcode1 +embed byte rcode2 +# dhcpcd always sets the E bit which means the fqdn itself is always +# RFC1035 encoded. +# The server MUST use the encoding as specified by the client as noted +# in RFC4702 Section 2.1. +embed optional domain fqdn + +# Option 82 is for Relay Agents and DHCP servers + +# iSNS, RFC4174 +define 83 embed isns +embed byte reserved1 +embed bitflags=00000SAE functions +embed byte reserved2 +embed bitflags=00fFsSCE dd +embed byte reserved3 +embed bitflags=0000DMHE admin +embed uint16 reserved4 +embed byte reserved5 +embed bitflags=0TXPAMSE server_security +embed array ipaddress servers + +# Option 84 are unused, RFC3679 + +# DHCP Novell Directory Services, RFC2241 +define 85 array ipaddress nds_servers +define 86 raw nds_tree_name +define 87 raw nds_context + +# DHCP Broadcast and Multicast Control Server, RFC4280 +define 88 array domain bcms_controller_names +define 89 array ipaddress bcms_controller_address + +# DHCP Authentication, RFC3118 +define 90 embed auth +embed byte protocol +embed byte algorithm +embed byte rdm +embed binhex:8 replay +embed binhex information + +# DHCP Leasequery, RFC4388 +define 91 uint32 client_last_transaction_time +define 92 array ipaddress associated_ip + +# DHCP Options for Intel Preboot eXecution Environent (PXE), RFC4578 +# Options 93, 94 and 97 are used but of no use to dhcpcd + +# Option 95 used by Apple but never published RFC3679 +# Option 96 is unused, RFC3679 + +# DHCP The Open Group's User Authentication Protocol, RFC2485 +define 98 string uap_servers + +# DHCP Civic Addresses Configuration Information, RFC4776 +define 99 encap geoconf_civic +embed byte what +embed uint16 country_code +# The rest of this option is not supported + +# DHCP Timezone, RFC4883 +define 100 string posix_timezone +define 101 string tzdb_timezone + +# Options 102-115 are unused, RFC3679 + +# DHCP IPv6-Only Preferred, RFC8925 +define 108 uint32 ipv6_only_preferred + +# DHCP Auto-Configuration, RFC2563 +define 116 byte auto_configure + +# DHCP Name Service Search, RFC2937 +define 117 array uint16 name_service_search + +# DHCP Subnet Selection, RFC3011 +define 118 ipaddress subnet_selection + +# DHCP Domain Search, RFC3397 +define 119 array domain domain_search + +# DHCP Session Initiated Protocol Servers, RFC3361 +define 120 rfc3361 sip_server + +# Option 121 is defined at the top of this file + +# DHCP CableLabs Client, RFC3495 +define 122 encap tsp +encap 1 ipaddress dhcp_server +encap 2 ipaddress dhcp_secondary_server +encap 3 rfc3361 provisioning_server +encap 4 embed as_req_as_rep_backoff +embed uint32 nominal +embed uint32 maximum +embed uint32 retry +encap 5 embed ap_req_ap_rep_backoff +embed uint32 nominal +embed uint32 maximum +embed uint32 retry +encap 6 domain kerberos_realm +encap 7 byte ticket_granting_server_utilization +encap 8 byte provisioning_timer + +# DHCP Coordinate LCI, RFC6225 +# We have no means of expressing 6 bit lengths +define 123 binhex geoconf + +# DHCP Vendor-Identifying Vendor Options, RFC3925 +define 124 binhex vivco +define 125 embed vivso +embed uint32 enterprise_number +# Vendor options are shared between DHCP/DHCPv6 +# Their code is matched to the enterprise number defined above +# see the end of this file for an example + +# Options 126 and 127 are unused, RFC3679 + +# DHCP Options for Intel Preboot eXecution Environent (PXE), RFC4578 +# Options 128-135 are used but of no use to dhcpcd + +# DHCP PANA Authentication Agent, RFC5192 +define 136 array ipaddress pana_agent + +# DHCP Lost Server, RFC5223 +define 137 domain lost_server + +# DHCP CAPWAP, RFC5417 +define 138 array ipaddress capwap_ac + +# DHCP Mobility Services, RFC5678 +define 139 encap mos_ip +encap 1 array ipaddress is +encap 2 array ipaddress cs +encap 3 array ipaddress es +define 140 encap mos_domain +encap 1 domain is +encap 2 domain cs +encap 3 domain es + +# DHCP SIP UA, RFC6011 +define 141 array domain sip_ua_cs_list + +# DHCP ANDSF, RFC6153 +define 142 array ipaddress andsf +define 143 array ip6address andsf6 + +# DHCP Coordinate LCI, RFC6225 +# We have no means of expressing 6 bit lengths +define 144 binhex geoloc + +# DHCP FORCERENEW Nonce Capability, RFC6704 +define 145 array byte forcerenew_nonce_capable + +# DHCP RDNSS Selection for MIF Nodes, RFC6731 +define 146 embed rdnss_selection +embed byte prf +embed ipaddress primary +embed ipaddress secondary +embed array domain domains + +# Options 147, 148 and 149 are unused, RFC3942 + +# DHCP TFTP Server Address, RFC5859 +define 150 array ipaddress tftp_servers + +# DHCP Bulk Lease Query, RFC6926 +# dhcpcd doesn't perform a lease query, but if it did these +# fields might be of use +#define 151 embed blklqry +#embed byte status_code +#embed string status_msg + +#define 152 uint32 blklqry_base_time +#define 153 uint32 blklqry_state_start_time +#define 154 uint32 blklqry_start_time +#define 155 uint32 blklqry_end_time +#define 156 byte blklqry_state +#define 157 bitflags=0000000R blklqry_source + +# DHCP MUD URL, draft-ietf-opsawg-mud +define 161 string mudurl + +# Apart from 161... +# Options 151-157 are used for Lease Query, RFC6926 and not for dhcpcd +# Options 158-174 are unused, RFC3942 + +# Options 175-177 are tentativel assigned for Etherboot +# Options 178-207 are unused, RFC3942 + +# DHCP PXELINUX, RFC5071 +define 208 binhex pxelinux_magic +define 209 string config_file +define 210 string path_prefix +define 211 uint32 reboot_time + +# DHCP IPv6 Rapid Deployment on IPv4 Infrastructures, RFC5969 +define 212 embed sixrd +embed byte mask_len +embed byte prefix_len +embed ip6address prefix +embed array ipaddress brip_address + +# DHCP Access Network Domain Name, RFC5986 +define 213 domain access_domain + +# Options 214-219 are unused, RFC3942 + +# DHCP Subnet Allocation, RFC6656 +# Option 220 looks specific to Cisco hardware. + +# DHCP Virtual Subnet Selection, RFC6607 +define 221 encap vss +encap 0 string nvt +encap 1 binhex vpn_id +encap 255 flag global + +# Options 222 and 223 are unused, RFC3942 + +# Options 224-254 are reserved for Private Use + +# Option 249 is an IANA assigned private number used by Windows DHCP servers +# to provide the exact same information as option 121, classless static routes +define 249 rfc3442 ms_classless_static_routes + +# An expired RFC for Web Proxy Auto Discovery Protocol does define +# Option 252 which is commonly used by major browsers. +# Apparently the code was assigned by agreement of the DHC working group chair. +define 252 string wpad_url + +# Option 255 End + +############################################################################## +# ND6 options, RFC4861 +definend 1 binhex source_address +definend 2 binhex target_address + +definend 3 index embed prefix_information +embed byte length +embed bitflags=LAH flags +embed uint32 vltime +embed uint32 pltime +embed uint32 reserved +embed array ip6address prefix + +# option 4 is only for Redirect messages + +definend 5 embed mtu +embed uint16 reserved +embed uint32 mtu + +# ND6 Mobile IP, RFC6275 +definend 8 embed homeagent_information +embed uint16 reserved +embed uint16 preference +embed uint16 lifetime + +# ND6 options, RFC6101 +definend 25 index embed rdnss +embed uint16 reserved +embed uint32 lifetime +embed array ip6address servers + +definend 31 index embed dnssl +embed uint16 reserved +embed uint32 lifetime +embed domain search + +############################################################################## +# DHCPv6 options, RFC3315 +define6 1 binhex client_id +define6 2 binhex server_id + +define6 3 norequest index embed ia_na +embed binhex:4 iaid +embed uint32 t1 +embed uint32 t2 +encap 5 option +encap 13 option + +define6 4 norequest index embed ia_ta +embed uint32 iaid +encap 5 option +encap 13 option + +define6 5 norequest index embed ia_addr +embed ip6address ia_addr +embed uint32 pltime +embed uint32 vltime +encap 13 option + +define6 6 array uint16 option_request +define6 7 byte preference +define6 8 uint16 elased_time +define6 9 binhex dhcp_relay_msg + +# Option 10 is unused + +define6 11 embed auth +embed byte protocol +embed byte algorithm +embed byte rdm +embed binhex:8 replay +embed binhex information + +define6 12 ip6address unicast + +define6 13 norequest embed status_code +embed uint16 status_code +embed optional string message + +define6 14 norequest flag rapid_commit +define6 15 binhex user_class + +define6 16 binhex vivco +define6 17 embed vivso +embed uint32 enterprise_number +# Vendor options are shared between DHCP/DHCPv6 +# Their code is matched to the enterprise number defined above +# See the end of this file for an example + +define6 18 binhex interface_id +define6 19 byte reconfigure_msg +define6 20 flag reconfigure_accept + +# DHCPv6 Session Initiation Protocol Options, RFC3319 +define6 21 array domain sip_servers_names +define6 22 array ip6address sip_servers_addresses + +# DHCPv6 DNS Configuration Options, RFC3646 +define6 23 array ip6address name_servers +define6 24 array domain domain_search + +# DHCPv6 Prefix Options, RFC6603 +define6 25 norequest index embed ia_pd +embed binhex:4 iaid +embed uint32 t1 +embed uint32 t2 +encap 26 option +define6 26 index embed prefix +embed uint32 pltime +embed uint32 vltime +embed byte length +embed ip6address prefix +encap 13 option +encap 67 option + +# DHCPv6 Network Information Service Options, RFC3898 +define6 27 array ip6address nis_servers +define6 28 array ip6address nisp_servers +define6 29 string nis_domain_name +define6 30 string nisp_domain_name + +# DHCPv6 Simple Network Time Protocol Servers Option, RFC4075 +define6 31 array ip6address sntp_servers + +# DHCPv6 Information Refresh Time, RFC4242 +define6 32 uint32 info_refresh_time + +# DHCPv6 Broadcast and Multicast Control Server, RFC4280 +define6 33 array domain bcms_server_d +define6 34 array ip6address bcms_server_a + +# DHCP Civic Addresses Configuration Information, RFC4776 +define6 36 encap geoconf_civic +embed byte what +embed uint16 country_code +# The rest of this option is not supported + +# DHCP Relay Agent Remote-ID, RFC4649 +define6 37 embed remote_id +embed uint32 enterprise_number +embed binhex remote_id + +# DHCP Relay Agent Subscriber-ID, RFC4580 +define6 38 binhex subscriber_id + +# DHCPv6 Fully Qualified Domain Name, RFC4704 +define6 39 embed fqdn +embed bitflags=00000NOS flags +embed optional domain fqdn + +# DHCPv6 PANA Authentication Agnet, RC5192 +define6 40 array ip6address pana_agent + +# DHCPv6 Timezone options, RFC4883 +define6 41 string posix_timezone +define6 42 string tzdb_timezone + +# DHCPv6 Relay Agent Echo Request +define6 43 array uint16 ero + +# Options 44-48 are used for Lease Query, RFC5007 and not for dhcpcd + +# DHCPv6 Home Info Discovery in MIPv6, RFC6610 +define6 49 domain mip6_hnidf +define6 50 encap mip6_vdinf +encap 71 option +encap 72 option +encap 73 option + +# DHCPv6 Lost Server, RFC5223 +define6 51 domain lost_server + +# DHCPv6 CAPWAP, RFC5417 +define6 52 array ip6address capwap_ac + +# DHCPv6 Relay-ID, RFC5460 +define6 53 binhex relay_id + +# DHCP Mobility Services, RFC5678 +define6 54 encap mos_ip +encap 1 array ip6address is +encap 2 array ip6address cs +encap 3 array ip6address es +define6 55 encap mos_domain +encap 1 domain is +encap 2 domain cs +encap 3 domain es + +# DHCPv6 Network Time Protocol Server, RFC5908 +define6 56 encap ntp_server +encap 1 ip6address addr +encap 2 ip6address mcast_addr +encap 3 domain fqdn + +# DHCPv6 LIS Discovery, RFC5986 +define6 57 domain access_domain + +# DHCPv6 SIP UA, RFC6011 +define6 58 array domain sip_ua_cs_list + +# DHCPv6 Network Boot, RFC5970 +define6 59 string bootfile_url +# We presently cannot decode bootfile_param +define6 60 binhex bootfile_param +define6 61 array uint16 architecture_types +define6 62 embed nii +embed byte type +embed byte major +embed byte minor + +# DHCPv6 Coordinate LCI, RFC6225 +# We have no means of expressing 6 bit lengths +define6 63 binhex geoloc + +# DHCPv6 AFTR-Name, RFC6334 +define6 64 domain aftr_name + +# DHCPv6 Prefix Exclude Option, RFC6603 +define6 67 embed pd_exclude +embed byte prefix_len +embed binhex subnetID + +# DHCPv6 Home Info Discovery in MIPv6, RFC6610 +define6 69 encap mip6_idinf +encap 71 option +encap 72 option +encap 73 option +define6 70 encap mip6_udinf +encap 71 option +encap 72 option +encap 73 option +define6 71 embed mip6_hnp +embed byte prefix_len +embed ip6address prefix +define6 72 ip6address mip6_haa +define6 73 domain mip6_haf + +# DHCPv6 RDNSS Selection for MIF Nodes, RFC6731 +define6 74 embed rdnss_selection +embed ip6address server +embed byte prf +embed array domain domains + +# DHCPv6 Kerberos, RFC6784 +define6 75 string krb_principal_name +define6 76 string krb_realm_name +define6 78 embed krb_kdc +embed uint16 priority +embed uint16 weight +embed byte transport_type +embed uint16 port +embed ip6address address +embed string realm_name + +# DHCPv6 Client Link-Layer Address, RFC6939 +# Section 7 states that clients MUST ignore the option 79 + +# DHCPv6 Relay-Triggered Reconfiguraion, RFC6977 +define6 80 ip6address link_address + +# DHCPv6 Radius, RFC7037 +# Section 7 states that clients MUST ignore the option 81 + +# DHCPv6 SOL_MAX_RT, RFC7083 +define6 82 request uint32 sol_max_rt +define6 83 request uint32 inf_max_rt + +# DHCPv6 Softwire Address and Port-Mapped Clients, RFC7598 +define6 89 embed s46_rule +embed bitflags=0000000F flags +embed byte ea_len +embed byte prefix4_len +embed ipaddress ipv4_prefix +embed ip6address ipv6_prefix +define6 90 ip6address s64_br +define6 91 embed s46_dmr +embed byte prefix_len +embed binhex prefix +define6 92 embed s46_v4v6bind +embed ipaddress ipv4_address +embed byte ipv6_prefix_len +embed binhex ipv6_prefix_and_options +# Cannot decode options after variable length address ... +#encap 93 option +define6 93 embed s46_portparams +embed byte offset +embed byte psid_len +embed uint16 psid +define6 94 embed s46_cont_mape +encap 89 option +encap 90 option +define6 95 embed s46_cont_mapt +encap 89 option +encap 91 option +define6 96 embed s46_cont_lw +encap 90 option +encap 92 option + +# DHCPv6 Address Selection Policy +# Currently not supported + +# DHCPv6 MUD URL, draft-ietf-opsawg-mud +define6 112 string mudurl + +# Options 86-65535 are unasssinged + +############################################################################## +# Vendor-Identifying Vendor Options +# An example: +#vendopt 12345 encap frobozzco +#encap 1 string maze_location +#encap 2 byte grue_probability Index: src/dhcpcd-embedded.h.in =================================================================== --- /dev/null +++ src/dhcpcd-embedded.h.in @@ -0,0 +1,38 @@ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifdef SMALL +#define INITDEFINES @INITDEFINES_SMALL@ +#define INITDEFINENDS @INITDEFINENDS_SMALL@ +#define INITDEFINE6S @INITDEFINE6S_SMALL@ +#else +#define INITDEFINES @INITDEFINES@ +#define INITDEFINENDS @INITDEFINENDS@ +#define INITDEFINE6S @INITDEFINE6S@ +#endif + +extern const char dhcpcd_embedded_conf[]; Index: src/dhcpcd-embedded.c.in =================================================================== --- /dev/null +++ src/dhcpcd-embedded.c.in @@ -0,0 +1,36 @@ +/* + * DO NOT EDIT! + * Automatically generated from dhcpcd-embedded.conf + * Ths allows us to simply generate DHCP structure without any C programming. + */ + +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 Roy Marples + * 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 + +const char dhcpcd_embedded_conf[] = Index: src/dhcpcd.h =================================================================== --- /dev/null +++ src/dhcpcd.h @@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef DHCPCD_H +#define DHCPCD_H + +#include +#include + +#include + +#include "config.h" +#ifdef HAVE_SYS_QUEUE_H +#include +#endif + +#include "defs.h" +#include "control.h" +#include "if-options.h" + +#define HWADDR_LEN 20 +#define IF_SSIDLEN 32 +#define PROFILE_LEN 64 +#define SECRET_LEN 64 + +#define IF_INACTIVE 0 +#define IF_ACTIVE 1 +#define IF_ACTIVE_USER 2 + +#define LINK_UP 1 +#define LINK_UNKNOWN 0 +#define LINK_DOWN -1 + +#define IF_DATA_IPV4 0 +#define IF_DATA_ARP 1 +#define IF_DATA_IPV4LL 2 +#define IF_DATA_DHCP 3 +#define IF_DATA_IPV6 4 +#define IF_DATA_IPV6ND 5 +#define IF_DATA_DHCP6 6 +#define IF_DATA_MAX 7 + +#ifdef __QNX__ +/* QNX carries defines for, but does not actually support PF_LINK */ +#undef IFLR_ACTIVE +#endif + +struct interface { + struct dhcpcd_ctx *ctx; + TAILQ_ENTRY(interface) next; + char name[IF_NAMESIZE]; + unsigned int index; + unsigned int active; + unsigned int flags; + uint16_t hwtype; /* ARPHRD_ETHER for example */ + unsigned char hwaddr[HWADDR_LEN]; + uint8_t hwlen; + unsigned short vlanid; + unsigned int metric; + int carrier; + bool wireless; + uint8_t ssid[IF_SSIDLEN]; + unsigned int ssid_len; + + char profile[PROFILE_LEN]; + struct if_options *options; + void *if_data[IF_DATA_MAX]; +}; +TAILQ_HEAD(if_head, interface); + +#include "privsep.h" + +/* dhcpcd requires CMSG_SPACE to evaluate to a compile time constant. */ +#if defined(__QNX) || \ + (defined(__NetBSD_Version__) && __NetBSD_Version__ < 600000000) +#undef CMSG_SPACE +#endif + +#ifndef ALIGNBYTES +#define ALIGNBYTES (sizeof(int) - 1) +#endif +#ifndef ALIGN +#define ALIGN(p) (((unsigned int)(p) + ALIGNBYTES) & ~ALIGNBYTES) +#endif +#ifndef CMSG_SPACE +#define CMSG_SPACE(len) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(len)) +#endif + +struct passwd; + +struct dhcpcd_ctx { + char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1]; + char vendor[256]; + bool stdin_valid; /* It's possible stdin, stdout and stderr */ + bool stdout_valid; /* could be closed when dhcpcd starts. */ + bool stderr_valid; + int stderr_fd; /* FD for logging to stderr */ + int fork_fd; /* FD for the fork init signal pipe */ + const char *cffile; + unsigned long long options; + char *logfile; + int argc; + char **argv; + int ifac; /* allowed interfaces */ + char **ifav; /* allowed interfaces */ + int ifdc; /* denied interfaces */ + char **ifdv; /* denied interfaces */ + int ifc; /* listed interfaces */ + char **ifv; /* listed interfaces */ + int ifcc; /* configured interfaces */ + char **ifcv; /* configured interfaces */ + uint8_t duid_type; + unsigned char *duid; + size_t duid_len; + struct if_head *ifaces; + + char *ctl_buf; + size_t ctl_buflen; + size_t ctl_bufpos; + size_t ctl_extra; + + rb_tree_t routes; /* our routes */ +#ifdef RT_FREE_ROUTE_TABLE + rb_tree_t froutes; /* free routes for re-use */ +#endif + size_t rt_order; /* route order storage */ + + int pf_inet_fd; +#ifdef PF_LINK + int pf_link_fd; +#endif + void *priv; + int link_fd; +#ifndef SMALL + int link_rcvbuf; +#endif + int seq; /* route message sequence no */ + int sseq; /* successful seq no sent */ + +#ifdef USE_SIGNALS + sigset_t sigset; +#endif + struct eloop *eloop; + + char *script; +#ifdef HAVE_OPEN_MEMSTREAM + FILE *script_fp; +#endif + char *script_buf; + size_t script_buflen; + char **script_env; + size_t script_envlen; + + int control_fd; + int control_unpriv_fd; + struct fd_list_head control_fds; + char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE]; + char control_sock_unpriv[sizeof(CONTROLSOCKET) + IF_NAMESIZE + 7]; + gid_t control_group; + + /* DHCP Enterprise options, RFC3925 */ + struct dhcp_opt *vivso; + size_t vivso_len; + + char *randomstate; /* original state */ + + /* For filtering RTM_MISS messages per router */ +#ifdef BSD + uint8_t *rt_missfilter; + size_t rt_missfilterlen; + size_t rt_missfiltersize; +#endif + +#ifdef PRIVSEP + struct passwd *ps_user; /* struct passwd for privsep user */ + pid_t ps_root_pid; + int ps_root_fd; /* Privileged Proxy commands */ + int ps_log_fd; /* chroot logging */ + int ps_data_fd; /* Data from root spawned processes */ + struct eloop *ps_eloop; /* eloop for polling root data */ + struct ps_process_head ps_processes; /* List of spawned processes */ + pid_t ps_inet_pid; + int ps_inet_fd; /* Network Proxy commands and data */ + pid_t ps_control_pid; + int ps_control_fd; /* Control Proxy - generic listener */ + int ps_control_data_fd; /* Control Proxy - data query */ + struct fd_list *ps_control; /* Queue for the above */ + struct fd_list *ps_control_client; /* Queue for the above */ +#endif + +#ifdef INET + struct dhcp_opt *dhcp_opts; + size_t dhcp_opts_len; + + int udp_rfd; + int udp_wfd; + + /* Our aggregate option buffer. + * We ONLY use this when options are split, which for most purposes is + * practically never. See RFC3396 for details. */ + uint8_t *opt_buffer; + size_t opt_buffer_len; +#endif +#ifdef INET6 + uint8_t *secret; + size_t secret_len; + +#ifndef __sun + int nd_fd; +#endif + struct ra_head *ra_routers; + + struct dhcp_opt *nd_opts; + size_t nd_opts_len; +#ifdef DHCP6 + int dhcp6_rfd; + int dhcp6_wfd; + struct dhcp_opt *dhcp6_opts; + size_t dhcp6_opts_len; +#endif + +#ifndef __linux__ + int ra_global; +#endif +#endif /* INET6 */ + +#ifdef PLUGIN_DEV + char *dev_load; + int dev_fd; + struct dev *dev; + void *dev_handle; +#endif +}; + +#ifdef USE_SIGNALS +extern const int dhcpcd_signals[]; +extern const size_t dhcpcd_signals_len; +extern const int dhcpcd_signals_ignore[]; +extern const size_t dhcpcd_signals_ignore_len; +#endif + +extern const char *dhcpcd_default_script; + +int dhcpcd_ifafwaiting(const struct interface *); +int dhcpcd_afwaiting(const struct dhcpcd_ctx *); +void dhcpcd_daemonise(struct dhcpcd_ctx *); + +void dhcpcd_linkoverflow(struct dhcpcd_ctx *); +int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **); +void dhcpcd_handlecarrier(struct interface *, int, unsigned int); +int dhcpcd_handleinterface(void *, int, const char *); +void dhcpcd_handlehwaddr(struct interface *, uint16_t, const void *, uint8_t); +void dhcpcd_dropinterface(struct interface *, const char *); +int dhcpcd_selectprofile(struct interface *, const char *); + +void dhcpcd_startinterface(void *); +void dhcpcd_activateinterface(struct interface *, unsigned long long); + +#endif Index: src/dhcpcd.8.in =================================================================== --- /dev/null +++ src/dhcpcd.8.in @@ -0,0 +1,885 @@ +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2006-2021 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd August 23, 2021 +.Dt DHCPCD 8 +.Os +.Sh NAME +.Nm dhcpcd +.Nd a DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl 146ABbDdEGgHJKLMNPpqTV +.Op Fl C , Fl Fl nohook Ar hook +.Op Fl c , Fl Fl script Ar script +.Op Fl e , Fl Fl env Ar value +.Op Fl F , Fl Fl fqdn Ar FQDN +.Op Fl f , Fl Fl config Ar file +.Op Fl h , Fl Fl hostname Ar hostname +.Op Fl I , Fl Fl clientid Ar clientid +.Op Fl i , Fl Fl vendorclassid Ar vendorclassid +.Op Fl j , Fl Fl logfile Ar logfile +.Op Fl l , Fl Fl leasetime Ar seconds +.Op Fl m , Fl Fl metric Ar metric +.Op Fl O , Fl Fl nooption Ar option +.Op Fl o , Fl Fl option Ar option +.Op Fl Q , Fl Fl require Ar option +.Op Fl r , Fl Fl request Ar address +.Op Fl S , Fl Fl static Ar value +.Op Fl s , Fl Fl inform Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address +.Op Fl Fl inform6 +.Op Fl t , Fl Fl timeout Ar seconds +.Op Fl u , Fl Fl userclass Ar class +.Op Fl v , Fl Fl vendor Ar code , Ar value +.Op Fl W , Fl Fl whitelist Ar address Ns Op Ar /cidr +.Op Fl w +.Op Fl Fl waitip Ns = Ns Op 4 | 6 +.Op Fl y , Fl Fl reboot Ar seconds +.Op Fl X , Fl Fl blacklist Ar address Ns Op Ar /cidr +.Op Fl Z , Fl Fl denyinterfaces Ar pattern +.Op Fl z , Fl Fl allowinterfaces Ar pattern +.Op Fl Fl inactive +.Op Fl Fl configure +.Op Fl Fl noconfigure +.Op interface +.Op ... +.Nm +.Fl n , Fl Fl rebind +.Op interface +.Nm +.Fl k , Fl Fl release +.Op interface +.Nm +.Fl U , Fl Fl dumplease +.Op Ar interface +.Nm +.Fl Fl version +.Nm +.Fl x , Fl Fl exit +.Op interface +.Sh DESCRIPTION +.Nm +is an implementation of the DHCP client specified in +.Li RFC 2131 . +.Nm +gets the host information +.Po +IP address, routes, etc +.Pc +from a DHCP server and configures the network +.Ar interface +of the +machine on which it is running. +.Nm +then runs the configuration script which writes DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currently blank, (null) or localhost, or +.Va force_hostname +is YES or TRUE or 1 then +.Nm +sets the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +It will then attempt to renew its lease and reconfigure if the new lease +changes when the lease begins to expire or the DHCP server sends a message +to renew early. +.Pp +If any interface reports a working carrier then +.Nm +will try to obtain a lease before forking to the background, +otherwise it will fork right away. +This behaviour can be modified with the +.Fl b , Fl Fl background +and +.Fl w , Fl Fl waitip +options. +.Pp +.Nm +is also an implementation of the BOOTP client specified in +.Li RFC 951 . +.Pp +.Nm +is also an implementation of the IPv6 Router Solicitor as specified in +.Li RFC 4861 +and +.Li RFC 6106 . +.Pp +.Nm +is also an implementation of the IPv6 Privacy Extensions to AutoConf as +specified in +.Li RFC 4941 . +This feature needs to be enabled in the kernel and +.Nm +will start using it. +.Pp +.Nm +is also an implementation of the DHCPv6 client as specified in +.Li RFC 3315 . +By default, +.Nm +only starts DHCPv6 when instructed to do so by an IPV6 Router Advertisement. +If no Identity Association is configured, +then a Non-temporary Address is requested. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it probes for a valid IPv4LL address +.Po +aka ZeroConf, aka APIPA +.Pc . +Once obtained it restarts the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the +.Fl L , Fl Fl noipv4ll +option. +.Ss Multiple interfaces +If a list of interfaces are given on the command line, then +.Nm +only works with those interfaces, otherwise +.Nm +discovers available Ethernet interfaces that can be configured. +When +.Nm +not limited to one interface on the command line, +it is running in Manager mode. +The +.Nm dhcpcd-ui +project expects dhcpcd to be running this way. +.Pp +If a single interface is given then +.Nm +only works for that interface and runs as a separate instance to other +.Nm +processes. +.Fl w , Fl Fl waitip +option is enabled in this instance to maintain compatibility with older +versions. +Using a single interface also affects the +.Fl k , +.Fl N , +.Fl n +and +.Fl x +options, where the same interface will need to be specified, as a lack of an +interface will imply Manager mode which this is not. +To force starting in Manager mode with only one interface, the +.Fl M , Fl Fl manager +option can be used. +.Pp +Interfaces are preferred by carrier, DHCP lease/IPv4LL and then lowest metric. +For systems that support route metrics, each route will be tagged with the +metric, otherwise +.Nm +changes the routes to use the interface with the same route and the lowest +metric. +See options below for controlling which interfaces we allow and deny through +the use of patterns. +.Pp +Non-ethernet interfaces and some virtual ethernet interfaces +such as TAP and bridge are ignored by default, +as is the FireWire interface. +To work with these devices they either need to be specified on the command line, +be listed in +.Fl Fl allowinterfaces +or have an interface directive in +.Pa @SYSCONFDIR@/dhcpcd.conf . +.Ss Hooking into events +.Nm +runs +.Pa @SCRIPT@ , +or the script specified by the +.Fl c , Fl Fl script +option. +This script runs each script found in +.Pa @HOOKDIR@ +in a lexical order. +The default installation supplies the scripts +.Pa 01-test , +.Pa 02-dump , +.Pa 20-resolv.conf +and +.Pa 30-hostname . +You can disable each script by using the +.Fl C , Fl Fl nohook +option. +See +.Xr dhcpcd-run-hooks 8 +for details on how these scripts work. +.Nm +currently ignores the exit code of the script. +.Pp +More scripts are supplied in +.Pa @DATADIR@/dhcpcd/hooks +and need to be copied to +.Pa @HOOKDIR@ +if you intend to use them. +For example, you could install +.Pa 29-lookup-hostname +so that +.Nm +can lookup the hostname of the IP address in DNS if no hostname +is given by the lease and one is not already set. +.Ss Fine tuning +You can fine-tune the behaviour of +.Nm +with the following options: +.Bl -tag -width indent +.It Fl b , Fl Fl background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.It Fl c , Fl Fl script Ar script +Use this +.Ar script +instead of the default +.Pa @SCRIPT@ . +.It Fl D , Fl Fl duid Op Ar ll | lt | uuid | value +Use a DHCP Unique Identifier. +If a system UUID is available, that will be used to create a DUID-UUID, +otheriwse if persistent storage is available then a DUID-LLT +(link local address + time) is generated, +otherwise DUID-LL is generated (link local address). +The DUID type can be hinted as an optional parameter if the file +.Pa @DBDIR@/duid +does not exist. +If not +.Va ll , +.Va lt +or +.Va uuid +then +.Va value +will be converted from 00:11:22:33 format. +This, plus the IAID will be used as the +.Fl I , Fl Fl clientid . +The DUID generated will be held in +.Pa @DBDIR@/duid +and should not be copied to other hosts. +This file also takes precedence over the above rules except for setting a value. +.It Fl d , Fl Fl debug +Echo debug messages to the stderr and syslog. +.It Fl E , Fl Fl lastlease +If +.Nm +cannot obtain a lease, then try to use the last lease acquired for the +interface. +.It Fl Fl lastleaseextend +Same as the above, but the lease will be retained even if it expires. +.Nm +will give it up if any other host tries to claim it for their own via ARP. +This violates RFC 2131, section 3.7, which states the lease should be +dropped once it has expired. +.It Fl e , Fl Fl env Ar value +Push +.Ar value +to the environment for use in +.Xr dhcpcd-run-hooks 8 . +For example, you can force the hostname hook to always set the hostname with +.Fl e +.Va force_hostname=YES . +.It Fl g , Fl Fl reconfigure +.Nm +will re-apply IP address, routing and run +.Xr dhcpcd-run-hooks 8 +for each interface. +This is useful so that a 3rd party such as PPP or VPN can change the routing +table and / or DNS, etc and then instruct +.Nm +to put things back afterwards. +.Nm +does not read a new configuration when this happens - you should rebind if you +need that functionality. +.It Fl F , Fl Fl fqdn Ar fqdn +Requests that the DHCP server updates DNS using FQDN instead of just a +hostname. +Valid values for +.Ar fqdn +are disable, none, ptr and both. +.Nm +itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC 1035 . +.It Fl f , Fl Fl config Ar file +Specify a config to load instead of +.Pa @SYSCONFDIR@/dhcpcd.conf . +.Nm +always processes the config file before any command line options. +.It Fl h , Fl Fl hostname Ar hostname +Sends +.Ar hostname +to the DHCP server so it can be registered in DNS. +If +.Ar hostname +is an empty string then the current system hostname is sent. +If +.Ar hostname +is a FQDN (i.e., contains a .) then it will be encoded as such. +.It Fl I , Fl Fl clientid Ar clientid +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Fl i , Fl Fl vendorclassid Ar vendorclassid +Override the DHCPv4 +.Ar vendorclassid +field sent. +The default is +dhcpcd-:::. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 +If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. +.It Fl j , Fl Fl logfile Ar logfile +Writes to the specified +.Ar logfile . +.Nm +still writes to +.Xr syslog 3 . +The +.Ar logfile +is reopened when +.Nm +receives the +.Dv SIGUSR2 +signal. +.It Fl k , Fl Fl release Op Ar interface +This causes an existing +.Nm +process running on the +.Ar interface +to release its lease and de-configure the +.Ar interface +regardless of the +.Fl p , Fl Fl persistent +option. +If no +.Ar interface +is specified then this applies to all interfaces in Manager mode. +If no interfaces are left running, +.Nm +will exit. +.It Fl l , Fl Fl leasetime Ar seconds +Request a lease time of +.Ar seconds . +.Ar -1 +represents an infinite lease time. +By default +.Nm +does not request any lease time and leaves it in the hands of the +DHCP server. +.It Fl M , Fl Fl manager +Start +.Nm +in Manager mode even if only one interface specified on the command line. +See the Multiple Interfaces section above. +.It Fl m , Fl Fl metric Ar metric +Metrics are used to prefer an interface over another one, lowest wins. +.Nm +will supply a default metric of 1000 + +.Xr if_nametoindex 3 . +This will be offset by 2000 for wireless interfaces, with additional offsets +of 1000000 for IPv4LL and 2000000 for roaming interfaces. +.It Fl n , Fl Fl rebind Op Ar interface +Notifies +.Nm +to reload its configuration and rebind the specified +.Ar interface . +If no +.Ar interface +is specified then this applies to all interfaces in Manager mode. +If +.Nm +is not running, then it starts up as normal. +.It Fl N , Fl Fl renew Op Ar interface +Notifies +.Nm +to renew existing addresses on the specified +.Ar interface . +If no +.Ar interface +is specified then this applies to all interfaces in Manager mode. +If +.Nm +is not running, then it starts up as normal. +Unlike the +.Fl n , Fl Fl rebind +option above, the configuration for +.Nm +is not reloaded. +.It Fl o , Fl Fl option Ar option +Request the DHCP +.Ar option +variable for use in +.Pa @SCRIPT@ . +.It Fl p , Fl Fl persistent +.Nm +normally de-configures the +.Ar interface +and configuration when it exits. +Sometimes, this isn't desirable if, for example, you have root mounted over +NFS or SSH clients connect to this host and they need to be notified of +the host shutting down. +You can use this option to stop this from happening. +.It Fl r , Fl Fl request Ar address +Request the +.Ar address +in the DHCP DISCOVER message. +There is no guarantee this is the address the DHCP server will actually give. +If no +.Ar address +is given then the first address currently assigned to the +.Ar interface +is used. +.It Fl s , Fl Fl inform Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address +Behaves like +.Fl r , Fl Fl request +as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST. +This does not get a lease as such, just notifies the DHCP server of the +.Ar address +in use. +You should also include the optional +.Ar cidr +network number in case the address is not already configured on the interface. +.Nm +remains running and pretends it has an infinite lease. +.Nm +will not de-configure the interface when it exits. +If +.Nm +fails to contact a DHCP server then it returns a failure instead of falling +back on IPv4LL. +.It Fl Fl inform6 +Performs a DHCPv6 Information Request. +No address is requested or specified, but all other DHCPv6 options are allowed. +This is normally performed automatically when the IPv6 Router Advertises +that the client should perform this operation. +This option is only needed when +.Nm +is not processing IPv6RA messages and the need for DHCPv6 Information Request +exists. +.It Fl S , Fl Fl static Ar value +Configures a static DHCP +.Ar value . +If you set +.Ic ip_address +then +.Nm +will not attempt to obtain a lease and just use the value for the address with +an infinite lease time. +.Pp +Here is an example which configures a static address, routes and DNS. +.D1 dhcpcd -S ip_address=192.168.0.10/24 \e +.D1 -S routers=192.168.0.1 \e +.D1 -S domain_name_servers=192.168.0.1 \e +.D1 eth0 +.Pp +You cannot presently set static DHCPv6 values. +Use the +.Fl e , Fl Fl env +option instead. +.It Fl t , Fl Fl timeout Ar seconds +Timeout after +.Ar seconds , +instead of the default 30. +A setting of 0 +.Ar seconds +causes +.Nm +to wait forever to get a lease. +If +.Nm +is working on a single interface then +.Nm +will exit when a timeout occurs, otherwise +.Nm +will fork into the background. +.It Fl u , Fl Fl userclass Ar class +Tags the DHCPv4 message with the userclass +.Ar class . +DHCP servers use this to give members of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , Fl Fl vendor Ar code , Ns Ar value +Add an encapsulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +To add a raw vendor string, omit +.Ar code +but keep the comma. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 dhcpcd \-v 01,192.168.0.2 eth0 +Set the vendor option 02 with a hex code. +.D1 dhcpcd \-v 02,01:02:03:04:05 eth0 +Set the vendor option 03 with an IP address as a string. +.D1 dhcpcd \-v 03,\e"192.168.0.2\e" eth0 +Set un-encapsulated vendor option to hello world. +.D1 dhcpcd \-v ,"hello world" eth0 +.It Fl Fl version +Display both program version and copyright information. +.Nm +then exits before doing any configuration. +.It Fl w +Wait for an address to be assigned before forking to the background. +Does not take an argument, unlike the below option. +.It Fl Fl waitip Ns = Ns Op 4 | 6 +Wait for an address to be assigned before forking to the background. +4 means wait for an IPv4 address to be assigned. +6 means wait for an IPv6 address to be assigned. +If no argument is given, +.Nm +will wait for any address protocol to be assigned. +It is possible to wait for more than one address protocol and +.Nm +will only fork to the background when all waiting conditions are satisfied. +.It Fl x , Fl Fl exit Op Ar interface +This will signal an existing +.Nm +process running on the +.Ar interface +to exit. +If no +.Ar interface +is specified, then the above is applied to all interfaces in Manager mode. +See the +.Fl p , Fl Fl persistent +option to control configuration persistence on exit, +which is enabled by default in +.Xr dhcpcd.conf 5 . +.Nm +then waits until this process has exited. +.It Fl y , Fl Fl reboot Ar seconds +Allow +.Ar reboot +seconds before moving to the discover phase if we have an old lease to use. +Allow +.Ar reboot +seconds before starting fallback states from the discover phase. +IPv4LL is started when the first +.Ar reboot +timeout is reached. +The default is 5 seconds. +A setting of 0 seconds causes +.Nm +to skip the reboot phase and go straight into discover. +This has no effect on DHCPv6 other than skipping the reboot phase. +.El +.Ss Restricting behaviour +.Nm +will try to do as much as it can by default. +However, there are sometimes situations where you don't want the things to be +configured exactly how the DHCP server wants. +Here are some options that deal with turning these bits off. +.Pp +Note that when +.Nm +is restricted to a single interface then the interface also needs to be +specified when asking +.Nm +to exit using the commandline. +If the protocol is restricted as well then the protocol needs to be included +with the exit instruction. +.Bl -tag -width indent +.It Fl 1 , Fl Fl oneshot +Exit after configuring an interface. +Use the +.Fl w , Fl Fl waitip +option to specify which protocol(s) to configure before exiting. +.It Fl 4 , Fl Fl ipv4only +Configure IPv4 only. +.It Fl 6 , Fl Fl ipv6only +Configure IPv6 only. +.It Fl A , Fl Fl noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl B , Fl Fl nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl C , Fl Fl nohook Ar script +Don't run this hook script. +Matches full name, or prefixed with 2 numbers optionally ending with +.Pa .sh . +.Pp +So to stop +.Nm +from touching your DNS settings you would do:- +.D1 dhcpcd -C resolv.conf eth0 +.It Fl G , Fl Fl nogateway +Don't set any default routes. +.It Fl H , Fl Fl xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. +.It Fl J , Fl Fl broadcast +Instructs the DHCP server to broadcast replies back to the client. +Normally this is only set for non-Ethernet interfaces, +such as FireWire and InfiniBand. +In most instances, +.Nm +will set this automatically. +.It Fl K , Fl Fl nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. +.It Fl L , Fl Fl noipv4ll +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). +.It Fl O , Fl Fl nooption Ar option +Removes the +.Ar option +from the DHCP message before processing. +.It Fl P , Fl Fl printpidfile +Print the +.Pa pidfile +.Nm +will use based on commmand-line arguments to stdout. +.It Fl Q , Fl Fl require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +To enforce that +.Nm +only responds to DHCP servers and not BOOTP servers, you can +.Fl Q +.Ar dhcp_message_type . +.It Fl q , Fl Fl quiet +Quiet +.Nm +on the command line, only warnings and errors will be displayed. +If this option is used another time then all console output is disabled. +These messages are still logged via +.Xr syslog 3 . +.It Fl T , Fl Fl test +On receipt of DHCP messages just call +.Pa @SCRIPT@ +with the reason of TEST which echos the DHCP variables found in the message +to the console. +The interface configuration isn't touched and neither are any configuration +files. +The +.Ar rapid_commit +option is not sent in TEST mode so that the server does not lease an address. +To test INFORM the interface needs to be configured with the desired address +before starting +.Nm . +.It Fl U , Fl Fl dumplease Op Ar interface +Dumps the current lease for the +.Ar interface +to stdout. +If no +.Ar interface +is given then all interfaces are dumped. +Use the +.Fl 4 +or +.Fl 6 +flags to specify an address family. +If a lease is piped in via standard input then that is dumped. +In this case, specifying an address family is mandatory. +.It Fl V , Fl Fl variables +Display a list of option codes, the associated variable and encoding for use in +.Xr dhcpcd-run-hooks 8 . +Variables are prefixed with new_ and old_ unless the option number is -. +Variables without an option are part of the DHCP message and cannot be +directly requested. +.It Fl W , Fl Fl whitelist Ar address Ns Op /cidr +Only accept packets from +.Ar address Ns Op /cidr . +.Fl X , Fl Fl blacklist +is ignored if +.Fl W , Fl Fl whitelist +is set. +.It Fl X , Fl Fl blacklist Ar address Ns Op Ar /cidr +Ignore all packets from +.Ar address Ns Op Ar /cidr . +.It Fl Z , Fl Fl denyinterfaces Ar pattern +When discovering interfaces, the interface name must not match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +.It Fl z , Fl Fl allowinterfaces Ar pattern +When discovering interfaces, the interface name must match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +If the same interface is matched in +.Fl Z , Fl Fl denyinterfaces +then it is still denied. +.It Fl Fl inactive +Don't start any interfaces other than those specified on the command line. +This allows +.Nm +to be started in Manager mode and then wait for subsequent +.Nm +commands to start each interface as required. +.It Fl Fl configure +Allows +.Nm +to configure the system. +This is the default behaviour and sets +.Ev if_configured=true . +.It Fl Fl noconfigure +.Nm +will not configure the system at all. +This is only of use if the +.Fl Fl script +that +.Nm +calls at each network event configures the system instead. +This is different from +.Fl T , Fl Fl test +mode in that it's not one shot and the only change to the environment is the +addition of +.Ev if_configured=false . +.It Fl Fl nodev +Don't load any +.Pa /dev +management modules. +.El +.Sh 3RDPARTY LINK MANAGEMENT +Some interfaces require configuration by 3rd parties, such as PPP or VPN. +When an interface configuration in +.Nm +is marked as STATIC or INFORM without an address then +.Nm +will monitor the interface until an address is added or removed from it and +act accordingly. +For point to point interfaces (like PPP), a default route to its +destination is automatically added to the configuration. +If the point to point interface is configured for INFORM, then +.Nm +unicasts INFORM to the destination, otherwise it defaults to STATIC. +.Sh NOTES +.Nm +requires a Berkley Packet Filter, or BPF device on BSD based systems and a +Linux Socket Filter, or LPF device on Linux based systems for all IPv4 +configuration. +.Pp +If restricting +.Nm +to a single interface and optionally address family via the command-line +then all further calls to +.Nm +to rebind, reconfigure or exit need to include the same restrictive flags +so that +.Nm +knows which process to signal. +.Pp +Some DHCP servers implement ClientID filtering. +If +.Nm +is replacing an in-use DHCP client then you might need to adjust the clientid +option +.Nm +sends to match. +If using a DUID in place of the ClientID, edit +.Pa @DBDIR@/duid +accordingly. +.Sh FILES +.Bl -ohang +.It Pa @SYSCONFDIR@/dhcpcd.conf +Configuration file for dhcpcd. +If you always use the same options, put them here. +.It Pa @SCRIPT@ +Bourne shell script that is run to configure or de-configure an interface. +.It Pa @LIBDIR@/dhcpcd/dev +Linux +.Pa /dev +management modules. +.It Pa @HOOKDIR@ +A directory containing bourne shell scripts that are run by the above script. +Each script can be disabled by using the +.Fl C , Fl Fl nohook +option described above. +.It Pa @DBDIR@/duid +Text file that holds the DUID used to identify the host. +.It Pa @DBDIR@/secret +Text file that holds a secret key known only to the host. +.It Pa @DBDIR@/ Ns Ar interface Ns Ar -ssid Ns .lease +The actual DHCP message sent by the server. +We use this when reading the last +lease and use the file's mtime as when it was issued. +.It Pa @DBDIR@/ Ns Ar interface Ns Ar -ssid Ns .lease6 +The actual DHCPv6 message sent by the server. +We use this when reading the last +lease and use the file's mtime as when it was issued. +.It Pa @DBDIR@/rdm_monotonic +Stores the monotonic counter used in the +.Ar replay +field in Authentication Options. +.It Pa @RUNDIR@/pid +Stores the PID of +.Nm +running on all interfaces. +.It Pa @RUNDIR@/ Ns Ar interface Ns .pid +Stores the PID of +.Nm +running on the +.Ar interface . +.It Pa @RUNDIR@/sock +Control socket to the manager daemon. +.It Pa @RUNDIR@/unpriv.sock +Unprivileged socket to the manager daemon, only allows state retrieval. +.It Pa @RUNDIR@/ Ns Ar interface Ns .sock +Control socket to per interface daemon. +.It Pa @RUNDIR@/ Ns Ar interface Ns .unpriv.sock +Unprivileged socket to per interface daemon, only allows state retrieval. +.El +.Sh SEE ALSO +.Xr fnmatch 3 , +.Xr if_nametoindex 3 , +.Xr dhcpcd.conf 5 , +.Xr resolv.conf 5 , +.Xr dhcpcd-run-hooks 8 , +.Xr resolvconf 8 +.Sh STANDARDS +RFC\ 951, RFC\ 1534, RFC\ 2104, RFC\ 2131, RFC\ 2132, RFC\ 2563, RFC\ 2855, +RFC\ 3004, RFC\ 3118, RFC\ 3203, RFC\ 3315, RFC\ 3361, RFC\ 3633, RFC\ 3396, +RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075, +RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833, +RFC\ 4941, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106, RFC\ 6334, RFC\ 6355, +RFC\ 6603, RFC\ 6704, RFC\ 7217, RFC\ 7550, RFC\ 7844. +.Sh AUTHORS +.An Roy Marples Aq Mt roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd Index: src/dhcpcd.c =================================================================== --- /dev/null +++ src/dhcpcd.c @@ -0,0 +1,2639 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +static const char dhcpcd_copyright[] = "Copyright (c) 2006-2021 Roy Marples"; + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "arp.h" +#include "common.h" +#include "control.h" +#include "dev.h" +#include "dhcp-common.h" +#include "dhcpcd.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "duid.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "script.h" + +#ifdef HAVE_CAPSICUM +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +#ifdef USE_SIGNALS +const int dhcpcd_signals[] = { + SIGTERM, + SIGINT, + SIGALRM, + SIGHUP, + SIGUSR1, + SIGUSR2, + SIGCHLD, +}; +const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals); + +const int dhcpcd_signals_ignore[] = { + SIGPIPE, +}; +const size_t dhcpcd_signals_ignore_len = __arraycount(dhcpcd_signals_ignore); +#endif + +const char *dhcpcd_default_script = SCRIPT; + +static void +usage(void) +{ + +printf("usage: "PACKAGE"\t[-146ABbDdEGgHJKLMNPpqTV]\n" + "\t\t[-C, --nohook hook] [-c, --script script]\n" + "\t\t[-e, --env value] [-F, --fqdn FQDN] [-f, --config file]\n" + "\t\t[-h, --hostname hostname] [-I, --clientid clientid]\n" + "\t\t[-i, --vendorclassid vendorclassid] [-j, --logfile logfile]\n" + "\t\t[-l, --leasetime seconds] [-m, --metric metric]\n" + "\t\t[-O, --nooption option] [-o, --option option]\n" + "\t\t[-Q, --require option] [-r, --request address]\n" + "\t\t[-S, --static value]\n" + "\t\t[-s, --inform address[/cidr[/broadcast_address]]]\n [--inform6]" + "\t\t[-t, --timeout seconds] [-u, --userclass class]\n" + "\t\t[-v, --vendor code, value] [-W, --whitelist address[/cidr]] [-w]\n" + "\t\t[--waitip [4 | 6]] [-y, --reboot seconds]\n" + "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n" + "\t\t[-z, --allowinterfaces pattern] [--inactive] [interface] [...]\n" + " "PACKAGE"\t-n, --rebind [interface]\n" + " "PACKAGE"\t-k, --release [interface]\n" + " "PACKAGE"\t-U, --dumplease interface\n" + " "PACKAGE"\t--version\n" + " "PACKAGE"\t-x, --exit [interface]\n"); +} + +static void +free_globals(struct dhcpcd_ctx *ctx) +{ + struct dhcp_opt *opt; + + if (ctx->ifac) { + for (; ctx->ifac > 0; ctx->ifac--) + free(ctx->ifav[ctx->ifac - 1]); + free(ctx->ifav); + ctx->ifav = NULL; + } + if (ctx->ifdc) { + for (; ctx->ifdc > 0; ctx->ifdc--) + free(ctx->ifdv[ctx->ifdc - 1]); + free(ctx->ifdv); + ctx->ifdv = NULL; + } + if (ctx->ifcc) { + for (; ctx->ifcc > 0; ctx->ifcc--) + free(ctx->ifcv[ctx->ifcc - 1]); + free(ctx->ifcv); + ctx->ifcv = NULL; + } + +#ifdef INET + if (ctx->dhcp_opts) { + for (opt = ctx->dhcp_opts; + ctx->dhcp_opts_len > 0; + opt++, ctx->dhcp_opts_len--) + free_dhcp_opt_embenc(opt); + free(ctx->dhcp_opts); + ctx->dhcp_opts = NULL; + } +#endif +#ifdef INET6 + if (ctx->nd_opts) { + for (opt = ctx->nd_opts; + ctx->nd_opts_len > 0; + opt++, ctx->nd_opts_len--) + free_dhcp_opt_embenc(opt); + free(ctx->nd_opts); + ctx->nd_opts = NULL; + } +#ifdef DHCP6 + if (ctx->dhcp6_opts) { + for (opt = ctx->dhcp6_opts; + ctx->dhcp6_opts_len > 0; + opt++, ctx->dhcp6_opts_len--) + free_dhcp_opt_embenc(opt); + free(ctx->dhcp6_opts); + ctx->dhcp6_opts = NULL; + } +#endif +#endif + if (ctx->vivso) { + for (opt = ctx->vivso; + ctx->vivso_len > 0; + opt++, ctx->vivso_len--) + free_dhcp_opt_embenc(opt); + free(ctx->vivso); + ctx->vivso = NULL; + } +} + +static void +handle_exit_timeout(void *arg) +{ + struct dhcpcd_ctx *ctx; + + ctx = arg; + logerrx("timed out"); + if (!(ctx->options & DHCPCD_MANAGER)) { + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->active == IF_ACTIVE_USER) + script_runreason(ifp, "STOPPED"); + } + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + ctx->options |= DHCPCD_NOWAITIP; + dhcpcd_daemonise(ctx); +} + +static const char * +dhcpcd_af(int af) +{ + + switch (af) { + case AF_UNSPEC: + return "IP"; + case AF_INET: + return "IPv4"; + case AF_INET6: + return "IPv6"; + default: + return NULL; + } +} + +int +dhcpcd_ifafwaiting(const struct interface *ifp) +{ + unsigned long long opts; + bool foundany = false; + + if (ifp->active != IF_ACTIVE_USER) + return AF_MAX; + +#define DHCPCD_WAITALL (DHCPCD_WAITIP4 | DHCPCD_WAITIP6) + opts = ifp->options->options; +#ifdef INET + if (opts & DHCPCD_WAITIP4 || + (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL))) + { + bool foundaddr = ipv4_hasaddr(ifp); + + if (opts & DHCPCD_WAITIP4 && !foundaddr) + return AF_INET; + if (foundaddr) + foundany = true; + } +#endif +#ifdef INET6 + if (opts & DHCPCD_WAITIP6 || + (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL))) + { + bool foundaddr = ipv6_hasaddr(ifp); + + if (opts & DHCPCD_WAITIP6 && !foundaddr) + return AF_INET; + if (foundaddr) + foundany = true; + } +#endif + + if (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL) && !foundany) + return AF_UNSPEC; + return AF_MAX; +} + +int +dhcpcd_afwaiting(const struct dhcpcd_ctx *ctx) +{ + unsigned long long opts; + const struct interface *ifp; + int af; + + if (!(ctx->options & DHCPCD_WAITOPTS)) + return AF_MAX; + + opts = ctx->options; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { +#ifdef INET + if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP4) && + ipv4_hasaddr(ifp)) + opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP4); +#endif +#ifdef INET6 + if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP6) && + ipv6_hasaddr(ifp)) + opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP6); +#endif + if (!(opts & DHCPCD_WAITOPTS)) + break; + } + if (opts & DHCPCD_WAITIP) + af = AF_UNSPEC; + else if (opts & DHCPCD_WAITIP4) + af = AF_INET; + else if (opts & DHCPCD_WAITIP6) + af = AF_INET6; + else + return AF_MAX; + return af; +} + +static int +dhcpcd_ipwaited(struct dhcpcd_ctx *ctx) +{ + struct interface *ifp; + int af; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { + logdebugx("%s: waiting for an %s address", + ifp->name, dhcpcd_af(af)); + return 0; + } + } + + if ((af = dhcpcd_afwaiting(ctx)) != AF_MAX) { + logdebugx("waiting for an %s address", + dhcpcd_af(af)); + return 0; + } + + return 1; +} + +/* Returns the pid of the child, otherwise 0. */ +void +dhcpcd_daemonise(struct dhcpcd_ctx *ctx) +{ +#ifdef THERE_IS_NO_FORK + eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); + errno = ENOSYS; + return; +#else + int i; + unsigned int logopts = loggetopts(); + + if (ctx->options & DHCPCD_DAEMONISE && + !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) + { + if (!dhcpcd_ipwaited(ctx)) + return; + } + + if (ctx->options & DHCPCD_ONESHOT) { + loginfox("exiting due to oneshot"); + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + + eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); + if (ctx->options & DHCPCD_DAEMONISED || + !(ctx->options & DHCPCD_DAEMONISE)) + return; + + /* Don't use loginfo because this makes no sense in a log. */ + if (!(logopts & LOGERR_QUIET) && ctx->stderr_valid) + (void)fprintf(stderr, + "forked to background, child pid %d\n", getpid()); + i = EXIT_SUCCESS; + if (write(ctx->fork_fd, &i, sizeof(i)) == -1) + logerr("write"); + ctx->options |= DHCPCD_DAEMONISED; + eloop_event_delete(ctx->eloop, ctx->fork_fd); + close(ctx->fork_fd); + ctx->fork_fd = -1; + + /* + * Stop writing to stderr. + * On the happy path, only the manager process writes to stderr, + * so this just stops wasting fprintf calls to nowhere. + * All other calls - ie errors in privsep processes or script output, + * will error when printing. + * If we *really* want to fix that, then we need to suck + * stderr/stdout in the manager process and either disacrd it or pass + * it to the launcher process and then to stderr. + */ + logopts &= ~LOGERR_ERR; + logsetopts(logopts); +#endif +} + +static void +dhcpcd_drop(struct interface *ifp, int stop) +{ + +#ifdef DHCP6 + dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); +#endif +#ifdef INET6 + ipv6nd_drop(ifp); + ipv6_drop(ifp); +#endif +#ifdef IPV4LL + ipv4ll_drop(ifp); +#endif +#ifdef INET + dhcp_drop(ifp, stop ? "STOP" : "EXPIRE"); +#endif +#ifdef ARP + arp_drop(ifp); +#endif +#if !defined(DHCP6) && !defined(DHCP) + UNUSED(stop); +#endif +} + +static void +stop_interface(struct interface *ifp, const char *reason) +{ + struct dhcpcd_ctx *ctx; + + ctx = ifp->ctx; + loginfox("%s: removing interface", ifp->name); + ifp->options->options |= DHCPCD_STOPPING; + + dhcpcd_drop(ifp, 1); + script_runreason(ifp, reason == NULL ? "STOPPED" : reason); + + /* Delete all timeouts for the interfaces */ + eloop_q_timeout_delete(ctx->eloop, ELOOP_QUEUE_ALL, NULL, ifp); + + /* De-activate the interface */ + ifp->active = IF_INACTIVE; + ifp->options->options &= ~DHCPCD_STOPPING; + + if (!(ctx->options & (DHCPCD_MANAGER | DHCPCD_TEST))) + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static void +configure_interface1(struct interface *ifp) +{ + struct if_options *ifo = ifp->options; + + /* Do any platform specific configuration */ + if_conf(ifp); + + /* If we want to release a lease, we can't really persist the + * address either. */ + if (ifo->options & DHCPCD_RELEASE) + ifo->options &= ~DHCPCD_PERSISTENT; + + if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) { + ifo->options &= ~DHCPCD_ARP; + if (!(ifp->flags & IFF_MULTICAST)) + ifo->options &= ~DHCPCD_IPV6RS; + if (!(ifo->options & (DHCPCD_INFORM | DHCPCD_WANTDHCP))) + ifo->options |= DHCPCD_STATIC; + } + + if (ifo->metric != -1) + ifp->metric = (unsigned int)ifo->metric; + +#ifdef INET6 + /* We want to setup INET6 on the interface as soon as possible. */ + if (ifp->active == IF_ACTIVE_USER && + ifo->options & DHCPCD_IPV6 && + !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) + { + /* If not doing any DHCP, disable the RDNSS requirement. */ + if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6))) + ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; + if_setup_inet6(ifp); + } +#endif + + if (!(ifo->options & DHCPCD_IAID)) { + /* + * An IAID is for identifying a unqiue interface within + * the client. It is 4 bytes long. Working out a default + * value is problematic. + * + * Interface name and number are not stable + * between different OS's. Some OS's also cannot make + * up their mind what the interface should be called + * (yes, udev, I'm looking at you). + * Also, the name could be longer than 4 bytes. + * Also, with pluggable interfaces the name and index + * could easily get swapped per actual interface. + * + * The MAC address is 6 bytes long, the final 3 + * being unique to the manufacturer and the initial 3 + * being unique to the organisation which makes it. + * We could use the last 4 bytes of the MAC address + * as the IAID as it's the most stable part given the + * above, but equally it's not guaranteed to be + * unique. + * + * Given the above, and our need to reliably work + * between reboots without persitent storage, + * generating the IAID from the MAC address is the only + * logical default. + * Saying that, if a VLANID has been specified then we + * can use that. It's possible that different interfaces + * can have the same VLANID, but this is no worse than + * generating the IAID from the duplicate MAC address. + * + * dhclient uses the last 4 bytes of the MAC address. + * dibbler uses an increamenting counter. + * wide-dhcpv6 uses 0 or a configured value. + * odhcp6c uses 1. + * Windows 7 uses the first 3 bytes of the MAC address + * and an unknown byte. + * dhcpcd-6.1.0 and earlier used the interface name, + * falling back to interface index if name > 4. + */ + if (ifp->vlanid != 0) { + uint32_t vlanid; + + /* Maximal VLANID is 4095, so prefix with 0xff + * so we don't conflict with an interface index. */ + vlanid = htonl(ifp->vlanid | 0xff000000); + memcpy(ifo->iaid, &vlanid, sizeof(vlanid)); + } else if (ifo->options & DHCPCD_ANONYMOUS) + memset(ifo->iaid, 0, sizeof(ifo->iaid)); + else if (ifp->hwlen >= sizeof(ifo->iaid)) { + memcpy(ifo->iaid, + ifp->hwaddr + ifp->hwlen - sizeof(ifo->iaid), + sizeof(ifo->iaid)); + } else { + uint32_t len; + + len = (uint32_t)strlen(ifp->name); + if (len <= sizeof(ifo->iaid)) { + memcpy(ifo->iaid, ifp->name, len); + if (len < sizeof(ifo->iaid)) + memset(ifo->iaid + len, 0, + sizeof(ifo->iaid) - len); + } else { + /* IAID is the same size as a uint32_t */ + len = htonl(ifp->index); + memcpy(ifo->iaid, &len, sizeof(ifo->iaid)); + } + } + ifo->options |= DHCPCD_IAID; + } + +#ifdef DHCP6 + if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 && + ifp->name[0] != '\0') + { + ifo->ia = malloc(sizeof(*ifo->ia)); + if (ifo->ia == NULL) + logerr(__func__); + else { + ifo->ia_len = 1; + ifo->ia->ia_type = D6_OPTION_IA_NA; + memcpy(ifo->ia->iaid, ifo->iaid, sizeof(ifo->iaid)); + memset(&ifo->ia->addr, 0, sizeof(ifo->ia->addr)); +#ifndef SMALL + ifo->ia->sla = NULL; + ifo->ia->sla_len = 0; +#endif + } + } else { + size_t i; + + for (i = 0; i < ifo->ia_len; i++) { + if (!ifo->ia[i].iaid_set) { + memcpy(&ifo->ia[i].iaid, ifo->iaid, + sizeof(ifo->ia[i].iaid)); + ifo->ia[i].iaid_set = 1; + } + } + } +#endif + + /* If root is network mounted, we don't want to kill the connection + * if the DHCP server goes the way of the dodo OR dhcpcd is rebooting + * and the lease file has expired. */ + if (is_root_local() == 0) + ifo->options |= DHCPCD_LASTLEASE_EXTEND; +} + +int +dhcpcd_selectprofile(struct interface *ifp, const char *profile) +{ + struct if_options *ifo; + char pssid[PROFILE_LEN]; + + if (ifp->ssid_len) { + ssize_t r; + + r = print_string(pssid, sizeof(pssid), OT_ESCSTRING, + ifp->ssid, ifp->ssid_len); + if (r == -1) { + logerr(__func__); + pssid[0] = '\0'; + } + } else + pssid[0] = '\0'; + ifo = read_config(ifp->ctx, ifp->name, pssid, profile); + if (ifo == NULL) { + logdebugx("%s: no profile %s", ifp->name, profile); + return -1; + } + if (profile != NULL) { + strlcpy(ifp->profile, profile, sizeof(ifp->profile)); + loginfox("%s: selected profile %s", ifp->name, profile); + } else + *ifp->profile = '\0'; + + free_options(ifp->ctx, ifp->options); + ifp->options = ifo; + if (profile) { + add_options(ifp->ctx, ifp->name, ifp->options, + ifp->ctx->argc, ifp->ctx->argv); + configure_interface1(ifp); + } + return 1; +} + +static void +configure_interface(struct interface *ifp, int argc, char **argv, + unsigned long long options) +{ + time_t old; + + old = ifp->options ? ifp->options->mtime : 0; + dhcpcd_selectprofile(ifp, NULL); + if (ifp->options == NULL) { + /* dhcpcd cannot continue with this interface. */ + ifp->active = IF_INACTIVE; + return; + } + add_options(ifp->ctx, ifp->name, ifp->options, argc, argv); + ifp->options->options |= options; + configure_interface1(ifp); + + /* If the mtime has changed drop any old lease */ + if (old != 0 && ifp->options->mtime != old) { + logwarnx("%s: config file changed, expiring leases", + ifp->name); + dhcpcd_drop(ifp, 0); + } +} + +static void +dhcpcd_initstate2(struct interface *ifp, unsigned long long options) +{ + struct if_options *ifo; + + if (options) { + if ((ifo = default_config(ifp->ctx)) == NULL) { + logerr(__func__); + return; + } + ifo->options |= options; + free(ifp->options); + ifp->options = ifo; + } else + ifo = ifp->options; + +#ifdef INET6 + if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == -1) { + logerr(__func__); + ifo->options &= ~DHCPCD_IPV6; + } +#endif +} + +static void +dhcpcd_initstate1(struct interface *ifp, int argc, char **argv, + unsigned long long options) +{ + + configure_interface(ifp, argc, argv, options); + if (ifp->active) + dhcpcd_initstate2(ifp, 0); +} + +static void +dhcpcd_initstate(struct interface *ifp, unsigned long long options) +{ + + dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); +} + +static void +dhcpcd_reportssid(struct interface *ifp) +{ + char pssid[IF_SSIDLEN * 4]; + + if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, + ifp->ssid, ifp->ssid_len) == -1) + { + logerr(__func__); + return; + } + + loginfox("%s: connected to Access Point: %s", ifp->name, pssid); +} + +static void +dhcpcd_nocarrier_roaming(struct interface *ifp) +{ + + loginfox("%s: carrier lost - roaming", ifp->name); + +#ifdef ARP + arp_drop(ifp); +#endif +#ifdef INET + dhcp_abort(ifp); +#endif +#ifdef DHCP6 + dhcp6_abort(ifp); +#endif + + rt_build(ifp->ctx, AF_UNSPEC); + script_runreason(ifp, "NOCARRIER_ROAMING"); +} + +void +dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags) +{ + bool was_link_up = if_is_link_up(ifp); + bool was_roaming = if_roaming(ifp); + + ifp->carrier = carrier; + ifp->flags = flags; + + if (!if_is_link_up(ifp)) { + if (!ifp->active || (!was_link_up && !was_roaming)) + return; + + /* + * If the interface is roaming (generally on wireless) + * then while we are not up, we are not down either. + * Preserve the network state until we either disconnect + * or re-connect. + */ + if (!ifp->options->randomise_hwaddr && if_roaming(ifp)) { + dhcpcd_nocarrier_roaming(ifp); + return; + } + + loginfox("%s: carrier lost", ifp->name); + script_runreason(ifp, "NOCARRIER"); + dhcpcd_drop(ifp, 0); + + if (ifp->options->randomise_hwaddr) { + bool is_up = ifp->flags & IFF_UP; + + if (is_up) + if_down(ifp); + if (if_randomisemac(ifp) == -1 && errno != ENXIO) + logerr(__func__); + if (is_up) + if_up(ifp); + } + + return; + } + + /* + * At this point carrier is NOT DOWN and we have IFF_UP. + * We should treat LINK_UNKNOWN as up as the driver may not support + * link state changes. + * The consideration of any other information about carrier should + * be handled in the OS specific if_carrier() function. + */ + if (was_link_up) + return; + + if (ifp->active) { + if (carrier == LINK_UNKNOWN) + loginfox("%s: carrier unknown, assuming up", ifp->name); + else + loginfox("%s: carrier acquired", ifp->name); + } + +#if !defined(__linux__) && !defined(__NetBSD__) + /* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the + * hardware address changes so we have to go + * through the disovery process to work it out. */ + dhcpcd_handleinterface(ifp->ctx, 0, ifp->name); +#endif + + if (ifp->wireless) { + uint8_t ossid[IF_SSIDLEN]; + size_t olen; + + olen = ifp->ssid_len; + memcpy(ossid, ifp->ssid, ifp->ssid_len); + if_getssid(ifp); + + /* If we changed SSID network, drop leases */ + if ((ifp->ssid_len != olen || + memcmp(ifp->ssid, ossid, ifp->ssid_len)) && ifp->active) + { + dhcpcd_reportssid(ifp); + dhcpcd_drop(ifp, 0); +#ifdef IPV4LL + ipv4ll_reset(ifp); +#endif + } + } + + if (!ifp->active) + return; + + dhcpcd_initstate(ifp, 0); + script_runreason(ifp, "CARRIER"); + +#ifdef INET6 + /* Set any IPv6 Routers we remembered to expire faster than they + * would normally as we maybe on a new network. */ + ipv6nd_startexpire(ifp); +#ifdef IPV6_MANAGETEMPADDR + /* RFC4941 Section 3.5 */ + ipv6_regentempaddrs(ifp); +#endif +#endif + + dhcpcd_startinterface(ifp); +} + +static void +warn_iaid_conflict(struct interface *ifp, uint16_t ia_type, uint8_t *iaid) +{ + struct interface *ifn; +#ifdef INET6 + size_t i; + struct if_ia *ia; +#endif + + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp || !ifn->active) + continue; + if (ifn->options->options & DHCPCD_ANONYMOUS) + continue; + if (ia_type == 0 && + memcmp(ifn->options->iaid, iaid, + sizeof(ifn->options->iaid)) == 0) + break; +#ifdef INET6 + for (i = 0; i < ifn->options->ia_len; i++) { + ia = &ifn->options->ia[i]; + if (ia->ia_type == ia_type && + memcmp(ia->iaid, iaid, sizeof(ia->iaid)) == 0) + break; + } +#endif + } + + /* This is only a problem if the interfaces are on the same network. */ + if (ifn) + logerrx("%s: IAID conflicts with one assigned to %s", + ifp->name, ifn->name); +} + +static void +dhcpcd_initduid(struct dhcpcd_ctx *ctx, struct interface *ifp) +{ + char buf[DUID_LEN * 3]; + + if (ctx->duid != NULL) { + if (ifp == NULL) + goto log; + return; + } + + duid_init(ctx, ifp); + if (ctx->duid == NULL) + return; + +log: + loginfox("DUID %s", + hwaddr_ntoa(ctx->duid, ctx->duid_len, buf, sizeof(buf))); +} + +void +dhcpcd_startinterface(void *arg) +{ + struct interface *ifp = arg; + struct if_options *ifo = ifp->options; + + if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) { + loginfox("%s: waiting for carrier", ifp->name); + return; + } + + if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) && + !(ifo->options & DHCPCD_ANONYMOUS)) + { + char buf[sizeof(ifo->iaid) * 3]; +#ifdef INET6 + size_t i; + struct if_ia *ia; +#endif + + /* Try and init DUID from the interface hardware address */ + dhcpcd_initduid(ifp->ctx, ifp); + + /* Report IAIDs */ + loginfox("%s: IAID %s", ifp->name, + hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid), + buf, sizeof(buf))); + warn_iaid_conflict(ifp, 0, ifo->iaid); + +#ifdef INET6 + for (i = 0; i < ifo->ia_len; i++) { + ia = &ifo->ia[i]; + if (memcmp(ifo->iaid, ia->iaid, sizeof(ifo->iaid))) { + loginfox("%s: IA type %u IAID %s", + ifp->name, ia->ia_type, + hwaddr_ntoa(ia->iaid, sizeof(ia->iaid), + buf, sizeof(buf))); + warn_iaid_conflict(ifp, ia->ia_type, ia->iaid); + } + } +#endif + } + +#ifdef INET6 + if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { + logerr("%s: ipv6_start", ifp->name); + ifo->options &= ~DHCPCD_IPV6; + } + + if (ifo->options & DHCPCD_IPV6) { + if (ifp->active == IF_ACTIVE_USER) { + ipv6_startstatic(ifp); + + if (ifo->options & DHCPCD_IPV6RS) + ipv6nd_startrs(ifp); + } + +#ifdef DHCP6 + /* DHCPv6 could be turned off, but the interface + * is still delegated to. */ + if (ifp->active) + dhcp6_find_delegates(ifp); + + if (ifo->options & DHCPCD_DHCP6) { + if (ifp->active == IF_ACTIVE_USER) { + enum DH6S d6_state; + + if (ifo->options & DHCPCD_IA_FORCED) + d6_state = DH6S_INIT; + else if (ifo->options & DHCPCD_INFORM6) + d6_state = DH6S_INFORM; + else + d6_state = DH6S_CONFIRM; + if (dhcp6_start(ifp, d6_state) == -1) + logerr("%s: dhcp6_start", ifp->name); + } + } +#endif + } +#endif + +#ifdef INET + if (ifo->options & DHCPCD_IPV4 && ifp->active == IF_ACTIVE_USER) { + /* Ensure we have an IPv4 state before starting DHCP */ + if (ipv4_getstate(ifp) != NULL) + dhcp_start(ifp); + } +#endif +} + +static void +dhcpcd_prestartinterface(void *arg) +{ + struct interface *ifp = arg; + struct dhcpcd_ctx *ctx = ifp->ctx; + bool randmac_down; + + if (ifp->carrier <= LINK_DOWN && + ifp->options->randomise_hwaddr && + ifp->flags & IFF_UP) + { + if_down(ifp); + randmac_down = true; + } else + randmac_down = false; + + if ((!(ctx->options & DHCPCD_MANAGER) || + ifp->options->options & DHCPCD_IF_UP || randmac_down) && + !(ifp->flags & IFF_UP)) + { + if (ifp->options->randomise_hwaddr && + if_randomisemac(ifp) == -1) + logerr(__func__); + if (if_up(ifp) == -1) + logerr(__func__); + } + + dhcpcd_startinterface(ifp); +} + +static void +run_preinit(struct interface *ifp) +{ + + if (ifp->ctx->options & DHCPCD_TEST) + return; + + script_runreason(ifp, "PREINIT"); + if (ifp->wireless && if_is_link_up(ifp)) + dhcpcd_reportssid(ifp); + if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN) + script_runreason(ifp, + ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER"); +} + +void +dhcpcd_activateinterface(struct interface *ifp, unsigned long long options) +{ + + if (ifp->active) + return; + + ifp->active = IF_ACTIVE; + dhcpcd_initstate2(ifp, options); + + /* It's possible we might not have been able to load + * a config. */ + if (!ifp->active) + return; + + configure_interface1(ifp); + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); +} + +int +dhcpcd_handleinterface(void *arg, int action, const char *ifname) +{ + struct dhcpcd_ctx *ctx = arg; + struct ifaddrs *ifaddrs; + struct if_head *ifs; + struct interface *ifp, *iff; + const char * const argv[] = { ifname }; + int e; + + if (action == -1) { + ifp = if_find(ctx->ifaces, ifname); + if (ifp == NULL) { + errno = ESRCH; + return -1; + } + if (ifp->active) { + logdebugx("%s: interface departed", ifp->name); + stop_interface(ifp, "DEPARTED"); + } + TAILQ_REMOVE(ctx->ifaces, ifp, next); + if_free(ifp); + return 0; + } + + ifs = if_discover(ctx, &ifaddrs, -1, UNCONST(argv)); + if (ifs == NULL) { + logerr(__func__); + return -1; + } + + ifp = if_find(ifs, ifname); + if (ifp == NULL) { + /* This can happen if an interface is quickly added + * and then removed. */ + errno = ENOENT; + e = -1; + goto out; + } + e = 1; + + /* Check if we already have the interface */ + iff = if_find(ctx->ifaces, ifp->name); + + if (iff != NULL) { + if (iff->active) + logdebugx("%s: interface updated", iff->name); + /* The flags and hwaddr could have changed */ + iff->flags = ifp->flags; + iff->hwlen = ifp->hwlen; + if (ifp->hwlen != 0) + memcpy(iff->hwaddr, ifp->hwaddr, iff->hwlen); + } else { + TAILQ_REMOVE(ifs, ifp, next); + TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); + if (ifp->active) { + logdebugx("%s: interface added", ifp->name); + dhcpcd_initstate(ifp, 0); + run_preinit(ifp); + } + iff = ifp; + } + + if (action > 0) { + if_learnaddrs(ctx, ifs, &ifaddrs); + if (iff->active) + dhcpcd_prestartinterface(iff); + } + +out: + /* Free our discovered list */ + while ((ifp = TAILQ_FIRST(ifs))) { + TAILQ_REMOVE(ifs, ifp, next); + if_free(ifp); + } + free(ifs); + + return e; +} + +static void +dhcpcd_handlelink(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (if_handlelink(ctx) == -1) { + if (errno == ENOBUFS || errno == ENOMEM) { + dhcpcd_linkoverflow(ctx); + return; + } + if (errno != ENOTSUP) + logerr(__func__); + } +} + +static void +dhcpcd_checkcarrier(void *arg) +{ + struct interface *ifp0 = arg, *ifp; + + ifp = if_find(ifp0->ctx->ifaces, ifp0->name); + if (ifp == NULL || ifp->carrier == ifp0->carrier) + return; + + dhcpcd_handlecarrier(ifp, ifp0->carrier, ifp0->flags); + if_free(ifp0); +} + +#ifndef SMALL +static void +dhcpcd_setlinkrcvbuf(struct dhcpcd_ctx *ctx) +{ + socklen_t socklen; + + if (ctx->link_rcvbuf == 0) + return; + + logdebugx("setting route socket receive buffer size to %d bytes", + ctx->link_rcvbuf); + + socklen = sizeof(ctx->link_rcvbuf); + if (setsockopt(ctx->link_fd, SOL_SOCKET, + SO_RCVBUF, &ctx->link_rcvbuf, socklen) == -1) + logerr(__func__); +} +#endif + +static void +dhcpcd_runprestartinterface(void *arg) +{ + struct interface *ifp = arg; + + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); +} + +void +dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx) +{ + socklen_t socklen; + int rcvbuflen; + char buf[2048]; + ssize_t rlen; + size_t rcnt; + struct if_head *ifaces; + struct ifaddrs *ifaddrs; + struct interface *ifp, *ifn, *ifp1; + + socklen = sizeof(rcvbuflen); + if (getsockopt(ctx->link_fd, SOL_SOCKET, + SO_RCVBUF, &rcvbuflen, &socklen) == -1) { + logerr("%s: getsockopt", __func__); + rcvbuflen = 0; + } +#ifdef __linux__ + else + rcvbuflen /= 2; +#endif + + logerrx("route socket overflowed (rcvbuflen %d)" + " - learning interface state", rcvbuflen); + + /* Drain the socket. + * We cannot open a new one due to privsep. */ + rcnt = 0; + do { + rlen = read(ctx->link_fd, buf, sizeof(buf)); + if (++rcnt % 1000 == 0) + logwarnx("drained %zu messages", rcnt); + } while (rlen != -1 || errno == ENOBUFS || errno == ENOMEM); + if (rcnt % 1000 != 0) + logwarnx("drained %zu messages", rcnt); + + /* Work out the current interfaces. */ + ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv); + if (ifaces == NULL) { + logerr(__func__); + return; + } + + /* Punt departed interfaces */ + TAILQ_FOREACH_SAFE(ifp, ctx->ifaces, next, ifn) { + if (if_find(ifaces, ifp->name) != NULL) + continue; + dhcpcd_handleinterface(ctx, -1, ifp->name); + } + + /* Add new interfaces */ + while ((ifp = TAILQ_FIRST(ifaces)) != NULL ) { + TAILQ_REMOVE(ifaces, ifp, next); + ifp1 = if_find(ctx->ifaces, ifp->name); + if (ifp1 != NULL) { + /* If the interface already exists, + * check carrier state. + * dhcpcd_checkcarrier will free ifp. */ + eloop_timeout_add_sec(ctx->eloop, 0, + dhcpcd_checkcarrier, ifp); + continue; + } + TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); + if (ifp->active) { + dhcpcd_initstate(ifp, 0); + eloop_timeout_add_sec(ctx->eloop, 0, + dhcpcd_runprestartinterface, ifp); + } + } + free(ifaces); + + /* Update address state. */ + if_markaddrsstale(ctx->ifaces); + if_learnaddrs(ctx, ctx->ifaces, &ifaddrs); + if_deletestaleaddrs(ctx->ifaces); +} + +void +dhcpcd_handlehwaddr(struct interface *ifp, + uint16_t hwtype, const void *hwaddr, uint8_t hwlen) +{ + char buf[sizeof(ifp->hwaddr) * 3]; + + if (hwaddr == NULL || !if_valid_hwaddr(hwaddr, hwlen)) + hwlen = 0; + + if (hwlen > sizeof(ifp->hwaddr)) { + errno = ENOBUFS; + logerr("%s: %s", __func__, ifp->name); + return; + } + + if (ifp->hwtype != hwtype) { + if (ifp->active) + loginfox("%s: hardware address type changed" + " from %d to %d", ifp->name, ifp->hwtype, hwtype); + ifp->hwtype = hwtype; + } + + if (ifp->hwlen == hwlen && + (hwlen == 0 || memcmp(ifp->hwaddr, hwaddr, hwlen) == 0)) + return; + + if (ifp->active) { + loginfox("%s: old hardware address: %s", ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf))); + loginfox("%s: new hardware address: %s", ifp->name, + hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf))); + } + ifp->hwlen = hwlen; + if (hwaddr != NULL) + memcpy(ifp->hwaddr, hwaddr, hwlen); +} + +static void +if_reboot(struct interface *ifp, int argc, char **argv) +{ +#ifdef INET + unsigned long long oldopts; + + oldopts = ifp->options->options; +#endif + script_runreason(ifp, "RECONFIGURE"); + dhcpcd_initstate1(ifp, argc, argv, 0); +#ifdef INET + dhcp_reboot_newopts(ifp, oldopts); +#endif +#ifdef DHCP6 + dhcp6_reboot(ifp); +#endif + dhcpcd_prestartinterface(ifp); +} + +static void +reload_config(struct dhcpcd_ctx *ctx) +{ + struct if_options *ifo; + + free_globals(ctx); + if ((ifo = read_config(ctx, NULL, NULL, NULL)) == NULL) + return; + add_options(ctx, NULL, ifo, ctx->argc, ctx->argv); + /* We need to preserve these options. */ + if (ctx->options & DHCPCD_STARTED) + ifo->options |= DHCPCD_STARTED; + if (ctx->options & DHCPCD_MANAGER) + ifo->options |= DHCPCD_MANAGER; + if (ctx->options & DHCPCD_DAEMONISED) + ifo->options |= DHCPCD_DAEMONISED; + if (ctx->options & DHCPCD_PRIVSEP) + ifo->options |= DHCPCD_PRIVSEP; + ctx->options = ifo->options; + free_options(ctx, ifo); +} + +static void +reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi) +{ + int i; + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + for (i = oi; i < argc; i++) { + if (strcmp(ifp->name, argv[i]) == 0) + break; + } + if (oi != argc && i == argc) + continue; + if (ifp->active == IF_ACTIVE_USER) { + if (action) + if_reboot(ifp, argc, argv); +#ifdef INET + else + ipv4_applyaddr(ifp); +#endif + } else if (i != argc) { + ifp->active = IF_ACTIVE_USER; + dhcpcd_initstate1(ifp, argc, argv, 0); + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); + } + } +} + +static void +stop_all_interfaces(struct dhcpcd_ctx *ctx, unsigned long long opts) +{ + struct interface *ifp; + + ctx->options |= DHCPCD_EXITING; + if (ctx->ifaces == NULL) + return; + + /* Drop the last interface first */ + TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { + if (!ifp->active) + continue; + ifp->options->options |= opts; + if (ifp->options->options & DHCPCD_RELEASE) + ifp->options->options &= ~DHCPCD_PERSISTENT; + ifp->options->options |= DHCPCD_EXITING; + stop_interface(ifp, NULL); + } +} + +static void +dhcpcd_ifrenew(struct interface *ifp) +{ + + if (!ifp->active) + return; + + if (ifp->options->options & DHCPCD_LINK && !if_is_link_up(ifp)) + return; + +#ifdef INET + dhcp_renew(ifp); +#endif +#ifdef INET6 +#define DHCPCD_RARENEW (DHCPCD_IPV6 | DHCPCD_IPV6RS) + if ((ifp->options->options & DHCPCD_RARENEW) == DHCPCD_RARENEW) + ipv6nd_startrs(ifp); +#endif +#ifdef DHCP6 + dhcp6_renew(ifp); +#endif +} + +static void +dhcpcd_renew(struct dhcpcd_ctx *ctx) +{ + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + dhcpcd_ifrenew(ifp); + } +} + +#ifdef USE_SIGNALS +#define sigmsg "received %s, %s" +static void +dhcpcd_signal_cb(int sig, void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + unsigned long long opts; + int exit_code; + + if (ctx->options & DHCPCD_DUMPLEASE) { + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + if (sig != SIGCHLD && ctx->options & DHCPCD_FORKED) { + if (sig != SIGHUP && + write(ctx->fork_fd, &sig, sizeof(sig)) == -1) + logerr("%s: write", __func__); + return; + } + + opts = 0; + exit_code = EXIT_FAILURE; + switch (sig) { + case SIGINT: + loginfox(sigmsg, "SIGINT", "stopping"); + break; + case SIGTERM: + loginfox(sigmsg, "SIGTERM", "stopping"); + exit_code = EXIT_SUCCESS; + break; + case SIGALRM: + loginfox(sigmsg, "SIGALRM", "releasing"); + opts |= DHCPCD_RELEASE; + exit_code = EXIT_SUCCESS; + break; + case SIGHUP: + loginfox(sigmsg, "SIGHUP", "rebinding"); + reload_config(ctx); + /* Preserve any options passed on the commandline + * when we were started. */ + reconf_reboot(ctx, 1, ctx->argc, ctx->argv, + ctx->argc - ctx->ifc); + return; + case SIGUSR1: + loginfox(sigmsg, "SIGUSR1", "renewing"); + dhcpcd_renew(ctx); + return; + case SIGUSR2: + loginfox(sigmsg, "SIGUSR2", "reopening log"); +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) { + if (ps_root_logreopen(ctx) == -1) + logerr("ps_root_logreopen"); + return; + } +#endif + if (logopen(ctx->logfile) == -1) + logerr("logopen"); + return; + case SIGCHLD: + while (waitpid(-1, NULL, WNOHANG) > 0) + ; + return; + default: + logerrx("received signal %d but don't know what to do with it", + sig); + return; + } + + if (!(ctx->options & DHCPCD_TEST)) + stop_all_interfaces(ctx, opts); + eloop_exit(ctx->eloop, exit_code); +} +#endif + +int +dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, + int argc, char **argv) +{ + struct interface *ifp; + unsigned long long opts; + int opt, oi, do_reboot, do_renew, af = AF_UNSPEC; + size_t len, l, nifaces; + char *tmp, *p; + + /* Special commands for our control socket + * as the other end should be blocking until it gets the + * expected reply we should be safely able just to change the + * write callback on the fd */ + /* Make any change here in privsep-control.c as well. */ + if (strcmp(*argv, "--version") == 0) { + return control_queue(fd, UNCONST(VERSION), + strlen(VERSION) + 1); + } else if (strcmp(*argv, "--getconfigfile") == 0) { + return control_queue(fd, UNCONST(fd->ctx->cffile), + strlen(fd->ctx->cffile) + 1); + } else if (strcmp(*argv, "--getinterfaces") == 0) { + optind = argc = 0; + goto dumplease; + } else if (strcmp(*argv, "--listen") == 0) { + fd->flags |= FD_LISTEN; + return 0; + } + + /* Log the command */ + len = 1; + for (opt = 0; opt < argc; opt++) + len += strlen(argv[opt]) + 1; + tmp = malloc(len); + if (tmp == NULL) + return -1; + p = tmp; + for (opt = 0; opt < argc; opt++) { + l = strlen(argv[opt]); + strlcpy(p, argv[opt], len); + len -= l + 1; + p += l; + *p++ = ' '; + } + *--p = '\0'; + loginfox("control command: %s", tmp); + free(tmp); + + optind = 0; + oi = 0; + opts = 0; + do_reboot = do_renew = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + switch (opt) { + case 'g': + /* Assumed if below not set */ + break; + case 'k': + opts |= DHCPCD_RELEASE; + break; + case 'n': + do_reboot = 1; + break; + case 'p': + opts |= DHCPCD_PERSISTENT; + break; + case 'x': + opts |= DHCPCD_EXITING; + break; + case 'N': + do_renew = 1; + break; + case 'U': + opts |= DHCPCD_DUMPLEASE; + break; + case '4': + af = AF_INET; + break; + case '6': + af = AF_INET6; + break; + } + } + + if (opts & DHCPCD_DUMPLEASE) { + ctx->options |= DHCPCD_DUMPLEASE; +dumplease: + nifaces = 0; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (!ifp->active) + continue; + for (oi = optind; oi < argc; oi++) { + if (strcmp(ifp->name, argv[oi]) == 0) + break; + } + if (optind == argc || oi < argc) { + opt = send_interface(NULL, ifp, af); + if (opt == -1) + goto dumperr; + nifaces += (size_t)opt; + } + } + if (write(fd->fd, &nifaces, sizeof(nifaces)) != sizeof(nifaces)) + goto dumperr; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (!ifp->active) + continue; + for (oi = optind; oi < argc; oi++) { + if (strcmp(ifp->name, argv[oi]) == 0) + break; + } + if (optind == argc || oi < argc) { + if (send_interface(fd, ifp, af) == -1) + goto dumperr; + } + } + ctx->options &= ~DHCPCD_DUMPLEASE; + return 0; +dumperr: + ctx->options &= ~DHCPCD_DUMPLEASE; + return -1; + } + + /* Only privileged users can control dhcpcd via the socket. */ + if (fd->flags & FD_UNPRIV) { + errno = EPERM; + return -1; + } + + if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { + if (optind == argc) { + stop_all_interfaces(ctx, opts); + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return 0; + } + for (oi = optind; oi < argc; oi++) { + if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) + continue; + if (!ifp->active) + continue; + ifp->options->options |= opts; + if (opts & DHCPCD_RELEASE) + ifp->options->options &= ~DHCPCD_PERSISTENT; + stop_interface(ifp, NULL); + } + return 0; + } + + if (do_renew) { + if (optind == argc) { + dhcpcd_renew(ctx); + return 0; + } + for (oi = optind; oi < argc; oi++) { + if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) + continue; + dhcpcd_ifrenew(ifp); + } + return 0; + } + + reload_config(ctx); + /* XXX: Respect initial commandline options? */ + reconf_reboot(ctx, do_reboot, argc, argv, optind - 1); + return 0; +} + +static void dhcpcd_readdump1(void *); + +static void +dhcpcd_readdump2(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + int exit_code = EXIT_FAILURE; + + len = read(ctx->control_fd, ctx->ctl_buf + ctx->ctl_bufpos, + ctx->ctl_buflen - ctx->ctl_bufpos); + if (len == -1) { + logerr(__func__); + goto finished; + } else if (len == 0) + goto finished; + if ((size_t)len + ctx->ctl_bufpos != ctx->ctl_buflen) { + ctx->ctl_bufpos += (size_t)len; + return; + } + + if (ctx->ctl_buf[ctx->ctl_buflen - 1] != '\0') /* unlikely */ + ctx->ctl_buf[ctx->ctl_buflen - 1] = '\0'; + script_dump(ctx->ctl_buf, ctx->ctl_buflen); + fflush(stdout); + if (--ctx->ctl_extra != 0) { + putchar('\n'); + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump1, ctx); + return; + } + exit_code = EXIT_SUCCESS; + +finished: + shutdown(ctx->control_fd, SHUT_RDWR); + eloop_exit(ctx->eloop, exit_code); +} + +static void +dhcpcd_readdump1(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + + len = read(ctx->control_fd, &ctx->ctl_buflen, sizeof(ctx->ctl_buflen)); + if (len != sizeof(ctx->ctl_buflen)) { + if (len != -1) + errno = EINVAL; + goto err; + } + if (ctx->ctl_buflen > SSIZE_MAX) { + errno = ENOBUFS; + goto err; + } + + free(ctx->ctl_buf); + ctx->ctl_buf = malloc(ctx->ctl_buflen); + if (ctx->ctl_buf == NULL) + goto err; + + ctx->ctl_bufpos = 0; + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump2, ctx); + return; + +err: + logerr(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static void +dhcpcd_readdump0(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + + len = read(ctx->control_fd, &ctx->ctl_extra, sizeof(ctx->ctl_extra)); + if (len != sizeof(ctx->ctl_extra)) { + if (len != -1) + errno = EINVAL; + logerr(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + if (ctx->ctl_extra == 0) { + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump1, ctx); +} + +static void +dhcpcd_readdumptimeout(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + logerrx(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static int +dhcpcd_readdump(struct dhcpcd_ctx *ctx) +{ + + ctx->options |= DHCPCD_FORKED; + if (eloop_timeout_add_sec(ctx->eloop, 5, + dhcpcd_readdumptimeout, ctx) == -1) + return -1; + return eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump0, ctx); +} + +static void +dhcpcd_fork_cb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + int exit_code; + ssize_t len; + + len = read(ctx->fork_fd, &exit_code, sizeof(exit_code)); + if (len == -1) { + logerr(__func__); + exit_code = EXIT_FAILURE; + } else if ((size_t)len < sizeof(exit_code)) { + logerrx("%s: truncated read %zd (expected %zu)", + __func__, len, sizeof(exit_code)); + exit_code = EXIT_FAILURE; + } + if (ctx->options & DHCPCD_FORKED) + eloop_exit(ctx->eloop, exit_code); + else + dhcpcd_signal_cb(exit_code, ctx); +} + +static void +dhcpcd_stderr_cb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char log[BUFSIZ]; + ssize_t len; + + len = read(ctx->stderr_fd, log, sizeof(log)); + if (len == -1) { + if (errno != ECONNRESET) + logerr(__func__); + return; + } + + log[len] = '\0'; + fprintf(stderr, "%s", log); +} + +int +main(int argc, char **argv, char **envp) +{ + struct dhcpcd_ctx ctx; + struct ifaddrs *ifaddrs = NULL; + struct if_options *ifo; + struct interface *ifp; + sa_family_t family = AF_UNSPEC; + int opt, oi = 0, i; + unsigned int logopts, t; + ssize_t len; +#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) + pid_t pid; + int fork_fd[2], stderr_fd[2]; +#endif +#ifdef USE_SIGNALS + int sig = 0; + const char *siga = NULL; + size_t si; +#endif + +#ifdef SETPROCTITLE_H + setproctitle_init(argc, argv, envp); +#else + UNUSED(envp); +#endif + + /* Test for --help and --version */ + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0) { + usage(); + return EXIT_SUCCESS; + } else if (strcmp(argv[1], "--version") == 0) { + printf(""PACKAGE" "VERSION"\n%s\n", dhcpcd_copyright); + printf("Compiled in features:" +#ifdef INET + " INET" +#endif +#ifdef ARP + " ARP" +#endif +#ifdef ARPING + " ARPing" +#endif +#ifdef IPV4LL + " IPv4LL" +#endif +#ifdef INET6 + " INET6" +#endif +#ifdef DHCP6 + " DHCPv6" +#endif +#ifdef AUTH + " AUTH" +#endif +#ifdef PRIVSEP + " PRIVSEP" +#endif + "\n"); + return EXIT_SUCCESS; + } + } + + memset(&ctx, 0, sizeof(ctx)); + + ifo = NULL; + ctx.cffile = CONFIG; + ctx.script = UNCONST(dhcpcd_default_script); + ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; + ctx.pf_inet_fd = -1; +#ifdef PF_LINK + ctx.pf_link_fd = -1; +#endif + + TAILQ_INIT(&ctx.control_fds); +#ifdef USE_SIGNALS + ctx.fork_fd = -1; +#endif +#ifdef PLUGIN_DEV + ctx.dev_fd = -1; +#endif +#ifdef INET + ctx.udp_rfd = -1; + ctx.udp_wfd = -1; +#endif +#if defined(INET6) && !defined(__sun) + ctx.nd_fd = -1; +#endif +#ifdef DHCP6 + ctx.dhcp6_rfd = -1; + ctx.dhcp6_wfd = -1; +#endif +#ifdef PRIVSEP + ctx.ps_root_fd = ctx.ps_log_fd = ctx.ps_data_fd = -1; + ctx.ps_inet_fd = ctx.ps_control_fd = -1; + TAILQ_INIT(&ctx.ps_processes); +#endif + + /* Check our streams for validity */ + ctx.stdin_valid = fcntl(STDIN_FILENO, F_GETFD) != -1; + ctx.stdout_valid = fcntl(STDOUT_FILENO, F_GETFD) != -1; + ctx.stderr_valid = fcntl(STDERR_FILENO, F_GETFD) != -1; + + logopts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID; + if (ctx.stderr_valid) + logopts |= LOGERR_ERR; + + i = 0; + + while ((opt = getopt_long(argc, argv, + ctx.options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : IF_OPTS, + cf_options, &oi)) != -1) + { + switch (opt) { + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; + case 'f': + ctx.cffile = optarg; + break; + case 'j': + free(ctx.logfile); + ctx.logfile = strdup(optarg); + break; +#ifdef USE_SIGNALS + case 'k': + sig = SIGALRM; + siga = "ALRM"; + break; + case 'n': + sig = SIGHUP; + siga = "HUP"; + break; + case 'g': + case 'p': + /* Force going via command socket as we're + * out of user definable signals. */ + i = 4; + break; + case 'q': + /* -qq disables console output entirely. + * This is important for systemd because it logs + * both console AND syslog to the same log + * resulting in untold confusion. */ + if (logopts & LOGERR_QUIET) + logopts &= ~LOGERR_ERR; + else + logopts |= LOGERR_QUIET; + break; + case 'x': + sig = SIGTERM; + siga = "TERM"; + break; + case 'N': + sig = SIGUSR1; + siga = "USR1"; + break; +#endif + case 'P': + ctx.options |= DHCPCD_PRINT_PIDFILE; + logopts &= ~(LOGERR_LOG | LOGERR_ERR); + break; + case 'T': + i = 1; + logopts &= ~LOGERR_LOG; + break; + case 'U': + i = 3; + break; + case 'V': + i = 2; + break; + case '?': + if (ctx.options & DHCPCD_PRINT_PIDFILE) + continue; + usage(); + goto exit_failure; + } + } + + if (optind != argc - 1) + ctx.options |= DHCPCD_MANAGER; + + logsetopts(logopts); + logopen(ctx.logfile); + + ctx.argv = argv; + ctx.argc = argc; + ctx.ifc = argc - optind; + ctx.ifv = argv + optind; + + rt_init(&ctx); + + ifo = read_config(&ctx, NULL, NULL, NULL); + if (ifo == NULL) { + if (ctx.options & DHCPCD_PRINT_PIDFILE) + goto printpidfile; + goto exit_failure; + } + + opt = add_options(&ctx, NULL, ifo, argc, argv); + if (opt != 1) { + if (ctx.options & DHCPCD_PRINT_PIDFILE) + goto printpidfile; + if (opt == 0) + usage(); + goto exit_failure; + } + if (i == 2) { + printf("Interface options:\n"); + if (optind == argc - 1) { + free_options(&ctx, ifo); + ifo = read_config(&ctx, argv[optind], NULL, NULL); + if (ifo == NULL) + goto exit_failure; + add_options(&ctx, NULL, ifo, argc, argv); + } + if_printoptions(); +#ifdef INET + if (family == 0 || family == AF_INET) { + printf("\nDHCPv4 options:\n"); + dhcp_printoptions(&ctx, + ifo->dhcp_override, ifo->dhcp_override_len); + } +#endif +#ifdef INET6 + if (family == 0 || family == AF_INET6) { + printf("\nND options:\n"); + ipv6nd_printoptions(&ctx, + ifo->nd_override, ifo->nd_override_len); +#ifdef DHCP6 + printf("\nDHCPv6 options:\n"); + dhcp6_printoptions(&ctx, + ifo->dhcp6_override, ifo->dhcp6_override_len); +#endif + } +#endif + goto exit_success; + } + ctx.options |= ifo->options; + + if (i == 1 || i == 3) { + if (i == 1) + ctx.options |= DHCPCD_TEST; + else + ctx.options |= DHCPCD_DUMPLEASE; + ctx.options |= DHCPCD_PERSISTENT; + ctx.options &= ~DHCPCD_DAEMONISE; + } + +#ifdef THERE_IS_NO_FORK + ctx.options &= ~DHCPCD_DAEMONISE; +#endif + + if (ctx.options & DHCPCD_DEBUG) + logsetopts(logopts | LOGERR_DEBUG); + + if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { +printpidfile: + /* If we have any other args, we should run as a single dhcpcd + * instance for that interface. */ + if (optind == argc - 1 && !(ctx.options & DHCPCD_MANAGER)) { + const char *per; + const char *ifname; + + ifname = *ctx.ifv; + if (ifname == NULL || strlen(ifname) > IF_NAMESIZE) { + errno = ifname == NULL ? EINVAL : E2BIG; + logerr("%s: ", ifname); + goto exit_failure; + } + /* Allow a dhcpcd interface per address family */ + switch(family) { + case AF_INET: + per = "-4"; + break; + case AF_INET6: + per = "-6"; + break; + default: + per = ""; + } + snprintf(ctx.pidfile, sizeof(ctx.pidfile), + PIDFILE, ifname, per, "."); + } else { + snprintf(ctx.pidfile, sizeof(ctx.pidfile), + PIDFILE, "", "", ""); + ctx.options |= DHCPCD_MANAGER; + } + if (ctx.options & DHCPCD_PRINT_PIDFILE) { + printf("%s\n", ctx.pidfile); + goto exit_success; + } + } + + if (chdir("/") == -1) + logerr("%s: chdir: /", __func__); + + /* Freeing allocated addresses from dumping leases can trigger + * eloop removals as well, so init here. */ + if ((ctx.eloop = eloop_new()) == NULL) { + logerr("%s: eloop_init", __func__); + goto exit_failure; + } + +#ifdef USE_SIGNALS + for (si = 0; si < dhcpcd_signals_ignore_len; si++) + signal(dhcpcd_signals_ignore[si], SIG_IGN); + + /* Save signal mask, block and redirect signals to our handler */ + eloop_signal_set_cb(ctx.eloop, + dhcpcd_signals, dhcpcd_signals_len, + dhcpcd_signal_cb, &ctx); + if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) { + logerr("%s: eloop_signal_mask", __func__); + goto exit_failure; + } + + if (sig != 0) { + pid = pidfile_read(ctx.pidfile); + if (pid != 0 && pid != -1) + loginfox("sending signal %s to pid %d", siga, pid); + if (pid == 0 || pid == -1 || kill(pid, sig) != 0) { + if (sig != SIGHUP && sig != SIGUSR1 && errno != EPERM) + logerrx(PACKAGE" not running"); + if (pid != 0 && pid != -1 && errno != ESRCH) { + logerr("kill"); + goto exit_failure; + } + unlink(ctx.pidfile); + if (sig != SIGHUP && sig != SIGUSR1) + goto exit_failure; + } else { + struct timespec ts; + + if (sig == SIGHUP || sig == SIGUSR1) + goto exit_success; + /* Spin until it exits */ + loginfox("waiting for pid %d to exit", pid); + ts.tv_sec = 0; + ts.tv_nsec = 100000000; /* 10th of a second */ + for(i = 0; i < 100; i++) { + nanosleep(&ts, NULL); + if (pidfile_read(ctx.pidfile) == -1) + goto exit_success; + } + logerrx("pid %d failed to exit", pid); + goto exit_failure; + } + } +#endif + +#ifdef PRIVSEP + ps_init(&ctx); +#endif + +#ifndef SMALL + if (ctx.options & DHCPCD_DUMPLEASE && + ioctl(fileno(stdin), FIONREAD, &i, sizeof(i)) == 0 && + i > 0) + { + ctx.options |= DHCPCD_FORKED; /* pretend child process */ +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) + goto exit_failure; +#endif + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) { + logerr(__func__); + goto exit_failure; + } + ifp->ctx = &ctx; + ifp->options = ifo; + switch (family) { + case AF_INET: +#ifdef INET + if (dhcp_dump(ifp) == -1) + goto exit_failure; + break; +#else + logerrx("No DHCP support"); + goto exit_failure; +#endif + case AF_INET6: +#ifdef DHCP6 + if (dhcp6_dump(ifp) == -1) + goto exit_failure; + break; +#else + logerrx("No DHCP6 support"); + goto exit_failure; +#endif + default: + logerrx("Family not specified. Please use -4 or -6."); + goto exit_failure; + } + goto exit_success; + } +#endif + + /* Test against siga instead of sig to avoid gcc + * warning about a bogus potential signed overflow. + * The end result will be the same. */ + if ((siga == NULL || i == 4 || ctx.ifc != 0) && + !(ctx.options & DHCPCD_TEST)) + { + ctx.options |= DHCPCD_FORKED; /* avoid socket unlink */ + if (!(ctx.options & DHCPCD_MANAGER)) + ctx.control_fd = control_open(argv[optind], family, + ctx.options & DHCPCD_DUMPLEASE); + if (!(ctx.options & DHCPCD_MANAGER) && ctx.control_fd == -1) + ctx.control_fd = control_open(argv[optind], AF_UNSPEC, + ctx.options & DHCPCD_DUMPLEASE); + if (ctx.control_fd == -1) + ctx.control_fd = control_open(NULL, AF_UNSPEC, + ctx.options & DHCPCD_DUMPLEASE); + if (ctx.control_fd != -1) { +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && + ps_managersandbox(&ctx, NULL) == -1) + goto exit_failure; +#endif + if (!(ctx.options & DHCPCD_DUMPLEASE)) + loginfox("sending commands to dhcpcd process"); + len = control_send(&ctx, argc, argv); + if (len > 0) + logdebugx("send OK"); + else { + logerr("%s: control_send", __func__); + goto exit_failure; + } + if (ctx.options & DHCPCD_DUMPLEASE) { + if (dhcpcd_readdump(&ctx) == -1) { + logerr("%s: dhcpcd_readdump", __func__); + goto exit_failure; + } + goto run_loop; + } + goto exit_success; + } else { + if (errno != ENOENT) + logerr("%s: control_open", __func__); + if (ctx.options & DHCPCD_DUMPLEASE) { + if (errno == ENOENT) + logerrx("dhcpcd is not running"); + goto exit_failure; + } + if (errno == EPERM || errno == EACCES) + goto exit_failure; + } + ctx.options &= ~DHCPCD_FORKED; + } + + if (!(ctx.options & DHCPCD_TEST)) { + /* Ensure we have the needed directories */ + if (mkdir(DBDIR, 0750) == -1 && errno != EEXIST) + logerr("%s: mkdir: %s", __func__, DBDIR); + if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) + logerr("%s: mkdir: %s", __func__, RUNDIR); + if ((pid = pidfile_lock(ctx.pidfile)) != 0) { + if (pid == -1) + logerr("%s: pidfile_lock: %s", + __func__, ctx.pidfile); + else + logerrx(PACKAGE + " already running on pid %d (%s)", + pid, ctx.pidfile); + goto exit_failure; + } + } + + loginfox(PACKAGE "-" VERSION " starting"); + if (ctx.stdin_valid && freopen(_PATH_DEVNULL, "w", stdin) == NULL) + logwarn("freopen stdin"); + +#if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK) + if (!(ctx.options & DHCPCD_DAEMONISE)) + goto start_manager; + + if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fork_fd) == -1 || + (ctx.stderr_valid && + xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, stderr_fd) == -1)) + { + logerr("socketpair"); + goto exit_failure; + } + switch (pid = fork()) { + case -1: + logerr("fork"); + goto exit_failure; + case 0: + ctx.fork_fd = fork_fd[1]; + close(fork_fd[0]); +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd(ctx.fork_fd) == -1) { + logerr("ps_rights_limit_fdpair"); + goto exit_failure; + } +#endif + eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); + + /* + * Redirect stderr to the stderr socketpair. + * Redirect stdout as well. + * dhcpcd doesn't output via stdout, but something in + * a called script might. + */ + if (ctx.stderr_valid) { + if (dup2(stderr_fd[1], STDERR_FILENO) == -1 || + (ctx.stdout_valid && + dup2(stderr_fd[1], STDOUT_FILENO) == -1)) + logerr("dup2"); + close(stderr_fd[0]); + close(stderr_fd[1]); + } else if (ctx.stdout_valid) { + if (freopen(_PATH_DEVNULL, "w", stdout) == NULL) + logerr("freopen stdout"); + } + if (setsid() == -1) { + logerr("%s: setsid", __func__); + goto exit_failure; + } + /* Ensure we can never get a controlling terminal */ + switch (pid = fork()) { + case -1: + logerr("fork"); + goto exit_failure; + case 0: + break; + default: + ctx.options |= DHCPCD_FORKED; /* A lie */ + i = EXIT_SUCCESS; + goto exit1; + } + break; + default: + setproctitle("[launcher]"); + ctx.options |= DHCPCD_FORKED | DHCPCD_LAUNCHER; + ctx.fork_fd = fork_fd[0]; + close(fork_fd[1]); +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd(ctx.fork_fd) == -1) { + logerr("ps_rights_limit_fd"); + goto exit_failure; + } +#endif + eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); + + if (ctx.stderr_valid) { + ctx.stderr_fd = stderr_fd[0]; + close(stderr_fd[1]); +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd(ctx.stderr_fd) == 1) { + logerr("ps_rights_limit_fd"); + goto exit_failure; + } +#endif + eloop_event_add(ctx.eloop, ctx.stderr_fd, + dhcpcd_stderr_cb, &ctx); + } +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) + goto exit_failure; +#endif + goto run_loop; + } + + /* We have now forked, setsid, forked once more. + * From this point on, we are the controlling daemon. */ + logdebugx("spawned manager process on PID %d", getpid()); +start_manager: + ctx.options |= DHCPCD_STARTED; + if ((pid = pidfile_lock(ctx.pidfile)) != 0) { + logerr("%s: pidfile_lock %d", __func__, pid); +#ifdef PRIVSEP + /* privsep has not started ... */ + ctx.options &= ~DHCPCD_PRIVSEP; +#endif + goto exit_failure; + } +#endif + + os_init(); + +#if defined(BSD) && defined(INET6) + /* Disable the kernel RTADV sysctl as early as possible. */ + if (ctx.options & DHCPCD_IPV6 && ctx.options & DHCPCD_IPV6RS) + if_disable_rtadv(); +#endif + +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_start(&ctx) == -1) { + logerr("ps_start"); + goto exit_failure; + } + if (ctx.options & DHCPCD_FORKED) + goto run_loop; +#endif + + if (!(ctx.options & DHCPCD_TEST)) { + if (control_start(&ctx, + ctx.options & DHCPCD_MANAGER ? + NULL : argv[optind], family) == -1) + { + logerr("%s: control_start", __func__); + goto exit_failure; + } + } + +#ifdef PLUGIN_DEV + /* Start any dev listening plugin which may want to + * change the interface name provided by the kernel */ + if (!IN_PRIVSEP(&ctx) && + (ctx.options & (DHCPCD_MANAGER | DHCPCD_DEV)) == + (DHCPCD_MANAGER | DHCPCD_DEV)) + dev_start(&ctx, dhcpcd_handleinterface); +#endif + + setproctitle("%s%s%s", + ctx.options & DHCPCD_MANAGER ? "[manager]" : argv[optind], + ctx.options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx.options & DHCPCD_IPV6 ? " [ip6]" : ""); + + if (if_opensockets(&ctx) == -1) { + logerr("%s: if_opensockets", __func__); + goto exit_failure; + } +#ifndef SMALL + dhcpcd_setlinkrcvbuf(&ctx); +#endif + + /* Try and create DUID from the machine UUID. */ + dhcpcd_initduid(&ctx, NULL); + + /* Cache the default vendor option. */ + if (dhcp_vendor(ctx.vendor, sizeof(ctx.vendor)) == -1) + logerr("dhcp_vendor"); + + /* Start handling kernel messages for interfaces, addresses and + * routes. */ + eloop_event_add(ctx.eloop, ctx.link_fd, dhcpcd_handlelink, &ctx); + +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, "stdio route") == -1) + goto exit_failure; +#endif + + /* When running dhcpcd against a single interface, we need to retain + * the old behaviour of waiting for an IP address */ + if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND)) + ctx.options |= DHCPCD_WAITIP; + + ctx.ifaces = if_discover(&ctx, &ifaddrs, ctx.ifc, ctx.ifv); + if (ctx.ifaces == NULL) { + logerr("%s: if_discover", __func__); + goto exit_failure; + } + for (i = 0; i < ctx.ifc; i++) { + if ((ifp = if_find(ctx.ifaces, ctx.ifv[i])) == NULL) + logerrx("%s: interface not found", + ctx.ifv[i]); + else if (!ifp->active) + logerrx("%s: interface has an invalid configuration", + ctx.ifv[i]); + } + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + if (ifp->active == IF_ACTIVE_USER) + break; + } + if (ifp == NULL) { + if (ctx.ifc == 0) { + int loglevel; + + loglevel = ctx.options & DHCPCD_INACTIVE ? + LOG_DEBUG : LOG_ERR; + logmessage(loglevel, "no valid interfaces found"); + dhcpcd_daemonise(&ctx); + } else + goto exit_failure; + if (!(ctx.options & DHCPCD_LINK)) { + logerrx("aborting as link detection is disabled"); + goto exit_failure; + } + } + + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + if (ifp->active) + dhcpcd_initstate1(ifp, argc, argv, 0); + } + if_learnaddrs(&ctx, ctx.ifaces, &ifaddrs); + + if (ctx.options & DHCPCD_BACKGROUND) + dhcpcd_daemonise(&ctx); + + opt = 0; + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + if (ifp->active) { + run_preinit(ifp); + if (if_is_link_up(ifp)) + opt = 1; + } + } + + if (!(ctx.options & DHCPCD_BACKGROUND)) { + if (ctx.options & DHCPCD_MANAGER) + t = ifo->timeout; + else { + t = 0; + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + if (ifp->active) { + t = ifp->options->timeout; + break; + } + } + } + if (opt == 0 && + ctx.options & DHCPCD_LINK && + !(ctx.options & DHCPCD_WAITIP)) + { + int loglevel; + + loglevel = ctx.options & DHCPCD_INACTIVE ? + LOG_DEBUG : LOG_WARNING; + logmessage(loglevel, "no interfaces have a carrier"); + dhcpcd_daemonise(&ctx); + } else if (t > 0 && + /* Test mode removes the daemonise bit, so check for both */ + ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) + { + eloop_timeout_add_sec(ctx.eloop, t, + handle_exit_timeout, &ctx); + } + } + free_options(&ctx, ifo); + ifo = NULL; + + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + if (ifp->active) + eloop_timeout_add_sec(ctx.eloop, 0, + dhcpcd_prestartinterface, ifp); + } + +run_loop: + i = eloop_start(ctx.eloop, &ctx.sigset); + if (i < 0) { + logerr("%s: eloop_start", __func__); + goto exit_failure; + } + goto exit1; + +exit_success: + i = EXIT_SUCCESS; + goto exit1; + +exit_failure: + i = EXIT_FAILURE; + +exit1: + if (!(ctx.options & DHCPCD_TEST) && control_stop(&ctx) == -1) + logerr("%s: control_stop", __func__); + if (ifaddrs != NULL) { +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(&ctx)) + free(ifaddrs); + else +#endif + freeifaddrs(ifaddrs); + } + /* ps_stop will clear DHCPCD_PRIVSEP but we need to + * remember it to avoid attemping to remove the pidfile */ + oi = ctx.options & DHCPCD_PRIVSEP ? 1 : 0; +#ifdef PRIVSEP + ps_stop(&ctx); +#endif + /* Free memory and close fd's */ + if (ctx.ifaces) { + while ((ifp = TAILQ_FIRST(ctx.ifaces))) { + TAILQ_REMOVE(ctx.ifaces, ifp, next); + if_free(ifp); + } + free(ctx.ifaces); + ctx.ifaces = NULL; + } + free_options(&ctx, ifo); +#ifdef HAVE_OPEN_MEMSTREAM + if (ctx.script_fp) + fclose(ctx.script_fp); +#endif + free(ctx.script_buf); + free(ctx.script_env); + rt_dispose(&ctx); + free(ctx.duid); + if (ctx.link_fd != -1) { + eloop_event_delete(ctx.eloop, ctx.link_fd); + close(ctx.link_fd); + } + if_closesockets(&ctx); + free_globals(&ctx); +#ifdef INET6 + ipv6_ctxfree(&ctx); +#endif +#ifdef PLUGIN_DEV + dev_stop(&ctx); +#endif +#ifdef PRIVSEP + eloop_free(ctx.ps_eloop); +#endif + eloop_free(ctx.eloop); + if (ctx.script != dhcpcd_default_script) + free(ctx.script); + if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) + loginfox(PACKAGE " exited"); + logclose(); + free(ctx.logfile); + free(ctx.ctl_buf); +#ifdef SETPROCTITLE_H + setproctitle_fini(); +#endif +#ifdef USE_SIGNALS + if (ctx.options & DHCPCD_STARTED) { + /* Try to detach from the launch process. */ + if (ctx.fork_fd != -1 && + write(ctx.fork_fd, &i, sizeof(i)) == -1) + logerr("%s: write", __func__); + } + if (ctx.options & DHCPCD_FORKED || oi != 0) + _exit(i); /* so atexit won't remove our pidfile */ +#endif + return i; +} Index: src/dhcpcd.conf =================================================================== --- /dev/null +++ src/dhcpcd.conf @@ -0,0 +1,48 @@ +# A sample configuration for dhcpcd. +# See dhcpcd.conf(5) for details. + +# Allow users of this group to interact with dhcpcd via the control socket. +#controlgroup wheel + +# Inform the DHCP server of our hostname for DDNS. +#hostname + +# Use the hardware address of the interface for the Client ID. +#clientid +# or +# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361. +# Some non-RFC compliant DHCP servers do not reply with this set. +# In this case, comment out duid and enable clientid above. +duid + +# Persist interface configuration when dhcpcd exits. +persistent + +# vendorclassid is set to blank to avoid sending the default of +# dhcpcd-::: +vendorclassid + +# A list of options to request from the DHCP server. +option domain_name_servers, domain_name, domain_search +option classless_static_routes +# Respect the network MTU. This is applied to DHCP routes. +option interface_mtu + +# Request a hostname from the network +option host_name + +# Most distributions have NTP support. +#option ntp_servers + +# Rapid commit support. +# Safe to enable by default because it requires the equivalent option set +# on the server to actually work. +option rapid_commit + +# A ServerID is required by RFC2131. +require dhcp_server_identifier + +# Generate SLAAC address using the Hardware Address of the interface +#slaac hwaddr +# OR generate Stable Private IPv6 Addresses based from the DUID +slaac private Index: src/dhcpcd.conf.5.in =================================================================== --- /dev/null +++ src/dhcpcd.conf.5.in @@ -0,0 +1,1004 @@ +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2006-2021 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd August 23, 2021 +.Dt DHCPCD.CONF 5 +.Os +.Sh NAME +.Nm dhcpcd.conf +.Nd dhcpcd configuration file +.Sh DESCRIPTION +Although +.Nm dhcpcd +can do everything from the command line, there are cases where it's just easier +to do it once in a configuration file. +Most of the options found in +.Xr dhcpcd 8 +can be used here. +The first word on the line is the option and the rest of the line is the value. +Leading and trailing whitespace for the option and value are trimmed. +You can escape characters in the value using the \\ character. +Comments can be prefixed with the # character. +String values should be quoted with the " character. +.Pp +Here's a list of available options: +.Bl -tag -width indent +.It Ic allowinterfaces Ar pattern +When discovering interfaces, the interface name must match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +If the same interface is matched in +.Ic denyinterfaces +then it is still denied. +.It Ic denyinterfaces Ar pattern +When discovering interfaces, the interface name must not match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +.It Ic anonymous +Enables Anonymity Profiles for DHCP, RFC 7844. +Any DUID is ignored and ClientID is set to LL only. +All non essential options are then masked at this point, +but they could be unmasked by explicitly requesting the option +.Sy after +the +.Ic anonymous +option is processed. +As such, the +.Ic anonymous +option +.Sy should +be the last option in the configuration unless you really want to +send something which could identify you. +.Nm dhcpcd +will not try and reboot an old lease, it will go straight into +DISCOVER/SOLICIT. +.It Ic randomise_hwaddr +Forces a hardware address randomisation when the interface is brought up +or when the carrier is lost. +This is generally used in tandem with the anonymous option. +.It Ic arping Ar address Op address +.Nm dhcpcd +will arping each address in order before attempting DHCP. +If an address is found, we will select the replying hardware address as the +profile, otherwise the IP address. +Example: +.Pp +.D1 interface bge0 +.D1 arping 192.168.0.1 +.Pp +.D1 # My specific 192.168.0.1 network +.D1 profile dd:ee:aa:dd:bb:ee +.D1 static ip_address=192.168.0.10/24 +.Pp +.D1 # A generic 192.168.0.1 network +.D1 profile 192.168.0.1 +.D1 static ip_address=192.168.0.98/24 +.It Ic authprotocol Ar protocol Op Ar algorithm Op Ar rdm +Authenticate DHCP messages. +See the Supported Authentication Protocols section. +If +.Ar protocol +is +.Ar token +then +.Ar algorithm is +snd_secretid/rcv_secretid so you can send and receive different tokens. +.It Ic authtoken Ar secretid Ar realm Ar expire Ar key +Define a shared key for use in authentication. +.Ar realm +can be "" to for use with the +.Ar delayed +protocol. +.Ar expire +is the date the token expires and should be formatted "yyy-mm-dd HH:MM". +You can use the keyword +.Ar forever +or +.Ar 0 +which means the token never expires. +For the token protocol, +.Ar secretid +needs to be 0 and +.Ar realm +needs to be "". +If +.Nm dhcpcd +has the error +.D1 dhcp_auth_encode: Invalid argument +then it means that +.Nm dhcpcd +could not find the correct authentication token in your configuration. +.It Ic background +Fork to the background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.It Ic blacklist Ar address Ns Op /cidr +Ignores all packets from +.Ar address Ns Op /cidr . +.It Ic whitelist Ar address Ns Op /cidr +Only accept packets from +.Ar address Ns Op /cidr . +.Ic blacklist +is ignored if +.Ic whitelist +is set. +.It Ic bootp +Be a BOOTP client. +Basically, this just doesn't send a DHCP Message Type option and will only +interact with a BOOTP server. +All other DHCP options still work. +.It Ic broadcast +Instructs the DHCP server to broadcast replies back to the client. +Normally this is only set for non-Ethernet interfaces, +such as FireWire and InfiniBand. +In most cases, +.Nm dhcpcd +will set this automatically. +.It Ic controlgroup Ar group +Sets the group ownership of +.Pa @RUNDIR@/sock +so that users other than root can connect to +.Nm dhcpcd . +.It Ic debug +Echo debug messages to the stderr and syslog. +.It Ic dev Ar value +Load the +.Ar value +.Pa /dev +management module. +.Nm dhcpcd +will load the first one found to work, if any. +.It Ic env Ar value +Push +.Ar value +to the environment for use in +.Xr dhcpcd-run-hooks 8 . +For example, you can force the hostname hook to always set the hostname with +.Ic env +.Va force_hostname=YES . +Or set which driver +.Xr wpa_supplicant 8 +should use with +.Ic env +.Va wpa_supplicant_driver=nl80211 +.Pp +If the hostname is set, it will be will set to the FQDN if possible as per +RFC 4702, section 3.1. +If the FQDN option is missing, +.Nm dhcpcd +will still try and set a FQDN from the hostname and domain options for +consistency. +To override this, set +.Ic env +.Va hostname_fqdn=[YES|NO|SERVER] . +A value of +.Va SERVER +means just what the server says, don't manipulate it. +This could lead to an inconsistent hostname on a DHCPv4 and DHCPv6 network +where the DHCPv4 hostname is short and the DHCPv6 has an FQDN. +DHCPv6 has no hostname option. +.It Ic clientid Ar string +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm dhcpcd +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Ic duid Op ll | lt | uuid | value +Use a DHCP Unique Identifier. +If a system UUID is available, that will be used to create a DUID-UUID, +otheriwse if persistent storage is available then a DUID-LLT +(link local address + time) is generated, +otherwise DUID-LL is generated (link local address). +The DUID type can be hinted as an optional parameter if the file +.Pa @DBDIR@/duid +does not exist. +If not +.Va ll , +.Va lt +or +.Va uuid +then +.Va value +will be converted from 00:11:22:33 format. +This, plus the IAID will be used as the +.Ic clientid . +The DUID generated will be held in +.Pa @DBDIR@/duid +and should not be copied to other hosts. +This file also takes precedence over the above rules except for setting a value. +.It Ic iaid Ar iaid +Set the Interface Association Identifier to +.Ar iaid . +This option must be used in an +.Ic interface +block. +This defaults to the VLANID (prefixed with 0xff) for the interface if set, +otherwise the last 4 bytes of the hardware address assigned to the +interface. +Each instance of this should be unique within the scope of the client and +.Nm dhcpcd +warns if a conflict is detected. +If there is a conflict, it is only a problem if the conflicted IAIDs are +used on the same network. +.It Ic dhcp +Enable DHCP on the interface, on by default. +.It Ic dhcp6 +Enable DHCPv6 on the interface, on by default. +.It Ic ipv4 +Enable IPv4 on the interface, on by default. +.It Ic ipv6 +Enable IPv6 on the interface, on by default. +.It Ic request Op Ar address +Request the +.Ar address +in the DHCP DISCOVER message. +There is no guarantee this is the address the DHCP server will actually give. +If no +.Ar address +is given then the first address currently assigned to the +.Ar interface +is used. +.It Ic inform Op Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address +Behaves like +.Ic request +as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST. +This does not get a lease as such, just notifies the DHCP server of the +.Ar address +in use. +You should also include the optional +.Ar cidr +network number in case the address is not already configured on the interface. +.Nm dhcpcd +remains running and pretends it has an infinite lease. +.Nm dhcpcd +will not de-configure the interface when it exits. +If +.Nm dhcpcd +fails to contact a DHCP server then it returns a failure instead of falling +back on IPv4LL. +.It Ic inform6 +Performs a DHCPv6 Information Request. +No address is requested or specified, but all other DHCPv6 options are allowed. +This is normally performed automatically when an IPv6 Router Advertisement +indicates that the client should perform this operation. +This option is only needed when +.Nm dhcpcd +is not processing IPv6 RA messages and the need for a DHCPv6 Information Request +exists. +.It Ic persistent +.Nm dhcpcd +normally de-configures the interface and configuration when it exits. +Sometimes, this isn't desirable if, for example, you have root mounted over +NFS or SSH clients connect to this host and they need to be notified of +the host shutting down. +You can use this option to stop this from happening. +.It Ic fallback Ar profile +Fall back to using this profile if DHCP fails. +This allows you to configure a static profile instead of using ZeroConf. +.It Ic hostname Ar name +Sends the hostname +.Ar name +to the DHCP server so it can be registered in DNS. +If +.Ar name +is an empty string then the current system hostname is sent. +If +.Ar name +is a FQDN (i.e., contains a .) then it will be encoded as such. +.It Ic hostname_short +Sends the short hostname to the DHCP server instead of the FQDN. +This is useful because DHCP servers will not register the FQDN in their +DNS if the domain part does not match theirs. +.Pp +Also, see the +.Ic env +option above to control how the hostname is set on the host. +.It Ic ia_na Op Ar iaid Op / address +Request a DHCPv6 Normal Address for +.Ar iaid . +.Ar iaid +defaults to the +.Ic iaid +option as described above. +You can request more than one ia_na by specifying a unique +.Ar iaid +for each one. +.It Ic ia_ta Op Ar iaid +Request a DHCPv6 Temporary Address for +.Ar iaid . +You can request more than one ia_ta by specifying a unique +.Ar iaid +for each one. +.It Ic ia_pd Op Ar iaid Oo / Ar prefix / Ar prefix_len Oc Op Ar interface Op / Ar sla_id Op / Ar prefix_len Op / Ar suffix +Request a DHCPv6 Delegated Prefix for +.Ar iaid . +This option must be used in an +.Ic interface +block. +Unless a +.Ar sla_id +of 0 is assigned with the same resultant prefix length as the delegation, +a reject route is installed for the Delegated Prefix to +stop unallocated addresses being resolved upstream. +If no +.Ar interface +is given then we will assign a prefix to every other interface with a +.Ar sla_id +equivalent to the interface index assigned by the OS. +Otherwise addresses are only assigned for each +.Ar interface +and +.Ar sla_id . +Each assigned address will have a +.Ar suffix , +defaulting to 1. +If the +.Ar suffix +is 0 then a SLAAC address is assigned. +You cannot assign a prefix to the requesting interface unless the +DHCPv6 server supports the +.Li RFC 6603 +Prefix Exclude Option. +.Nm dhcpcd +has to be running for all the interfaces it is delegating to. +A default +.Ar prefix_len +of 64 is assumed, unless the maximum +.Ar sla_id +does not fit. +In this case +.Ar prefix_len +is increased to the highest multiple of 8 that can accommodate the +.Ar sla_id . +.Ar sla_id +is an integer which must be unique inside the +.Ar iaid +and is added to the prefix which must fit inside +.Ar prefix_len +less the length of the delegated prefix. +You can specify multiple +.Ar interface / +.Ar sla_id / +.Ar prefix_len +per +.Ic ia_pd , +space separated. +IPv6RS should be disabled globally when requesting a Prefix Delegation. +.Pp +In the following example eth0 is the externally facing interface to be +configured for both IPv4 and IPv6. +The DHCPv4 server will provide us with an IPv4 address and a default route. +The DHCPv6 server is going to provide us with an IPv6 address, a default +route and a /64 subnet to be delegated to the internal interface. +The eth1 interface will be automatically configured +for IPv6 using the first address (::1) from the delegated prefix. +A second prefix is requested and assigned to two other interfaces. +.Xr rtadvd 8 +can be used with an empty configuration file on eth1, eth2 and eth3, +to provide automatic +IPv6 address configuration for the internal network. +.Bd -literal +noipv6rs # disable routing solicitation +denyinterfaces eth2 # Don't touch eth2 at all +interface eth0 + ipv6rs # enable routing solicitation for eth0 + ia_na 1 # request an IPv6 address + ia_pd 2 eth1/0 # request a PD and assign it to eth1 + ia_pd 3 eth2/1 eth3/2 # req a PD and assign it to eth2 and eth3 +.Ed +.It Ic ipv4only +Only configure IPv4. +.It Ic ipv6only +Only configure IPv6. +.It Ic fqdn Op disable | none | ptr | both +.Ar none +will not ask the DHCP server to update DNS. +.Ar ptr +just asks the DHCP server to update the PTR +record of the host in DNS, whereas +.Ar both +also updates the A record. +.Ar disable +will disable the FQDN option. +The default is +.Ar both . +.Nm dhcpcd +itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC 1035 . +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic ipv6ra_autoconf +Generate SLAAC addresses for each Prefix advertised by an IPv6 +Router Advertisement message with the Auto flag set. +On by default. +.It Ic ipv6ra_noautoconf +Disables the above option. +.It Ic ipv6ra_fork +By default, when +.Nm dhcpcd +receives an IPv6 Router Advertisement, +.Nm dhcpcd +will only fork to the background if the RA contains at least one unexpired +RDNSS option and a valid prefix or no DHCPv6 instruction. +Set this option so to make +.Nm dhcpcd +always fork on a RA. +.It Ic ipv6rs +Enables IPv6 Router Advertisement solicitation. +This is on by default, but is documented here in the case where it is disabled +globally but needs to be enabled for one interface. +.It Ic leasetime Ar seconds +Request a lease time of +.Ar seconds . +.Ar -1 +represents an infinite lease time. +By default +.Nm dhcpcd +does not request any lease time and leaves it in the hands of the +DHCP server. +.It Ic link_rcvbuf Ar size +Override the size of the link receive buffer from the kernel default. +While +.Nm dhcpcd +will recover from link buffer overflows, +this may not be desirable on heavily loaded systems. +.It Ic logfile Ar logfile +Writes to the specified +.Ar logfile . +.Nm dhcpcd +still writes to +.Xr syslog 3 . +The +.Ar logfile +is reopened when +.Nm dhcpcd +receives the +.Dv SIGUSR2 +signal. +.It Ic metric Ar metric +Metrics are used to prefer an interface over another one, lowest wins. +.Nm dhcpcd +will supply a default metric of 1000 + +.Xr if_nametoindex 3 . +This will be offset by 2000 for wireless interfaces, with additional offsets +of 1000000 for IPv4LL and 2000000 for roaming interfaces. +.It Ic mudurl Ar url +Specifies the URL for a Manufacturer Usage Description (MUD). +The description is used by upstream network devices to instantiate any +desired access lists. +See draft-ietf-opsawg-mud for more information. +.It Ic noalias +Any pre-existing IPv4 addresses will be removed from the interface when +adding a new IPv4 address. +.It Ic noarp +Don't send any ARP requests. +This also disables IPv4LL. +.It Ic noauthrequired +Don't require authentication even though we requested it. +Also allows FORCERENEW and RECONFIGURE messages without authentication. +.It Ic nodelay +Don't delay for an initial randomised time when starting protocols. +.It Ic nodev +Don't load +.Pa /dev +management modules. +.It Ic nodhcp +Don't start DHCP or listen to DHCP messages. +This is only useful when allowing IPv4LL. +.It Ic nodhcp6 +Don't start DHCPv6 or listen to DHCPv6 messages. +Normally DHCPv6 is started by an IPv6 Router Advertisement instruction or +configuration. +.It Ic nogateway +Don't install any default routes. +.It Ic gateway +Install a default route if available (default). +.It Ic nohook Ar script +Don't run this hook script. +Matches full name, or prefixed with 2 numbers optionally ending with +.Pa .sh . +.Pp +So to stop +.Nm dhcpcd +from touching your DNS settings or starting wpa_supplicant you would do:- +.D1 nohook resolv.conf, wpa_supplicant +.It Ic noipv4 +Don't attempt to configure an IPv4 address. +.It Ic noipv4ll +Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP. +See +.Rs +.%T "RFC 3927" +.Re +.It Ic noipv6 +Don't solicit or accept IPv6 Router Advertisements and DHCPv6. +.It Ic noipv6rs +Don't solicit or accept IPv6 Router Advertisements. +.It Ic nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic noup +Don't bring the interface up when in manager mode. +.It Ic option Ar option +Requests the +.Ar option +from the server. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more +.Ar option Ns s +separated by commas, spaces or more +.Ic option +lines. +Prepend dhcp6_ to +.Ar option +to request a DHCPv6 option. +If no DHCPv6 options are configured, +then DHCPv4 options are mapped to equivalent DHCPv6 options. +.Pp +Prepend nd_ to +.Ar option +to handle ND options, but this only works for the +.Ic nooption , +.Ic reject +and +.Ic require +options. +.Pp +To see a list of options you can use, call +.Nm dhcpcd +with the +.Fl V , Fl Fl variables +argument. +.It Ic nooption Ar option +Remove the option from the message before it's processed. +.It Ic require Ar option +Requires the +.Ar option +to be present in all messages, otherwise the message is ignored. +To enforce that +.Nm dhcpcd +only responds to DHCP servers and not BOOTP servers, you can +.Ic require +.Ar dhcp_message_type . +This isn't an exact science though because a BOOTP server can send DHCP-like +options. +.It Ic reject Ar option +Reject a message that contains the +.Ar option . +This is useful when you cannot use +.Ic require +to select / de-select BOOTP messages. +.It Ic destination Ar option +If +.Nm +detects an address added to a point to point interface (PPP, TUN, etc) then +it will set the listed DHCP options to the destination address of the +interface. +.It Ic profile Ar name +Subsequent options are only parsed for this profile +.Ar name . +.It Ic quiet +Suppress any dhcpcd output to the console, except for errors. +.It Ic reboot Ar seconds +Allow +.Ar reboot +seconds before moving to the DISCOVER phase if we have an old lease to use. +Allow +.Ar reboot +seconds before starting fallback states from the DISCOVER phase. +IPv4LL is started when the first +.Ar reboot +timeout is reached. +The default is 5 seconds. +A setting of 0 seconds causes +.Nm +to skip the reboot phase and go straight into DISCOVER. +This is desirable for mobile users because if you change from network A to +network B and they use the same subnet and the address from network A isn't +in use on network B, then the DHCP server will remain silent even if +authoritative which means +.Nm dhcpcd +will timeout before moving back to the DISCOVER phase. +This has no effect on DHCPv6 other than skipping the reboot phase. +.It Ic release +.Nm dhcpcd +will release the lease prior to stopping the interface. +.It Ic script Ar script +Use +.Ar script +instead of the default +.Pa @SCRIPT@ . +.It Ic ssid Ar ssid +Subsequent options are only parsed for this wireless +.Ar ssid . +.It Ic slaac Ar hwaddr | Ar private Op Ar temp | Ar temporary +Selects the interface identifier used for SLAAC generated IPv6 addresses. +If +.Ar private +is used, a RFC 7217 address is generated. +The +.Ar temporary +directive will create a temporary address for the prefix as well. +.It Ic static Ar value +Configures a static +.Ar value . +If you set +.Ic ip_address +then +.Nm dhcpcd +will not attempt to obtain a lease and will just use the value for the address +with an infinite lease time. +If you set +.Ic ip6_address , +.Nm dhcpcd +will continue auto-configuration as normal. +.Pp +Here is an example which configures two static address, overriding the default +IPv4 broadcast address, an IPv4 router, DNS and disables IPv6 auto-configuration. +You could also use the +.Ic inform6 +command here if you wished to obtain more information via DHCPv6. +For IPv4, you should use the +.Ic inform Ar ipaddress +option instead of setting a static address. +.D1 interface eth0 +.D1 noipv6rs +.D1 static ip_address=192.168.0.10/24 +.D1 static broadcast_address=192.168.0.63 +.D1 static ip6_address=fd51:42f8:caae:d92e::ff/64 +.D1 static routers=192.168.0.1 +.D1 static domain_name_servers=192.168.0.1 fd51:42f8:caae:d92e::1 +.Pp +Here is an example for PPP which gives the destination a default route. +It uses the special +.Ar destination +keyword to insert the destination address +into the value. +.D1 interface ppp0 +.D1 static ip_address= +.D1 destination routers +.It Ic timeout Ar seconds +Time out after +.Ar seconds , +instead of the default 30. +A setting of 0 +.Ar seconds +causes +.Nm dhcpcd +to wait forever to get a lease. +If +.Nm dhcpcd +is working on a single interface then +.Nm dhcpcd +will exit when a timeout occurs, otherwise +.Nm dhcpcd +will fork into the background. +If using IPv4LL then +.Nm dhcpcd +start the IPv4LL process after the timeout and then wait a little longer +before really timing out. +.It Ic userclass Ar string +Tag the DHCPv4 message with the userclass. +You can specify more than one. +.It Ic msuserclass Ar string +Tag the DHCPv4 mesasge with the Microsoft userclass. +Unlike the +.Ic userclass +option, this one can only be added once. +It should only be used for Microsoft DHCP servers and the +.Ic vendorclassid +should be set to "MSFT 98" or "MSFT 5.0". +This option is not RFC compliant. +.It Ic vendor Ar code , Ns Ar value +Add an encapsulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +To add a raw vendor string, omit +.Ar code +but keep the comma. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 vendor 01,192.168.0.2 +Set the vendor option 02 with a hex code. +.D1 vendor 02,01:02:03:04:05 +Set the vendor option 03 with an IP address as a string. +.D1 vendor 03,\e"192.168.0.2\e" +Set un-encapsulated vendor option to hello world. +.D1 vendor ,"hello world" +.It Ic vendorclassid Ar string +Set the DHCP Vendor Class. +DHCPv6 has its own option as shown below. +The default is +dhcpcd-:::. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 +If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. +.It Ic vendclass Ar en Ar data +Add the DHCPv6 Vendor Indetifying Vendor Class with the IANA assigned Enterprise +Number +.Ar en +with the +.Ar data . +This option can be set more than once to add more data, but the behaviour, +as per RFC 3925 is undefined if the Enterprise Number differs. +.It Ic waitip Op 4 | 6 +Wait for an address to be assigned before forking to the background. +4 means wait for an IPv4 address to be assigned. +6 means wait for an IPv6 address to be assigned. +If no argument is given, +.Nm +will wait for any address protocol to be assigned. +It is possible to wait for more than one address protocol and +.Nm +will only fork to the background when all waiting conditions are satisfied. +.It Ic xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. +.El +.Ss Defining new options +DHCP, ND and DHCPv6 allow for the use of custom options, and RFC 3925 vendor +options for DHCP can also be supplied. +Each option needs to be started with the +.Ic define , +.Ic definend , +.Ic define6 +or +.Ic vendopt +directive. +This can optionally be followed by both +.Ic embed +or +.Ic encap +options. +Both can be specified more than once and +.Ic embed +must come before +.Ic encap . +.Bl -tag -width indent +.It Ic define Ar code Ar type Ar variable +Defines the DHCP option +.Ar code +of +.Ar type +with a name of +.Ar variable +exported to +.Xr dhcpcd-run-hooks 8 . +.It Ic definend Ar code Ar type Ar variable +Defines the ND option +.Ar code +of +.Ar type +with a name of +.Ar variable +exported to +.Xr dhcpcd-run-hooks 8 , +with a prefix of +.Va nd_ . +.It Ic define6 Ar code Ar type Ar variable +Defines the DHCPv6 option +.Ar code +of +.Ar type +with a name of +.Ar variable +exported to +.Xr dhcpcd-run-hooks 8 , +with a prefix of +.Va dhcp6_ . +.It Ic vendopt Ar code Ar type Ar variable +Defines the Vendor-Identifying Vendor Options. +The +.Ar code +is the IANA Enterprise Number which will uniquely describe the encapsulated +options. +.Ar type +is normally +.Ar encap . +.Ar variable +names the Vendor option to be exported. +.It Ic embed Ar type Ar variable +Defines an embedded variable within the defined option. +The length is determined by the +.Ar type . +If the +.Ar variable +is not the same as defined in the parent option, +it is prefixed with the parent +.Ar variable +first with an underscore. +If the +.Ar variable +has the name of +.Ar reserved +then it is not processed. +.It Ic encap Ar code Ar type Ar variable +Defines an encapsulated variable within the defined option. +The length is determined by the +.Ar type . +If the +.Ar variable +is not the same as defined in the parent option, +it is prefixed with the parent +.Ar variable +first with an underscore. +.El +.Ss Type prefix +These keywords come before the type itself, to describe it more fully. +You can use more than one, but they must appear in the order listed below. +.Bl -tag -width -indent +.It Ic request +Requests the option by default without having to be specified in user +configuration. +.It Ic norequest +This option cannot be requested, regardless of user configuration. +.It Ic optional +This option is optional. +Only makes sense for embedded options like the client FQDN option, where +the FQDN string itself is optional. +.It Ic index +The option can appear more than once and will be indexed. +.It Ic array +The option data is split into a space separated array, each element being +the same type. +.El +.Ss Types to define +The type directly affects the length of data consumed inside the option. +Any remaining data is normally discarded. +Lengths can be specified for string and binhex types, but this is generally +with other data embedded afterwards in the same option. +.Bl -tag -width indent +.It Ic ipaddress +An IPv4 address, 4 bytes. +.It Ic ip6address +An IPv6 address, 16 bytes. +.It Ic string Op : Ic length +A NVT ASCII string of printable characters. +.It Ic byte +A byte. +.It Ic bitflags : Ic flags +A byte represented as a string of flags, most significant bit first. +For example, using ABCDEFGH then A would equal 10000000, B 01000000, +C 00100000, etc. +If the bit is not set, the flag is not printed. +A flag of 0 is not printed even if the bit position is set. +This is to allow reservation of the first bits while assigning the last bits. +.It Ic int16 +A signed 16bit integer, 2 bytes. +.It Ic uint16 +An unsigned 16bit integer, 2 bytes. +.It Ic int32 +A signed 32bit integer, 4 bytes. +.It Ic uint32 +An unsigned 32bit integer, 4 bytes. +.It Ic flag +A fixed value (1) to indicate that the option is present, 0 bytes. +.It Ic domain +An RFC 3397 encoded string. +.It Ic dname +An RFC 1035 validated string. +.It Ic binhex Op : Ic length +Binary data expressed as hexadecimal. +.It Ic embed +Contains embedded options (implies encap as well). +.It Ic encap +Contains encapsulated options (implies embed as well). +.It Ic option +References an option from the global definition. +.El +.Ss Example definition +.D1 # DHCP option 81, Fully Qualified Domain Name, RFC 4702 +.D1 define 81 embed fqdn +.D1 embed byte flags +.D1 embed byte rcode1 +.D1 embed byte rcode2 +.D1 embed domain fqdn +.Pp +.D1 # DHCP option 125, Vendor Specific Information Option, RFC 3925 +.D1 define 125 encap vsio +.D1 embed uint32 enterprise_number +.D1 # Options defined for the enterprise number +.D1 encap 1 ipaddress ipaddress +.Ss Supported Authentication Protocols +.Bl -tag -width -indent +.It Ic token +Sends a plain text token the server expects and matches a token sent by +the server. +The tokens do not have to be the same. +If unspecified, the token with a +.Ar secretid +of 0 will be used in sending messages +and validating received messages. +.It Ic delayedrealm +Delayed Authentication. +.Nm dhcpcd +will send an authentication option with no key or MAC. +The server will see this option, and select a key for +.Nm , writing the +.Ar realm +and +.Ar secretid +in it. +.Nm dhcpcd +will then look for an unexpired token with a matching +.Ar realm +and +.Ar secretid . +This token is used to authenticate all other messages. +.It Ic delayed +Same as above, but without a realm. +.El +.Ss Supported Authentication Algorithms +If none specified, +.Ic hmac-md5 +is the default. +.Bl -tag -width -indent +.It Ic hmac-md5 +.El +.Ss Supported Replay Detection Mechanisms +If none specified, +.Ic monotonic +is the default. +If this is changed from what was previously used, +or the means of calculating or storing it is broken, then the DHCP server +will probably have to have its notion of the client's Replay Detection Value +reset. +.Bl -tag -width -indent +.It Ic monocounter +Read the number in the file +.Pa @DBDIR@/dhcpcd-rdm.monotonic +and add one to it. +.It Ic monotime +Create an NTP timestamp from the system time. +.It Ic monotonic +Same as +.Ic monotime . +.El +.Sh SEE ALSO +.Xr fnmatch 3 , +.Xr if_nametoindex 3 , +.Xr dhcpcd 8 , +.Xr dhcpcd-run-hooks 8 +.Sh AUTHORS +.An Roy Marples Aq Mt roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd Index: src/duid.h =================================================================== --- /dev/null +++ src/duid.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef DUID_H +#define DUID_H + +#define DUID_LEN 128 + 2 +#define DUID_DEFAULT 0 +#define DUID_LLT 1 +#define DUID_LL 3 +#define DUID_UUID 4 + +size_t duid_make(void *, const struct interface *, uint16_t); +size_t duid_init(struct dhcpcd_ctx *, const struct interface *); + +#endif Index: src/duid.c =================================================================== --- /dev/null +++ src/duid.c @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#define UUID_LEN 36 +#define DUID_TIME_EPOCH 946684800 + +#include +#include +#include +#ifdef BSD +# include +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "dhcpcd.h" +#include "duid.h" +#include "logerr.h" + +static size_t +duid_machineuuid(char *uuid, size_t uuid_len) +{ + int r; + size_t len = uuid_len; + +#if defined(HW_UUID) /* OpenBSD */ + int mib[] = { CTL_HW, HW_UUID }; + + r = sysctl(mib, sizeof(mib)/sizeof(mib[0]), uuid, &len, NULL, 0); +#elif defined(KERN_HOSTUUID) /* FreeBSD */ + int mib[] = { CTL_KERN, KERN_HOSTUUID }; + + r = sysctl(mib, sizeof(mib)/sizeof(mib[0]), uuid, &len, NULL, 0); +#elif defined(__NetBSD__) + r = sysctlbyname("machdep.dmi.system-uuid", uuid, &len, NULL, 0); +#elif defined(__linux__) + FILE *fp; + + fp = fopen("/sys/class/dmi/id/product_uuid", "r"); + if (fp == NULL) + return 0; + if (fgets(uuid, (int)uuid_len, fp) == NULL) { + fclose(fp); + return 0; + } + len = strlen(uuid) + 1; + fclose(fp); + r = len == 1 ? -1 : 0; +#else + UNUSED(uuid); + r = -1; + errno = ENOSYS; +#endif + + if (r == -1) + return 0; + return len; +} + +static size_t +duid_make_uuid(uint8_t *d) +{ + uint16_t type = htons(DUID_UUID); + char uuid[UUID_LEN + 1]; + size_t l; + + if (duid_machineuuid(uuid, sizeof(uuid)) != sizeof(uuid)) + return 0; + + /* All zeros UUID is not valid */ + if (strcmp("00000000-0000-0000-0000-000000000000", uuid) == 0) + return 0; + + memcpy(d, &type, sizeof(type)); + l = sizeof(type); + d += sizeof(type); + l += hwaddr_aton(d, uuid); + return l; +} + +size_t +duid_make(void *d, const struct interface *ifp, uint16_t type) +{ + uint8_t *p; + uint16_t u16; + time_t t; + uint32_t u32; + + if (ifp->hwlen == 0) + return 0; + + p = d; + u16 = htons(type); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + u16 = htons(ifp->hwtype); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + if (type == DUID_LLT) { + /* time returns seconds from jan 1 1970, but DUID-LLT is + * seconds from jan 1 2000 modulo 2^32 */ + t = time(NULL) - DUID_TIME_EPOCH; + u32 = htonl((uint32_t)t & 0xffffffff); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + } + /* Finally, add the MAC address of the interface */ + memcpy(p, ifp->hwaddr, ifp->hwlen); + p += ifp->hwlen; + return (size_t)(p - (uint8_t *)d); +} + +#define DUID_STRLEN DUID_LEN * 3 +static size_t +duid_get(struct dhcpcd_ctx *ctx, const struct interface *ifp) +{ + uint8_t *data; + size_t len, slen; + char line[DUID_STRLEN]; + const struct interface *ifp2; + + /* If we already have a DUID then use it as it's never supposed + * to change once we have one even if the interfaces do */ + if ((len = dhcp_read_hwaddr_aton(ctx, &data, DUID)) != 0) { + if (len <= DUID_LEN) { + ctx->duid = data; + return len; + } + logerrx("DUID too big (max %u): %s", DUID_LEN, DUID); + /* Keep the buffer, will assign below. */ + } else { + if (errno != ENOENT) + logerr("%s", DUID); + if ((data = malloc(DUID_LEN)) == NULL) { + logerr(__func__); + return 0; + } + } + + /* No file? OK, lets make one based the machines UUID */ + if (ifp == NULL) { + if (ctx->duid_type != DUID_DEFAULT && + ctx->duid_type != DUID_UUID) + len = 0; + else + len = duid_make_uuid(data); + if (len == 0) + free(data); + else + ctx->duid = data; + return len; + } + + /* Regardless of what happens we will create a DUID to use. */ + ctx->duid = data; + + /* No UUID? OK, lets make one based on our interface */ + if (ifp->hwlen == 0) { + logwarnx("%s: does not have hardware address", ifp->name); + TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { + if (ifp2->hwlen != 0) + break; + } + if (ifp2) { + ifp = ifp2; + logwarnx("picked interface %s to generate a DUID", + ifp->name); + } else { + if (ctx->duid_type != DUID_LL) + logwarnx("no interfaces have a fixed hardware " + "address"); + return duid_make(data, ifp, DUID_LL); + } + } + + len = duid_make(data, ifp, + ctx->duid_type == DUID_LL ? DUID_LL : DUID_LLT); + hwaddr_ntoa(data, len, line, sizeof(line)); + slen = strlen(line); + if (slen < sizeof(line) - 2) { + line[slen++] = '\n'; + line[slen] = '\0'; + } + if (dhcp_writefile(ctx, DUID, 0640, line, slen) == -1) { + logerr("%s: cannot write duid", __func__); + if (ctx->duid_type != DUID_LL) + return duid_make(data, ifp, DUID_LL); + } + return len; +} + +size_t +duid_init(struct dhcpcd_ctx *ctx, const struct interface *ifp) +{ + + if (ctx->duid == NULL) + ctx->duid_len = duid_get(ctx, ifp); + return ctx->duid_len; +} Index: src/eloop.h =================================================================== --- /dev/null +++ src/eloop.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef ELOOP_H +#define ELOOP_H + +#include + +/* Handy macros to create subsecond timeouts */ +#define CSEC_PER_SEC 100 +#define MSEC_PER_SEC 1000 +#define NSEC_PER_CSEC 10000000 +#define NSEC_PER_MSEC 1000000 +#define NSEC_PER_SEC 1000000000 + +/* eloop queues are really only for deleting timeouts registered + * for a function or object. + * The idea being that one interface has different timeouts for + * say DHCP and DHCPv6. */ +#ifndef ELOOP_QUEUE + #define ELOOP_QUEUE 1 +#endif + +/* Used for deleting a timeout for all queues. */ +#define ELOOP_QUEUE_ALL 0 + +/* Forward declare eloop - the content should be invisible to the outside */ +struct eloop; + +unsigned long long eloop_timespec_diff(const struct timespec *tsp, + const struct timespec *usp, unsigned int *nsp); +size_t eloop_event_count(const struct eloop *); +int eloop_event_add_rw(struct eloop *, int, + void (*)(void *), void *, + void (*)(void *), void *); +int eloop_event_add(struct eloop *, int, + void (*)(void *), void *); +int eloop_event_add_w(struct eloop *, int, + void (*)(void *), void *); +#define eloop_event_delete(eloop, fd) \ + eloop_event_delete_write((eloop), (fd), 0) +#define eloop_event_remove_writecb(eloop, fd) \ + eloop_event_delete_write((eloop), (fd), 1) +int eloop_event_delete_write(struct eloop *, int, int); + +#define eloop_timeout_add_tv(eloop, tv, cb, ctx) \ + eloop_q_timeout_add_tv((eloop), ELOOP_QUEUE, (tv), (cb), (ctx)) +#define eloop_timeout_add_sec(eloop, tv, cb, ctx) \ + eloop_q_timeout_add_sec((eloop), ELOOP_QUEUE, (tv), (cb), (ctx)) +#define eloop_timeout_add_msec(eloop, ms, cb, ctx) \ + eloop_q_timeout_add_msec((eloop), ELOOP_QUEUE, (ms), (cb), (ctx)) +#define eloop_timeout_delete(eloop, cb, ctx) \ + eloop_q_timeout_delete((eloop), ELOOP_QUEUE, (cb), (ctx)) +int eloop_q_timeout_add_tv(struct eloop *, int, + const struct timespec *, void (*)(void *), void *); +int eloop_q_timeout_add_sec(struct eloop *, int, + unsigned int, void (*)(void *), void *); +int eloop_q_timeout_add_msec(struct eloop *, int, + unsigned long, void (*)(void *), void *); +int eloop_q_timeout_delete(struct eloop *, int, void (*)(void *), void *); + +void eloop_signal_set_cb(struct eloop *, const int *, size_t, + void (*)(int, void *), void *); +int eloop_signal_mask(struct eloop *, sigset_t *oldset); + +struct eloop * eloop_new(void); +void eloop_clear(struct eloop *); +void eloop_free(struct eloop *); +void eloop_exit(struct eloop *, int); +void eloop_enter(struct eloop *); +int eloop_start(struct eloop *, sigset_t *); + +#endif Index: src/eloop.c =================================================================== --- /dev/null +++ src/eloop.c @@ -0,0 +1,787 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * eloop - portable event based main loop. + * Copyright (c) 2006-2021 Roy Marples + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* config.h should define HAVE_PPOLL, etc. */ +#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H) +#include "config.h" +#endif + +#if defined(HAVE_PPOLL) +#elif defined(HAVE_POLLTS) +#define ppoll pollts +#elif !defined(HAVE_PSELECT) +#pragma message("Compiling eloop with pselect(2) support.") +#define HAVE_PSELECT +#define ppoll eloop_ppoll +#endif + +#include "eloop.h" + +#ifndef UNUSED +#define UNUSED(a) (void)((a)) +#endif +#ifndef __unused +#ifdef __GNUC__ +#define __unused __attribute__((__unused__)) +#else +#define __unused +#endif +#endif + +#ifdef HAVE_PSELECT +#include +#endif + +/* Our structures require TAILQ macros, which really every libc should + * ship as they are useful beyond belief. + * Sadly some libc's don't have sys/queue.h and some that do don't have + * the TAILQ_FOREACH macro. For those that don't, the application using + * this implementation will need to ship a working queue.h somewhere. + * If we don't have sys/queue.h found in config.h, then + * allow QUEUE_H to override loading queue.h in the current directory. */ +#ifndef TAILQ_FOREACH +#ifdef HAVE_SYS_QUEUE_H +#include +#elif defined(QUEUE_H) +#define __QUEUE_HEADER(x) #x +#define _QUEUE_HEADER(x) __QUEUE_HEADER(x) +#include _QUEUE_HEADER(QUEUE_H) +#else +#include "queue.h" +#endif +#endif + +#ifdef ELOOP_DEBUG +#include +#endif + +/* + * Allow a backlog of signals. + * If you use many eloops in the same process, they should all + * use the same signal handler or have the signal handler unset. + * Otherwise the signal might not behave as expected. + */ +#define ELOOP_NSIGNALS 5 + +/* + * time_t is a signed integer of an unspecified size. + * To adjust for time_t wrapping, we need to work the maximum signed + * value and use that as a maximum. + */ +#ifndef TIME_MAX +#define TIME_MAX ((1ULL << (sizeof(time_t) * NBBY - 1)) - 1) +#endif +/* The unsigned maximum is then simple - multiply by two and add one. */ +#ifndef UTIME_MAX +#define UTIME_MAX (TIME_MAX * 2) + 1 +#endif + +struct eloop_event { + TAILQ_ENTRY(eloop_event) next; + int fd; + void (*read_cb)(void *); + void *read_cb_arg; + void (*write_cb)(void *); + void *write_cb_arg; + struct pollfd *pollfd; +}; + +struct eloop_timeout { + TAILQ_ENTRY(eloop_timeout) next; + unsigned int seconds; + unsigned int nseconds; + void (*callback)(void *); + void *arg; + int queue; +}; + +struct eloop { + TAILQ_HEAD (event_head, eloop_event) events; + size_t nevents; + struct event_head free_events; + bool events_need_setup; + + struct timespec now; + TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; + struct timeout_head free_timeouts; + + const int *signals; + size_t signals_len; + void (*signal_cb)(int, void *); + void *signal_cb_ctx; + + struct pollfd *fds; + size_t nfds; + + int exitnow; + int exitcode; +}; + +#ifdef HAVE_REALLOCARRAY +#define eloop_realloca reallocarray +#else +/* Handy routing to check for potential overflow. + * reallocarray(3) and reallocarr(3) are not portable. */ +#define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2)) +static void * +eloop_realloca(void *ptr, size_t n, size_t size) +{ + + if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) { + errno = EOVERFLOW; + return NULL; + } + return realloc(ptr, n * size); +} +#endif + +#ifdef HAVE_PSELECT +/* Wrapper around pselect, to imitate the ppoll call. */ +static int +eloop_ppoll(struct pollfd * fds, nfds_t nfds, + const struct timespec *ts, const sigset_t *sigmask) +{ + fd_set read_fds, write_fds; + nfds_t n; + int maxfd, r; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + maxfd = 0; + for (n = 0; n < nfds; n++) { + if (fds[n].events & POLLIN) { + FD_SET(fds[n].fd, &read_fds); + if (fds[n].fd > maxfd) + maxfd = fds[n].fd; + } + if (fds[n].events & POLLOUT) { + FD_SET(fds[n].fd, &write_fds); + if (fds[n].fd > maxfd) + maxfd = fds[n].fd; + } + } + + r = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask); + if (r > 0) { + for (n = 0; n < nfds; n++) { + fds[n].revents = + FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0; + if (FD_ISSET(fds[n].fd, &write_fds)) + fds[n].revents |= POLLOUT; + } + } + + return r; +} +#endif + +unsigned long long +eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp, + unsigned int *nsp) +{ + unsigned long long tsecs, usecs, secs; + long nsecs; + + if (tsp->tv_sec < 0) /* time wreapped */ + tsecs = UTIME_MAX - (unsigned long long)(-tsp->tv_sec); + else + tsecs = (unsigned long long)tsp->tv_sec; + if (usp->tv_sec < 0) /* time wrapped */ + usecs = UTIME_MAX - (unsigned long long)(-usp->tv_sec); + else + usecs = (unsigned long long)usp->tv_sec; + + if (usecs > tsecs) /* time wrapped */ + secs = (UTIME_MAX - usecs) + tsecs; + else + secs = tsecs - usecs; + + nsecs = tsp->tv_nsec - usp->tv_nsec; + if (nsecs < 0) { + if (secs == 0) + nsecs = 0; + else { + secs--; + nsecs += NSEC_PER_SEC; + } + } + if (nsp != NULL) + *nsp = (unsigned int)nsecs; + return secs; +} + +static void +eloop_reduce_timers(struct eloop *eloop) +{ + struct timespec now; + unsigned long long secs; + unsigned int nsecs; + struct eloop_timeout *t; + + clock_gettime(CLOCK_MONOTONIC, &now); + secs = eloop_timespec_diff(&now, &eloop->now, &nsecs); + + TAILQ_FOREACH(t, &eloop->timeouts, next) { + if (secs > t->seconds) { + t->seconds = 0; + t->nseconds = 0; + } else { + t->seconds -= (unsigned int)secs; + if (nsecs > t->nseconds) { + if (t->seconds == 0) + t->nseconds = 0; + else { + t->seconds--; + t->nseconds = NSEC_PER_SEC + - (nsecs - t->nseconds); + } + } else + t->nseconds -= nsecs; + } + } + + eloop->now = now; +} + +static void +eloop_event_setup_fds(struct eloop *eloop) +{ + struct eloop_event *e, *ne; + struct pollfd *pfd; + + pfd = eloop->fds; + TAILQ_FOREACH_SAFE(e, &eloop->events, next, ne) { + if (e->fd == -1) { + TAILQ_REMOVE(&eloop->events, e, next); + TAILQ_INSERT_TAIL(&eloop->free_events, e, next); + continue; + } +#ifdef ELOOP_DEBUG + fprintf(stderr, "%s(%d) fd=%d, rcb=%p, wcb=%p\n", + __func__, getpid(), e->fd, e->read_cb, e->write_cb); +#endif + e->pollfd = pfd; + pfd->fd = e->fd; + pfd->events = 0; + if (e->read_cb != NULL) + pfd->events |= POLLIN; + if (e->write_cb != NULL) + pfd->events |= POLLOUT; + pfd->revents = 0; + pfd++; + } + eloop->events_need_setup = false; +} + +size_t +eloop_event_count(const struct eloop *eloop) +{ + + return eloop->nevents; +} + +int +eloop_event_add_rw(struct eloop *eloop, int fd, + void (*read_cb)(void *), void *read_cb_arg, + void (*write_cb)(void *), void *write_cb_arg) +{ + struct eloop_event *e; + struct pollfd *pfd; + + assert(eloop != NULL); + assert(read_cb != NULL || write_cb != NULL); + if (fd == -1) { + errno = EINVAL; + return -1; + } + + TAILQ_FOREACH(e, &eloop->events, next) { + if (e->fd == fd) + break; + } + + if (e == NULL) { + if (eloop->nevents + 1 > eloop->nfds) { + pfd = eloop_realloca(eloop->fds, eloop->nevents + 1, + sizeof(*pfd)); + if (pfd == NULL) + return -1; + eloop->fds = pfd; + eloop->nfds++; + } + + e = TAILQ_FIRST(&eloop->free_events); + if (e != NULL) + TAILQ_REMOVE(&eloop->free_events, e, next); + else { + e = malloc(sizeof(*e)); + if (e == NULL) + return -1; + } + TAILQ_INSERT_HEAD(&eloop->events, e, next); + eloop->nevents++; + e->fd = fd; + e->read_cb = read_cb; + e->read_cb_arg = read_cb_arg; + e->write_cb = write_cb; + e->write_cb_arg = write_cb_arg; + goto setup; + } + + if (read_cb) { + e->read_cb = read_cb; + e->read_cb_arg = read_cb_arg; + } + if (write_cb) { + e->write_cb = write_cb; + e->write_cb_arg = write_cb_arg; + } + +setup: + e->pollfd = NULL; + eloop->events_need_setup = true; + return 0; +} + +int +eloop_event_add(struct eloop *eloop, int fd, + void (*read_cb)(void *), void *read_cb_arg) +{ + + return eloop_event_add_rw(eloop, fd, read_cb, read_cb_arg, NULL, NULL); +} + +int +eloop_event_add_w(struct eloop *eloop, int fd, + void (*write_cb)(void *), void *write_cb_arg) +{ + + return eloop_event_add_rw(eloop, fd, NULL,NULL, write_cb, write_cb_arg); +} + +int +eloop_event_delete_write(struct eloop *eloop, int fd, int write_only) +{ + struct eloop_event *e; + + assert(eloop != NULL); + if (fd == -1) { + errno = EINVAL; + return -1; + } + + TAILQ_FOREACH(e, &eloop->events, next) { + if (e->fd == fd) + break; + } + if (e == NULL) { + errno = ENOENT; + return -1; + } + + if (write_only) { + if (e->read_cb == NULL) + goto remove; + e->write_cb = NULL; + e->write_cb_arg = NULL; + if (e->pollfd != NULL) { + e->pollfd->events &= ~POLLOUT; + e->pollfd->revents &= ~POLLOUT; + } + return 1; + } + +remove: + e->fd = -1; + eloop->nevents--; + eloop->events_need_setup = true; + return 1; +} + +/* + * This implementation should cope with UINT_MAX seconds on a system + * where time_t is INT32_MAX. It should also cope with the monotonic timer + * wrapping, although this is highly unlikely. + * unsigned int should match or be greater than any on wire specified timeout. + */ +static int +eloop_q_timeout_add(struct eloop *eloop, int queue, + unsigned int seconds, unsigned int nseconds, + void (*callback)(void *), void *arg) +{ + struct eloop_timeout *t, *tt = NULL; + + assert(eloop != NULL); + assert(callback != NULL); + assert(nseconds <= NSEC_PER_SEC); + + /* Remove existing timeout if present. */ + TAILQ_FOREACH(t, &eloop->timeouts, next) { + if (t->callback == callback && t->arg == arg) { + TAILQ_REMOVE(&eloop->timeouts, t, next); + break; + } + } + + if (t == NULL) { + /* No existing, so allocate or grab one from the free pool. */ + if ((t = TAILQ_FIRST(&eloop->free_timeouts))) { + TAILQ_REMOVE(&eloop->free_timeouts, t, next); + } else { + if ((t = malloc(sizeof(*t))) == NULL) + return -1; + } + } + + eloop_reduce_timers(eloop); + + t->seconds = seconds; + t->nseconds = nseconds; + t->callback = callback; + t->arg = arg; + t->queue = queue; + + /* The timeout list should be in chronological order, + * soonest first. */ + TAILQ_FOREACH(tt, &eloop->timeouts, next) { + if (t->seconds < tt->seconds || + (t->seconds == tt->seconds && t->nseconds < tt->nseconds)) + { + TAILQ_INSERT_BEFORE(tt, t, next); + return 0; + } + } + TAILQ_INSERT_TAIL(&eloop->timeouts, t, next); + return 0; +} + +int +eloop_q_timeout_add_tv(struct eloop *eloop, int queue, + const struct timespec *when, void (*callback)(void *), void *arg) +{ + + if (when->tv_sec < 0 || (unsigned long)when->tv_sec > UINT_MAX) { + errno = EINVAL; + return -1; + } + if (when->tv_nsec < 0 || when->tv_nsec > NSEC_PER_SEC) { + errno = EINVAL; + return -1; + } + + return eloop_q_timeout_add(eloop, queue, + (unsigned int)when->tv_sec, (unsigned int)when->tv_sec, + callback, arg); +} + +int +eloop_q_timeout_add_sec(struct eloop *eloop, int queue, unsigned int seconds, + void (*callback)(void *), void *arg) +{ + + return eloop_q_timeout_add(eloop, queue, seconds, 0, callback, arg); +} + +int +eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when, + void (*callback)(void *), void *arg) +{ + unsigned long seconds, nseconds; + + seconds = when / MSEC_PER_SEC; + if (seconds > UINT_MAX) { + errno = EINVAL; + return -1; + } + + nseconds = (when % MSEC_PER_SEC) * NSEC_PER_MSEC; + return eloop_q_timeout_add(eloop, queue, + (unsigned int)seconds, (unsigned int)nseconds, callback, arg); +} + +int +eloop_q_timeout_delete(struct eloop *eloop, int queue, + void (*callback)(void *), void *arg) +{ + struct eloop_timeout *t, *tt; + int n; + + assert(eloop != NULL); + + n = 0; + TAILQ_FOREACH_SAFE(t, &eloop->timeouts, next, tt) { + if ((queue == 0 || t->queue == queue) && + t->arg == arg && + (!callback || t->callback == callback)) + { + TAILQ_REMOVE(&eloop->timeouts, t, next); + TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); + n++; + } + } + return n; +} + +void +eloop_exit(struct eloop *eloop, int code) +{ + + assert(eloop != NULL); + + eloop->exitcode = code; + eloop->exitnow = 1; +} + +void +eloop_enter(struct eloop *eloop) +{ + + eloop->exitnow = 0; +} + +void +eloop_signal_set_cb(struct eloop *eloop, + const int *signals, size_t signals_len, + void (*signal_cb)(int, void *), void *signal_cb_ctx) +{ + + assert(eloop != NULL); + + eloop->signals = signals; + eloop->signals_len = signals_len; + eloop->signal_cb = signal_cb; + eloop->signal_cb_ctx = signal_cb_ctx; +} + +static volatile int _eloop_sig[ELOOP_NSIGNALS]; +static volatile size_t _eloop_nsig; + +static void +eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg) +{ + + if (_eloop_nsig == __arraycount(_eloop_sig)) { +#ifdef ELOOP_DEBUG + fprintf(stderr, "%s: signal storm, discarding signal %d\n", + __func__, sig); +#endif + return; + } + + _eloop_sig[_eloop_nsig++] = sig; +} + +int +eloop_signal_mask(struct eloop *eloop, sigset_t *oldset) +{ + sigset_t newset; + size_t i; + struct sigaction sa = { + .sa_sigaction = eloop_signal3, + .sa_flags = SA_SIGINFO, + }; + + assert(eloop != NULL); + + sigemptyset(&newset); + for (i = 0; i < eloop->signals_len; i++) + sigaddset(&newset, eloop->signals[i]); + if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) + return -1; + + sigemptyset(&sa.sa_mask); + + for (i = 0; i < eloop->signals_len; i++) { + if (sigaction(eloop->signals[i], &sa, NULL) == -1) + return -1; + } + return 0; +} + +struct eloop * +eloop_new(void) +{ + struct eloop *eloop; + + eloop = calloc(1, sizeof(*eloop)); + if (eloop == NULL) + return NULL; + + /* Check we have a working monotonic clock. */ + if (clock_gettime(CLOCK_MONOTONIC, &eloop->now) == -1) { + free(eloop); + return NULL; + } + + TAILQ_INIT(&eloop->events); + TAILQ_INIT(&eloop->free_events); + TAILQ_INIT(&eloop->timeouts); + TAILQ_INIT(&eloop->free_timeouts); + eloop->exitcode = EXIT_FAILURE; + + return eloop; +} + +void +eloop_clear(struct eloop *eloop) +{ + struct eloop_event *e; + struct eloop_timeout *t; + + if (eloop == NULL) + return; + + eloop->nevents = 0; + eloop->signals = NULL; + eloop->signals_len = 0; + + while ((e = TAILQ_FIRST(&eloop->events))) { + TAILQ_REMOVE(&eloop->events, e, next); + free(e); + } + while ((e = TAILQ_FIRST(&eloop->free_events))) { + TAILQ_REMOVE(&eloop->free_events, e, next); + free(e); + } + while ((t = TAILQ_FIRST(&eloop->timeouts))) { + TAILQ_REMOVE(&eloop->timeouts, t, next); + free(t); + } + while ((t = TAILQ_FIRST(&eloop->free_timeouts))) { + TAILQ_REMOVE(&eloop->free_timeouts, t, next); + free(t); + } + + free(eloop->fds); + eloop->fds = NULL; + eloop->nfds = 0; +} + +void +eloop_free(struct eloop *eloop) +{ + + eloop_clear(eloop); + free(eloop); +} + +int +eloop_start(struct eloop *eloop, sigset_t *signals) +{ + int n; + struct eloop_event *e; + struct eloop_timeout *t; + struct timespec ts, *tsp; + + assert(eloop != NULL); + + for (;;) { + if (eloop->exitnow) + break; + + if (_eloop_nsig != 0) { + n = _eloop_sig[--_eloop_nsig]; + if (eloop->signal_cb != NULL) + eloop->signal_cb(n, eloop->signal_cb_ctx); + continue; + } + + t = TAILQ_FIRST(&eloop->timeouts); + if (t == NULL && eloop->nevents == 0) + break; + + if (t != NULL) + eloop_reduce_timers(eloop); + + if (t != NULL && t->seconds == 0 && t->nseconds == 0) { + TAILQ_REMOVE(&eloop->timeouts, t, next); + t->callback(t->arg); + TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); + continue; + } + + if (t != NULL) { + if (t->seconds > INT_MAX) { + ts.tv_sec = (time_t)INT_MAX; + ts.tv_nsec = 0; + } else { + ts.tv_sec = (time_t)t->seconds; + ts.tv_nsec = (long)t->nseconds; + } + tsp = &ts; + } else + tsp = NULL; + + if (eloop->events_need_setup) + eloop_event_setup_fds(eloop); + + n = ppoll(eloop->fds, (nfds_t)eloop->nevents, tsp, signals); + if (n == -1) { + if (errno == EINTR) + continue; + return -errno; + } + if (n == 0) + continue; + + TAILQ_FOREACH(e, &eloop->events, next) { + /* Skip freshly added events */ + if (e->pollfd == NULL) + continue; + if (e->pollfd->revents) + n--; + if (e->fd != -1 && e->pollfd->revents & POLLOUT) { + if (e->write_cb != NULL) + e->write_cb(e->write_cb_arg); + } + if (e->fd != -1 && + e->pollfd != NULL && e->pollfd->revents) + { + if (e->read_cb != NULL) + e->read_cb(e->read_cb_arg); + } + if (n == 0) + break; + } + } + + return eloop->exitcode; +} Index: src/genembedc =================================================================== --- /dev/null +++ src/genembedc @@ -0,0 +1,30 @@ +#!/bin/sh +set -e + +: ${TOOL_CAT:=cat} +: ${TOOL_SED:=sed} +CONF=${1:-dhcpcd-definitions.conf} +CONF_SMALL=${2:-dhcpcd-definitions.conf} +C=${3:-dhcpcd-embedded.c.in} + +$TOOL_CAT $C +echo "#ifdef SMALL" +$TOOL_SED \ + -e 's/#.*$//' \ + -e '/^$/d' \ + -e 's/^/"/g' \ + -e 's/$/\\n\"/g' \ + -e 's/ [ ]*/ /g' \ + -e 's/ [ ]*/ /g' \ + $CONF_SMALL +echo "#else" +$TOOL_SED \ + -e 's/#.*$//' \ + -e '/^$/d' \ + -e 's/^/"/g' \ + -e 's/$/\\n"/g' \ + -e 's/ [ ]*/ /g' \ + -e 's/ [ ]*/ /g' \ + $CONF +echo "#endif" +printf "%s\n%s\n" '"\0";' Index: src/genembedh =================================================================== --- /dev/null +++ src/genembedh @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +: ${TOOL_SED:=sed} +: ${TOOL_GREP:=grep} +: ${TOOL_WC:=wc} +CONF=${1:-dhcpcd-definitions.conf} +CONF_SMALL=${2:-dhcpcd-definitions-small.conf} +H=${3:-dhcpcd-embedded.h.in} + +INITDEFINES=$($TOOL_GREP "^define " $CONF | $TOOL_WC -l) +INITDEFINENDS=$($TOOL_GREP "^definend " $CONF | $TOOL_WC -l) +INITDEFINE6S=$($TOOL_GREP "^define6 " $CONF | $TOOL_WC -l) +INITDEFINES_SMALL=$($TOOL_GREP "^define " $CONF_SMALL | $TOOL_WC -l) +INITDEFINENDS_SMALL=$($TOOL_GREP "^definend " $CONF_SMALL | $TOOL_WC -l) +INITDEFINE6S_SMALL=$($TOOL_GREP "^define6 " $CONF_SMALL | $TOOL_WC -l) +$TOOL_SED \ + -e "s/@INITDEFINES@/$INITDEFINES/" \ + -e "s/@INITDEFINENDS@/$INITDEFINENDS/" \ + -e "s/@INITDEFINE6S@/$INITDEFINE6S/" \ + -e "s/@INITDEFINES_SMALL@/$INITDEFINES_SMALL/" \ + -e "s/@INITDEFINENDS_SMALL@/$INITDEFINENDS_SMALL/" \ + -e "s/@INITDEFINE6S_SMALL@/$INITDEFINE6S_SMALL/" \ + $H Index: src/if-bsd.c =================================================================== --- /dev/null +++ src/if-bsd.c @@ -0,0 +1,1917 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * BSD interface driver for dhcpcd + * Copyright (c) 2006-2021 Roy Marples + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __NetBSD__ +#include /* Needs netinet/if_ether.h */ +#elif defined(__DragonFly__) +#include +#else +#include +#endif +#ifdef __DragonFly__ +# include +#else +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(OpenBSD) && OpenBSD >= 201411 +/* OpenBSD dropped the global setting from sysctl but left the #define + * which causes a EPERM error when trying to use it. + * I think both the error and keeping the define are wrong, so we #undef it. */ +#undef IPV6CTL_ACCEPT_RTADV +#endif + +#include "common.h" +#include "dhcp.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "route.h" +#include "sa.h" + +#ifndef RT_ROUNDUP +#define RT_ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) +#define RT_ADVANCE(x, n) (x += RT_ROUNDUP((n)->sa_len)) +#endif + +/* Ignore these interface names which look like ethernet but are virtual or + * just won't work without explicit configuration. */ +static const char * const ifnames_ignore[] = { + "bridge", + "fwe", /* Firewire */ + "fwip", /* Firewire */ + "tap", + "vether", + "xvif", /* XEN DOM0 -> guest interface */ + NULL +}; + +struct priv { + int pf_inet6_fd; +}; + +struct rtm +{ + struct rt_msghdr hdr; + char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; +}; + +int +os_init(void) +{ + return 0; +} + +int +if_init(__unused struct interface *iface) +{ + /* BSD promotes secondary address by default */ + return 0; +} + +int +if_conf(__unused struct interface *iface) +{ + /* No extra checks needed on BSD */ + return 0; +} + +int +if_opensockets_os(struct dhcpcd_ctx *ctx) +{ + struct priv *priv; + int n; +#if defined(RO_MSGFILTER) || defined(ROUTE_MSGFILTER) + unsigned char msgfilter[] = { + RTM_IFINFO, +#ifdef RTM_IFANNOUNCE + RTM_IFANNOUNCE, +#endif + RTM_ADD, RTM_CHANGE, RTM_DELETE, RTM_MISS, +#ifdef RTM_CHGADDR + RTM_CHGADDR, +#endif + RTM_NEWADDR, RTM_DELADDR + }; +#ifdef ROUTE_MSGFILTER + unsigned int i, msgfilter_mask; +#endif +#endif + + if ((priv = malloc(sizeof(*priv))) == NULL) + return -1; + ctx->priv = priv; + +#ifdef INET6 + priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); +#ifdef PRIVSEP_RIGHTS + if (IN_PRIVSEP(ctx)) + ps_rights_limit_ioctl(priv->pf_inet6_fd); +#endif + /* Don't return an error so we at least work on kernels witout INET6 + * even though we expect INET6 support. + * We will fail noisily elsewhere anyway. */ +#else + priv->pf_inet6_fd = -1; +#endif + + ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_CXNB, AF_UNSPEC); + if (ctx->link_fd == -1) + return -1; + +#ifdef SO_RERROR + n = 1; + if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RERROR, &n,sizeof(n)) == -1) + logerr("%s: SO_RERROR", __func__); +#endif + + /* Ignore our own route(4) messages. + * Sadly there is no way of doing this for route(4) messages + * generated from addresses we add/delete. */ + n = 0; + if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK, + &n, sizeof(n)) == -1) + logerr("%s: SO_USELOOPBACK", __func__); + +#if defined(RO_MSGFILTER) + if (setsockopt(ctx->link_fd, PF_ROUTE, RO_MSGFILTER, + &msgfilter, sizeof(msgfilter)) == -1) + logerr(__func__); +#elif defined(ROUTE_MSGFILTER) + /* Convert the array into a bitmask. */ + msgfilter_mask = 0; + for (i = 0; i < __arraycount(msgfilter); i++) + msgfilter_mask |= ROUTE_FILTER(msgfilter[i]); + if (setsockopt(ctx->link_fd, PF_ROUTE, ROUTE_MSGFILTER, + &msgfilter_mask, sizeof(msgfilter_mask)) == -1) + logerr(__func__); +#else +#warning kernel does not support route message filtering +#endif + +#ifdef PRIVSEP_RIGHTS + /* We need to getsockopt for SO_RCVBUF and + * setsockopt for RO_MISSFILTER. */ + if (IN_PRIVSEP(ctx)) + ps_rights_limit_fd_sockopt(ctx->link_fd); +#endif + + return 0; +} + +void +if_closesockets_os(struct dhcpcd_ctx *ctx) +{ + struct priv *priv; + + priv = (struct priv *)ctx->priv; + if (priv->pf_inet6_fd != -1) + close(priv->pf_inet6_fd); + free(priv); + ctx->priv = NULL; + free(ctx->rt_missfilter); +} + +#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ +static int +if_ioctllink(struct dhcpcd_ctx *ctx, unsigned long req, void *data, size_t len) +{ + int s; + int retval; + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return (int)ps_root_ioctllink(ctx, req, data, len); +#else + UNUSED(ctx); +#endif + + s = socket(PF_LINK, SOCK_DGRAM, 0); + if (s == -1) + return -1; + retval = ioctl(s, req, data, len); + close(s); + return retval; +} +#endif + +int +if_setmac(struct interface *ifp, void *mac, uint8_t maclen) +{ + + if (ifp->hwlen != maclen) { + errno = EINVAL; + return -1; + } + +#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ + struct if_laddrreq iflr = { .flags = IFLR_ACTIVE }; + struct sockaddr_dl *sdl = satosdl(&iflr.addr); + int retval; + + strlcpy(iflr.iflr_name, ifp->name, sizeof(iflr.iflr_name)); + sdl->sdl_family = AF_LINK; + sdl->sdl_len = sizeof(*sdl); + sdl->sdl_alen = maclen; + memcpy(LLADDR(sdl), mac, maclen); + retval = if_ioctllink(ifp->ctx, SIOCALIFADDR, &iflr, sizeof(iflr)); + + /* Try and remove the old address */ + memcpy(LLADDR(sdl), ifp->hwaddr, ifp->hwlen); + if_ioctllink(ifp->ctx, SIOCDLIFADDR, &iflr, sizeof(iflr)); + + return retval; +#else + struct ifreq ifr = { + .ifr_addr.sa_family = AF_LINK, + .ifr_addr.sa_len = maclen, + }; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + memcpy(ifr.ifr_addr.sa_data, mac, maclen); + return if_ioctl(ifp->ctx, SIOCSIFLLADDR, &ifr, sizeof(ifr)); +#endif +} + +static bool +if_ignore1(const char *drvname) +{ + const char * const *p; + + for (p = ifnames_ignore; *p; p++) { + if (strcmp(*p, drvname) == 0) + return true; + } + return false; +} + +#ifdef SIOCGIFGROUP +int +if_ignoregroup(int s, const char *ifname) +{ + struct ifgroupreq ifgr = { .ifgr_len = 0 }; + struct ifg_req *ifg; + size_t ifg_len; + + /* Sadly it is possible to remove the device name + * from the interface groups, but hopefully this + * will be very unlikely.... */ + + strlcpy(ifgr.ifgr_name, ifname, sizeof(ifgr.ifgr_name)); + if (ioctl(s, SIOCGIFGROUP, &ifgr) == -1 || + (ifgr.ifgr_groups = malloc(ifgr.ifgr_len)) == NULL || + ioctl(s, SIOCGIFGROUP, &ifgr) == -1) + { + logerr(__func__); + return -1; + } + + for (ifg = ifgr.ifgr_groups, ifg_len = ifgr.ifgr_len; + ifg && ifg_len >= sizeof(*ifg); + ifg++, ifg_len -= sizeof(*ifg)) + { + if (if_ignore1(ifg->ifgrq_group)) + return 1; + } + return 0; +} +#endif + +bool +if_ignore(struct dhcpcd_ctx *ctx, const char *ifname) +{ + struct if_spec spec; + + if (if_nametospec(ifname, &spec) != 0) + return false; + + if (if_ignore1(spec.drvname)) + return true; + +#ifdef SIOCGIFGROUP +#if defined(PRIVSEP) && defined(HAVE_PLEDGE) + if (IN_PRIVSEP(ctx)) + return ps_root_ifignoregroup(ctx, ifname) == 1 ? true : false; +#endif + else + return if_ignoregroup(ctx->pf_inet_fd, ifname) == 1 ? + true : false; +#else + UNUSED(ctx); + return false; +#endif +} + +static int if_indirect_ioctl(struct dhcpcd_ctx *ctx, + const char *ifname, unsigned long cmd, void *data, size_t len) +{ + struct ifreq ifr = { .ifr_flags = 0 }; + +#if defined(PRIVSEP) && (defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)) + if (IN_PRIVSEP(ctx)) + return (int)ps_root_indirectioctl(ctx, cmd, ifname, data, len); +#else + UNUSED(len); +#endif + + strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_data = data; + return ioctl(ctx->pf_inet_fd, cmd, &ifr); +} + +int +if_carrier(struct interface *ifp, const void *ifadata) +{ + const struct if_data *ifi = ifadata; + + /* + * Every BSD returns this and it is the sole source of truth. + * Not all BSD's support SIOCGIFDATA and not all interfaces + * support SIOCGIFMEDIA. + */ + assert(ifadata != NULL); + + if (ifi->ifi_link_state >= LINK_STATE_UP) + return LINK_UP; + if (ifi->ifi_link_state == LINK_STATE_UNKNOWN) { + /* + * Work around net80211 issues in some BSDs. + * Wireless MUST support link state change. + */ + if (ifp->wireless) + return LINK_DOWN; + return LINK_UNKNOWN; + } + return LINK_DOWN; +} + +bool +if_roaming(struct interface *ifp) +{ + +/* Check for NetBSD as a safety measure. + * If other BSD's gain IN_IFF_TENTATIVE check they re-do DAD + * when the carrier comes up again. */ +#if defined(IN_IFF_TENTATIVE) && defined(__NetBSD__) + return ifp->flags & IFF_UP && ifp->carrier == LINK_DOWN; +#else + UNUSED(ifp); + return false; +#endif +} + +static void +if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp) +{ + + memset(sdl, 0, sizeof(*sdl)); + sdl->sdl_family = AF_LINK; + sdl->sdl_len = sizeof(*sdl); + sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0; + sdl->sdl_index = (unsigned short)ifp->index; +} + +static int +if_getssid1(struct dhcpcd_ctx *ctx, const char *ifname, void *ssid) +{ + int retval = -1; +#if defined(SIOCG80211NWID) + struct ieee80211_nwid nwid; +#elif defined(IEEE80211_IOC_SSID) + struct ieee80211req ireq; + char nwid[IEEE80211_NWID_LEN]; +#endif + +#if defined(SIOCG80211NWID) /* NetBSD */ + memset(&nwid, 0, sizeof(nwid)); + if (if_indirect_ioctl(ctx, ifname, SIOCG80211NWID, + &nwid, sizeof(nwid)) == 0) + { + if (ssid == NULL) + retval = nwid.i_len; + else if (nwid.i_len > IF_SSIDLEN) + errno = ENOBUFS; + else { + retval = nwid.i_len; + memcpy(ssid, nwid.i_nwid, nwid.i_len); + } + } +#elif defined(IEEE80211_IOC_SSID) /* FreeBSD */ + memset(&ireq, 0, sizeof(ireq)); + strlcpy(ireq.i_name, ifname, sizeof(ireq.i_name)); + ireq.i_type = IEEE80211_IOC_SSID; + ireq.i_val = -1; + memset(nwid, 0, sizeof(nwid)); + ireq.i_data = &nwid; + if (ioctl(ctx->pf_inet_fd, SIOCG80211, &ireq) == 0) { + if (ssid == NULL) + retval = ireq.i_len; + else if (ireq.i_len > IF_SSIDLEN) + errno = ENOBUFS; + else { + retval = ireq.i_len; + memcpy(ssid, nwid, ireq.i_len); + } + } +#else + errno = ENOSYS; +#endif + + return retval; +} + +int +if_getssid(struct interface *ifp) +{ + int r; + + r = if_getssid1(ifp->ctx, ifp->name, ifp->ssid); + if (r != -1) + ifp->ssid_len = (unsigned int)r; + else + ifp->ssid_len = 0; + ifp->ssid[ifp->ssid_len] = '\0'; + return r; +} + +/* + * FreeBSD allows for Virtual Access Points + * We need to check if the interface is a Virtual Interface Master + * and if so, don't use it. + * This check is made by virtue of being a IEEE80211 device but + * returning the SSID gives an error. + */ +int +if_vimaster(struct dhcpcd_ctx *ctx, const char *ifname) +{ + int r; + struct ifmediareq ifmr = { .ifm_active = 0 }; + + strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); + r = ioctl(ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr); + if (r == -1) + return -1; + if (ifmr.ifm_status & IFM_AVALID && + IFM_TYPE(ifmr.ifm_active) == IFM_IEEE80211) + { + if (if_getssid1(ctx, ifname, NULL) == -1) + return 1; + } + return 0; +} + +unsigned short +if_vlanid(const struct interface *ifp) +{ +#ifdef SIOCGETVLAN + struct vlanreq vlr = { .vlr_tag = 0 }; + + if (if_indirect_ioctl(ifp->ctx, ifp->name, SIOCGETVLAN, + &vlr, sizeof(vlr)) != 0) + return 0; /* 0 means no VLANID */ + return vlr.vlr_tag; +#elif defined(SIOCGVNETID) + struct ifreq ifr = { .ifr_vnetid = 0 }; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(ifp->ctx->pf_inet_fd, SIOCGVNETID, &ifr) != 0) + return 0; /* 0 means no VLANID */ + return ifr.ifr_vnetid; +#else + UNUSED(ifp); + return 0; /* 0 means no VLANID */ +#endif +} + +static int +get_addrs(int type, const void *data, size_t data_len, + const struct sockaddr **sa) +{ + const char *cp, *ep; + int i; + + cp = data; + ep = cp + data_len; + for (i = 0; i < RTAX_MAX; i++) { + if (type & (1 << i)) { + if (cp >= ep) { + errno = EINVAL; + return -1; + } + sa[i] = (const struct sockaddr *)cp; + RT_ADVANCE(cp, sa[i]); + } else + sa[i] = NULL; + } + + return 0; +} + +static struct interface * +if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl) +{ + + if (sdl->sdl_index) + return if_findindex(ctx->ifaces, sdl->sdl_index); + + if (sdl->sdl_nlen) { + char ifname[IF_NAMESIZE]; + + memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen); + ifname[sdl->sdl_nlen] = '\0'; + return if_find(ctx->ifaces, ifname); + } + if (sdl->sdl_alen) { + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->hwlen == sdl->sdl_alen && + memcmp(ifp->hwaddr, + sdl->sdl_data, sdl->sdl_alen) == 0) + return ifp; + } + } + + errno = ENOENT; + return NULL; +} + +static struct interface * +if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa) +{ + if (sa == NULL) { + errno = EINVAL; + return NULL; + } + + switch (sa->sa_family) { + case AF_LINK: + { + const struct sockaddr_dl *sdl; + + sdl = (const void *)sa; + return if_findsdl(ctx, sdl); + } +#ifdef INET + case AF_INET: + { + const struct sockaddr_in *sin; + struct ipv4_addr *ia; + + sin = (const void *)sa; + if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr))) + return ia->iface; + if ((ia = ipv4_findmaskbrd(ctx, &sin->sin_addr))) + return ia->iface; + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + const struct sockaddr_in6 *sin; + unsigned int scope; + struct ipv6_addr *ia; + + sin = (const void *)sa; + scope = ipv6_getscope(sin); + if (scope != 0) + return if_findindex(ctx->ifaces, scope); + if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr))) + return ia->iface; + break; + } +#endif + default: + errno = EAFNOSUPPORT; + return NULL; + } + + errno = ENOENT; + return NULL; +} + +static void +if_copysa(struct sockaddr *dst, const struct sockaddr *src) +{ + + assert(dst != NULL); + assert(src != NULL); + + memcpy(dst, src, src->sa_len); +#if defined(INET6) && defined(__KAME__) + if (dst->sa_family == AF_INET6) { + struct in6_addr *in6; + + in6 = &satosin6(dst)->sin6_addr; + if (IN6_IS_ADDR_LINKLOCAL(in6)) + in6->s6_addr[2] = in6->s6_addr[3] = '\0'; + } +#endif +} + +int +if_route(unsigned char cmd, const struct rt *rt) +{ + struct dhcpcd_ctx *ctx; + struct rtm rtmsg; + struct rt_msghdr *rtm = &rtmsg.hdr; + char *bp = rtmsg.buffer; + struct sockaddr_dl sdl; + bool gateway_unspec; + + assert(rt != NULL); + assert(rt->rt_ifp != NULL); + assert(rt->rt_ifp->ctx != NULL); + ctx = rt->rt_ifp->ctx; + +#define ADDSA(sa) do { \ + memcpy(bp, (sa), (sa)->sa_len); \ + bp += RT_ROUNDUP((sa)->sa_len); \ + } while (0 /* CONSTCOND */) + + memset(&rtmsg, 0, sizeof(rtmsg)); + rtm->rtm_version = RTM_VERSION; + rtm->rtm_type = cmd; +#ifdef __OpenBSD__ + rtm->rtm_pid = getpid(); +#endif + rtm->rtm_seq = ++ctx->seq; + rtm->rtm_flags = (int)rt->rt_flags; + rtm->rtm_addrs = RTA_DST; +#ifdef RTF_PINNED + if (cmd != RTM_ADD) + rtm->rtm_flags |= RTF_PINNED; +#endif + + gateway_unspec = sa_is_unspecified(&rt->rt_gateway); + + if (cmd == RTM_ADD || cmd == RTM_CHANGE) { + bool netmask_bcast = sa_is_allones(&rt->rt_netmask); + + rtm->rtm_flags |= RTF_UP; + rtm->rtm_addrs |= RTA_GATEWAY; + if (!(rtm->rtm_flags & RTF_REJECT) && + !sa_is_loopback(&rt->rt_gateway)) + { + rtm->rtm_index = (unsigned short)rt->rt_ifp->index; +/* + * OpenBSD rejects the message for on-link routes. + * FreeBSD-12 kernel apparently panics. + * I can't replicate the panic, but better safe than sorry! + * https://roy.marples.name/archives/dhcpcd-discuss/0002286.html + * + * Neither OS currently allows IPv6 address sharing anyway, so let's + * try to encourage someone to fix that by logging a waring during compile. + */ +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#warning kernel does not allow IPv6 address sharing + if (!gateway_unspec || rt->rt_dest.sa_family!=AF_INET6) +#endif + rtm->rtm_addrs |= RTA_IFP; + if (!sa_is_unspecified(&rt->rt_ifa)) + rtm->rtm_addrs |= RTA_IFA; + } + if (netmask_bcast) + rtm->rtm_flags |= RTF_HOST; + /* Network routes are cloning or connected if supported. + * All other routes are static. */ + if (gateway_unspec) { +#ifdef RTF_CLONING + rtm->rtm_flags |= RTF_CLONING; +#endif +#ifdef RTF_CONNECTED + rtm->rtm_flags |= RTF_CONNECTED; +#endif +#ifdef RTP_CONNECTED + rtm->rtm_priority = RTP_CONNECTED; +#endif +#ifdef RTF_CLONING + if (netmask_bcast) { + /* + * We add a cloning network route for a single + * host. Traffic to the host will generate a + * cloned route and the hardware address will + * resolve correctly. + * It might be more correct to use RTF_HOST + * instead of RTF_CLONING, and that does work, + * but some OS generate an arp warning + * diagnostic which we don't want to do. + */ + rtm->rtm_flags &= ~RTF_HOST; + } +#endif + } else + rtm->rtm_flags |= RTF_GATEWAY; + + if (rt->rt_dflags & RTDF_STATIC) + rtm->rtm_flags |= RTF_STATIC; + + if (rt->rt_mtu != 0) { + rtm->rtm_inits |= RTV_MTU; + rtm->rtm_rmx.rmx_mtu = rt->rt_mtu; + } + } + + if (!(rtm->rtm_flags & RTF_HOST)) + rtm->rtm_addrs |= RTA_NETMASK; + + if_linkaddr(&sdl, rt->rt_ifp); + + ADDSA(&rt->rt_dest); + + if (rtm->rtm_addrs & RTA_GATEWAY) { + if (gateway_unspec) + ADDSA((struct sockaddr *)&sdl); + else { + union sa_ss gateway; + + if_copysa(&gateway.sa, &rt->rt_gateway); +#ifdef INET6 + if (gateway.sa.sa_family == AF_INET6) + ipv6_setscope(&gateway.sin6, rt->rt_ifp->index); +#endif + ADDSA(&gateway.sa); + } + } + + if (rtm->rtm_addrs & RTA_NETMASK) + ADDSA(&rt->rt_netmask); + + if (rtm->rtm_addrs & RTA_IFP) + ADDSA((struct sockaddr *)&sdl); + + if (rtm->rtm_addrs & RTA_IFA) + ADDSA(&rt->rt_ifa); + +#undef ADDSA + + rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm); + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) { + if (ps_root_route(ctx, rtm, rtm->rtm_msglen) == -1) + return -1; + return 0; + } +#endif + if (write(ctx->link_fd, rtm, rtm->rtm_msglen) == -1) + return -1; + return 0; +} + +static bool +if_realroute(const struct rt_msghdr *rtm) +{ + +#ifdef RTF_CLONED + if (rtm->rtm_flags & RTF_CLONED) + return false; +#endif +#ifdef RTF_WASCLONED + if (rtm->rtm_flags & RTF_WASCLONED) + return false; +#endif +#ifdef RTF_LOCAL + if (rtm->rtm_flags & RTF_LOCAL) + return false; +#endif +#ifdef RTF_BROADCAST + if (rtm->rtm_flags & RTF_BROADCAST) + return false; +#endif + return true; +} + +static int +if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm) +{ + const struct sockaddr *rti_info[RTAX_MAX]; + + if (!(rtm->rtm_addrs & RTA_DST)) { + errno = EINVAL; + return -1; + } + if (rtm->rtm_type != RTM_MISS && !(rtm->rtm_addrs & RTA_GATEWAY)) { + errno = EINVAL; + return -1; + } + + if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm), + rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1) + return -1; + memset(rt, 0, sizeof(*rt)); + + rt->rt_flags = (unsigned int)rtm->rtm_flags; + if_copysa(&rt->rt_dest, rti_info[RTAX_DST]); + if (rtm->rtm_addrs & RTA_NETMASK) { + if_copysa(&rt->rt_netmask, rti_info[RTAX_NETMASK]); + if (rt->rt_netmask.sa_family == 255) /* Why? */ + rt->rt_netmask.sa_family = rt->rt_dest.sa_family; + } + + /* dhcpcd likes an unspecified gateway to indicate via the link. + * However we need to know if gateway was a link with an address. */ + if (rtm->rtm_addrs & RTA_GATEWAY) { + if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) { + const struct sockaddr_dl *sdl; + + sdl = (const struct sockaddr_dl*) + (const void *)rti_info[RTAX_GATEWAY]; + if (sdl->sdl_alen != 0) + rt->rt_dflags |= RTDF_GATELINK; + } else if (rtm->rtm_flags & RTF_GATEWAY) + if_copysa(&rt->rt_gateway, rti_info[RTAX_GATEWAY]); + } + + if (rtm->rtm_addrs & RTA_IFA) + if_copysa(&rt->rt_ifa, rti_info[RTAX_IFA]); + + rt->rt_mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu; + + if (rtm->rtm_index) + rt->rt_ifp = if_findindex(ctx->ifaces, rtm->rtm_index); + else if (rtm->rtm_addrs & RTA_IFP) + rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_IFP]); + else if (rtm->rtm_addrs & RTA_GATEWAY) + rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_GATEWAY]); + else + rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_DST]); + + if (rt->rt_ifp == NULL && rtm->rtm_type == RTM_MISS) + rt->rt_ifp = if_find(ctx->ifaces, "lo0"); + + if (rt->rt_ifp == NULL) { + errno = ESRCH; + return -1; + } + return 0; +} + +int +if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af) +{ + struct rt_msghdr *rtm; + int mib[6]; + size_t needed; + char *buf, *p, *end; + struct rt rt, *rtn; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = af; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + + if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1) + return -1; + if (needed == 0) + return 0; + if ((buf = malloc(needed)) == NULL) + return -1; + if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1) { + free(buf); + return -1; + } + + end = buf + needed; + for (p = buf; p < end; p += rtm->rtm_msglen) { + rtm = (void *)p; + if (p + rtm->rtm_msglen >= end) { + errno = EINVAL; + break; + } + if (!if_realroute(rtm)) + continue; + if (if_copyrt(ctx, &rt, rtm) != 0) + continue; + if ((rtn = rt_new(rt.rt_ifp)) == NULL) { + logerr(__func__); + break; + } + memcpy(rtn, &rt, sizeof(*rtn)); + if (rb_tree_insert_node(kroutes, rtn) != rtn) + rt_free(rtn); + } + free(buf); + return p == end ? 0 : -1; +} + +#ifdef INET +int +if_address(unsigned char cmd, const struct ipv4_addr *ia) +{ + int r; + struct in_aliasreq ifra; + struct dhcpcd_ctx *ctx = ia->iface->ctx; + + memset(&ifra, 0, sizeof(ifra)); + strlcpy(ifra.ifra_name, ia->iface->name, sizeof(ifra.ifra_name)); + +#define ADDADDR(var, addr) do { \ + (var)->sin_family = AF_INET; \ + (var)->sin_len = sizeof(*(var)); \ + (var)->sin_addr = *(addr); \ + } while (/*CONSTCOND*/0) + ADDADDR(&ifra.ifra_addr, &ia->addr); + ADDADDR(&ifra.ifra_mask, &ia->mask); + if (cmd == RTM_NEWADDR && ia->brd.s_addr != INADDR_ANY) + ADDADDR(&ifra.ifra_broadaddr, &ia->brd); +#undef ADDADDR + + r = if_ioctl(ctx, + cmd == RTM_DELADDR ? SIOCDIFADDR : SIOCAIFADDR, &ifra,sizeof(ifra)); + return r; +} + +#if !(defined(HAVE_IFADDRS_ADDRFLAGS) && defined(HAVE_IFAM_ADDRFLAGS)) +int +if_addrflags(const struct interface *ifp, const struct in_addr *addr, + __unused const char *alias) +{ +#ifdef SIOCGIFAFLAG_IN + struct ifreq ifr; + struct sockaddr_in *sin; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + sin = (void *)&ifr.ifr_addr; + sin->sin_family = AF_INET; + sin->sin_addr = *addr; + if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFAFLAG_IN, &ifr) == -1) + return -1; + return ifr.ifr_addrflags; +#else + UNUSED(ifp); + UNUSED(addr); + return 0; +#endif +} +#endif +#endif /* INET */ + +#ifdef INET6 +static int +if_ioctl6(struct dhcpcd_ctx *ctx, unsigned long req, void *data, size_t len) +{ + struct priv *priv; + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return (int)ps_root_ioctl6(ctx, req, data, len); +#endif + + priv = ctx->priv; + return ioctl(priv->pf_inet6_fd, req, data, len); +} + +int +if_address6(unsigned char cmd, const struct ipv6_addr *ia) +{ + struct in6_aliasreq ifa = { .ifra_flags = 0 }; + struct in6_addr mask; + struct dhcpcd_ctx *ctx = ia->iface->ctx; + + strlcpy(ifa.ifra_name, ia->iface->name, sizeof(ifa.ifra_name)); +#if defined(__FreeBSD__) || defined(__DragonFly__) + /* This is a bug - the kernel should work this out. */ + if (ia->addr_flags & IN6_IFF_TENTATIVE) + ifa.ifra_flags |= IN6_IFF_TENTATIVE; +#endif +#if (defined(__NetBSD__) || defined(__OpenBSD__)) && \ + (defined(IPV6CTL_ACCEPT_RTADV) || defined(ND6_IFF_ACCEPT_RTADV)) + /* These kernels don't accept userland setting IN6_IFF_AUTOCONF */ +#else + if (ia->flags & IPV6_AF_AUTOCONF) + ifa.ifra_flags |= IN6_IFF_AUTOCONF; +#endif +#ifdef IPV6_MANAGETEMPADDR + if (ia->flags & IPV6_AF_TEMPORARY) + ifa.ifra_flags |= IN6_IFF_TEMPORARY; +#endif + +#define ADDADDR(v, addr) { \ + (v)->sin6_family = AF_INET6; \ + (v)->sin6_len = sizeof(*v); \ + (v)->sin6_addr = *(addr); \ + } + + ADDADDR(&ifa.ifra_addr, &ia->addr); + ipv6_setscope(&ifa.ifra_addr, ia->iface->index); + ipv6_mask(&mask, ia->prefix_len); + ADDADDR(&ifa.ifra_prefixmask, &mask); + +#undef ADDADDR + + /* + * Every BSD kernel wants to add the prefix of the address to it's + * list of RA received prefixes. + * THIS IS WRONG because there (as the comments in the kernel state) + * is no API for managing prefix lifetime and the kernel should not + * pretend it's from a RA either. + * + * The issue is that the very first assigned prefix will inherit the + * lifetime of the address, but any subsequent alteration of the + * address OR it's lifetime will not affect the prefix lifetime. + * As such, we cannot stop the prefix from timing out and then + * constantly removing the prefix route dhcpcd is capable of adding + * in it's absense. + * + * What we can do to mitigate the issue is to add the address with + * infinite lifetimes, so the prefix route will never time out. + * Once done, we can then set lifetimes on the address and all is good. + * The downside of this approach is that we need to manually remove + * the kernel route because it has no lifetime, but this is OK as + * dhcpcd will handle this too. + * + * This issue is discussed on the NetBSD mailing lists here: + * http://mail-index.netbsd.org/tech-net/2016/08/05/msg006044.html + * + * Fixed in NetBSD-7.99.36 + * NOT fixed in FreeBSD - bug 195197 + * Fixed in OpenBSD-5.9 + */ + +#if !((defined(__NetBSD_Version__) && __NetBSD_Version__ >= 799003600) || \ + (defined(__OpenBSD__) && OpenBSD >= 201605)) + if (cmd == RTM_NEWADDR && !(ia->flags & IPV6_AF_ADDED)) { + ifa.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + ifa.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + (void)if_ioctl6(ctx, SIOCAIFADDR_IN6, &ifa, sizeof(ifa)); + } +#endif + +#if defined(__OpenBSD__) && OpenBSD <= 201705 + /* BUT OpenBSD older than 6.2 does not reset the address lifetime + * for subsequent calls... + * Luckily dhcpcd will remove the lease when it expires so + * just set an infinite lifetime, unless a temporary address. */ + if (ifa.ifra_flags & IN6_IFF_PRIVACY) { + ifa.ifra_lifetime.ia6t_vltime = ia->prefix_vltime; + ifa.ifra_lifetime.ia6t_pltime = ia->prefix_pltime; + } else { + ifa.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + ifa.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + } +#else + ifa.ifra_lifetime.ia6t_vltime = ia->prefix_vltime; + ifa.ifra_lifetime.ia6t_pltime = ia->prefix_pltime; +#endif + + return if_ioctl6(ctx, + cmd == RTM_DELADDR ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, + &ifa, sizeof(ifa)); +} + +int +if_addrflags6(const struct interface *ifp, const struct in6_addr *addr, + __unused const char *alias) +{ + int flags; + struct in6_ifreq ifr6; + struct priv *priv; + + memset(&ifr6, 0, sizeof(ifr6)); + strlcpy(ifr6.ifr_name, ifp->name, sizeof(ifr6.ifr_name)); + ifr6.ifr_addr.sin6_family = AF_INET6; + ifr6.ifr_addr.sin6_addr = *addr; + ipv6_setscope(&ifr6.ifr_addr, ifp->index); + priv = (struct priv *)ifp->ctx->priv; + if (ioctl(priv->pf_inet6_fd, SIOCGIFAFLAG_IN6, &ifr6) != -1) + flags = ifr6.ifr_ifru.ifru_flags6; + else + flags = -1; + return flags; +} + +int +if_getlifetime6(struct ipv6_addr *ia) +{ + struct in6_ifreq ifr6; + time_t t; + struct in6_addrlifetime *lifetime; + struct priv *priv; + + memset(&ifr6, 0, sizeof(ifr6)); + strlcpy(ifr6.ifr_name, ia->iface->name, sizeof(ifr6.ifr_name)); + ifr6.ifr_addr.sin6_family = AF_INET6; + ifr6.ifr_addr.sin6_addr = ia->addr; + ipv6_setscope(&ifr6.ifr_addr, ia->iface->index); + priv = (struct priv *)ia->iface->ctx->priv; + if (ioctl(priv->pf_inet6_fd, SIOCGIFALIFETIME_IN6, &ifr6) == -1) + return -1; + clock_gettime(CLOCK_MONOTONIC, &ia->created); + +#if defined(__FreeBSD__) || defined(__DragonFly__) + t = ia->created.tv_sec; +#else + t = time(NULL); +#endif + + lifetime = &ifr6.ifr_ifru.ifru_lifetime; + if (lifetime->ia6t_preferred) + ia->prefix_pltime = (uint32_t)(lifetime->ia6t_preferred - + MIN(t, lifetime->ia6t_preferred)); + else + ia->prefix_pltime = ND6_INFINITE_LIFETIME; + if (lifetime->ia6t_expire) { + ia->prefix_vltime = (uint32_t)(lifetime->ia6t_expire - + MIN(t, lifetime->ia6t_expire)); + /* Calculate the created time */ + ia->created.tv_sec -= lifetime->ia6t_vltime - ia->prefix_vltime; + } else + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + return 0; +} +#endif + +static int +if_announce(struct dhcpcd_ctx *ctx, const struct if_announcemsghdr *ifan) +{ + + if (ifan->ifan_msglen < sizeof(*ifan)) { + errno = EINVAL; + return -1; + } + + switch(ifan->ifan_what) { + case IFAN_ARRIVAL: + return dhcpcd_handleinterface(ctx, 1, ifan->ifan_name); + case IFAN_DEPARTURE: + return dhcpcd_handleinterface(ctx, -1, ifan->ifan_name); + } + + return 0; +} + +static int +if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm) +{ + struct interface *ifp; + int link_state; + + if (ifm->ifm_msglen < sizeof(*ifm)) { + errno = EINVAL; + return -1; + } + + if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL) + return 0; + + link_state = if_carrier(ifp, &ifm->ifm_data); + dhcpcd_handlecarrier(ifp, link_state, (unsigned int)ifm->ifm_flags); + return 0; +} + +static int +if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) +{ + struct rt rt; + + if (rtm->rtm_msglen < sizeof(*rtm)) { + errno = EINVAL; + return -1; + } + + /* Ignore errors. */ + if (rtm->rtm_errno != 0) + return 0; + + /* Ignore messages from ourself. */ +#ifdef PRIVSEP + if (ctx->ps_root_pid != 0) { + if (rtm->rtm_pid == ctx->ps_root_pid) + return 0; + } +#endif + + if (if_copyrt(ctx, &rt, rtm) == -1) + return errno == ENOTSUP ? 0 : -1; + +#ifdef INET6 + /* + * BSD announces host routes. + * As such, we should be notified of reachability by its + * existance with a hardware address. + * Ensure we don't call this for a newly incomplete state. + */ + if (rt.rt_dest.sa_family == AF_INET6 && + (rt.rt_flags & RTF_HOST || rtm->rtm_type == RTM_MISS) && + !(rtm->rtm_type == RTM_ADD && !(rt.rt_dflags & RTDF_GATELINK))) + { + bool reachable; + + reachable = (rtm->rtm_type == RTM_ADD || + rtm->rtm_type == RTM_CHANGE) && + rt.rt_dflags & RTDF_GATELINK; + ipv6nd_neighbour(ctx, &rt.rt_ss_dest.sin6.sin6_addr, reachable); + } +#endif + + if (rtm->rtm_type != RTM_MISS && if_realroute(rtm)) + rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid); + return 0; +} + +static int +if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam) +{ + struct interface *ifp; + const struct sockaddr *rti_info[RTAX_MAX]; + int flags; + pid_t pid; + + if (ifam->ifam_msglen < sizeof(*ifam)) { + errno = EINVAL; + return -1; + } + +#ifdef HAVE_IFAM_PID + /* Ignore address deletions from ourself. + * We need to process address flag changes though. */ + if (ifam->ifam_type == RTM_DELADDR) { +#ifdef PRIVSEP + if (ctx->ps_root_pid != 0) { + if (ifam->ifam_pid == ctx->ps_root_pid) + return 0; + } else +#endif + /* address management is done via ioctl, + * so SO_USELOOPBACK has no effect, + * so we do need to check the pid. */ + if (ifam->ifam_pid == getpid()) + return 0; + } + pid = ifam->ifam_pid; +#else + pid = 0; +#endif + + if (~ifam->ifam_addrs & RTA_IFA) + return 0; + if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL) + return 0; + + if (get_addrs(ifam->ifam_addrs, (const char *)ifam + sizeof(*ifam), + ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1) + return -1; + + switch (rti_info[RTAX_IFA]->sa_family) { + case AF_LINK: + { + struct sockaddr_dl sdl; + +#ifdef RTM_CHGADDR + if (ifam->ifam_type != RTM_CHGADDR) + break; +#else + if (ifam->ifam_type != RTM_NEWADDR) + break; +#endif + memcpy(&sdl, rti_info[RTAX_IFA], rti_info[RTAX_IFA]->sa_len); + dhcpcd_handlehwaddr(ifp, ifp->hwtype, + CLLADDR(&sdl), sdl.sdl_alen); + break; + } +#ifdef INET + case AF_INET: + case 255: /* FIXME: Why 255? */ + { + const struct sockaddr_in *sin; + struct in_addr addr, mask, bcast; + + sin = (const void *)rti_info[RTAX_IFA]; + addr.s_addr = sin != NULL && sin->sin_family == AF_INET ? + sin->sin_addr.s_addr : INADDR_ANY; + sin = (const void *)rti_info[RTAX_NETMASK]; + mask.s_addr = sin != NULL && sin->sin_family == AF_INET ? + sin->sin_addr.s_addr : INADDR_ANY; + sin = (const void *)rti_info[RTAX_BRD]; + bcast.s_addr = sin != NULL && sin->sin_family == AF_INET ? + sin->sin_addr.s_addr : INADDR_ANY; + + /* + * NetBSD-7 and older send an invalid broadcast address. + * So we need to query the actual address to get + * the right one. + * We can also use this to test if the address + * has really been added or deleted. + */ +#ifdef SIOCGIFALIAS + struct in_aliasreq ifra; + + memset(&ifra, 0, sizeof(ifra)); + strlcpy(ifra.ifra_name, ifp->name, sizeof(ifra.ifra_name)); + ifra.ifra_addr.sin_family = AF_INET; + ifra.ifra_addr.sin_len = sizeof(ifra.ifra_addr); + ifra.ifra_addr.sin_addr = addr; + if (ioctl(ctx->pf_inet_fd, SIOCGIFALIAS, &ifra) == -1) { + if (errno != ENXIO && errno != EADDRNOTAVAIL) + logerr("%s: SIOCGIFALIAS", __func__); + if (ifam->ifam_type != RTM_DELADDR) + break; + } else { + if (ifam->ifam_type == RTM_DELADDR) + break; +#if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000 + bcast = ifra.ifra_broadaddr.sin_addr; +#endif + } +#else +#warning No SIOCGIFALIAS support + /* + * No SIOCGIFALIAS? That sucks! + * This makes this call very heavy weight, but we + * really need to know if the message is late or not. + */ + const struct sockaddr *sa; + struct ifaddrs *ifaddrs = NULL, *ifa; + + sa = rti_info[RTAX_IFA]; +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(ctx)) { + if (ps_root_getifaddrs(ctx, &ifaddrs) == -1) { + logerr("ps_root_getifaddrs"); + break; + } + } else +#endif + if (getifaddrs(&ifaddrs) == -1) { + logerr("getifaddrs"); + break; + } + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (sa_cmp(ifa->ifa_addr, sa) == 0 && + strcmp(ifa->ifa_name, ifp->name) == 0) + break; + } +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(ctx)) + free(ifaddrs); + else +#endif + freeifaddrs(ifaddrs); + if (ifam->ifam_type == RTM_DELADDR) { + if (ifa != NULL) + break; + } else { + if (ifa == NULL) + break; + } +#endif + +#ifdef HAVE_IFAM_ADDRFLAGS + flags = ifam->ifam_addrflags; +#else + flags = 0; +#endif + + ipv4_handleifa(ctx, ifam->ifam_type, NULL, ifp->name, + &addr, &mask, &bcast, flags, pid); + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + struct in6_addr addr6, mask6; + const struct sockaddr_in6 *sin6; + + sin6 = (const void *)rti_info[RTAX_IFA]; + addr6 = sin6->sin6_addr; + sin6 = (const void *)rti_info[RTAX_NETMASK]; + mask6 = sin6->sin6_addr; + + /* + * If the address was deleted, lets check if it's + * a late message and it still exists (maybe modified). + * If so, ignore it as deleting an address causes + * dhcpcd to drop any lease to which it belongs. + * Also check an added address was really added. + */ + flags = if_addrflags6(ifp, &addr6, NULL); + if (flags == -1) { + if (errno != ENXIO && errno != EADDRNOTAVAIL) + logerr("%s: if_addrflags6", __func__); + if (ifam->ifam_type != RTM_DELADDR) + break; + flags = 0; + } else if (ifam->ifam_type == RTM_DELADDR) + break; + +#ifdef __KAME__ + if (IN6_IS_ADDR_LINKLOCAL(&addr6)) + /* Remove the scope from the address */ + addr6.s6_addr[2] = addr6.s6_addr[3] = '\0'; +#endif + + ipv6_handleifa(ctx, ifam->ifam_type, NULL, + ifp->name, &addr6, ipv6_prefixlen(&mask6), flags, pid); + break; + } +#endif + } + + return 0; +} + +static int +if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) +{ + + if (rtm->rtm_version != RTM_VERSION) + return 0; + + switch(rtm->rtm_type) { +#ifdef RTM_IFANNOUNCE + case RTM_IFANNOUNCE: + return if_announce(ctx, (const void *)rtm); +#endif + case RTM_IFINFO: + return if_ifinfo(ctx, (const void *)rtm); + case RTM_ADD: /* FALLTHROUGH */ + case RTM_CHANGE: /* FALLTHROUGH */ + case RTM_DELETE: /* FALLTHROUGH */ + case RTM_MISS: + return if_rtm(ctx, (const void *)rtm); +#ifdef RTM_CHGADDR + case RTM_CHGADDR: /* FALLTHROUGH */ +#endif + case RTM_DELADDR: /* FALLTHROUGH */ + case RTM_NEWADDR: + return if_ifa(ctx, (const void *)rtm); +#ifdef RTM_DESYNC + case RTM_DESYNC: + dhcpcd_linkoverflow(ctx); +#elif !defined(SO_RERROR) +#warning cannot detect route socket overflow within kernel +#endif + } + + return 0; +} + +static int +if_missfilter0(struct dhcpcd_ctx *ctx, struct interface *ifp, + struct sockaddr *sa) +{ + size_t salen = (size_t)RT_ROUNDUP(sa->sa_len); + size_t newlen = ctx->rt_missfilterlen + salen; + size_t diff = salen - (sa->sa_len); + uint8_t *cp; + + if (ctx->rt_missfiltersize < newlen) { + void *n = realloc(ctx->rt_missfilter, newlen); + if (n == NULL) + return -1; + ctx->rt_missfilter = n; + ctx->rt_missfiltersize = newlen; + } + +#ifdef INET6 + if (sa->sa_family == AF_INET6) + ipv6_setscope(satosin6(sa), ifp->index); +#else + UNUSED(ifp); +#endif + + cp = ctx->rt_missfilter + ctx->rt_missfilterlen; + memcpy(cp, sa, sa->sa_len); + if (diff != 0) + memset(cp + sa->sa_len, 0, diff); + ctx->rt_missfilterlen += salen; + +#ifdef INET6 + if (sa->sa_family == AF_INET6) + ipv6_setscope(satosin6(sa), 0); +#endif + + return 0; +} + +int +if_missfilter(struct interface *ifp, struct sockaddr *sa) +{ + + return if_missfilter0(ifp->ctx, ifp, sa); +} + +int +if_missfilter_apply(struct dhcpcd_ctx *ctx) +{ +#ifdef RO_MISSFILTER + if (ctx->rt_missfilterlen == 0) { + struct sockaddr sa = { + .sa_family = AF_UNSPEC, + .sa_len = sizeof(sa), + }; + + if (if_missfilter0(ctx, NULL, &sa) == -1) + return -1; + } + + return setsockopt(ctx->link_fd, PF_ROUTE, RO_MISSFILTER, + ctx->rt_missfilter, (socklen_t)ctx->rt_missfilterlen); +#else +#warning kernel does not support RTM_MISS DST filtering + UNUSED(ctx); + errno = ENOTSUP; + return -1; +#endif +} + +__CTASSERT(offsetof(struct rt_msghdr, rtm_msglen) == 0); +int +if_handlelink(struct dhcpcd_ctx *ctx) +{ + struct rtm rtm; + ssize_t len; + + len = read(ctx->link_fd, &rtm, sizeof(rtm)); + if (len == -1) + return -1; + if (len == 0) + return 0; + if ((size_t)len < sizeof(rtm.hdr.rtm_msglen) || + len != rtm.hdr.rtm_msglen) + { + errno = EINVAL; + return -1; + } + /* + * Coverity thinks that the data could be tainted from here. + * I have no idea how because the length of the data we read + * is guarded by len and checked to match rtm_msglen. + * The issue seems to be related to extracting the addresses + * at the end of the header, but seems to have no issues with the + * equivalent call in if_initrt. + */ + /* coverity[tainted_data] */ + return if_dispatch(ctx, &rtm.hdr); +} + +#ifndef SYS_NMLN /* OSX */ +# define SYS_NMLN __SYS_NAMELEN +#endif +#ifndef HW_MACHINE_ARCH +# ifdef HW_MODEL /* OpenBSD */ +# define HW_MACHINE_ARCH HW_MODEL +# endif +#endif +int +if_machinearch(char *str, size_t len) +{ + int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; + + return sysctl(mib, sizeof(mib) / sizeof(mib[0]), str, &len, NULL, 0); +} + +#ifdef INET6 +#if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \ + defined(IPV6CTL_FORWARDING) +#define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0) +#define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1) +static int +inet6_sysctl(int code, int val, int action) +{ + int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 }; + size_t size; + + mib[3] = code; + size = sizeof(val); + if (action) { + if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), + NULL, 0, &val, size) == -1) + return -1; + return 0; + } + if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &val, &size, NULL, 0) == -1) + return -1; + return val; +} +#endif + +int +if_applyra(const struct ra *rap) +{ +#ifdef SIOCSIFINFO_IN6 + struct in6_ndireq nd = { .ndi.chlim = 0 }; + struct dhcpcd_ctx *ctx = rap->iface->ctx; + int error; + + strlcpy(nd.ifname, rap->iface->name, sizeof(nd.ifname)); + +#ifdef IPV6CTL_ACCEPT_RTADV + struct priv *priv = ctx->priv; + + /* + * NetBSD changed SIOCSIFINFO_IN6 to NOT set flags when kernel + * RA was removed, however both FreeBSD and DragonFlyBSD still do. + * linkmtu was also removed. + * Hopefully this guard will still work if either remove kernel RA. + */ + if (ioctl(priv->pf_inet6_fd, SIOCGIFINFO_IN6, &nd, sizeof(nd)) == -1) + return -1; + + nd.ndi.linkmtu = rap->mtu; +#endif + + nd.ndi.chlim = rap->hoplimit; + nd.ndi.retrans = rap->retrans; + nd.ndi.basereachable = rap->reachable; + error = if_ioctl6(ctx, SIOCSIFINFO_IN6, &nd, sizeof(nd)); +#ifdef IPV6CTL_ACCEPT_RTADV + if (error == -1 && errno == EINVAL) { + /* + * Very likely that this is caused by a dodgy MTU + * setting specific to the interface. + * Let's set it to "unspecified" and try again. + * Doesn't really matter as we fix the MTU against the + * routes we add as not all OS support SIOCSIFINFO_IN6. + */ + nd.ndi.linkmtu = 0; + error = if_ioctl6(ctx, SIOCSIFINFO_IN6, &nd, sizeof(nd)); + } +#endif + return error; +#else +#warning OS does not allow setting of RA bits hoplimit, retrans or reachable + UNUSED(rap); + return 0; +#endif +} + +#ifndef IPV6CTL_FORWARDING +#define get_inet6_sysctlbyname(code) inet6_sysctlbyname(code, 0, 0) +#define set_inet6_sysctlbyname(code, val) inet6_sysctlbyname(code, val, 1) +static int +inet6_sysctlbyname(const char *name, int val, int action) +{ + size_t size; + + size = sizeof(val); + if (action) { + if (sysctlbyname(name, NULL, 0, &val, size) == -1) + return -1; + return 0; + } + if (sysctlbyname(name, &val, &size, NULL, 0) == -1) + return -1; + return val; +} +#endif + +int +ip6_forwarding(__unused const char *ifname) +{ + int val; + +#ifdef IPV6CTL_FORWARDING + val = get_inet6_sysctl(IPV6CTL_FORWARDING); +#else + val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding"); +#endif + return val < 0 ? 0 : val; +} + +#ifdef SIOCIFAFATTACH +static int +if_af_attach(const struct interface *ifp, int af) +{ + struct if_afreq ifar; + + strlcpy(ifar.ifar_name, ifp->name, sizeof(ifar.ifar_name)); + ifar.ifar_af = af; + return if_ioctl6(ifp->ctx, SIOCIFAFATTACH, &ifar, sizeof(ifar)); +} +#endif + +#ifdef SIOCGIFXFLAGS +static int +if_set_ifxflags(const struct interface *ifp) +{ + struct ifreq ifr; + int flags; + struct priv *priv = ifp->ctx->priv; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(priv->pf_inet6_fd, SIOCGIFXFLAGS, &ifr) == -1) + return -1; + flags = ifr.ifr_flags; +#ifdef IFXF_NOINET6 + flags &= ~IFXF_NOINET6; +#endif + /* + * If not doing autoconf, don't disable the kernel from doing it. + * If we need to, we should have another option actively disable it. + * + * OpenBSD moved from kernel based SLAAC to userland via slaacd(8). + * It has a similar featureset to dhcpcd such as stable private + * addresses, but lacks the ability to handle DNS inside the RA + * which is a serious shortfall in this day and age. + * Appease their user base by working alongside slaacd(8) if + * dhcpcd is instructed not to do auto configuration of addresses. + */ +#if defined(ND6_IFF_ACCEPT_RTADV) +#define BSD_AUTOCONF DHCPCD_IPV6RS +#else +#define BSD_AUTOCONF DHCPCD_IPV6RA_AUTOCONF +#endif + if (ifp->options->options & BSD_AUTOCONF) + flags &= ~IFXF_AUTOCONF6; + if (ifr.ifr_flags == flags) + return 0; + ifr.ifr_flags = flags; + return if_ioctl6(ifp->ctx, SIOCSIFXFLAGS, &ifr, sizeof(ifr)); +} +#endif + +/* OpenBSD removed ND6 flags entirely, so we need to check for their + * existance. */ +#if defined(ND6_IFF_AUTO_LINKLOCAL) || \ + defined(ND6_IFF_PERFORMNUD) || \ + defined(ND6_IFF_ACCEPT_RTADV) || \ + defined(ND6_IFF_OVERRIDE_RTADV) || \ + defined(ND6_IFF_IFDISABLED) +#define ND6_NDI_FLAGS +#endif + +void +if_disable_rtadv(void) +{ +#if defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV) + int ra = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV); + + if (ra == -1) { + if (errno != ENOENT) + logerr("IPV6CTL_ACCEPT_RTADV"); + else if (ra != 0) + if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1) + logerr("IPV6CTL_ACCEPT_RTADV"); + } +#endif +} + +void +if_setup_inet6(const struct interface *ifp) +{ + struct priv *priv; + int s; +#ifdef ND6_NDI_FLAGS + struct in6_ndireq nd; + int flags; +#endif + + priv = (struct priv *)ifp->ctx->priv; + s = priv->pf_inet6_fd; + +#ifdef ND6_NDI_FLAGS + memset(&nd, 0, sizeof(nd)); + strlcpy(nd.ifname, ifp->name, sizeof(nd.ifname)); + if (ioctl(s, SIOCGIFINFO_IN6, &nd) == -1) + logerr("%s: SIOCGIFINFO_FLAGS", ifp->name); + flags = (int)nd.ndi.flags; +#endif + +#ifdef ND6_IFF_AUTO_LINKLOCAL + /* Unlike the kernel, dhcpcd make make a stable private address. */ + flags &= ~ND6_IFF_AUTO_LINKLOCAL; +#endif + +#ifdef ND6_IFF_PERFORMNUD + /* NUD is kind of essential. */ + flags |= ND6_IFF_PERFORMNUD; +#endif + +#ifdef ND6_IFF_IFDISABLED + /* Ensure the interface is not disabled. */ + flags &= ~ND6_IFF_IFDISABLED; +#endif + + /* + * If not doing autoconf, don't disable the kernel from doing it. + * If we need to, we should have another option actively disable it. + */ +#ifdef ND6_IFF_ACCEPT_RTADV + if (ifp->options->options & DHCPCD_IPV6RS) + flags &= ~ND6_IFF_ACCEPT_RTADV; +#ifdef ND6_IFF_OVERRIDE_RTADV + if (ifp->options->options & DHCPCD_IPV6RS) + flags |= ND6_IFF_OVERRIDE_RTADV; +#endif +#endif + +#ifdef ND6_NDI_FLAGS + if (nd.ndi.flags != (uint32_t)flags) { + nd.ndi.flags = (uint32_t)flags; + if (if_ioctl6(ifp->ctx, SIOCSIFINFO_FLAGS, + &nd, sizeof(nd)) == -1) + logerr("%s: SIOCSIFINFO_FLAGS", ifp->name); + } +#endif + + /* Enabling IPv6 by whatever means must be the + * last action undertaken to ensure kernel RS and + * LLADDR auto configuration are disabled where applicable. */ +#ifdef SIOCIFAFATTACH + if (if_af_attach(ifp, AF_INET6) == -1) + logerr("%s: if_af_attach", ifp->name); +#endif + +#ifdef SIOCGIFXFLAGS + if (if_set_ifxflags(ifp) == -1) + logerr("%s: set_ifxflags", ifp->name); +#endif + +#ifdef SIOCSRTRFLUSH_IN6 + /* Flush the kernel knowledge of advertised routers + * and prefixes so the kernel does not expire prefixes + * and default routes we are trying to own. */ + if (ifp->options->options & DHCPCD_IPV6RS) { + struct in6_ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (if_ioctl6(ifp->ctx, SIOCSRTRFLUSH_IN6, + &ifr, sizeof(ifr)) == -1 && + errno != ENOTSUP && errno != ENOTTY) + logwarn("SIOCSRTRFLUSH_IN6 %d", errno); +#ifdef SIOCSPFXFLUSH_IN6 + if (if_ioctl6(ifp->ctx, SIOCSPFXFLUSH_IN6, + &ifr, sizeof(ifr)) == -1 && + errno != ENOTSUP && errno != ENOTTY) + logwarn("SIOCSPFXFLUSH_IN6"); +#endif + } +#endif +} +#endif Index: src/if-linux-wext.c =================================================================== --- /dev/null +++ src/if-linux-wext.c @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2009-2021 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * THIS IS A NASTY HACK THAT SHOULD NEVER HAVE HAPPENED + * Basically we cannot include linux/if.h and net/if.h because + * they have conflicting structures. + * Sadly, linux/wireless.h includes linux/if.h all the time. + * Some kernel-header installs fix this and some do not. + * This file solely exists for those who do not. + * + * We *could* include wireless.h as that is designed for userspace, + * but that then depends on the correct version of wireless-tools being + * installed which isn't always the case. + */ + +#include +#include + +#include +#include +/* Support older kernels */ +#ifdef IFLA_WIRELESS +# include +# include +#else +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +#include +#include + +#include "config.h" + +/* We can't include if.h or dhcpcd.h because + * they would pull in net/if.h, which defeats the purpose of this hack. */ +#define IF_SSIDLEN 32 +int if_getssid_wext(const char *ifname, uint8_t *ssid); + +int +if_getssid_wext(const char *ifname, uint8_t *ssid) +{ +#ifdef SIOCGIWESSID + int s, retval; + struct iwreq iwr; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&iwr, 0, sizeof(iwr)); + strlcpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name)); + iwr.u.essid.pointer = ssid; + iwr.u.essid.length = IF_SSIDLEN; + + if (ioctl(s, SIOCGIWESSID, &iwr) == 0) + retval = iwr.u.essid.length; + else + retval = -1; + close(s); + return retval; +#else + /* Stop gcc warning about unused parameters */ + ifname = ssid; + return -1; +#endif +} Index: src/if-linux.c =================================================================== --- /dev/null +++ src/if-linux.c @@ -0,0 +1,2175 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Linux interface driver for dhcpcd + * Copyright (c) 2006-2021 Roy Marples + * 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 /* Needed for 2.4 kernels */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* musl has its own definition of struct ethhdr, so only include + * netinet/if_ether.h on systems with GLIBC. For the ARPHRD constants, + * we must include linux/if_arp.h instead. */ +#if defined(__GLIBC__) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "bpf.h" +#include "common.h" +#include "dev.h" +#include "dhcp.h" +#include "if.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "route.h" +#include "sa.h" + +#ifdef HAVE_NL80211_H +#include +#include +#else +int if_getssid_wext(const char *ifname, uint8_t *ssid); +#endif + +/* Support older kernels */ +#ifndef IFLA_WIRELESS +#define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +/* For some reason, glibc doesn't include newer flags from linux/if.h + * However, we cannot include linux/if.h directly as it conflicts + * with the glibc version. D'oh! */ +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 /* driver signals L1 up */ +#endif + +/* Buggy CentOS and RedHat */ +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +/* + * Someone should fix kernel headers for clang alignment warnings. + * But this is unlikely. + * https://www.spinics.net/lists/netdev/msg646934.html + */ + +#undef NLA_ALIGNTO +#undef NLA_ALIGN +#undef NLA_HDRLEN +#define NLA_ALIGNTO 4U +#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) +#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) + +#undef IFA_RTA +#define IFA_RTA(r) ((struct rtattr *)(void *)(((char *)(r)) \ + + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#undef IFLA_RTA +#define IFLA_RTA(r) ((struct rtattr *)(void *)(((char *)(r)) \ + + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#undef NLMSG_NEXT +#define NLMSG_NEXT(nlh, len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ + (struct nlmsghdr *)(void *)(((char *)(nlh)) \ + + NLMSG_ALIGN((nlh)->nlmsg_len))) +#undef RTM_RTA +#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))) +#undef RTA_NEXT +#define RTA_NEXT(rta, attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \ + (struct rtattr *)(void *)(((char *)(rta)) \ + + RTA_ALIGN((rta)->rta_len))) + +struct priv { + int route_fd; + int generic_fd; + uint32_t route_pid; +}; + +/* We need this to send a broadcast for InfiniBand. + * Our old code used sendto, but our new code writes to a raw BPF socket. + * What header structure does IPoIB use? */ +#if 0 +/* Broadcast address for IPoIB */ +static const uint8_t ipv4_bcast_addr[] = { + 0x00, 0xff, 0xff, 0xff, + 0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff +}; +#endif + +static int if_addressexists(struct interface *, struct in_addr *); + +#define PROC_INET6 "/proc/net/if_inet6" +#define PROC_PROMOTE "/proc/sys/net/ipv4/conf/%s/promote_secondaries" +#define SYS_BRIDGE "/sys/class/net/%s/bridge/bridge_id" +#define SYS_LAYER2 "/sys/class/net/%s/device/layer2" +#define SYS_TUNTAP "/sys/class/net/%s/tun_flags" + +#if defined(__aarch64__) +static const char *mproc = "AArch64"; +int +if_machinearch(char *str, size_t len) +{ + + return snprintf(str, len, "%s", mproc); +} +#else +static const char *mproc = +#if defined(__alpha__) + "system type" +#elif defined(__arm__) + "Hardware" +#elif defined(__avr32__) + "cpu family" +#elif defined(__bfin__) + "BOARD Name" +#elif defined(__cris__) + "cpu model" +#elif defined(__frv__) + "System" +#elif defined(__i386__) || defined(__x86_64__) + "vendor_id" +#elif defined(__ia64__) + "vendor" +#elif defined(__hppa__) + "model" +#elif defined(__m68k__) + "MMU" +#elif defined(__mips__) + "system type" +#elif defined(__powerpc__) || defined(__powerpc64__) + "machine" +#elif defined(__s390__) || defined(__s390x__) + "Manufacturer" +#elif defined(__sh__) + "machine" +#elif defined(sparc) || defined(__sparc__) + "cpu" +#elif defined(__vax__) + "cpu" +#else + NULL +#endif + ; + +int +if_machinearch(char *str, size_t len) +{ + FILE *fp; + char buf[256]; + + if (mproc == NULL) { + errno = EINVAL; + return -1; + } + + fp = fopen("/proc/cpuinfo", "r"); + if (fp == NULL) + return -1; + + while (fscanf(fp, "%255s : ", buf) != EOF) { + if (strncmp(buf, mproc, strlen(mproc)) == 0 && + fscanf(fp, "%255s", buf) == 1) + { + fclose(fp); + return snprintf(str, len, "%s", buf); + } + } + fclose(fp); + errno = ESRCH; + return -1; +} +#endif + +static int +check_proc_int(struct dhcpcd_ctx *ctx, const char *path) +{ + char buf[64]; + int error, i; + + if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1) + return -1; + i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error); + if (error != 0 && error != ENOTSUP) { + errno = error; + return -1; + } + return i; +} + +static int +check_proc_uint(struct dhcpcd_ctx *ctx, const char *path, unsigned int *u) +{ + char buf[64]; + int error; + + if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1) + return -1; + *u = (unsigned int)strtou(buf, NULL, 0, 0, UINT_MAX, &error); + if (error != 0 && error != ENOTSUP) { + errno = error; + return error; + } + return 0; +} + +static ssize_t +if_writepathuint(struct dhcpcd_ctx *ctx, const char *path, unsigned int val) +{ + char buf[64]; + int len; + + len = snprintf(buf, sizeof(buf), "%u\n", val); + if (len == -1) + return -1; + return dhcp_writefile(ctx, path, 0664, buf, (size_t)len); +} + +int +if_init(struct interface *ifp) +{ + char path[sizeof(PROC_PROMOTE) + IF_NAMESIZE]; + int n; + + /* We enable promote_secondaries so that we can do this + * add 192.168.1.2/24 + * add 192.168.1.3/24 + * del 192.168.1.2/24 + * and the subnet mask moves onto 192.168.1.3/24 + * This matches the behaviour of BSD which makes coding dhcpcd + * a little easier as there's just one behaviour. */ + snprintf(path, sizeof(path), PROC_PROMOTE, ifp->name); + n = check_proc_int(ifp->ctx, path); + if (n == -1) + return errno == ENOENT ? 0 : -1; + if (n == 1) + return 0; + return if_writepathuint(ifp->ctx, path, 1) == -1 ? -1 : 0; +} + +int +if_conf(struct interface *ifp) +{ + char path[sizeof(SYS_LAYER2) + IF_NAMESIZE]; + int n; + + /* Some qeth setups require the use of the broadcast flag. */ + snprintf(path, sizeof(path), SYS_LAYER2, ifp->name); + n = check_proc_int(ifp->ctx, path); + if (n == -1) + return errno == ENOENT ? 0 : -1; + if (n == 0) + ifp->options->options |= DHCPCD_BROADCAST; + return 0; +} + +static bool +if_bridge(struct dhcpcd_ctx *ctx, const char *ifname) +{ + char path[sizeof(SYS_BRIDGE) + IF_NAMESIZE], buf[64]; + + snprintf(path, sizeof(path), SYS_BRIDGE, ifname); + if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1) + return false; + return true; +} + +static bool +if_tap(struct dhcpcd_ctx *ctx, const char *ifname) +{ + char path[sizeof(SYS_TUNTAP) + IF_NAMESIZE]; + unsigned int u; + + snprintf(path, sizeof(path), SYS_TUNTAP, ifname); + if (check_proc_uint(ctx, path, &u) == -1) + return false; + return u & IFF_TAP; +} + +bool +if_ignore(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (if_tap(ctx, ifname) || if_bridge(ctx, ifname)) + return true; + return false; +} + +/* XXX work out Virtal Interface Masters */ +int +if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname) +{ + + return 0; +} + +unsigned short +if_vlanid(const struct interface *ifp) +{ + struct vlan_ioctl_args v; + + memset(&v, 0, sizeof(v)); + strlcpy(v.device1, ifp->name, sizeof(v.device1)); + v.cmd = GET_VLAN_VID_CMD; + if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFVLAN, &v) != 0) + return 0; /* 0 means no VLANID */ + return (unsigned short)v.u.VID; +} + +int +if_linksocket(struct sockaddr_nl *nl, int protocol, int flags) +{ + int fd; + + fd = xsocket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | flags, protocol); + if (fd == -1) + return -1; + nl->nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) { + close(fd); + return -1; + } + return fd; +} + +char * +if_getnetworknamespace(char *buf, size_t len) +{ + struct stat sb_self, sb_netns; + DIR *dir; + struct dirent *de; + char file[PATH_MAX], *bufp = NULL; + + if (stat("/proc/self/ns/net", &sb_self) == -1) + return NULL; + + dir = opendir("/var/run/netns"); + if (dir == NULL) + return NULL; + + while ((de = readdir(dir)) != NULL) { + snprintf(file, sizeof(file), "/var/run/netns/%s", de->d_name); + if (stat(file, &sb_netns) == -1) + continue; + if (sb_self.st_dev != sb_netns.st_dev || + sb_self.st_ino != sb_netns.st_ino) + continue; + strlcpy(buf, de->d_name, len); + bufp = buf; + break; + } + closedir(dir); + return bufp; +} + +int +os_init(void) +{ + char netns[PATH_MAX], *p; + + p = if_getnetworknamespace(netns, sizeof(netns)); + if (p != NULL) + loginfox("network namespace: %s", p); + + return 0; +} + +int +if_opensockets_os(struct dhcpcd_ctx *ctx) +{ + struct priv *priv; + struct sockaddr_nl snl; + socklen_t len; +#ifdef NETLINK_BROADCAST_ERROR + int on = 1; +#endif + + /* Open the link socket first so it gets pid() for the socket. + * Then open our persistent route socket so we get a unique + * pid that doesn't clash with a process id for after we fork. */ + memset(&snl, 0, sizeof(snl)); + snl.nl_groups = RTMGRP_LINK; + +#ifdef INET + snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR; +#endif +#ifdef INET6 + snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH; +#endif + + ctx->link_fd = if_linksocket(&snl, NETLINK_ROUTE, SOCK_NONBLOCK); + if (ctx->link_fd == -1) + return -1; +#ifdef NETLINK_BROADCAST_ERROR + if (setsockopt(ctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR, + &on, sizeof(on)) == -1) + logerr("%s: NETLINK_BROADCAST_ERROR", __func__); +#endif + + if ((priv = calloc(1, sizeof(*priv))) == NULL) + return -1; + + ctx->priv = priv; + memset(&snl, 0, sizeof(snl)); + priv->route_fd = if_linksocket(&snl, NETLINK_ROUTE, 0); + if (priv->route_fd == -1) + return -1; + len = sizeof(snl); + if (getsockname(priv->route_fd, (struct sockaddr *)&snl, &len) == -1) + return -1; + priv->route_pid = snl.nl_pid; + + memset(&snl, 0, sizeof(snl)); + priv->generic_fd = if_linksocket(&snl, NETLINK_GENERIC, 0); + if (priv->generic_fd == -1) + return -1; + + return 0; +} + +void +if_closesockets_os(struct dhcpcd_ctx *ctx) +{ + struct priv *priv; + + if (ctx->priv != NULL) { + priv = (struct priv *)ctx->priv; + close(priv->route_fd); + close(priv->generic_fd); + } +} + +int +if_setmac(struct interface *ifp, void *mac, uint8_t maclen) +{ + struct ifreq ifr = { + .ifr_hwaddr.sa_family = ifp->hwtype, + }; + + if (ifp->hwlen != maclen || maclen > sizeof(ifr.ifr_hwaddr.sa_data)) { + errno = EINVAL; + return -1; + } + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + memcpy(ifr.ifr_hwaddr.sa_data, mac, maclen); + return if_ioctl(ifp->ctx, SIOCSIFHWADDR, &ifr, sizeof(ifr)); +} + +int +if_carrier(struct interface *ifp, __unused const void *ifadata) +{ + + return ifp->flags & IFF_RUNNING ? LINK_UP : LINK_DOWN; +} + +bool +if_roaming(struct interface *ifp) +{ + +#ifdef IFF_LOWER_UP + if (!ifp->wireless || + ifp->flags & IFF_RUNNING || + (ifp->flags & (IFF_UP | IFF_LOWER_UP)) != (IFF_UP | IFF_LOWER_UP)) + return false; + return true; +#else + return false; +#endif +} + +int +if_getnetlink(struct dhcpcd_ctx *ctx, struct iovec *iov, int fd, int flags, + int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg) +{ + struct sockaddr_nl nladdr = { .nl_pid = 0 }; + struct msghdr msg = { + .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), + .msg_iov = iov, .msg_iovlen = 1, + }; + ssize_t len; + struct nlmsghdr *nlm; + int r = 0; + unsigned int again; + bool terminated; + +recv_again: + len = recvmsg(fd, &msg, flags); + if (len == -1 || len == 0) + return (int)len; + + /* Check sender */ + if (msg.msg_namelen != sizeof(nladdr)) { + errno = EINVAL; + return -1; + } + + /* Ignore message if it is not from kernel */ + if (nladdr.nl_pid != 0) + return 0; + + again = 0; + terminated = false; + for (nlm = iov->iov_base; + nlm && NLMSG_OK(nlm, (size_t)len); + nlm = NLMSG_NEXT(nlm, len)) + { + again = (nlm->nlmsg_flags & NLM_F_MULTI); + if (nlm->nlmsg_type == NLMSG_NOOP) + continue; + + if (nlm->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err; + + if (nlm->nlmsg_len - sizeof(*nlm) < sizeof(*err)) { + errno = EBADMSG; + return -1; + } + err = (struct nlmsgerr *)NLMSG_DATA(nlm); + if (err->error != 0) { + errno = -err->error; + return -1; + } + again = 0; + terminated = true; + break; + } + if (nlm->nlmsg_type == NLMSG_DONE) { + again = 0; + terminated = true; + break; + } + if (cb == NULL) + continue; + if (nlm->nlmsg_seq != (uint32_t)ctx->seq && fd != ctx->link_fd) + logwarnx("%s: received sequence %u, expecting %d", + __func__, nlm->nlmsg_seq, ctx->seq); + else + r = cb(ctx, cbarg, nlm); + } + + if ((again || !terminated) && (ctx != NULL && ctx->link_fd != fd)) + goto recv_again; + + return r; +} + +static int +if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct nlmsghdr *nlm) +{ + size_t len; + struct rtmsg *rtm; + struct rtattr *rta; + unsigned int ifindex; + struct sockaddr *sa; + + len = nlm->nlmsg_len - sizeof(*nlm); + if (len < sizeof(*rtm)) { + errno = EBADMSG; + return -1; + } + rtm = (struct rtmsg *)NLMSG_DATA(nlm); + if (rtm->rtm_table != RT_TABLE_MAIN) + return -1; + + memset(rt, 0, sizeof(*rt)); + if (rtm->rtm_type == RTN_UNREACHABLE) + rt->rt_flags |= RTF_REJECT; + + rta = RTM_RTA(rtm); + len = RTM_PAYLOAD(nlm); + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + sa = NULL; + switch (rta->rta_type) { + case RTA_DST: + sa = &rt->rt_dest; + break; + case RTA_GATEWAY: + sa = &rt->rt_gateway; + break; + case RTA_PREFSRC: + sa = &rt->rt_ifa; + break; + case RTA_OIF: + ifindex = *(unsigned int *)RTA_DATA(rta); + rt->rt_ifp = if_findindex(ctx->ifaces, ifindex); + break; + case RTA_PRIORITY: + rt->rt_metric = *(unsigned int *)RTA_DATA(rta); + break; + case RTA_METRICS: + { + struct rtattr *r2; + size_t l2; + + l2 = rta->rta_len; + r2 = (struct rtattr *)RTA_DATA(rta); + for (; RTA_OK(r2, l2); r2 = RTA_NEXT(r2, l2)) { + switch (r2->rta_type) { + case RTAX_MTU: + rt->rt_mtu = *(unsigned int *)RTA_DATA(r2); + break; + } + } + break; + } + } + + if (sa != NULL) { + socklen_t salen; + + sa->sa_family = rtm->rtm_family; + salen = sa_addrlen(sa); + /* sa is a union where sockaddr_in6 is the biggest. */ + /* coverity[overrun-buffer-arg] */ + memcpy((char *)sa + sa_addroffset(sa), RTA_DATA(rta), + MIN(salen, RTA_PAYLOAD(rta))); + } + } + + /* If no RTA_DST set the unspecified address for the family. */ + if (rt->rt_dest.sa_family == AF_UNSPEC) + rt->rt_dest.sa_family = rtm->rtm_family; + + rt->rt_netmask.sa_family = rtm->rtm_family; + sa_fromprefix(&rt->rt_netmask, rtm->rtm_dst_len); + if (sa_is_allones(&rt->rt_netmask)) + rt->rt_flags |= RTF_HOST; + + #if 0 + if (rt->rtp_ifp == NULL && rt->src.s_addr != INADDR_ANY) { + struct ipv4_addr *ap; + + /* For some reason the default route comes back with the + * loopback interface in RTA_OIF? Lets find it by + * preferred source address */ + if ((ap = ipv4_findaddr(ctx, &rt->src))) + rt->iface = ap->iface; + } + #endif + + if (rt->rt_ifp == NULL) { + errno = ESRCH; + return -1; + } + return 0; +} + +static int +link_route(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, + struct nlmsghdr *nlm) +{ + size_t len; + int cmd; + struct priv *priv; + struct rt rt; + + switch (nlm->nlmsg_type) { + case RTM_NEWROUTE: + cmd = RTM_ADD; + break; + case RTM_DELROUTE: + cmd = RTM_DELETE; + break; + default: + return 0; + } + + len = nlm->nlmsg_len - sizeof(*nlm); + if (len < sizeof(struct rtmsg)) { + errno = EBADMSG; + return -1; + } + + /* Ignore messages we sent. */ +#ifdef PRIVSEP + if (ctx->ps_root_pid != 0 && + nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid) + return 0; +#endif + priv = (struct priv *)ctx->priv; + if (nlm->nlmsg_pid == priv->route_pid) + return 0; + + if (if_copyrt(ctx, &rt, nlm) == 0) + rt_recvrt(cmd, &rt, (pid_t)nlm->nlmsg_pid); + + return 0; +} + +static int +link_addr(struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm) +{ + size_t len; + struct rtattr *rta; + struct ifaddrmsg *ifa; + struct priv *priv; +#ifdef INET + struct in_addr addr, net, brd; + int ret; +#endif +#ifdef INET6 + struct in6_addr addr6; + int flags; +#endif + + if (nlm->nlmsg_type != RTM_DELADDR && nlm->nlmsg_type != RTM_NEWADDR) + return 0; + + len = nlm->nlmsg_len - sizeof(*nlm); + if (len < sizeof(*ifa)) { + errno = EBADMSG; + return -1; + } + + /* Ignore address deletions from ourself. + * We need to process address flag changes though. */ + if (nlm->nlmsg_type == RTM_DELADDR) { +#ifdef PRIVSEP + if (ctx->ps_root_pid != 0 && + nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid) + return 0; +#endif + priv = (struct priv*)ctx->priv; + if (nlm->nlmsg_pid == priv->route_pid) + return 0; + } + + ifa = NLMSG_DATA(nlm); + if ((ifp = if_findindex(ctx->ifaces, ifa->ifa_index)) == NULL) { + /* We don't know about the interface the address is for + * so it's not really an error */ + return 1; + } + rta = IFA_RTA(ifa); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifa)); + switch (ifa->ifa_family) { +#ifdef INET + case AF_INET: + addr.s_addr = brd.s_addr = INADDR_ANY; + inet_cidrtoaddr(ifa->ifa_prefixlen, &net); + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + switch (rta->rta_type) { + case IFA_ADDRESS: + if (ifp->flags & IFF_POINTOPOINT) { + memcpy(&brd.s_addr, RTA_DATA(rta), + sizeof(brd.s_addr)); + } + break; + case IFA_BROADCAST: + memcpy(&brd.s_addr, RTA_DATA(rta), + sizeof(brd.s_addr)); + break; + case IFA_LOCAL: + memcpy(&addr.s_addr, RTA_DATA(rta), + sizeof(addr.s_addr)); + break; + } + } + + /* Validate RTM_DELADDR really means address deleted + * and anything else really means address exists. */ + ret = if_addressexists(ifp, &addr); + if (ret == -1) { + logerr("if_addressexists: %s", inet_ntoa(addr)); + break; + } else if (ret == 1) { + if (nlm->nlmsg_type == RTM_DELADDR) + break; + } else { + if (nlm->nlmsg_type != RTM_DELADDR) + break; + } + + ipv4_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, + &addr, &net, &brd, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid); + break; +#endif +#ifdef INET6 + case AF_INET6: + memset(&addr6, 0, sizeof(addr6)); + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + switch (rta->rta_type) { + case IFA_ADDRESS: + memcpy(&addr6.s6_addr, RTA_DATA(rta), + sizeof(addr6.s6_addr)); + break; + } + } + + /* Validate RTM_DELADDR really means address deleted + * and anything else really means address exists. */ + flags = if_addrflags6(ifp, &addr6, NULL); + if (nlm->nlmsg_type == RTM_DELADDR) { + if (flags != -1) + break; + } else { + if (flags == -1) + break; + } + + ipv6_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, + &addr6, ifa->ifa_prefixlen, ifa->ifa_flags, + (pid_t)nlm->nlmsg_pid); + break; +#endif + } + return 0; +} + +static uint8_t +l2addr_len(unsigned short if_type) +{ + + switch (if_type) { + case ARPHRD_ETHER: /* FALLTHROUGH */ + case ARPHRD_IEEE802: /*FALLTHROUGH */ + case ARPHRD_IEEE80211: + return 6; + case ARPHRD_IEEE1394: + return 8; + case ARPHRD_INFINIBAND: + return 20; + } + + /* Impossible */ + return 0; +} + +#ifdef INET6 +static int +link_neigh(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, + struct nlmsghdr *nlm) +{ + struct ndmsg *r; + struct rtattr *rta; + size_t len; + + if (nlm->nlmsg_type != RTM_NEWNEIGH && nlm->nlmsg_type != RTM_DELNEIGH) + return 0; + if (nlm->nlmsg_len < sizeof(*r)) + return -1; + + r = NLMSG_DATA(nlm); + rta = RTM_RTA(r); + len = RTM_PAYLOAD(nlm); + if (r->ndm_family == AF_INET6) { + bool unreachable; + struct in6_addr addr6; + + unreachable = (nlm->nlmsg_type == RTM_NEWNEIGH && + r->ndm_state & NUD_FAILED); + memset(&addr6, 0, sizeof(addr6)); + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + switch (rta->rta_type) { + case NDA_DST: + memcpy(&addr6.s6_addr, RTA_DATA(rta), + sizeof(addr6.s6_addr)); + break; + } + } + ipv6nd_neighbour(ctx, &addr6, !unreachable); + } + + return 0; +} +#endif + +static int +link_netlink(struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm) +{ + struct interface *ifp = arg; + int r; + size_t len; + struct rtattr *rta, *hwaddr; + struct ifinfomsg *ifi; + char ifn[IF_NAMESIZE + 1]; + + r = link_route(ctx, ifp, nlm); + if (r != 0) + return r; + r = link_addr(ctx, ifp, nlm); + if (r != 0) + return r; +#ifdef INET6 + r = link_neigh(ctx, ifp, nlm); + if (r != 0) + return r; +#endif + + if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK) + return 0; + len = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)len < sizeof(*ifi)) { + errno = EBADMSG; + return -1; + } + ifi = NLMSG_DATA(nlm); + if (ifi->ifi_flags & IFF_LOOPBACK) + return 0; + rta = (void *)((char *)ifi + NLMSG_ALIGN(sizeof(*ifi))); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifi)); + *ifn = '\0'; + hwaddr = NULL; + + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + switch (rta->rta_type) { + case IFLA_WIRELESS: + /* Ignore wireless messages */ + if (nlm->nlmsg_type == RTM_NEWLINK && + ifi->ifi_change == 0) + return 0; + break; + case IFLA_IFNAME: + strlcpy(ifn, (char *)RTA_DATA(rta), sizeof(ifn)); + break; + case IFLA_ADDRESS: + hwaddr = rta; + break; + } + } + + if (nlm->nlmsg_type == RTM_DELLINK) { +#ifdef PLUGIN_DEV + /* If are listening to a dev manager, let that remove + * the interface rather than the kernel. */ + if (dev_listening(ctx) < 1) +#endif + dhcpcd_handleinterface(ctx, -1, ifn); + return 0; + } + + /* Virtual interfaces may not get a valid hardware address + * at this point. + * To trigger a valid hardware address pickup we need to pretend + * that that don't exist until they have one. */ + if (ifi->ifi_flags & IFF_MASTER && !hwaddr) { + dhcpcd_handleinterface(ctx, -1, ifn); + return 0; + } + + /* Check for a new interface */ + ifp = if_findindex(ctx->ifaces, (unsigned int)ifi->ifi_index); + if (ifp == NULL) { +#ifdef PLUGIN_DEV + /* If are listening to a dev manager, let that announce + * the interface rather than the kernel. */ + if (dev_listening(ctx) < 1) +#endif + dhcpcd_handleinterface(ctx, 1, ifn); + return 0; + } + + /* Handle interface being renamed */ + if (strcmp(ifp->name, ifn) != 0) { + dhcpcd_handleinterface(ctx, -1, ifn); + dhcpcd_handleinterface(ctx, 1, ifn); + return 0; + } + + /* Re-read hardware address and friends */ + if (!(ifi->ifi_flags & IFF_UP)) { + void *hwa = hwaddr != NULL ? RTA_DATA(hwaddr) : NULL; + uint8_t hwl = l2addr_len(ifi->ifi_type); + + if (hwaddr != NULL && hwaddr->rta_len != RTA_LENGTH(hwl)) + hwa = NULL; + dhcpcd_handlehwaddr(ifp, ifi->ifi_type, hwa, hwl); + } + + dhcpcd_handlecarrier(ifp, + ifi->ifi_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN, + ifi->ifi_flags); + return 0; +} + +int +if_handlelink(struct dhcpcd_ctx *ctx) +{ + unsigned char buf[16 * 1024]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + + return if_getnetlink(ctx, &iov, ctx->link_fd, MSG_DONTWAIT, + &link_netlink, NULL); +} + +#ifdef PRIVSEP +static bool +if_netlinkpriv(int protocol, struct nlmsghdr *nlm) +{ + + if (protocol != NETLINK_ROUTE) + return false; + + switch(nlm->nlmsg_type) { + case RTM_NEWADDR: /* FALLTHROUGH */ + case RTM_DELADDR: /* FALLTHROUGH */ + case RTM_NEWROUTE: /* FALLTHROUGH */ + case RTM_DELROUTE: /* FALLTHROUGH */ + case RTM_NEWLINK: + return true; + default: + return false; + } +} +#endif + +static int +if_sendnetlink(struct dhcpcd_ctx *ctx, int protocol, struct nlmsghdr *hdr, + int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg) +{ + int s; + struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; + struct iovec iov = { .iov_base = hdr, .iov_len = hdr->nlmsg_len }; + struct msghdr msg = { + .msg_name = &snl, .msg_namelen = sizeof(snl), + .msg_iov = &iov, .msg_iovlen = 1 + }; + struct priv *priv = (struct priv *)ctx->priv; + unsigned char buf[16 * 1024]; + struct iovec riov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + + /* Request a reply */ + hdr->nlmsg_flags |= NLM_F_ACK; + hdr->nlmsg_seq = (uint32_t)++ctx->seq; + if ((unsigned int)ctx->seq > UINT32_MAX) + ctx->seq = 0; + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && if_netlinkpriv(protocol, hdr)) + return (int)ps_root_sendnetlink(ctx, protocol, &msg); +#endif + + switch (protocol) { + case NETLINK_ROUTE: + s = priv->route_fd; + break; + case NETLINK_GENERIC: + s = priv->generic_fd; +#if 0 +#ifdef NETLINK_GET_STRICT_CHK + if (hdr->nlmsg_type == RTM_GETADDR) { + int on = 1; + + if (setsockopt(s, SOL_NETLINK, NETLINK_GET_STRICT_CHK, + &on, sizeof(on)) == -1 && errno != ENOPROTOOPT) + logerr("%s: NETLINK_GET_STRICT_CHK", __func__); + } +#endif +#endif + break; + default: + errno = EINVAL; + return -1; + } + + if (sendmsg(s, &msg, 0) == -1) + return -1; + + return if_getnetlink(ctx, &riov, s, 0, cb, cbarg); +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len))) + +static int +add_attr_l(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, + const void *data, unsigned short alen) +{ + unsigned short len = (unsigned short)RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + errno = ENOBUFS; + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + if (alen) + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return 0; +} + +static int +add_attr_8(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, + uint8_t data) +{ + + return add_attr_l(n, maxlen, type, &data, sizeof(data)); +} + +static int +add_attr_32(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, + uint32_t data) +{ + unsigned short len = RTA_LENGTH(sizeof(data)); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + errno = ENOBUFS; + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, sizeof(data)); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + + return 0; +} + +static int +rta_add_attr_32(struct rtattr *rta, unsigned short maxlen, + unsigned short type, uint32_t data) +{ + unsigned short len = RTA_LENGTH(sizeof(data)); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + errno = ENOBUFS; + return -1; + } + + subrta = (void *)((char*)rta + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, sizeof(data)); + rta->rta_len = (unsigned short)(NLMSG_ALIGN(rta->rta_len) + len); + return 0; +} + +#ifdef HAVE_NL80211_H +static struct nlattr * +nla_next(struct nlattr *nla, size_t *rem) +{ + + *rem -= (size_t)NLA_ALIGN(nla->nla_len); + return (void *)((char *)nla + NLA_ALIGN(nla->nla_len)); +} + +#define NLA_TYPE(nla) ((nla)->nla_type & NLA_TYPE_MASK) +#define NLA_LEN(nla) (unsigned int)((nla)->nla_len - NLA_HDRLEN) +#define NLA_OK(nla, rem) \ + ((rem) >= sizeof(struct nlattr) && \ + (nla)->nla_len >= sizeof(struct nlattr) && \ + (nla)->nla_len <= rem) +#define NLA_DATA(nla) (void *)((char *)(nla) + NLA_HDRLEN) +#define NLA_FOR_EACH_ATTR(pos, head, len, rem) \ + for (pos = head, rem = len; \ + NLA_OK(pos, rem); \ + pos = nla_next(pos, &(rem))) + +struct nlmg +{ + struct nlmsghdr hdr; + struct genlmsghdr ghdr; + char buffer[64]; +}; + +static int +nla_put_32(struct nlmsghdr *n, unsigned short maxlen, + unsigned short type, uint32_t data) +{ + unsigned short len; + struct nlattr *nla; + + len = NLA_ALIGN(NLA_HDRLEN + sizeof(data)); + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + errno = ENOBUFS; + return -1; + } + + nla = (struct nlattr *)NLMSG_TAIL(n); + nla->nla_type = type; + nla->nla_len = len; + memcpy(NLA_DATA(nla), &data, sizeof(data)); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + + return 0; +} + +static int +nla_put_string(struct nlmsghdr *n, unsigned short maxlen, + unsigned short type, const char *data) +{ + struct nlattr *nla; + size_t len, sl; + + sl = strlen(data) + 1; + len = NLA_ALIGN(NLA_HDRLEN + sl); + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + errno = ENOBUFS; + return -1; + } + + nla = (struct nlattr *)NLMSG_TAIL(n); + nla->nla_type = type; + nla->nla_len = (unsigned short)len; + memcpy(NLA_DATA(nla), data, sl); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + (unsigned short)len; + return 0; +} + +static int +nla_parse(struct nlattr *tb[], struct nlattr *head, size_t len, int maxtype) +{ + struct nlattr *nla; + size_t rem; + int type; + + memset(tb, 0, sizeof(*tb) * ((unsigned int)maxtype + 1)); + NLA_FOR_EACH_ATTR(nla, head, len, rem) { + type = NLA_TYPE(nla); + if (type > maxtype) + continue; + tb[type] = nla; + } + return 0; +} + +static int +genl_parse(struct nlmsghdr *nlm, struct nlattr *tb[], int maxtype) +{ + struct genlmsghdr *ghdr; + struct nlattr *head; + size_t len; + + ghdr = NLMSG_DATA(nlm); + head = (void *)((char *)ghdr + GENL_HDRLEN); + len = nlm->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN; + return nla_parse(tb, head, len, maxtype); +} + +static int +_gnl_getfamily(__unused struct dhcpcd_ctx *ctx, __unused void *arg, + struct nlmsghdr *nlm) +{ + struct nlattr *tb[CTRL_ATTR_FAMILY_ID + 1]; + uint16_t family; + + if (genl_parse(nlm, tb, CTRL_ATTR_FAMILY_ID) == -1) + return -1; + if (tb[CTRL_ATTR_FAMILY_ID] == NULL) { + errno = ENOENT; + return -1; + } + memcpy(&family, NLA_DATA(tb[CTRL_ATTR_FAMILY_ID]), sizeof(family)); + return (int)family; +} + +static int +gnl_getfamily(struct dhcpcd_ctx *ctx, const char *name) +{ + struct nlmg nlm; + + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); + nlm.hdr.nlmsg_type = GENL_ID_CTRL; + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + nlm.ghdr.cmd = CTRL_CMD_GETFAMILY; + nlm.ghdr.version = 1; + if (nla_put_string(&nlm.hdr, sizeof(nlm), + CTRL_ATTR_FAMILY_NAME, name) == -1) + return -1; + return if_sendnetlink(ctx, NETLINK_GENERIC, &nlm.hdr, + &_gnl_getfamily, NULL); +} + +static int +_if_getssid_nl80211(__unused struct dhcpcd_ctx *ctx, void *arg, + struct nlmsghdr *nlm) +{ + struct interface *ifp = arg; + struct nlattr *tb[NL80211_ATTR_BSS + 1]; + struct nlattr *bss[NL80211_BSS_STATUS + 1]; + uint32_t status; + unsigned char *ie; + int ie_len; + + if (genl_parse(nlm, tb, NL80211_ATTR_BSS) == -1) + return 0; + + if (tb[NL80211_ATTR_BSS] == NULL) + return 0; + + if (nla_parse(bss, + NLA_DATA(tb[NL80211_ATTR_BSS]), + NLA_LEN(tb[NL80211_ATTR_BSS]), + NL80211_BSS_STATUS) == -1) + return 0; + + if (bss[NL80211_BSS_BSSID] == NULL || bss[NL80211_BSS_STATUS] == NULL) + return 0; + + memcpy(&status, NLA_DATA(bss[NL80211_BSS_STATUS]), sizeof(status)); + if (status != NL80211_BSS_STATUS_ASSOCIATED) + return 0; + + if (bss[NL80211_BSS_INFORMATION_ELEMENTS] == NULL) + return 0; + + ie = NLA_DATA(bss[NL80211_BSS_INFORMATION_ELEMENTS]); + ie_len = (int)NLA_LEN(bss[NL80211_BSS_INFORMATION_ELEMENTS]); + /* ie[0] is type, ie[1] is lenth, ie[2..] is data */ + while (ie_len >= 2 && ie_len >= ie[1]) { + if (ie[0] == 0) { + /* SSID */ + if (ie[1] > IF_SSIDLEN) { + errno = ENOBUFS; + return -1; + } + ifp->ssid_len = ie[1]; + memcpy(ifp->ssid, ie + 2, ifp->ssid_len); + return (int)ifp->ssid_len; + } + ie_len -= ie[1] + 2; + ie += ie[1] + 2; + } + + return 0; +} + +static int +if_getssid_nl80211(struct interface *ifp) +{ + int family; + struct nlmg nlm; + + errno = 0; + family = gnl_getfamily(ifp->ctx, "nl80211"); + if (family == -1) + return -1; + + /* Is this a wireless interface? */ + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); + nlm.hdr.nlmsg_type = (unsigned short)family; + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + nlm.ghdr.cmd = NL80211_CMD_GET_WIPHY; + nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index); + if (if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr, + NULL, NULL) == -1) + return -1; + + /* We need to parse out the list of scan results and find the one + * we are connected to. */ + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); + nlm.hdr.nlmsg_type = (unsigned short)family; + nlm.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlm.ghdr.cmd = NL80211_CMD_GET_SCAN; + nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index); + + return if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr, + &_if_getssid_nl80211, ifp); +} +#endif + +int +if_getssid(struct interface *ifp) +{ + int r; + +#ifdef HAVE_NL80211_H + r = if_getssid_nl80211(ifp); + if (r == -1) + ifp->ssid_len = 0; +#else + r = if_getssid_wext(ifp->name, ifp->ssid); + if (r != -1) + ifp->ssid_len = (unsigned int)r; +#endif + + ifp->ssid[ifp->ssid_len] = '\0'; + return r; +} + +struct nlma +{ + struct nlmsghdr hdr; + struct ifaddrmsg ifa; + char buffer[64]; +}; + +#ifdef INET +struct ifiaddr +{ + unsigned int ifa_ifindex; + struct in_addr ifa_addr; + bool ifa_found; +}; + +static int +_if_addressexists(__unused struct dhcpcd_ctx *ctx, + void *arg, struct nlmsghdr *nlm) +{ + struct ifiaddr *ia = arg; + in_addr_t this_addr; + size_t len; + struct rtattr *rta; + struct ifaddrmsg *ifa; + + ifa = NLMSG_DATA(nlm); + if (ifa->ifa_index != ia->ifa_ifindex || ifa->ifa_family != AF_INET) + return 0; + + rta = IFA_RTA(ifa); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifa)); + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + switch (rta->rta_type) { + case IFA_LOCAL: + memcpy(&this_addr, RTA_DATA(rta), sizeof(this_addr)); + if (this_addr == ia->ifa_addr.s_addr) { + ia->ifa_found = true; + return 1; + } + break; + } + } + return 0; +} + +static int +if_addressexists(struct interface *ifp, struct in_addr *addr) +{ + struct ifiaddr ia = { + .ifa_ifindex = ifp->index, + .ifa_addr = *addr, + .ifa_found = false, + }; + struct nlma nlm = { + .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), + .hdr.nlmsg_type = RTM_GETADDR, + .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH, + .ifa.ifa_family = AF_INET, + .ifa.ifa_index = ifp->index, + }; + + int error = if_sendnetlink(ifp->ctx, NETLINK_ROUTE, &nlm.hdr, + &_if_addressexists, &ia); + if (error == -1) + return -1; + return ia.ifa_found ? 1 : 0; +} +#endif + +struct nlmr +{ + struct nlmsghdr hdr; + struct rtmsg rt; + char buffer[256]; +}; + +int +if_route(unsigned char cmd, const struct rt *rt) +{ + struct nlmr nlm; + bool gateway_unspec; + + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + switch (cmd) { + case RTM_CHANGE: + nlm.hdr.nlmsg_type = RTM_NEWROUTE; + nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_REPLACE; + break; + case RTM_ADD: + nlm.hdr.nlmsg_type = RTM_NEWROUTE; + nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL; + break; + case RTM_DELETE: + nlm.hdr.nlmsg_type = RTM_DELROUTE; + break; + } + nlm.hdr.nlmsg_flags |= NLM_F_REQUEST; + nlm.rt.rtm_family = (unsigned char)rt->rt_dest.sa_family; + nlm.rt.rtm_table = RT_TABLE_MAIN; + + gateway_unspec = sa_is_unspecified(&rt->rt_gateway); + + if (cmd == RTM_DELETE) { + nlm.rt.rtm_scope = RT_SCOPE_NOWHERE; + } else { + /* Address generated routes are RTPROT_KERNEL, + * otherwise RTPROT_BOOT */ +#ifdef RTPROT_RA + if (rt->rt_dflags & RTDF_RA) + nlm.rt.rtm_protocol = RTPROT_RA; + else +#endif +#ifdef RTPROT_DHCP + if (rt->rt_dflags & RTDF_DHCP) + nlm.rt.rtm_protocol = RTPROT_DHCP; + else +#endif + if (rt->rt_dflags & RTDF_IFA_ROUTE) + nlm.rt.rtm_protocol = RTPROT_KERNEL; + else + nlm.rt.rtm_protocol = RTPROT_BOOT; + if (rt->rt_ifp->flags & IFF_LOOPBACK) + nlm.rt.rtm_scope = RT_SCOPE_HOST; + else if (gateway_unspec) + nlm.rt.rtm_scope = RT_SCOPE_LINK; + else + nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE; + if (rt->rt_flags & RTF_REJECT) + nlm.rt.rtm_type = RTN_UNREACHABLE; + else + nlm.rt.rtm_type = RTN_UNICAST; + } + +#define ADDSA(type, sa) \ + add_attr_l(&nlm.hdr, sizeof(nlm), (type), \ + (const char *)(sa) + sa_addroffset((sa)), \ + (unsigned short)sa_addrlen((sa))); + nlm.rt.rtm_dst_len = (unsigned char)sa_toprefix(&rt->rt_netmask); + /* rt->rt_dest and rt->gateway are unions where sockaddr_in6 + * is the biggest member. However, we access them as the + * generic sockaddr and coverity thinks this will overrun. */ + /* coverity[overrun-buffer-arg] */ + ADDSA(RTA_DST, &rt->rt_dest); + if (cmd == RTM_ADD || cmd == RTM_CHANGE) { + if (!gateway_unspec) { + /* coverity[overrun-buffer-arg] */ + ADDSA(RTA_GATEWAY, &rt->rt_gateway); + } + /* Cannot add tentative source addresses. + * We don't know this here, so just skip INET6 ifa's.*/ + if (!sa_is_unspecified(&rt->rt_ifa) && + rt->rt_ifa.sa_family != AF_INET6) + ADDSA(RTA_PREFSRC, &rt->rt_ifa); + if (rt->rt_mtu) { + char metricsbuf[32]; + struct rtattr *metrics = (void *)metricsbuf; + + metrics->rta_type = RTA_METRICS; + metrics->rta_len = RTA_LENGTH(0); + rta_add_attr_32(metrics, sizeof(metricsbuf), + RTAX_MTU, rt->rt_mtu); + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_METRICS, + RTA_DATA(metrics), + (unsigned short)RTA_PAYLOAD(metrics)); + } + +#ifdef HAVE_ROUTE_PREF + if (rt->rt_dflags & RTDF_RA) { + uint8_t pref; + + switch(rt->rt_pref) { + case RTPREF_LOW: + pref = ICMPV6_ROUTER_PREF_LOW; + break; + case RTPREF_MEDIUM: + pref = ICMPV6_ROUTER_PREF_MEDIUM; + break; + case RTPREF_HIGH: + pref = ICMPV6_ROUTER_PREF_HIGH; + break; + default: + pref = ICMPV6_ROUTER_PREF_INVALID; + break; + } + add_attr_8(&nlm.hdr, sizeof(nlm), RTA_PREF, pref); + } +#endif + } + + if (!sa_is_loopback(&rt->rt_gateway)) + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->rt_ifp->index); + + if (rt->rt_metric != 0) + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_PRIORITY, + rt->rt_metric); + + return if_sendnetlink(rt->rt_ifp->ctx, NETLINK_ROUTE, &nlm.hdr, + NULL, NULL); +} + +static int +_if_initrt(struct dhcpcd_ctx *ctx, void *arg, + struct nlmsghdr *nlm) +{ + struct rt rt, *rtn; + rb_tree_t *kroutes = arg; + + if (if_copyrt(ctx, &rt, nlm) != 0) + return 0; + if ((rtn = rt_new(rt.rt_ifp)) == NULL) { + logerr(__func__); + return 0; + } + memcpy(rtn, &rt, sizeof(*rtn)); + if (rb_tree_insert_node(kroutes, rtn) != rtn) + rt_free(rtn); + return 0; +} + +int +if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af) +{ + struct nlmr nlm = { + .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), + .hdr.nlmsg_type = RTM_GETROUTE, + .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH, + .rt.rtm_table = RT_TABLE_MAIN, + .rt.rtm_family = (unsigned char)af, + }; + + return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr, + &_if_initrt, kroutes); +} + +#ifdef INET +/* Linux is a special snowflake when it comes to BPF. */ +const char *bpf_name = "Packet Socket"; + +/* Linux is a special snowflake for opening BPF. */ +struct bpf * +bpf_open(const struct interface *ifp, + int (*filter)(const struct bpf *, const struct in_addr *), + const struct in_addr *ia) +{ + struct bpf *bpf; + union sockunion { + struct sockaddr sa; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su = { + .sll = { + .sll_family = PF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + .sll_ifindex = (int)ifp->index, + } + }; +#ifdef PACKET_AUXDATA + int n; +#endif + + bpf = calloc(1, sizeof(*bpf)); + if (bpf == NULL) + return NULL; + bpf->bpf_ifp = ifp; + + /* Allocate a suitably large buffer for a single packet. */ + bpf->bpf_size = ETH_DATA_LEN; + bpf->bpf_buffer = malloc(bpf->bpf_size); + if (bpf->bpf_buffer == NULL) + goto eexit; + + bpf->bpf_fd = xsocket(PF_PACKET, SOCK_RAW|SOCK_CXNB,htons(ETH_P_ALL)); + if (bpf->bpf_fd == -1) + goto eexit; + + /* We cannot validate the correct interface, + * so we MUST set this first. */ + if (bind(bpf->bpf_fd, &su.sa, sizeof(su.sll)) == -1) + goto eexit; + + if (filter(bpf, ia) != 0) + goto eexit; + + /* In the ideal world, this would be set before the bind and filter. */ +#ifdef PACKET_AUXDATA + n = 1; + if (setsockopt(bpf->bpf_fd, SOL_PACKET, PACKET_AUXDATA, + &n, sizeof(n)) != 0) { + if (errno != ENOPROTOOPT) + goto eexit; + } +#endif + + /* + * At this point we could have received packets for the wrong + * interface or which don't pass the filter. + * Linux should flush upon setting the filter like every other OS. + * There is no way of flushing them from userland. + * As such, consumers need to inspect each packet to ensure it's valid. + * Or to put it another way, don't trust the Linux BPF filter. + */ + + return bpf; + +eexit: + if (bpf->bpf_fd != -1) + close(bpf->bpf_fd); + free(bpf->bpf_buffer); + free(bpf); + return NULL; +} + +/* BPF requires that we read the entire buffer. + * So we pass the buffer in the API so we can loop on >1 packet. */ +ssize_t +bpf_read(struct bpf *bpf, void *data, size_t len) +{ + ssize_t bytes; + struct iovec iov = { + .iov_base = bpf->bpf_buffer, + .iov_len = bpf->bpf_size, + }; + struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; +#ifdef PACKET_AUXDATA + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))]; + } cmsgbuf = { .buf = { 0 } }; + struct cmsghdr *cmsg; + struct tpacket_auxdata *aux; +#endif + +#ifdef PACKET_AUXDATA + msg.msg_control = cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); +#endif + + bytes = recvmsg(bpf->bpf_fd, &msg, 0); + if (bytes == -1) + return -1; + bpf->bpf_flags |= BPF_EOF; /* We only ever read one packet. */ + bpf->bpf_flags &= ~BPF_PARTIALCSUM; + if (bytes) { + if (bpf_frame_bcast(bpf->bpf_ifp, bpf->bpf_buffer) == 0) + bpf->bpf_flags |= BPF_BCAST; + else + bpf->bpf_flags &= ~BPF_BCAST; + if ((size_t)bytes > len) + bytes = (ssize_t)len; + memcpy(data, bpf->bpf_buffer, (size_t)bytes); +#ifdef PACKET_AUXDATA + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == SOL_PACKET && + cmsg->cmsg_type == PACKET_AUXDATA) { + aux = (void *)CMSG_DATA(cmsg); + if (aux->tp_status & TP_STATUS_CSUMNOTREADY) + bpf->bpf_flags |= BPF_PARTIALCSUM; + } + } +#endif + } + return bytes; +} + +int +bpf_attach(int s, void *filter, unsigned int filter_len) +{ + struct sock_fprog pf = { + .filter = filter, + .len = (unsigned short)filter_len, + }; + + /* Install the filter. */ + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) == -1) + return -1; + +#ifdef SO_LOCK_FILTER + int on = 1; + + if (setsockopt(s, SOL_SOCKET, SO_LOCK_FILTER, &on, sizeof(on)) == -1) + return -1; +#endif + + return 0; +} + +int +if_address(unsigned char cmd, const struct ipv4_addr *ia) +{ + struct nlma nlm; + struct ifa_cacheinfo cinfo; + int retval = 0; +#ifdef IFA_F_NOPREFIXROUTE + uint32_t flags = 0; +#endif + + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + nlm.hdr.nlmsg_type = cmd; + if (cmd == RTM_NEWADDR) + nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + nlm.ifa.ifa_index = ia->iface->index; + nlm.ifa.ifa_family = AF_INET; + + nlm.ifa.ifa_prefixlen = inet_ntocidr(ia->mask); + +#if 0 + /* This creates the aliased interface */ + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, + ia->iface->alias, + (unsigned short)(strlen(ia->iface->alias) + 1)); +#endif + + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, + &ia->addr.s_addr, sizeof(ia->addr.s_addr)); + + if (cmd == RTM_NEWADDR) { +#ifdef IFA_F_NOPREFIXROUTE + if (nlm.ifa.ifa_prefixlen < 32) + flags |= IFA_F_NOPREFIXROUTE; + add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); +#endif + + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_BROADCAST, + &ia->brd.s_addr, sizeof(ia->brd.s_addr)); + + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.ifa_prefered = ia->pltime; + cinfo.ifa_valid = ia->vltime; + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO, + &cinfo, sizeof(cinfo)); + } + + if (if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr, + NULL, NULL) == -1) + retval = -1; + return retval; +} + +int +if_addrflags(__unused const struct interface *ifp, +__unused const struct in_addr *addr, __unused const char *alias) +{ + + /* Linux has no support for IPv4 address flags */ + return 0; +} +#endif + +#ifdef INET6 +int +if_address6(unsigned char cmd, const struct ipv6_addr *ia) +{ + struct nlma nlm; + struct ifa_cacheinfo cinfo; +#if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE) + uint32_t flags = 0; +#endif + + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + nlm.hdr.nlmsg_type = cmd; + if (cmd == RTM_NEWADDR) + nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + nlm.ifa.ifa_index = ia->iface->index; + nlm.ifa.ifa_family = AF_INET6; + + /* Add as /128 if no IFA_F_NOPREFIXROUTE ? */ + nlm.ifa.ifa_prefixlen = ia->prefix_len; + +#if 0 + /* This creates the aliased interface */ + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, + ia->iface->alias, (unsigned short)(strlen(ia->iface->alias) + 1)); +#endif + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, + &ia->addr.s6_addr, sizeof(ia->addr.s6_addr)); + + if (cmd == RTM_NEWADDR) { +#ifdef IPV6_MANAGETEMPADDR + if (ia->flags & IPV6_AF_TEMPORARY) { + /* Currently the kernel filters out these flags */ +#ifdef IFA_F_NOPREFIXROUTE + flags |= IFA_F_TEMPORARY; +#else + nlm.ifa.ifa_flags |= IFA_F_TEMPORARY; +#endif + } +#elif IFA_F_MANAGETEMPADDR + if (ia->flags & IPV6_AF_AUTOCONF && IA6_CANAUTOCONF(ia)) + flags |= IFA_F_MANAGETEMPADDR; +#endif +#ifdef IFA_F_NOPREFIXROUTE + if (!IN6_IS_ADDR_LINKLOCAL(&ia->addr)) + flags |= IFA_F_NOPREFIXROUTE; +#endif +#if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE) + add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); +#endif + + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.ifa_prefered = ia->prefix_pltime; + cinfo.ifa_valid = ia->prefix_vltime; + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO, + &cinfo, sizeof(cinfo)); + } + + return if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr, + NULL, NULL); +} + +int +if_addrflags6(const struct interface *ifp, const struct in6_addr *addr, + __unused const char *alias) +{ + char buf[PS_BUFLEN], *bp = buf, *line; + ssize_t buflen; + char *p, ifaddress[33], address[33], name[IF_NAMESIZE + 1]; + unsigned int ifindex; + int prefix, scope, flags, i; + + buflen = dhcp_readfile(ifp->ctx, PROC_INET6, buf, sizeof(buf)); + if (buflen == -1) + return -1; + if ((size_t)buflen == sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + + p = ifaddress; + for (i = 0; i < (int)sizeof(addr->s6_addr); i++) { + p += snprintf(p, 3, "%.2x", addr->s6_addr[i]); + } + *p = '\0'; + + while ((line = get_line(&bp, &buflen)) != NULL) { + if (sscanf(line, + "%32[a-f0-9] %x %x %x %x %"TOSTRING(IF_NAMESIZE)"s\n", + address, &ifindex, &prefix, &scope, &flags, name) != 6 || + strlen(address) != 32) + { + errno = EINVAL; + return -1; + } + if (strcmp(name, ifp->name) == 0 && + strcmp(ifaddress, address) == 0) + return flags; + } + + errno = ESRCH; + return -1; +} + +int +if_getlifetime6(__unused struct ipv6_addr *ia) +{ + + /* God knows how to work out address lifetimes on Linux */ + errno = ENOTSUP; + return -1; +} + +struct nlml +{ + struct nlmsghdr hdr; + struct ifinfomsg i; + char buffer[32]; +}; + +#ifdef HAVE_IN6_ADDR_GEN_MODE_NONE +static struct rtattr * +add_attr_nest(struct nlmsghdr *n, unsigned short maxlen, unsigned short type) +{ + struct rtattr *nest; + + nest = NLMSG_TAIL(n); + add_attr_l(n, maxlen, type, NULL, 0); + return nest; +} + +static void +add_attr_nest_end(struct nlmsghdr *n, struct rtattr *nest) +{ + + nest->rta_len = (unsigned short)((char *)NLMSG_TAIL(n) - (char *)nest); +} +#endif + +static int +if_disable_autolinklocal(struct dhcpcd_ctx *ctx, unsigned int ifindex) +{ +#ifdef HAVE_IN6_ADDR_GEN_MODE_NONE + struct nlml nlm; + struct rtattr *afs, *afs6; + + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + nlm.hdr.nlmsg_type = RTM_NEWLINK; + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + nlm.i.ifi_family = AF_INET6; + nlm.i.ifi_index = (int)ifindex; + afs = add_attr_nest(&nlm.hdr, sizeof(nlm), IFLA_AF_SPEC); + afs6 = add_attr_nest(&nlm.hdr, sizeof(nlm), AF_INET6); + add_attr_8(&nlm.hdr, sizeof(nlm), IFLA_INET6_ADDR_GEN_MODE, + IN6_ADDR_GEN_MODE_NONE); + add_attr_nest_end(&nlm.hdr, afs6); + add_attr_nest_end(&nlm.hdr, afs); + + return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL); +#else + UNUSED(ctx); + UNUSED(ifindex); + errno = ENOTSUP; + return -1; +#endif +} + +static const char *p_conf = "/proc/sys/net/ipv6/conf"; +static const char *p_neigh = "/proc/sys/net/ipv6/neigh"; + +void +if_setup_inet6(const struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + int ra; + char path[256]; + + /* The kernel cannot make stable private addresses. + * However, a lot of distros ship newer kernel headers than + * the kernel itself so sweep that error under the table. */ + if (if_disable_autolinklocal(ctx, ifp->index) == -1 && + errno != ENODEV && errno != ENOTSUP && errno != EINVAL) + logdebug("%s: if_disable_autolinklocal", ifp->name); + + /* + * If not doing autoconf, don't disable the kernel from doing it. + * If we need to, we should have another option actively disable it. + */ + if (!(ifp->options->options & DHCPCD_IPV6RS)) + return; + + snprintf(path, sizeof(path), "%s/%s/autoconf", p_conf, ifp->name); + ra = check_proc_int(ctx, path); + if (ra != 1 && ra != -1) { + if (if_writepathuint(ctx, path, 0) == -1) + logerr("%s: %s", __func__, path); + } + + snprintf(path, sizeof(path), "%s/%s/accept_ra", p_conf, ifp->name); + ra = check_proc_int(ctx, path); + if (ra == -1) { + /* The sysctl probably doesn't exist, but this isn't an + * error as such so just log it and continue */ + if (errno != ENOENT) + logerr("%s: %s", __func__, path); + } else if (ra != 0) { + if (if_writepathuint(ctx, path, 0) == -1) + logerr("%s: %s", __func__, path); + } +} + +int +if_applyra(const struct ra *rap) +{ + char path[256]; + const char *ifname = rap->iface->name; + struct dhcpcd_ctx *ctx = rap->iface->ctx; + int error = 0; + + if (rap->hoplimit != 0) { + snprintf(path, sizeof(path), "%s/%s/hop_limit", p_conf, ifname); + if (if_writepathuint(ctx, path, rap->hoplimit) == -1) + error = -1; + } + + if (rap->retrans != 0) { + snprintf(path, sizeof(path), "%s/%s/retrans_time_ms", + p_neigh, ifname); + if (if_writepathuint(ctx, path, rap->retrans) == -1) + error = -1; + } + + if (rap->reachable != 0) { + snprintf(path, sizeof(path), "%s/%s/base_reachable_time_ms", + p_neigh, ifname); + if (if_writepathuint(ctx, path, rap->reachable) == -1) + error = -1; + } + + return error; +} + +int +ip6_forwarding(const char *ifname) +{ + char path[256], buf[64]; + int error, i; + + if (ifname == NULL) + ifname = "all"; + snprintf(path, sizeof(path), "%s/%s/forwarding", p_conf, ifname); + if (readfile(path, buf, sizeof(buf)) == -1) + return 0; + i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error); + if (error != 0 && error != ENOTSUP) + return 0; + return i; +} + +#endif /* INET6 */ Index: src/if-options.h =================================================================== --- /dev/null +++ src/if-options.h @@ -0,0 +1,293 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef IF_OPTIONS_H +#define IF_OPTIONS_H + +#include +#include +#include +#include + +#include +#include +#include + +#include "auth.h" +#include "route.h" + +/* Don't set any optional arguments here so we retain POSIX + * compatibility with getopt */ +#define IF_OPTS "146bc:de:f:gh:i:j:kl:m:no:pqr:s:t:u:v:wxy:z:" \ + "ABC:DEF:GHI:JKLMNO:PQ:S:TUVW:X:Z:" +#define NOERR_IF_OPTS ":" IF_OPTS + +#define DEFAULT_TIMEOUT 30 +#define DEFAULT_REBOOT 5 + +#ifndef HOSTNAME_MAX_LEN +#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#endif +#define VENDORCLASSID_MAX_LEN 255 +#define CLIENTID_MAX_LEN 48 +#define USERCLASS_MAX_LEN 255 +#define VENDOR_MAX_LEN 255 +#define MUDURL_MAX_LEN 255 + +#define DHCPCD_ARP (1ULL << 0) +#define DHCPCD_RELEASE (1ULL << 1) +#define DHCPCD_RTBUILD (1ULL << 2) +#define DHCPCD_GATEWAY (1ULL << 3) +#define DHCPCD_STATIC (1ULL << 4) +#define DHCPCD_DEBUG (1ULL << 5) +#define DHCPCD_LASTLEASE (1ULL << 7) +#define DHCPCD_INFORM (1ULL << 8) +#define DHCPCD_REQUEST (1ULL << 9) +#define DHCPCD_IPV4LL (1ULL << 10) +#define DHCPCD_DUID (1ULL << 11) +#define DHCPCD_PERSISTENT (1ULL << 12) +#define DHCPCD_DAEMONISE (1ULL << 14) +#define DHCPCD_DAEMONISED (1ULL << 15) +#define DHCPCD_TEST (1ULL << 16) +#define DHCPCD_MANAGER (1ULL << 17) +#define DHCPCD_HOSTNAME (1ULL << 18) +#define DHCPCD_CLIENTID (1ULL << 19) +#define DHCPCD_LINK (1ULL << 20) +#define DHCPCD_ANONYMOUS (1ULL << 21) +#define DHCPCD_BACKGROUND (1ULL << 22) +#define DHCPCD_VENDORRAW (1ULL << 23) +#define DHCPCD_NOWAITIP (1ULL << 24) /* To force daemonise */ +#define DHCPCD_WAITIP (1ULL << 25) +#define DHCPCD_SLAACPRIVATE (1ULL << 26) +#define DHCPCD_CSR_WARNED (1ULL << 27) +#define DHCPCD_XID_HWADDR (1ULL << 28) +#define DHCPCD_BROADCAST (1ULL << 29) +#define DHCPCD_DUMPLEASE (1ULL << 30) +#define DHCPCD_IPV6RS (1ULL << 31) +#define DHCPCD_IPV6RA_REQRDNSS (1ULL << 32) +#define DHCPCD_PRIVSEP (1ULL << 33) +#define DHCPCD_CONFIGURE (1ULL << 34) +#define DHCPCD_IPV4 (1ULL << 35) +#define DHCPCD_FORKED (1ULL << 36) +#define DHCPCD_IPV6 (1ULL << 37) +#define DHCPCD_STARTED (1ULL << 38) +#define DHCPCD_NOALIAS (1ULL << 39) +#define DHCPCD_IA_FORCED (1ULL << 40) +#define DHCPCD_STOPPING (1ULL << 41) +#define DHCPCD_LAUNCHER (1ULL << 42) +#define DHCPCD_HOSTNAME_SHORT (1ULL << 43) +#define DHCPCD_EXITING (1ULL << 44) +#define DHCPCD_WAITIP4 (1ULL << 45) +#define DHCPCD_WAITIP6 (1ULL << 46) +#define DHCPCD_DEV (1ULL << 47) +#define DHCPCD_IAID (1ULL << 48) +#define DHCPCD_DHCP (1ULL << 49) +#define DHCPCD_DHCP6 (1ULL << 50) +#define DHCPCD_IF_UP (1ULL << 51) +#define DHCPCD_INFORM6 (1ULL << 52) +#define DHCPCD_WANTDHCP (1ULL << 53) +#define DHCPCD_IPV6RA_AUTOCONF (1ULL << 54) +#define DHCPCD_ROUTER_HOST_ROUTE_WARNED (1ULL << 55) +#define DHCPCD_LASTLEASE_EXTEND (1ULL << 56) +#define DHCPCD_BOOTP (1ULL << 57) +#define DHCPCD_INITIAL_DELAY (1ULL << 58) +#define DHCPCD_PRINT_PIDFILE (1ULL << 59) +#define DHCPCD_ONESHOT (1ULL << 60) +#define DHCPCD_INACTIVE (1ULL << 61) +#define DHCPCD_SLAACTEMP (1ULL << 62) +#define DHCPCD_PRIVSEPROOT (1ULL << 63) + +#define DHCPCD_NODROP (DHCPCD_EXITING | DHCPCD_PERSISTENT) + +#define DHCPCD_WAITOPTS (DHCPCD_WAITIP | DHCPCD_WAITIP4 | DHCPCD_WAITIP6) + +#define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \ + DHCPCD_ROUTER_HOST_ROUTE_WARNED) + +/* These options only make sense in the config file, so don't use any + valid short options for them */ +#define O_BASE MAX('z', 'Z') + 1 +#define O_ARPING O_BASE + 1 +#define O_FALLBACK O_BASE + 2 +#define O_DESTINATION O_BASE + 3 +#define O_IPV6RS O_BASE + 4 +#define O_NOIPV6RS O_BASE + 5 +#define O_IPV6RA_FORK O_BASE + 6 +#define O_LINK_RCVBUF O_BASE + 7 +#define O_ANONYMOUS O_BASE + 8 +#define O_NOALIAS O_BASE + 9 +#define O_IA_NA O_BASE + 10 +#define O_IA_TA O_BASE + 11 +#define O_IA_PD O_BASE + 12 +#define O_HOSTNAME_SHORT O_BASE + 13 +#define O_DEV O_BASE + 14 +#define O_NODEV O_BASE + 15 +#define O_NOIPV4 O_BASE + 16 +#define O_NOIPV6 O_BASE + 17 +#define O_IAID O_BASE + 18 +#define O_DEFINE O_BASE + 19 +#define O_DEFINE6 O_BASE + 20 +#define O_EMBED O_BASE + 21 +#define O_ENCAP O_BASE + 22 +#define O_VENDOPT O_BASE + 23 +#define O_VENDCLASS O_BASE + 24 +#define O_AUTHPROTOCOL O_BASE + 25 +#define O_AUTHTOKEN O_BASE + 26 +#define O_AUTHNOTREQUIRED O_BASE + 27 +#define O_NODHCP O_BASE + 28 +#define O_NODHCP6 O_BASE + 29 +#define O_DHCP O_BASE + 30 +#define O_DHCP6 O_BASE + 31 +#define O_IPV4 O_BASE + 32 +#define O_IPV6 O_BASE + 33 +#define O_CONTROLGRP O_BASE + 34 +#define O_SLAAC O_BASE + 35 +#define O_GATEWAY O_BASE + 36 +#define O_NOUP O_BASE + 37 +#define O_IPV6RA_AUTOCONF O_BASE + 38 +#define O_IPV6RA_NOAUTOCONF O_BASE + 39 +#define O_REJECT O_BASE + 40 +#define O_BOOTP O_BASE + 42 +#define O_DEFINEND O_BASE + 43 +#define O_NODELAY O_BASE + 44 +#define O_INFORM6 O_BASE + 45 +#define O_LASTLEASE_EXTEND O_BASE + 46 +#define O_INACTIVE O_BASE + 47 +#define O_MUDURL O_BASE + 48 +#define O_MSUSERCLASS O_BASE + 49 +#define O_CONFIGURE O_BASE + 50 +#define O_NOCONFIGURE O_BASE + 51 +#define O_RANDOMISE_HWADDR O_BASE + 52 + +extern const struct option cf_options[]; + +struct if_sla { + char ifname[IF_NAMESIZE]; + uint32_t sla; + uint8_t prefix_len; + uint64_t suffix; + bool sla_set; +}; + +struct if_ia { + uint8_t iaid[4]; +#ifdef INET6 + uint16_t ia_type; + uint8_t iaid_set; + struct in6_addr addr; + uint8_t prefix_len; +#ifndef SMALL + uint32_t sla_max; + size_t sla_len; + struct if_sla *sla; +#endif +#endif +}; + +struct vivco { + size_t len; + uint8_t *data; +}; + +struct if_options { + time_t mtime; + uint8_t iaid[4]; + int metric; + uint8_t requestmask[256 / NBBY]; + uint8_t requiremask[256 / NBBY]; + uint8_t nomask[256 / NBBY]; + uint8_t rejectmask[256 / NBBY]; + uint8_t dstmask[256 / NBBY]; + uint8_t requestmasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t requiremasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t nomasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t rejectmasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t requestmask6[(UINT16_MAX + 1) / NBBY]; + uint8_t requiremask6[(UINT16_MAX + 1) / NBBY]; + uint8_t nomask6[(UINT16_MAX + 1) / NBBY]; + uint8_t rejectmask6[(UINT16_MAX + 1) / NBBY]; + uint32_t leasetime; + uint32_t timeout; + uint32_t reboot; + unsigned long long options; + bool randomise_hwaddr; + + struct in_addr req_addr; + struct in_addr req_mask; + struct in_addr req_brd; + rb_tree_t routes; + struct in6_addr req_addr6; + uint8_t req_prefix_len; + unsigned int mtu; + char **config; + + char **environ; + + char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the length */ + uint8_t fqdn; + uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 2]; + uint8_t clientid[CLIENTID_MAX_LEN + 2]; + uint8_t userclass[USERCLASS_MAX_LEN + 2]; + uint8_t vendor[VENDOR_MAX_LEN + 2]; + uint8_t mudurl[MUDURL_MAX_LEN + 2]; + + size_t blacklist_len; + in_addr_t *blacklist; + size_t whitelist_len; + in_addr_t *whitelist; + ssize_t arping_len; + in_addr_t *arping; + char *fallback; + + struct if_ia *ia; + size_t ia_len; + + struct dhcp_opt *dhcp_override; + size_t dhcp_override_len; + struct dhcp_opt *nd_override; + size_t nd_override_len; + struct dhcp_opt *dhcp6_override; + size_t dhcp6_override_len; + uint32_t vivco_en; + struct vivco *vivco; + size_t vivco_len; + struct dhcp_opt *vivso_override; + size_t vivso_override_len; + + struct auth auth; +}; + +struct if_options *default_config(struct dhcpcd_ctx *); +struct if_options *read_config(struct dhcpcd_ctx *, + const char *, const char *, const char *); +int add_options(struct dhcpcd_ctx *, const char *, + struct if_options *, int, char **); +void free_dhcp_opt_embenc(struct dhcp_opt *); +void free_options(struct dhcpcd_ctx *, struct if_options *); + +#endif Index: src/if-options.c =================================================================== --- /dev/null +++ src/if-options.c @@ -0,0 +1,2773 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "dhcpcd-embedded.h" +#include "duid.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "logerr.h" +#include "sa.h" + +#define IN_CONFIG_BLOCK(ifo) ((ifo)->options & DHCPCD_FORKED) +#define SET_CONFIG_BLOCK(ifo) ((ifo)->options |= DHCPCD_FORKED) +#define CLEAR_CONFIG_BLOCK(ifo) ((ifo)->options &= ~DHCPCD_FORKED) + +static unsigned long long default_options; + +const struct option cf_options[] = { + {"background", no_argument, NULL, 'b'}, + {"script", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"env", required_argument, NULL, 'e'}, + {"config", required_argument, NULL, 'f'}, + {"reconfigure", no_argument, NULL, 'g'}, + {"hostname", optional_argument, NULL, 'h'}, + {"vendorclassid", optional_argument, NULL, 'i'}, + {"logfile", required_argument, NULL, 'j'}, + {"release", no_argument, NULL, 'k'}, + {"leasetime", required_argument, NULL, 'l'}, + {"metric", required_argument, NULL, 'm'}, + {"rebind", no_argument, NULL, 'n'}, + {"option", required_argument, NULL, 'o'}, + {"persistent", no_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"request", optional_argument, NULL, 'r'}, + {"inform", optional_argument, NULL, 's'}, + {"inform6", optional_argument, NULL, O_INFORM6}, + {"timeout", required_argument, NULL, 't'}, + {"userclass", required_argument, NULL, 'u'}, +#ifndef SMALL + {"msuserclass", required_argument, NULL, O_MSUSERCLASS}, +#endif + {"vendor", required_argument, NULL, 'v'}, + {"waitip", optional_argument, NULL, 'w'}, + {"exit", no_argument, NULL, 'x'}, + {"allowinterfaces", required_argument, NULL, 'z'}, + {"reboot", required_argument, NULL, 'y'}, + {"noarp", no_argument, NULL, 'A'}, + {"nobackground", no_argument, NULL, 'B'}, + {"nohook", required_argument, NULL, 'C'}, + {"duid", optional_argument, NULL, 'D'}, + {"lastlease", no_argument, NULL, 'E'}, + {"fqdn", optional_argument, NULL, 'F'}, + {"nogateway", no_argument, NULL, 'G'}, + {"xidhwaddr", no_argument, NULL, 'H'}, + {"clientid", optional_argument, NULL, 'I'}, + {"broadcast", no_argument, NULL, 'J'}, + {"nolink", no_argument, NULL, 'K'}, + {"noipv4ll", no_argument, NULL, 'L'}, + {"manager", no_argument, NULL, 'M'}, + {"renew", no_argument, NULL, 'N'}, + {"nooption", required_argument, NULL, 'O'}, + {"printpidfile", no_argument, NULL, 'P'}, + {"require", required_argument, NULL, 'Q'}, + {"static", required_argument, NULL, 'S'}, + {"test", no_argument, NULL, 'T'}, + {"dumplease", no_argument, NULL, 'U'}, + {"variables", no_argument, NULL, 'V'}, + {"whitelist", required_argument, NULL, 'W'}, + {"blacklist", required_argument, NULL, 'X'}, + {"denyinterfaces", required_argument, NULL, 'Z'}, + {"oneshot", no_argument, NULL, '1'}, + {"ipv4only", no_argument, NULL, '4'}, + {"ipv6only", no_argument, NULL, '6'}, + {"anonymous", no_argument, NULL, O_ANONYMOUS}, + {"randomise_hwaddr",no_argument, NULL, O_RANDOMISE_HWADDR}, + {"arping", required_argument, NULL, O_ARPING}, + {"destination", required_argument, NULL, O_DESTINATION}, + {"fallback", required_argument, NULL, O_FALLBACK}, + {"ipv6rs", no_argument, NULL, O_IPV6RS}, + {"noipv6rs", no_argument, NULL, O_NOIPV6RS}, + {"ipv6ra_autoconf", no_argument, NULL, O_IPV6RA_AUTOCONF}, + {"ipv6ra_noautoconf", no_argument, NULL, O_IPV6RA_NOAUTOCONF}, + {"ipv6ra_fork", no_argument, NULL, O_IPV6RA_FORK}, + {"ipv4", no_argument, NULL, O_IPV4}, + {"noipv4", no_argument, NULL, O_NOIPV4}, + {"ipv6", no_argument, NULL, O_IPV6}, + {"noipv6", no_argument, NULL, O_NOIPV6}, + {"noalias", no_argument, NULL, O_NOALIAS}, + {"iaid", required_argument, NULL, O_IAID}, + {"ia_na", optional_argument, NULL, O_IA_NA}, + {"ia_ta", optional_argument, NULL, O_IA_TA}, + {"ia_pd", optional_argument, NULL, O_IA_PD}, + {"hostname_short", no_argument, NULL, O_HOSTNAME_SHORT}, + {"dev", required_argument, NULL, O_DEV}, + {"nodev", no_argument, NULL, O_NODEV}, + {"define", required_argument, NULL, O_DEFINE}, + {"definend", required_argument, NULL, O_DEFINEND}, + {"define6", required_argument, NULL, O_DEFINE6}, + {"embed", required_argument, NULL, O_EMBED}, + {"encap", required_argument, NULL, O_ENCAP}, + {"vendopt", required_argument, NULL, O_VENDOPT}, + {"vendclass", required_argument, NULL, O_VENDCLASS}, + {"authprotocol", required_argument, NULL, O_AUTHPROTOCOL}, + {"authtoken", required_argument, NULL, O_AUTHTOKEN}, + {"noauthrequired", no_argument, NULL, O_AUTHNOTREQUIRED}, + {"dhcp", no_argument, NULL, O_DHCP}, + {"nodhcp", no_argument, NULL, O_NODHCP}, + {"dhcp6", no_argument, NULL, O_DHCP6}, + {"nodhcp6", no_argument, NULL, O_NODHCP6}, + {"controlgroup", required_argument, NULL, O_CONTROLGRP}, + {"slaac", required_argument, NULL, O_SLAAC}, + {"gateway", no_argument, NULL, O_GATEWAY}, + {"reject", required_argument, NULL, O_REJECT}, + {"bootp", no_argument, NULL, O_BOOTP}, + {"nodelay", no_argument, NULL, O_NODELAY}, + {"noup", no_argument, NULL, O_NOUP}, + {"lastleaseextend", no_argument, NULL, O_LASTLEASE_EXTEND}, + {"inactive", no_argument, NULL, O_INACTIVE}, + {"mudurl", required_argument, NULL, O_MUDURL}, + {"link_rcvbuf", required_argument, NULL, O_LINK_RCVBUF}, + {"configure", no_argument, NULL, O_CONFIGURE}, + {"noconfigure", no_argument, NULL, O_NOCONFIGURE}, + {NULL, 0, NULL, '\0'} +}; + +static char * +add_environ(char ***array, const char *value, int uniq) +{ + char **newlist, **list = *array; + size_t i = 0, l, lv; + char *match = NULL, *p, *n; + + match = strdup(value); + if (match == NULL) { + logerr(__func__); + return NULL; + } + p = strchr(match, '='); + if (p == NULL) { + logerrx("%s: no assignment: %s", __func__, value); + free(match); + return NULL; + } + *p++ = '\0'; + l = strlen(match); + + while (list && list[i]) { + if (match && strncmp(list[i], match, l) == 0) { + if (uniq) { + n = strdup(value); + if (n == NULL) { + logerr(__func__); + free(match); + return NULL; + } + free(list[i]); + list[i] = n; + } else { + /* Append a space and the value to it */ + l = strlen(list[i]); + lv = strlen(p); + n = realloc(list[i], l + lv + 2); + if (n == NULL) { + logerr(__func__); + free(match); + return NULL; + } + list[i] = n; + list[i][l] = ' '; + memcpy(list[i] + l + 1, p, lv); + list[i][l + lv + 1] = '\0'; + } + free(match); + return list[i]; + } + i++; + } + + free(match); + n = strdup(value); + if (n == NULL) { + logerr(__func__); + return NULL; + } + newlist = reallocarray(list, i + 2, sizeof(char *)); + if (newlist == NULL) { + logerr(__func__); + free(n); + return NULL; + } + newlist[i] = n; + newlist[i + 1] = NULL; + *array = newlist; + return newlist[i]; +} + +#define PARSE_STRING 0 +#define PARSE_STRING_NULL 1 +#define PARSE_HWADDR 2 +#define parse_string(a, b, c) parse_str((a), (b), (c), PARSE_STRING) +#define parse_nstring(a, b, c) parse_str((a), (b), (c), PARSE_STRING_NULL) +#define parse_hwaddr(a, b, c) parse_str((a), (b), (c), PARSE_HWADDR) +static ssize_t +parse_str(char *sbuf, size_t slen, const char *str, int flags) +{ + size_t l; + const char *p, *end; + int i; + char c[4], cmd; + + end = str + strlen(str); + /* If surrounded by quotes then it's a string */ + if (*str == '"') { + p = end - 1; + if (*p == '"') { + str++; + end = p; + } + } else { + l = (size_t)hwaddr_aton(NULL, str); + if ((ssize_t) l != -1 && l > 1) { + if (l > slen) { + errno = ENOBUFS; + return -1; + } + hwaddr_aton((uint8_t *)sbuf, str); + return (ssize_t)l; + } + } + + /* Process escapes */ + l = 0; + /* If processing a string on the clientid, first byte should be + * 0 to indicate a non hardware type */ + if (flags == PARSE_HWADDR && *str) { + if (sbuf) + *sbuf++ = 0; + l++; + } + c[3] = '\0'; + while (str < end) { + if (++l > slen && sbuf) { + errno = ENOBUFS; + return -1; + } + if (*str == '\\') { + str++; + switch((cmd = *str++)) { + case '\0': + str--; + break; + case 'b': + if (sbuf) + *sbuf++ = '\b'; + break; + case 'n': + if (sbuf) + *sbuf++ = '\n'; + break; + case 'r': + if (sbuf) + *sbuf++ = '\r'; + break; + case 't': + if (sbuf) + *sbuf++ = '\t'; + break; + case 'x': + /* Grab a hex code */ + c[1] = '\0'; + for (i = 0; i < 2; i++) { + if (isxdigit((unsigned char)*str) == 0) + break; + c[i] = *str++; + } + if (c[1] != '\0') { + c[2] = '\0'; + if (sbuf) + *sbuf++ = (char)strtol(c, NULL, 16); + } else + l--; + break; + case '0': + /* Grab an octal code */ + c[2] = '\0'; + for (i = 0; i < 3; i++) { + if (*str < '0' || *str > '7') + break; + c[i] = *str++; + } + if (c[2] != '\0') { + i = (int)strtol(c, NULL, 8); + if (i > 255) + i = 255; + if (sbuf) + *sbuf++ = (char)i; + } else + l--; + break; + default: + if (sbuf) + *sbuf++ = cmd; + break; + } + } else { + if (sbuf) + *sbuf++ = *str; + str++; + } + } + if (flags == PARSE_STRING_NULL) { + l++; + if (sbuf != NULL) { + if (l > slen) { + errno = ENOBUFS; + return -1; + } + *sbuf = '\0'; + } + } + return (ssize_t)l; +} + +static int +parse_iaid1(uint8_t *iaid, const char *arg, size_t len, int n) +{ + int e; + uint32_t narg; + ssize_t s; + + narg = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e == 0) { + if (n) + narg = htonl(narg); + memcpy(iaid, &narg, sizeof(narg)); + return 0; + } + + if ((s = parse_string((char *)iaid, len, arg)) < 1) + return -1; + if (s < 4) + iaid[3] = '\0'; + if (s < 3) + iaid[2] = '\0'; + if (s < 2) + iaid[1] = '\0'; + return 0; +} + +static int +parse_iaid(uint8_t *iaid, const char *arg, size_t len) +{ + + return parse_iaid1(iaid, arg, len, 1); +} + +#ifdef AUTH +static int +parse_uint32(uint32_t *i, const char *arg) +{ + + return parse_iaid1((uint8_t *)i, arg, sizeof(uint32_t), 0); +} +#endif + +static char ** +splitv(int *argc, char **argv, const char *arg) +{ + char **n, **v = argv; + char *o = strdup(arg), *p, *t, *nt; + + if (o == NULL) { + logerr(__func__); + return v; + } + p = o; + while ((t = strsep(&p, ", "))) { + nt = strdup(t); + if (nt == NULL) { + logerr(__func__); + free(o); + return v; + } + n = reallocarray(v, (size_t)(*argc) + 1, sizeof(char *)); + if (n == NULL) { + logerr(__func__); + free(o); + free(nt); + return v; + } + v = n; + v[(*argc)++] = nt; + } + free(o); + return v; +} + +#ifdef INET +static int +parse_addr(struct in_addr *addr, struct in_addr *net, const char *arg) +{ + char *p; + + if (arg == NULL || *arg == '\0') { + if (addr != NULL) + addr->s_addr = 0; + if (net != NULL) + net->s_addr = 0; + return 0; + } + if ((p = strchr(arg, '/')) != NULL) { + int e; + intmax_t i; + + *p++ = '\0'; + i = strtoi(p, NULL, 10, 0, 32, &e); + if (e != 0 || + (net != NULL && inet_cidrtoaddr((int)i, net) != 0)) + { + logerrx("invalid CIDR: %s", p); + return -1; + } + } + + if (addr != NULL && inet_aton(arg, addr) == 0) { + logerrx("invalid IP address: %s", arg); + return -1; + } + if (p != NULL) + *--p = '/'; + else if (net != NULL && addr != NULL) + net->s_addr = ipv4_getnetmask(addr->s_addr); + return 0; +} +#else +static int +parse_addr(__unused struct in_addr *addr, __unused struct in_addr *net, + __unused const char *arg) +{ + + logerrx("No IPv4 support"); + return -1; +} +#endif + +static void +set_option_space(struct dhcpcd_ctx *ctx, + const char *arg, + const struct dhcp_opt **d, size_t *dl, + const struct dhcp_opt **od, size_t *odl, + struct if_options *ifo, + uint8_t *request[], uint8_t *require[], uint8_t *no[], uint8_t *reject[]) +{ + +#if !defined(INET) && !defined(INET6) + UNUSED(ctx); +#endif + +#ifdef INET6 + if (strncmp(arg, "nd_", strlen("nd_")) == 0) { + *d = ctx->nd_opts; + *dl = ctx->nd_opts_len; + *od = ifo->nd_override; + *odl = ifo->nd_override_len; + *request = ifo->requestmasknd; + *require = ifo->requiremasknd; + *no = ifo->nomasknd; + *reject = ifo->rejectmasknd; + return; + } + +#ifdef DHCP6 + if (strncmp(arg, "dhcp6_", strlen("dhcp6_")) == 0) { + *d = ctx->dhcp6_opts; + *dl = ctx->dhcp6_opts_len; + *od = ifo->dhcp6_override; + *odl = ifo->dhcp6_override_len; + *request = ifo->requestmask6; + *require = ifo->requiremask6; + *no = ifo->nomask6; + *reject = ifo->rejectmask6; + return; + } +#endif +#else + UNUSED(arg); +#endif + +#ifdef INET + *d = ctx->dhcp_opts; + *dl = ctx->dhcp_opts_len; + *od = ifo->dhcp_override; + *odl = ifo->dhcp_override_len; +#else + *d = NULL; + *dl = 0; + *od = NULL; + *odl = 0; +#endif + *request = ifo->requestmask; + *require = ifo->requiremask; + *no = ifo->nomask; + *reject = ifo->rejectmask; +} + +void +free_dhcp_opt_embenc(struct dhcp_opt *opt) +{ + size_t i; + struct dhcp_opt *o; + + free(opt->var); + + for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++) + free_dhcp_opt_embenc(o); + free(opt->embopts); + opt->embopts_len = 0; + opt->embopts = NULL; + + for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++) + free_dhcp_opt_embenc(o); + free(opt->encopts); + opt->encopts_len = 0; + opt->encopts = NULL; +} + +static char * +strwhite(const char *s) +{ + + if (s == NULL) + return NULL; + while (*s != ' ' && *s != '\t') { + if (*s == '\0') + return NULL; + s++; + } + return UNCONST(s); +} + +static char * +strskipwhite(const char *s) +{ + + if (s == NULL || *s == '\0') + return NULL; + while (*s == ' ' || *s == '\t') { + s++; + if (*s == '\0') + return NULL; + } + return UNCONST(s); +} + +#ifdef AUTH +/* Find the end pointer of a string. */ +static char * +strend(const char *s) +{ + + s = strskipwhite(s); + if (s == NULL) + return NULL; + if (*s != '"') + return strchr(s, ' '); + s++; + for (; *s != '"' ; s++) { + if (*s == '\0') + return NULL; + if (*s == '\\') { + if (*(++s) == '\0') + return NULL; + } + } + return UNCONST(++s); +} +#endif + +static int +parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, + int opt, const char *arg, struct dhcp_opt **ldop, struct dhcp_opt **edop) +{ + int e, i, t; + long l; + unsigned long u; + char *p = NULL, *bp, *fp, *np; + ssize_t s; + struct in_addr addr, addr2; + in_addr_t *naddr; + struct rt *rt; + const struct dhcp_opt *d, *od; + uint8_t *request, *require, *no, *reject; + struct dhcp_opt **dop, *ndop; + size_t *dop_len, dl, odl; + struct vivco *vivco; + struct group *grp; +#ifdef AUTH + struct token *token; +#endif +#ifdef _REENTRANT + struct group grpbuf; +#endif +#ifdef DHCP6 + size_t sl; + struct if_ia *ia; + uint8_t iaid[4]; +#ifndef SMALL + struct if_sla *sla, *slap; +#endif +#endif + + dop = NULL; + dop_len = NULL; +#ifdef INET6 + i = 0; +#endif + +/* Add a guard for static analysers. + * This should not be needed really because of the argument_required option + * in the options declaration above. */ +#define ARG_REQUIRED if (arg == NULL) goto arg_required + + switch(opt) { + case 'f': /* FALLTHROUGH */ + case 'g': /* FALLTHROUGH */ + case 'n': /* FALLTHROUGH */ + case 'q': /* FALLTHROUGH */ + case 'x': /* FALLTHROUGH */ + case 'N': /* FALLTHROUGH */ + case 'P': /* FALLTHROUGH */ + case 'T': /* FALLTHROUGH */ + case 'U': /* FALLTHROUGH */ + case 'V': /* We need to handle non interface options */ + break; + case 'b': + ifo->options |= DHCPCD_BACKGROUND; + break; + case 'c': + ARG_REQUIRED; + if (IN_CONFIG_BLOCK(ifo)) { + logerrx("%s: per interface scripts" + " are no longer supported", + ifname); + return -1; + } + if (ctx->script != dhcpcd_default_script) + free(ctx->script); + s = parse_nstring(NULL, 0, arg); + if (s == 0) { + ctx->script = NULL; + break; + } + dl = (size_t)s; + if (s == -1 || (ctx->script = malloc(dl)) == NULL) { + ctx->script = NULL; + logerr(__func__); + return -1; + } + s = parse_nstring(ctx->script, dl, arg); + if (s == -1 || + ctx->script[0] == '\0' || + strcmp(ctx->script, "/dev/null") == 0) + { + free(ctx->script); + ctx->script = NULL; + } + break; + case 'd': + ifo->options |= DHCPCD_DEBUG; + break; + case 'e': + ARG_REQUIRED; + add_environ(&ifo->environ, arg, 1); + break; + case 'h': + if (!arg) { + ifo->options |= DHCPCD_HOSTNAME; + break; + } + s = parse_nstring(ifo->hostname, sizeof(ifo->hostname), arg); + if (s == -1) { + logerr("%s: hostname", __func__); + return -1; + } + if (s != 0 && ifo->hostname[0] == '.') { + logerrx("hostname cannot begin with ."); + return -1; + } + if (ifo->hostname[0] == '\0') + ifo->options &= ~DHCPCD_HOSTNAME; + else + ifo->options |= DHCPCD_HOSTNAME; + break; + case 'i': + if (arg) + s = parse_string((char *)ifo->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, arg); + else + s = 0; + if (s == -1) { + logerr("vendorclassid"); + return -1; + } + *ifo->vendorclassid = (uint8_t)s; + break; + case 'j': + ARG_REQUIRED; + /* per interface logging is not supported + * don't want to overide the commandline */ + if (!IN_CONFIG_BLOCK(ifo) && ctx->logfile == NULL) { + logclose(); + ctx->logfile = strdup(arg); + logopen(ctx->logfile); + } + break; + case 'k': + ifo->options |= DHCPCD_RELEASE; + break; + case 'l': + ARG_REQUIRED; + if (strcmp(arg, "-1") == 0) { + ifo->leasetime = DHCP_INFINITE_LIFETIME; + break; + } + ifo->leasetime = (uint32_t)strtou(arg, NULL, + 0, 0, UINT32_MAX, &e); + if (e) { + logerrx("failed to convert leasetime %s", arg); + return -1; + } + break; + case 'm': + ARG_REQUIRED; + ifo->metric = (int)strtoi(arg, NULL, 0, 0, INT32_MAX, &e); + if (e) { + logerrx("failed to convert metric %s", arg); + return -1; + } + break; + case 'o': + ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; + set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, + &request, &require, &no, &reject); + if (make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || + make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || + make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) + { + logerrx("unknown option: %s", arg); + return -1; + } + break; + case O_REJECT: + ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; + set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, + &request, &require, &no, &reject); + if (make_option_mask(d, dl, od, odl, reject, arg, 1) != 0 || + make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || + make_option_mask(d, dl, od, odl, require, arg, -1) != 0) + { + logerrx("unknown option: %s", arg); + return -1; + } + break; + case 'p': + ifo->options |= DHCPCD_PERSISTENT; + break; + case 'r': + if (parse_addr(&ifo->req_addr, NULL, arg) != 0) + return -1; + ifo->options |= DHCPCD_REQUEST; + ifo->req_mask.s_addr = 0; + break; + case 's': + if (arg && *arg != '\0') { + /* Strip out a broadcast address */ + p = strchr(arg, '/'); + if (p != NULL) { + p = strchr(p + 1, '/'); + if (p != NULL) + *p = '\0'; + } + i = parse_addr(&ifo->req_addr, &ifo->req_mask, arg); + if (p != NULL) { + /* Ensure the original string is preserved */ + *p++ = '/'; + if (i == 0) + i = parse_addr(&ifo->req_brd, NULL, p); + } + if (i != 0) + return -1; + } else { + ifo->req_addr.s_addr = 0; + ifo->req_mask.s_addr = 0; + } + ifo->options |= DHCPCD_INFORM | DHCPCD_PERSISTENT; + ifo->options &= ~DHCPCD_STATIC; + break; + case O_INFORM6: + ifo->options |= DHCPCD_INFORM6; + break; + case 't': + ARG_REQUIRED; + ifo->timeout = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logerrx("failed to convert timeout %s", arg); + return -1; + } + break; + case 'u': + dl = sizeof(ifo->userclass) - ifo->userclass[0] - 1; + s = parse_string((char *)ifo->userclass + + ifo->userclass[0] + 2, dl, arg); + if (s == -1) { + logerr("userclass"); + return -1; + } + if (s != 0) { + ifo->userclass[ifo->userclass[0] + 1] = (uint8_t)s; + ifo->userclass[0] = (uint8_t)(ifo->userclass[0] + s +1); + } + break; +#ifndef SMALL + case O_MSUSERCLASS: + /* Some Microsoft DHCP servers expect userclass to be an + * opaque blob. This is not RFC 3004 compliant. */ + s = parse_string((char *)ifo->userclass + 1, + sizeof(ifo->userclass) - 1, arg); + if (s == -1) { + logerr("msuserclass"); + return -1; + } + ifo->userclass[0] = (uint8_t)s; + break; +#endif + case 'v': + ARG_REQUIRED; + p = strchr(arg, ','); + if (!p || !p[1]) { + logerrx("invalid vendor format: %s", arg); + return -1; + } + + /* If vendor starts with , then it is not encapsulated */ + if (p == arg) { + arg++; + s = parse_string((char *)ifo->vendor + 1, + VENDOR_MAX_LEN, arg); + if (s == -1) { + logerr("vendor"); + return -1; + } + ifo->vendor[0] = (uint8_t)s; + ifo->options |= DHCPCD_VENDORRAW; + break; + } + + /* Encapsulated vendor options */ + if (ifo->options & DHCPCD_VENDORRAW) { + ifo->options &= ~DHCPCD_VENDORRAW; + ifo->vendor[0] = 0; + } + + /* Strip and preserve the comma */ + *p = '\0'; + i = (int)strtoi(arg, NULL, 0, 1, 254, &e); + *p = ','; + if (e) { + logerrx("vendor option should be between" + " 1 and 254 inclusive"); + return -1; + } + + arg = p + 1; + s = VENDOR_MAX_LEN - ifo->vendor[0] - 2; + if (inet_aton(arg, &addr) == 1) { + if (s < 6) { + s = -1; + errno = ENOBUFS; + } else { + memcpy(ifo->vendor + ifo->vendor[0] + 3, + &addr.s_addr, sizeof(addr.s_addr)); + s = sizeof(addr.s_addr); + } + } else { + s = parse_string((char *)ifo->vendor + + ifo->vendor[0] + 3, (size_t)s, arg); + } + if (s == -1) { + logerr("vendor"); + return -1; + } + if (s != 0) { + ifo->vendor[ifo->vendor[0] + 1] = (uint8_t)i; + ifo->vendor[ifo->vendor[0] + 2] = (uint8_t)s; + ifo->vendor[0] = (uint8_t)(ifo->vendor[0] + s + 2); + } + break; + case 'w': + ifo->options |= DHCPCD_WAITIP; + if (arg != NULL && arg[0] != '\0') { + if (arg[0] == '4' || arg[1] == '4') + ifo->options |= DHCPCD_WAITIP4; + if (arg[0] == '6' || arg[1] == '6') + ifo->options |= DHCPCD_WAITIP6; + } + break; + case 'y': + ARG_REQUIRED; + ifo->reboot = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logerr("failed to convert reboot %s", arg); + return -1; + } + break; + case 'z': + ARG_REQUIRED; + if (!IN_CONFIG_BLOCK(ifo)) + ctx->ifav = splitv(&ctx->ifac, ctx->ifav, arg); + break; + case 'A': + ifo->options &= ~DHCPCD_ARP; + /* IPv4LL requires ARP */ + ifo->options &= ~DHCPCD_IPV4LL; + break; + case 'B': + ifo->options &= ~DHCPCD_DAEMONISE; + break; + case 'C': + ARG_REQUIRED; + /* Commas to spaces for shell */ + while ((p = strchr(arg, ','))) + *p = ' '; + dl = strlen("skip_hooks=") + strlen(arg) + 1; + p = malloc(sizeof(char) * dl); + if (p == NULL) { + logerr(__func__); + return -1; + } + snprintf(p, dl, "skip_hooks=%s", arg); + add_environ(&ifo->environ, p, 0); + free(p); + break; + case 'D': + ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; + if (ifname != NULL) /* duid type only a global option */ + break; + if (arg == NULL) + ctx->duid_type = DUID_DEFAULT; + else if (strcmp(arg, "ll") == 0) + ctx->duid_type = DUID_LL; + else if (strcmp(arg, "llt") == 0) + ctx->duid_type = DUID_LLT; + else if (strcmp(arg, "uuid") == 0) + ctx->duid_type = DUID_UUID; + else { + dl = hwaddr_aton(NULL, arg); + if (dl != 0) { + no = realloc(ctx->duid, dl); + if (no == NULL) + logerrx(__func__); + else { + ctx->duid = no; + ctx->duid_len = hwaddr_aton(no, arg); + } + } + } + break; + case 'E': + ifo->options |= DHCPCD_LASTLEASE; + break; + case 'F': + if (!arg) { + ifo->fqdn = FQDN_BOTH; + break; + } + if (strcmp(arg, "none") == 0) + ifo->fqdn = FQDN_NONE; + else if (strcmp(arg, "ptr") == 0) + ifo->fqdn = FQDN_PTR; + else if (strcmp(arg, "both") == 0) + ifo->fqdn = FQDN_BOTH; + else if (strcmp(arg, "disable") == 0) + ifo->fqdn = FQDN_DISABLE; + else { + logerrx("invalid FQDN value: %s", arg); + return -1; + } + break; + case 'G': + ifo->options &= ~DHCPCD_GATEWAY; + break; + case 'H': + ifo->options |= DHCPCD_XID_HWADDR; + break; + case 'I': + /* Strings have a type of 0 */; + ifo->clientid[1] = 0; + if (arg) + s = parse_hwaddr((char *)ifo->clientid + 1, + CLIENTID_MAX_LEN, arg); + else + s = 0; + if (s == -1) { + logerr("clientid"); + return -1; + } + ifo->options |= DHCPCD_CLIENTID; + ifo->clientid[0] = (uint8_t)s; + ifo->options &= ~DHCPCD_DUID; + break; + case 'J': + ifo->options |= DHCPCD_BROADCAST; + break; + case 'K': + ifo->options &= ~DHCPCD_LINK; + break; + case 'L': + ifo->options &= ~DHCPCD_IPV4LL; + break; + case 'M': + ifo->options |= DHCPCD_MANAGER; + break; + case 'O': + ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; + set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, + &request, &require, &no, &reject); + if (make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || + make_option_mask(d, dl, od, odl, require, arg, -1) != 0 || + make_option_mask(d, dl, od, odl, no, arg, 1) != 0) + { + logerrx("unknown option: %s", arg); + return -1; + } + break; + case 'Q': + ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; + set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, + &request, &require, &no, &reject); + if (make_option_mask(d, dl, od, odl, require, arg, 1) != 0 || + make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || + make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || + make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) + { + logerrx("unknown option: %s", arg); + return -1; + } + break; + case 'S': + ARG_REQUIRED; + p = strchr(arg, '='); + if (p == NULL) { + logerrx("static assignment required"); + return -1; + } + p++; + if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { + if (parse_addr(&ifo->req_addr, + ifo->req_mask.s_addr == 0 ? &ifo->req_mask : NULL, + p) != 0) + return -1; + + ifo->options |= DHCPCD_STATIC; + ifo->options &= ~DHCPCD_INFORM; + } else if (strncmp(arg, "subnet_mask=", + strlen("subnet_mask=")) == 0) + { + if (parse_addr(&ifo->req_mask, NULL, p) != 0) + return -1; + } else if (strncmp(arg, "broadcast_address=", + strlen("broadcast_address=")) == 0) + { + if (parse_addr(&ifo->req_brd, NULL, p) != 0) + return -1; + } else if (strncmp(arg, "routes=", strlen("routes=")) == 0 || + strncmp(arg, "static_routes=", + strlen("static_routes=")) == 0 || + strncmp(arg, "classless_static_routes=", + strlen("classless_static_routes=")) == 0 || + strncmp(arg, "ms_classless_static_routes=", + strlen("ms_classless_static_routes=")) == 0) + { + struct in_addr addr3; + + fp = np = strwhite(p); + if (np == NULL) { + logerrx("all routes need a gateway"); + return -1; + } + *np++ = '\0'; + np = strskipwhite(np); + if (parse_addr(&addr, &addr2, p) == -1 || + parse_addr(&addr3, NULL, np) == -1) + { + *fp = ' '; + return -1; + } + *fp = ' '; + if ((rt = rt_new0(ctx)) == NULL) + return -1; + sa_in_init(&rt->rt_dest, &addr); + sa_in_init(&rt->rt_netmask, &addr2); + sa_in_init(&rt->rt_gateway, &addr3); + if (rt_proto_add_ctx(&ifo->routes, rt, ctx)) + add_environ(&ifo->config, arg, 0); + } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) { + if (parse_addr(&addr, NULL, p) == -1) + return -1; + if ((rt = rt_new0(ctx)) == NULL) + return -1; + addr2.s_addr = INADDR_ANY; + sa_in_init(&rt->rt_dest, &addr2); + sa_in_init(&rt->rt_netmask, &addr2); + sa_in_init(&rt->rt_gateway, &addr); + if (rt_proto_add_ctx(&ifo->routes, rt, ctx)) + add_environ(&ifo->config, arg, 0); + } else if (strncmp(arg, "interface_mtu=", + strlen("interface_mtu=")) == 0 || + strncmp(arg, "mtu=", strlen("mtu=")) == 0) + { + ifo->mtu = (unsigned int)strtou(p, NULL, 0, + MTU_MIN, MTU_MAX, &e); + if (e) { + logerrx("invalid MTU %s", p); + return -1; + } + } else if (strncmp(arg, "ip6_address=", strlen("ip6_address=")) == 0) { + np = strchr(p, '/'); + if (np) + *np++ = '\0'; + if ((i = inet_pton(AF_INET6, p, &ifo->req_addr6)) == 1) { + if (np) { + ifo->req_prefix_len = (uint8_t)strtou(np, + NULL, 0, 0, 128, &e); + if (e) { + logerrx("%s: failed to " + "convert prefix len", + ifname); + return -1; + } + } else + ifo->req_prefix_len = 128; + } + if (np) + *(--np) = '\0'; + if (i != 1) { + logerrx("invalid AF_INET6: %s", p); + memset(&ifo->req_addr6, 0, + sizeof(ifo->req_addr6)); + return -1; + } + } else + add_environ(&ifo->config, arg, 1); + break; + case 'W': + if (parse_addr(&addr, &addr2, arg) != 0) + return -1; + if (strchr(arg, '/') == NULL) + addr2.s_addr = INADDR_BROADCAST; + naddr = reallocarray(ifo->whitelist, + ifo->whitelist_len + 2, sizeof(in_addr_t)); + if (naddr == NULL) { + logerr(__func__); + return -1; + } + ifo->whitelist = naddr; + ifo->whitelist[ifo->whitelist_len++] = addr.s_addr; + ifo->whitelist[ifo->whitelist_len++] = addr2.s_addr; + break; + case 'X': + if (parse_addr(&addr, &addr2, arg) != 0) + return -1; + if (strchr(arg, '/') == NULL) + addr2.s_addr = INADDR_BROADCAST; + naddr = reallocarray(ifo->blacklist, + ifo->blacklist_len + 2, sizeof(in_addr_t)); + if (naddr == NULL) { + logerr(__func__); + return -1; + } + ifo->blacklist = naddr; + ifo->blacklist[ifo->blacklist_len++] = addr.s_addr; + ifo->blacklist[ifo->blacklist_len++] = addr2.s_addr; + break; + case 'Z': + ARG_REQUIRED; + if (!IN_CONFIG_BLOCK(ifo)) + ctx->ifdv = splitv(&ctx->ifdc, ctx->ifdv, arg); + break; + case '1': + ifo->options |= DHCPCD_ONESHOT; + break; + case '4': +#ifdef INET + ifo->options &= ~DHCPCD_IPV6; + ifo->options |= DHCPCD_IPV4; + break; +#else + logerrx("INET has been compiled out"); + return -1; +#endif + case '6': +#ifdef INET6 + ifo->options &= ~DHCPCD_IPV4; + ifo->options |= DHCPCD_IPV6; + break; +#else + logerrx("INET6 has been compiled out"); + return -1; +#endif + case O_IPV4: + ifo->options |= DHCPCD_IPV4; + break; + case O_NOIPV4: + ifo->options &= ~DHCPCD_IPV4; + break; + case O_IPV6: + ifo->options |= DHCPCD_IPV6; + break; + case O_NOIPV6: + ifo->options &= ~DHCPCD_IPV6; + break; + case O_ANONYMOUS: + ifo->options |= DHCPCD_ANONYMOUS; + ifo->options &= ~DHCPCD_HOSTNAME; + ifo->fqdn = FQDN_DISABLE; + + /* Block everything */ + memset(ifo->nomask, 0xff, sizeof(ifo->nomask)); + memset(ifo->nomask6, 0xff, sizeof(ifo->nomask6)); + + /* Allow the bare minimum through */ +#ifdef INET + del_option_mask(ifo->nomask, DHO_SUBNETMASK); + del_option_mask(ifo->nomask, DHO_CSR); + del_option_mask(ifo->nomask, DHO_ROUTER); + del_option_mask(ifo->nomask, DHO_DNSSERVER); + del_option_mask(ifo->nomask, DHO_DNSDOMAIN); + del_option_mask(ifo->nomask, DHO_BROADCAST); + del_option_mask(ifo->nomask, DHO_STATICROUTE); + del_option_mask(ifo->nomask, DHO_SERVERID); + del_option_mask(ifo->nomask, DHO_RENEWALTIME); + del_option_mask(ifo->nomask, DHO_REBINDTIME); + del_option_mask(ifo->nomask, DHO_DNSSEARCH); +#endif + +#ifdef DHCP6 + del_option_mask(ifo->nomask6, D6_OPTION_DNS_SERVERS); + del_option_mask(ifo->nomask6, D6_OPTION_DOMAIN_LIST); + del_option_mask(ifo->nomask6, D6_OPTION_SOL_MAX_RT); + del_option_mask(ifo->nomask6, D6_OPTION_INF_MAX_RT); +#endif + + break; + case O_RANDOMISE_HWADDR: + ifo->randomise_hwaddr = true; + break; +#ifdef INET + case O_ARPING: + while (arg != NULL) { + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + if (parse_addr(&addr, NULL, arg) != 0) + return -1; + naddr = reallocarray(ifo->arping, + (size_t)ifo->arping_len + 1, sizeof(in_addr_t)); + if (naddr == NULL) { + logerr(__func__); + return -1; + } + ifo->arping = naddr; + ifo->arping[ifo->arping_len++] = addr.s_addr; + arg = strskipwhite(fp); + } + break; + case O_DESTINATION: + ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; + set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, + &request, &require, &no, &reject); + if (make_option_mask(d, dl, od, odl, + ifo->dstmask, arg, 2) != 0) + { + if (errno == EINVAL) + logerrx("option does not take" + " an IPv4 address: %s", arg); + else + logerrx("unknown option: %s", arg); + return -1; + } + break; + case O_FALLBACK: + ARG_REQUIRED; + free(ifo->fallback); + ifo->fallback = strdup(arg); + if (ifo->fallback == NULL) { + logerrx(__func__); + return -1; + } + break; +#endif + case O_IAID: + ARG_REQUIRED; + if (ctx->options & DHCPCD_MANAGER && !IN_CONFIG_BLOCK(ifo)) { + logerrx("IAID must belong in an interface block"); + return -1; + } + if (parse_iaid(ifo->iaid, arg, sizeof(ifo->iaid)) == -1) { + logerrx("invalid IAID %s", arg); + return -1; + } + ifo->options |= DHCPCD_IAID; + break; + case O_IPV6RS: + ifo->options |= DHCPCD_IPV6RS; + break; + case O_NOIPV6RS: + ifo->options &= ~DHCPCD_IPV6RS; + break; + case O_IPV6RA_FORK: + ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; + break; + case O_IPV6RA_AUTOCONF: + ifo->options |= DHCPCD_IPV6RA_AUTOCONF; + break; + case O_IPV6RA_NOAUTOCONF: + ifo->options &= ~DHCPCD_IPV6RA_AUTOCONF; + break; + case O_NOALIAS: + ifo->options |= DHCPCD_NOALIAS; + break; +#ifdef DHCP6 + case O_IA_NA: + i = D6_OPTION_IA_NA; + /* FALLTHROUGH */ + case O_IA_TA: + if (i == 0) + i = D6_OPTION_IA_TA; + /* FALLTHROUGH */ + case O_IA_PD: + if (i == 0) { +#ifdef SMALL + logwarnx("%s: IA_PD not compiled in", ifname); + return -1; +#else + if (ctx->options & DHCPCD_MANAGER && + !IN_CONFIG_BLOCK(ifo)) + { + logerrx("IA PD must belong in an " + "interface block"); + return -1; + } + i = D6_OPTION_IA_PD; +#endif + } + if (ctx->options & DHCPCD_MANAGER && + !IN_CONFIG_BLOCK(ifo) && arg) + { + logerrx("IA with IAID must belong in an " + "interface block"); + return -1; + } + ifo->options |= DHCPCD_IA_FORCED; + fp = strwhite(arg); + if (fp) { + *fp++ = '\0'; + fp = strskipwhite(fp); + } + if (arg) { + p = strchr(arg, '/'); + if (p) + *p++ = '\0'; + if (parse_iaid(iaid, arg, sizeof(iaid)) == -1) { + logerr("invalid IAID: %s", arg); + return -1; + } + } + ia = NULL; + for (sl = 0; sl < ifo->ia_len; sl++) { + if ((arg == NULL && !ifo->ia[sl].iaid_set) || + (arg != NULL && ifo->ia[sl].iaid_set && + ifo->ia[sl].ia_type == (uint16_t)i && + ifo->ia[sl].iaid[0] == iaid[0] && + ifo->ia[sl].iaid[1] == iaid[1] && + ifo->ia[sl].iaid[2] == iaid[2] && + ifo->ia[sl].iaid[3] == iaid[3])) + { + ia = &ifo->ia[sl]; + break; + } + } + if (ia == NULL) { + ia = reallocarray(ifo->ia, + ifo->ia_len + 1, sizeof(*ifo->ia)); + if (ia == NULL) { + logerr(__func__); + return -1; + } + ifo->ia = ia; + ia = &ifo->ia[ifo->ia_len++]; + ia->ia_type = (uint16_t)i; + if (arg) { + ia->iaid[0] = iaid[0]; + ia->iaid[1] = iaid[1]; + ia->iaid[2] = iaid[2]; + ia->iaid[3] = iaid[3]; + ia->iaid_set = 1; + } else + ia->iaid_set = 0; + if (!ia->iaid_set || + p == NULL || + ia->ia_type == D6_OPTION_IA_TA) + { + memset(&ia->addr, 0, sizeof(ia->addr)); + ia->prefix_len = 0; + } else { + arg = p; + p = strchr(arg, '/'); + if (p) + *p++ = '\0'; + if (inet_pton(AF_INET6, arg, &ia->addr) != 1) { + logerrx("invalid AF_INET6: %s", arg); + memset(&ia->addr, 0, sizeof(ia->addr)); + } + if (p && ia->ia_type == D6_OPTION_IA_PD) { + ia->prefix_len = (uint8_t)strtou(p, + NULL, 0, 8, 120, &e); + if (e) { + logerrx("%s: failed to convert" + " prefix len", + p); + ia->prefix_len = 0; + } + } + } +#ifndef SMALL + ia->sla_max = 0; + ia->sla_len = 0; + ia->sla = NULL; +#endif + } + +#ifdef SMALL + break; +#else + if (ia->ia_type != D6_OPTION_IA_PD) + break; + + for (p = fp; p; p = fp) { + fp = strwhite(p); + if (fp) { + *fp++ = '\0'; + fp = strskipwhite(fp); + } + sla = reallocarray(ia->sla, + ia->sla_len + 1, sizeof(*ia->sla)); + if (sla == NULL) { + logerr(__func__); + return -1; + } + ia->sla = sla; + sla = &ia->sla[ia->sla_len++]; + np = strchr(p, '/'); + if (np) + *np++ = '\0'; + if (strlcpy(sla->ifname, p, + sizeof(sla->ifname)) >= sizeof(sla->ifname)) + { + logerrx("%s: interface name too long", arg); + goto err_sla; + } + sla->sla_set = false; + sla->prefix_len = 0; + sla->suffix = 1; + p = np; + if (p) { + np = strchr(p, '/'); + if (np) + *np++ = '\0'; + if (*p != '\0') { + sla->sla = (uint32_t)strtou(p, NULL, + 0, 0, UINT32_MAX, &e); + sla->sla_set = true; + if (e) { + logerrx("%s: failed to convert " + "sla", + ifname); + goto err_sla; + } + } + p = np; + } + if (p) { + np = strchr(p, '/'); + if (np) + *np++ = '\0'; + if (*p != '\0') { + sla->prefix_len = (uint8_t)strtou(p, + NULL, 0, 0, 120, &e); + if (e) { + logerrx("%s: failed to " + "convert prefix len", + ifname); + goto err_sla; + } + } + p = np; + } + if (p) { + np = strchr(p, '/'); + if (np) + *np = '\0'; + if (*p != '\0') { + sla->suffix = (uint64_t)strtou(p, NULL, + 0, 0, UINT64_MAX, &e); + if (e) { + logerrx("%s: failed to " + "convert suffix", + ifname); + goto err_sla; + } + } + } + /* Sanity check */ + for (sl = 0; sl < ia->sla_len - 1; sl++) { + slap = &ia->sla[sl]; + if (slap->sla_set != sla->sla_set) { + logerrx("%s: cannot mix automatic " + "and fixed SLA", + sla->ifname); + goto err_sla; + } + if (ia->prefix_len && + (sla->prefix_len == ia->prefix_len || + slap->prefix_len == ia->prefix_len)) + { + logerrx("%s: cannot delegte the same" + "prefix length more than once", + sla->ifname); + goto err_sla; + } + if (!sla->sla_set && + strcmp(slap->ifname, sla->ifname) == 0) + { + logwarnx("%s: cannot specify the " + "same interface twice with " + "an automatic SLA", + sla->ifname); + goto err_sla; + } + if (slap->sla_set && sla->sla_set && + slap->sla == sla->sla) + { + logerrx("%s: cannot" + " assign the same SLA %u" + " more than once", + sla->ifname, sla->sla); + goto err_sla; + } + } + if (sla->sla_set && sla->sla > ia->sla_max) + ia->sla_max = sla->sla; + } + break; +err_sla: + ia->sla_len--; + return -1; +#endif +#endif + case O_HOSTNAME_SHORT: + ifo->options |= DHCPCD_HOSTNAME | DHCPCD_HOSTNAME_SHORT; + break; + case O_DEV: + ARG_REQUIRED; +#ifdef PLUGIN_DEV + if (ctx->dev_load) + free(ctx->dev_load); + ctx->dev_load = strdup(arg); +#endif + break; + case O_NODEV: + ifo->options &= ~DHCPCD_DEV; + break; + case O_DEFINE: + dop = &ifo->dhcp_override; + dop_len = &ifo->dhcp_override_len; + /* FALLTHROUGH */ + case O_DEFINEND: + if (dop == NULL) { + dop = &ifo->nd_override; + dop_len = &ifo->nd_override_len; + } + /* FALLTHROUGH */ + case O_DEFINE6: + if (dop == NULL) { + dop = &ifo->dhcp6_override; + dop_len = &ifo->dhcp6_override_len; + } + /* FALLTHROUGH */ + case O_VENDOPT: + if (dop == NULL) { + dop = &ifo->vivso_override; + dop_len = &ifo->vivso_override_len; + } + *edop = *ldop = NULL; + /* FALLTHROUGH */ + case O_EMBED: + if (dop == NULL) { + if (*edop) { + dop = &(*edop)->embopts; + dop_len = &(*edop)->embopts_len; + } else if (ldop) { + dop = &(*ldop)->embopts; + dop_len = &(*ldop)->embopts_len; + } else { + logerrx("embed must be after a define " + "or encap"); + return -1; + } + } + /* FALLTHROUGH */ + case O_ENCAP: + ARG_REQUIRED; + if (dop == NULL) { + if (*ldop == NULL) { + logerrx("encap must be after a define"); + return -1; + } + dop = &(*ldop)->encopts; + dop_len = &(*ldop)->encopts_len; + } + + /* Shared code for define, define6, embed and encap */ + + /* code */ + if (opt == O_EMBED) /* Embedded options don't have codes */ + u = 0; + else { + fp = strwhite(arg); + if (fp == NULL) { + logerrx("invalid syntax: %s", arg); + return -1; + } + *fp++ = '\0'; + u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logerrx("invalid code: %s", arg); + return -1; + } + arg = strskipwhite(fp); + if (arg == NULL) { + logerrx("invalid syntax"); + return -1; + } + } + /* type */ + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + np = strchr(arg, ':'); + /* length */ + if (np) { + *np++ = '\0'; + bp = NULL; /* No bitflag */ + l = (long)strtou(np, NULL, 0, 0, LONG_MAX, &e); + if (e) { + logerrx("failed to convert length"); + return -1; + } + } else { + l = 0; + bp = strchr(arg, '='); /* bitflag assignment */ + if (bp) + *bp++ = '\0'; + } + t = 0; + if (strcasecmp(arg, "request") == 0) { + t |= OT_REQUEST; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logerrx("incomplete request type"); + return -1; + } + *fp++ = '\0'; + } else if (strcasecmp(arg, "norequest") == 0) { + t |= OT_NOREQ; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logerrx("incomplete request type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "optional") == 0) { + t |= OT_OPTIONAL; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logerrx("incomplete optional type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "index") == 0) { + t |= OT_INDEX; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logerrx("incomplete index type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "array") == 0) { + t |= OT_ARRAY; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logerrx("incomplete array type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "ipaddress") == 0) + t |= OT_ADDRIPV4; + else if (strcasecmp(arg, "ip6address") == 0) + t |= OT_ADDRIPV6; + else if (strcasecmp(arg, "string") == 0) + t |= OT_STRING; + else if (strcasecmp(arg, "byte") == 0) + t |= OT_UINT8; + else if (strcasecmp(arg, "bitflags") == 0) + t |= OT_BITFLAG; + else if (strcasecmp(arg, "uint8") == 0) + t |= OT_UINT8; + else if (strcasecmp(arg, "int8") == 0) + t |= OT_INT8; + else if (strcasecmp(arg, "uint16") == 0) + t |= OT_UINT16; + else if (strcasecmp(arg, "int16") == 0) + t |= OT_INT16; + else if (strcasecmp(arg, "uint32") == 0) + t |= OT_UINT32; + else if (strcasecmp(arg, "int32") == 0) + t |= OT_INT32; + else if (strcasecmp(arg, "flag") == 0) + t |= OT_FLAG; + else if (strcasecmp(arg, "raw") == 0) + t |= OT_STRING | OT_RAW; + else if (strcasecmp(arg, "ascii") == 0) + t |= OT_STRING | OT_ASCII; + else if (strcasecmp(arg, "domain") == 0) + t |= OT_STRING | OT_DOMAIN | OT_RFC1035; + else if (strcasecmp(arg, "dname") == 0) + t |= OT_STRING | OT_DOMAIN; + else if (strcasecmp(arg, "binhex") == 0) + t |= OT_STRING | OT_BINHEX; + else if (strcasecmp(arg, "embed") == 0) + t |= OT_EMBED; + else if (strcasecmp(arg, "encap") == 0) + t |= OT_ENCAP; + else if (strcasecmp(arg, "rfc3361") ==0) + t |= OT_STRING | OT_RFC3361; + else if (strcasecmp(arg, "rfc3442") ==0) + t |= OT_STRING | OT_RFC3442; + else if (strcasecmp(arg, "option") == 0) + t |= OT_OPTION; + else { + logerrx("unknown type: %s", arg); + return -1; + } + if (l && !(t & (OT_STRING | OT_BINHEX))) { + logwarnx("ignoring length for type: %s", arg); + l = 0; + } + if (t & OT_ARRAY && t & (OT_STRING | OT_BINHEX) && + !(t & (OT_RFC1035 | OT_DOMAIN))) + { + logwarnx("ignoring array for strings"); + t &= ~OT_ARRAY; + } + if (t & OT_BITFLAG) { + if (bp == NULL) + logwarnx("missing bitflag assignment"); + } + /* variable */ + if (!fp) { + if (!(t & OT_OPTION)) { + logerrx("type %s requires a variable name", + arg); + return -1; + } + np = NULL; + } else { + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + if (strcasecmp(arg, "reserved")) { + np = strdup(arg); + if (np == NULL) { + logerr(__func__); + return -1; + } + } else { + np = NULL; + t |= OT_RESERVED; + } + } + if (opt != O_EMBED) { + for (dl = 0, ndop = *dop; dl < *dop_len; dl++, ndop++) + { + /* type 0 seems freshly malloced struct + * for us to use */ + if (ndop->option == u || ndop->type == 0) + break; + } + if (dl == *dop_len) + ndop = NULL; + } else + ndop = NULL; + if (ndop == NULL) { + ndop = reallocarray(*dop, *dop_len + 1, sizeof(**dop)); + if (ndop == NULL) { + logerr(__func__); + free(np); + return -1; + } + *dop = ndop; + ndop = &(*dop)[(*dop_len)++]; + ndop->embopts = NULL; + ndop->embopts_len = 0; + ndop->encopts = NULL; + ndop->encopts_len = 0; + } else + free_dhcp_opt_embenc(ndop); + ndop->option = (uint32_t)u; /* could have been 0 */ + ndop->type = t; + ndop->len = (size_t)l; + ndop->var = np; + if (bp) { + dl = strlen(bp); + memcpy(ndop->bitflags, bp, dl); + memset(ndop->bitflags + dl, 0, + sizeof(ndop->bitflags) - dl); + } else + memset(ndop->bitflags, 0, sizeof(ndop->bitflags)); + /* Save the define for embed and encap options */ + switch (opt) { + case O_DEFINE: + case O_DEFINEND: + case O_DEFINE6: + case O_VENDOPT: + *ldop = ndop; + break; + case O_ENCAP: + *edop = ndop; + break; + } + break; + case O_VENDCLASS: + ARG_REQUIRED; + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logerrx("invalid code: %s", arg); + return -1; + } + fp = strskipwhite(fp); + if (fp) { + s = parse_string(NULL, 0, fp); + if (s == -1) { + logerr(__func__); + return -1; + } + dl = (size_t)s; + if (dl + (sizeof(uint16_t) * 2) > UINT16_MAX) { + logerrx("vendor class is too big"); + return -1; + } + np = malloc(dl); + if (np == NULL) { + logerr(__func__); + return -1; + } + parse_string(np, dl, fp); + } else { + dl = 0; + np = NULL; + } + vivco = reallocarray(ifo->vivco, + ifo->vivco_len + 1, sizeof(*ifo->vivco)); + if (vivco == NULL) { + logerr( __func__); + free(np); + return -1; + } + ifo->vivco = vivco; + ifo->vivco_en = (uint32_t)u; + vivco = &ifo->vivco[ifo->vivco_len++]; + vivco->len = dl; + vivco->data = (uint8_t *)np; + break; + case O_AUTHPROTOCOL: + ARG_REQUIRED; +#ifdef AUTH + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + if (strcasecmp(arg, "token") == 0) + ifo->auth.protocol = AUTH_PROTO_TOKEN; + else if (strcasecmp(arg, "delayed") == 0) + ifo->auth.protocol = AUTH_PROTO_DELAYED; + else if (strcasecmp(arg, "delayedrealm") == 0) + ifo->auth.protocol = AUTH_PROTO_DELAYEDREALM; + else { + logerrx("%s: unsupported protocol", arg); + return -1; + } + arg = strskipwhite(fp); + fp = strwhite(arg); + if (arg == NULL) { + ifo->auth.options |= DHCPCD_AUTH_SEND; + if (ifo->auth.protocol == AUTH_PROTO_TOKEN) + ifo->auth.protocol = AUTH_ALG_NONE; + else + ifo->auth.algorithm = AUTH_ALG_HMAC_MD5; + ifo->auth.rdm = AUTH_RDM_MONOTONIC; + break; + } + if (fp) + *fp++ = '\0'; + if (ifo->auth.protocol == AUTH_PROTO_TOKEN) { + np = strchr(arg, '/'); + if (np) { + if (fp == NULL || np < fp) + *np++ = '\0'; + else + np = NULL; + } + if (parse_uint32(&ifo->auth.token_snd_secretid, + arg) == -1) + logerrx("%s: not a number", arg); + else + ifo->auth.token_rcv_secretid = + ifo->auth.token_snd_secretid; + if (np && + parse_uint32(&ifo->auth.token_rcv_secretid, + np) == -1) + logerrx("%s: not a number", arg); + } else { + if (strcasecmp(arg, "hmacmd5") == 0 || + strcasecmp(arg, "hmac-md5") == 0) + ifo->auth.algorithm = AUTH_ALG_HMAC_MD5; + else { + logerrx("%s: unsupported algorithm", arg); + return 1; + } + } + arg = fp; + if (arg == NULL) { + ifo->auth.options |= DHCPCD_AUTH_SEND; + ifo->auth.rdm = AUTH_RDM_MONOTONIC; + break; + } + if (strcasecmp(arg, "monocounter") == 0) { + ifo->auth.rdm = AUTH_RDM_MONOTONIC; + ifo->auth.options |= DHCPCD_AUTH_RDM_COUNTER; + } else if (strcasecmp(arg, "monotonic") ==0 || + strcasecmp(arg, "monotime") == 0) + ifo->auth.rdm = AUTH_RDM_MONOTONIC; + else { + logerrx("%s: unsupported RDM", arg); + return -1; + } + ifo->auth.options |= DHCPCD_AUTH_SEND; + break; +#else + logerrx("no authentication support"); + return -1; +#endif + case O_AUTHTOKEN: + ARG_REQUIRED; +#ifdef AUTH + fp = strwhite(arg); + if (fp == NULL) { + logerrx("authtoken requires a realm"); + return -1; + } + *fp++ = '\0'; + token = calloc(1, sizeof(*token)); + if (token == NULL) { + logerr(__func__); + return -1; + } + if (parse_uint32(&token->secretid, arg) == -1) { + logerrx("%s: not a number", arg); + goto invalid_token; + } + arg = fp; + fp = strend(arg); + if (fp == NULL) { + logerrx("authtoken requies an a key"); + goto invalid_token; + } + *fp++ = '\0'; + s = parse_string(NULL, 0, arg); + if (s == -1) { + logerr("realm_len"); + goto invalid_token; + } + if (s != 0) { + token->realm_len = (size_t)s; + token->realm = malloc(token->realm_len); + if (token->realm == NULL) { + logerr(__func__); + goto invalid_token; + } + parse_string((char *)token->realm, token->realm_len, + arg); + } + arg = fp; + fp = strend(arg); + if (fp == NULL) { + logerrx("authtoken requies an expiry date"); + goto invalid_token; + } + *fp++ = '\0'; + if (*arg == '"') { + arg++; + np = strchr(arg, '"'); + if (np) + *np = '\0'; + } + if (strcmp(arg, "0") == 0 || strcasecmp(arg, "forever") == 0) + token->expire =0; + else { + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) { + logerrx("%s: invalid date time", arg); + goto invalid_token; + } + if ((token->expire = mktime(&tm)) == (time_t)-1) { + logerr("%s: mktime", __func__); + goto invalid_token; + } + } + arg = fp; + s = parse_string(NULL, 0, arg); + if (s == -1 || s == 0) { + if (s == -1) + logerr("token_len"); + else + logerrx("authtoken needs a key"); + goto invalid_token; + } + token->key_len = (size_t)s; + token->key = malloc(token->key_len); + if (token->key == NULL) { + logerr(__func__); + goto invalid_token; + } + parse_string((char *)token->key, token->key_len, arg); + TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next); + break; + +invalid_token: + free(token->realm); + free(token); +#else + logerrx("no authentication support"); +#endif + return -1; + case O_AUTHNOTREQUIRED: + ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE; + break; + case O_DHCP: + ifo->options |= DHCPCD_DHCP | DHCPCD_WANTDHCP | DHCPCD_IPV4; + break; + case O_NODHCP: + ifo->options &= ~DHCPCD_DHCP; + break; + case O_DHCP6: + ifo->options |= DHCPCD_DHCP6 | DHCPCD_IPV6; + break; + case O_NODHCP6: + ifo->options &= ~DHCPCD_DHCP6; + break; + case O_CONTROLGRP: + ARG_REQUIRED; +#ifdef PRIVSEP + /* Control group is already set by this point. + * We don't need to pledge getpw either with this. */ + if (IN_PRIVSEP(ctx)) + break; +#endif +#ifdef _REENTRANT + l = sysconf(_SC_GETGR_R_SIZE_MAX); + if (l == -1) + dl = 1024; + else + dl = (size_t)l; + p = malloc(dl); + if (p == NULL) { + logerr(__func__); + return -1; + } + while ((i = getgrnam_r(arg, &grpbuf, p, dl, &grp)) == + ERANGE) + { + size_t nl = dl * 2; + if (nl < dl) { + logerrx("control_group: out of buffer"); + free(p); + return -1; + } + dl = nl; + np = realloc(p, dl); + if (np == NULL) { + logerr(__func__); + free(p); + return -1; + } + p = np; + } + if (i != 0) { + errno = i; + logerr("getgrnam_r"); + free(p); + return -1; + } + if (grp == NULL) { + if (!ctx->control_group) + logerrx("controlgroup: %s: not found", arg); + free(p); + return -1; + } + ctx->control_group = grp->gr_gid; + free(p); +#else + grp = getgrnam(arg); + if (grp == NULL) { + if (!ctx->control_group) + logerrx("controlgroup: %s: not found", arg); + return -1; + } + ctx->control_group = grp->gr_gid; +#endif + break; + case O_GATEWAY: + ifo->options |= DHCPCD_GATEWAY; + break; + case O_NOUP: + ifo->options &= ~DHCPCD_IF_UP; + break; + case O_SLAAC: + ARG_REQUIRED; + np = strwhite(arg); + if (np != NULL) { + *np++ = '\0'; + np = strskipwhite(np); + } + if (strcmp(arg, "private") == 0 || + strcmp(arg, "stableprivate") == 0 || + strcmp(arg, "stable") == 0) + ifo->options |= DHCPCD_SLAACPRIVATE; + else + ifo->options &= ~DHCPCD_SLAACPRIVATE; + if (np != NULL && + (strcmp(np, "temp") == 0 || strcmp(np, "temporary") == 0)) + ifo->options |= DHCPCD_SLAACTEMP; + break; + case O_BOOTP: + ifo->options |= DHCPCD_BOOTP; + break; + case O_NODELAY: + ifo->options &= ~DHCPCD_INITIAL_DELAY; + break; + case O_LASTLEASE_EXTEND: + ifo->options |= DHCPCD_LASTLEASE | DHCPCD_LASTLEASE_EXTEND; + break; + case O_INACTIVE: + ifo->options |= DHCPCD_INACTIVE; + break; + case O_MUDURL: + ARG_REQUIRED; + s = parse_string((char *)ifo->mudurl + 1, MUDURL_MAX_LEN, arg); + if (s == -1) { + logerr("mudurl"); + return -1; + } + *ifo->mudurl = (uint8_t)s; + break; + case O_LINK_RCVBUF: +#ifndef SMALL + ARG_REQUIRED; + ctx->link_rcvbuf = (int)strtoi(arg, NULL, 0, 0, INT32_MAX, &e); + if (e) { + logerrx("failed to convert link_rcvbuf %s", arg); + return -1; + } +#endif + break; + case O_CONFIGURE: + ifo->options |= DHCPCD_CONFIGURE; + break; + case O_NOCONFIGURE: + ifo->options &= ~DHCPCD_CONFIGURE; + break; + default: + return 0; + } + + return 1; + +#ifdef ARG_REQUIRED +arg_required: + logerrx("option %d requires an argument", opt); + return -1; +#undef ARG_REQUIRED +#endif +} + +static int +parse_config_line(struct dhcpcd_ctx *ctx, const char *ifname, + struct if_options *ifo, const char *opt, char *line, + struct dhcp_opt **ldop, struct dhcp_opt **edop) +{ + unsigned int i; + + for (i = 0; i < sizeof(cf_options) / sizeof(cf_options[0]); i++) { + if (!cf_options[i].name || + strcmp(cf_options[i].name, opt) != 0) + continue; + + if (cf_options[i].has_arg == required_argument && !line) { + logerrx("option requires an argument -- %s", opt); + return -1; + } + + return parse_option(ctx, ifname, ifo, cf_options[i].val, line, + ldop, edop); + } + + if (!(ctx->options & DHCPCD_PRINT_PIDFILE)) + logerrx("unknown option: %s", opt); + return -1; +} + +static void +finish_config(struct if_options *ifo) +{ + + /* Terminate the encapsulated options */ + if (ifo->vendor[0] && !(ifo->options & DHCPCD_VENDORRAW)) { + ifo->vendor[0]++; + ifo->vendor[ifo->vendor[0]] = DHO_END; + /* We are called twice. + * This should be fixed, but in the meantime, this + * guard should suffice */ + ifo->options |= DHCPCD_VENDORRAW; + } + + if (!(ifo->options & DHCPCD_ARP) || + ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) + ifo->options &= ~DHCPCD_IPV4LL; + + if (!(ifo->options & DHCPCD_IPV4)) + ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL | DHCPCD_WAITIP4); + + if (!(ifo->options & DHCPCD_IPV6)) + ifo->options &= + ~(DHCPCD_IPV6RS | DHCPCD_DHCP6 | DHCPCD_WAITIP6); + + if (!(ifo->options & DHCPCD_IPV6RS)) + ifo->options &= + ~(DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS); +} + +struct if_options * +default_config(struct dhcpcd_ctx *ctx) +{ + struct if_options *ifo; + + /* Seed our default options */ + if ((ifo = calloc(1, sizeof(*ifo))) == NULL) { + logerr(__func__); + return NULL; + } + ifo->options |= DHCPCD_IF_UP | DHCPCD_LINK | DHCPCD_INITIAL_DELAY; + ifo->timeout = DEFAULT_TIMEOUT; + ifo->reboot = DEFAULT_REBOOT; + ifo->metric = -1; + ifo->auth.options |= DHCPCD_AUTH_REQUIRE; + rb_tree_init(&ifo->routes, &rt_compare_list_ops); +#ifdef AUTH + TAILQ_INIT(&ifo->auth.tokens); +#endif + + /* Inherit some global defaults */ + if (ctx->options & DHCPCD_CONFIGURE) + ifo->options |= DHCPCD_CONFIGURE; + if (ctx->options & DHCPCD_PERSISTENT) + ifo->options |= DHCPCD_PERSISTENT; + if (ctx->options & DHCPCD_SLAACPRIVATE) + ifo->options |= DHCPCD_SLAACPRIVATE; + + return ifo; +} + +struct if_options * +read_config(struct dhcpcd_ctx *ctx, + const char *ifname, const char *ssid, const char *profile) +{ + struct if_options *ifo; + char buf[UDPLEN_MAX], *bp; /* 64k max config file size */ + char *line, *option, *p; + ssize_t buflen; + size_t vlen; + int skip, have_profile, new_block, had_block; +#if !defined(INET) || !defined(INET6) + size_t i; + struct dhcp_opt *opt; +#endif + struct dhcp_opt *ldop, *edop; + + /* Seed our default options */ + if ((ifo = default_config(ctx)) == NULL) + return NULL; + if (default_options == 0) { + default_options |= DHCPCD_CONFIGURE | DHCPCD_DAEMONISE | + DHCPCD_GATEWAY; +#ifdef INET + skip = socket(PF_INET, SOCK_DGRAM, 0); + if (skip != -1) { + close(skip); + default_options |= DHCPCD_IPV4 | DHCPCD_ARP | + DHCPCD_DHCP | DHCPCD_IPV4LL; + } +#endif +#ifdef INET6 + skip = socket(PF_INET6, SOCK_DGRAM, 0); + if (skip != -1) { + close(skip); + default_options |= DHCPCD_IPV6 | DHCPCD_IPV6RS | + DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS | + DHCPCD_DHCP6; + } +#endif +#ifdef PLUGIN_DEV + default_options |= DHCPCD_DEV; +#endif + } + ifo->options |= default_options; + + CLEAR_CONFIG_BLOCK(ifo); + + vlen = strlcpy((char *)ifo->vendorclassid + 1, ctx->vendor, + sizeof(ifo->vendorclassid) - 1); + ifo->vendorclassid[0] = (uint8_t)(vlen > 255 ? 0 : vlen); + + /* Reset route order */ + ctx->rt_order = 0; + + /* Parse our embedded options file */ + if (ifname == NULL && !(ctx->options & DHCPCD_PRINT_PIDFILE)) { + /* Space for initial estimates */ +#if defined(INET) && defined(INITDEFINES) + ifo->dhcp_override = + calloc(INITDEFINES, sizeof(*ifo->dhcp_override)); + if (ifo->dhcp_override == NULL) + logerr(__func__); + else + ifo->dhcp_override_len = INITDEFINES; +#endif + +#if defined(INET6) && defined(INITDEFINENDS) + ifo->nd_override = + calloc(INITDEFINENDS, sizeof(*ifo->nd_override)); + if (ifo->nd_override == NULL) + logerr(__func__); + else + ifo->nd_override_len = INITDEFINENDS; +#endif +#if defined(INET6) && defined(INITDEFINE6S) + ifo->dhcp6_override = + calloc(INITDEFINE6S, sizeof(*ifo->dhcp6_override)); + if (ifo->dhcp6_override == NULL) + logerr(__func__); + else + ifo->dhcp6_override_len = INITDEFINE6S; +#endif + + /* Now load our embedded config */ +#ifdef EMBEDDED_CONFIG + buflen = dhcp_readfile(ctx, EMBEDDED_CONFIG, buf, sizeof(buf)); + if (buflen == -1) { + logerr("%s: %s", __func__, EMBEDDED_CONFIG); + return ifo; + } + if (buf[buflen - 1] != '\0') { + if ((size_t)buflen < sizeof(buf) - 1) + buflen++; + buf[buflen - 1] = '\0'; + } +#else + buflen = (ssize_t)strlcpy(buf, dhcpcd_embedded_conf, + sizeof(buf)); + if ((size_t)buflen >= sizeof(buf)) { + logerrx("%s: embedded config too big", __func__); + return ifo; + } + /* Our embedded config is NULL terminated */ +#endif + bp = buf; + while ((line = get_line(&bp, &buflen)) != NULL) { + option = strsep(&line, " \t"); + if (line) + line = strskipwhite(line); + /* Trim trailing whitespace */ + if (line) { + p = line + strlen(line) - 1; + while (p != line && + (*p == ' ' || *p == '\t') && + *(p - 1) != '\\') + *p-- = '\0'; + } + parse_config_line(ctx, NULL, ifo, option, line, + &ldop, &edop); + } + +#ifdef INET + ctx->dhcp_opts = ifo->dhcp_override; + ctx->dhcp_opts_len = ifo->dhcp_override_len; +#else + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + free_dhcp_opt_embenc(opt); + free(ifo->dhcp_override); +#endif + ifo->dhcp_override = NULL; + ifo->dhcp_override_len = 0; + +#ifdef INET6 + ctx->nd_opts = ifo->nd_override; + ctx->nd_opts_len = ifo->nd_override_len; +#ifdef DHCP6 + ctx->dhcp6_opts = ifo->dhcp6_override; + ctx->dhcp6_opts_len = ifo->dhcp6_override_len; +#endif +#else + for (i = 0, opt = ifo->nd_override; + i < ifo->nd_override_len; + i++, opt++) + free_dhcp_opt_embenc(opt); + free(ifo->nd_override); + for (i = 0, opt = ifo->dhcp6_override; + i < ifo->dhcp6_override_len; + i++, opt++) + free_dhcp_opt_embenc(opt); + free(ifo->dhcp6_override); +#endif + ifo->nd_override = NULL; + ifo->nd_override_len = 0; + ifo->dhcp6_override = NULL; + ifo->dhcp6_override_len = 0; + + ctx->vivso = ifo->vivso_override; + ctx->vivso_len = ifo->vivso_override_len; + ifo->vivso_override = NULL; + ifo->vivso_override_len = 0; + } + + /* Parse our options file */ + buflen = dhcp_readfile(ctx, ctx->cffile, buf, sizeof(buf)); + if (buflen == -1) { + /* dhcpcd can continue without it, but no DNS options + * would be requested ... */ + logerr("%s: %s", __func__, ctx->cffile); + return ifo; + } + if (buf[buflen - 1] != '\0') { + if ((size_t)buflen < sizeof(buf) - 1) + buflen++; + buf[buflen - 1] = '\0'; + } + dhcp_filemtime(ctx, ctx->cffile, &ifo->mtime); + + ldop = edop = NULL; + skip = have_profile = new_block = 0; + had_block = ifname == NULL ? 1 : 0; + bp = buf; + while ((line = get_line(&bp, &buflen)) != NULL) { + option = strsep(&line, " \t"); + if (line) + line = strskipwhite(line); + /* Trim trailing whitespace */ + if (line) { + p = line + strlen(line) - 1; + while (p != line && + (*p == ' ' || *p == '\t') && + *(p - 1) != '\\') + *p-- = '\0'; + } + if (skip == 0 && new_block) { + had_block = 1; + new_block = 0; + ifo->options &= ~DHCPCD_WAITOPTS; + SET_CONFIG_BLOCK(ifo); + } + + /* Start of an interface block, skip if not ours */ + if (strcmp(option, "interface") == 0) { + char **n; + + new_block = 1; + if (line == NULL) { + /* No interface given */ + skip = 1; + continue; + } + if (ifname && strcmp(line, ifname) == 0) + skip = 0; + else + skip = 1; + if (ifname) + continue; + + n = reallocarray(ctx->ifcv, + (size_t)ctx->ifcc + 1, sizeof(char *)); + if (n == NULL) { + logerr(__func__); + continue; + } + ctx->ifcv = n; + ctx->ifcv[ctx->ifcc] = strdup(line); + if (ctx->ifcv[ctx->ifcc] == NULL) { + logerr(__func__); + continue; + } + ctx->ifcc++; + continue; + } + /* Start of an ssid block, skip if not ours */ + if (strcmp(option, "ssid") == 0) { + new_block = 1; + if (ssid && line && strcmp(line, ssid) == 0) + skip = 0; + else + skip = 1; + continue; + } + /* Start of a profile block, skip if not ours */ + if (strcmp(option, "profile") == 0) { + new_block = 1; + if (profile && line && strcmp(line, profile) == 0) { + skip = 0; + have_profile = 1; + } else + skip = 1; + continue; + } + /* Skip arping if we have selected a profile but not parsing + * one. */ + if (profile && !have_profile && strcmp(option, "arping") == 0) + continue; + if (skip) + continue; + + parse_config_line(ctx, ifname, ifo, option, line, &ldop, &edop); + } + + if (profile && !have_profile) { + free_options(ctx, ifo); + errno = ENOENT; + return NULL; + } + + if (!had_block) + ifo->options &= ~DHCPCD_WAITOPTS; + CLEAR_CONFIG_BLOCK(ifo); + finish_config(ifo); + return ifo; +} + +int +add_options(struct dhcpcd_ctx *ctx, const char *ifname, + struct if_options *ifo, int argc, char **argv) +{ + int oi, opt, r; + unsigned long long wait_opts; + + if (argc == 0) + return 1; + + optind = 0; + r = 1; + /* Don't apply the command line wait options to each interface, + * only use the dhcpcd.conf entry for that. */ + if (ifname != NULL) + wait_opts = ifo->options & DHCPCD_WAITOPTS; + while ((opt = getopt_long(argc, argv, + ctx->options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : IF_OPTS, + cf_options, &oi)) != -1) + { + r = parse_option(ctx, ifname, ifo, opt, optarg, NULL, NULL); + if (r != 1) + break; + } + if (ifname != NULL) { + ifo->options &= ~DHCPCD_WAITOPTS; + ifo->options |= wait_opts; + } + + finish_config(ifo); + return r; +} + +void +free_options(struct dhcpcd_ctx *ctx, struct if_options *ifo) +{ + size_t i; +#ifdef RT_FREE_ROUTE_TABLE + struct interface *ifp; + struct rt *rt; +#endif + struct dhcp_opt *opt; + struct vivco *vo; +#ifdef AUTH + struct token *token; +#endif + + if (ifo == NULL) + return; + + if (ifo->environ) { + i = 0; + while (ifo->environ[i]) + free(ifo->environ[i++]); + free(ifo->environ); + } + if (ifo->config) { + i = 0; + while (ifo->config[i]) + free(ifo->config[i++]); + free(ifo->config); + } + +#ifdef RT_FREE_ROUTE_TABLE + /* Stupidly, we don't know the interface when creating the options. + * As such, make sure each route has one so they can goto the + * free list. */ + ifp = ctx->ifaces != NULL ? TAILQ_FIRST(ctx->ifaces) : NULL; + if (ifp != NULL) { + RB_TREE_FOREACH(rt, &ifo->routes) { + if (rt->rt_ifp == NULL) + rt->rt_ifp = ifp; + } + } +#endif + rt_headclear0(ctx, &ifo->routes, AF_UNSPEC); + + free(ifo->arping); + free(ifo->blacklist); + free(ifo->fallback); + + for (opt = ifo->dhcp_override; + ifo->dhcp_override_len > 0; + opt++, ifo->dhcp_override_len--) + free_dhcp_opt_embenc(opt); + free(ifo->dhcp_override); + for (opt = ifo->nd_override; + ifo->nd_override_len > 0; + opt++, ifo->nd_override_len--) + free_dhcp_opt_embenc(opt); + free(ifo->nd_override); + for (opt = ifo->dhcp6_override; + ifo->dhcp6_override_len > 0; + opt++, ifo->dhcp6_override_len--) + free_dhcp_opt_embenc(opt); + free(ifo->dhcp6_override); + for (vo = ifo->vivco; + ifo->vivco_len > 0; + vo++, ifo->vivco_len--) + free(vo->data); + free(ifo->vivco); + for (opt = ifo->vivso_override; + ifo->vivso_override_len > 0; + opt++, ifo->vivso_override_len--) + free_dhcp_opt_embenc(opt); + free(ifo->vivso_override); + +#if defined(INET6) && !defined(SMALL) + for (; ifo->ia_len > 0; ifo->ia_len--) + free(ifo->ia[ifo->ia_len - 1].sla); +#endif + free(ifo->ia); + +#ifdef AUTH + while ((token = TAILQ_FIRST(&ifo->auth.tokens))) { + TAILQ_REMOVE(&ifo->auth.tokens, token, next); + if (token->realm_len) + free(token->realm); + free(token->key); + free(token); + } +#endif + free(ifo); +} Index: src/if-sun.c =================================================================== --- /dev/null +++ src/if-sun.c @@ -0,0 +1,1761 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Solaris interface driver for dhcpcd + * Copyright (c) 2016-2021 Roy Marples + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +/* Private libsocket interface we can hook into to get + * a better getifaddrs(3). + * From libsocket_priv.h, which is not always distributed so is here. */ +extern int getallifaddrs(sa_family_t, struct ifaddrs **, int64_t); + +#include "config.h" +#include "bpf.h" +#include "common.h" +#include "dhcp.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "route.h" +#include "sa.h" + +#ifndef ARP_MOD_NAME +# define ARP_MOD_NAME "arp" +#endif + +#ifndef RT_ROUNDUP +#define RT_ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(int32_t) - 1))) : sizeof(int32_t)) +#define RT_ADVANCE(x, n) ((x) += RT_ROUNDUP(sa_len((n)))) +#endif + +#define COPYOUT(sin, sa) do { \ + if ((sa) && ((sa)->sa_family == AF_INET)) \ + (sin) = ((const struct sockaddr_in *)(const void *) \ + (sa))->sin_addr; \ + } while (0) + +#define COPYOUT6(sin, sa) do { \ + if ((sa) && ((sa)->sa_family == AF_INET6)) \ + (sin) = ((const struct sockaddr_in6 *)(const void *) \ + (sa))->sin6_addr; \ + } while (0) + +#define COPYSA(dst, src) memcpy((dst), (src), sa_len((src))) + +struct priv { +#ifdef INET6 + int pf_inet6_fd; +#endif +}; + +struct rtm +{ + struct rt_msghdr hdr; + char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; +}; + +static int if_plumb(int, const struct dhcpcd_ctx *, int, const char *); + +int +os_init(void) +{ + return 0; +} + +int +if_init(struct interface *ifp) +{ + +#ifdef INET + if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET, ifp->name) == -1 && + errno != EEXIST) + return -1; +#endif + +#ifdef INET6 + if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET6, ifp->name) == -1 && + errno != EEXIST) + return -1; +#endif + + if (ifp->index == 0) + ifp->index = if_nametoindex(ifp->name); + + return 0; +} + +int +if_conf(__unused struct interface *ifp) +{ + + return 0; +} + +int +if_opensockets_os(struct dhcpcd_ctx *ctx) +{ + struct priv *priv; + int n; + + if ((priv = malloc(sizeof(*priv))) == NULL) + return -1; + ctx->priv = priv; + +#ifdef INET6 + priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); + /* Don't return an error so we at least work on kernels witout INET6 + * even though we expect INET6 support. + * We will fail noisily elsewhere anyway. */ +#endif + + ctx->link_fd = socket(PF_ROUTE, + SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + + if (ctx->link_fd == -1) { + free(ctx->priv); + return -1; + } + + /* Ignore our own route(4) messages. + * Sadly there is no way of doing this for route(4) messages + * generated from addresses we add/delete. */ + n = 0; + if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK, + &n, sizeof(n)) == -1) + logerr("%s: SO_USELOOPBACK", __func__); + + return 0; +} + +void +if_closesockets_os(struct dhcpcd_ctx *ctx) +{ +#ifdef INET6 + struct priv *priv; + + priv = (struct priv *)ctx->priv; + if (priv->pf_inet6_fd != -1) + close(priv->pf_inet6_fd); +#endif + + /* each interface should have closed itself */ + free(ctx->priv); +} + +int +if_setmac(struct interface *ifp, void *mac, uint8_t maclen) +{ + + errno = ENOTSUP; + return -1; +} + +int +if_carrier(struct interface *ifp, __unused const void *ifadata) +{ + kstat_ctl_t *kcp; + kstat_t *ksp; + kstat_named_t *knp; + link_state_t linkstate; + + if (if_getflags(ifp) == -1) + return LINK_UNKNOWN; + + kcp = kstat_open(); + if (kcp == NULL) + goto err; + ksp = kstat_lookup(kcp, UNCONST("link"), 0, ifp->name); + if (ksp == NULL) + goto err; + if (kstat_read(kcp, ksp, NULL) == -1) + goto err; + knp = kstat_data_lookup(ksp, UNCONST("link_state")); + if (knp == NULL) + goto err; + if (knp->data_type != KSTAT_DATA_UINT32) + goto err; + linkstate = (link_state_t)knp->value.ui32; + kstat_close(kcp); + + switch (linkstate) { + case LINK_STATE_UP: + ifp->flags |= IFF_UP; + return LINK_UP; + case LINK_STATE_DOWN: + return LINK_DOWN; + default: + return LINK_UNKNOWN; + } + +err: + if (kcp != NULL) + kstat_close(kcp); + return LINK_UNKNOWN; +} + +bool +if_roaming(__unused struct interface *ifp) +{ + + return false; +} + +int +if_mtu_os(const struct interface *ifp) +{ + dlpi_handle_t dh; + dlpi_info_t dlinfo; + int mtu; + + if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS) + return -1; + if (dlpi_info(dh, &dlinfo, 0) == DLPI_SUCCESS) + mtu = dlinfo.di_max_sdu; + else + mtu = -1; + dlpi_close(dh); + return mtu; +} + +int +if_getssid(__unused struct interface *ifp) +{ + + errno = ENOTSUP; + return -1; +} + +/* XXX work out TAP interfaces? */ +bool +if_ignore(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname) +{ + + return false; +} + +unsigned short +if_vlanid(__unused const struct interface *ifp) +{ + + return 0; +} + +int +if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname) +{ + + return 0; +} + +int +if_machinearch(__unused char *str, __unused size_t len) +{ + + /* There is no extra data really. + * isainfo -v does return amd64, but also i386. */ + return 0; +} + +struct linkwalk { + struct ifaddrs *lw_ifa; + int lw_error; +}; + +static boolean_t +if_newaddr(const char *ifname, void *arg) +{ + struct linkwalk *lw = arg; + int error; + struct ifaddrs *ifa; + dlpi_handle_t dh; + dlpi_info_t dlinfo; + uint8_t pa[DLPI_PHYSADDR_MAX]; + size_t pa_len; + struct sockaddr_dl *sdl; + + ifa = NULL; + error = dlpi_open(ifname, &dh, 0); + if (error == DLPI_ENOLINK) /* Just vanished or in global zone */ + return B_FALSE; + if (error != DLPI_SUCCESS) + goto failed1; + if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS) + goto failed; + + /* For some reason, dlpi_info won't return the + * physical address, it's all zero's. + * So cal dlpi_get_physaddr. */ + pa_len = DLPI_PHYSADDR_MAX; + if (dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, + pa, &pa_len) != DLPI_SUCCESS) + goto failed; + + if ((ifa = calloc(1, sizeof(*ifa))) == NULL) + goto failed; + if ((ifa->ifa_name = strdup(ifname)) == NULL) + goto failed; + if ((sdl = calloc(1, sizeof(*sdl))) == NULL) + goto failed; + + ifa->ifa_addr = (struct sockaddr *)sdl; + sdl->sdl_index = if_nametoindex(ifname); + sdl->sdl_family = AF_LINK; + switch (dlinfo.di_mactype) { + case DL_ETHER: + sdl->sdl_type = IFT_ETHER; + break; + case DL_IB: + sdl->sdl_type = IFT_IB; + break; + default: + sdl->sdl_type = IFT_OTHER; + break; + } + + sdl->sdl_alen = pa_len; + memcpy(sdl->sdl_data, pa, pa_len); + + ifa->ifa_next = lw->lw_ifa; + lw->lw_ifa = ifa; + dlpi_close(dh); + return B_FALSE; + +failed: + dlpi_close(dh); + if (ifa != NULL) { + free(ifa->ifa_name); + free(ifa->ifa_addr); + free(ifa); + } +failed1: + lw->lw_error = errno; + return B_TRUE; +} + +/* Creates an empty sockaddr_dl for lo0. */ +static struct ifaddrs * +if_ifa_lo0(void) +{ + struct ifaddrs *ifa; + struct sockaddr_dl *sdl; + + if ((ifa = calloc(1, sizeof(*ifa))) == NULL) + return NULL; + if ((sdl = calloc(1, sizeof(*sdl))) == NULL) { + free(ifa); + return NULL; + } + if ((ifa->ifa_name = strdup("lo0")) == NULL) { + free(ifa); + free(sdl); + return NULL; + } + + ifa->ifa_addr = (struct sockaddr *)sdl; + ifa->ifa_flags = IFF_LOOPBACK; + sdl->sdl_family = AF_LINK; + sdl->sdl_index = if_nametoindex("lo0"); + + return ifa; +} + +/* getifaddrs(3) does not support AF_LINK, strips aliases and won't + * report addresses that are not UP. + * As such it's just totally useless, so we need to roll our own. */ +int +if_getifaddrs(struct ifaddrs **ifap) +{ + struct linkwalk lw; + struct ifaddrs *ifa; + + /* Private libc function which we should not have to call + * to get non UP addresses. */ + if (getallifaddrs(AF_UNSPEC, &lw.lw_ifa, 0) == -1) + return -1; + + /* Start with some AF_LINK addresses. */ + lw.lw_error = 0; + dlpi_walk(if_newaddr, &lw, 0); + if (lw.lw_error != 0) { + freeifaddrs(lw.lw_ifa); + errno = lw.lw_error; + return -1; + } + + /* lo0 doesn't appear in dlpi_walk, so fudge it. */ + if ((ifa = if_ifa_lo0()) == NULL) { + freeifaddrs(lw.lw_ifa); + return -1; + } + ifa->ifa_next = lw.lw_ifa; + + *ifap = ifa; + return 0; +} + +static void +if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp) +{ + + memset(sdl, 0, sizeof(*sdl)); + sdl->sdl_family = AF_LINK; + sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0; + sdl->sdl_index = (unsigned short)ifp->index; +} + +static int +get_addrs(int type, const void *data, size_t data_len, + const struct sockaddr **sa) +{ + const char *cp, *ep; + int i; + + cp = data; + ep = cp + data_len; + for (i = 0; i < RTAX_MAX; i++) { + if (type & (1 << i)) { + if (cp >= ep) { + errno = EINVAL; + return -1; + } + sa[i] = (const struct sockaddr *)cp; + RT_ADVANCE(cp, sa[i]); + } else + sa[i] = NULL; + } + + return 0; +} + +static struct interface * +if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl) +{ + + if (sdl->sdl_index) + return if_findindex(ctx->ifaces, sdl->sdl_index); + + if (sdl->sdl_nlen) { + char ifname[IF_NAMESIZE]; + + memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen); + ifname[sdl->sdl_nlen] = '\0'; + return if_find(ctx->ifaces, ifname); + } + if (sdl->sdl_alen) { + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->hwlen == sdl->sdl_alen && + memcmp(ifp->hwaddr, + sdl->sdl_data, sdl->sdl_alen) == 0) + return ifp; + } + } + + errno = ENOENT; + return NULL; +} + +static struct interface * +if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa) +{ + if (sa == NULL) { + errno = EINVAL; + return NULL; + } + + switch (sa->sa_family) { + case AF_LINK: + { + const struct sockaddr_dl *sdl; + + sdl = (const void *)sa; + return if_findsdl(ctx, sdl); + } +#ifdef INET + case AF_INET: + { + const struct sockaddr_in *sin; + struct ipv4_addr *ia; + + sin = (const void *)sa; + if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr))) + return ia->iface; + if ((ia = ipv4_findmaskbrd(ctx, &sin->sin_addr))) + return ia->iface; + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + const struct sockaddr_in6 *sin; + struct ipv6_addr *ia; + + sin = (const void *)sa; + if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr))) + return ia->iface; + break; + } +#endif + default: + errno = EAFNOSUPPORT; + return NULL; + } + + errno = ENOENT; + return NULL; +} + +static void +if_route0(struct dhcpcd_ctx *ctx, struct rtm *rtmsg, + unsigned char cmd, const struct rt *rt) +{ + struct rt_msghdr *rtm; + char *bp = rtmsg->buffer; + socklen_t sl; + bool gateway_unspec; + + /* WARNING: Solaris will not allow you to delete RTF_KERNEL routes. + * This includes subnet/prefix routes. */ + +#define ADDSA(sa) do { \ + sl = sa_len((sa)); \ + memcpy(bp, (sa), sl); \ + bp += RT_ROUNDUP(sl); \ + } while (/* CONSTCOND */ 0) + + memset(rtmsg, 0, sizeof(*rtmsg)); + rtm = &rtmsg->hdr; + rtm->rtm_version = RTM_VERSION; + rtm->rtm_type = cmd; + rtm->rtm_seq = ++ctx->seq; + rtm->rtm_flags = rt->rt_flags; + rtm->rtm_addrs = RTA_DST | RTA_GATEWAY; + + gateway_unspec = sa_is_unspecified(&rt->rt_gateway); + + if (cmd == RTM_ADD || cmd == RTM_CHANGE) { + bool netmask_bcast = sa_is_allones(&rt->rt_netmask); + + rtm->rtm_flags |= RTF_UP; + if (!(rtm->rtm_flags & RTF_REJECT) && + !sa_is_loopback(&rt->rt_gateway)) + { + rtm->rtm_addrs |= RTA_IFP; + /* RTA_IFA is currently ignored by the kernel. + * RTA_SRC and RTF_SETSRC look like what we want, + * but they don't work with RTF_GATEWAY. + * We set RTA_IFA just in the hope that the + * kernel will one day support this. */ + if (!sa_is_unspecified(&rt->rt_ifa)) + rtm->rtm_addrs |= RTA_IFA; + } + + if (netmask_bcast) + rtm->rtm_flags |= RTF_HOST; + else if (!gateway_unspec) + rtm->rtm_flags |= RTF_GATEWAY; + + if (rt->rt_dflags & RTDF_STATIC) + rtm->rtm_flags |= RTF_STATIC; + + if (rt->rt_mtu != 0) { + rtm->rtm_inits |= RTV_MTU; + rtm->rtm_rmx.rmx_mtu = rt->rt_mtu; + } + } + + if (!(rtm->rtm_flags & RTF_HOST)) + rtm->rtm_addrs |= RTA_NETMASK; + + ADDSA(&rt->rt_dest); + + if (gateway_unspec) + ADDSA(&rt->rt_ifa); + else + ADDSA(&rt->rt_gateway); + + if (rtm->rtm_addrs & RTA_NETMASK) + ADDSA(&rt->rt_netmask); + + if (rtm->rtm_addrs & RTA_IFP) { + struct sockaddr_dl sdl; + + if_linkaddr(&sdl, rt->rt_ifp); + ADDSA((struct sockaddr *)&sdl); + } + + if (rtm->rtm_addrs & RTA_IFA) + ADDSA(&rt->rt_ifa); + +#if 0 + if (rtm->rtm_addrs & RTA_SRC) + ADDSA(&rt->rt_ifa); +#endif + + rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm); +} + +int +if_route(unsigned char cmd, const struct rt *rt) +{ + struct rtm rtm; + struct dhcpcd_ctx *ctx = rt->rt_ifp->ctx; + + if_route0(ctx, &rtm, cmd, rt); + + if (write(ctx->link_fd, &rtm, rtm.hdr.rtm_msglen) == -1) + return -1; + return 0; +} + +static int +if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm) +{ + const struct sockaddr *rti_info[RTAX_MAX]; + + if (~rtm->rtm_addrs & RTA_DST) { + errno = EINVAL; + return -1; + } + + if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm), + rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1) + return -1; + + memset(rt, 0, sizeof(*rt)); + + rt->rt_flags = (unsigned int)rtm->rtm_flags; + COPYSA(&rt->rt_dest, rti_info[RTAX_DST]); + if (rtm->rtm_addrs & RTA_NETMASK) + COPYSA(&rt->rt_netmask, rti_info[RTAX_NETMASK]); + + /* dhcpcd likes an unspecified gateway to indicate via the link. + * However we need to know if gateway was a link with an address. */ + if (rtm->rtm_addrs & RTA_GATEWAY) { + if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) { + const struct sockaddr_dl *sdl; + + sdl = (const struct sockaddr_dl*) + (const void *)rti_info[RTAX_GATEWAY]; + if (sdl->sdl_alen != 0) + rt->rt_dflags |= RTDF_GATELINK; + } else if (rtm->rtm_flags & RTF_GATEWAY) + COPYSA(&rt->rt_gateway, rti_info[RTAX_GATEWAY]); + } + + if (rtm->rtm_addrs & RTA_SRC) + COPYSA(&rt->rt_ifa, rti_info[RTAX_SRC]); + rt->rt_mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu; + + if (rtm->rtm_index) + rt->rt_ifp = if_findindex(ctx->ifaces, rtm->rtm_index); + else if (rtm->rtm_addrs & RTA_IFP) + rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_IFP]); + else if (rtm->rtm_addrs & RTA_GATEWAY) + rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_GATEWAY]); + else + rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_DST]); + + if (rt->rt_ifp == NULL && rtm->rtm_type == RTM_MISS) + rt->rt_ifp = if_loopback(ctx); + + if (rt->rt_ifp == NULL) { + errno = ESRCH; + return -1; + } + + return 0; +} + +static struct rt * +if_route_get(struct dhcpcd_ctx *ctx, struct rt *rt) +{ + struct rtm rtm; + int s; + struct iovec iov = { .iov_base = &rtm, .iov_len = sizeof(rtm) }; + struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; + ssize_t len; + struct rt *rtw = rt; + + if_route0(ctx, &rtm, RTM_GET, rt); + rt = NULL; + s = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC, 0); + if (s == -1) + return NULL; + if (write(s, &rtm, rtm.hdr.rtm_msglen) == -1) + goto out; + if ((len = recvmsg(s, &msg, 0)) == -1) + goto out; + if ((size_t)len < sizeof(rtm.hdr) || len < rtm.hdr.rtm_msglen) { + errno = EINVAL; + goto out; + } + if (if_copyrt(ctx, rtw, &rtm.hdr) == -1) + goto out; + rt = rtw; + +out: + close(s); + return rt; +} + +static int +if_finishrt(struct dhcpcd_ctx *ctx, struct rt *rt) +{ + int mtu; + + /* Solaris has a subnet route with the gateway + * of the owning address. + * dhcpcd has a blank gateway here to indicate a + * subnet route. */ + if (!sa_is_unspecified(&rt->rt_dest) && + !sa_is_unspecified(&rt->rt_gateway)) + { + switch(rt->rt_gateway.sa_family) { +#ifdef INET + case AF_INET: + { + struct in_addr *in; + + in = &satosin(&rt->rt_gateway)->sin_addr; + if (ipv4_findaddr(ctx, in)) + in->s_addr = INADDR_ANY; + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + struct in6_addr *in6; + + in6 = &satosin6(&rt->rt_gateway)->sin6_addr; + if (ipv6_findaddr(ctx, in6, 0)) + *in6 = in6addr_any; + break; + } +#endif + } + } + + /* Solaris doesn't set interfaces for some routes. + * This sucks, so we need to call RTM_GET to + * work out the interface. */ + if (rt->rt_ifp == NULL) { + if (if_route_get(ctx, rt) == NULL) { + rt->rt_ifp = if_loopback(ctx); + if (rt->rt_ifp == NULL) + return - 1; + } + } + + /* Solaris likes to set route MTU to match + * interface MTU when adding routes. + * This confuses dhcpcd as it expects MTU to be 0 + * when no explicit MTU has been set. */ + mtu = if_getmtu(rt->rt_ifp); + if (mtu == -1) + return -1; + if (rt->rt_mtu == (unsigned int)mtu) + rt->rt_mtu = 0; + + return 0; +} + +static int +if_addrflags0(int fd, int af, const char *ifname) +{ + struct lifreq lifr; + int flags; + + memset(&lifr, 0, sizeof(lifr)); + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) + return -1; + + flags = 0; + if (lifr.lifr_flags & IFF_DUPLICATE) + flags |= af == AF_INET6 ? IN6_IFF_DUPLICATED:IN_IFF_DUPLICATED; + else if (!(lifr.lifr_flags & IFF_UP)) + flags |= af == AF_INET6 ? IN6_IFF_TENTATIVE:IN_IFF_TENTATIVE; + return flags; +} + +static int +if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) +{ + const struct sockaddr *sa; + struct rt rt; + + if (rtm->rtm_msglen < sizeof(*rtm) + sizeof(*sa)) { + errno = EINVAL; + return -1; + } + + if (if_copyrt(ctx, &rt, rtm) == -1 && errno != ESRCH) + return -1; + +#ifdef INET6 + /* + * BSD announces host routes. + * As such, we should be notified of reachability by its + * existance with a hardware address. + * Ensure we don't call this for a newly incomplete state. + */ + if (rt.rt_dest.sa_family == AF_INET6 && + (rt.rt_flags & RTF_HOST || rtm->rtm_type == RTM_MISS) && + !(rtm->rtm_type == RTM_ADD && !(rt.rt_dflags & RTDF_GATELINK))) + { + bool reachable; + + reachable = (rtm->rtm_type == RTM_ADD || + rtm->rtm_type == RTM_CHANGE) && + rt.rt_dflags & RTDF_GATELINK; + ipv6nd_neighbour(ctx, &rt.rt_ss_dest.sin6.sin6_addr, reachable); + } +#endif + + if (if_finishrt(ctx, &rt) == -1) + return -1; + rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid); + return 0; +} + +static bool +if_getalias(struct interface *ifp, const struct sockaddr *sa, char *alias) +{ + struct ifaddrs *ifaddrs, *ifa; + struct interface *ifpx; + bool found; + + ifaddrs = NULL; + if (getallifaddrs(sa->sa_family, &ifaddrs, 0) == -1) + return false; + found = false; + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (sa_cmp(sa, ifa->ifa_addr) != 0) + continue; + /* Check it's for the right interace. */ + ifpx = if_find(ifp->ctx->ifaces, ifa->ifa_name); + if (ifp == ifpx) { + strlcpy(alias, ifa->ifa_name, IF_NAMESIZE); + found = true; + break; + } + } + freeifaddrs(ifaddrs); + return found; +} + +static int +if_getbrdaddr(struct dhcpcd_ctx *ctx, const char *ifname, struct in_addr *brd) +{ + struct lifreq lifr = { 0 }; + int r; + + memset(&lifr, 0, sizeof(lifr)); + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + errno = 0; + r = ioctl(ctx->pf_inet_fd, SIOCGLIFBRDADDR, &lifr, sizeof(lifr)); + if (r != -1) + COPYOUT(*brd, (struct sockaddr *)&lifr.lifr_broadaddr); + return r; +} + +static int +if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam) +{ + struct interface *ifp; + const struct sockaddr *sa, *rti_info[RTAX_MAX]; + int flags; + char ifalias[IF_NAMESIZE]; + + if (ifam->ifam_msglen < sizeof(*ifam)) { + errno = EINVAL; + return -1; + } + if (~ifam->ifam_addrs & RTA_IFA) + return 0; + + if (get_addrs(ifam->ifam_addrs, (const char *)ifam + sizeof(*ifam), + ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1) + return -1; + sa = rti_info[RTAX_IFA]; + + /* XXX We have no way of knowing who generated these + * messages wich truely sucks because we want to + * avoid listening to our own delete messages. */ + if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL) + return 0; + + /* + * ifa_msghdr does not supply the alias, just the interface index. + * This is very bad, because it means we have to call getifaddrs + * and trawl the list of addresses to find the added address. + * To make life worse, you can have the same address on the same + * interface with different aliases. + * So this hack is not entirely accurate. + * + * IF anyone is going to fix Solaris, plesse consider adding the + * following fields to extend ifa_msghdr: + * ifam_alias + * ifam_pid + */ + if (ifam->ifam_type != RTM_DELADDR && !if_getalias(ifp, sa, ifalias)) + return 0; + + switch (sa->sa_family) { + case AF_LINK: + { + struct sockaddr_dl sdl; + + if (ifam->ifam_type != RTM_CHGADDR && + ifam->ifam_type != RTM_NEWADDR) + break; + memcpy(&sdl, rti_info[RTAX_IFA], sizeof(sdl)); + dhcpcd_handlehwaddr(ifp, ifp->hwtype, + CLLADDR(&sdl), sdl.sdl_alen); + break; + } +#ifdef INET + case AF_INET: + { + struct in_addr addr, mask, bcast; + + COPYOUT(addr, rti_info[RTAX_IFA]); + COPYOUT(mask, rti_info[RTAX_NETMASK]); + COPYOUT(bcast, rti_info[RTAX_BRD]); + + if (ifam->ifam_type == RTM_DELADDR) { + struct ipv4_addr *ia; + + ia = ipv4_iffindaddr(ifp, &addr, &mask); + if (ia == NULL) + return 0; + strlcpy(ifalias, ia->alias, sizeof(ifalias)); + } else if (bcast.s_addr == INADDR_ANY) { + /* Work around a bug where broadcast + * address is not correctly reported. */ + if (if_getbrdaddr(ctx, ifalias, &bcast) == -1) + return 0; + } + flags = if_addrflags(ifp, NULL, ifalias); + if (ifam->ifam_type == RTM_DELADDR) { + if (flags != -1) + return 0; + } else if (flags == -1) + return 0; + + ipv4_handleifa(ctx, + ifam->ifam_type == RTM_CHGADDR ? + RTM_NEWADDR : ifam->ifam_type, + NULL, ifalias, &addr, &mask, &bcast, flags, 0); + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + struct in6_addr addr6, mask6; + const struct sockaddr_in6 *sin6; + + sin6 = (const void *)rti_info[RTAX_IFA]; + addr6 = sin6->sin6_addr; + sin6 = (const void *)rti_info[RTAX_NETMASK]; + mask6 = sin6->sin6_addr; + + if (ifam->ifam_type == RTM_DELADDR) { + struct ipv6_addr *ia; + + ia = ipv6_iffindaddr(ifp, &addr6, 0); + if (ia == NULL) + return 0; + strlcpy(ifalias, ia->alias, sizeof(ifalias)); + } + flags = if_addrflags6(ifp, NULL, ifalias); + if (ifam->ifam_type == RTM_DELADDR) { + if (flags != -1) + return 0; + } else if (flags == -1) + return 0; + + ipv6_handleifa(ctx, + ifam->ifam_type == RTM_CHGADDR ? + RTM_NEWADDR : ifam->ifam_type, + NULL, ifalias, &addr6, ipv6_prefixlen(&mask6), flags, 0); + break; + } +#endif + } + + return 0; +} + +static int +if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm) +{ + struct interface *ifp; + int state; + unsigned int flags; + + if (ifm->ifm_msglen < sizeof(*ifm)) { + errno = EINVAL; + return -1; + } + + if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL) + return 0; + flags = (unsigned int)ifm->ifm_flags; + if (ifm->ifm_flags & IFF_OFFLINE) + state = LINK_DOWN; + else { + state = LINK_UP; + flags |= IFF_UP; + } + dhcpcd_handlecarrier(ifp, state, flags); + return 0; +} + +static int +if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) +{ + + if (rtm->rtm_version != RTM_VERSION) + return 0; + + switch(rtm->rtm_type) { + case RTM_IFINFO: + return if_ifinfo(ctx, (const void *)rtm); + case RTM_ADD: /* FALLTHROUGH */ + case RTM_CHANGE: /* FALLTHROUGH */ + case RTM_DELETE: /* FALLTHROUGH */ + case RTM_MISS: + return if_rtm(ctx, (const void *)rtm); + case RTM_CHGADDR: /* FALLTHROUGH */ + case RTM_DELADDR: /* FALLTHROUGH */ + case RTM_NEWADDR: + return if_ifa(ctx, (const void *)rtm); + } + + return 0; +} + +int +if_handlelink(struct dhcpcd_ctx *ctx) +{ + struct rtm rtm; + ssize_t len; + + len = read(ctx->link_fd, &rtm, sizeof(rtm)); + if (len == -1) + return -1; + if (len == 0) + return 0; + if ((size_t)len < sizeof(rtm.hdr.rtm_msglen) || + len != rtm.hdr.rtm_msglen) + { + errno = EINVAL; + return -1; + } + /* + * Coverity thinks that the data could be tainted from here. + * I have no idea how because the length of the data we read + * is guarded by len and checked to match rtm_msglen. + * The issue seems to be related to extracting the addresses + * at the end of the header, but seems to have no issues with the + * equivalent call in if_initrt. + */ + /* coverity[tainted_data] */ + return if_dispatch(ctx, &rtm.hdr); +} + +static void +if_octetstr(char *buf, const Octet_t *o, ssize_t len) +{ + int i; + char *p; + + p = buf; + for (i = 0; i < o->o_length; i++) { + if ((p + 1) - buf < len) + *p++ = o->o_bytes[i]; + else + break; + } + *p = '\0'; +} + +static int +if_setflags(int fd, const char *ifname, uint64_t flags) +{ + struct lifreq lifr = { .lifr_addrlen = 0 }; + + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) + return -1; + if ((lifr.lifr_flags & flags) != flags) { + lifr.lifr_flags |= flags; + if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) + return -1; + } + return 0; +} + +static int +if_addaddr(int fd, const char *ifname, + struct sockaddr_storage *addr, struct sockaddr_storage *mask, + struct sockaddr_storage *brd, uint8_t plen) +{ + struct lifreq lifr = { .lifr_addrlen = plen }; + + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + + /* First assign the netmask. */ + lifr.lifr_addr = *mask; + if (addr == NULL) { + lifr.lifr_addrlen = plen; + if (ioctl(fd, SIOCSLIFSUBNET, &lifr) == -1) + return -1; + goto up; + } else { + if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) + return -1; + } + + /* Then assign the address. */ + lifr.lifr_addr = *addr; + if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1) + return -1; + + /* Then assign the broadcast address. */ + if (brd != NULL) { + lifr.lifr_broadaddr = *brd; + if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1) + return -1; + } + +up: + return if_setflags(fd, ifname, IFF_UP); +} + +static int +if_getaf_fd(const struct dhcpcd_ctx *ctx, int af) +{ + + if (af == AF_INET) + return ctx->pf_inet_fd; + if (af == AF_INET6) { + struct priv *priv; + + priv = (struct priv *)ctx->priv; + return priv->pf_inet6_fd; + } + + errno = EAFNOSUPPORT; + return -1; +} + +int +if_getsubnet(struct dhcpcd_ctx *ctx, const char *ifname, int af, + void *subnet, size_t subnet_len) +{ + struct lifreq lifr = { .lifr_addrlen = 0 }; + int fd; + + fd = if_getaf_fd(ctx, af); + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + if (ioctl(fd, SIOCGLIFSUBNET, &lifr) == -1) + return -1; + memcpy(subnet, &lifr.lifr_addr, MIN(subnet_len,sizeof(lifr.lifr_addr))); + return 0; +} + +static int +if_plumblif(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname) +{ + struct lifreq lifr; + int s; + + memset(&lifr, 0, sizeof(lifr)); + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + lifr.lifr_addr.ss_family = af; + s = if_getaf_fd(ctx, af); + return ioctl(s, + cmd == RTM_NEWADDR ? SIOCLIFADDIF : SIOCLIFREMOVEIF, + &lifr) == -1 && errno != EEXIST ? -1 : 0; +} + +static int +if_plumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname) +{ + dlpi_handle_t dh, dh_arp = NULL; + int fd, af_fd, mux_fd, arp_fd = -1, mux_id, retval; + uint64_t flags; + struct lifreq lifr; + const char *udp_dev; + struct strioctl ioc; + struct if_spec spec; + + if (if_nametospec(ifname, &spec) == -1) + return -1; + + af_fd = if_getaf_fd(ctx, af); + + switch (af) { + case AF_INET: + flags = IFF_IPV4; + udp_dev = UDP_DEV_NAME; + break; + case AF_INET6: + /* We will take care of setting the link local address. */ + flags = IFF_IPV6 | IFF_NOLINKLOCAL; + udp_dev = UDP6_DEV_NAME; + break; + default: + errno = EPROTONOSUPPORT; + return -1; + } + + if (dlpi_open(ifname, &dh, DLPI_NOATTACH) != DLPI_SUCCESS) { + errno = EINVAL; + return -1; + } + + fd = dlpi_fd(dh); + retval = -1; + mux_fd = -1; + if (ioctl(fd, I_PUSH, IP_MOD_NAME) == -1) + goto out; + memset(&lifr, 0, sizeof(lifr)); + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + lifr.lifr_ppa = spec.ppa; + lifr.lifr_flags = flags; + if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1) + goto out; + + /* Get full flags. */ + if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1) + goto out; + flags = lifr.lifr_flags; + + /* Open UDP as a multiplexor to PLINK the interface stream. + * UDP is used because STREAMS will not let you PLINK a driver + * under itself and IP is generally at the bottom of the stream. */ + if ((mux_fd = open(udp_dev, O_RDWR)) == -1) + goto out; + /* POP off all undesired modules. */ + while (ioctl(mux_fd, I_POP, 0) != -1) + ; + if (errno != EINVAL) + goto out; + if(ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1) + goto out; + + if (flags & (IFF_NOARP | IFF_IPV6)) { + /* PLINK the interface stream so it persists. */ + if (ioctl(mux_fd, I_PLINK, fd) == -1) + goto out; + goto done; + } + + if (dlpi_open(ifname, &dh_arp, DLPI_NOATTACH) != DLPI_SUCCESS) + goto out; + arp_fd = dlpi_fd(dh_arp); + if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1) + goto out; + + memset(&lifr, 0, sizeof(lifr)); + strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); + lifr.lifr_ppa = spec.ppa; + lifr.lifr_flags = flags; + memset(&ioc, 0, sizeof(ioc)); + ioc.ic_cmd = SIOCSLIFNAME; + ioc.ic_dp = (char *)&lifr; + ioc.ic_len = sizeof(lifr); + if (ioctl(arp_fd, I_STR, &ioc) == -1) + goto out; + + /* PLINK the interface stream so it persists. */ + mux_id = ioctl(mux_fd, I_PLINK, fd); + if (mux_id == -1) + goto out; + if (ioctl(mux_fd, I_PLINK, arp_fd) == -1) { + ioctl(mux_fd, I_PUNLINK, mux_id); + goto out; + } + +done: + retval = 0; + +out: + dlpi_close(dh); + if (dh_arp != NULL) + dlpi_close(dh_arp); + if (mux_fd != -1) + close(mux_fd); + return retval; +} + +static int +if_unplumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname) +{ + struct sockaddr_storage addr = { .ss_family = af }; + int fd; + + /* For the time being, don't unplumb the interface, just + * set the address to zero. */ + fd = if_getaf_fd(ctx, af); + return if_addaddr(fd, ifname, &addr, &addr, + af == AF_INET ? &addr : NULL, 0); +} + +static int +if_plumb(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname) +{ + struct if_spec spec; + + if (if_nametospec(ifname, &spec) == -1) + return -1; + if (spec.lun != -1) + return if_plumblif(cmd, ctx, af, ifname); + if (cmd == RTM_NEWADDR) + return if_plumbif(ctx, af, ifname); + else + return if_unplumbif(ctx, af, ifname); +} + +#ifdef INET +static int +if_walkrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len) +{ + mib2_ipRouteEntry_t *re, *e; + struct rt rt, *rtn; + char ifname[IF_NAMESIZE]; + struct in_addr in; + + if (len % sizeof(*re) != 0) { + errno = EINVAL; + return -1; + } + + re = (mib2_ipRouteEntry_t *)data; + e = (mib2_ipRouteEntry_t *)(data + len); + do { + /* Skip route types we don't want. */ + switch (re->ipRouteInfo.re_ire_type) { + case IRE_IF_CLONE: + case IRE_BROADCAST: + case IRE_MULTICAST: + case IRE_NOROUTE: + case IRE_LOCAL: + continue; + default: + break; + } + + memset(&rt, 0, sizeof(rt)); + in.s_addr = re->ipRouteDest; + sa_in_init(&rt.rt_dest, &in); + in.s_addr = re->ipRouteMask; + sa_in_init(&rt.rt_netmask, &in); + in.s_addr = re->ipRouteNextHop; + sa_in_init(&rt.rt_gateway, &in); + rt.rt_flags = re->ipRouteInfo.re_flags; + in.s_addr = re->ipRouteInfo.re_src_addr; + sa_in_init(&rt.rt_ifa, &in); + rt.rt_mtu = re->ipRouteInfo.re_max_frag; + if_octetstr(ifname, &re->ipRouteIfIndex, sizeof(ifname)); + rt.rt_ifp = if_find(ctx->ifaces, ifname); + if (if_finishrt(ctx, &rt) == -1) { + logerr(__func__); + continue; + } + if ((rtn = rt_new(rt.rt_ifp)) == NULL) { + logerr(__func__); + break; + } + memcpy(rtn, &rt, sizeof(*rtn)); + if (rb_tree_insert_node(routes, rtn) != rtn) + rt_free(rtn); + } while (++re < e); + return 0; +} +#endif + +#ifdef INET6 +static int +if_walkrt6(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len) +{ + mib2_ipv6RouteEntry_t *re, *e; + struct rt rt, *rtn; + char ifname[IF_NAMESIZE]; + struct in6_addr in6; + + if (len % sizeof(*re) != 0) { + errno = EINVAL; + return -1; + } + + re = (mib2_ipv6RouteEntry_t *)data; + e = (mib2_ipv6RouteEntry_t *)(data + len); + + do { + /* Skip route types we don't want. */ + switch (re->ipv6RouteInfo.re_ire_type) { + case IRE_IF_CLONE: + case IRE_BROADCAST: + case IRE_MULTICAST: + case IRE_NOROUTE: + case IRE_LOCAL: + continue; + default: + break; + } + + memset(&rt, 0, sizeof(rt)); + sa_in6_init(&rt.rt_dest, &re->ipv6RouteDest); + ipv6_mask(&in6, re->ipv6RoutePfxLength); + sa_in6_init(&rt.rt_netmask, &in6); + sa_in6_init(&rt.rt_gateway, &re->ipv6RouteNextHop); + sa_in6_init(&rt.rt_ifa, &re->ipv6RouteInfo.re_src_addr); + rt.rt_mtu = re->ipv6RouteInfo.re_max_frag; + if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname)); + rt.rt_ifp = if_find(ctx->ifaces, ifname); + if (if_finishrt(ctx, &rt) == -1) { + logerr(__func__); + continue; + } + if ((rtn = rt_new(rt.rt_ifp)) == NULL) { + logerr(__func__); + break; + } + memcpy(rtn, &rt, sizeof(*rtn)); + if (rb_tree_insert_node(routes, rtn) != rtn) + rt_free(rtn); + } while (++re < e); + return 0; +} +#endif + +static int +if_parsert(struct dhcpcd_ctx *ctx, rb_tree_t *routes, + unsigned int level, unsigned int name, + int (*walkrt)(struct dhcpcd_ctx *, rb_tree_t *, char *, size_t)) +{ + int s, retval, code, flags; + uintptr_t buf[512 / sizeof(uintptr_t)]; + struct strbuf ctlbuf, databuf; + struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf; + struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf; + struct T_error_ack *tea = (struct T_error_ack *)buf; + struct opthdr *req; + + if ((s = open("/dev/arp", O_RDWR)) == -1) + return -1; + + /* Assume we are erroring. */ + retval = -1; + + tor->PRIM_type = T_SVR4_OPTMGMT_REQ; + tor->OPT_offset = sizeof (struct T_optmgmt_req); + tor->OPT_length = sizeof (struct opthdr); + tor->MGMT_flags = T_CURRENT; + + req = (struct opthdr *)&tor[1]; + req->level = EXPER_IP_AND_ALL_IRES; + req->name = 0; + req->len = 1; + + ctlbuf.buf = (char *)buf; + ctlbuf.len = tor->OPT_length + tor->OPT_offset; + if (putmsg(s, &ctlbuf, NULL, 0) == 1) + goto out; + + req = (struct opthdr *)&toa[1]; + ctlbuf.maxlen = sizeof(buf); + + /* Create a reasonable buffer to start with */ + databuf.maxlen = BUFSIZ * 2; + if ((databuf.buf = malloc(databuf.maxlen)) == NULL) + goto out; + + for (;;) { + flags = 0; + if ((code = getmsg(s, &ctlbuf, 0, &flags)) == -1) + break; + if (code == 0 && + toa->PRIM_type == T_OPTMGMT_ACK && + toa->MGMT_flags == T_SUCCESS && + (size_t)ctlbuf.len >= sizeof(struct T_optmgmt_ack)) + { + /* End of messages, so return success! */ + retval = 0; + break; + } + if (tea->PRIM_type == T_ERROR_ACK) { + errno = tea->TLI_error == TSYSERR ? + tea->UNIX_error : EPROTO; + break; + } + if (code != MOREDATA || + toa->PRIM_type != T_OPTMGMT_ACK || + toa->MGMT_flags != T_SUCCESS) + { + errno = ENOMSG; + break; + } + + /* Try to ensure out buffer is big enough + * for future messages as well. */ + if ((size_t)databuf.maxlen < req->len) { + size_t newlen; + + free(databuf.buf); + newlen = roundup(req->len, BUFSIZ); + if ((databuf.buf = malloc(newlen)) == NULL) + break; + databuf.maxlen = newlen; + } + + flags = 0; + if (getmsg(s, NULL, &databuf, &flags) == -1) + break; + + /* We always have to get the data before moving onto + * the next item, so don't move this test higher up + * to avoid the buffer allocation and getmsg calls. */ + if (req->level == level && req->name == name) { + if (walkrt(ctx, routes, databuf.buf, req->len) == -1) + break; + } + } + + free(databuf.buf); +out: + close(s); + return retval; +} + + +int +if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, int af) +{ + +#ifdef INET + if ((af == AF_UNSPEC || af == AF_INET) && + if_parsert(ctx, routes, MIB2_IP,MIB2_IP_ROUTE, if_walkrt) == -1) + return -1; +#endif +#ifdef INET6 + if ((af == AF_UNSPEC || af == AF_INET6) && + if_parsert(ctx, routes, MIB2_IP6, MIB2_IP6_ROUTE, if_walkrt6) == -1) + return -1; +#endif + return 0; +} + + +#ifdef INET +/* XXX We should fix this to write via the BPF interface. */ +ssize_t +bpf_send(const struct bpf *bpf, uint16_t protocol, const void *data, size_t len) +{ + const struct interface *ifp = bpf->bpf_ifp; + dlpi_handle_t dh; + dlpi_info_t di; + int r; + + if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS) + return -1; + if ((r = dlpi_info(dh, &di, 0)) == DLPI_SUCCESS && + (r = dlpi_bind(dh, protocol, NULL)) == DLPI_SUCCESS) + r = dlpi_send(dh, di.di_bcastaddr, ifp->hwlen, data, len, NULL); + dlpi_close(dh); + return r == DLPI_SUCCESS ? (ssize_t)len : -1; +} + +int +if_address(unsigned char cmd, const struct ipv4_addr *ia) +{ + union { + struct sockaddr sa; + struct sockaddr_storage ss; + } addr, mask, brd; + int fd = ia->iface->ctx->pf_inet_fd; + + /* Either remove the alias or ensure it exists. */ + if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1 && + errno != EEXIST) + return -1; + + if (cmd == RTM_DELADDR) + return 0; + + if (cmd != RTM_NEWADDR) { + errno = EINVAL; + return -1; + } + + /* We need to update the index now */ + ia->iface->index = if_nametoindex(ia->alias); + + sa_in_init(&addr.sa, &ia->addr); + sa_in_init(&mask.sa, &ia->mask); + sa_in_init(&brd.sa, &ia->brd); + return if_addaddr(fd, ia->alias, &addr.ss, &mask.ss, &brd.ss, 0); +} + +int +if_addrflags(const struct interface *ifp, __unused const struct in_addr * ia, + const char *alias) +{ + + return if_addrflags0(ifp->ctx->pf_inet_fd, AF_INET, alias); +} + +#endif + +#ifdef INET6 +int +if_address6(unsigned char cmd, const struct ipv6_addr *ia) +{ + union { + struct sockaddr sa; + struct sockaddr_in6 sin6; + struct sockaddr_storage ss; + } addr, mask; + int fd, r; + + /* Either remove the alias or ensure it exists. */ + if (if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias) == -1 && + errno != EEXIST) + return -1; + + if (cmd == RTM_DELADDR) + return 0; + + if (cmd != RTM_NEWADDR) { + errno = EINVAL; + return -1; + } + + fd = if_getaf_fd(ia->iface->ctx, AF_INET6); + + if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) { + if (if_setflags(fd, ia->alias, IFF_NOLOCAL) ==-1) + return -1; + sa_in6_init(&mask.sa, &ia->prefix); + r = if_addaddr(fd, ia->alias, + NULL, &mask.ss, NULL, ia->prefix_len); + } else { + sa_in6_init(&addr.sa, &ia->addr); + mask.sin6.sin6_family = AF_INET6; + ipv6_mask(&mask.sin6.sin6_addr, ia->prefix_len); + r = if_addaddr(fd, ia->alias, + &addr.ss, &mask.ss, NULL, ia->prefix_len); + } + if (r == -1 && errno == EEXIST) + return 0; + return r; +} + +int +if_addrflags6(const struct interface *ifp, __unused const struct in6_addr *ia, + const char *alias) +{ + int fd; + + fd = if_getaf_fd(ifp->ctx, AF_INET6); + return if_addrflags0(fd, AF_INET6, alias); +} + +int +if_getlifetime6(struct ipv6_addr *addr) +{ + + UNUSED(addr); + errno = ENOTSUP; + return -1; +} + +int +if_applyra(const struct ra *rap) +{ + struct lifreq lifr = { + .lifr_ifinfo.lir_maxhops = rap->hoplimit, + .lifr_ifinfo.lir_reachtime = rap->reachable, + .lifr_ifinfo.lir_reachretrans = rap->retrans, + }; + + strlcpy(lifr.lifr_name, rap->iface->name, sizeof(lifr.lifr_name)); + return ioctl(rap->iface->ctx->pf_inet_fd, SIOCSLIFLNKINFO, &lifr); +} + +void +if_setup_inet6(__unused const struct interface *ifp) +{ + +} + +int +ip6_forwarding(__unused const char *ifname) +{ + + return 1; +} +#endif Index: src/if.h =================================================================== --- /dev/null +++ src/if.h @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include +#include /* for RTM_ADD et all */ +#include +#ifdef BSD +#include /* for IN_IFF_TENTATIVE et all */ +#endif + +#include + +/* If the interface does not support carrier status (ie PPP), + * dhcpcd can poll it for the relevant flags periodically */ +#define IF_POLL_UP 100 /* milliseconds */ + +/* + * Systems which handle 1 address per alias. + * Currenly this is just Solaris. + * While Linux can do aliased addresses, it is only useful for their + * legacy ifconfig(8) tool which cannot display >1 IPv4 address + * (it can display many IPv6 addresses which makes the limitation odd). + * Linux has ip(8) which is a more feature rich tool, without the above + * restriction. + */ +#ifndef ALIAS_ADDR +# ifdef __sun +# define ALIAS_ADDR +# endif +#endif + +#include "config.h" + +/* POSIX defines ioctl request as an int, which Solaris and musl use. + * Everyone else use an unsigned long, which happens to be the bigger one + * so we use that in our on wire API. */ +#ifdef IOCTL_REQUEST_TYPE +typedef IOCTL_REQUEST_TYPE ioctl_request_t; +#else +typedef unsigned long ioctl_request_t; +#endif + +#include "dhcpcd.h" +#include "ipv4.h" +#include "ipv6.h" +#include "route.h" + +#define EUI64_ADDR_LEN 8 +#define INFINIBAND_ADDR_LEN 20 + +/* Linux 2.4 doesn't define this */ +#ifndef ARPHRD_IEEE1394 +# define ARPHRD_IEEE1394 24 +#endif + +/* The BSD's don't define this yet */ +#ifndef ARPHRD_INFINIBAND +# define ARPHRD_INFINIBAND 32 +#endif + +/* Maximum frame length. + * Support jumbo frames and some extra. */ +#define FRAMEHDRLEN_MAX 14 /* only ethernet support */ +#define FRAMELEN_MAX (FRAMEHDRLEN_MAX + 9216) + +#define UDPLEN_MAX 64 * 1024 + +/* Work out if we have a private address or not + * 10/8 + * 172.16/12 + * 192.168/16 + */ +#ifndef IN_PRIVATE +# define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \ + ((addr & 0xfff00000) == 0xac100000) || \ + ((addr & IN_CLASSB_NET) == 0xc0a80000)) +#endif + +#ifndef CLLADDR +#ifdef AF_LINK +# define CLLADDR(sdl) (const void *)((sdl)->sdl_data + (sdl)->sdl_nlen) +#endif +#endif + +#ifdef __sun +/* Solaris stupidly defines this for compat with BSD + * but then ignores it. */ +#undef RTF_CLONING + +/* This interface is busted on DilOS at least. + * It used to work, but lukily Solaris can fall back to + * IP_PKTINFO. */ +#undef IP_RECVIF + +/* Solaris getifaddrs is very un-suitable for dhcpcd. + * See if-sun.c for details why. */ +struct ifaddrs; +int if_getifaddrs(struct ifaddrs **); +#define getifaddrs if_getifaddrs +int if_getsubnet(struct dhcpcd_ctx *, const char *, int, void *, size_t); +#endif + +int if_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t); +#ifdef HAVE_PLEDGE +#define pioctl(ctx, req, data, len) if_ioctl((ctx), (req), (data), (len)) +#else +#define pioctl(ctx, req, data, len) ioctl((ctx)->pf_inet_fd, (req),(data),(len)) +#endif +int if_getflags(struct interface *); +int if_setflag(struct interface *, short, short); +#define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING), 0) +#define if_down(ifp) if_setflag((ifp), 0, IFF_UP); +bool if_is_link_up(const struct interface *); +bool if_valid_hwaddr(const uint8_t *, size_t); +struct if_head *if_discover(struct dhcpcd_ctx *, struct ifaddrs **, + int, char * const *); +void if_markaddrsstale(struct if_head *); +void if_learnaddrs(struct dhcpcd_ctx *, struct if_head *, struct ifaddrs **); +void if_deletestaleaddrs(struct if_head *); +struct interface *if_find(struct if_head *, const char *); +struct interface *if_findindex(struct if_head *, unsigned int); +struct interface *if_loopback(struct dhcpcd_ctx *); +void if_free(struct interface *); +int if_domtu(const struct interface *, short int); +#define if_getmtu(ifp) if_domtu((ifp), 0) +#define if_setmtu(ifp, mtu) if_domtu((ifp), (mtu)) +int if_carrier(struct interface *, const void *); +bool if_roaming(struct interface *); + +#ifdef ALIAS_ADDR +int if_makealias(char *, size_t, const char *, int); +#endif + +int if_mtu_os(const struct interface *); + +/* + * Helper to decode an interface name of bge0:1 to + * devname = bge0, drvname = bge0, ppa = 0, lun = 1. + * If ppa or lun are invalid they are set to -1. + */ +struct if_spec { + char ifname[IF_NAMESIZE]; + char devname[IF_NAMESIZE]; + char drvname[IF_NAMESIZE]; + int ppa; + int vlid; + int lun; +}; +int if_nametospec(const char *, struct if_spec *); + +/* The below functions are provided by if-KERNEL.c */ +int os_init(void); +int if_conf(struct interface *); +int if_init(struct interface *); +int if_getssid(struct interface *); +int if_ignoregroup(int, const char *); +bool if_ignore(struct dhcpcd_ctx *, const char *); +int if_vimaster(struct dhcpcd_ctx *ctx, const char *); +unsigned short if_vlanid(const struct interface *); +char * if_getnetworknamespace(char *, size_t); +int if_opensockets(struct dhcpcd_ctx *); +int if_opensockets_os(struct dhcpcd_ctx *); +void if_closesockets(struct dhcpcd_ctx *); +void if_closesockets_os(struct dhcpcd_ctx *); +int if_handlelink(struct dhcpcd_ctx *); +int if_randomisemac(struct interface *); +int if_setmac(struct interface *ifp, void *, uint8_t); + +/* dhcpcd uses the same routing flags as BSD. + * If the platform doesn't use these flags, + * map them in the platform interace file. */ +#ifndef RTM_ADD +#define RTM_ADD 0x1 /* Add Route */ +#define RTM_DELETE 0x2 /* Delete Route */ +#define RTM_CHANGE 0x3 /* Change Metrics or flags */ +#define RTM_GET 0x4 /* Report Metrics */ +#endif + +/* Define SOCK_CLOEXEC and SOCK_NONBLOCK for systems that lack it. + * xsocket() in if.c will map them to fctnl FD_CLOEXEC and O_NONBLOCK. */ +#ifdef SOCK_CLOEXEC +# define HAVE_SOCK_CLOEXEC +#else +# define SOCK_CLOEXEC 0x10000000 +#endif +#ifdef SOCK_NONBLOCK +# define HAVE_SOCK_NONBLOCK +#else +# define SOCK_NONBLOCK 0x20000000 +#endif +#ifndef SOCK_CXNB +#define SOCK_CXNB SOCK_CLOEXEC | SOCK_NONBLOCK +#endif +int xsocket(int, int, int); +int xsocketpair(int, int, int, int[2]); + +int if_route(unsigned char, const struct rt *rt); +int if_initrt(struct dhcpcd_ctx *, rb_tree_t *, int); + +int if_missfilter(struct interface *, struct sockaddr *); +int if_missfilter_apply(struct dhcpcd_ctx *); + +#ifdef INET +int if_address(unsigned char, const struct ipv4_addr *); +int if_addrflags(const struct interface *, const struct in_addr *, + const char *); + +#endif + +#ifdef INET6 +void if_disable_rtadv(void); +void if_setup_inet6(const struct interface *); +int ip6_forwarding(const char *ifname); + +struct ra; +struct ipv6_addr; + +int if_applyra(const struct ra *); +int if_address6(unsigned char, const struct ipv6_addr *); +int if_addrflags6(const struct interface *, const struct in6_addr *, + const char *); +int if_getlifetime6(struct ipv6_addr *); + +#else +#define if_checkipv6(a, b, c) (-1) +#endif + +int if_machinearch(char *, size_t); +struct interface *if_findifpfromcmsg(struct dhcpcd_ctx *, + struct msghdr *, int *); + +#ifdef __linux__ +int if_linksocket(struct sockaddr_nl *, int, int); +int if_getnetlink(struct dhcpcd_ctx *, struct iovec *, int, int, + int (*)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *); +#endif +#endif Index: src/if.c =================================================================== --- /dev/null +++ src/if.c @@ -0,0 +1,1042 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include +#include +#include + +#include /* Needs to be here for old Linux */ + +#include "config.h" + +#include +#include +#include +#ifdef AF_LINK +# include +# include +# include +# undef AF_PACKET /* Newer Illumos defines this */ +#endif +#ifdef AF_PACKET +# include +#endif +#ifdef SIOCGIFMEDIA +# include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ELOOP_QUEUE ELOOP_IF +#include "common.h" +#include "eloop.h" +#include "dev.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +void +if_free(struct interface *ifp) +{ + + if (ifp == NULL) + return; +#ifdef IPV4LL + ipv4ll_free(ifp); +#endif +#ifdef INET + dhcp_free(ifp); + ipv4_free(ifp); +#endif +#ifdef DHCP6 + dhcp6_free(ifp); +#endif +#ifdef INET6 + ipv6nd_free(ifp); + ipv6_free(ifp); +#endif + rt_freeif(ifp); + free_options(ifp->ctx, ifp->options); + free(ifp); +} + +int +if_opensockets(struct dhcpcd_ctx *ctx) +{ + + if (if_opensockets_os(ctx) == -1) + return -1; + +#ifdef IFLR_ACTIVE + ctx->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (ctx->pf_link_fd == -1) + return -1; +#ifdef HAVE_CAPSICUM + if (ps_rights_limit_ioctl(ctx->pf_link_fd) == -1) + return -1; +#endif +#endif + + /* We use this socket for some operations without INET. */ + ctx->pf_inet_fd = xsocket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (ctx->pf_inet_fd == -1) + return -1; + + return 0; +} + +void +if_closesockets(struct dhcpcd_ctx *ctx) +{ + + if (ctx->pf_inet_fd != -1) + close(ctx->pf_inet_fd); +#ifdef PF_LINK + if (ctx->pf_link_fd != -1) + close(ctx->pf_link_fd); +#endif + + if (ctx->priv) { + if_closesockets_os(ctx); + free(ctx->priv); + } +} + +int +if_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, size_t len) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return (int)ps_root_ioctl(ctx, req, data, len); +#endif + return ioctl(ctx->pf_inet_fd, req, data, len); +} + +int +if_getflags(struct interface *ifp) +{ + struct ifreq ifr = { .ifr_flags = 0 }; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1) + return -1; + ifp->flags = (unsigned int)ifr.ifr_flags; + return 0; +} + +int +if_setflag(struct interface *ifp, short setflag, short unsetflag) +{ + struct ifreq ifr = { .ifr_flags = 0 }; + short oflags; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1) + return -1; + + oflags = ifr.ifr_flags; + ifr.ifr_flags |= setflag; + ifr.ifr_flags &= (short)~unsetflag; + if (ifr.ifr_flags != oflags && + if_ioctl(ifp->ctx, SIOCSIFFLAGS, &ifr, sizeof(ifr)) == -1) + return -1; + + /* + * Do NOT set ifp->flags here. + * We need to listen for flag updates from the kernel as they + * need to sync with carrier. + */ + return 0; +} + +bool +if_is_link_up(const struct interface *ifp) +{ + + return ifp->flags & IFF_UP && + (ifp->carrier != LINK_DOWN || + (ifp->options != NULL && !(ifp->options->options & DHCPCD_LINK))); +} + +int +if_randomisemac(struct interface *ifp) +{ + uint32_t randnum; + size_t hwlen = ifp->hwlen, rlen = 0; + uint8_t buf[HWADDR_LEN], *bp = buf, *rp = (uint8_t *)&randnum; + char sbuf[HWADDR_LEN * 3]; + int retval; + + if (hwlen == 0) { + errno = ENOTSUP; + return -1; + } + if (hwlen > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + + for (; hwlen != 0; hwlen--) { + if (rlen == 0) { + randnum = arc4random(); + rp = (uint8_t *)&randnum; + rlen = sizeof(randnum); + } + *bp++ = *rp++; + rlen--; + } + + /* Unicast address and locally administered. */ + buf[0] &= 0xFC; + buf[0] |= 0x02; + + logdebugx("%s: hardware address randomised to %s", + ifp->name, + hwaddr_ntoa(buf, ifp->hwlen, sbuf, sizeof(sbuf))); + retval = if_setmac(ifp, buf, ifp->hwlen); + if (retval == 0) + memcpy(ifp->hwaddr, buf, ifp->hwlen); + return retval; +} + +static int +if_hasconf(struct dhcpcd_ctx *ctx, const char *ifname) +{ + int i; + + for (i = 0; i < ctx->ifcc; i++) { + if (strcmp(ctx->ifcv[i], ifname) == 0) + return 1; + } + return 0; +} + +void +if_markaddrsstale(struct if_head *ifs) +{ + struct interface *ifp; + + TAILQ_FOREACH(ifp, ifs, next) { +#ifdef INET + ipv4_markaddrsstale(ifp); +#endif +#ifdef INET6 + ipv6_markaddrsstale(ifp, 0); +#endif + } +} + +void +if_learnaddrs(struct dhcpcd_ctx *ctx, struct if_head *ifs, + struct ifaddrs **ifaddrs) +{ + struct ifaddrs *ifa; + struct interface *ifp; +#ifdef INET + const struct sockaddr_in *addr, *net, *brd; +#endif +#ifdef INET6 + struct sockaddr_in6 *sin6, *net6; +#endif + int addrflags; + + for (ifa = *ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if ((ifp = if_find(ifs, ifa->ifa_name)) == NULL) + continue; +#ifdef HAVE_IFADDRS_ADDRFLAGS + addrflags = (int)ifa->ifa_addrflags; +#endif + switch(ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + addr = (void *)ifa->ifa_addr; + net = (void *)ifa->ifa_netmask; + if (ifa->ifa_flags & IFF_POINTOPOINT) + brd = (void *)ifa->ifa_dstaddr; + else + brd = (void *)ifa->ifa_broadaddr; +#ifndef HAVE_IFADDRS_ADDRFLAGS + addrflags = if_addrflags(ifp, &addr->sin_addr, + ifa->ifa_name); + if (addrflags == -1) { + if (errno != EEXIST && errno != EADDRNOTAVAIL) { + char dbuf[INET_ADDRSTRLEN]; + const char *dbp; + + dbp = inet_ntop(AF_INET, &addr->sin_addr, + dbuf, sizeof(dbuf)); + logerr("%s: if_addrflags: %s%%%s", + __func__, dbp, ifp->name); + } + continue; + } +#endif + ipv4_handleifa(ctx, RTM_NEWADDR, ifs, ifa->ifa_name, + &addr->sin_addr, &net->sin_addr, + brd ? &brd->sin_addr : NULL, addrflags, 0); + break; +#endif +#ifdef INET6 + case AF_INET6: + sin6 = (void *)ifa->ifa_addr; + net6 = (void *)ifa->ifa_netmask; + +#ifdef __KAME__ + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + /* Remove the scope from the address */ + sin6->sin6_addr.s6_addr[2] = + sin6->sin6_addr.s6_addr[3] = '\0'; +#endif +#ifndef HAVE_IFADDRS_ADDRFLAGS + addrflags = if_addrflags6(ifp, &sin6->sin6_addr, + ifa->ifa_name); + if (addrflags == -1) { + if (errno != EEXIST && errno != EADDRNOTAVAIL) { + char dbuf[INET6_ADDRSTRLEN]; + const char *dbp; + + dbp = inet_ntop(AF_INET6, &sin6->sin6_addr, + dbuf, sizeof(dbuf)); + logerr("%s: if_addrflags6: %s%%%s", + __func__, dbp, ifp->name); + } + continue; + } +#endif + ipv6_handleifa(ctx, RTM_NEWADDR, ifs, + ifa->ifa_name, &sin6->sin6_addr, + ipv6_prefixlen(&net6->sin6_addr), addrflags, 0); + break; +#endif + } + } + +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(ctx)) + free(*ifaddrs); + else +#endif + freeifaddrs(*ifaddrs); + *ifaddrs = NULL; +} + +void +if_deletestaleaddrs(struct if_head *ifs) +{ + struct interface *ifp; + + TAILQ_FOREACH(ifp, ifs, next) { +#ifdef INET + ipv4_deletestaleaddrs(ifp); +#endif +#ifdef INET6 + ipv6_deletestaleaddrs(ifp); +#endif + } +} + +bool +if_valid_hwaddr(const uint8_t *hwaddr, size_t hwlen) +{ + size_t i; + bool all_zeros, all_ones; + + all_zeros = all_ones = true; + for (i = 0; i < hwlen; i++) { + if (hwaddr[i] != 0x00) + all_zeros = false; + if (hwaddr[i] != 0xff) + all_ones = false; + if (!all_zeros && !all_ones) + return true; + } + return false; +} + +#if defined(AF_PACKET) && !defined(AF_LINK) +static unsigned int +if_check_arphrd(struct interface *ifp, unsigned int active, bool if_noconf) +{ + + switch(ifp->hwtype) { + case ARPHRD_ETHER: /* FALLTHROUGH */ + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: /* FALLTHROUGH */ + case ARPHRD_NONE: /* FALLTHROUGH */ + break; + case ARPHRD_LOOPBACK: + case ARPHRD_PPP: + if (if_noconf && active) { + logdebugx("%s: ignoring due to interface type and" + " no config", + ifp->name); + active = IF_INACTIVE; + } + break; + default: + if (active) { + int i; + + if (if_noconf) + active = IF_INACTIVE; + i = active ? LOG_WARNING : LOG_DEBUG; + logmessage(i, "%s: unsupported" + " interface type 0x%.2x", + ifp->name, ifp->hwtype); + } + break; + } + + return active; +} +#endif + +struct if_head * +if_discover(struct dhcpcd_ctx *ctx, struct ifaddrs **ifaddrs, + int argc, char * const *argv) +{ + struct ifaddrs *ifa; + int i; + unsigned int active; + struct if_head *ifs; + struct interface *ifp; + struct if_spec spec; + bool if_noconf; +#ifdef AF_LINK + const struct sockaddr_dl *sdl; +#ifdef IFLR_ACTIVE + struct if_laddrreq iflr = { .flags = IFLR_PREFIX }; +#endif +#elif defined(AF_PACKET) + const struct sockaddr_ll *sll; +#endif +#if defined(SIOCGIFPRIORITY) + struct ifreq ifr; +#endif + + if ((ifs = malloc(sizeof(*ifs))) == NULL) { + logerr(__func__); + return NULL; + } + TAILQ_INIT(ifs); + +#ifdef PRIVSEP_GETIFADDRS + if (ctx->options & DHCPCD_PRIVSEP) { + if (ps_root_getifaddrs(ctx, ifaddrs) == -1) { + logerr("ps_root_getifaddrs"); + free(ifs); + return NULL; + } + } else +#endif + if (getifaddrs(ifaddrs) == -1) { + logerr("getifaddrs"); + free(ifs); + return NULL; + } + + for (ifa = *ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr != NULL) { +#ifdef AF_LINK + if (ifa->ifa_addr->sa_family != AF_LINK) + continue; +#elif defined(AF_PACKET) + if (ifa->ifa_addr->sa_family != AF_PACKET) + continue; +#endif + } + if (if_nametospec(ifa->ifa_name, &spec) != 0) + continue; + + /* It's possible for an interface to have >1 AF_LINK. + * For our purposes, we use the first one. */ + TAILQ_FOREACH(ifp, ifs, next) { + if (strcmp(ifp->name, spec.devname) == 0) + break; + } + if (ifp) + continue; + + if (argc > 0) { + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], spec.devname) == 0) + break; + } + active = (i == argc) ? IF_INACTIVE : IF_ACTIVE_USER; + } else { + /* -1 means we're discovering against a specific + * interface, but we still need the below rules + * to apply. */ + if (argc == -1 && strcmp(argv[0], spec.devname) != 0) + continue; + active = ctx->options & DHCPCD_INACTIVE ? + IF_INACTIVE: IF_ACTIVE_USER; + } + + for (i = 0; i < ctx->ifdc; i++) + if (fnmatch(ctx->ifdv[i], spec.devname, 0) == 0) + break; + if (i < ctx->ifdc) + active = IF_INACTIVE; + for (i = 0; i < ctx->ifc; i++) + if (fnmatch(ctx->ifv[i], spec.devname, 0) == 0) + break; + if (ctx->ifc && i == ctx->ifc) + active = IF_INACTIVE; + for (i = 0; i < ctx->ifac; i++) + if (fnmatch(ctx->ifav[i], spec.devname, 0) == 0) + break; + if (ctx->ifac && i == ctx->ifac) + active = IF_INACTIVE; + +#ifdef PLUGIN_DEV + /* Ensure that the interface name has settled */ + if (!dev_initialised(ctx, spec.devname)) { + logdebugx("%s: waiting for interface to initialise", + spec.devname); + continue; + } +#endif + + if (if_vimaster(ctx, spec.devname) == 1) { + int loglevel = argc != 0 ? LOG_ERR : LOG_DEBUG; + logmessage(loglevel, + "%s: is a Virtual Interface Master, skipping", + spec.devname); + continue; + } + + if_noconf = ((argc == 0 || argc == -1) && ctx->ifac == 0 && + !if_hasconf(ctx, spec.devname)); + + /* Don't allow some reserved interface names unless explicit. */ + if (if_noconf && if_ignore(ctx, spec.devname)) { + logdebugx("%s: ignoring due to interface type and" + " no config", spec.devname); + active = IF_INACTIVE; + } + + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) { + logerr(__func__); + break; + } + ifp->ctx = ctx; + strlcpy(ifp->name, spec.devname, sizeof(ifp->name)); + ifp->flags = ifa->ifa_flags; + + if (ifa->ifa_addr != NULL) { +#ifdef AF_LINK + sdl = (const void *)ifa->ifa_addr; + +#ifdef IFLR_ACTIVE + /* We need to check for active address */ + strlcpy(iflr.iflr_name, ifp->name, + sizeof(iflr.iflr_name)); + memcpy(&iflr.addr, ifa->ifa_addr, + MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr))); + iflr.flags = IFLR_PREFIX; + iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY; + if (ioctl(ctx->pf_link_fd, SIOCGLIFADDR, &iflr) == -1 || + !(iflr.flags & IFLR_ACTIVE)) + { + if_free(ifp); + continue; + } +#endif + + ifp->index = sdl->sdl_index; + switch(sdl->sdl_type) { +#ifdef IFT_BRIDGE + case IFT_BRIDGE: /* FALLTHROUGH */ +#endif +#ifdef IFT_PROPVIRTUAL + case IFT_PROPVIRTUAL: /* FALLTHROUGH */ +#endif +#ifdef IFT_TUNNEL + case IFT_TUNNEL: /* FALLTHROUGH */ +#endif + case IFT_LOOP: /* FALLTHROUGH */ + case IFT_PPP: + /* Don't allow unless explicit */ + if (if_noconf && active) { + logdebugx("%s: ignoring due to" + " interface type and" + " no config", + ifp->name); + active = IF_INACTIVE; + } + __fallthrough; /* appease gcc */ + /* FALLTHROUGH */ +#ifdef IFT_L2VLAN + case IFT_L2VLAN: /* FALLTHROUGH */ +#endif +#ifdef IFT_L3IPVLAN + case IFT_L3IPVLAN: /* FALLTHROUGH */ +#endif + case IFT_ETHER: + ifp->hwtype = ARPHRD_ETHER; + break; +#ifdef IFT_IEEE1394 + case IFT_IEEE1394: + ifp->hwtype = ARPHRD_IEEE1394; + break; +#endif +#ifdef IFT_INFINIBAND + case IFT_INFINIBAND: + ifp->hwtype = ARPHRD_INFINIBAND; + break; +#endif + default: + /* Don't allow unless explicit */ + if (active) { + if (if_noconf) + active = IF_INACTIVE; + i = active ? LOG_WARNING : LOG_DEBUG; + logmessage(i, "%s: unsupported" + " interface type 0x%.2x", + ifp->name, sdl->sdl_type); + } + /* Pretend it's ethernet */ + ifp->hwtype = ARPHRD_ETHER; + break; + } + ifp->hwlen = sdl->sdl_alen; + memcpy(ifp->hwaddr, CLLADDR(sdl), ifp->hwlen); +#elif defined(AF_PACKET) + sll = (const void *)ifa->ifa_addr; + ifp->index = (unsigned int)sll->sll_ifindex; + ifp->hwtype = sll->sll_hatype; + ifp->hwlen = sll->sll_halen; + if (ifp->hwlen != 0) + memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen); + active = if_check_arphrd(ifp, active, if_noconf); +#endif + } +#ifdef __linux__ + else { + struct ifreq ifr = { .ifr_flags = 0 }; + + /* This is a huge bug in getifaddrs(3) as there + * is no reason why this can't be returned in + * ifa_addr. */ + strlcpy(ifr.ifr_name, ifa->ifa_name, + sizeof(ifr.ifr_name)); + if (ioctl(ctx->pf_inet_fd, SIOCGIFHWADDR, &ifr) == -1) + logerr("%s: SIOCGIFHWADDR", ifa->ifa_name); + ifp->hwtype = ifr.ifr_hwaddr.sa_family; + if (ioctl(ctx->pf_inet_fd, SIOCGIFINDEX, &ifr) == -1) + logerr("%s: SIOCGIFINDEX", ifa->ifa_name); + ifp->index = (unsigned int)ifr.ifr_ifindex; + if_check_arphrd(ifp, active, if_noconf); + } +#endif + + if (!(ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) { + /* Handle any platform init for the interface */ + if (active != IF_INACTIVE && if_init(ifp) == -1) { + logerr("%s: if_init", ifp->name); + if_free(ifp); + continue; + } + } + + ifp->vlanid = if_vlanid(ifp); + +#ifdef SIOCGIFPRIORITY + /* Respect the interface priority */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (pioctl(ctx, SIOCGIFPRIORITY, &ifr, sizeof(ifr)) == 0) + ifp->metric = (unsigned int)ifr.ifr_metric; + if_getssid(ifp); +#else + /* Leave a low portion for user config */ + ifp->metric = RTMETRIC_BASE + ifp->index; + if (if_getssid(ifp) != -1) { + ifp->wireless = true; + ifp->metric += RTMETRIC_WIRELESS; + } +#endif + + ifp->active = active; + ifp->carrier = if_carrier(ifp, ifa->ifa_data); + TAILQ_INSERT_TAIL(ifs, ifp, next); + } + + return ifs; +} + +/* + * eth0.100:2 OR eth0i100:2 (seems to be NetBSD xvif(4) only) + * + * drvname == eth + * devname == eth0.100 OR eth0i100 + * ppa = 0 + * lun = 2 + */ +int +if_nametospec(const char *ifname, struct if_spec *spec) +{ + char *ep, *pp; + int e; + + if (ifname == NULL || *ifname == '\0' || + strlcpy(spec->ifname, ifname, sizeof(spec->ifname)) >= + sizeof(spec->ifname) || + strlcpy(spec->drvname, ifname, sizeof(spec->drvname)) >= + sizeof(spec->drvname)) + { + errno = EINVAL; + return -1; + } + + /* :N is an alias */ + ep = strchr(spec->drvname, ':'); + if (ep) { + spec->lun = (int)strtoi(ep + 1, NULL, 10, 0, INT_MAX, &e); + if (e != 0) { + errno = e; + return -1; + } + *ep = '\0'; +#ifdef __sun + ep--; +#endif + } else { + spec->lun = -1; +#ifdef __sun + ep = spec->drvname + strlen(spec->drvname) - 1; +#endif + } + + strlcpy(spec->devname, spec->drvname, sizeof(spec->devname)); +#ifdef __sun + /* Solaris has numbers in the driver name, such as e1000g */ + while (ep > spec->drvname && isdigit((int)*ep)) + ep--; + if (*ep++ == ':') { + errno = EINVAL; + return -1; + } +#else + /* BSD and Linux no not have numbers in the driver name */ + for (ep = spec->drvname; *ep != '\0' && !isdigit((int)*ep); ep++) { + if (*ep == ':') { + errno = EINVAL; + return -1; + } + } +#endif + spec->ppa = (int)strtoi(ep, &pp, 10, 0, INT_MAX, &e); + *ep = '\0'; + +#ifndef __sun + /* + * . is used for VLAN style names + * i is used on NetBSD for xvif interfaces + */ + if (pp != NULL && (*pp == '.' || *pp == 'i')) { + spec->vlid = (int)strtoi(pp + 1, NULL, 10, 0, INT_MAX, &e); + if (e) + spec->vlid = -1; + } else +#endif + spec->vlid = -1; + + return 0; +} + +static struct interface * +if_findindexname(struct if_head *ifaces, unsigned int idx, const char *name) +{ + + if (ifaces != NULL) { + struct if_spec spec; + struct interface *ifp; + + if (name && if_nametospec(name, &spec) == -1) + return NULL; + + TAILQ_FOREACH(ifp, ifaces, next) { + if ((name && strcmp(ifp->name, spec.devname) == 0) || + (!name && ifp->index == idx)) + return ifp; + } + } + + errno = ENXIO; + return NULL; +} + +struct interface * +if_find(struct if_head *ifaces, const char *name) +{ + + return if_findindexname(ifaces, 0, name); +} + +struct interface * +if_findindex(struct if_head *ifaces, unsigned int idx) +{ + + return if_findindexname(ifaces, idx, NULL); +} + +struct interface * +if_loopback(struct dhcpcd_ctx *ctx) +{ + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->flags & IFF_LOOPBACK) + return ifp; + } + return NULL; +} + +int +if_domtu(const struct interface *ifp, short int mtu) +{ + int r; + struct ifreq ifr; + +#ifdef __sun + if (mtu == 0) + return if_mtu_os(ifp); +#endif + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + if (mtu != 0) + r = if_ioctl(ifp->ctx, SIOCSIFMTU, &ifr, sizeof(ifr)); + else + r = pioctl(ifp->ctx, SIOCGIFMTU, &ifr, sizeof(ifr)); + + if (r == -1) + return -1; + return ifr.ifr_mtu; +} + +#ifdef ALIAS_ADDR +int +if_makealias(char *alias, size_t alias_len, const char *ifname, int lun) +{ + + if (lun == 0) + return strlcpy(alias, ifname, alias_len); + return snprintf(alias, alias_len, "%s:%u", ifname, lun); +} +#endif + +struct interface * +if_findifpfromcmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, int *hoplimit) +{ + struct cmsghdr *cm; + unsigned int ifindex = 0; + struct interface *ifp; +#ifdef INET +#ifdef IP_RECVIF + struct sockaddr_dl sdl; +#else + struct in_pktinfo ipi; +#endif +#endif +#ifdef INET6 + struct in6_pktinfo ipi6; +#else + UNUSED(hoplimit); +#endif + + for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(msg); + cm; + cm = (struct cmsghdr *)CMSG_NXTHDR(msg, cm)) + { +#ifdef INET + if (cm->cmsg_level == IPPROTO_IP) { + switch(cm->cmsg_type) { +#ifdef IP_RECVIF + case IP_RECVIF: + if (cm->cmsg_len < + offsetof(struct sockaddr_dl, sdl_index) + + sizeof(sdl.sdl_index)) + continue; + memcpy(&sdl, CMSG_DATA(cm), + MIN(sizeof(sdl), cm->cmsg_len)); + ifindex = sdl.sdl_index; + break; +#else + case IP_PKTINFO: + if (cm->cmsg_len != CMSG_LEN(sizeof(ipi))) + continue; + memcpy(&ipi, CMSG_DATA(cm), sizeof(ipi)); + ifindex = (unsigned int)ipi.ipi_ifindex; + break; +#endif + } + } +#endif +#ifdef INET6 + if (cm->cmsg_level == IPPROTO_IPV6) { + switch(cm->cmsg_type) { + case IPV6_PKTINFO: + if (cm->cmsg_len != CMSG_LEN(sizeof(ipi6))) + continue; + memcpy(&ipi6, CMSG_DATA(cm), sizeof(ipi6)); + ifindex = (unsigned int)ipi6.ipi6_ifindex; + break; + case IPV6_HOPLIMIT: + if (cm->cmsg_len != CMSG_LEN(sizeof(int))) + continue; + if (hoplimit == NULL) + break; + memcpy(hoplimit, CMSG_DATA(cm), sizeof(int)); + break; + } + } +#endif + } + + /* Find the receiving interface */ + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->index == ifindex) + break; + } + if (ifp == NULL) + errno = ESRCH; + return ifp; +} + +int +xsocket(int domain, int type, int protocol) +{ + int s; +#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) + int xflags, xtype = type; +#endif + +#ifndef HAVE_SOCK_CLOEXEC + if (xtype & SOCK_CLOEXEC) + type &= ~SOCK_CLOEXEC; +#endif +#ifndef HAVE_SOCK_NONBLOCK + if (xtype & SOCK_NONBLOCK) + type &= ~SOCK_NONBLOCK; +#endif + + if ((s = socket(domain, type, protocol)) == -1) + return -1; + +#ifndef HAVE_SOCK_CLOEXEC + if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(s, F_GETFD)) == -1 || + fcntl(s, F_SETFD, xflags | FD_CLOEXEC) == -1)) + goto out; +#endif +#ifndef HAVE_SOCK_NONBLOCK + if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(s, F_GETFL)) == -1 || + fcntl(s, F_SETFL, xflags | O_NONBLOCK) == -1)) + goto out; +#endif + + return s; + +#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) +out: + close(s); + return -1; +#endif +} + +int +xsocketpair(int domain, int type, int protocol, int fd[2]) +{ + int s; +#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) + int xflags, xtype = type; +#endif + +#ifndef HAVE_SOCK_CLOEXEC + if (xtype & SOCK_CLOEXEC) + type &= ~SOCK_CLOEXEC; +#endif +#ifndef HAVE_SOCK_NONBLOCK + if (xtype & SOCK_NONBLOCK) + type &= ~SOCK_NONBLOCK; +#endif + + if ((s = socketpair(domain, type, protocol, fd)) == -1) + return -1; + +#ifndef HAVE_SOCK_CLOEXEC + if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(fd[0], F_GETFD)) == -1 || + fcntl(fd[0], F_SETFD, xflags | FD_CLOEXEC) == -1)) + goto out; + if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(fd[1], F_GETFD)) == -1 || + fcntl(fd[1], F_SETFD, xflags | FD_CLOEXEC) == -1)) + goto out; +#endif +#ifndef HAVE_SOCK_NONBLOCK + if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(fd[0], F_GETFL)) == -1 || + fcntl(fd[0], F_SETFL, xflags | O_NONBLOCK) == -1)) + goto out; + if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(fd[1], F_GETFL)) == -1 || + fcntl(fd[1], F_SETFL, xflags | O_NONBLOCK) == -1)) + goto out; +#endif + + return s; + +#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) +out: + close(fd[0]); + close(fd[1]); + return -1; +#endif +} Index: src/ipv4.h =================================================================== --- /dev/null +++ src/ipv4.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef IPV4_H +#define IPV4_H + +#include "dhcpcd.h" + +/* Prefer our macro */ +#ifdef HTONL +#undef HTONL +#endif + +#ifndef BYTE_ORDER +#define BIG_ENDIAN 1234 +#define LITTLE_ENDIAN 4321 +#if defined(_BIG_ENDIAN) +#define BYTE_ORDER BIG_ENDIAN +#elif defined(_LITTLE_ENDIAN) +#define BYTE_ORDER LITTLE_ENDIAN +#else +#error Endian unknown +#endif +#endif + +#if BYTE_ORDER == BIG_ENDIAN +#define HTONL(A) (A) +#elif BYTE_ORDER == LITTLE_ENDIAN +#define HTONL(A) \ + ((((uint32_t)(A) & 0xff000000) >> 24) | \ + (((uint32_t)(A) & 0x00ff0000) >> 8) | \ + (((uint32_t)(A) & 0x0000ff00) << 8) | \ + (((uint32_t)(A) & 0x000000ff) << 24)) +#endif /* BYTE_ORDER */ + +#ifdef __sun + /* Solaris lacks these defines. + * While it supports DaD, to seems to only expose IFF_DUPLICATE + * so we have no way of knowing if it's tentative or not. + * I don't even know if Solaris has any special treatment for tentative. */ +# define IN_IFF_TENTATIVE 0x01 +# define IN_IFF_DUPLICATED 0x02 +# define IN_IFF_DETACHED 0x00 +#endif + +#ifdef IN_IFF_TENTATIVE +#define IN_IFF_NOTUSEABLE \ + (IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED) +#endif + +#define IN_ARE_ADDR_EQUAL(a, b) ((a)->s_addr == (b)->s_addr) +#define IN_IS_ADDR_UNSPECIFIED(a) ((a)->s_addr == INADDR_ANY) + +#ifdef __linux__ +#define IP_LIFETIME +#endif + +struct ipv4_addr { + TAILQ_ENTRY(ipv4_addr) next; + struct in_addr addr; + struct in_addr mask; + struct in_addr brd; + struct interface *iface; + int addr_flags; + unsigned int flags; +#ifdef IP_LIFETIME + uint32_t vltime; + uint32_t pltime; +#endif + char saddr[INET_ADDRSTRLEN + 3]; +#ifdef ALIAS_ADDR + char alias[IF_NAMESIZE]; +#endif +}; +TAILQ_HEAD(ipv4_addrhead, ipv4_addr); + +#define IPV4_AF_STALE (1U << 0) +#define IPV4_AF_NEW (1U << 1) + +#define IPV4_ADDR_EQ(a1, a2) ((a1) && (a1)->addr.s_addr == (a2)->addr.s_addr) +#define IPV4_MASK1_EQ(a1, a2) ((a1) && (a1)->mask.s_addr == (a2)->mask.s_addr) +#define IPV4_MASK_EQ(a1, a2) (IPV4_ADDR_EQ(a1, a2) && IPV4_MASK1_EQ(a1, a2)) +#define IPV4_BRD1_EQ(a1, a2) ((a1) && (a1)->brd.s_addr == (a2)->brd.s_addr) +#define IPV4_BRD_EQ(a1, a2) (IPV4_MASK_EQ(a1, a2) && IPV4_BRD1_EQ(a1, a2)) + +struct ipv4_state { + struct ipv4_addrhead addrs; +}; + +#define IPV4_STATE(ifp) \ + ((struct ipv4_state *)(ifp)->if_data[IF_DATA_IPV4]) +#define IPV4_CSTATE(ifp) \ + ((const struct ipv4_state *)(ifp)->if_data[IF_DATA_IPV4]) + +#ifdef INET +struct ipv4_state *ipv4_getstate(struct interface *); +int ipv4_ifcmp(const struct interface *, const struct interface *); +uint8_t inet_ntocidr(struct in_addr); +int inet_cidrtoaddr(int, struct in_addr *); +uint32_t ipv4_getnetmask(uint32_t); +int ipv4_hasaddr(const struct interface *); + +bool inet_getroutes(struct dhcpcd_ctx *, rb_tree_t *); + +#define STATE_ADDED 0x01 +#define STATE_FAKE 0x02 +#define STATE_EXPIRED 0x04 + +int ipv4_deladdr(struct ipv4_addr *, int); +struct ipv4_addr *ipv4_addaddr(struct interface *, + const struct in_addr *, const struct in_addr *, const struct in_addr *, + uint32_t, uint32_t); +struct ipv4_addr *ipv4_applyaddr(void *); + +struct ipv4_addr *ipv4_iffindaddr(struct interface *, + const struct in_addr *, const struct in_addr *); +struct ipv4_addr *ipv4_iffindlladdr(struct interface *); +struct ipv4_addr *ipv4_findaddr(struct dhcpcd_ctx *, const struct in_addr *); +struct ipv4_addr *ipv4_findmaskaddr(struct dhcpcd_ctx *, + const struct in_addr *); +struct ipv4_addr *ipv4_findmaskbrd(struct dhcpcd_ctx *, + const struct in_addr *); +void ipv4_markaddrsstale(struct interface *); +void ipv4_deletestaleaddrs(struct interface *); +void ipv4_handleifa(struct dhcpcd_ctx *, int, struct if_head *, const char *, + const struct in_addr *, const struct in_addr *, const struct in_addr *, + int, pid_t); + +void ipv4_free(struct interface *); +#endif /* INET */ + +#endif /* IPV4_H */ Index: src/ipv4.c =================================================================== --- /dev/null +++ src/ipv4.c @@ -0,0 +1,1002 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "arp.h" +#include "common.h" +#include "dhcpcd.h" +#include "dhcp.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "logerr.h" +#include "route.h" +#include "script.h" +#include "sa.h" + +#define IPV4_LOOPBACK_ROUTE +#if defined(__linux__) || defined(__sun) || (defined(BSD) && defined(RTF_LOCAL)) +/* Linux has had loopback routes in the local table since 2.2 + * Solaris does not seem to support loopback routes. */ +#undef IPV4_LOOPBACK_ROUTE +#endif + +uint8_t +inet_ntocidr(struct in_addr address) +{ + uint8_t cidr = 0; + uint32_t mask = htonl(address.s_addr); + + while (mask) { + cidr++; + mask <<= 1; + } + return cidr; +} + +int +inet_cidrtoaddr(int cidr, struct in_addr *addr) +{ + int ocets; + + if (cidr < 1 || cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (cidr + 7) / NBBY; + + addr->s_addr = 0; + if (ocets > 0) { + memset(&addr->s_addr, 255, (size_t)ocets - 1); + memset((unsigned char *)&addr->s_addr + (ocets - 1), + (256 - (1 << (32 - cidr) % NBBY)), 1); + } + + return 0; +} + +uint32_t +ipv4_getnetmask(uint32_t addr) +{ + uint32_t dst; + + if (addr == 0) + return 0; + + dst = htonl(addr); + if (IN_CLASSA(dst)) + return ntohl(IN_CLASSA_NET); + if (IN_CLASSB(dst)) + return ntohl(IN_CLASSB_NET); + if (IN_CLASSC(dst)) + return ntohl(IN_CLASSC_NET); + + return 0; +} + +struct ipv4_addr * +ipv4_iffindaddr(struct interface *ifp, + const struct in_addr *addr, const struct in_addr *mask) +{ + struct ipv4_state *state; + struct ipv4_addr *ap; + + state = IPV4_STATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if ((addr == NULL || ap->addr.s_addr == addr->s_addr) && + (mask == NULL || ap->mask.s_addr == mask->s_addr)) + return ap; + } + } + return NULL; +} + +struct ipv4_addr * +ipv4_iffindlladdr(struct interface *ifp) +{ + struct ipv4_state *state; + struct ipv4_addr *ap; + + state = IPV4_STATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (IN_LINKLOCAL(ntohl(ap->addr.s_addr))) + return ap; + } + } + return NULL; +} + +static struct ipv4_addr * +ipv4_iffindmaskaddr(struct interface *ifp, const struct in_addr *addr) +{ + struct ipv4_state *state; + struct ipv4_addr *ap; + + state = IPV4_STATE(ifp); + if (state) { + TAILQ_FOREACH (ap, &state->addrs, next) { + if ((ap->addr.s_addr & ap->mask.s_addr) == + (addr->s_addr & ap->mask.s_addr)) + return ap; + } + } + return NULL; +} + +static struct ipv4_addr * +ipv4_iffindmaskbrd(struct interface *ifp, const struct in_addr *addr) +{ + struct ipv4_state *state; + struct ipv4_addr *ap; + + state = IPV4_STATE(ifp); + if (state) { + TAILQ_FOREACH (ap, &state->addrs, next) { + if ((ap->brd.s_addr & ap->mask.s_addr) == + (addr->s_addr & ap->mask.s_addr)) + return ap; + } + } + return NULL; +} + +struct ipv4_addr * +ipv4_findaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr) +{ + struct interface *ifp; + struct ipv4_addr *ap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ap = ipv4_iffindaddr(ifp, addr, NULL); + if (ap) + return ap; + } + return NULL; +} + +struct ipv4_addr * +ipv4_findmaskaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr) +{ + struct interface *ifp; + struct ipv4_addr *ap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ap = ipv4_iffindmaskaddr(ifp, addr); + if (ap) + return ap; + } + return NULL; +} + +struct ipv4_addr * +ipv4_findmaskbrd(struct dhcpcd_ctx *ctx, const struct in_addr *addr) +{ + struct interface *ifp; + struct ipv4_addr *ap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ap = ipv4_iffindmaskbrd(ifp, addr); + if (ap) + return ap; + } + return NULL; +} + +int +ipv4_hasaddr(const struct interface *ifp) +{ + const struct dhcp_state *dstate; + +#ifdef IPV4LL + if (IPV4LL_STATE_RUNNING(ifp)) + return 1; +#endif + + dstate = D_CSTATE(ifp); + return (dstate && + dstate->added == STATE_ADDED && + dstate->addr != NULL); +} + +/* Interface comparer for working out ordering. */ +int +ipv4_ifcmp(const struct interface *si, const struct interface *ti) +{ + const struct dhcp_state *sis, *tis; + + sis = D_CSTATE(si); + tis = D_CSTATE(ti); + if (sis && !tis) + return -1; + if (!sis && tis) + return 1; + if (!sis && !tis) + return 0; + /* If one has a lease and the other not, it takes precedence. */ + if (sis->new && !tis->new) + return -1; + if (!sis->new && tis->new) + return 1; + /* Always prefer proper leases */ + if (!(sis->added & STATE_FAKE) && (tis->added & STATE_FAKE)) + return -1; + if ((sis->added & STATE_FAKE) && !(tis->added & STATE_FAKE)) + return 1; + /* If we are either, they neither have a lease, or they both have. + * We need to check for IPv4LL and make it non-preferred. */ + if (sis->new && tis->new) { + if (IS_DHCP(sis->new) && !IS_DHCP(tis->new)) + return -1; + if (!IS_DHCP(sis->new) && IS_DHCP(tis->new)) + return 1; + } + return 0; +} + +static int +inet_dhcproutes(rb_tree_t *routes, struct interface *ifp, bool *have_default) +{ + const struct dhcp_state *state; + rb_tree_t nroutes; + struct rt *rt, *r = NULL; + struct in_addr in; + uint16_t mtu; + int n; + + state = D_CSTATE(ifp); + if (state == NULL || state->state != DHS_BOUND || !state->added) + return 0; + + /* An address does have to exist. */ + assert(state->addr); + + rb_tree_init(&nroutes, &rt_compare_proto_ops); + + /* First, add a subnet route. */ + if (state->addr->mask.s_addr != INADDR_ANY +#ifndef BSD + /* BSD adds a route in this instance */ + && state->addr->mask.s_addr != INADDR_BROADCAST +#endif + ) { + if ((rt = rt_new(ifp)) == NULL) + return -1; + rt->rt_dflags |= RTDF_IFA_ROUTE; + in.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr; + sa_in_init(&rt->rt_dest, &in); + in.s_addr = state->addr->mask.s_addr; + sa_in_init(&rt->rt_netmask, &in); + //in.s_addr = INADDR_ANY; + //sa_in_init(&rt->rt_gateway, &in); + rt->rt_gateway.sa_family = AF_UNSPEC; + rt_proto_add(&nroutes, rt); + } + + /* If any set routes, grab them, otherwise DHCP routes. */ + if (RB_TREE_MIN(&ifp->options->routes)) { + RB_TREE_FOREACH(r, &ifp->options->routes) { + if (sa_is_unspecified(&r->rt_gateway)) + break; + if ((rt = rt_new0(ifp->ctx)) == NULL) + return -1; + memcpy(rt, r, sizeof(*rt)); + rt_setif(rt, ifp); + rt->rt_dflags = RTDF_STATIC; + rt_proto_add(&nroutes, rt); + } + } else { + if (dhcp_get_routes(&nroutes, ifp) == -1) + return -1; + } + + /* If configured, install a gateway to the desintion + * for P2P interfaces. */ + if (ifp->flags & IFF_POINTOPOINT && + has_option_mask(ifp->options->dstmask, DHO_ROUTER)) + { + if ((rt = rt_new(ifp)) == NULL) + return -1; + in.s_addr = INADDR_ANY; + sa_in_init(&rt->rt_dest, &in); + sa_in_init(&rt->rt_netmask, &in); + sa_in_init(&rt->rt_gateway, &state->addr->brd); + sa_in_init(&rt->rt_ifa, &state->addr->addr); + rt_proto_add(&nroutes, rt); + } + + /* Copy our address as the source address and set mtu */ + mtu = dhcp_get_mtu(ifp); + n = 0; + while ((rt = RB_TREE_MIN(&nroutes)) != NULL) { + rb_tree_remove_node(&nroutes, rt); + rt->rt_mtu = mtu; + if (!(rt->rt_dflags & RTDF_STATIC)) + rt->rt_dflags |= RTDF_DHCP; + sa_in_init(&rt->rt_ifa, &state->addr->addr); + if (rb_tree_insert_node(routes, rt) != rt) { + rt_free(rt); + continue; + } + if (rt_is_default(rt)) + *have_default = true; + n = 1; + } + + return n; +} + +/* We should check to ensure the routers are on the same subnet + * OR supply a host route. If not, warn and add a host route. */ +static int +inet_routerhostroute(rb_tree_t *routes, struct interface *ifp) +{ + struct rt *rt, *rth, *rtp; + struct sockaddr_in *dest, *netmask, *gateway; + const char *cp, *cp2, *cp3, *cplim; + struct if_options *ifo; + const struct dhcp_state *state; + struct in_addr in; + rb_tree_t troutes; + + /* Don't add a host route for these interfaces. */ + if (ifp->flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) + return 0; + + rb_tree_init(&troutes, &rt_compare_proto_ops); + + RB_TREE_FOREACH(rt, routes) { + if (rt->rt_dest.sa_family != AF_INET) + continue; + if (!sa_is_unspecified(&rt->rt_dest) || + sa_is_unspecified(&rt->rt_gateway)) + continue; + gateway = satosin(&rt->rt_gateway); + /* Scan for a route to match */ + RB_TREE_FOREACH(rth, routes) { + if (rth == rt) + break; + /* match host */ + if (sa_cmp(&rth->rt_dest, &rt->rt_gateway) == 0) + break; + /* match subnet */ + /* XXX ADD TO RT_COMARE? XXX */ + cp = (const char *)&gateway->sin_addr.s_addr; + dest = satosin(&rth->rt_dest); + cp2 = (const char *)&dest->sin_addr.s_addr; + netmask = satosin(&rth->rt_netmask); + cp3 = (const char *)&netmask->sin_addr.s_addr; + cplim = cp3 + sizeof(netmask->sin_addr.s_addr); + while (cp3 < cplim) { + if ((*cp++ ^ *cp2++) & *cp3++) + break; + } + if (cp3 == cplim) + break; + } + if (rth != rt) + continue; + if ((state = D_CSTATE(ifp)) == NULL) + continue; + ifo = ifp->options; + if (ifp->flags & IFF_NOARP) { + if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) && + !(state->added & STATE_FAKE)) + { + char buf[INET_MAX_ADDRSTRLEN]; + + ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED; + logwarnx("%s: forcing router %s through " + "interface", + ifp->name, + sa_addrtop(&rt->rt_gateway, + buf, sizeof(buf))); + } + gateway->sin_addr.s_addr = INADDR_ANY; + continue; + } + if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) && + !(state->added & STATE_FAKE)) + { + char buf[INET_MAX_ADDRSTRLEN]; + + ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED; + logwarnx("%s: router %s requires a host route", + ifp->name, + sa_addrtop(&rt->rt_gateway, buf, sizeof(buf))); + } + + if ((rth = rt_new(ifp)) == NULL) + return -1; + rth->rt_flags |= RTF_HOST; + sa_in_init(&rth->rt_dest, &gateway->sin_addr); + in.s_addr = INADDR_BROADCAST; + sa_in_init(&rth->rt_netmask, &in); + in.s_addr = INADDR_ANY; + sa_in_init(&rth->rt_gateway, &in); + rth->rt_mtu = dhcp_get_mtu(ifp); + if (state->addr != NULL) + sa_in_init(&rth->rt_ifa, &state->addr->addr); + else + rth->rt_ifa.sa_family = AF_UNSPEC; + + /* We need to insert the host route just before the router. */ + while ((rtp = RB_TREE_MAX(routes)) != NULL) { + rb_tree_remove_node(routes, rtp); + rt_proto_add(&troutes, rtp); + if (rtp == rt) + break; + } + rt_proto_add(routes, rth); + /* troutes is now reversed, so add backwards again. */ + while ((rtp = RB_TREE_MAX(&troutes)) != NULL) { + rb_tree_remove_node(&troutes, rtp); + rt_proto_add(routes, rtp); + } + } + return 0; +} + +bool +inet_getroutes(struct dhcpcd_ctx *ctx, rb_tree_t *routes) +{ + struct interface *ifp; + bool have_default = false; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (!ifp->active) + continue; + if (inet_dhcproutes(routes, ifp, &have_default) == -1) + return false; +#ifdef IPV4LL + if (ipv4ll_subnetroute(routes, ifp) == -1) + return false; +#endif + if (inet_routerhostroute(routes, ifp) == -1) + return false; + } + +#ifdef IPV4LL + /* If there is no default route, see if we can use an IPv4LL one. */ + if (have_default) + return true; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->active && + ipv4ll_defaultroute(routes, ifp) == 1) + break; + } +#endif + + return true; +} + +int +ipv4_deladdr(struct ipv4_addr *addr, int keeparp) +{ + int r; + struct ipv4_state *state; + struct ipv4_addr *ap; + + logdebugx("%s: deleting IP address %s", + addr->iface->name, addr->saddr); + + r = if_address(RTM_DELADDR, addr); + if (r == -1 && + errno != EADDRNOTAVAIL && errno != ESRCH && + errno != ENXIO && errno != ENODEV) + logerr("%s: %s", addr->iface->name, __func__); + +#ifdef ARP + if (!keeparp) + arp_freeaddr(addr->iface, &addr->addr); +#else + UNUSED(keeparp); +#endif + + state = IPV4_STATE(addr->iface); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (IPV4_MASK_EQ(ap, addr)) { + struct dhcp_state *dstate; + + dstate = D_STATE(ap->iface); + TAILQ_REMOVE(&state->addrs, ap, next); + free(ap); + + if (dstate && dstate->addr == ap) { + dstate->added = 0; + dstate->addr = NULL; + } + break; + } + } + + return r; +} + +static int +delete_address(struct interface *ifp) +{ + int r; + struct if_options *ifo; + struct dhcp_state *state; + + state = D_STATE(ifp); + ifo = ifp->options; + /* The lease could have been added, but the address deleted + * by a 3rd party. */ + if (state->addr == NULL || + ifo->options & DHCPCD_INFORM || + (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0)) + return 0; +#ifdef ARP + arp_freeaddr(ifp, &state->addr->addr); +#endif + r = ipv4_deladdr(state->addr, 0); + return r; +} + +struct ipv4_state * +ipv4_getstate(struct interface *ifp) +{ + struct ipv4_state *state; + + state = IPV4_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_IPV4] = malloc(sizeof(*state)); + state = IPV4_STATE(ifp); + if (state == NULL) { + logerr(__func__); + return NULL; + } + TAILQ_INIT(&state->addrs); + } + return state; +} + +#ifdef ALIAS_ADDR +/* Find the next logical aliase address we can use. */ +static int +ipv4_aliasaddr(struct ipv4_addr *ia, struct ipv4_addr **repl) +{ + struct ipv4_state *state; + struct ipv4_addr *iap; + unsigned int lun; + char alias[IF_NAMESIZE]; + + if (ia->alias[0] != '\0') + return 0; + + lun = 0; + state = IPV4_STATE(ia->iface); +find_lun: + if (if_makealias(alias, IF_NAMESIZE, ia->iface->name, lun) >= + IF_NAMESIZE) + { + errno = ENOMEM; + return -1; + } + TAILQ_FOREACH(iap, &state->addrs, next) { + if (iap->alias[0] != '\0' && iap->addr.s_addr == INADDR_ANY) { + /* No address assigned? Lets use it. */ + strlcpy(ia->alias, iap->alias, sizeof(ia->alias)); + if (repl) + *repl = iap; + return 1; + } + if (strcmp(iap->alias, alias) == 0) + break; + } + + if (iap != NULL) { + if (lun == UINT_MAX) { + errno = ERANGE; + return -1; + } + lun++; + goto find_lun; + } + + strlcpy(ia->alias, alias, sizeof(ia->alias)); + return 0; +} +#endif + +struct ipv4_addr * +ipv4_addaddr(struct interface *ifp, const struct in_addr *addr, + const struct in_addr *mask, const struct in_addr *bcast, + uint32_t vltime, uint32_t pltime) +{ + struct ipv4_state *state; + struct ipv4_addr *ia; +#ifdef ALIAS_ADDR + int replaced, blank; + struct ipv4_addr *replaced_ia; +#endif + + if ((state = ipv4_getstate(ifp)) == NULL) { + logerr(__func__); + return NULL; + } + if (ifp->options->options & DHCPCD_NOALIAS) { + struct ipv4_addr *ian; + + TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ian) { + if (ia->addr.s_addr != addr->s_addr) + ipv4_deladdr(ia, 0); + } + } + + ia = ipv4_iffindaddr(ifp, addr, NULL); + if (ia == NULL) { + ia = malloc(sizeof(*ia)); + if (ia == NULL) { + logerr(__func__); + return NULL; + } + ia->iface = ifp; + ia->addr = *addr; +#ifdef IN_IFF_TENTATIVE + ia->addr_flags = IN_IFF_TENTATIVE; +#endif + ia->flags = IPV4_AF_NEW; + } else + ia->flags &= ~IPV4_AF_NEW; + + ia->mask = *mask; + ia->brd = *bcast; +#ifdef IP_LIFETIME + if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { + /* We don't want the kernel to expire the address. */ + ia->vltime = ia->pltime = DHCP_INFINITE_LIFETIME; + } else { + ia->vltime = vltime; + ia->pltime = pltime; + } +#else + UNUSED(vltime); + UNUSED(pltime); +#endif + snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", + inet_ntoa(*addr), inet_ntocidr(*mask)); + +#ifdef ALIAS_ADDR + blank = (ia->alias[0] == '\0'); + if ((replaced = ipv4_aliasaddr(ia, &replaced_ia)) == -1) { + logerr("%s: ipv4_aliasaddr", ifp->name); + free(ia); + return NULL; + } + if (blank) + logdebugx("%s: aliased %s", ia->alias, ia->saddr); +#endif + + logdebugx("%s: adding IP address %s %s %s", + ifp->name, ia->saddr, + ifp->flags & IFF_POINTOPOINT ? "destination" : "broadcast", + inet_ntoa(*bcast)); + if (if_address(RTM_NEWADDR, ia) == -1) { + if (errno != EEXIST) + logerr("%s: if_addaddress", + __func__); + if (ia->flags & IPV4_AF_NEW) + free(ia); + return NULL; + } + +#ifdef ALIAS_ADDR + if (replaced) { + TAILQ_REMOVE(&state->addrs, replaced_ia, next); + free(replaced_ia); + } +#endif + + if (ia->flags & IPV4_AF_NEW) + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + return ia; +} + +static int +ipv4_daddaddr(struct interface *ifp, const struct dhcp_lease *lease) +{ + struct dhcp_state *state; + struct ipv4_addr *ia; + + ia = ipv4_addaddr(ifp, &lease->addr, &lease->mask, &lease->brd, + lease->leasetime, lease->rebindtime); + if (ia == NULL) + return -1; + + state = D_STATE(ifp); + state->added = STATE_ADDED; + state->addr = ia; + return 0; +} + +struct ipv4_addr * +ipv4_applyaddr(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease; + struct if_options *ifo = ifp->options; + struct ipv4_addr *ia; + + if (state == NULL) + return NULL; + + lease = &state->lease; + if (state->new == NULL) { + if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + { + if (state->added) { + delete_address(ifp); + rt_build(ifp->ctx, AF_INET); +#ifdef ARP + /* Announce the preferred address to + * kick ARP caches. */ + arp_announceaddr(ifp->ctx,&lease->addr); +#endif + } + script_runreason(ifp, state->reason); + } else + rt_build(ifp->ctx, AF_INET); + return NULL; + } + + ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); + /* If the netmask or broadcast is different, re-add the addresss. + * If IP addresses do not have lifetimes, there is a very real chance + * that re-adding them will scrub the subnet route temporarily + * which is a bad thing, so avoid it. */ + if (ia != NULL && + ia->mask.s_addr == lease->mask.s_addr && + ia->brd.s_addr == lease->brd.s_addr) + { +#ifndef IP_LIFETIME + logdebugx("%s: IP address %s already exists", + ifp->name, ia->saddr); +#endif + } else { +#ifdef __linux__ + /* Linux does not change netmask/broadcast address + * for re-added addresses, so we need to delete the old one + * first. */ + if (ia != NULL) + ipv4_deladdr(ia, 0); +#endif +#ifndef IP_LIFETIME + if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST) + return NULL; +#endif + } +#ifdef IP_LIFETIME + if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST) + return NULL; +#endif + + ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); + if (ia == NULL) { + logerrx("%s: added address vanished", ifp->name); + return NULL; + } +#if defined(ARP) && defined(IN_IFF_NOTUSEABLE) + if (ia->addr_flags & IN_IFF_NOTUSEABLE) + return NULL; +#endif + + /* Delete the old address if different */ + if (state->addr && + state->addr->addr.s_addr != lease->addr.s_addr && + ipv4_iffindaddr(ifp, &lease->addr, NULL)) + delete_address(ifp); + + state->addr = ia; + state->added = STATE_ADDED; + + rt_build(ifp->ctx, AF_INET); + +#ifdef ARP + arp_announceaddr(ifp->ctx, &state->addr->addr); +#endif + + if (state->state == DHS_BOUND) { + script_runreason(ifp, state->reason); + dhcpcd_daemonise(ifp->ctx); + } + return ia; +} + +void +ipv4_markaddrsstale(struct interface *ifp) +{ + struct ipv4_state *state; + struct ipv4_addr *ia; + + state = IPV4_STATE(ifp); + if (state == NULL) + return; + + TAILQ_FOREACH(ia, &state->addrs, next) { + ia->flags |= IPV4_AF_STALE; + } +} + +void +ipv4_deletestaleaddrs(struct interface *ifp) +{ + struct ipv4_state *state; + struct ipv4_addr *ia, *ia1; + + state = IPV4_STATE(ifp); + if (state == NULL) + return; + + TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) { + if (!(ia->flags & IPV4_AF_STALE)) + continue; + ipv4_handleifa(ifp->ctx, RTM_DELADDR, + ifp->ctx->ifaces, ifp->name, + &ia->addr, &ia->mask, &ia->brd, 0, getpid()); + } +} + +void +ipv4_handleifa(struct dhcpcd_ctx *ctx, + int cmd, struct if_head *ifs, const char *ifname, + const struct in_addr *addr, const struct in_addr *mask, + const struct in_addr *brd, int addrflags, pid_t pid) +{ + struct interface *ifp; + struct ipv4_state *state; + struct ipv4_addr *ia; + bool ia_is_new; + +#if 0 + char sbrdbuf[INET_ADDRSTRLEN]; + const char *sbrd; + + if (brd) + sbrd = inet_ntop(AF_INET, brd, sbrdbuf, sizeof(sbrdbuf)); + else + sbrd = NULL; + logdebugx("%s: %s %s/%d %s %d", ifname, + cmd == RTM_NEWADDR ? "RTM_NEWADDR" : + cmd == RTM_DELADDR ? "RTM_DELADDR" : "???", + inet_ntoa(*addr), inet_ntocidr(*mask), sbrd, addrflags); +#endif + + if (ifs == NULL) + ifs = ctx->ifaces; + if (ifs == NULL) { + errno = ESRCH; + return; + } + if ((ifp = if_find(ifs, ifname)) == NULL) + return; + if ((state = ipv4_getstate(ifp)) == NULL) { + errno = ENOENT; + return; + } + + ia = ipv4_iffindaddr(ifp, addr, NULL); + switch (cmd) { + case RTM_NEWADDR: + if (ia == NULL) { + if ((ia = malloc(sizeof(*ia))) == NULL) { + logerr(__func__); + return; + } + ia->iface = ifp; + ia->addr = *addr; + ia->mask = *mask; + ia->flags = 0; + ia_is_new = true; +#ifdef ALIAS_ADDR + strlcpy(ia->alias, ifname, sizeof(ia->alias)); +#endif + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + } else + ia_is_new = false; + /* Mask could have changed */ + if (ia_is_new || + (mask->s_addr != INADDR_ANY && + mask->s_addr != ia->mask.s_addr)) + { + ia->mask = *mask; + snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", + inet_ntoa(*addr), inet_ntocidr(*mask)); + } + if (brd != NULL) + ia->brd = *brd; + else + ia->brd.s_addr = INADDR_ANY; + ia->addr_flags = addrflags; + ia->flags &= ~IPV4_AF_STALE; + break; + case RTM_DELADDR: + if (ia == NULL) + return; + if (mask->s_addr != INADDR_ANY && + mask->s_addr != ia->mask.s_addr) + return; + TAILQ_REMOVE(&state->addrs, ia, next); + break; + default: + return; + } + + if (addr->s_addr != INADDR_ANY && addr->s_addr != INADDR_BROADCAST) { + ia = dhcp_handleifa(cmd, ia, pid); +#ifdef IPV4LL + if (ia != NULL) + ipv4ll_handleifa(cmd, ia, pid); +#endif + } + + if (cmd == RTM_DELADDR) + free(ia); +} + +void +ipv4_free(struct interface *ifp) +{ + struct ipv4_state *state; + struct ipv4_addr *ia; + + if (ifp == NULL || (state = IPV4_STATE(ifp)) == NULL) + return; + + while ((ia = TAILQ_FIRST(&state->addrs))) { + TAILQ_REMOVE(&state->addrs, ia, next); + free(ia); + } + free(state); +} Index: src/ipv4ll.h =================================================================== --- /dev/null +++ src/ipv4ll.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef IPV4LL_H +#define IPV4LL_H + +#define LINKLOCAL_ADDR 0xa9fe0000 +#define LINKLOCAL_MASK IN_CLASSB_NET +#define LINKLOCAL_BCAST (LINKLOCAL_ADDR | ~LINKLOCAL_MASK) + +#ifndef IN_LINKLOCAL +# define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR) +#endif + +#ifdef IPV4LL +#include "arp.h" + +struct ipv4ll_state { + struct in_addr pickedaddr; + struct ipv4_addr *addr; + char randomstate[128]; + bool seeded; + bool down; + size_t conflicts; +#ifndef KERNEL_RFC5227 + struct arp_state *arp; +#endif +}; + +#define IPV4LL_STATE(ifp) \ + ((struct ipv4ll_state *)(ifp)->if_data[IF_DATA_IPV4LL]) +#define IPV4LL_CSTATE(ifp) \ + ((const struct ipv4ll_state *)(ifp)->if_data[IF_DATA_IPV4LL]) +#define IPV4LL_STATE_RUNNING(ifp) \ + (IPV4LL_CSTATE((ifp)) && !IPV4LL_CSTATE((ifp))->down && \ + (IPV4LL_CSTATE((ifp))->addr != NULL)) + +int ipv4ll_subnetroute(rb_tree_t *, struct interface *); +int ipv4ll_defaultroute(rb_tree_t *,struct interface *); +ssize_t ipv4ll_env(FILE *, const char *, const struct interface *); +void ipv4ll_start(void *); +void ipv4ll_claimed(void *); +void ipv4ll_handle_failure(void *); +struct ipv4_addr *ipv4ll_handleifa(int, struct ipv4_addr *, pid_t pid); +#ifdef HAVE_ROUTE_METRIC +int ipv4ll_recvrt(int, const struct rt *); +#endif + +void ipv4ll_reset(struct interface *); +void ipv4ll_drop(struct interface *); +void ipv4ll_free(struct interface *); +#endif + +#endif Index: src/ipv4ll.c =================================================================== --- /dev/null +++ src/ipv4ll.c @@ -0,0 +1,554 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ELOOP_QUEUE IPV4LL +#include "config.h" +#include "arp.h" +#include "common.h" +#include "dhcp.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "logerr.h" +#include "sa.h" +#include "script.h" + +static const struct in_addr inaddr_llmask = { + .s_addr = HTONL(LINKLOCAL_MASK) +}; +static const struct in_addr inaddr_llbcast = { + .s_addr = HTONL(LINKLOCAL_BCAST) +}; + +static void +ipv4ll_pickaddr(struct interface *ifp) +{ + struct in_addr addr = { .s_addr = 0 }; + struct ipv4ll_state *state; + + state = IPV4LL_STATE(ifp); + setstate(state->randomstate); + + do { + long r; + +again: + /* RFC 3927 Section 2.1 states that the first 256 and + * last 256 addresses are reserved for future use. + * See ipv4ll_start for why we don't use arc4random. */ + /* coverity[dont_call] */ + r = random(); + addr.s_addr = ntohl(LINKLOCAL_ADDR | + ((uint32_t)(r % 0xFD00) + 0x0100)); + + /* No point using a failed address */ + if (IN_ARE_ADDR_EQUAL(&addr, &state->pickedaddr)) + goto again; + /* Ensure we don't have the address on another interface */ + } while (ipv4_findaddr(ifp->ctx, &addr) != NULL); + + /* Restore the original random state */ + setstate(ifp->ctx->randomstate); + state->pickedaddr = addr; +} + +int +ipv4ll_subnetroute(rb_tree_t *routes, struct interface *ifp) +{ + struct ipv4ll_state *state; + struct rt *rt; + struct in_addr in; + + assert(ifp != NULL); + if ((state = IPV4LL_STATE(ifp)) == NULL || + state->addr == NULL) + return 0; + + if ((rt = rt_new(ifp)) == NULL) + return -1; + + in.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr; + sa_in_init(&rt->rt_dest, &in); + in.s_addr = state->addr->mask.s_addr; + sa_in_init(&rt->rt_netmask, &in); + in.s_addr = INADDR_ANY; + sa_in_init(&rt->rt_gateway, &in); + sa_in_init(&rt->rt_ifa, &state->addr->addr); + rt->rt_dflags |= RTDF_IPV4LL; + return rt_proto_add(routes, rt) ? 1 : 0; +} + +int +ipv4ll_defaultroute(rb_tree_t *routes, struct interface *ifp) +{ + struct ipv4ll_state *state; + struct rt *rt; + struct in_addr in; + + assert(ifp != NULL); + if ((state = IPV4LL_STATE(ifp)) == NULL || + state->addr == NULL) + return 0; + + if ((rt = rt_new(ifp)) == NULL) + return -1; + + in.s_addr = INADDR_ANY; + sa_in_init(&rt->rt_dest, &in); + sa_in_init(&rt->rt_netmask, &in); + sa_in_init(&rt->rt_gateway, &in); + sa_in_init(&rt->rt_ifa, &state->addr->addr); + rt->rt_dflags |= RTDF_IPV4LL; +#ifdef HAVE_ROUTE_METRIC + rt->rt_metric += RTMETRIC_IPV4LL; +#endif + return rt_proto_add(routes, rt) ? 1 : 0; +} + +ssize_t +ipv4ll_env(FILE *fp, const char *prefix, const struct interface *ifp) +{ + const struct ipv4ll_state *state; + const char *pf = prefix == NULL ? "" : "_"; + struct in_addr netnum; + + assert(ifp != NULL); + if ((state = IPV4LL_CSTATE(ifp)) == NULL || state->addr == NULL) + return 0; + + /* Emulate a DHCP environment */ + if (efprintf(fp, "%s%sip_address=%s", + prefix, pf, inet_ntoa(state->addr->addr)) == -1) + return -1; + if (efprintf(fp, "%s%ssubnet_mask=%s", + prefix, pf, inet_ntoa(state->addr->mask)) == -1) + return -1; + if (efprintf(fp, "%s%ssubnet_cidr=%d", + prefix, pf, inet_ntocidr(state->addr->mask)) == -1) + return -1; + if (efprintf(fp, "%s%sbroadcast_address=%s", + prefix, pf, inet_ntoa(state->addr->brd)) == -1) + return -1; + netnum.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr; + if (efprintf(fp, "%s%snetwork_number=%s", + prefix, pf, inet_ntoa(netnum)) == -1) + return -1; + return 5; +} + +static void +ipv4ll_announced_arp(struct arp_state *astate) +{ + struct ipv4ll_state *state = IPV4LL_STATE(astate->iface); + + state->conflicts = 0; +#ifdef KERNEL_RFC5227 + arp_free(astate); +#endif +} + +#ifndef KERNEL_RFC5227 +/* This is the callback by ARP freeing */ +static void +ipv4ll_free_arp(struct arp_state *astate) +{ + struct ipv4ll_state *state; + + state = IPV4LL_STATE(astate->iface); + if (state != NULL && state->arp == astate) + state->arp = NULL; +} + +/* This is us freeing any ARP state */ +static void +ipv4ll_freearp(struct interface *ifp) +{ + struct ipv4ll_state *state; + + state = IPV4LL_STATE(ifp); + if (state == NULL || state->arp == NULL) + return; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp); + arp_free(state->arp); + state->arp = NULL; +} +#else +#define ipv4ll_freearp(ifp) +#endif + +static void +ipv4ll_not_found(struct interface *ifp) +{ + struct ipv4ll_state *state; + struct ipv4_addr *ia; + struct arp_state *astate; + + state = IPV4LL_STATE(ifp); + ia = ipv4_iffindaddr(ifp, &state->pickedaddr, &inaddr_llmask); +#ifdef IN_IFF_NOTREADY + if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY) +#endif + loginfox("%s: using IPv4LL address %s", + ifp->name, inet_ntoa(state->pickedaddr)); + if (!(ifp->options->options & DHCPCD_CONFIGURE)) + goto run; + if (ia == NULL) { + if (ifp->ctx->options & DHCPCD_TEST) + goto test; + ia = ipv4_addaddr(ifp, &state->pickedaddr, + &inaddr_llmask, &inaddr_llbcast, + DHCP_INFINITE_LIFETIME, DHCP_INFINITE_LIFETIME); + } + if (ia == NULL) + return; +#ifdef IN_IFF_NOTREADY + if (ia->addr_flags & IN_IFF_NOTREADY) + return; + logdebugx("%s: DAD completed for %s", ifp->name, ia->saddr); +#endif + +test: + state->addr = ia; + state->down = false; + if (ifp->ctx->options & DHCPCD_TEST) { + script_runreason(ifp, "TEST"); + eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); + return; + } + rt_build(ifp->ctx, AF_INET); +run: + astate = arp_announceaddr(ifp->ctx, &ia->addr); + if (astate != NULL) + astate->announced_cb = ipv4ll_announced_arp; + script_runreason(ifp, "IPV4LL"); + dhcpcd_daemonise(ifp->ctx); +} + +static void +ipv4ll_found(struct interface *ifp) +{ + struct ipv4ll_state *state = IPV4LL_STATE(ifp); + + ipv4ll_freearp(ifp); + if (++state->conflicts == MAX_CONFLICTS) + logerrx("%s: failed to acquire an IPv4LL address", + ifp->name); + ipv4ll_pickaddr(ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + state->conflicts >= MAX_CONFLICTS ? + RATE_LIMIT_INTERVAL : PROBE_WAIT, + ipv4ll_start, ifp); +} + +static void +ipv4ll_defend_failed(struct interface *ifp) +{ + struct ipv4ll_state *state = IPV4LL_STATE(ifp); + + ipv4ll_freearp(ifp); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(state->addr, 1); + state->addr = NULL; + rt_build(ifp->ctx, AF_INET); + script_runreason(ifp, "IPV4LL"); + ipv4ll_pickaddr(ifp); + ipv4ll_start(ifp); +} + +#ifndef KERNEL_RFC5227 +static void +ipv4ll_not_found_arp(struct arp_state *astate) +{ + + ipv4ll_not_found(astate->iface); +} + +static void +ipv4ll_found_arp(struct arp_state *astate, __unused const struct arp_msg *amsg) +{ + + ipv4ll_found(astate->iface); +} + +static void +ipv4ll_defend_failed_arp(struct arp_state *astate) +{ + + ipv4ll_defend_failed(astate->iface); +} +#endif + +void +ipv4ll_start(void *arg) +{ + struct interface *ifp = arg; + struct ipv4ll_state *state; + struct ipv4_addr *ia; + bool repick; +#ifndef KERNEL_RFC5227 + struct arp_state *astate; +#endif + + if ((state = IPV4LL_STATE(ifp)) == NULL) { + ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state)); + if ((state = IPV4LL_STATE(ifp)) == NULL) { + logerr(__func__); + return; + } + } + + /* RFC 3927 Section 2.1 states that the random number generator + * SHOULD be seeded with a value derived from persistent information + * such as the IEEE 802 MAC address so that it usually picks + * the same address without persistent storage. */ + if (!state->seeded) { + unsigned int seed; + char *orig; + + if (sizeof(seed) > ifp->hwlen) { + seed = 0; + memcpy(&seed, ifp->hwaddr, ifp->hwlen); + } else + memcpy(&seed, ifp->hwaddr + ifp->hwlen - sizeof(seed), + sizeof(seed)); + /* coverity[dont_call] */ + orig = initstate(seed, + state->randomstate, sizeof(state->randomstate)); + + /* Save the original state. */ + if (ifp->ctx->randomstate == NULL) + ifp->ctx->randomstate = orig; + + /* Set back the original state until we need the seeded one. */ + setstate(ifp->ctx->randomstate); + state->seeded = true; + } + + /* Find the previosuly used address. */ + if (state->pickedaddr.s_addr != INADDR_ANY) + ia = ipv4_iffindaddr(ifp, &state->pickedaddr, NULL); + else + ia = NULL; + + /* Find an existing IPv4LL address and ensure we can work with it. */ + if (ia == NULL) + ia = ipv4_iffindlladdr(ifp); + + repick = false; +#ifdef IN_IFF_DUPLICATED + if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) { + state->pickedaddr = ia->addr; /* So it's not picked again. */ + repick = true; + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(ia, 0); + ia = NULL; + } +#endif + + state->addr = ia; + state->down = true; + if (ia != NULL) { + state->pickedaddr = ia->addr; +#ifdef IN_IFF_TENTATIVE + if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) { + loginfox("%s: waiting for DAD to complete on %s", + ifp->name, inet_ntoa(ia->addr)); + return; + } +#endif +#ifdef IN_IFF_DUPLICATED + loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr); +#endif + } else { + loginfox("%s: probing for an IPv4LL address", ifp->name); + if (repick || state->pickedaddr.s_addr == INADDR_ANY) + ipv4ll_pickaddr(ifp); + } + +#ifdef KERNEL_RFC5227 + ipv4ll_not_found(ifp); +#else + ipv4ll_freearp(ifp); + state->arp = astate = arp_new(ifp, &state->pickedaddr); + if (state->arp == NULL) + return; + + astate->found_cb = ipv4ll_found_arp; + astate->not_found_cb = ipv4ll_not_found_arp; + astate->announced_cb = ipv4ll_announced_arp; + astate->defend_failed_cb = ipv4ll_defend_failed_arp; + astate->free_cb = ipv4ll_free_arp; + arp_probe(astate); +#endif +} + +void +ipv4ll_drop(struct interface *ifp) +{ + struct ipv4ll_state *state; + bool dropped = false; + struct ipv4_state *istate; + + assert(ifp != NULL); + + ipv4ll_freearp(ifp); + + if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP) + return; + + state = IPV4LL_STATE(ifp); + if (state && state->addr != NULL) { + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(state->addr, 1); + state->addr = NULL; + dropped = true; + } + + /* Free any other link local addresses that might exist. */ + if ((istate = IPV4_STATE(ifp)) != NULL) { + struct ipv4_addr *ia, *ian; + + TAILQ_FOREACH_SAFE(ia, &istate->addrs, next, ian) { + if (IN_LINKLOCAL(ntohl(ia->addr.s_addr))) { + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(ia, 0); + dropped = true; + } + } + } + + if (dropped) { + rt_build(ifp->ctx, AF_INET); + script_runreason(ifp, "IPV4LL"); + } +} + +void +ipv4ll_reset(struct interface *ifp) +{ + struct ipv4ll_state *state = IPV4LL_STATE(ifp); + + if (state == NULL) + return; + ipv4ll_freearp(ifp); + state->pickedaddr.s_addr = INADDR_ANY; + state->seeded = false; +} + +void +ipv4ll_free(struct interface *ifp) +{ + + assert(ifp != NULL); + + ipv4ll_freearp(ifp); + free(IPV4LL_STATE(ifp)); + ifp->if_data[IF_DATA_IPV4LL] = NULL; +} + +/* This may cause issues in BSD systems, where running as a single dhcpcd + * daemon would solve this issue easily. */ +#ifdef HAVE_ROUTE_METRIC +int +ipv4ll_recvrt(__unused int cmd, const struct rt *rt) +{ + struct dhcpcd_ctx *ctx; + struct interface *ifp; + + /* Only interested in default route changes. */ + if (sa_is_unspecified(&rt->rt_dest)) + return 0; + + /* If any interface is running IPv4LL, rebuild our routing table. */ + ctx = rt->rt_ifp->ctx; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (IPV4LL_STATE_RUNNING(ifp)) { + rt_build(ctx, AF_INET); + break; + } + } + + return 0; +} +#endif + +struct ipv4_addr * +ipv4ll_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid) +{ + struct interface *ifp; + struct ipv4ll_state *state; + + ifp = ia->iface; + state = IPV4LL_STATE(ifp); + if (state == NULL) + return ia; + + if (cmd == RTM_DELADDR && + state->addr != NULL && + IN_ARE_ADDR_EQUAL(&state->addr->addr, &ia->addr)) + { + loginfox("%s: pid %d deleted IP address %s", + ifp->name, pid, ia->saddr); + ipv4ll_defend_failed(ifp); + return ia; + } + +#ifdef IN_IFF_DUPLICATED + if (cmd != RTM_NEWADDR) + return ia; + if (!IN_ARE_ADDR_EQUAL(&state->pickedaddr, &ia->addr)) + return ia; + if (!(ia->addr_flags & IN_IFF_NOTUSEABLE)) + ipv4ll_not_found(ifp); + else if (ia->addr_flags & IN_IFF_DUPLICATED) { + logerrx("%s: DAD detected %s", ifp->name, ia->saddr); + ipv4ll_freearp(ifp); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(ia, 1); + state->addr = NULL; + rt_build(ifp->ctx, AF_INET); + ipv4ll_found(ifp); + return NULL; + } +#endif + + return ia; +} Index: src/ipv6.h =================================================================== --- /dev/null +++ src/ipv6.h @@ -0,0 +1,316 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef IPV6_H +#define IPV6_H + +#include +#include + +#include "config.h" +#include "if.h" + +#ifndef __linux__ +# if !defined(__QNX__) && !defined(__sun) +# include +# endif +# include +# ifndef __sun +# include +# endif +#endif + +#define EUI64_GBIT 0x01 +#define EUI64_UBIT 0x02 +#define EUI64_TO_IFID(in6) do {(in6)->s6_addr[8] ^= EUI64_UBIT; } while (0) +#define EUI64_GROUP(in6) ((in6)->s6_addr[8] & EUI64_GBIT) + +#ifndef ND6_INFINITE_LIFETIME +# define ND6_INFINITE_LIFETIME ((uint32_t)~0) +#endif + +/* RFC4941 constants */ +#define TEMP_VALID_LIFETIME 604800 /* 1 week */ +#define TEMP_PREFERRED_LIFETIME 86400 /* 1 day */ +#define REGEN_ADVANCE 5 /* seconds */ +#define MAX_DESYNC_FACTOR 600 /* 10 minutes */ +#define TEMP_IDGEN_RETRIES 3 + +/* RFC7217 constants */ +#define IDGEN_RETRIES 3 +#define IDGEN_DELAY 1 /* second */ + +/* Interface identifier length. Prefix + this == 128 for autoconf */ +#define ipv6_ifidlen(ifp) 64 +#define IA6_CANAUTOCONF(ia) \ + ((ia)->prefix_len + ipv6_ifidlen((ia)->iface) == 128) + +#ifndef IN6_ARE_MASKED_ADDR_EQUAL +#define IN6_ARE_MASKED_ADDR_EQUAL(d, a, m) ( \ + (((d)->s6_addr32[0] ^ (a)->s6_addr32[0]) & (m)->s6_addr32[0]) == 0 && \ + (((d)->s6_addr32[1] ^ (a)->s6_addr32[1]) & (m)->s6_addr32[1]) == 0 && \ + (((d)->s6_addr32[2] ^ (a)->s6_addr32[2]) & (m)->s6_addr32[2]) == 0 && \ + (((d)->s6_addr32[3] ^ (a)->s6_addr32[3]) & (m)->s6_addr32[3]) == 0 ) +#endif + +#ifndef IN6ADDR_LINKLOCAL_ALLNODES_INIT +#define IN6ADDR_LINKLOCAL_ALLNODES_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}} +#endif +#ifndef IN6ADDR_LINKLOCAL_ALLROUTERS_INIT +#define IN6ADDR_LINKLOCAL_ALLROUTERS_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}} +#endif + +/* + * BSD kernels don't inform userland of DAD results. + * See the discussion here: + * http://mail-index.netbsd.org/tech-net/2013/03/15/msg004019.html + */ +#ifndef __linux__ +/* We guard here to avoid breaking a compile on linux ppc-64 headers */ +# include +#endif +#ifdef BSD +# define IPV6_POLLADDRFLAG +#endif + +/* This was fixed in NetBSD */ +#if (defined(__DragonFly_version) && __DragonFly_version >= 500704) || \ + (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 699002000) +# undef IPV6_POLLADDRFLAG +#endif + +/* Of course OpenBSD has their own special name. */ +#if !defined(IN6_IFF_TEMPORARY) && defined(IN6_IFF_PRIVACY) +#define IN6_IFF_TEMPORARY IN6_IFF_PRIVACY +#endif + +#ifdef __sun + /* Solaris lacks these defines. + * While it supports DaD, to seems to only expose IFF_DUPLICATE + * so we have no way of knowing if it's tentative or not. + * I don't even know if Solaris has any special treatment for tentative. */ +# define IN6_IFF_TENTATIVE 0x02 +# define IN6_IFF_DUPLICATED 0x04 +# define IN6_IFF_DETACHED 0x00 +#endif + +#define IN6_IFF_NOTUSEABLE \ + (IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED) + +/* + * If dhcpcd handles RA processing instead of the kernel, the kernel needs + * to either allow userland to set temporary addresses or mark an address + * for the kernel to manage temporary addresses from. + * If the kernel allows the former, a global #define is needed, otherwise + * the address marking will be handled in the platform specific address handler. + * + * Some BSDs do not allow userland to set temporary addresses. + * Linux-3.18 allows the marking of addresses from which to manage temp addrs. + */ +#if defined(IN6_IFF_TEMPORARY) && !defined(__linux__) +#define IPV6_MANAGETEMPADDR +#endif + +#ifdef __linux__ + /* Match Linux defines to BSD */ +# ifdef IFA_F_TEMPORARY +# define IN6_IFF_TEMPORARY IFA_F_TEMPORARY +# endif +# ifdef IFA_F_OPTIMISTIC +# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC) +# else +# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | 0x04) +# endif +# ifdef IF_F_DADFAILED +# define IN6_IFF_DUPLICATED IFA_F_DADFAILED +# else +# define IN6_IFF_DUPLICATED 0x08 +# endif +# define IN6_IFF_DETACHED 0 +#endif + +/* + * ND6 Advertising is only used for IP address sharing to prefer + * the address on a specific interface. + * This just fails to work on OpenBSD and causes erroneous duplicate + * address messages on BSD's other then DragonFly and NetBSD. + */ +#if !defined(SMALL) && \ + ((defined(__DragonFly_version) && __DragonFly_version >= 500703) || \ + (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 899002800) || \ + defined(__linux__) || defined(__sun)) +# define ND6_ADVERTISE +#endif + +#ifdef INET6 +TAILQ_HEAD(ipv6_addrhead, ipv6_addr); +struct ipv6_addr { + TAILQ_ENTRY(ipv6_addr) next; + struct interface *iface; + struct in6_addr prefix; + uint8_t prefix_len; + uint32_t prefix_vltime; + uint32_t prefix_pltime; + struct timespec created; + struct timespec acquired; + struct in6_addr addr; + int addr_flags; + unsigned int flags; + char saddr[INET6_ADDRSTRLEN]; + uint8_t iaid[4]; + uint16_t ia_type; + int dhcp6_fd; + +#ifndef SMALL + struct ipv6_addr *delegating_prefix; + struct ipv6_addrhead pd_pfxs; + TAILQ_ENTRY(ipv6_addr) pd_next; + + uint8_t prefix_exclude_len; + struct in6_addr prefix_exclude; +#endif + + void (*dadcallback)(void *); + int dadcounter; + + struct nd_neighbor_advert *na; + size_t na_len; + int na_count; + +#ifdef ALIAS_ADDR + char alias[IF_NAMESIZE]; +#endif +}; + +#define IPV6_AF_ONLINK (1U << 0) +#define IPV6_AF_NEW (1U << 1) +#define IPV6_AF_STALE (1U << 2) +#define IPV6_AF_ADDED (1U << 3) +#define IPV6_AF_AUTOCONF (1U << 4) +#define IPV6_AF_DADCOMPLETED (1U << 5) +#define IPV6_AF_DELEGATED (1U << 6) +#define IPV6_AF_DELEGATEDPFX (1U << 7) +#define IPV6_AF_NOREJECT (1U << 8) +#define IPV6_AF_REQUEST (1U << 9) +#define IPV6_AF_STATIC (1U << 10) +#define IPV6_AF_DELEGATEDLOG (1U << 11) +#define IPV6_AF_RAPFX (1U << 12) +#define IPV6_AF_EXTENDED (1U << 13) +#define IPV6_AF_REGEN (1U << 14) +#define IPV6_AF_ROUTER (1U << 15) +#ifdef IPV6_MANAGETEMPADDR +#define IPV6_AF_TEMPORARY (1U << 16) +#endif + +struct ll_callback { + TAILQ_ENTRY(ll_callback) next; + void (*callback)(void *); + void *arg; +}; +TAILQ_HEAD(ll_callback_head, ll_callback); + +struct ipv6_state { + struct ipv6_addrhead addrs; + struct ll_callback_head ll_callbacks; + +#ifdef IPV6_MANAGETEMPADDR + uint32_t desync_factor; +#endif +}; + +#define IPV6_STATE(ifp) \ + ((struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6]) +#define IPV6_CSTATE(ifp) \ + ((const struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6]) +#define IPV6_STATE_RUNNING(ifp) ipv6_staticdadcompleted((ifp)) + + +int ipv6_init(struct dhcpcd_ctx *); +int ipv6_makestableprivate(struct in6_addr *, + const struct in6_addr *, int, const struct interface *, int *); +int ipv6_makeaddr(struct in6_addr *, struct interface *, + const struct in6_addr *, int, unsigned int); +int ipv6_mask(struct in6_addr *, int); +uint8_t ipv6_prefixlen(const struct in6_addr *); +int ipv6_userprefix( const struct in6_addr *, short prefix_len, + uint64_t user_number, struct in6_addr *result, short result_len); +void ipv6_checkaddrflags(void *); +void ipv6_markaddrsstale(struct interface *, unsigned int); +void ipv6_deletestaleaddrs(struct interface *); +int ipv6_addaddr(struct ipv6_addr *, const struct timespec *); +int ipv6_doaddr(struct ipv6_addr *, struct timespec *); +ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs); +void ipv6_deleteaddr(struct ipv6_addr *); +void ipv6_freedrop_addrs(struct ipv6_addrhead *, int, + const struct interface *); +void ipv6_handleifa(struct dhcpcd_ctx *ctx, int, struct if_head *, + const char *, const struct in6_addr *, uint8_t, int, pid_t); +int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct ipv6_addr *, + pid_t); +struct ipv6_addr *ipv6_iffindaddr(struct interface *, + const struct in6_addr *, int); +int ipv6_hasaddr(const struct interface *); +struct ipv6_addr *ipv6_anyglobal(struct interface *); +int ipv6_findaddrmatch(const struct ipv6_addr *, const struct in6_addr *, + unsigned int); +struct ipv6_addr *ipv6_findaddr(struct dhcpcd_ctx *, + const struct in6_addr *, unsigned int); +struct ipv6_addr *ipv6_findmaskaddr(struct dhcpcd_ctx *, + const struct in6_addr *); +#define ipv6_linklocal(ifp) ipv6_iffindaddr((ifp), NULL, IN6_IFF_NOTUSEABLE) +int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *); +void ipv6_setscope(struct sockaddr_in6 *, unsigned int); +unsigned int ipv6_getscope(const struct sockaddr_in6 *); +struct ipv6_addr *ipv6_newaddr(struct interface *, const struct in6_addr *, + uint8_t, unsigned int); +void ipv6_freeaddr(struct ipv6_addr *); +void ipv6_freedrop(struct interface *, int); +#define ipv6_free(ifp) ipv6_freedrop((ifp), 0) +#define ipv6_drop(ifp) ipv6_freedrop((ifp), 2) + +#ifdef IPV6_MANAGETEMPADDR +struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *, + const struct timespec *); +struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int); +void ipv6_addtempaddrs(struct interface *, const struct timespec *); +void ipv6_regentempaddrs(void *); +#endif + +int ipv6_start(struct interface *); +int ipv6_staticdadcompleted(const struct interface *); +int ipv6_startstatic(struct interface *); +ssize_t ipv6_env(FILE *, const char *, const struct interface *); +void ipv6_ctxfree(struct dhcpcd_ctx *); +bool inet6_getroutes(struct dhcpcd_ctx *, rb_tree_t *); +#endif /* INET6 */ + +#endif /* INET6_H */ Index: src/ipv6.c =================================================================== --- /dev/null +++ src/ipv6.c @@ -0,0 +1,2389 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef HAVE_SYS_BITOPS_H +#include +#else +#include "compat/bitops.h" +#endif + +#ifdef BSD +/* Purely for the ND6_IFF_AUTO_LINKLOCAL #define which is solely used + * to generate our CAN_ADD_LLADDR #define. */ +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#define ELOOP_QUEUE ELOOP_IPV6 +#include "common.h" +#include "if.h" +#include "dhcpcd.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "sa.h" +#include "script.h" + +#ifdef HAVE_MD5_H +# ifndef DEPGEN +# include +# endif +#endif + +#ifdef SHA2_H +# include SHA2_H +#endif + +#ifndef SHA256_DIGEST_LENGTH +# define SHA256_DIGEST_LENGTH 32 +#endif + +#ifdef IPV6_POLLADDRFLAG +# warning kernel does not report IPv6 address flag changes +# warning polling tentative address flags periodically +#endif + +/* Hackery at it's finest. */ +#ifndef s6_addr32 +# ifdef __sun +# define s6_addr32 _S6_un._S6_u32 +# else +# define s6_addr32 __u6_addr.__u6_addr32 +# endif +#endif + +#if defined(HAVE_IN6_ADDR_GEN_MODE_NONE) || defined(ND6_IFF_AUTO_LINKLOCAL) || \ + defined(IFF_NOLINKLOCAL) +/* Only add the LL address if we have a carrier, so DaD works. */ +#define CAN_ADD_LLADDR(ifp) \ + (!((ifp)->options->options & DHCPCD_LINK) || if_is_link_up((ifp))) +#ifdef __sun +/* Although we can add our own LL address, we cannot drop it + * without unplumbing the if which is a lot of code. + * So just keep it for the time being. */ +#define CAN_DROP_LLADDR(ifp) (0) +#else +#define CAN_DROP_LLADDR(ifp) (1) +#endif +#else +/* We have no control over the OS adding the LLADDR, so just let it do it + * as we cannot force our own view on it. */ +#define CAN_ADD_LLADDR(ifp) (0) +#define CAN_DROP_LLADDR(ifp) (0) +#endif + +#ifdef IPV6_MANAGETEMPADDR +static void ipv6_regentempaddr(void *); +#endif + +int +ipv6_init(struct dhcpcd_ctx *ctx) +{ + + if (ctx->ra_routers != NULL) + return 0; + + ctx->ra_routers = malloc(sizeof(*ctx->ra_routers)); + if (ctx->ra_routers == NULL) + return -1; + TAILQ_INIT(ctx->ra_routers); + +#ifndef __sun + ctx->nd_fd = -1; +#endif +#ifdef DHCP6 + ctx->dhcp6_rfd = -1; + ctx->dhcp6_wfd = -1; +#endif + return 0; +} + +static ssize_t +ipv6_readsecret(struct dhcpcd_ctx *ctx) +{ + char line[1024]; + unsigned char *p; + size_t len; + uint32_t r; + + ctx->secret_len = dhcp_read_hwaddr_aton(ctx, &ctx->secret, SECRET); + if (ctx->secret_len != 0) + return (ssize_t)ctx->secret_len; + + if (errno != ENOENT) + logerr("%s: cannot read secret", __func__); + + /* Chaining arc4random should be good enough. + * RFC7217 section 5.1 states the key SHOULD be at least 128 bits. + * To attempt and future proof ourselves, we'll generate a key of + * 512 bits (64 bytes). */ + if (ctx->secret_len < 64) { + if ((ctx->secret = malloc(64)) == NULL) { + logerr(__func__); + return -1; + } + ctx->secret_len = 64; + } + p = ctx->secret; + for (len = 0; len < 512 / NBBY; len += sizeof(r)) { + r = arc4random(); + memcpy(p, &r, sizeof(r)); + p += sizeof(r); + } + + hwaddr_ntoa(ctx->secret, ctx->secret_len, line, sizeof(line)); + len = strlen(line); + if (len < sizeof(line) - 2) { + line[len++] = '\n'; + line[len] = '\0'; + } + if (dhcp_writefile(ctx, SECRET, S_IRUSR, line, len) == -1) { + logerr("%s: cannot write secret", __func__); + ctx->secret_len = 0; + return -1; + } + return (ssize_t)ctx->secret_len; +} + +/* http://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml + * RFC5453 */ +static const struct reslowhigh { + const uint8_t high[8]; + const uint8_t low[8]; +} reslowhigh[] = { + /* RFC4291 + RFC6543 */ + { { 0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x00, 0x00 }, + { 0x02, 0x00, 0x5e, 0xff, 0xfe, 0xff, 0xff, 0xff } }, + /* RFC2526 */ + { { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 }, + { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } } +}; + +static bool +ipv6_reserved(const struct in6_addr *addr) +{ + uint64_t id, low, high; + size_t i; + const struct reslowhigh *r; + + id = be64dec(addr->s6_addr + sizeof(id)); + if (id == 0) /* RFC4291 */ + return 1; + for (i = 0; i < __arraycount(reslowhigh); i++) { + r = &reslowhigh[i]; + low = be64dec(r->low); + high = be64dec(r->high); + if (id >= low && id <= high) + return true; + } + return false; +} + +/* RFC7217 */ +static int +ipv6_makestableprivate1(struct dhcpcd_ctx *ctx, + struct in6_addr *addr, const struct in6_addr *prefix, int prefix_len, + const unsigned char *netiface, size_t netiface_len, + const unsigned char *netid, size_t netid_len, + unsigned short vlanid, + uint32_t *dad_counter) +{ + unsigned char buf[2048], *p, digest[SHA256_DIGEST_LENGTH]; + size_t len, l; + SHA256_CTX sha_ctx; + + if (prefix_len < 0 || prefix_len > 120) { + errno = EINVAL; + return -1; + } + + if (ctx->secret_len == 0) { + if (ipv6_readsecret(ctx) == -1) + return -1; + } + + l = (size_t)(ROUNDUP8(prefix_len) / NBBY); + len = l + netiface_len + netid_len + sizeof(*dad_counter) + + ctx->secret_len; + if (vlanid != 0) + len += sizeof(vlanid); + if (len > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + + for (;; (*dad_counter)++) { + /* Combine all parameters into one buffer */ + p = buf; + memcpy(p, prefix, l); + p += l; + memcpy(p, netiface, netiface_len); + p += netiface_len; + memcpy(p, netid, netid_len); + p += netid_len; + /* Don't use a vlanid if not set. + * This ensures prior versions have the same unique address. */ + if (vlanid != 0) { + memcpy(p, &vlanid, sizeof(vlanid)); + p += sizeof(vlanid); + } + memcpy(p, dad_counter, sizeof(*dad_counter)); + p += sizeof(*dad_counter); + memcpy(p, ctx->secret, ctx->secret_len); + + /* Make an address using the digest of the above. + * RFC7217 Section 5.1 states that we shouldn't use MD5. + * Pity as we use that for HMAC-MD5 which is still deemed OK. + * SHA-256 is recommended */ + SHA256_Init(&sha_ctx); + SHA256_Update(&sha_ctx, buf, len); + SHA256_Final(digest, &sha_ctx); + + p = addr->s6_addr; + memcpy(p, prefix, l); + /* RFC7217 section 5.2 says we need to start taking the id from + * the least significant bit */ + len = sizeof(addr->s6_addr) - l; + memcpy(p + l, digest + (sizeof(digest) - len), len); + + /* Ensure that the Interface ID does not match a reserved one, + * if it does then treat it as a DAD failure. + * RFC7217 section 5.2 */ + if (prefix_len != 64) + break; + if (!ipv6_reserved(addr)) + break; + } + + return 0; +} + +int +ipv6_makestableprivate(struct in6_addr *addr, + const struct in6_addr *prefix, int prefix_len, + const struct interface *ifp, + int *dad_counter) +{ + uint32_t dad; + int r; + + dad = (uint32_t)*dad_counter; + + /* For our implementation, we shall set the hardware address + * as the interface identifier */ + r = ipv6_makestableprivate1(ifp->ctx, addr, prefix, prefix_len, + ifp->hwaddr, ifp->hwlen, + ifp->ssid, ifp->ssid_len, + ifp->vlanid, &dad); + + if (r == 0) + *dad_counter = (int)dad; + return r; +} + +#ifdef IPV6_AF_TEMPORARY +static int +ipv6_maketemporaryaddress(struct in6_addr *addr, + const struct in6_addr *prefix, int prefix_len, + const struct interface *ifp) +{ + struct in6_addr mask; + struct interface *ifpn; + + if (ipv6_mask(&mask, prefix_len) == -1) + return -1; + *addr = *prefix; + +again: + addr->s6_addr32[2] |= (arc4random() & ~mask.s6_addr32[2]); + addr->s6_addr32[3] |= (arc4random() & ~mask.s6_addr32[3]); + + TAILQ_FOREACH(ifpn, ifp->ctx->ifaces, next) { + if (ipv6_iffindaddr(ifpn, addr, 0) != NULL) + break; + } + if (ifpn != NULL) + goto again; + if (ipv6_reserved(addr)) + goto again; + return 0; +} +#endif + +int +ipv6_makeaddr(struct in6_addr *addr, struct interface *ifp, + const struct in6_addr *prefix, int prefix_len, unsigned int flags) +{ + const struct ipv6_addr *ap; + int dad; + + if (prefix_len < 0 || prefix_len > 120) { + errno = EINVAL; + return -1; + } + +#ifdef IPV6_AF_TEMPORARY + if (flags & IPV6_AF_TEMPORARY) + return ipv6_maketemporaryaddress(addr, prefix, prefix_len, ifp); +#else + UNUSED(flags); +#endif + + if (ifp->options->options & DHCPCD_SLAACPRIVATE) { + dad = 0; + if (ipv6_makestableprivate(addr, + prefix, prefix_len, ifp, &dad) == -1) + return -1; + return dad; + } + + if (prefix_len > 64) { + errno = EINVAL; + return -1; + } + if ((ap = ipv6_linklocal(ifp)) == NULL) { + /* We delay a few functions until we get a local-link address + * so this should never be hit. */ + errno = ENOENT; + return -1; + } + + /* Make the address from the first local-link address */ + memcpy(addr, prefix, sizeof(*prefix)); + addr->s6_addr32[2] = ap->addr.s6_addr32[2]; + addr->s6_addr32[3] = ap->addr.s6_addr32[3]; + return 0; +} + +static int +ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len) +{ + struct in6_addr mask; + size_t i; + + if (ipv6_mask(&mask, len) == -1) + return -1; + *prefix = *addr; + for (i = 0; i < sizeof(prefix->s6_addr); i++) + prefix->s6_addr[i] &= mask.s6_addr[i]; + return 0; +} + +int +ipv6_mask(struct in6_addr *mask, int len) +{ + static const unsigned char masks[NBBY] = + { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; + int bytes, bits, i; + + if (len < 0 || len > 128) { + errno = EINVAL; + return -1; + } + + memset(mask, 0, sizeof(*mask)); + bytes = len / NBBY; + bits = len % NBBY; + for (i = 0; i < bytes; i++) + mask->s6_addr[i] = 0xff; + if (bits != 0) { + /* Coverify false positive. + * bytelen cannot be 16 if bitlen is non zero */ + /* coverity[overrun-local] */ + mask->s6_addr[bytes] = masks[bits - 1]; + } + return 0; +} + +uint8_t +ipv6_prefixlen(const struct in6_addr *mask) +{ + int x = 0, y; + const unsigned char *lim, *p; + + lim = (const unsigned char *)mask + sizeof(*mask); + for (p = (const unsigned char *)mask; p < lim; x++, p++) { + if (*p != 0xff) + break; + } + y = 0; + if (p < lim) { + for (y = 0; y < NBBY; y++) { + if ((*p & (0x80 >> y)) == 0) + break; + } + } + + /* + * when the limit pointer is given, do a stricter check on the + * remaining bits. + */ + if (p < lim) { + if (y != 0 && (*p & (0x00ff >> y)) != 0) + return 0; + for (p = p + 1; p < lim; p++) + if (*p != 0) + return 0; + } + + return (uint8_t)(x * NBBY + y); +} + +static void +in6_to_h64(uint64_t *vhigh, uint64_t *vlow, const struct in6_addr *addr) +{ + + *vhigh = be64dec(addr->s6_addr); + *vlow = be64dec(addr->s6_addr + 8); +} + +static void +h64_to_in6(struct in6_addr *addr, uint64_t vhigh, uint64_t vlow) +{ + + be64enc(addr->s6_addr, vhigh); + be64enc(addr->s6_addr + 8, vlow); +} + +int +ipv6_userprefix( + const struct in6_addr *prefix, // prefix from router + short prefix_len, // length of prefix received + uint64_t user_number, // "random" number from user + struct in6_addr *result, // resultant prefix + short result_len) // desired prefix length +{ + uint64_t vh, vl, user_low, user_high; + + if (prefix_len < 1 || prefix_len > 128 || + result_len < 1 || result_len > 128) + { + errno = EINVAL; + return -1; + } + + /* Check that the user_number fits inside result_len less prefix_len */ + if (result_len < prefix_len || + fls64(user_number) > result_len - prefix_len) + { + errno = ERANGE; + return -1; + } + + /* If user_number is zero, just copy the prefix into the result. */ + if (user_number == 0) { + *result = *prefix; + return 0; + } + + /* Shift user_number so it fit's just inside result_len. + * Shifting by 0 or sizeof(user_number) is undefined, + * so we cater for that. */ + if (result_len == 128) { + user_high = 0; + user_low = user_number; + } else if (result_len > 64) { + if (prefix_len >= 64) + user_high = 0; + else + user_high = user_number >> (result_len - prefix_len); + user_low = user_number << (128 - result_len); + } else if (result_len == 64) { + user_high = user_number; + user_low = 0; + } else { + user_high = user_number << (64 - result_len); + user_low = 0; + } + + /* convert to two 64bit host order values */ + in6_to_h64(&vh, &vl, prefix); + + vh |= user_high; + vl |= user_low; + + /* copy back result */ + h64_to_in6(result, vh, vl); + + return 0; +} + +#ifdef IPV6_POLLADDRFLAG +void +ipv6_checkaddrflags(void *arg) +{ + struct ipv6_addr *ia; + int flags; + const char *alias; + + ia = arg; +#ifdef ALIAS_ADDR + alias = ia->alias; +#else + alias = NULL; +#endif + if ((flags = if_addrflags6(ia->iface, &ia->addr, alias)) == -1) { + if (errno != EEXIST && errno != EADDRNOTAVAIL) + logerr("%s: if_addrflags6", __func__); + return; + } + + if (!(flags & IN6_IFF_TENTATIVE)) { + /* Simulate the kernel announcing the new address. */ + ipv6_handleifa(ia->iface->ctx, RTM_NEWADDR, + ia->iface->ctx->ifaces, ia->iface->name, + &ia->addr, ia->prefix_len, flags, 0); + } else { + /* Still tentative? Check again in a bit. */ + eloop_timeout_add_msec(ia->iface->ctx->eloop, + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); + } +} +#endif + +static void +ipv6_deletedaddr(struct ipv6_addr *ia) +{ + +#ifdef DHCP6 +#ifdef PRIVSEP + if (!(ia->iface->ctx->options & DHCPCD_MANAGER)) + ps_inet_closedhcp6(ia); +#elif defined(SMALL) + UNUSED(ia); +#else + /* NOREJECT is set if we delegated exactly the prefix to another + * address. + * This can only be one address, so just clear the flag. + * This should ensure the reject route will be restored. */ + if (ia->delegating_prefix != NULL) + ia->delegating_prefix->flags &= ~IPV6_AF_NOREJECT; +#endif +#else + UNUSED(ia); +#endif +} + +void +ipv6_deleteaddr(struct ipv6_addr *ia) +{ + struct ipv6_state *state; + struct ipv6_addr *ap; + + loginfox("%s: deleting address %s", ia->iface->name, ia->saddr); + if (if_address6(RTM_DELADDR, ia) == -1 && + errno != EADDRNOTAVAIL && errno != ESRCH && + errno != ENXIO && errno != ENODEV) + logerr(__func__); + + ipv6_deletedaddr(ia); + + state = IPV6_STATE(ia->iface); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, &ia->addr)) { + TAILQ_REMOVE(&state->addrs, ap, next); + ipv6_freeaddr(ap); + break; + } + } + +#ifdef ND6_ADVERTISE + /* Advertise the address if it exists on another interface. */ + ipv6nd_advertise(ia); +#endif +} + +static int +ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now) +{ + struct interface *ifp; + uint32_t pltime, vltime; + int loglevel; +#ifdef ND6_ADVERTISE + bool vltime_was_zero = ia->prefix_vltime == 0; +#endif +#ifdef __sun + struct ipv6_state *state; + struct ipv6_addr *ia2; + + /* If we re-add then address on Solaris then the prefix + * route will be scrubbed and re-added. Something might + * be using it, so let's avoid it. */ + if (ia->flags & IPV6_AF_DADCOMPLETED) { + logdebugx("%s: IP address %s already exists", + ia->iface->name, ia->saddr); +#ifdef ND6_ADVERTISE + goto advertise; +#else + return 0; +#endif + } +#endif + + /* Remember the interface of the address. */ + ifp = ia->iface; + + if (!(ia->flags & IPV6_AF_DADCOMPLETED) && + ipv6_iffindaddr(ifp, &ia->addr, IN6_IFF_NOTUSEABLE)) + ia->flags |= IPV6_AF_DADCOMPLETED; + + /* Adjust plftime and vltime based on acquired time */ + pltime = ia->prefix_pltime; + vltime = ia->prefix_vltime; + + if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { + /* We don't want the kernel to expire the address. + * The saved times will be re-applied to the ia + * before exiting this function. */ + ia->prefix_vltime = ia->prefix_pltime = ND6_INFINITE_LIFETIME; + } + + if (timespecisset(&ia->acquired) && + (ia->prefix_pltime != ND6_INFINITE_LIFETIME || + ia->prefix_vltime != ND6_INFINITE_LIFETIME)) + { + uint32_t elapsed; + struct timespec n; + + if (now == NULL) { + clock_gettime(CLOCK_MONOTONIC, &n); + now = &n; + } + elapsed = (uint32_t)eloop_timespec_diff(now, &ia->acquired, + NULL); + if (ia->prefix_pltime != ND6_INFINITE_LIFETIME) { + if (elapsed > ia->prefix_pltime) + ia->prefix_pltime = 0; + else + ia->prefix_pltime -= elapsed; + } + if (ia->prefix_vltime != ND6_INFINITE_LIFETIME) { + if (elapsed > ia->prefix_vltime) + ia->prefix_vltime = 0; + else + ia->prefix_vltime -= elapsed; + } + } + + loglevel = ia->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG; + logmessage(loglevel, "%s: adding %saddress %s", ifp->name, +#ifdef IPV6_AF_TEMPORARY + ia->flags & IPV6_AF_TEMPORARY ? "temporary " : "", +#else + "", +#endif + ia->saddr); + if (ia->prefix_pltime == ND6_INFINITE_LIFETIME && + ia->prefix_vltime == ND6_INFINITE_LIFETIME) + logdebugx("%s: pltime infinity, vltime infinity", + ifp->name); + else if (ia->prefix_pltime == ND6_INFINITE_LIFETIME) + logdebugx("%s: pltime infinity, vltime %"PRIu32" seconds", + ifp->name, ia->prefix_vltime); + else if (ia->prefix_vltime == ND6_INFINITE_LIFETIME) + logdebugx("%s: pltime %"PRIu32"seconds, vltime infinity", + ifp->name, ia->prefix_pltime); + else + logdebugx("%s: pltime %"PRIu32" seconds, vltime %"PRIu32 + " seconds", + ifp->name, ia->prefix_pltime, ia->prefix_vltime); + + if (if_address6(RTM_NEWADDR, ia) == -1) { + logerr(__func__); + /* Restore real pltime and vltime */ + ia->prefix_pltime = pltime; + ia->prefix_vltime = vltime; + return -1; + } + +#ifdef IPV6_MANAGETEMPADDR + /* RFC4941 Section 3.4 */ + if (ia->flags & IPV6_AF_TEMPORARY && + ia->prefix_pltime && + ia->prefix_vltime && + ifp->options->options & DHCPCD_SLAACTEMP) + eloop_timeout_add_sec(ifp->ctx->eloop, + ia->prefix_pltime - REGEN_ADVANCE, + ipv6_regentempaddr, ia); +#endif + + /* Restore real pltime and vltime */ + ia->prefix_pltime = pltime; + ia->prefix_vltime = vltime; + + ia->flags &= ~IPV6_AF_NEW; + ia->flags |= IPV6_AF_ADDED; +#ifndef SMALL + if (ia->delegating_prefix != NULL) + ia->flags |= IPV6_AF_DELEGATED; +#endif + +#ifdef IPV6_POLLADDRFLAG + eloop_timeout_delete(ifp->ctx->eloop, + ipv6_checkaddrflags, ia); + if (!(ia->flags & IPV6_AF_DADCOMPLETED)) { + eloop_timeout_add_msec(ifp->ctx->eloop, + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); + } +#endif + +#ifdef __sun + /* Solaris does not announce new addresses which need DaD + * so we need to take a copy and add it to our list. + * Otherwise aliasing gets confused if we add another + * address during DaD. */ + + state = IPV6_STATE(ifp); + TAILQ_FOREACH(ia2, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr)) + break; + } + if (ia2 == NULL) { + if ((ia2 = malloc(sizeof(*ia2))) == NULL) { + logerr(__func__); + return 0; /* Well, we did add the address */ + } + memcpy(ia2, ia, sizeof(*ia2)); + TAILQ_INSERT_TAIL(&state->addrs, ia2, next); + } +#endif + +#ifdef ND6_ADVERTISE +#ifdef __sun +advertise: +#endif + /* Re-advertise the preferred address to be safe. */ + if (!vltime_was_zero) + ipv6nd_advertise(ia); +#endif + + return 0; +} + +#ifdef ALIAS_ADDR +/* Find the next logical alias address we can use. */ +static int +ipv6_aliasaddr(struct ipv6_addr *ia, struct ipv6_addr **repl) +{ + struct ipv6_state *state; + struct ipv6_addr *iap; + unsigned int lun; + char alias[IF_NAMESIZE]; + + if (ia->alias[0] != '\0') + return 0; + state = IPV6_STATE(ia->iface); + + /* First find an existng address. + * This can happen when dhcpcd restarts as ND and DHCPv6 + * maintain their own lists of addresses. */ + TAILQ_FOREACH(iap, &state->addrs, next) { + if (iap->alias[0] != '\0' && + IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) + { + strlcpy(ia->alias, iap->alias, sizeof(ia->alias)); + return 0; + } + } + + lun = 0; +find_unit: + if (if_makealias(alias, IF_NAMESIZE, ia->iface->name, lun) >= + IF_NAMESIZE) + { + errno = ENOMEM; + return -1; + } + TAILQ_FOREACH(iap, &state->addrs, next) { + if (iap->alias[0] == '\0') + continue; + if (IN6_IS_ADDR_UNSPECIFIED(&iap->addr)) { + /* No address assigned? Lets use it. */ + strlcpy(ia->alias, iap->alias, sizeof(ia->alias)); + if (repl) + *repl = iap; + return 1; + } + if (strcmp(iap->alias, alias) == 0) + break; + } + + if (iap != NULL) { + if (lun == UINT_MAX) { + errno = ERANGE; + return -1; + } + lun++; + goto find_unit; + } + + strlcpy(ia->alias, alias, sizeof(ia->alias)); + return 0; +} +#endif + +int +ipv6_addaddr(struct ipv6_addr *ia, const struct timespec *now) +{ + int r; +#ifdef ALIAS_ADDR + int replaced, blank; + struct ipv6_addr *replaced_ia; + + blank = (ia->alias[0] == '\0'); + if ((replaced = ipv6_aliasaddr(ia, &replaced_ia)) == -1) + return -1; + if (blank) + logdebugx("%s: aliased %s", ia->alias, ia->saddr); +#endif + + if ((r = ipv6_addaddr1(ia, now)) == 0) { +#ifdef ALIAS_ADDR + if (replaced) { + struct ipv6_state *state; + + state = IPV6_STATE(ia->iface); + TAILQ_REMOVE(&state->addrs, replaced_ia, next); + ipv6_freeaddr(replaced_ia); + } +#endif + } + return r; +} + +int +ipv6_findaddrmatch(const struct ipv6_addr *addr, const struct in6_addr *match, + unsigned int flags) +{ + + if (match == NULL) { + if ((addr->flags & + (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) == + (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) + return 1; + } else if (addr->prefix_vltime && + IN6_ARE_ADDR_EQUAL(&addr->addr, match) && + (!flags || addr->flags & flags)) + return 1; + + return 0; +} + +struct ipv6_addr * +ipv6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, unsigned int flags) +{ + struct ipv6_addr *nap; +#ifdef DHCP6 + struct ipv6_addr *dap; +#endif + + nap = ipv6nd_findaddr(ctx, addr, flags); +#ifdef DHCP6 + dap = dhcp6_findaddr(ctx, addr, flags); + if (!dap && !nap) + return NULL; + if (dap && !nap) + return dap; + if (nap && !dap) + return nap; + if (nap->iface->metric < dap->iface->metric) + return nap; + return dap; +#else + return nap; +#endif +} + +int +ipv6_doaddr(struct ipv6_addr *ia, struct timespec *now) +{ + + /* A delegated prefix is not an address. */ + if (ia->flags & IPV6_AF_DELEGATEDPFX) + return 0; + + if (ia->prefix_vltime == 0) { + if (ia->flags & IPV6_AF_ADDED) + ipv6_deleteaddr(ia); + eloop_q_timeout_delete(ia->iface->ctx->eloop, + ELOOP_QUEUE_ALL, NULL, ia); + if (ia->flags & IPV6_AF_REQUEST) { + ia->flags &= ~IPV6_AF_ADDED; + return 0; + } + return -1; + } + + if (ia->flags & IPV6_AF_STALE || + IN6_IS_ADDR_UNSPECIFIED(&ia->addr)) + return 0; + + if (!timespecisset(now)) + clock_gettime(CLOCK_MONOTONIC, now); + ipv6_addaddr(ia, now); + return ia->flags & IPV6_AF_NEW ? 1 : 0; +} + +ssize_t +ipv6_addaddrs(struct ipv6_addrhead *iaddrs) +{ + struct timespec now; + struct ipv6_addr *ia, *ian; + ssize_t i, r; + + i = 0; + timespecclear(&now); + TAILQ_FOREACH_SAFE(ia, iaddrs, next, ian) { + r = ipv6_doaddr(ia, &now); + if (r != 0) + i++; + if (r == -1) { + TAILQ_REMOVE(iaddrs, ia, next); + ipv6_freeaddr(ia); + } + } + return i; +} + +void +ipv6_freeaddr(struct ipv6_addr *ia) +{ + struct eloop *eloop = ia->iface->ctx->eloop; +#ifndef SMALL + struct ipv6_addr *iad; + + /* Forget the reference */ + if (ia->flags & IPV6_AF_DELEGATEDPFX) { + TAILQ_FOREACH(iad, &ia->pd_pfxs, pd_next) { + iad->delegating_prefix = NULL; + } + } else if (ia->delegating_prefix != NULL) { + TAILQ_REMOVE(&ia->delegating_prefix->pd_pfxs, ia, pd_next); + } +#endif + + if (ia->dhcp6_fd != -1) { + close(ia->dhcp6_fd); + eloop_event_delete(eloop, ia->dhcp6_fd); + } + + eloop_q_timeout_delete(eloop, ELOOP_QUEUE_ALL, NULL, ia); + free(ia->na); + free(ia); +} + +void +ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop, + const struct interface *ifd) +{ + struct ipv6_addr *ap, *apn, *apf; + struct timespec now; + +#ifdef SMALL + UNUSED(ifd); +#endif + timespecclear(&now); + TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { +#ifndef SMALL + if (ifd != NULL && + (ap->delegating_prefix == NULL || + ap->delegating_prefix->iface != ifd)) + continue; +#endif + if (drop != 2) + TAILQ_REMOVE(addrs, ap, next); + if (drop && ap->flags & IPV6_AF_ADDED && + (ap->iface->options->options & + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + { + /* Don't drop link-local addresses. */ + if (!IN6_IS_ADDR_LINKLOCAL(&ap->addr) || + CAN_DROP_LLADDR(ap->iface)) + { + if (drop == 2) + TAILQ_REMOVE(addrs, ap, next); + /* Find the same address somewhere else */ + apf = ipv6_findaddr(ap->iface->ctx, &ap->addr, + 0); + if ((apf == NULL || + (apf->iface != ap->iface))) + ipv6_deleteaddr(ap); + if (!(ap->iface->options->options & + DHCPCD_EXITING) && apf) + { + if (!timespecisset(&now)) + clock_gettime(CLOCK_MONOTONIC, + &now); + ipv6_addaddr(apf, &now); + } + if (drop == 2) + ipv6_freeaddr(ap); + } + } + if (drop != 2) + ipv6_freeaddr(ap); + } +} + +static struct ipv6_state * +ipv6_getstate(struct interface *ifp) +{ + struct ipv6_state *state; + + state = IPV6_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_IPV6] = calloc(1, sizeof(*state)); + state = IPV6_STATE(ifp); + if (state == NULL) { + logerr(__func__); + return NULL; + } + TAILQ_INIT(&state->addrs); + TAILQ_INIT(&state->ll_callbacks); + } + return state; +} + +struct ipv6_addr * +ipv6_anyglobal(struct interface *sifp) +{ + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *ia; + bool forwarding; + + /* BSD forwarding is either on or off. + * Linux forwarding is technically the same as it's + * configured by the "all" interface. + * Per interface only affects IsRouter of NA messages. */ +#if defined(PRIVSEP) && (defined(HAVE_PLEDGE) || defined(__linux__)) + if (IN_PRIVSEP(sifp->ctx)) + forwarding = ps_root_ip6forwarding(sifp->ctx, NULL) != 0; + else +#endif + forwarding = ip6_forwarding(NULL) != 0; + + TAILQ_FOREACH(ifp, sifp->ctx->ifaces, next) { + if (ifp != sifp && !forwarding) + continue; + + state = IPV6_STATE(ifp); + if (state == NULL) + continue; + + TAILQ_FOREACH(ia, &state->addrs, next) { + if (IN6_IS_ADDR_LINKLOCAL(&ia->addr)) + continue; + /* Let's be optimistic. + * Any decent OS won't forward or accept traffic + * from/to tentative or detached addresses. */ + if (!(ia->addr_flags & IN6_IFF_DUPLICATED)) + return ia; + } + } + return NULL; +} + +void +ipv6_handleifa(struct dhcpcd_ctx *ctx, + int cmd, struct if_head *ifs, const char *ifname, + const struct in6_addr *addr, uint8_t prefix_len, int addrflags, pid_t pid) +{ + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *ia; + struct ll_callback *cb; + bool anyglobal; + +#ifdef __sun + struct sockaddr_in6 subnet; + + /* Solaris on-link route is an unspecified address! */ + if (IN6_IS_ADDR_UNSPECIFIED(addr)) { + if (if_getsubnet(ctx, ifname, AF_INET6, + &subnet, sizeof(subnet)) == -1) + { + logerr(__func__); + return; + } + addr = &subnet.sin6_addr; + } +#endif + +#if 0 + char dbuf[INET6_ADDRSTRLEN]; + const char *dbp; + + dbp = inet_ntop(AF_INET6, &addr->s6_addr, + dbuf, INET6_ADDRSTRLEN); + loginfox("%s: cmd %d addr %s addrflags %d", + ifname, cmd, dbp, addrflags); +#endif + + if (ifs == NULL) + ifs = ctx->ifaces; + if (ifs == NULL) + return; + if ((ifp = if_find(ifs, ifname)) == NULL) + return; + if ((state = ipv6_getstate(ifp)) == NULL) + return; + anyglobal = ipv6_anyglobal(ifp) != NULL; + + TAILQ_FOREACH(ia, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ia->addr, addr)) + break; + } + + switch (cmd) { + case RTM_DELADDR: + if (ia != NULL) { + TAILQ_REMOVE(&state->addrs, ia, next); +#ifdef ND6_ADVERTISE + /* Advertise the address if it exists on + * another interface. */ + ipv6nd_advertise(ia); +#endif + /* We'll free it at the end of the function. */ + } + break; + case RTM_NEWADDR: + if (ia == NULL) { + ia = ipv6_newaddr(ifp, addr, prefix_len, 0); +#ifdef ALIAS_ADDR + strlcpy(ia->alias, ifname, sizeof(ia->alias)); +#endif + if (if_getlifetime6(ia) == -1) { + /* No support or address vanished. + * Either way, just set a deprecated + * infinite time lifetime and continue. + * This is fine because we only want + * to know this when trying to extend + * temporary addresses. + * As we can't extend infinite, we'll + * create a new temporary address. */ + ia->prefix_pltime = 0; + ia->prefix_vltime = + ND6_INFINITE_LIFETIME; + } + /* This is a minor regression against RFC 4941 + * because the kernel only knows when the + * lifetimes were last updated, not when the + * address was initially created. + * Provided dhcpcd is not restarted, this + * won't be a problem. + * If we don't like it, we can always + * pretend lifetimes are infinite and always + * generate a new temporary address on + * restart. */ + ia->acquired = ia->created; + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + } + ia->addr_flags = addrflags; + ia->flags &= ~IPV6_AF_STALE; +#ifdef IPV6_MANAGETEMPADDR + if (ia->addr_flags & IN6_IFF_TEMPORARY) + ia->flags |= IPV6_AF_TEMPORARY; +#endif + if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) { +#ifdef IPV6_POLLADDRFLAG + if (ia->addr_flags & IN6_IFF_TENTATIVE) { + eloop_timeout_add_msec( + ia->iface->ctx->eloop, + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); + break; + } +#endif + + if (ia->dadcallback) + ia->dadcallback(ia); + + if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) && + !(ia->addr_flags & IN6_IFF_NOTUSEABLE)) + { + /* Now run any callbacks. + * Typically IPv6RS or DHCPv6 */ + while ((cb = + TAILQ_FIRST(&state->ll_callbacks))) + { + TAILQ_REMOVE( + &state->ll_callbacks, + cb, next); + cb->callback(cb->arg); + free(cb); + } + } + } + break; + } + + if (ia == NULL) + return; + + ctx->options &= ~DHCPCD_RTBUILD; + ipv6nd_handleifa(cmd, ia, pid); +#ifdef DHCP6 + dhcp6_handleifa(cmd, ia, pid); +#endif + + /* Done with the ia now, so free it. */ + if (cmd == RTM_DELADDR) + ipv6_freeaddr(ia); + else if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE)) + ia->flags |= IPV6_AF_DADCOMPLETED; + + /* If we've not already called rt_build via the IPv6ND + * or DHCP6 handlers and the existance of any useable + * global address on the interface has changed, + * call rt_build to add/remove the default route. */ + if (ifp->active && + ((ifp->options != NULL && ifp->options->options & DHCPCD_IPV6) || + (ifp->options == NULL && ctx->options & DHCPCD_IPV6)) && + !(ctx->options & DHCPCD_RTBUILD) && + (ipv6_anyglobal(ifp) != NULL) != anyglobal) + rt_build(ctx, AF_INET6); +} + +int +ipv6_hasaddr(const struct interface *ifp) +{ + + if (ipv6nd_iffindaddr(ifp, NULL, 0) != NULL) + return 1; +#ifdef DHCP6 + if (dhcp6_iffindaddr(ifp, NULL, 0) != NULL) + return 1; +#endif + return 0; +} + +struct ipv6_addr * +ipv6_iffindaddr(struct interface *ifp, const struct in6_addr *addr, + int revflags) +{ + struct ipv6_state *state; + struct ipv6_addr *ap; + + state = IPV6_STATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (addr == NULL) { + if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) && + (!revflags || !(ap->addr_flags & revflags))) + return ap; + } else { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr) && + (!revflags || !(ap->addr_flags & revflags))) + return ap; + } + } + } + return NULL; +} + +static struct ipv6_addr * +ipv6_iffindmaskaddr(const struct interface *ifp, const struct in6_addr *addr) +{ + struct ipv6_state *state; + struct ipv6_addr *ap; + struct in6_addr mask; + + state = IPV6_STATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ipv6_mask(&mask, ap->prefix_len) == -1) + continue; + if (IN6_ARE_MASKED_ADDR_EQUAL(&ap->addr, addr, &mask)) + return ap; + } + } + return NULL; +} + +struct ipv6_addr * +ipv6_findmaskaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr) +{ + struct interface *ifp; + struct ipv6_addr *ap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ap = ipv6_iffindmaskaddr(ifp, addr); + if (ap != NULL) + return ap; + } + return NULL; +} + +int +ipv6_addlinklocalcallback(struct interface *ifp, + void (*callback)(void *), void *arg) +{ + struct ipv6_state *state; + struct ll_callback *cb; + + state = ipv6_getstate(ifp); + TAILQ_FOREACH(cb, &state->ll_callbacks, next) { + if (cb->callback == callback && cb->arg == arg) + break; + } + if (cb == NULL) { + cb = malloc(sizeof(*cb)); + if (cb == NULL) { + logerr(__func__); + return -1; + } + cb->callback = callback; + cb->arg = arg; + TAILQ_INSERT_TAIL(&state->ll_callbacks, cb, next); + } + return 0; +} + +static struct ipv6_addr * +ipv6_newlinklocal(struct interface *ifp) +{ + struct ipv6_addr *ia; + struct in6_addr in6; + + memset(&in6, 0, sizeof(in6)); + in6.s6_addr32[0] = htonl(0xfe800000); + ia = ipv6_newaddr(ifp, &in6, 64, 0); + if (ia != NULL) { + ia->prefix_pltime = ND6_INFINITE_LIFETIME; + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + } + return ia; +} + +static const uint8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +static const uint8_t allone[8] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static int +ipv6_addlinklocal(struct interface *ifp) +{ + struct ipv6_state *state; + struct ipv6_addr *ap, *ap2; + int dadcounter; + + if (!(ifp->options->options & DHCPCD_CONFIGURE)) + return 0; + + /* Check sanity before malloc */ + if (!(ifp->options->options & DHCPCD_SLAACPRIVATE)) { + switch (ifp->hwtype) { + case ARPHRD_ETHER: + /* Check for a valid hardware address */ + if (ifp->hwlen != 6 && ifp->hwlen != 8) { + errno = ENOTSUP; + return -1; + } + if (memcmp(ifp->hwaddr, allzero, ifp->hwlen) == 0 || + memcmp(ifp->hwaddr, allone, ifp->hwlen) == 0) + { + errno = EINVAL; + return -1; + } + break; + default: + errno = ENOTSUP; + return -1; + } + } + + state = ipv6_getstate(ifp); + if (state == NULL) + return -1; + + ap = ipv6_newlinklocal(ifp); + if (ap == NULL) + return -1; + + dadcounter = 0; + if (ifp->options->options & DHCPCD_SLAACPRIVATE) { +nextslaacprivate: + if (ipv6_makestableprivate(&ap->addr, + &ap->prefix, ap->prefix_len, ifp, &dadcounter) == -1) + { + free(ap); + return -1; + } + ap->dadcounter = dadcounter; + } else { + memcpy(ap->addr.s6_addr, ap->prefix.s6_addr, 8); + switch (ifp->hwtype) { + case ARPHRD_ETHER: + if (ifp->hwlen == 6) { + ap->addr.s6_addr[ 8] = ifp->hwaddr[0]; + ap->addr.s6_addr[ 9] = ifp->hwaddr[1]; + ap->addr.s6_addr[10] = ifp->hwaddr[2]; + ap->addr.s6_addr[11] = 0xff; + ap->addr.s6_addr[12] = 0xfe; + ap->addr.s6_addr[13] = ifp->hwaddr[3]; + ap->addr.s6_addr[14] = ifp->hwaddr[4]; + ap->addr.s6_addr[15] = ifp->hwaddr[5]; + } else if (ifp->hwlen == 8) + memcpy(&ap->addr.s6_addr[8], ifp->hwaddr, 8); + else { + free(ap); + errno = ENOTSUP; + return -1; + } + break; + } + + /* Sanity check: g bit must not indciate "group" */ + if (EUI64_GROUP(&ap->addr)) { + free(ap); + errno = EINVAL; + return -1; + } + EUI64_TO_IFID(&ap->addr); + } + + /* Do we already have this address? */ + TAILQ_FOREACH(ap2, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, &ap2->addr)) { + if (ap2->addr_flags & IN6_IFF_DUPLICATED) { + if (ifp->options->options & + DHCPCD_SLAACPRIVATE) + { + dadcounter++; + goto nextslaacprivate; + } + free(ap); + errno = EADDRNOTAVAIL; + return -1; + } + + logwarnx("%s: waiting for %s to complete", + ap2->iface->name, ap2->saddr); + free(ap); + errno = EEXIST; + return 0; + } + } + + inet_ntop(AF_INET6, &ap->addr, ap->saddr, sizeof(ap->saddr)); + TAILQ_INSERT_TAIL(&state->addrs, ap, next); + ipv6_addaddr(ap, NULL); + return 1; +} + +static int +ipv6_tryaddlinklocal(struct interface *ifp) +{ + struct ipv6_addr *ia; + + /* We can't assign a link-locak address to this, + * the ppp process has to. */ + if (ifp->flags & IFF_POINTOPOINT) + return 0; + + ia = ipv6_iffindaddr(ifp, NULL, IN6_IFF_DUPLICATED); + if (ia != NULL) { +#ifdef IPV6_POLLADDRFLAG + if (ia->addr_flags & IN6_IFF_TENTATIVE) { + eloop_timeout_add_msec( + ia->iface->ctx->eloop, + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); + } +#endif + return 0; + } + if (!CAN_ADD_LLADDR(ifp)) + return 0; + + return ipv6_addlinklocal(ifp); +} + +void +ipv6_setscope(struct sockaddr_in6 *sin, unsigned int ifindex) +{ + +#ifdef __KAME__ + /* KAME based systems want to store the scope inside the sin6_addr + * for link local addresses */ + if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) { + uint16_t scope = htons((uint16_t)ifindex); + memcpy(&sin->sin6_addr.s6_addr[2], &scope, + sizeof(scope)); + } + sin->sin6_scope_id = 0; +#else + if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) + sin->sin6_scope_id = ifindex; + else + sin->sin6_scope_id = 0; +#endif +} + +unsigned int +ipv6_getscope(const struct sockaddr_in6 *sin) +{ +#ifdef __KAME__ + uint16_t scope; +#endif + + if (!IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) + return 0; +#ifdef __KAME__ + memcpy(&scope, &sin->sin6_addr.s6_addr[2], sizeof(scope)); + return (unsigned int)ntohs(scope); +#else + return (unsigned int)sin->sin6_scope_id; +#endif +} + +struct ipv6_addr * +ipv6_newaddr(struct interface *ifp, const struct in6_addr *addr, + uint8_t prefix_len, unsigned int flags) +{ + struct ipv6_addr *ia, *iaf; + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + bool tempaddr; + int addr_flags; + +#ifdef IPV6_AF_TEMPORARY + tempaddr = flags & IPV6_AF_TEMPORARY; +#else + tempaddr = false; +#endif + + /* If adding a new DHCP / RA derived address, check current flags + * from an existing address. */ + if (tempaddr) + iaf = NULL; + else if (flags & IPV6_AF_AUTOCONF) + iaf = ipv6nd_iffindprefix(ifp, addr, prefix_len); + else + iaf = ipv6_iffindaddr(ifp, addr, 0); + if (iaf != NULL) { + addr_flags = iaf->addr_flags; + flags |= IPV6_AF_ADDED; + } else + addr_flags = IN6_IFF_TENTATIVE; + + ia = calloc(1, sizeof(*ia)); + if (ia == NULL) + goto err; + + ia->iface = ifp; + ia->addr_flags = addr_flags; + ia->flags = IPV6_AF_NEW | flags; + if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE)) + ia->flags |= IPV6_AF_DADCOMPLETED; + ia->prefix_len = prefix_len; + ia->dhcp6_fd = -1; + +#ifndef SMALL + TAILQ_INIT(&ia->pd_pfxs); +#endif + + if (prefix_len == 128) + goto makepfx; + else if (ia->flags & IPV6_AF_AUTOCONF) { + ia->prefix = *addr; + if (iaf != NULL) + memcpy(&ia->addr, &iaf->addr, sizeof(ia->addr)); + else { + ia->dadcounter = ipv6_makeaddr(&ia->addr, ifp, + &ia->prefix, + ia->prefix_len, + ia->flags); + if (ia->dadcounter == -1) + goto err; + } + } else if (ia->flags & IPV6_AF_RAPFX) { + ia->prefix = *addr; +#ifdef __sun + ia->addr = *addr; + cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); + goto paddr; +#else + return ia; +#endif + } else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX)) { + ia->prefix = *addr; + cbp = inet_ntop(AF_INET6, &ia->prefix, buf, sizeof(buf)); + goto paddr; + } else { +makepfx: + ia->addr = *addr; + if (ipv6_makeprefix(&ia->prefix, + &ia->addr, ia->prefix_len) == -1) + goto err; + } + + cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); +paddr: + if (cbp == NULL) + goto err; + snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", cbp, ia->prefix_len); + + return ia; + +err: + logerr(__func__); + free(ia); + return NULL; +} + +static void +ipv6_staticdadcallback(void *arg) +{ + struct ipv6_addr *ia = arg; + int wascompleted; + + wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); + ia->flags |= IPV6_AF_DADCOMPLETED; + if (ia->addr_flags & IN6_IFF_DUPLICATED) + logwarnx("%s: DAD detected %s", ia->iface->name, + ia->saddr); + else if (!wascompleted) { + logdebugx("%s: IPv6 static DAD completed", + ia->iface->name); + } + +#define FINISHED (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED) + if (!wascompleted) { + struct interface *ifp; + struct ipv6_state *state; + + ifp = ia->iface; + state = IPV6_STATE(ifp); + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_STATIC && + (ia->flags & FINISHED) != FINISHED) + { + wascompleted = 1; + break; + } + } + if (!wascompleted) + script_runreason(ifp, "STATIC6"); + } +#undef FINISHED +} + +ssize_t +ipv6_env(FILE *fp, const char *prefix, const struct interface *ifp) +{ + struct ipv6_addr *ia; + + ia = ipv6_iffindaddr(UNCONST(ifp), &ifp->options->req_addr6, + IN6_IFF_NOTUSEABLE); + if (ia == NULL) + return 0; + if (efprintf(fp, "%s_ip6_address=%s", prefix, ia->saddr) == -1) + return -1; + return 1; +} + +int +ipv6_staticdadcompleted(const struct interface *ifp) +{ + const struct ipv6_state *state; + const struct ipv6_addr *ia; + int n; + + if ((state = IPV6_CSTATE(ifp)) == NULL) + return 0; + n = 0; +#define COMPLETED (IPV6_AF_STATIC | IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED) + TAILQ_FOREACH(ia, &state->addrs, next) { + if ((ia->flags & COMPLETED) == COMPLETED && + !(ia->addr_flags & IN6_IFF_NOTUSEABLE)) + n++; + } + return n; +} + +int +ipv6_startstatic(struct interface *ifp) +{ + struct ipv6_addr *ia; + int run_script; + + if (IN6_IS_ADDR_UNSPECIFIED(&ifp->options->req_addr6)) + return 0; + + ia = ipv6_iffindaddr(ifp, &ifp->options->req_addr6, 0); + if (ia != NULL && + (ia->prefix_len != ifp->options->req_prefix_len || + ia->addr_flags & IN6_IFF_NOTUSEABLE)) + { + ipv6_deleteaddr(ia); + ia = NULL; + } + if (ia == NULL) { + struct ipv6_state *state; + + ia = ipv6_newaddr(ifp, &ifp->options->req_addr6, + ifp->options->req_prefix_len, 0); + if (ia == NULL) + return -1; + state = IPV6_STATE(ifp); + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + run_script = 0; + } else + run_script = 1; + ia->flags |= IPV6_AF_STATIC | IPV6_AF_ONLINK; + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + ia->prefix_pltime = ND6_INFINITE_LIFETIME; + ia->dadcallback = ipv6_staticdadcallback; + ipv6_addaddr(ia, NULL); + rt_build(ifp->ctx, AF_INET6); + if (run_script) + script_runreason(ifp, "STATIC6"); + return 1; +} + +/* Ensure the interface has a link-local address */ +int +ipv6_start(struct interface *ifp) +{ +#ifdef IPV6_POLLADDRFLAG + struct ipv6_state *state; + + /* We need to update the address flags. */ + if ((state = IPV6_STATE(ifp)) != NULL) { + struct ipv6_addr *ia; + const char *alias; + int flags; + + TAILQ_FOREACH(ia, &state->addrs, next) { +#ifdef ALIAS_ADDR + alias = ia->alias; +#else + alias = NULL; +#endif + flags = if_addrflags6(ia->iface, &ia->addr, alias); + if (flags != -1) + ia->addr_flags = flags; + } + } +#endif + + if (ipv6_tryaddlinklocal(ifp) == -1) + return -1; + + return 0; +} + +void +ipv6_freedrop(struct interface *ifp, int drop) +{ + struct ipv6_state *state; + struct ll_callback *cb; + + if (ifp == NULL) + return; + + if ((state = IPV6_STATE(ifp)) == NULL) + return; + + /* If we got here, we can get rid of any LL callbacks. */ + while ((cb = TAILQ_FIRST(&state->ll_callbacks))) { + TAILQ_REMOVE(&state->ll_callbacks, cb, next); + free(cb); + } + + ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL); + if (drop) { + if (ifp->ctx->ra_routers != NULL) + rt_build(ifp->ctx, AF_INET6); + } else { + /* Because we need to cache the addresses we don't control, + * we only free the state on when NOT dropping addresses. */ + free(state); + ifp->if_data[IF_DATA_IPV6] = NULL; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + } +} + +void +ipv6_ctxfree(struct dhcpcd_ctx *ctx) +{ + + free(ctx->ra_routers); + free(ctx->secret); +} + +int +ipv6_handleifa_addrs(int cmd, + struct ipv6_addrhead *addrs, const struct ipv6_addr *addr, pid_t pid) +{ + struct ipv6_addr *ia, *ian; + uint8_t found, alldadcompleted; + + alldadcompleted = 1; + found = 0; + TAILQ_FOREACH_SAFE(ia, addrs, next, ian) { + if (!IN6_ARE_ADDR_EQUAL(&addr->addr, &ia->addr)) { + if (ia->flags & IPV6_AF_ADDED && + !(ia->flags & IPV6_AF_DADCOMPLETED)) + alldadcompleted = 0; + continue; + } + switch (cmd) { + case RTM_DELADDR: + if (ia->flags & IPV6_AF_ADDED) { + logwarnx("%s: pid %d deleted address %s", + ia->iface->name, pid, ia->saddr); + ia->flags &= ~IPV6_AF_ADDED; + } + ipv6_deletedaddr(ia); + if (ia->flags & IPV6_AF_DELEGATED) { + TAILQ_REMOVE(addrs, ia, next); + ipv6_freeaddr(ia); + } + break; + case RTM_NEWADDR: + ia->addr_flags = addr->addr_flags; + /* Safety - ignore tentative announcements */ + if (ia->addr_flags & + (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) + break; + if ((ia->flags & IPV6_AF_DADCOMPLETED) == 0) { + found++; + if (ia->dadcallback) + ia->dadcallback(ia); + /* We need to set this here in-case the + * dadcallback function checks it */ + ia->flags |= IPV6_AF_DADCOMPLETED; + } + break; + } + } + + return alldadcompleted ? found : 0; +} + +#ifdef IPV6_MANAGETEMPADDR +static void +ipv6_regen_desync(struct interface *ifp, bool force) +{ + struct ipv6_state *state; + unsigned int max; + + state = IPV6_STATE(ifp); + + /* RFC4941 Section 5 states that DESYNC_FACTOR must never be + * greater than TEMP_VALID_LIFETIME - REGEN_ADVANCE. + * I believe this is an error and it should be never be greater than + * TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE. */ + max = TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE; + if (state->desync_factor && !force && state->desync_factor < max) + return; + if (state->desync_factor == 0) + state->desync_factor = + arc4random_uniform(MIN(MAX_DESYNC_FACTOR, max)); + max = TEMP_PREFERRED_LIFETIME - state->desync_factor - REGEN_ADVANCE; + eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempaddrs, ifp); +} + +/* RFC4941 Section 3.3.7 */ +static void +ipv6_tempdadcallback(void *arg) +{ + struct ipv6_addr *ia = arg; + + if (ia->addr_flags & IN6_IFF_DUPLICATED) { + struct ipv6_addr *ia1; + struct timespec tv; + + if (++ia->dadcounter == TEMP_IDGEN_RETRIES) { + logerrx("%s: too many duplicate temporary addresses", + ia->iface->name); + return; + } + clock_gettime(CLOCK_MONOTONIC, &tv); + if ((ia1 = ipv6_createtempaddr(ia, &tv)) == NULL) + logerr(__func__); + else + ia1->dadcounter = ia->dadcounter; + ipv6_deleteaddr(ia); + if (ia1) + ipv6_addaddr(ia1, &ia1->acquired); + } +} + +struct ipv6_addr * +ipv6_createtempaddr(struct ipv6_addr *ia0, const struct timespec *now) +{ + struct ipv6_state *state; + struct interface *ifp = ia0->iface; + struct ipv6_addr *ia; + + ia = ipv6_newaddr(ifp, &ia0->prefix, ia0->prefix_len, + IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY); + if (ia == NULL) + return NULL; + + ia->dadcallback = ipv6_tempdadcallback; + ia->created = ia->acquired = now ? *now : ia0->acquired; + + /* Ensure desync is still valid */ + ipv6_regen_desync(ifp, false); + + /* RFC4941 Section 3.3.4 */ + state = IPV6_STATE(ia->iface); + ia->prefix_pltime = MIN(ia0->prefix_pltime, + TEMP_PREFERRED_LIFETIME - state->desync_factor); + ia->prefix_vltime = MIN(ia0->prefix_vltime, TEMP_VALID_LIFETIME); + if (ia->prefix_pltime <= REGEN_ADVANCE || + ia->prefix_pltime > ia0->prefix_vltime) + { + errno = EINVAL; + free(ia); + return NULL; + } + + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + return ia; +} + +struct ipv6_addr * +ipv6_settemptime(struct ipv6_addr *ia, int flags) +{ + struct ipv6_state *state; + struct ipv6_addr *ap, *first; + + state = IPV6_STATE(ia->iface); + first = NULL; + TAILQ_FOREACH_REVERSE(ap, &state->addrs, ipv6_addrhead, next) { + if (ap->flags & IPV6_AF_TEMPORARY && + ap->prefix_pltime && + IN6_ARE_ADDR_EQUAL(&ia->prefix, &ap->prefix)) + { + unsigned int max, ext; + + if (flags == 0) { + if (ap->prefix_pltime - + (uint32_t)(ia->acquired.tv_sec - + ap->acquired.tv_sec) + < REGEN_ADVANCE) + continue; + + return ap; + } + + if (!(ap->flags & IPV6_AF_ADDED)) + ap->flags |= IPV6_AF_NEW | IPV6_AF_AUTOCONF; + ap->flags &= ~IPV6_AF_STALE; + + /* RFC4941 Section 3.4 + * Deprecated prefix, deprecate the temporary address */ + if (ia->prefix_pltime == 0) { + ap->prefix_pltime = 0; + goto valid; + } + + /* Ensure desync is still valid */ + ipv6_regen_desync(ap->iface, false); + + /* RFC4941 Section 3.3.2 + * Extend temporary times, but ensure that they + * never last beyond the system limit. */ + ext = (unsigned int)ia->acquired.tv_sec + + ia->prefix_pltime; + max = (unsigned int)(ap->created.tv_sec + + TEMP_PREFERRED_LIFETIME - + state->desync_factor); + if (ext < max) + ap->prefix_pltime = ia->prefix_pltime; + else + ap->prefix_pltime = + (uint32_t)(max - ia->acquired.tv_sec); + +valid: + ext = (unsigned int)ia->acquired.tv_sec + + ia->prefix_vltime; + max = (unsigned int)(ap->created.tv_sec + + TEMP_VALID_LIFETIME); + if (ext < max) + ap->prefix_vltime = ia->prefix_vltime; + else + ap->prefix_vltime = + (uint32_t)(max - ia->acquired.tv_sec); + + /* Just extend the latest matching prefix */ + ap->acquired = ia->acquired; + + /* If extending return the last match as + * it's the most current. + * If deprecating, deprecate any other addresses we + * may have, although this should not be needed */ + if (ia->prefix_pltime) + return ap; + if (first == NULL) + first = ap; + } + } + return first; +} + +void +ipv6_addtempaddrs(struct interface *ifp, const struct timespec *now) +{ + struct ipv6_state *state; + struct ipv6_addr *ia; + + state = IPV6_STATE(ifp); + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_TEMPORARY && + !(ia->flags & IPV6_AF_STALE)) + ipv6_addaddr(ia, now); + } +} + +static void +ipv6_regentempaddr0(struct ipv6_addr *ia, struct timespec *tv) +{ + struct ipv6_addr *ia1; + + logdebugx("%s: regen temp addr %s", ia->iface->name, ia->saddr); + ia1 = ipv6_createtempaddr(ia, tv); + if (ia1) + ipv6_addaddr(ia1, tv); + else + logerr(__func__); +} + +static void +ipv6_regentempaddr(void *arg) +{ + struct timespec tv; + + clock_gettime(CLOCK_MONOTONIC, &tv); + ipv6_regentempaddr0(arg, &tv); +} + +void +ipv6_regentempaddrs(void *arg) +{ + struct interface *ifp = arg; + struct timespec tv; + struct ipv6_state *state; + struct ipv6_addr *ia; + + state = IPV6_STATE(ifp); + if (state == NULL) + return; + + ipv6_regen_desync(ifp, true); + + clock_gettime(CLOCK_MONOTONIC, &tv); + + /* Mark addresses for regen so we don't infinite loop. */ + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_TEMPORARY && + ia->flags & IPV6_AF_ADDED && + !(ia->flags & IPV6_AF_STALE)) + ia->flags |= IPV6_AF_REGEN; + else + ia->flags &= ~IPV6_AF_REGEN; + } + + /* Now regen temp addrs */ + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_REGEN) { + ipv6_regentempaddr0(ia, &tv); + ia->flags &= ~IPV6_AF_REGEN; + } + } +} +#endif /* IPV6_MANAGETEMPADDR */ + +void +ipv6_markaddrsstale(struct interface *ifp, unsigned int flags) +{ + struct ipv6_state *state; + struct ipv6_addr *ia; + + state = IPV6_STATE(ifp); + if (state == NULL) + return; + + TAILQ_FOREACH(ia, &state->addrs, next) { + if (flags == 0 || ia->flags & flags) + ia->flags |= IPV6_AF_STALE; + } +} + +void +ipv6_deletestaleaddrs(struct interface *ifp) +{ + struct ipv6_state *state; + struct ipv6_addr *ia, *ia1; + + state = IPV6_STATE(ifp); + if (state == NULL) + return; + + TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) { + if (ia->flags & IPV6_AF_STALE) + ipv6_handleifa(ifp->ctx, RTM_DELADDR, + ifp->ctx->ifaces, ifp->name, + &ia->addr, ia->prefix_len, 0, getpid()); + } +} + + +static struct rt * +inet6_makeroute(struct interface *ifp, const struct ra *rap) +{ + struct rt *rt; + + if ((rt = rt_new(ifp)) == NULL) + return NULL; + +#ifdef HAVE_ROUTE_METRIC + rt->rt_metric = ifp->metric; +#endif + if (rap != NULL) + rt->rt_mtu = rap->mtu; + return rt; +} + +static struct rt * +inet6_makeprefix(struct interface *ifp, const struct ra *rap, + const struct ipv6_addr *addr) +{ + struct rt *rt; + struct in6_addr netmask; + + if (addr == NULL || addr->prefix_len > 128) { + errno = EINVAL; + return NULL; + } + + /* There is no point in trying to manage a /128 prefix, + * ones without a lifetime. */ + if (addr->prefix_len == 128 || addr->prefix_vltime == 0) + return NULL; + + /* Don't install a reject route when not creating bigger prefixes. */ + if (addr->flags & IPV6_AF_NOREJECT) + return NULL; + + /* This address is the delegated prefix, so add a reject route for + * it via the loopback interface. */ + if (addr->flags & IPV6_AF_DELEGATEDPFX) { + struct interface *lo0; + + TAILQ_FOREACH(lo0, ifp->ctx->ifaces, next) { + if (lo0->flags & IFF_LOOPBACK) + break; + } + if (lo0 == NULL) + logwarnx("cannot find a loopback interface " + "to reject via"); + else + ifp = lo0; + } + + if ((rt = inet6_makeroute(ifp, rap)) == NULL) + return NULL; + + sa_in6_init(&rt->rt_dest, &addr->prefix); + ipv6_mask(&netmask, addr->prefix_len); + sa_in6_init(&rt->rt_netmask, &netmask); + if (addr->flags & IPV6_AF_DELEGATEDPFX) { + rt->rt_flags |= RTF_REJECT; + /* Linux does not like a gateway for a reject route. */ +#ifndef __linux__ + sa_in6_init(&rt->rt_gateway, &in6addr_loopback); +#endif + } else if (!(addr->flags & IPV6_AF_ONLINK)) + sa_in6_init(&rt->rt_gateway, &rap->from); + else + rt->rt_gateway.sa_family = AF_UNSPEC; + sa_in6_init(&rt->rt_ifa, &addr->addr); + return rt; +} + +static struct rt * +inet6_makerouter(struct ra *rap) +{ + struct rt *rt; + + if ((rt = inet6_makeroute(rap->iface, rap)) == NULL) + return NULL; + sa_in6_init(&rt->rt_dest, &in6addr_any); + sa_in6_init(&rt->rt_netmask, &in6addr_any); + sa_in6_init(&rt->rt_gateway, &rap->from); + return rt; +} + +#define RT_IS_DEFAULT(rtp) \ + (IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) && \ + IN6_ARE_ADDR_EQUAL(&((rtp)->mask), &in6addr_any)) + +static int +inet6_staticroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx) +{ + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *ia; + struct rt *rt; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if ((state = IPV6_STATE(ifp)) == NULL) + continue; + TAILQ_FOREACH(ia, &state->addrs, next) { + if ((ia->flags & (IPV6_AF_ADDED | IPV6_AF_STATIC)) == + (IPV6_AF_ADDED | IPV6_AF_STATIC)) + { + rt = inet6_makeprefix(ifp, NULL, ia); + if (rt) + rt_proto_add(routes, rt); + } + } + } + return 0; +} + +static int +inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx) +{ + struct rt *rt; + struct ra *rap; + const struct ipv6_addr *addr; + + if (ctx->ra_routers == NULL) + return 0; + + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + if (rap->expired) + continue; + TAILQ_FOREACH(addr, &rap->addrs, next) { + if (addr->prefix_vltime == 0) + continue; + rt = inet6_makeprefix(rap->iface, rap, addr); + if (rt) { + rt->rt_dflags |= RTDF_RA; +#ifdef HAVE_ROUTE_PREF + rt->rt_pref = ipv6nd_rtpref(rap); +#endif + rt_proto_add(routes, rt); + } + } + if (rap->lifetime == 0) + continue; + if (ipv6_anyglobal(rap->iface) == NULL) + continue; + rt = inet6_makerouter(rap); + if (rt == NULL) + continue; + rt->rt_dflags |= RTDF_RA; +#ifdef HAVE_ROUTE_PREF + rt->rt_pref = ipv6nd_rtpref(rap); +#endif + rt_proto_add(routes, rt); + } + return 0; +} + +#ifdef DHCP6 +static int +inet6_dhcproutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx, + enum DH6S dstate) +{ + struct interface *ifp; + const struct dhcp6_state *d6_state; + const struct ipv6_addr *addr; + struct rt *rt; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + d6_state = D6_CSTATE(ifp); + if (d6_state && d6_state->state == dstate) { + TAILQ_FOREACH(addr, &d6_state->addrs, next) { + rt = inet6_makeprefix(ifp, NULL, addr); + if (rt == NULL) + continue; + rt->rt_dflags |= RTDF_DHCP; + rt_proto_add(routes, rt); + } + } + } + return 0; +} +#endif + +bool +inet6_getroutes(struct dhcpcd_ctx *ctx, rb_tree_t *routes) +{ + + /* Should static take priority? */ + if (inet6_staticroutes(routes, ctx) == -1) + return false; + + /* First add reachable routers and their prefixes */ + if (inet6_raroutes(routes, ctx) == -1) + return false; + +#ifdef DHCP6 + /* We have no way of knowing if prefixes added by DHCP are reachable + * or not, so we have to assume they are. + * Add bound before delegated so we can prefer interfaces better. */ + if (inet6_dhcproutes(routes, ctx, DH6S_BOUND) == -1) + return false; + if (inet6_dhcproutes(routes, ctx, DH6S_DELEGATED) == -1) + return false; +#endif + + return true; +} Index: src/ipv6nd.h =================================================================== --- /dev/null +++ src/ipv6nd.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - IPv6 ND handling + * 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. + */ + +#ifndef IPV6ND_H +#define IPV6ND_H + +#ifdef INET6 + +#include + +#include "config.h" +#include "dhcpcd.h" +#include "ipv6.h" + +struct ra { + TAILQ_ENTRY(ra) next; + struct interface *iface; + struct in6_addr from; + char sfrom[INET6_ADDRSTRLEN]; + uint8_t *data; + size_t data_len; + struct timespec acquired; + unsigned char flags; + uint32_t lifetime; + uint32_t reachable; + uint32_t retrans; + uint32_t mtu; + uint8_t hoplimit; + struct ipv6_addrhead addrs; + bool hasdns; + bool expired; + bool willexpire; + bool doexpire; + bool isreachable; +}; + +TAILQ_HEAD(ra_head, ra); + +struct rs_state { + struct nd_router_solicit *rs; + size_t rslen; + int rsprobes; + uint32_t retrans; +#ifdef __sun + int nd_fd; +#endif +}; + +#define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND]) +#define RS_CSTATE(a) ((const struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND]) +#define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a))) + +#ifndef MAX_RTR_SOLICITATION_DELAY +#define MAX_RTR_SOLICITATION_DELAY 1 /* seconds */ +#define MAX_UNICAST_SOLICIT 3 /* 3 transmissions */ +#define RTR_SOLICITATION_INTERVAL 4 /* seconds */ +#define MAX_RTR_SOLICITATIONS 3 /* times */ +#define MAX_NEIGHBOR_ADVERTISEMENT 3 /* 3 transmissions */ + +#ifndef IPV6_DEFHLIM +#define IPV6_DEFHLIM 64 +#endif +#endif + +/* On carrier up, expire known routers after RTR_CARRIER_EXPIRE seconds. */ +#define RTR_CARRIER_EXPIRE \ + (MAX_RTR_SOLICITATION_DELAY + \ + (MAX_RTR_SOLICITATIONS + 1) * \ + RTR_SOLICITATION_INTERVAL) + +#define MAX_REACHABLE_TIME 3600000 /* milliseconds */ +#define REACHABLE_TIME 30000 /* milliseconds */ +#define RETRANS_TIMER 1000 /* milliseconds */ +#define DELAY_FIRST_PROBE_TIME 5 /* seconds */ + +int ipv6nd_open(bool); +#ifdef __sun +int ipv6nd_openif(struct interface *); +#endif +void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *); +int ipv6nd_rtpref(struct ra *); +void ipv6nd_printoptions(const struct dhcpcd_ctx *, + const struct dhcp_opt *, size_t); +void ipv6nd_startrs(struct interface *); +ssize_t ipv6nd_env(FILE *, const struct interface *); +const struct ipv6_addr *ipv6nd_iffindaddr(const struct interface *ifp, + const struct in6_addr *addr, unsigned int flags); +struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *, + const struct in6_addr *, unsigned int); +struct ipv6_addr *ipv6nd_iffindprefix(struct interface *, + const struct in6_addr *, uint8_t); +ssize_t ipv6nd_free(struct interface *); +void ipv6nd_expirera(void *arg); +bool ipv6nd_hasralifetime(const struct interface *, bool); +#define ipv6nd_hasra(i) ipv6nd_hasralifetime((i), false) +bool ipv6nd_hasradhcp(const struct interface *, bool); +void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t); +int ipv6nd_dadcompleted(const struct interface *); +void ipv6nd_advertise(struct ipv6_addr *); +void ipv6nd_startexpire(struct interface *); +void ipv6nd_drop(struct interface *); +void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, bool); +#endif /* INET6 */ + +#endif /* IPV6ND_H */ Index: src/ipv6nd.c =================================================================== --- /dev/null +++ src/ipv6nd.c @@ -0,0 +1,2075 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - IPv6 ND handling + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ELOOP_QUEUE ELOOP_IPV6ND +#include "common.h" +#include "dhcpcd.h" +#include "dhcp-common.h" +#include "dhcp6.h" +#include "eloop.h" +#include "if.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "route.h" +#include "script.h" + +/* Debugging Router Solicitations is a lot of spam, so disable it */ +//#define DEBUG_RS + +#ifndef ND_RA_FLAG_HOME_AGENT +#define ND_RA_FLAG_HOME_AGENT 0x20 /* Home Agent flag in RA */ +#endif +#ifndef ND_RA_FLAG_PROXY +#define ND_RA_FLAG_PROXY 0x04 /* Proxy */ +#endif +#ifndef ND_OPT_PI_FLAG_ROUTER +#define ND_OPT_PI_FLAG_ROUTER 0x20 /* Router flag in PI */ +#endif + +#ifndef ND_OPT_RDNSS +#define ND_OPT_RDNSS 25 +struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ + uint8_t nd_opt_rdnss_type; + uint8_t nd_opt_rdnss_len; + uint16_t nd_opt_rdnss_reserved; + uint32_t nd_opt_rdnss_lifetime; + /* followed by list of IP prefixes */ +}; +__CTASSERT(sizeof(struct nd_opt_rdnss) == 8); +#endif + +#ifndef ND_OPT_DNSSL +#define ND_OPT_DNSSL 31 +struct nd_opt_dnssl { /* DNSSL option RFC 6106 */ + uint8_t nd_opt_dnssl_type; + uint8_t nd_opt_dnssl_len; + uint16_t nd_opt_dnssl_reserved; + uint32_t nd_opt_dnssl_lifetime; + /* followed by list of DNS servers */ +}; +__CTASSERT(sizeof(struct nd_opt_rdnss) == 8); +#endif + +/* Impossible options, so we can easily add extras */ +#define _ND_OPT_PREFIX_ADDR 255 + 1 + +/* Minimal IPv6 MTU */ +#ifndef IPV6_MMTU +#define IPV6_MMTU 1280 +#endif + +#ifndef ND_RA_FLAG_RTPREF_HIGH +#define ND_RA_FLAG_RTPREF_MASK 0x18 +#define ND_RA_FLAG_RTPREF_HIGH 0x08 +#define ND_RA_FLAG_RTPREF_MEDIUM 0x00 +#define ND_RA_FLAG_RTPREF_LOW 0x18 +#define ND_RA_FLAG_RTPREF_RSV 0x10 +#endif + +#define EXPIRED_MAX 5 /* Remember 5 expired routers to avoid + logspam. */ + +#define MIN_RANDOM_FACTOR 500 /* millisecs */ +#define MAX_RANDOM_FACTOR 1500 /* millisecs */ +#define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */ +#define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */ + +#if BYTE_ORDER == BIG_ENDIAN +#define IPV6_ADDR_INT32_ONE 1 +#define IPV6_ADDR_INT16_MLL 0xff02 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define IPV6_ADDR_INT32_ONE 0x01000000 +#define IPV6_ADDR_INT16_MLL 0x02ff +#endif + +/* Debugging Neighbor Solicitations is a lot of spam, so disable it */ +//#define DEBUG_NS +// + +static void ipv6nd_handledata(void *); + +/* + * Android ships buggy ICMP6 filter headers. + * Supply our own until they fix their shit. + * References: + * https://android-review.googlesource.com/#/c/58438/ + * http://code.google.com/p/android/issues/original?id=32621&seq=24 + */ +#ifdef __ANDROID__ +#undef ICMP6_FILTER_WILLPASS +#undef ICMP6_FILTER_WILLBLOCK +#undef ICMP6_FILTER_SETPASS +#undef ICMP6_FILTER_SETBLOCK +#undef ICMP6_FILTER_SETPASSALL +#undef ICMP6_FILTER_SETBLOCKALL +#define ICMP6_FILTER_WILLPASS(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) +#define ICMP6_FILTER_WILLBLOCK(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) +#define ICMP6_FILTER_SETPASS(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31)))) +#define ICMP6_FILTER_SETBLOCK(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31)))) +#define ICMP6_FILTER_SETPASSALL(filterp) \ + memset(filterp, 0, sizeof(struct icmp6_filter)); +#define ICMP6_FILTER_SETBLOCKALL(filterp) \ + memset(filterp, 0xff, sizeof(struct icmp6_filter)); +#endif + +/* Support older systems with different defines */ +#if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT) +#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT +#endif +#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#endif + +/* Handy defines */ +#define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra), 0) +#define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra), 1) + +void +ipv6nd_printoptions(const struct dhcpcd_ctx *ctx, + const struct dhcp_opt *opts, size_t opts_len) +{ + size_t i, j; + const struct dhcp_opt *opt, *opt2; + int cols; + + for (i = 0, opt = ctx->nd_opts; + i < ctx->nd_opts_len; i++, opt++) + { + for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) + if (opt2->option == opt->option) + break; + if (j == opts_len) { + cols = printf("%03d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } + } + for (i = 0, opt = opts; i < opts_len; i++, opt++) { + cols = printf("%03d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } +} + +int +ipv6nd_open(bool recv) +{ + int fd, on; + struct icmp6_filter filt; + + fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6); + if (fd == -1) + return -1; + + ICMP6_FILTER_SETBLOCKALL(&filt); + + /* RFC4861 4.1 */ + on = 255; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &on, sizeof(on)) == -1) + goto eexit; + + if (recv) { + on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof(on)) == -1) + goto eexit; + + on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, + &on, sizeof(on)) == -1) + goto eexit; + + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); + +#ifdef SO_RERROR + on = 1; + if (setsockopt(fd, SOL_SOCKET, SO_RERROR, + &on, sizeof(on)) == -1) + goto eexit; +#endif + } + + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, + &filt, sizeof(filt)) == -1) + goto eexit; + + return fd; + +eexit: + close(fd); + return -1; +} + +#ifdef __sun +int +ipv6nd_openif(struct interface *ifp) +{ + int fd; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, + .ipv6mr_interface = ifp->index + }; + struct rs_state *state = RS_STATE(ifp); + uint_t ifindex = ifp->index; + + if (state->nd_fd != -1) + return state->nd_fd; + + fd = ipv6nd_open(true); + if (fd == -1) + return -1; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, + &ifindex, sizeof(ifindex)) == -1) + { + close(fd); + return -1; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq, sizeof(mreq)) == -1) + { + close(fd); + return -1; + } + + state->nd_fd = fd; + eloop_event_add(ifp->ctx->eloop, fd, ipv6nd_handledata, ifp); + return fd; +} +#endif + +static int +ipv6nd_makersprobe(struct interface *ifp) +{ + struct rs_state *state; + struct nd_router_solicit *rs; + + state = RS_STATE(ifp); + free(state->rs); + state->rslen = sizeof(*rs); + if (ifp->hwlen != 0) + state->rslen += (size_t)ROUNDUP8(ifp->hwlen + 2); + state->rs = calloc(1, state->rslen); + if (state->rs == NULL) + return -1; + rs = state->rs; + rs->nd_rs_type = ND_ROUTER_SOLICIT; + //rs->nd_rs_code = 0; + //rs->nd_rs_cksum = 0; + //rs->nd_rs_reserved = 0; + + if (ifp->hwlen != 0) { + struct nd_opt_hdr *nd; + + nd = (struct nd_opt_hdr *)(state->rs + 1); + nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + nd->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); + memcpy(nd + 1, ifp->hwaddr, ifp->hwlen); + } + return 0; +} + +static void +ipv6nd_sendrsprobe(void *arg) +{ + struct interface *ifp = arg; + struct rs_state *state = RS_STATE(ifp); + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT, + .sin6_scope_id = ifp->index, + }; + struct iovec iov = { .iov_base = state->rs, .iov_len = state->rslen }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &dst, .msg_namelen = sizeof(dst), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + struct cmsghdr *cm; + struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; + int s; +#ifndef __sun + struct dhcpcd_ctx *ctx = ifp->ctx; +#endif + + if (ipv6_linklocal(ifp) == NULL) { + logdebugx("%s: delaying Router Solicitation for LL address", + ifp->name); + ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp); + return; + } + +#ifdef HAVE_SA_LEN + dst.sin6_len = sizeof(dst); +#endif + + /* Set the outbound interface */ + cm = CMSG_FIRSTHDR(&msg); + if (cm == NULL) /* unlikely */ + return; + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(pi)); + memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); + + logdebugx("%s: sending Router Solicitation", ifp->name); +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_inet_sendnd(ifp, &msg) == -1) + logerr(__func__); + goto sent; + } +#endif +#ifdef __sun + if (state->nd_fd == -1) { + if (ipv6nd_openif(ifp) == -1) { + logerr(__func__); + return; + } + } + s = state->nd_fd; +#else + if (ctx->nd_fd == -1) { + ctx->nd_fd = ipv6nd_open(true); + if (ctx->nd_fd == -1) { + logerr(__func__); + return; + } + eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx); + } + s = ifp->ctx->nd_fd; +#endif + if (sendmsg(s, &msg, 0) == -1) { + logerr(__func__); + /* Allow IPv6ND to continue .... at most a few errors + * would be logged. + * Generally the error is ENOBUFS when struggling to + * associate with an access point. */ + } + +#ifdef PRIVSEP +sent: +#endif + if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) + eloop_timeout_add_sec(ifp->ctx->eloop, + RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); + else + logwarnx("%s: no IPv6 Routers available", ifp->name); +} + +#ifdef ND6_ADVERTISE +static void +ipv6nd_sendadvertisement(void *arg) +{ + struct ipv6_addr *ia = arg; + struct interface *ifp = ia->iface; + struct dhcpcd_ctx *ctx = ifp->ctx; + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, + .sin6_scope_id = ifp->index, + }; + struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &dst, .msg_namelen = sizeof(dst), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + struct cmsghdr *cm; + struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; + const struct rs_state *state = RS_CSTATE(ifp); + int s; + + if (state == NULL || !if_is_link_up(ifp)) + goto freeit; + +#ifdef SIN6_LEN + dst.sin6_len = sizeof(dst); +#endif + + /* Set the outbound interface. */ + cm = CMSG_FIRSTHDR(&msg); + assert(cm != NULL); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(pi)); + memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); + logdebugx("%s: sending NA for %s", ifp->name, ia->saddr); + +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_inet_sendnd(ifp, &msg) == -1) + logerr(__func__); + goto sent; + } +#endif +#ifdef __sun + s = state->nd_fd; +#else + s = ctx->nd_fd; +#endif + if (sendmsg(s, &msg, 0) == -1) + logerr(__func__); + +#ifdef PRIVSEP +sent: +#endif + if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) { + eloop_timeout_add_sec(ctx->eloop, + state->retrans / 1000, ipv6nd_sendadvertisement, ia); + return; + } + +freeit: + free(ia->na); + ia->na = NULL; + ia->na_count = 0; +} + +void +ipv6nd_advertise(struct ipv6_addr *ia) +{ + struct dhcpcd_ctx *ctx; + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *iap, *iaf; + struct nd_neighbor_advert *na; + + if (IN6_IS_ADDR_MULTICAST(&ia->addr)) + return; + +#ifdef __sun + if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) + return; +#endif + + ctx = ia->iface->ctx; + /* Find the most preferred address to advertise. */ + iaf = NULL; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = IPV6_STATE(ifp); + if (state == NULL || !if_is_link_up(ifp)) + continue; + + TAILQ_FOREACH(iap, &state->addrs, next) { + if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) + continue; + + /* Cancel any current advertisement. */ + eloop_timeout_delete(ctx->eloop, + ipv6nd_sendadvertisement, iap); + + /* Don't advertise what we can't use. */ + if (iap->prefix_vltime == 0 || + iap->addr_flags & IN6_IFF_NOTUSEABLE) + continue; + + if (iaf == NULL || + iaf->iface->metric > iap->iface->metric) + iaf = iap; + } + } + if (iaf == NULL) + return; + + /* Make the packet. */ + ifp = iaf->iface; + iaf->na_len = sizeof(*na); + if (ifp->hwlen != 0) + iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2); + na = calloc(1, iaf->na_len); + if (na == NULL) { + logerr(__func__); + return; + } + + na->nd_na_type = ND_NEIGHBOR_ADVERT; + na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; +#if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE)) + if (IN_PRIVSEP(ctx)) { + if (ps_root_ip6forwarding(ctx, ifp->name) != 0) + na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; + } else +#endif + if (ip6_forwarding(ifp->name) != 0) + na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; + na->nd_na_target = ia->addr; + + if (ifp->hwlen != 0) { + struct nd_opt_hdr *opt; + + opt = (struct nd_opt_hdr *)(na + 1); + opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; + opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); + memcpy(opt + 1, ifp->hwaddr, ifp->hwlen); + } + + iaf->na_count = 0; + free(iaf->na); + iaf->na = na; + eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf); + ipv6nd_sendadvertisement(iaf); +} +#elif !defined(SMALL) +#warning kernel does not support userland sending ND6 advertisements +#endif /* ND6_ADVERTISE */ + +static void +ipv6nd_expire(void *arg) +{ + struct interface *ifp = arg; + struct ra *rap; + + if (ifp->ctx->ra_routers == NULL) + return; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface == ifp && rap->willexpire) + rap->doexpire = true; + } + ipv6nd_expirera(ifp); +} + +void +ipv6nd_startexpire(struct interface *ifp) +{ + struct ra *rap; + + if (ifp->ctx->ra_routers == NULL) + return; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface == ifp) + rap->willexpire = true; + } + eloop_q_timeout_add_sec(ifp->ctx->eloop, ELOOP_IPV6RA_EXPIRE, + RTR_CARRIER_EXPIRE, ipv6nd_expire, ifp); +} + +int +ipv6nd_rtpref(struct ra *rap) +{ + + switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) { + case ND_RA_FLAG_RTPREF_HIGH: + return RTPREF_HIGH; + case ND_RA_FLAG_RTPREF_MEDIUM: + case ND_RA_FLAG_RTPREF_RSV: + return RTPREF_MEDIUM; + case ND_RA_FLAG_RTPREF_LOW: + return RTPREF_LOW; + default: + logerrx("%s: impossible RA flag %x", __func__, rap->flags); + return RTPREF_INVALID; + } + /* NOTREACHED */ +} + +static void +ipv6nd_sortrouters(struct dhcpcd_ctx *ctx) +{ + struct ra_head sorted_routers = TAILQ_HEAD_INITIALIZER(sorted_routers); + struct ra *ra1, *ra2; + + while ((ra1 = TAILQ_FIRST(ctx->ra_routers)) != NULL) { + TAILQ_REMOVE(ctx->ra_routers, ra1, next); + TAILQ_FOREACH(ra2, &sorted_routers, next) { + if (ra1->iface->metric > ra2->iface->metric) + continue; + if (ra1->expired && !ra2->expired) + continue; + if (ra1->willexpire && !ra2->willexpire) + continue; + if (ra1->lifetime == 0 && ra2->lifetime != 0) + continue; + if (!ra1->isreachable && ra2->reachable) + continue; + if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2)) + continue; + /* All things being equal, prefer older routers. */ + /* We don't need to check time, becase newer + * routers are always added to the tail and then + * sorted. */ + TAILQ_INSERT_BEFORE(ra2, ra1, next); + break; + } + if (ra2 == NULL) + TAILQ_INSERT_TAIL(&sorted_routers, ra1, next); + } + + TAILQ_CONCAT(ctx->ra_routers, &sorted_routers, next); +} + +static void +ipv6nd_applyra(struct interface *ifp) +{ + struct ra *rap; + struct rs_state *state = RS_STATE(ifp); + struct ra defra = { + .iface = ifp, + .hoplimit = IPV6_DEFHLIM , + .reachable = REACHABLE_TIME, + .retrans = RETRANS_TIMER, + }; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface == ifp) + break; + } + + /* If we have no Router Advertisement, then set default values. */ + if (rap == NULL || rap->expired || rap->willexpire) + rap = &defra; + + state->retrans = rap->retrans; + if (if_applyra(rap) == -1 && errno != ENOENT) + logerr(__func__); +} + +/* + * Neighbour reachability. + * + * RFC 4681 6.2.5 says when a node is no longer a router it MUST + * send a RA with a zero lifetime. + * All OS's I know of set the NA router flag if they are a router + * or not and disregard that they are actively advertising or + * shutting down. If the interface is disabled, it cant't send a NA at all. + * + * As such we CANNOT rely on the NA Router flag and MUST use + * unreachability or receive a RA with a lifetime of zero to remove + * the node as a default router. + */ +void +ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, bool reachable) +{ + struct ra *rap, *rapr; + + if (ctx->ra_routers == NULL) + return; + + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) + break; + } + + if (rap == NULL || rap->expired || rap->isreachable == reachable) + return; + + rap->isreachable = reachable; + loginfox("%s: %s is %s", rap->iface->name, rap->sfrom, + reachable ? "reachable again" : "unreachable"); + + /* See if we can install a reachable default router. */ + ipv6nd_sortrouters(ctx); + ipv6nd_applyra(rap->iface); + rt_build(ctx, AF_INET6); + + if (reachable) + return; + + /* If we have no reachable default routers, try and solicit one. */ + TAILQ_FOREACH(rapr, ctx->ra_routers, next) { + if (rap == rapr || rap->iface != rapr->iface) + continue; + if (rapr->isreachable && !rapr->expired && rapr->lifetime) + break; + } + + if (rapr == NULL) + ipv6nd_startrs(rap->iface); +} + +const struct ipv6_addr * +ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr, + unsigned int flags) +{ + struct ra *rap; + struct ipv6_addr *ap; + + if (ifp->ctx->ra_routers == NULL) + return NULL; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface != ifp) + continue; + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (ipv6_findaddrmatch(ap, addr, flags)) + return ap; + } + } + return NULL; +} + +struct ipv6_addr * +ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, + unsigned int flags) +{ + struct ra *rap; + struct ipv6_addr *ap; + + if (ctx->ra_routers == NULL) + return NULL; + + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (ipv6_findaddrmatch(ap, addr, flags)) + return ap; + } + } + return NULL; +} + +static struct ipv6_addr * +ipv6nd_rapfindprefix(struct ra *rap, + const struct in6_addr *pfx, uint8_t pfxlen) +{ + struct ipv6_addr *ia; + + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (ia->prefix_vltime == 0) + continue; + if (ia->prefix_len == pfxlen && + IN6_ARE_ADDR_EQUAL(&ia->prefix, pfx)) + break; + } + return ia; +} + +struct ipv6_addr * +ipv6nd_iffindprefix(struct interface *ifp, + const struct in6_addr *pfx, uint8_t pfxlen) +{ + struct ra *rap; + struct ipv6_addr *ia; + + ia = NULL; + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface != ifp) + continue; + ia = ipv6nd_rapfindprefix(rap, pfx, pfxlen); + if (ia != NULL) + break; + } + return ia; +} + +static void +ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra) +{ + + eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); + eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); + if (remove_ra) + TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next); + ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL); + free(rap->data); + free(rap); +} + +static void +ipv6nd_freedrop_ra(struct ra *rap, int drop) +{ + + ipv6nd_removefreedrop_ra(rap, 1, drop); +} + +ssize_t +ipv6nd_free(struct interface *ifp) +{ + struct rs_state *state; + struct ra *rap, *ran; + struct dhcpcd_ctx *ctx; + ssize_t n; + + state = RS_STATE(ifp); + if (state == NULL) + return 0; + + ctx = ifp->ctx; +#ifdef __sun + eloop_event_delete(ctx->eloop, state->nd_fd); + close(state->nd_fd); +#endif + free(state->rs); + free(state); + ifp->if_data[IF_DATA_IPV6ND] = NULL; + n = 0; + TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { + if (rap->iface == ifp) { + ipv6nd_free_ra(rap); + n++; + } + } + +#ifndef __sun + /* If we don't have any more IPv6 enabled interfaces, + * close the global socket and release resources */ + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (RS_STATE(ifp)) + break; + } + if (ifp == NULL) { + if (ctx->nd_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->nd_fd); + close(ctx->nd_fd); + ctx->nd_fd = -1; + } + } +#endif + + return n; +} + +static void +ipv6nd_scriptrun(struct ra *rap) +{ + int hasdns, hasaddress; + struct ipv6_addr *ap; + + hasaddress = 0; + /* If all addresses have completed DAD run the script */ + TAILQ_FOREACH(ap, &rap->addrs, next) { + if ((ap->flags & (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) == + (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) + { + hasaddress = 1; + if (!(ap->flags & IPV6_AF_DADCOMPLETED) && + ipv6_iffindaddr(ap->iface, &ap->addr, + IN6_IFF_TENTATIVE)) + ap->flags |= IPV6_AF_DADCOMPLETED; + if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { + logdebugx("%s: waiting for Router Advertisement" + " DAD to complete", + rap->iface->name); + return; + } + } + } + + /* If we don't require RDNSS then set hasdns = 1 so we fork */ + if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS)) + hasdns = 1; + else { + hasdns = rap->hasdns; + } + + script_runreason(rap->iface, "ROUTERADVERT"); + if (hasdns && (hasaddress || + !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) + dhcpcd_daemonise(rap->iface->ctx); +#if 0 + else if (options & DHCPCD_DAEMONISE && + !(options & DHCPCD_DAEMONISED) && new_data) + logwarnx("%s: did not fork due to an absent" + " RDNSS option in the RA", + ifp->name); +#endif +} + +static void +ipv6nd_addaddr(void *arg) +{ + struct ipv6_addr *ap = arg; + + ipv6_addaddr(ap, NULL); +} + +int +ipv6nd_dadcompleted(const struct interface *ifp) +{ + const struct ra *rap; + const struct ipv6_addr *ap; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface != ifp) + continue; + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (ap->flags & IPV6_AF_AUTOCONF && + ap->flags & IPV6_AF_ADDED && + !(ap->flags & IPV6_AF_DADCOMPLETED)) + return 0; + } + } + return 1; +} + +static void +ipv6nd_dadcallback(void *arg) +{ + struct ipv6_addr *ia = arg, *rapap; + struct interface *ifp; + struct ra *rap; + int wascompleted, found; + char buf[INET6_ADDRSTRLEN]; + const char *p; + int dadcounter; + + ifp = ia->iface; + wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); + ia->flags |= IPV6_AF_DADCOMPLETED; + if (ia->addr_flags & IN6_IFF_DUPLICATED) { + ia->dadcounter++; + logwarnx("%s: DAD detected %s", ifp->name, ia->saddr); + + /* Try and make another stable private address. + * Because ap->dadcounter is always increamented, + * a different address is generated. */ + /* XXX Cache DAD counter per prefix/id/ssid? */ + if (ifp->options->options & DHCPCD_SLAACPRIVATE && + IA6_CANAUTOCONF(ia)) + { + unsigned int delay; + + if (ia->dadcounter >= IDGEN_RETRIES) { + logerrx("%s: unable to obtain a" + " stable private address", + ifp->name); + goto try_script; + } + loginfox("%s: deleting address %s", + ifp->name, ia->saddr); + if (if_address6(RTM_DELADDR, ia) == -1 && + errno != EADDRNOTAVAIL && errno != ENXIO) + logerr(__func__); + dadcounter = ia->dadcounter; + if (ipv6_makestableprivate(&ia->addr, + &ia->prefix, ia->prefix_len, + ifp, &dadcounter) == -1) + { + logerr("ipv6_makestableprivate"); + return; + } + ia->dadcounter = dadcounter; + ia->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); + ia->flags |= IPV6_AF_NEW; + p = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); + if (p) + snprintf(ia->saddr, + sizeof(ia->saddr), + "%s/%d", + p, ia->prefix_len); + else + ia->saddr[0] = '\0'; + delay = arc4random_uniform(IDGEN_DELAY * MSEC_PER_SEC); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, + ipv6nd_addaddr, ia); + return; + } + } + +try_script: + if (!wascompleted) { + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface != ifp) + continue; + wascompleted = 1; + found = 0; + TAILQ_FOREACH(rapap, &rap->addrs, next) { + if (rapap->flags & IPV6_AF_AUTOCONF && + rapap->flags & IPV6_AF_ADDED && + (rapap->flags & IPV6_AF_DADCOMPLETED) == 0) + { + wascompleted = 0; + break; + } + if (rapap == ia) + found = 1; + } + + if (wascompleted && found) { + logdebugx("%s: Router Advertisement DAD " + "completed", + rap->iface->name); + ipv6nd_scriptrun(rap); + } + } +#ifdef ND6_ADVERTISE + ipv6nd_advertise(ia); +#endif + } +} + +static struct ipv6_addr * +ipv6nd_findmarkstale(struct ra *rap, struct ipv6_addr *ia, bool mark) +{ + struct dhcpcd_ctx *ctx = ia->iface->ctx; + struct ra *rap2; + struct ipv6_addr *ia2; + + TAILQ_FOREACH(rap2, ctx->ra_routers, next) { + if (rap2 == rap || + rap2->iface != rap->iface || + rap2->expired) + continue; + TAILQ_FOREACH(ia2, &rap2->addrs, next) { + if (!IN6_ARE_ADDR_EQUAL(&ia->prefix, &ia2->prefix)) + continue; + if (!(ia2->flags & IPV6_AF_STALE)) + return ia2; + if (mark) + ia2->prefix_pltime = 0; + } + } + return NULL; +} + +#ifndef DHCP6 +/* If DHCPv6 is compiled out, supply a shim to provide an error message + * if IPv6RA requests DHCPv6. */ +enum DH6S { + DH6S_REQUEST, + DH6S_INFORM, +}; +static int +dhcp6_start(__unused struct interface *ifp, __unused enum DH6S init_state) +{ + + errno = ENOTSUP; + return -1; +} +#endif + +static void +ipv6nd_handlera(struct dhcpcd_ctx *ctx, + const struct sockaddr_in6 *from, const char *sfrom, + struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit) +{ + size_t i, olen; + struct nd_router_advert *nd_ra; + struct nd_opt_hdr ndo; + struct nd_opt_prefix_info pi; + struct nd_opt_mtu mtu; + struct nd_opt_rdnss rdnss; + uint8_t *p; + struct ra *rap; + struct in6_addr pi_prefix; + struct ipv6_addr *ia; + struct dhcp_opt *dho; + bool new_rap, new_data, has_address; + uint32_t old_lifetime; + int ifmtu; + int loglevel; + unsigned int flags; +#ifdef IPV6_MANAGETEMPADDR + bool new_ia; +#endif + + if (ifp == NULL || RS_STATE(ifp) == NULL) { +#ifdef DEBUG_RS + logdebugx("RA for unexpected interface from %s", sfrom); +#endif + return; + } + + if (len < sizeof(struct nd_router_advert)) { + logerrx("IPv6 RA packet too short from %s", sfrom); + return; + } + + /* RFC 4861 7.1.2 */ + if (hoplimit != 255) { + logerrx("invalid hoplimit(%d) in RA from %s", hoplimit, sfrom); + return; + } + if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { + logerrx("RA from non local address %s", sfrom); + return; + } + + if (!(ifp->options->options & DHCPCD_IPV6RS)) { +#ifdef DEBUG_RS + logerrx("%s: unexpected RA from %s", ifp->name, sfrom); +#endif + return; + } + + /* We could receive a RA before we sent a RS*/ + if (ipv6_linklocal(ifp) == NULL) { +#ifdef DEBUG_RS + logdebugx("%s: received RA from %s (no link-local)", + ifp->name, sfrom); +#endif + return; + } + + if (ipv6_iffindaddr(ifp, &from->sin6_addr, IN6_IFF_TENTATIVE)) { + logdebugx("%s: ignoring RA from ourself %s", + ifp->name, sfrom); + return; + } + + /* + * Because we preserve RA's and expire them quickly after + * carrier up, it's important to reset the kernels notion of + * reachable timers back to default values before applying + * new RA values. + */ + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + if (ifp == rap->iface) + break; + } + if (rap != NULL && rap->willexpire) + ipv6nd_applyra(ifp); + + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + if (ifp == rap->iface && + IN6_ARE_ADDR_EQUAL(&rap->from, &from->sin6_addr)) + break; + } + + nd_ra = (struct nd_router_advert *)icp; + + /* We don't want to spam the log with the fact we got an RA every + * 30 seconds or so, so only spam the log if it's different. */ + if (rap == NULL || (rap->data_len != len || + memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) + { + if (rap) { + free(rap->data); + rap->data_len = 0; + } + new_data = true; + } else + new_data = false; + if (rap == NULL) { + rap = calloc(1, sizeof(*rap)); + if (rap == NULL) { + logerr(__func__); + return; + } + rap->iface = ifp; + rap->from = from->sin6_addr; + strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom)); + TAILQ_INIT(&rap->addrs); + new_rap = true; + rap->isreachable = true; + } else + new_rap = false; + if (rap->data_len == 0) { + rap->data = malloc(len); + if (rap->data == NULL) { + logerr(__func__); + if (new_rap) + free(rap); + return; + } + memcpy(rap->data, icp, len); + rap->data_len = len; + } + + /* We could change the debug level based on new_data, but some + * routers like to decrease the advertised valid and preferred times + * in accordance with the own prefix times which would result in too + * much needless log spam. */ + if (rap->willexpire) + new_data = true; + loglevel = new_rap || rap->willexpire || !rap->isreachable ? + LOG_INFO : LOG_DEBUG; + logmessage(loglevel, "%s: Router Advertisement from %s", + ifp->name, rap->sfrom); + + clock_gettime(CLOCK_MONOTONIC, &rap->acquired); + rap->flags = nd_ra->nd_ra_flags_reserved; + old_lifetime = rap->lifetime; + rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); + if (!new_rap && rap->lifetime == 0 && old_lifetime != 0) + logwarnx("%s: %s: no longer a default router", + ifp->name, rap->sfrom); + if (nd_ra->nd_ra_curhoplimit != 0) + rap->hoplimit = nd_ra->nd_ra_curhoplimit; + else + rap->hoplimit = IPV6_DEFHLIM; + if (nd_ra->nd_ra_reachable != 0) { + rap->reachable = ntohl(nd_ra->nd_ra_reachable); + if (rap->reachable > MAX_REACHABLE_TIME) + rap->reachable = 0; + } else + rap->reachable = REACHABLE_TIME; + if (nd_ra->nd_ra_retransmit != 0) + rap->retrans = ntohl(nd_ra->nd_ra_retransmit); + else + rap->retrans = RETRANS_TIMER; + rap->expired = rap->willexpire = rap->doexpire = false; + rap->hasdns = false; + rap->isreachable = true; + has_address = false; + rap->mtu = 0; + +#ifdef IPV6_AF_TEMPORARY + ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY); +#endif + TAILQ_FOREACH(ia, &rap->addrs, next) { + ia->flags |= IPV6_AF_STALE; + } + + len -= sizeof(struct nd_router_advert); + p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); + for (; len > 0; p += olen, len -= olen) { + if (len < sizeof(ndo)) { + logerrx("%s: short option", ifp->name); + break; + } + memcpy(&ndo, p, sizeof(ndo)); + olen = (size_t)ndo.nd_opt_len * 8; + if (olen == 0) { + logerrx("%s: zero length option", ifp->name); + break; + } + if (olen > len) { + logerrx("%s: option length exceeds message", + ifp->name); + break; + } + + if (has_option_mask(ifp->options->rejectmasknd, + ndo.nd_opt_type)) + { + for (i = 0, dho = ctx->nd_opts; + i < ctx->nd_opts_len; + i++, dho++) + { + if (dho->option == ndo.nd_opt_type) + break; + } + if (dho != NULL) + logwarnx("%s: reject RA (option %s) from %s", + ifp->name, dho->var, rap->sfrom); + else + logwarnx("%s: reject RA (option %d) from %s", + ifp->name, ndo.nd_opt_type, rap->sfrom); + if (new_rap) + ipv6nd_removefreedrop_ra(rap, 0, 0); + else + ipv6nd_free_ra(rap); + return; + } + + if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type)) + continue; + + switch (ndo.nd_opt_type) { + case ND_OPT_PREFIX_INFORMATION: + loglevel = new_data ? LOG_ERR : LOG_DEBUG; + if (ndo.nd_opt_len != 4) { + logmessage(loglevel, + "%s: invalid option len for prefix", + ifp->name); + continue; + } + memcpy(&pi, p, sizeof(pi)); + if (pi.nd_opt_pi_prefix_len > 128) { + logmessage(loglevel, "%s: invalid prefix len", + ifp->name); + continue; + } + /* nd_opt_pi_prefix is not aligned. */ + memcpy(&pi_prefix, &pi.nd_opt_pi_prefix, + sizeof(pi_prefix)); + if (IN6_IS_ADDR_MULTICAST(&pi_prefix) || + IN6_IS_ADDR_LINKLOCAL(&pi_prefix)) + { + logmessage(loglevel, "%s: invalid prefix in RA", + ifp->name); + continue; + } + if (ntohl(pi.nd_opt_pi_preferred_time) > + ntohl(pi.nd_opt_pi_valid_time)) + { + logmessage(loglevel, "%s: pltime > vltime", + ifp->name); + continue; + } + + flags = IPV6_AF_RAPFX; + /* If no flags are set, that means the prefix is + * available via the router. */ + if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) + flags |= IPV6_AF_ONLINK; + if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO && + rap->iface->options->options & + DHCPCD_IPV6RA_AUTOCONF) + flags |= IPV6_AF_AUTOCONF; + if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ROUTER) + flags |= IPV6_AF_ROUTER; + + ia = ipv6nd_rapfindprefix(rap, + &pi_prefix, pi.nd_opt_pi_prefix_len); + if (ia == NULL) { + ia = ipv6_newaddr(rap->iface, + &pi_prefix, pi.nd_opt_pi_prefix_len, flags); + if (ia == NULL) + break; + ia->prefix = pi_prefix; + if (flags & IPV6_AF_AUTOCONF) + ia->dadcallback = ipv6nd_dadcallback; + ia->created = ia->acquired = rap->acquired; + TAILQ_INSERT_TAIL(&rap->addrs, ia, next); + +#ifdef IPV6_MANAGETEMPADDR + /* New address to dhcpcd RA handling. + * If the address already exists and a valid + * temporary address also exists then + * extend the existing one rather than + * create a new one */ + if (flags & IPV6_AF_AUTOCONF && + ipv6_iffindaddr(ifp, &ia->addr, + IN6_IFF_NOTUSEABLE) && + ipv6_settemptime(ia, 0)) + new_ia = false; + else + new_ia = true; +#endif + } else { +#ifdef IPV6_MANAGETEMPADDR + new_ia = false; +#endif + ia->flags |= flags; + ia->flags &= ~IPV6_AF_STALE; + ia->acquired = rap->acquired; + } + ia->prefix_vltime = + ntohl(pi.nd_opt_pi_valid_time); + ia->prefix_pltime = + ntohl(pi.nd_opt_pi_preferred_time); + if (ia->prefix_vltime != 0 && + ia->flags & IPV6_AF_AUTOCONF) + has_address = true; + +#ifdef IPV6_MANAGETEMPADDR + /* RFC4941 Section 3.3.3 */ + if (ia->flags & IPV6_AF_AUTOCONF && + ia->iface->options->options & DHCPCD_SLAACTEMP && + IA6_CANAUTOCONF(ia)) + { + if (!new_ia) { + if (ipv6_settemptime(ia, 1) == NULL) + new_ia = true; + } + if (new_ia && ia->prefix_pltime) { + if (ipv6_createtempaddr(ia, + &ia->acquired) == NULL) + logerr("ipv6_createtempaddr"); + } + } +#endif + break; + + case ND_OPT_MTU: + if (len < sizeof(mtu)) { + logmessage(loglevel, "%s: short MTU option", ifp->name); + break; + } + memcpy(&mtu, p, sizeof(mtu)); + mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu); + if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) { + logmessage(loglevel, "%s: invalid MTU %d", + ifp->name, mtu.nd_opt_mtu_mtu); + break; + } + ifmtu = if_getmtu(ifp); + if (ifmtu == -1) + logerr("if_getmtu"); + else if (mtu.nd_opt_mtu_mtu > (uint32_t)ifmtu) { + logmessage(loglevel, "%s: advertised MTU %d" + " is greater than link MTU %d", + ifp->name, mtu.nd_opt_mtu_mtu, ifmtu); + rap->mtu = (uint32_t)ifmtu; + } else + rap->mtu = mtu.nd_opt_mtu_mtu; + break; + case ND_OPT_RDNSS: + if (len < sizeof(rdnss)) { + logmessage(loglevel, "%s: short RDNSS option", ifp->name); + break; + } + memcpy(&rdnss, p, sizeof(rdnss)); + if (rdnss.nd_opt_rdnss_lifetime && + rdnss.nd_opt_rdnss_len > 1) + rap->hasdns = 1; + break; + default: + continue; + } + } + + for (i = 0, dho = ctx->nd_opts; + i < ctx->nd_opts_len; + i++, dho++) + { + if (has_option_mask(ifp->options->requiremasknd, + dho->option)) + { + logwarnx("%s: reject RA (no option %s) from %s", + ifp->name, dho->var, rap->sfrom); + if (new_rap) + ipv6nd_removefreedrop_ra(rap, 0, 0); + else + ipv6nd_free_ra(rap); + return; + } + } + + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0) + continue; + if (ipv6nd_findmarkstale(rap, ia, false) != NULL) + continue; + ipv6nd_findmarkstale(rap, ia, true); + logdebugx("%s: %s: became stale", ifp->name, ia->saddr); + /* Technically this violates RFC 4861 6.3.4, + * but we need a mechanism to tell the kernel to + * try and prefer other addresses. */ + ia->prefix_pltime = 0; + } + + if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp)) + logwarnx("%s: no global addresses for default route", + ifp->name); + + if (new_rap) + TAILQ_INSERT_TAIL(ctx->ra_routers, rap, next); + if (new_data) + ipv6nd_sortrouters(ifp->ctx); + + if (ifp->ctx->options & DHCPCD_TEST) { + script_runreason(ifp, "TEST"); + goto handle_flag; + } + + if (!(ifp->options->options & DHCPCD_CONFIGURE)) + goto run; + + ipv6nd_applyra(ifp); + ipv6_addaddrs(&rap->addrs); +#ifdef IPV6_MANAGETEMPADDR + ipv6_addtempaddrs(ifp, &rap->acquired); +#endif + rt_build(ifp->ctx, AF_INET6); + +run: + ipv6nd_scriptrun(rap); + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */ + +handle_flag: + if (!(ifp->options->options & DHCPCD_DHCP6)) + goto nodhcp6; +/* Only log a DHCPv6 start error if compiled in or debugging is enabled. */ +#ifdef DHCP6 +#define LOG_DHCP6 logerr +#else +#define LOG_DHCP6 logdebug +#endif + if (rap->flags & ND_RA_FLAG_MANAGED) { + if (new_data && dhcp6_start(ifp, DH6S_REQUEST) == -1) + LOG_DHCP6("dhcp6_start: %s", ifp->name); + } else if (rap->flags & ND_RA_FLAG_OTHER) { + if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1) + LOG_DHCP6("dhcp6_start: %s", ifp->name); + } else { +#ifdef DHCP6 + if (new_data) + logdebugx("%s: No DHCPv6 instruction in RA", ifp->name); +#endif +nodhcp6: + if (ifp->ctx->options & DHCPCD_TEST) { + eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); + return; + } + } + + /* Expire should be called last as the rap object could be destroyed */ + ipv6nd_expirera(ifp); +} + +bool +ipv6nd_hasralifetime(const struct interface *ifp, bool lifetime) +{ + const struct ra *rap; + + if (ifp->ctx->ra_routers) { + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) + if (rap->iface == ifp && + !rap->expired && + (!lifetime ||rap->lifetime)) + return true; + } + return false; +} + +bool +ipv6nd_hasradhcp(const struct interface *ifp, bool managed) +{ + const struct ra *rap; + + if (ifp->ctx->ra_routers) { + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface == ifp && + !rap->expired && !rap->willexpire && + ((managed && rap->flags & ND_RA_FLAG_MANAGED) || + (!managed && rap->flags & ND_RA_FLAG_OTHER))) + return true; + } + } + return false; +} + +static const uint8_t * +ipv6nd_getoption(struct dhcpcd_ctx *ctx, + size_t *os, unsigned int *code, size_t *len, + const uint8_t *od, size_t ol, struct dhcp_opt **oopt) +{ + struct nd_opt_hdr ndo; + size_t i; + struct dhcp_opt *opt; + + if (od) { + *os = sizeof(ndo); + if (ol < *os) { + errno = EINVAL; + return NULL; + } + memcpy(&ndo, od, sizeof(ndo)); + i = (size_t)(ndo.nd_opt_len * 8); + if (i > ol) { + errno = EINVAL; + return NULL; + } + *len = i; + *code = ndo.nd_opt_type; + } + + for (i = 0, opt = ctx->nd_opts; + i < ctx->nd_opts_len; i++, opt++) + { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + if (od) + return od + sizeof(ndo); + return NULL; +} + +ssize_t +ipv6nd_env(FILE *fp, const struct interface *ifp) +{ + size_t i, j, n, len, olen; + struct ra *rap; + char ndprefix[32]; + struct dhcp_opt *opt; + uint8_t *p; + struct nd_opt_hdr ndo; + struct ipv6_addr *ia; + struct timespec now; + int pref; + + clock_gettime(CLOCK_MONOTONIC, &now); + i = n = 0; + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface != ifp || rap->expired) + continue; + i++; + snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i); + if (efprintf(fp, "%s_from=%s", ndprefix, rap->sfrom) == -1) + return -1; + if (efprintf(fp, "%s_acquired=%lld", ndprefix, + (long long)rap->acquired.tv_sec) == -1) + return -1; + if (efprintf(fp, "%s_now=%lld", ndprefix, + (long long)now.tv_sec) == -1) + return -1; + if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1) + return -1; + pref = ipv6nd_rtpref(rap); + if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix, + rap->flags & ND_RA_FLAG_MANAGED ? "M" : "", + rap->flags & ND_RA_FLAG_OTHER ? "O" : "", + rap->flags & ND_RA_FLAG_HOME_AGENT ? "H" : "", + pref == RTPREF_HIGH ? "h" : pref == RTPREF_LOW ? "l" : "", + rap->flags & ND_RA_FLAG_PROXY ? "P" : "") == -1) + return -1; + if (efprintf(fp, "%s_lifetime=%u", ndprefix, rap->lifetime) == -1) + return -1; + + /* Zero our indexes */ + for (j = 0, opt = rap->iface->ctx->nd_opts; + j < rap->iface->ctx->nd_opts_len; + j++, opt++) + dhcp_zero_index(opt); + for (j = 0, opt = rap->iface->options->nd_override; + j < rap->iface->options->nd_override_len; + j++, opt++) + dhcp_zero_index(opt); + + /* Unlike DHCP, ND6 options *may* occur more than once. + * There is also no provision for option concatenation + * unlike DHCP. */ + len = rap->data_len - sizeof(struct nd_router_advert); + for (p = rap->data + sizeof(struct nd_router_advert); + len >= sizeof(ndo); + p += olen, len -= olen) + { + memcpy(&ndo, p, sizeof(ndo)); + olen = (size_t)(ndo.nd_opt_len * 8); + if (olen > len) { + errno = EINVAL; + break; + } + if (has_option_mask(rap->iface->options->nomasknd, + ndo.nd_opt_type)) + continue; + for (j = 0, opt = rap->iface->options->nd_override; + j < rap->iface->options->nd_override_len; + j++, opt++) + if (opt->option == ndo.nd_opt_type) + break; + if (j == rap->iface->options->nd_override_len) { + for (j = 0, opt = rap->iface->ctx->nd_opts; + j < rap->iface->ctx->nd_opts_len; + j++, opt++) + if (opt->option == ndo.nd_opt_type) + break; + if (j == rap->iface->ctx->nd_opts_len) + opt = NULL; + } + if (opt == NULL) + continue; + dhcp_envoption(rap->iface->ctx, fp, + ndprefix, rap->iface->name, + opt, ipv6nd_getoption, + p + sizeof(ndo), olen - sizeof(ndo)); + } + + /* We need to output the addresses we actually made + * from the prefix information options as well. */ + j = 0; + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (!(ia->flags & IPV6_AF_AUTOCONF) || +#ifdef IPV6_AF_TEMPORARY + ia->flags & IPV6_AF_TEMPORARY || +#endif + !(ia->flags & IPV6_AF_ADDED) || + ia->prefix_vltime == 0) + continue; + if (efprintf(fp, "%s_addr%zu=%s", + ndprefix, ++j, ia->saddr) == -1) + return -1; + } + } + return 1; +} + +void +ipv6nd_handleifa(int cmd, struct ipv6_addr *addr, pid_t pid) +{ + struct ra *rap; + + /* IPv6 init may not have happened yet if we are learning + * existing addresses when dhcpcd starts. */ + if (addr->iface->ctx->ra_routers == NULL) + return; + + TAILQ_FOREACH(rap, addr->iface->ctx->ra_routers, next) { + if (rap->iface != addr->iface) + continue; + ipv6_handleifa_addrs(cmd, &rap->addrs, addr, pid); + } +} + +void +ipv6nd_expirera(void *arg) +{ + struct interface *ifp; + struct ra *rap, *ran; + struct timespec now; + uint32_t elapsed; + bool expired, valid; + struct ipv6_addr *ia; + size_t len, olen; + uint8_t *p; + struct nd_opt_hdr ndo; +#if 0 + struct nd_opt_prefix_info pi; +#endif + struct nd_opt_dnssl dnssl; + struct nd_opt_rdnss rdnss; + unsigned int next = 0, ltime; + size_t nexpired = 0; + + ifp = arg; + clock_gettime(CLOCK_MONOTONIC, &now); + expired = false; + + TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { + if (rap->iface != ifp || rap->expired) + continue; + valid = false; + if (rap->lifetime) { + elapsed = (uint32_t)eloop_timespec_diff(&now, + &rap->acquired, NULL); + if (elapsed >= rap->lifetime || rap->doexpire) { + if (!rap->expired) { + logwarnx("%s: %s: router expired", + ifp->name, rap->sfrom); + rap->lifetime = 0; + expired = true; + } + } else { + valid = true; + ltime = rap->lifetime - elapsed; + if (next == 0 || ltime < next) + next = ltime; + } + } + + /* Not every prefix is tied to an address which + * the kernel can expire, so we need to handle it ourself. + * Also, some OS don't support address lifetimes (Solaris). */ + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (ia->prefix_vltime == 0) + continue; + if (ia->prefix_vltime == ND6_INFINITE_LIFETIME && + !rap->doexpire) + { + valid = true; + continue; + } + elapsed = (uint32_t)eloop_timespec_diff(&now, + &ia->acquired, NULL); + if (elapsed >= ia->prefix_vltime || rap->doexpire) { + if (ia->flags & IPV6_AF_ADDED) { + logwarnx("%s: expired %s %s", + ia->iface->name, + ia->flags & IPV6_AF_AUTOCONF ? + "address" : "prefix", + ia->saddr); + if (if_address6(RTM_DELADDR, ia)== -1 && + errno != EADDRNOTAVAIL && + errno != ENXIO) + logerr(__func__); + } + ia->prefix_vltime = ia->prefix_pltime = 0; + ia->flags &= + ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); + expired = true; + } else { + valid = true; + ltime = ia->prefix_vltime - elapsed; + if (next == 0 || ltime < next) + next = ltime; + } + } + + /* Work out expiry for ND options */ + elapsed = (uint32_t)eloop_timespec_diff(&now, + &rap->acquired, NULL); + len = rap->data_len - sizeof(struct nd_router_advert); + for (p = rap->data + sizeof(struct nd_router_advert); + len >= sizeof(ndo); + p += olen, len -= olen) + { + memcpy(&ndo, p, sizeof(ndo)); + olen = (size_t)(ndo.nd_opt_len * 8); + if (olen > len) { + errno = EINVAL; + break; + } + + if (has_option_mask(rap->iface->options->nomasknd, + ndo.nd_opt_type)) + continue; + + switch (ndo.nd_opt_type) { + /* Prefix info is already checked in the above loop. */ +#if 0 + case ND_OPT_PREFIX_INFORMATION: + if (len < sizeof(pi)) + break; + memcpy(&pi, p, sizeof(pi)); + ltime = pi.nd_opt_pi_valid_time; + break; +#endif + case ND_OPT_DNSSL: + if (len < sizeof(dnssl)) + continue; + memcpy(&dnssl, p, sizeof(dnssl)); + ltime = dnssl.nd_opt_dnssl_lifetime; + break; + case ND_OPT_RDNSS: + if (len < sizeof(rdnss)) + continue; + memcpy(&rdnss, p, sizeof(rdnss)); + ltime = rdnss.nd_opt_rdnss_lifetime; + break; + default: + continue; + } + + if (ltime == 0) + continue; + if (rap->doexpire) { + expired = true; + continue; + } + if (ltime == ND6_INFINITE_LIFETIME) { + valid = true; + continue; + } + + ltime = ntohl(ltime); + if (elapsed >= ltime) { + expired = true; + continue; + } + + valid = true; + ltime -= elapsed; + if (next == 0 || ltime < next) + next = ltime; + } + + if (valid) + continue; + + /* Router has expired. Let's not keep a lot of them. */ + rap->expired = true; + if (++nexpired > EXPIRED_MAX) + ipv6nd_free_ra(rap); + } + + if (next != 0) + eloop_timeout_add_sec(ifp->ctx->eloop, + next, ipv6nd_expirera, ifp); + if (expired) { + logwarnx("%s: part of a Router Advertisement expired", + ifp->name); + ipv6nd_sortrouters(ifp->ctx); + ipv6nd_applyra(ifp); + rt_build(ifp->ctx, AF_INET6); + script_runreason(ifp, "ROUTERADVERT"); + } +} + +void +ipv6nd_drop(struct interface *ifp) +{ + struct ra *rap, *ran; + bool expired = false; + + if (ifp->ctx->ra_routers == NULL) + return; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { + if (rap->iface == ifp) { + rap->expired = expired = true; + ipv6nd_drop_ra(rap); + } + } + if (expired) { + ipv6nd_applyra(ifp); + rt_build(ifp->ctx, AF_INET6); + if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP) + script_runreason(ifp, "ROUTERADVERT"); + } +} + +void +ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) +{ + struct sockaddr_in6 *from = (struct sockaddr_in6 *)msg->msg_name; + char sfrom[INET6_ADDRSTRLEN]; + int hoplimit = 0; + struct icmp6_hdr *icp; + struct interface *ifp; + size_t len = msg->msg_iov[0].iov_len; + + inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom)); + if ((size_t)len < sizeof(struct icmp6_hdr)) { + logerrx("IPv6 ICMP packet too short from %s", sfrom); + return; + } + + ifp = if_findifpfromcmsg(ctx, msg, &hoplimit); + if (ifp == NULL) { + logerr(__func__); + return; + } + + /* Don't do anything if the user hasn't configured it. */ + if (ifp->active != IF_ACTIVE_USER || + !(ifp->options->options & DHCPCD_IPV6)) + return; + + icp = (struct icmp6_hdr *)msg->msg_iov[0].iov_base; + if (icp->icmp6_code == 0) { + switch(icp->icmp6_type) { + case ND_ROUTER_ADVERT: + ipv6nd_handlera(ctx, from, sfrom, + ifp, icp, (size_t)len, hoplimit); + return; + } + } + + logerrx("invalid IPv6 type %d or code %d from %s", + icp->icmp6_type, icp->icmp6_code, sfrom); +} + +static void +ipv6nd_handledata(void *arg) +{ + struct dhcpcd_ctx *ctx; + int fd; + struct sockaddr_in6 from; + union { + struct icmp6_hdr hdr; + uint8_t buf[64 * 1024]; /* Maximum ICMPv6 size */ + } iovbuf; + struct iovec iov = { + .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf), + }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + + CMSG_SPACE(sizeof(int))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &from, .msg_namelen = sizeof(from), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + ssize_t len; + +#ifdef __sun + struct interface *ifp; + struct rs_state *state; + + ifp = arg; + state = RS_STATE(ifp); + ctx = ifp->ctx; + fd = state->nd_fd; +#else + ctx = arg; + fd = ctx->nd_fd; +#endif + len = recvmsg(fd, &msg, 0); + if (len == -1) { + logerr(__func__); + return; + } + + iov.iov_len = (size_t)len; + ipv6nd_recvmsg(ctx, &msg); +} + +static void +ipv6nd_startrs1(void *arg) +{ + struct interface *ifp = arg; + struct rs_state *state; + + loginfox("%s: soliciting an IPv6 router", ifp->name); + state = RS_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state)); + state = RS_STATE(ifp); + if (state == NULL) { + logerr(__func__); + return; + } +#ifdef __sun + state->nd_fd = -1; +#endif + } + + /* Always make a new probe as the underlying hardware + * address could have changed. */ + ipv6nd_makersprobe(ifp); + if (state->rs == NULL) { + logerr(__func__); + return; + } + + state->retrans = RETRANS_TIMER; + state->rsprobes = 0; + ipv6nd_sendrsprobe(ifp); +} + +void +ipv6nd_startrs(struct interface *ifp) +{ + unsigned int delay; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) { + ipv6nd_startrs1(ifp); + return; + } + + delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC); + logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds", + ifp->name, (float)delay / MSEC_PER_SEC); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp); + return; +} Index: src/logerr.h =================================================================== --- /dev/null +++ src/logerr.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * logerr: errx with logging + * 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. + */ + +#ifndef LOGERR_H +#define LOGERR_H + +#include + +#ifndef __printflike +#if __GNUC__ > 2 || defined(__INTEL_COMPILER) +#define __printflike(a, b) __attribute__((format(printf, a, b))) +#else +#define __printflike(a, b) +#endif +#endif /* !__printflike */ + +/* Please do not call log_* functions directly, use macros below */ +__printflike(1, 2) void log_debug(const char *, ...); +__printflike(1, 2) void log_debugx(const char *, ...); +__printflike(1, 2) void log_info(const char *, ...); +__printflike(1, 2) void log_infox(const char *, ...); +__printflike(1, 2) void log_warn(const char *, ...); +__printflike(1, 2) void log_warnx(const char *, ...); +__printflike(1, 2) void log_err(const char *, ...); +__printflike(1, 2) void log_errx(const char *, ...); +#define LOGERROR logerr("%s: %d", __FILE__, __LINE__) + +__printflike(2, 3) void logmessage(int pri, const char *fmt, ...); +__printflike(2, 3) void logerrmessage(int pri, const char *fmt, ...); + +/* + * These are macros to prevent taking address of them so + * __FILE__, __LINE__, etc can easily be added. + * + * We should be using + * #define loginfox(fmt, __VA_OPT__(,) __VA_ARGS__) + * but that requires gcc-8 or clang-6 and we still have a need to support + * old OS's without modern compilers. + * + * Likewise, ##__VA_ARGS__ can't be used as that's a gcc only extension. + * + * The solution is to put fmt into __VA_ARGS__. + * It's not pretty but it's 100% portable. + */ +#define logdebug(...) log_debug(__VA_ARGS__) +#define logdebugx(...) log_debugx(__VA_ARGS__) +#define loginfo(...) log_info(__VA_ARGS__) +#define loginfox(...) log_infox(__VA_ARGS__) +#define logwarn(...) log_warn(__VA_ARGS__) +#define logwarnx(...) log_warnx(__VA_ARGS__) +#define logerr(...) log_err(__VA_ARGS__) +#define logerrx(...) log_errx(__VA_ARGS__) + +/* For logging in a chroot */ +int loggetfd(void); +void logsetfd(int); +int logreadfd(int); + +unsigned int loggetopts(void); +void logsetopts(unsigned int); +#define LOGERR_DEBUG (1U << 6) +#define LOGERR_QUIET (1U << 7) +#define LOGERR_LOG (1U << 11) +#define LOGERR_LOG_DATE (1U << 12) +#define LOGERR_LOG_HOST (1U << 13) +#define LOGERR_LOG_TAG (1U << 14) +#define LOGERR_LOG_PID (1U << 15) +#define LOGERR_ERR (1U << 21) +#define LOGERR_ERR_DATE (1U << 22) +#define LOGERR_ERR_HOST (1U << 23) +#define LOGERR_ERR_TAG (1U << 24) +#define LOGERR_ERR_PID (1U << 25) + +/* To build tag support or not. */ +//#define LOGERR_TAG +#if defined(LOGERR_TAG) +void logsettag(const char *); +#endif + +/* Can be called more than once. */ +int logopen(const char *); + +/* Should only be called at program exit. */ +void logclose(void); + +#endif Index: src/logerr.c =================================================================== --- /dev/null +++ src/logerr.c @@ -0,0 +1,497 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * logerr: errx with logging + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logerr.h" + +#ifndef LOGERR_SYSLOG_FACILITY +#define LOGERR_SYSLOG_FACILITY LOG_DAEMON +#endif + +#ifdef SMALL +#undef LOGERR_TAG +#endif + +/* syslog protocol is 1k message max, RFC 3164 section 4.1 */ +#define LOGERR_SYSLOGBUF 1024 + sizeof(int) + sizeof(pid_t) + +#define UNUSED(a) (void)(a) + +struct logctx { + char log_buf[BUFSIZ]; + unsigned int log_opts; + int log_fd; + pid_t log_pid; +#ifndef SMALL + FILE *log_file; +#ifdef LOGERR_TAG + const char *log_tag; +#endif +#endif +}; + +static struct logctx _logctx = { + /* syslog style, but without the hostname or tag. */ + .log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID, + .log_fd = -1, + .log_pid = 0, +}; + +#if defined(__linux__) +/* Poor man's getprogname(3). */ +static char *_logprog; +static const char * +getprogname(void) +{ + const char *p; + + /* Use PATH_MAX + 1 to avoid truncation. */ + if (_logprog == NULL) { + /* readlink(2) does not append a NULL byte, + * so zero the buffer. */ + if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL) + return NULL; + if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) { + free(_logprog); + _logprog = NULL; + return NULL; + } + } + if (_logprog[0] == '[') + return NULL; + p = strrchr(_logprog, '/'); + if (p == NULL) + return _logprog; + return p + 1; +} +#endif + +#ifndef SMALL +/* Write the time, syslog style. month day time - */ +static int +logprintdate(FILE *stream) +{ + struct timeval tv; + time_t now; + struct tm tmnow; + char buf[32]; + + if (gettimeofday(&tv, NULL) == -1) + return -1; + + now = tv.tv_sec; + if (localtime_r(&now, &tmnow) == NULL) + return -1; + if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0) + return -1; + return fprintf(stream, "%s", buf); +} +#endif + +__printflike(3, 0) static int +vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args) +{ + int len = 0, e; + va_list a; +#ifndef SMALL + bool log_pid; +#ifdef LOGERR_TAG + bool log_tag; +#endif + + if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) || + (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE)) + { + if ((e = logprintdate(stream)) == -1) + return -1; + len += e; + } + +#ifdef LOGERR_TAG + log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) || + (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG)); + if (log_tag) { + if (ctx->log_tag == NULL) + ctx->log_tag = getprogname(); + if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1) + return -1; + len += e; + } +#endif + + log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) || + (stream != stderr && ctx->log_opts & LOGERR_LOG_PID)); + if (log_pid) { + pid_t pid; + + if (ctx->log_pid == 0) + pid = getpid(); + else + pid = ctx->log_pid; + if ((e = fprintf(stream, "[%d]", pid)) == -1) + return -1; + len += e; + } + +#ifdef LOGERR_TAG + if (log_tag || log_pid) +#else + if (log_pid) +#endif + { + if ((e = fprintf(stream, ": ")) == -1) + return -1; + len += e; + } +#else + UNUSED(ctx); +#endif + + va_copy(a, args); + e = vfprintf(stream, fmt, a); + if (fputc('\n', stream) == EOF) + e = -1; + else if (e != -1) + e++; + va_end(a); + + return e == -1 ? -1 : len + e; +} + +/* + * NetBSD's gcc has been modified to check for the non standard %m in printf + * like functions and warn noisily about it that they should be marked as + * syslog like instead. + * This is all well and good, but our logger also goes via vfprintf and + * when marked as a sysloglike funcion, gcc will then warn us that the + * function should be printflike instead! + * This creates an infinte loop of gcc warnings. + * Until NetBSD solves this issue, we have to disable a gcc diagnostic + * for our fully standards compliant code in the logger function. + */ +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-format-attribute" +#endif +__printflike(2, 0) static int +vlogmessage(int pri, const char *fmt, va_list args) +{ + struct logctx *ctx = &_logctx; + int len = 0; + + if (ctx->log_fd != -1) { + char buf[LOGERR_SYSLOGBUF]; + pid_t pid; + + memcpy(buf, &pri, sizeof(pri)); + pid = getpid(); + memcpy(buf + sizeof(pri), &pid, sizeof(pid)); + len = vsnprintf(buf + sizeof(pri) + sizeof(pid), + sizeof(buf) - sizeof(pri) - sizeof(pid), + fmt, args); + if (len != -1) + len = (int)write(ctx->log_fd, buf, + ((size_t)++len) + sizeof(pri) + sizeof(pid)); + return len; + } + + if (ctx->log_opts & LOGERR_ERR && + (pri <= LOG_ERR || + (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) || + (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG))) + len = vlogprintf_r(ctx, stderr, fmt, args); + +#ifndef SMALL + if (ctx->log_file != NULL && + (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG))) + len = vlogprintf_r(ctx, ctx->log_file, fmt, args); +#endif + + if (ctx->log_opts & LOGERR_LOG) + vsyslog(pri, fmt, args); + + return len; +} +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)) +#pragma GCC diagnostic pop +#endif + +__printflike(2, 3) void +logmessage(int pri, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogmessage(pri, fmt, args); + va_end(args); +} + +__printflike(2, 0) static void +vlogerrmessage(int pri, const char *fmt, va_list args) +{ + int _errno = errno; + char buf[1024]; + + vsnprintf(buf, sizeof(buf), fmt, args); + logmessage(pri, "%s: %s", buf, strerror(_errno)); + errno = _errno; +} + +__printflike(2, 3) void +logerrmessage(int pri, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogerrmessage(pri, fmt, args); + va_end(args); +} + +void +log_debug(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogerrmessage(LOG_DEBUG, fmt, args); + va_end(args); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogmessage(LOG_DEBUG, fmt, args); + va_end(args); +} + +void +log_info(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogerrmessage(LOG_INFO, fmt, args); + va_end(args); +} + +void +log_infox(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogmessage(LOG_INFO, fmt, args); + va_end(args); +} + +void +log_warn(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogerrmessage(LOG_WARNING, fmt, args); + va_end(args); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogmessage(LOG_WARNING, fmt, args); + va_end(args); +} + +void +log_err(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogerrmessage(LOG_ERR, fmt, args); + va_end(args); +} + +void +log_errx(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogmessage(LOG_ERR, fmt, args); + va_end(args); +} + +int +loggetfd(void) +{ + struct logctx *ctx = &_logctx; + + return ctx->log_fd; +} + +void +logsetfd(int fd) +{ + struct logctx *ctx = &_logctx; + + ctx->log_fd = fd; +#ifndef SMALL + if (fd != -1 && ctx->log_file != NULL) { + fclose(ctx->log_file); + ctx->log_file = NULL; + } +#endif +} + +int +logreadfd(int fd) +{ + struct logctx *ctx = &_logctx; + char buf[LOGERR_SYSLOGBUF]; + int len, pri; + + len = (int)read(fd, buf, sizeof(buf)); + if (len == -1) + return -1; + + /* Ensure we have pri, pid and a terminator */ + if (len < (int)(sizeof(pri) + sizeof(pid_t) + 1) || + buf[len - 1] != '\0') + { + errno = EINVAL; + return -1; + } + + memcpy(&pri, buf, sizeof(pri)); + memcpy(&ctx->log_pid, buf + sizeof(pri), sizeof(ctx->log_pid)); + logmessage(pri, "%s", buf + sizeof(pri) + sizeof(ctx->log_pid)); + ctx->log_pid = 0; + return len; +} + +unsigned int +loggetopts(void) +{ + struct logctx *ctx = &_logctx; + + return ctx->log_opts; +} + +void +logsetopts(unsigned int opts) +{ + struct logctx *ctx = &_logctx; + + ctx->log_opts = opts; + setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO)); +} + +#ifdef LOGERR_TAG +void +logsettag(const char *tag) +{ +#if !defined(SMALL) + struct logctx *ctx = &_logctx; + + ctx->log_tag = tag; +#else + UNUSED(tag); +#endif +} +#endif + +int +logopen(const char *path) +{ + struct logctx *ctx = &_logctx; + int opts = 0; + + /* Cache timezone */ + tzset(); + + (void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf)); + +#ifndef SMALL + if (ctx->log_file != NULL) { + fclose(ctx->log_file); + ctx->log_file = NULL; + } +#endif + + if (ctx->log_opts & LOGERR_LOG_PID) + opts |= LOG_PID; + openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY); + if (path == NULL) + return 1; + +#ifndef SMALL + if ((ctx->log_file = fopen(path, "ae")) == NULL) + return -1; + setlinebuf(ctx->log_file); + return fileno(ctx->log_file); +#else + errno = ENOTSUP; + return -1; +#endif +} + +void +logclose(void) +{ +#ifndef SMALL + struct logctx *ctx = &_logctx; +#endif + + closelog(); +#if defined(__linux__) + free(_logprog); + _logprog = NULL; +#endif +#ifndef SMALL + if (ctx->log_file == NULL) + return; + fclose(ctx->log_file); + ctx->log_file = NULL; +#endif +} Index: src/privsep-bpf.h =================================================================== --- /dev/null +++ src/privsep-bpf.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * 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. + */ + +#ifndef PRIVSEP_BPF_H +#define PRIVSEP_BPF_H + +ssize_t ps_bpf_cmd(struct dhcpcd_ctx *, + struct ps_msghdr *, struct msghdr *); +ssize_t ps_bpf_dispatch(struct dhcpcd_ctx *, + struct ps_msghdr *, struct msghdr *); + +#ifdef ARP +ssize_t ps_bpf_openarp(const struct interface *, const struct in_addr *); +ssize_t ps_bpf_closearp(const struct interface *, const struct in_addr *); +ssize_t ps_bpf_sendarp(const struct interface *, const struct in_addr *, + const void *, size_t); +#endif + +ssize_t ps_bpf_openbootp(const struct interface *); +ssize_t ps_bpf_closebootp(const struct interface *); +ssize_t ps_bpf_sendbootp(const struct interface *, const void *, size_t); +ssize_t ps_bpf_openbootpudp(const struct interface *); +ssize_t ps_bpf_closebootpudp(const struct interface *); +ssize_t ps_bpf_sendbootpudp(const struct interface *, const void *, size_t); +#endif Index: src/privsep-bpf.c =================================================================== --- /dev/null +++ src/privsep-bpf.c @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation BPF Initiator + * 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. + */ + +#include +#include + +/* Need these headers just for if_ether on some OS. */ +#ifndef __NetBSD__ +#include +#include +#include +#endif +#include + +#include +#include +#include +#include +#include +#include + +#include "arp.h" +#include "bpf.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +static void +ps_bpf_recvbpf(void *arg) +{ + struct ps_process *psp = arg; + struct bpf *bpf = psp->psp_bpf; + uint8_t buf[FRAMELEN_MAX]; + ssize_t len; + struct ps_msghdr psm = { + .ps_id = psp->psp_id, + .ps_cmd = psp->psp_id.psi_cmd, + }; + + bpf->bpf_flags &= ~BPF_EOF; + /* A BPF read can read more than one filtered packet at time. + * This mechanism allows us to read each packet from the buffer. */ + while (!(bpf->bpf_flags & BPF_EOF)) { + len = bpf_read(bpf, buf, sizeof(buf)); + if (len == -1) { + int error = errno; + + if (errno != ENETDOWN) + logerr("%s: %s", psp->psp_ifname, __func__); + if (error != ENXIO) + break; + /* If the interface has departed, close the BPF + * socket. This stops log spam if RTM_IFANNOUNCE is + * delayed in announcing the departing interface. */ + eloop_event_delete(psp->psp_ctx->eloop, bpf->bpf_fd); + bpf_close(bpf); + psp->psp_bpf = NULL; + break; + } + if (len == 0) + break; + psm.ps_flags = bpf->bpf_flags; + len = ps_sendpsmdata(psp->psp_ctx, psp->psp_ctx->ps_data_fd, + &psm, buf, (size_t)len); + if (len == -1) + logerr(__func__); + if (len == -1 || len == 0) + break; + } +} + +static ssize_t +ps_bpf_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct ps_process *psp = arg; + struct iovec *iov = msg->msg_iov; + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + switch(psm->ps_cmd) { +#ifdef ARP + case PS_BPF_ARP: /* FALLTHROUGH */ +#endif + case PS_BPF_BOOTP: + break; + default: + /* IPC failure, we should not be processing any commands + * at this point!/ */ + errno = EINVAL; + return -1; + } + + /* We might have had an earlier ENXIO error. */ + if (psp->psp_bpf == NULL) { + errno = ENXIO; + return -1; + } + + return bpf_send(psp->psp_bpf, psp->psp_proto, + iov->iov_base, iov->iov_len); +} + +static void +ps_bpf_recvmsg(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, + ps_bpf_recvmsgcb, arg) == -1) + logerr(__func__); +} + +static int +ps_bpf_start_bpf(void *arg) +{ + struct ps_process *psp = arg; + struct dhcpcd_ctx *ctx = psp->psp_ctx; + char *addr; + struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr; + + if (ia->s_addr == INADDR_ANY) { + ia = NULL; + addr = NULL; + } else + addr = inet_ntoa(*ia); + setproctitle("[BPF %s] %s%s%s", psp->psp_protostr, psp->psp_ifname, + addr != NULL ? " " : "", addr != NULL ? addr : ""); + ps_freeprocesses(ctx, psp); + + psp->psp_bpf = bpf_open(&psp->psp_ifp, psp->psp_filter, ia); + if (psp->psp_bpf == NULL) + logerr("%s: bpf_open",__func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd(psp->psp_bpf->bpf_fd) == -1) + logerr("%s: ps_rights_limit_fd", __func__); +#endif + else if (eloop_event_add(ctx->eloop, + psp->psp_bpf->bpf_fd, ps_bpf_recvbpf, psp) == -1) + logerr("%s: eloop_event_add", __func__); + else { + psp->psp_work_fd = psp->psp_bpf->bpf_fd; + return 0; + } + + eloop_exit(ctx->eloop, EXIT_FAILURE); + return -1; +} + +ssize_t +ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) +{ + uint16_t cmd; + struct ps_process *psp; + pid_t start; + struct iovec *iov = msg->msg_iov; + struct interface *ifp; + + cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); + psp = ps_findprocess(ctx, &psm->ps_id); + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + switch (cmd) { +#ifdef ARP + case PS_BPF_ARP: /* FALLTHROUGH */ +#endif + case PS_BPF_BOOTP: + break; + default: + logerrx("%s: unknown command %x", __func__, psm->ps_cmd); + errno = ENOTSUP; + return -1; + } + + if (!(psm->ps_cmd & PS_START)) { + errno = EINVAL; + return -1; + } + + if (psp != NULL) + return 1; + + psp = ps_newprocess(ctx, &psm->ps_id); + if (psp == NULL) + return -1; + + ifp = &psp->psp_ifp; + assert(msg->msg_iovlen == 1); + assert(iov->iov_len == sizeof(*ifp)); + memcpy(ifp, iov->iov_base, sizeof(*ifp)); + ifp->ctx = psp->psp_ctx; + ifp->options = NULL; + memset(ifp->if_data, 0, sizeof(ifp->if_data)); + + memcpy(psp->psp_ifname, ifp->name, sizeof(psp->psp_ifname)); + + switch (cmd) { +#ifdef ARP + case PS_BPF_ARP: + psp->psp_proto = ETHERTYPE_ARP; + psp->psp_protostr = "ARP"; + psp->psp_filter = bpf_arp; + break; +#endif + case PS_BPF_BOOTP: + psp->psp_proto = ETHERTYPE_IP; + psp->psp_protostr = "BOOTP"; + psp->psp_filter = bpf_bootp; + break; + } + + start = ps_dostart(ctx, + &psp->psp_pid, &psp->psp_fd, + ps_bpf_recvmsg, NULL, psp, + ps_bpf_start_bpf, NULL, + PSF_DROPPRIVS); + switch (start) { + case -1: + ps_freeprocess(psp); + return -1; + case 0: + ps_entersandbox("stdio", NULL); + break; + default: + logdebugx("%s: spawned BPF %s on PID %d", + psp->psp_ifname, psp->psp_protostr, start); + break; + } + return start; +} + +ssize_t +ps_bpf_dispatch(struct dhcpcd_ctx *ctx, + struct ps_msghdr *psm, struct msghdr *msg) +{ + struct iovec *iov = msg->msg_iov; + struct interface *ifp; + uint8_t *bpf; + size_t bpf_len; + + switch (psm->ps_cmd) { +#ifdef ARP + case PS_BPF_ARP: +#endif + case PS_BPF_BOOTP: + break; + default: + errno = ENOTSUP; + return -1; + } + + ifp = if_findindex(ctx->ifaces, psm->ps_id.psi_ifindex); + /* interface may have departed .... */ + if (ifp == NULL) + return -1; + + bpf = iov->iov_base; + bpf_len = iov->iov_len; + + switch (psm->ps_cmd) { +#ifdef ARP + case PS_BPF_ARP: + arp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags); + break; +#endif + case PS_BPF_BOOTP: + dhcp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags); + break; + } + + return 1; +} + +static ssize_t +ps_bpf_send(const struct interface *ifp, const struct in_addr *ia, + uint16_t cmd, const void *data, size_t len) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_ifindex = ifp->index, + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + }, + }; + + if (ia != NULL) + psm.ps_id.psi_addr.psa_in_addr = *ia; + + return ps_sendpsmdata(ctx, ctx->ps_root_fd, &psm, data, len); +} + +#ifdef ARP +ssize_t +ps_bpf_openarp(const struct interface *ifp, const struct in_addr *ia) +{ + + assert(ia != NULL); + return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_START, + ifp, sizeof(*ifp)); +} + +ssize_t +ps_bpf_closearp(const struct interface *ifp, const struct in_addr *ia) +{ + + return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_STOP, NULL, 0); +} + +ssize_t +ps_bpf_sendarp(const struct interface *ifp, const struct in_addr *ia, + const void *data, size_t len) +{ + + assert(ia != NULL); + return ps_bpf_send(ifp, ia, PS_BPF_ARP, data, len); +} +#endif + +ssize_t +ps_bpf_openbootp(const struct interface *ifp) +{ + + return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_START, + ifp, sizeof(*ifp)); +} + +ssize_t +ps_bpf_closebootp(const struct interface *ifp) +{ + + return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_STOP, NULL, 0); +} + +ssize_t +ps_bpf_sendbootp(const struct interface *ifp, const void *data, size_t len) +{ + + return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP, data, len); +} Index: src/privsep-bsd.c =================================================================== --- /dev/null +++ src/privsep-bsd.c @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, BSD driver + * Copyright (c) 2006-2021 Roy Marples + * 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 + +/* Need these for filtering the ioctls */ +#include +#include +#include +#include +#include +#include +#ifdef __NetBSD__ +#include +#include /* Needs netinet/if_ether.h */ +#elif defined(__DragonFly__) +#include +#else +#include +#endif +#ifdef __DragonFly__ +# include +#else +# include +# include +#endif + +#include +#include +#include + +#include "dhcpcd.h" +#include "logerr.h" +#include "privsep.h" + +static ssize_t +ps_root_doioctldom(int domain, unsigned long req, void *data, size_t len) +{ + int s, err; + + /* Only allow these ioctls */ + switch(req) { +#ifdef SIOCGIFDATA + case SIOCGIFDATA: /* FALLTHROUGH */ +#endif +#ifdef SIOCG80211NWID + case SIOCG80211NWID: /* FALLTHROUGH */ +#endif +#ifdef SIOCGETVLAN + case SIOCGETVLAN: /* FALLTHROUGH */ +#endif +#ifdef SIOCIFAFATTACH + case SIOCIFAFATTACH: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFXFLAGS + case SIOCSIFXFLAGS: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFINFO_FLAGS + case SIOCSIFINFO_FLAGS: /* FALLTHROUGH */ +#endif +#ifdef SIOCSRTRFLUSH_IN6 + case SIOCSRTRFLUSH_IN6: /* FALLTHROUGH */ + case SIOCSPFXFLUSH_IN6: /* FALLTHROUGH */ +#endif +#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) + case SIOCALIFADDR: /* FALLTHROUGH */ + case SIOCDLIFADDR: /* FALLTHROUGH */ +#else + case SIOCSIFLLADDR: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFINFO_IN6 + case SIOCSIFINFO_IN6: /* FALLTHROUGH */ +#endif + case SIOCAIFADDR_IN6: /* FALLTHROUGH */ + case SIOCDIFADDR_IN6: + break; + default: + errno = EPERM; + return -1; + } + + s = socket(domain, SOCK_DGRAM, 0); + if (s == -1) + return -1; + err = ioctl(s, req, data, len); + close(s); + return err; +} + +static ssize_t +ps_root_doroute(void *data, size_t len) +{ + int s; + ssize_t err; + + s = socket(PF_ROUTE, SOCK_RAW, 0); + if (s != -1) + err = write(s, data, len); + else + err = -1; + if (s != -1) + close(s); + return err; +} + +#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) +static ssize_t +ps_root_doindirectioctl(unsigned long req, void *data, size_t len) +{ + char *p = data; + struct ifreq ifr = { .ifr_flags = 0 }; + + /* ioctl filtering is done in ps_root_doioctldom */ + + if (len < IFNAMSIZ + 1) { + errno = EINVAL; + return -1; + } + + strlcpy(ifr.ifr_name, p, IFNAMSIZ); + len -= IFNAMSIZ; + memmove(data, p + IFNAMSIZ, len); + ifr.ifr_data = data; + + return ps_root_doioctldom(PF_INET, req, &ifr, sizeof(ifr)); +} +#endif + +#ifdef HAVE_PLEDGE +static ssize_t +ps_root_doifignoregroup(void *data, size_t len) +{ + int s, err; + + if (len == 0 || ((const char *)data)[len - 1] != '\0') { + errno = EINVAL; + return -1; + } + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s == -1) + return -1; + err = if_ignoregroup(s, data); + close(s); + return err; +} +#endif + +ssize_t +ps_root_os(struct ps_msghdr *psm, struct msghdr *msg, + void **rdata, size_t *rlen) +{ + struct iovec *iov = msg->msg_iov; + void *data = iov->iov_base; + size_t len = iov->iov_len; + ssize_t err; + + switch (psm->ps_cmd) { + case PS_IOCTLLINK: + err = ps_root_doioctldom(PF_LINK, psm->ps_flags, data, len); + break; + case PS_IOCTL6: + err = ps_root_doioctldom(PF_INET6, psm->ps_flags, data, len); + break; + case PS_ROUTE: + return ps_root_doroute(data, len); +#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) + case PS_IOCTLINDIRECT: + err = ps_root_doindirectioctl(psm->ps_flags, data, len); + break; +#endif +#ifdef HAVE_PLEDGE + case PS_IFIGNOREGRP: + return ps_root_doifignoregroup(data, len); +#endif + default: + errno = ENOTSUP; + return -1; + } + + if (err != -1) { + *rdata = data; + *rlen = len; + } + return err; +} + +static ssize_t +ps_root_ioctldom(struct dhcpcd_ctx *ctx, uint16_t domain, unsigned long request, + void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, domain, + request, data, len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_ioctllink(struct dhcpcd_ctx *ctx, unsigned long request, + void *data, size_t len) +{ + + return ps_root_ioctldom(ctx, PS_IOCTLLINK, request, data, len); +} + +ssize_t +ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request, + void *data, size_t len) +{ + + return ps_root_ioctldom(ctx, PS_IOCTL6, request, data, len); +} + +ssize_t +ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_ROUTE, 0, data, len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) +ssize_t +ps_root_indirectioctl(struct dhcpcd_ctx *ctx, unsigned long request, + const char *ifname, void *data, size_t len) +{ + char buf[PS_BUFLEN]; + + if (IFNAMSIZ + len > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + + strlcpy(buf, ifname, IFNAMSIZ); + memcpy(buf + IFNAMSIZ, data, len); + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTLINDIRECT, + request, buf, IFNAMSIZ + len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_ifignoregroup(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IFIGNOREGRP, 0, + ifname, strlen(ifname) + 1) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} +#endif Index: src/privsep-control.h =================================================================== --- /dev/null +++ src/privsep-control.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * 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. + */ + +#ifndef PRIVSEP_CTL_H +#define PRIVSEP_CTL_H + +#define IN_PRIVSEP_CONTROLLER(ctx) \ + (IN_PRIVSEP((ctx)) && (ctx)->ps_control_pid == getpid()) + +pid_t ps_ctl_start(struct dhcpcd_ctx *); +int ps_ctl_stop(struct dhcpcd_ctx *); +ssize_t ps_ctl_handleargs(struct fd_list *, char *, size_t); +ssize_t ps_ctl_sendargs(struct fd_list *, void *, size_t); +ssize_t ps_ctl_sendeof(struct fd_list *fd); + +#endif Index: src/privsep-control.c =================================================================== --- /dev/null +++ src/privsep-control.c @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, control proxy + * Copyright (c) 2006-2021 Roy Marples + * 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 +#include +#include + +#include "dhcpcd.h" +#include "control.h" +#include "eloop.h" +#include "logerr.h" +#include "privsep.h" + +static int +ps_ctl_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + sa_family_t af; + + if (ctx->options & DHCPCD_MANAGER) { + setproctitle("[control proxy]"); + af = AF_UNSPEC; + } else { + setproctitle("[control proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + if ((ctx->options & + (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4) + af = AF_INET; + else if ((ctx->options & + (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6) + af = AF_INET6; + else + af = AF_UNSPEC; + } + + ctx->ps_control_pid = getpid(); + + return control_start(ctx, + ctx->options & DHCPCD_MANAGER ? NULL : *ctx->ifv, af); +} + +static ssize_t +ps_ctl_recvmsgcb(void *arg, struct ps_msghdr *psm, __unused struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (psm->ps_cmd != PS_CTL_EOF) { + errno = ENOTSUP; + return -1; + } + + if (ctx->ps_control_client != NULL) + ctx->ps_control_client = NULL; + return 0; +} + +static void +ps_ctl_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_recvmsgcb, ctx) == -1) + logerr(__func__); +} + +ssize_t +ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) +{ + + /* Make any change here in dhcpcd.c as well. */ + if (strncmp(data, "--version", + MIN(strlen("--version"), len)) == 0) { + return control_queue(fd, UNCONST(VERSION), + strlen(VERSION) + 1); + } else if (strncmp(data, "--getconfigfile", + MIN(strlen("--getconfigfile"), len)) == 0) { + return control_queue(fd, UNCONST(fd->ctx->cffile), + strlen(fd->ctx->cffile) + 1); + } else if (strncmp(data, "--listen", + MIN(strlen("--listen"), len)) == 0) { + fd->flags |= FD_LISTEN; + return 0; + } + + if (fd->ctx->ps_control_client != NULL && + fd->ctx->ps_control_client != fd) + { + logerrx("%s: cannot handle another client", __func__); + return 0; + } + return 1; +} + +static ssize_t +ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + struct iovec *iov = msg->msg_iov; + struct fd_list *fd; + unsigned int fd_flags = FD_SENDLEN; + + switch (psm->ps_flags) { + case PS_CTL_PRIV: + break; + case PS_CTL_UNPRIV: + fd_flags |= FD_UNPRIV; + break; + } + + switch (psm->ps_cmd) { + case PS_CTL: + if (msg->msg_iovlen != 1) { + errno = EINVAL; + return -1; + } + if (ctx->ps_control_client != NULL) { + logerrx("%s: cannot handle another client", __func__); + return 0; + } + fd = control_new(ctx, ctx->ps_control_data_fd, fd_flags); + if (fd == NULL) + return -1; + ctx->ps_control_client = fd; + control_recvdata(fd, iov->iov_base, iov->iov_len); + break; + case PS_CTL_EOF: + control_free(ctx->ps_control_client); + break; + default: + errno = ENOTSUP; + return -1; + } + return 0; +} + +static void +ps_ctl_dodispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_dispatch, ctx) == -1) + logerr(__func__); +} + +static void +ps_ctl_recv(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char buf[BUFSIZ]; + ssize_t len; + + errno = 0; + len = read(ctx->ps_control_data_fd, buf, sizeof(buf)); + if (len == -1 || len == 0) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + if (ctx->ps_control_client == NULL) /* client disconnected */ + return; + errno = 0; + if (control_queue(ctx->ps_control_client, buf, (size_t)len) == -1) + logerr("%s: control_queue", __func__); +} + +static void +ps_ctl_listen(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char buf[BUFSIZ]; + ssize_t len; + struct fd_list *fd; + + errno = 0; + len = read(ctx->ps_control->fd, buf, sizeof(buf)); + if (len == -1 || len == 0) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + /* Send to our listeners */ + TAILQ_FOREACH(fd, &ctx->control_fds, next) { + if (!(fd->flags & FD_LISTEN)) + continue; + if (control_queue(fd, buf, (size_t)len)== -1) + logerr("%s: control_queue", __func__); + } +} + +pid_t +ps_ctl_start(struct dhcpcd_ctx *ctx) +{ + int data_fd[2], listen_fd[2]; + pid_t pid; + + if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, data_fd) == -1 || + xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(data_fd) == -1 || + ps_rights_limit_fdpair(listen_fd) == -1) + return -1; +#endif + + pid = ps_dostart(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd, + ps_ctl_recvmsg, ps_ctl_dodispatch, ctx, + ps_ctl_startcb, NULL, + PSF_DROPPRIVS); + + if (pid == -1) + return -1; + else if (pid != 0) { + ctx->ps_control_data_fd = data_fd[1]; + close(data_fd[0]); + ctx->ps_control = control_new(ctx, + listen_fd[1], FD_SENDLEN | FD_LISTEN); + if (ctx->ps_control == NULL) + return -1; + close(listen_fd[0]); + return pid; + } + + ctx->ps_control_data_fd = data_fd[0]; + close(data_fd[1]); + if (eloop_event_add(ctx->eloop, ctx->ps_control_data_fd, + ps_ctl_recv, ctx) == -1) + return -1; + + ctx->ps_control = control_new(ctx, + listen_fd[0], 0); + close(listen_fd[1]); + if (ctx->ps_control == NULL) + return -1; + if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, + ps_ctl_listen, ctx) == -1) + return -1; + + ps_entersandbox("stdio inet", NULL); + return 0; +} + +int +ps_ctl_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd); +} + +ssize_t +ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len) +{ + struct dhcpcd_ctx *ctx = fd->ctx; + + if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd) + logerrx("%s: cannot deal with another client", __func__); + ctx->ps_control_client = fd; + return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL, + fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV, + data, len); +} + +ssize_t +ps_ctl_sendeof(struct fd_list *fd) +{ + struct dhcpcd_ctx *ctx = fd->ctx; + + return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL_EOF, 0, NULL, 0); +} Index: src/privsep-inet.h =================================================================== --- /dev/null +++ src/privsep-inet.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * 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. + */ + +#ifndef PRIVSEP_INET_H +#define PRIVSEP_INET_H + +bool ps_inet_canstart(const struct dhcpcd_ctx *); +pid_t ps_inet_start(struct dhcpcd_ctx *); +int ps_inet_stop(struct dhcpcd_ctx *); +ssize_t ps_inet_cmd(struct dhcpcd_ctx *, struct ps_msghdr *, struct msghdr *); +ssize_t ps_inet_dispatch(void *, struct ps_msghdr *, struct msghdr *); + +#ifdef INET +struct ipv4_addr; +ssize_t ps_inet_openbootp(struct ipv4_addr *); +ssize_t ps_inet_closebootp(struct ipv4_addr *); +ssize_t ps_inet_sendbootp(struct interface *, const struct msghdr *); +#endif + +#ifdef INET6 +struct ipv6_addr; +#ifdef __sun +ssize_t ps_inet_opennd(struct interface *); +ssize_t ps_inet_closend(struct interface *); +#endif +ssize_t ps_inet_sendnd(struct interface *, const struct msghdr *); +#ifdef DHCP6 +ssize_t ps_inet_opendhcp6(struct ipv6_addr *); +ssize_t ps_inet_closedhcp6(struct ipv6_addr *); +ssize_t ps_inet_senddhcp6(struct interface *, const struct msghdr *); +#endif /* DHCP6 */ +#endif /* INET6 */ +#endif Index: src/privsep-inet.c =================================================================== --- /dev/null +++ src/privsep-inet.c @@ -0,0 +1,717 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, network proxy + * Copyright (c) 2006-2021 Roy Marples + * 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 +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "arp.h" +#include "bpf.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +#ifdef INET +static void +ps_inet_recvbootp(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvmsg(ctx, ctx->udp_rfd, PS_BOOTP, ctx->ps_inet_fd) == -1) + logerr(__func__); +} +#endif + +#ifdef INET6 +static void +ps_inet_recvra(void *arg) +{ +#ifdef __sun + struct interface *ifp = arg; + struct rs_state *state = RS_STATE(ifp); + struct dhcpcd_ctx *ctx = ifp->ctx; + + if (ps_recvmsg(ctx, state->nd_fd, PS_ND, ctx->ps_inet_fd) == -1) + logerr(__func__); +#else + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvmsg(ctx, ctx->nd_fd, PS_ND, ctx->ps_inet_fd) == -1) + logerr(__func__); +#endif +} +#endif + +#ifdef DHCP6 +static void +ps_inet_recvdhcp6(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvmsg(ctx, ctx->dhcp6_rfd, PS_DHCP6, ctx->ps_inet_fd) == -1) + logerr(__func__); +} +#endif + +bool +ps_inet_canstart(const struct dhcpcd_ctx *ctx) +{ + +#ifdef INET + if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MANAGER)) == + (DHCPCD_IPV4 | DHCPCD_MANAGER)) + return true; +#endif +#if defined(INET6) && !defined(__sun) + if (ctx->options & DHCPCD_IPV6) + return true; +#endif +#ifdef DHCP6 + if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MANAGER)) == + (DHCPCD_IPV6 | DHCPCD_MANAGER)) + return true; +#endif + + return false; +} + +static int +ps_inet_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + int ret = 0; + + if (ctx->options & DHCPCD_MANAGER) + setproctitle("[network proxy]"); + else + setproctitle("[network proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + + /* This end is the main engine, so it's useless for us. */ + close(ctx->ps_data_fd); + ctx->ps_data_fd = -1; + + errno = 0; + +#ifdef INET + if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MANAGER)) == + (DHCPCD_IPV4 | DHCPCD_MANAGER)) + { + ctx->udp_rfd = dhcp_openudp(NULL); + if (ctx->udp_rfd == -1) + logerr("%s: dhcp_open", __func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd_rdonly(ctx->udp_rfd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + close(ctx->udp_rfd); + ctx->udp_rfd = -1; + } +#endif + else if (eloop_event_add(ctx->eloop, ctx->udp_rfd, + ps_inet_recvbootp, ctx) == -1) + { + logerr("%s: eloop_event_add DHCP", __func__); + close(ctx->udp_rfd); + ctx->udp_rfd = -1; + } else + ret++; + } +#endif +#if defined(INET6) && !defined(__sun) + if (ctx->options & DHCPCD_IPV6) { + ctx->nd_fd = ipv6nd_open(true); + if (ctx->nd_fd == -1) + logerr("%s: ipv6nd_open", __func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd_rdonly(ctx->nd_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + close(ctx->nd_fd); + ctx->nd_fd = -1; + } +#endif + else if (eloop_event_add(ctx->eloop, ctx->nd_fd, + ps_inet_recvra, ctx) == -1) + { + logerr("%s: eloop_event_add RA", __func__); + close(ctx->nd_fd); + ctx->nd_fd = -1; + } else + ret++; + } +#endif +#ifdef DHCP6 + if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MANAGER)) == + (DHCPCD_IPV6 | DHCPCD_MANAGER)) + { + ctx->dhcp6_rfd = dhcp6_openudp(0, NULL); + if (ctx->dhcp6_rfd == -1) + logerr("%s: dhcp6_open", __func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd_rdonly(ctx->dhcp6_rfd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + close(ctx->dhcp6_rfd); + ctx->dhcp6_rfd = -1; + } +#endif + else if (eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, + ps_inet_recvdhcp6, ctx) == -1) + { + logerr("%s: eloop_event_add DHCP6", __func__); + close(ctx->dhcp6_rfd); + ctx->dhcp6_rfd = -1; + } else + ret++; + } +#endif + + if (ret == 0 && errno == 0) { + errno = ENXIO; + return -1; + } + return ret; +} + +static bool +ps_inet_validudp(struct msghdr *msg, uint16_t sport, uint16_t dport) +{ + struct udphdr udp; + struct iovec *iov = msg->msg_iov; + + if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(udp)) { + errno = EINVAL; + return false; + } + + memcpy(&udp, iov->iov_base, sizeof(udp)); + if (udp.uh_sport != htons(sport) || udp.uh_dport != htons(dport)) { + errno = EPERM; + return false; + } + return true; +} + +#ifdef INET6 +static bool +ps_inet_validnd(struct msghdr *msg) +{ + struct icmp6_hdr icmp6; + struct iovec *iov = msg->msg_iov; + + if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(icmp6)) { + errno = EINVAL; + return false; + } + + memcpy(&icmp6, iov->iov_base, sizeof(icmp6)); + switch(icmp6.icmp6_type) { + case ND_ROUTER_SOLICIT: + case ND_NEIGHBOR_ADVERT: + break; + default: + errno = EPERM; + return false; + } + + return true; +} +#endif + +static ssize_t +ps_inet_sendmsg(struct dhcpcd_ctx *ctx, + struct ps_msghdr *psm, struct msghdr *msg) +{ + struct ps_process *psp; + int s; + + psp = ps_findprocess(ctx, &psm->ps_id); + if (psp != NULL) { + s = psp->psp_work_fd; + goto dosend; + } + + switch (psm->ps_cmd) { +#ifdef INET + case PS_BOOTP: + if (!ps_inet_validudp(msg, BOOTPC, BOOTPS)) + return -1; + s = ctx->udp_wfd; + break; +#endif +#if defined(INET6) && !defined(__sun) + case PS_ND: + if (!ps_inet_validnd(msg)) + return -1; + s = ctx->nd_fd; + break; +#endif +#ifdef DHCP6 + case PS_DHCP6: + if (!ps_inet_validudp(msg, DHCP6_CLIENT_PORT,DHCP6_SERVER_PORT)) + return -1; + s = ctx->dhcp6_wfd; + break; +#endif + default: + errno = EINVAL; + return -1; + } + +dosend: + return sendmsg(s, msg, 0); +} + +static void +ps_inet_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + /* Receive shutdown */ + if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, NULL, NULL) == -1) + logerr(__func__); +} + +ssize_t +ps_inet_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + + switch (psm->ps_cmd) { +#ifdef INET + case PS_BOOTP: + dhcp_recvmsg(ctx, msg); + break; +#endif +#ifdef INET6 + case PS_ND: + ipv6nd_recvmsg(ctx, msg); + break; +#endif +#ifdef DHCP6 + case PS_DHCP6: + dhcp6_recvmsg(ctx, msg, NULL); + break; +#endif + default: + errno = ENOTSUP; + return -1; + } + return 1; +} + +static void +ps_inet_dodispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, ps_inet_dispatch, ctx) == -1) + logerr(__func__); +} + +pid_t +ps_inet_start(struct dhcpcd_ctx *ctx) +{ + pid_t pid; + + pid = ps_dostart(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd, + ps_inet_recvmsg, ps_inet_dodispatch, ctx, + ps_inet_startcb, NULL, + PSF_DROPPRIVS); + + if (pid == 0) + ps_entersandbox("stdio", NULL); + + return pid; +} + +int +ps_inet_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd); +} + +#ifdef INET +static void +ps_inet_recvinbootp(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, + PS_BOOTP, psp->psp_ctx->ps_data_fd) == -1) + logerr(__func__); +} + +static int +ps_inet_listenin(void *arg) +{ + struct ps_process *psp = arg; + struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr; + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, ia, buf, sizeof(buf)); + setproctitle("[network proxy] %s", buf); + + psp->psp_work_fd = dhcp_openudp(ia); + if (psp->psp_work_fd == -1) { + logerr(__func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + return -1; + } +#endif + + if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, + ps_inet_recvinbootp, psp) == -1) + { + logerr("%s: eloop_event_add DHCP", __func__); + return -1; + } + + logdebugx("spawned listener %s on PID %d", buf, getpid()); + return 0; +} +#endif + +#if defined(INET6) && defined(__sun) +static void +ps_inet_recvin6nd(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, + PS_ND, psp->psp_ctx->ps_data_fd) == -1) + logerr(__func__); +} + +static int +ps_inet_listennd(void *arg) +{ + struct ps_process *psp = arg; + + setproctitle("[ND network proxy]"); + + psp->psp_work_fd = ipv6nd_open(&psp->psp_ifp); + if (psp->psp_work_fd == -1) { + logerr(__func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + return -1; + } +#endif + + if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, + ps_inet_recvin6nd, psp) == -1) + { + logerr(__func__); + return -1; + } + + logdebugx("spawned ND listener on PID %d", getpid()); + return 0; +} +#endif + +#ifdef DHCP6 +static void +ps_inet_recvin6dhcp6(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, + PS_DHCP6, psp->psp_ctx->ps_data_fd) == -1) + logerr(__func__); +} + +static int +ps_inet_listenin6(void *arg) +{ + struct ps_process *psp = arg; + struct in6_addr *ia = &psp->psp_id.psi_addr.psa_in6_addr; + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, ia, buf, sizeof(buf)); + setproctitle("[network proxy] %s", buf); + + psp->psp_work_fd = dhcp6_openudp(psp->psp_id.psi_ifindex, ia); + if (psp->psp_work_fd == -1) { + logerr(__func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + return -1; + } +#endif + + if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, + ps_inet_recvin6dhcp6, psp) == -1) + { + logerr("%s: eloop_event_add DHCP", __func__); + return -1; + } + + logdebugx("spawned listener %s on PID %d", buf, getpid()); + return 0; +} +#endif + +static void +ps_inet_recvmsgpsp(void *arg) +{ + struct ps_process *psp = arg; + + /* Receive shutdown. */ + if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, NULL, NULL) == -1) + logerr(__func__); +} + +ssize_t +ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) +{ + uint16_t cmd; + struct ps_process *psp; + int (*start_func)(void *); + pid_t start; + + cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); + if (cmd == psm->ps_cmd) + return ps_inet_sendmsg(ctx, psm, msg); + + psp = ps_findprocess(ctx, &psm->ps_id); + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + if (psm->ps_cmd & PS_STOP) { + assert(psp == NULL); + return 0; + } + + switch (cmd) { +#ifdef INET + case PS_BOOTP: + start_func = ps_inet_listenin; + break; +#endif +#ifdef INET6 +#ifdef __sun + case PS_ND: + start_func = ps_inet_listennd; + break; +#endif +#ifdef DHCP6 + case PS_DHCP6: + start_func = ps_inet_listenin6; + break; +#endif +#endif + default: + logerrx("%s: unknown command %x", __func__, psm->ps_cmd); + errno = ENOTSUP; + return -1; + } + + if (!(psm->ps_cmd & PS_START)) { + errno = EINVAL; + return -1; + } + + if (psp != NULL) + return 1; + + psp = ps_newprocess(ctx, &psm->ps_id); + if (psp == NULL) + return -1; + + start = ps_dostart(ctx, + &psp->psp_pid, &psp->psp_fd, + ps_inet_recvmsgpsp, NULL, psp, + start_func, NULL, + PSF_DROPPRIVS); + switch (start) { + case -1: + ps_freeprocess(psp); + return -1; + case 0: + ps_entersandbox("stdio", NULL); + break; + default: + break; + } + return start; +} + +#ifdef INET +static ssize_t +ps_inet_in_docmd(struct ipv4_addr *ia, uint16_t cmd, const struct msghdr *msg) +{ + assert(ia != NULL); + struct dhcpcd_ctx *ctx = ia->iface->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + .psi_ifindex = ia->iface->index, + .psi_addr.psa_in_addr = ia->addr, + }, + }; + + return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); +} + +ssize_t +ps_inet_openbootp(struct ipv4_addr *ia) +{ + + return ps_inet_in_docmd(ia, PS_START | PS_BOOTP, NULL); +} + +ssize_t +ps_inet_closebootp(struct ipv4_addr *ia) +{ + + return ps_inet_in_docmd(ia, PS_STOP | PS_BOOTP, NULL); +} + +ssize_t +ps_inet_sendbootp(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_BOOTP, 0, msg); +} +#endif /* INET */ + +#ifdef INET6 +#ifdef __sun +static ssize_t +ps_inet_ifp_docmd(struct interface *ifp, uint16_t cmd, const struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + .psi_ifindex = ifp->index, + }, + }; + + return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); +} + +ssize_t +ps_inet_opennd(struct interface *ifp) +{ + + return ps_inet_ifp_docmd(ifp, PS_ND | PS_START, NULL); +} + +ssize_t +ps_inet_closend(struct interface *ifp) +{ + + return ps_inet_ifp_docmd(ifp, PS_ND | PS_STOP, NULL); +} + +ssize_t +ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_inet_ifp_docmd(ifp, PS_ND, msg); +} +#else +ssize_t +ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_ND, 0, msg); +} +#endif + +#ifdef DHCP6 +static ssize_t +ps_inet_in6_docmd(struct ipv6_addr *ia, uint16_t cmd, const struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = ia->iface->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + .psi_ifindex = ia->iface->index, + .psi_addr.psa_in6_addr = ia->addr, + }, + }; + + return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); +} + +ssize_t +ps_inet_opendhcp6(struct ipv6_addr *ia) +{ + + return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_START, NULL); +} + +ssize_t +ps_inet_closedhcp6(struct ipv6_addr *ia) +{ + + return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_STOP, NULL); +} + +ssize_t +ps_inet_senddhcp6(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_DHCP6, 0, msg); +} +#endif /* DHCP6 */ +#endif /* INET6 */ Index: src/privsep-linux.c =================================================================== --- /dev/null +++ src/privsep-linux.c @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, Linux driver + * Copyright (c) 2006-2021 Roy Marples + * 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 +#include +#include +#include +#include /* For TCGETS */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "if.h" +#include "logerr.h" +#include "privsep.h" + +/* + * Set this to debug SECCOMP. + * Then run dhcpcd with strace -f and strace will even translate + * the failing syscall into the __NR_name define we need to use below. + * DO NOT ENABLE THIS FOR PRODUCTION BUILDS! + */ +//#define SECCOMP_FILTER_DEBUG + +static ssize_t +ps_root_dosendnetlink(int protocol, struct msghdr *msg) +{ + struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; + int s; + unsigned char buf[16 * 1024]; + struct iovec riov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + ssize_t retval; + + if ((s = if_linksocket(&snl, protocol, 0)) == -1) + return -1; + + if (sendmsg(s, msg, 0) == -1) { + retval = -1; + goto out; + } + + retval = if_getnetlink(NULL, &riov, s, 0, NULL, NULL); +out: + close(s); + return retval; +} + +ssize_t +ps_root_os(struct ps_msghdr *psm, struct msghdr *msg, + __unused void **rdata, __unused size_t *rlen) +{ + + switch (psm->ps_cmd) { + case PS_ROUTE: + return ps_root_dosendnetlink((int)psm->ps_flags, msg); + default: + errno = ENOTSUP; + return -1; + } +} + +ssize_t +ps_root_sendnetlink(struct dhcpcd_ctx *ctx, int protocol, struct msghdr *msg) +{ + + if (ps_sendmsg(ctx, ctx->ps_root_fd, PS_ROUTE, + (unsigned long)protocol, msg) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +#if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_ARG_LO 0 +# define SECCOMP_ARG_HI sizeof(uint32_t) +#elif (BYTE_ORDER == BIG_ENDIAN) +# define SECCOMP_ARG_LO sizeof(uint32_t) +# define SECCOMP_ARG_HI 0 +#else +# error "Uknown endian" +#endif + +#define SECCOMP_ALLOW(_nr) \ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (_nr), 0, 1), \ + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) + +#define SECCOMP_ALLOW_ARG(_nr, _arg, _val) \ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (_nr), 0, 6), \ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, \ + offsetof(struct seccomp_data, args[(_arg)]) + SECCOMP_ARG_LO), \ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, \ + ((_val) & 0xffffffff), 0, 3), \ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, \ + offsetof(struct seccomp_data, args[(_arg)]) + SECCOMP_ARG_HI), \ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, \ + (((uint32_t)((uint64_t)(_val) >> 32)) & 0xffffffff), 0, 1), \ + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), \ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, \ + offsetof(struct seccomp_data, nr)) + +#ifdef SECCOMP_FILTER_DEBUG +#define SECCOMP_FILTER_FAIL SECCOMP_RET_TRAP +#else +#define SECCOMP_FILTER_FAIL SECCOMP_RET_KILL +#endif + +/* I personally find this quite nutty. + * Why can a system header not define a default for this? */ +#if defined(__i386__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64 +#elif defined(__arc__) +# if defined(__A7__) +# if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCOMPACT +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCOMPACTBE +# endif +# elif defined(__HS__) +# if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCV2 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCV2BE +# endif +# else +# error "Platform does not support seccomp filter yet" +# endif +#elif defined(__arm__) +# ifndef EM_ARM +# define EM_ARM 40 +# endif +# if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARM +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARMEB +# endif +#elif defined(__aarch64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_AARCH64 +#elif defined(__alpha__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ALPHA +#elif defined(__hppa__) +# if defined(__LP64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PARISC64 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PARISC +# endif +#elif defined(__ia64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_IA64 +#elif defined(__microblaze__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MICROBLAZE +#elif defined(__m68k__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_M68K +#elif defined(__mips__) +# if defined(__MIPSEL__) +# if defined(__LP64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPSEL64 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPSEL +# endif +# elif defined(__LP64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPS64 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_MIPS +# endif +#elif defined(__nds32__) +# if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_NDS32 +#else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_NDS32BE +#endif +#elif defined(__nios2__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_NIOS2 +#elif defined(__or1k__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_OPENRISC +#elif defined(__powerpc64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PPC64 +#elif defined(__powerpc__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PPC +#elif defined(__riscv) +# if defined(__LP64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_RISCV64 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_RISCV32 +# endif +#elif defined(__s390x__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_S390X +#elif defined(__s390__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_S390 +#elif defined(__sh__) +# if defined(__LP64__) +# if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SHEL64 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SH64 +# endif +# else +# if (BYTE_ORDER == LITTLE_ENDIAN) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SHEL +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SH +# endif +# endif +#elif defined(__sparc__) +# if defined(__arch64__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SPARC64 +# else +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_SPARC +# endif +#elif defined(__xtensa__) +# define SECCOMP_AUDIT_ARCH AUDIT_ARCH_XTENSA +#else +# error "Platform does not support seccomp filter yet" +#endif + +static struct sock_filter ps_seccomp_filter[] = { + /* Check syscall arch */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, + offsetof(struct seccomp_data, arch)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_AUDIT_ARCH, 1, 0), + BPF_STMT(BPF_RET + BPF_K, SECCOMP_FILTER_FAIL), + /* Allow syscalls */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, + offsetof(struct seccomp_data, nr)), +#ifdef __NR_accept + SECCOMP_ALLOW(__NR_accept), +#endif +#ifdef __NR_brk + SECCOMP_ALLOW(__NR_brk), +#endif +#ifdef __NR_clock_gettime + SECCOMP_ALLOW(__NR_clock_gettime), +#endif +#if defined(__x86_64__) && defined(__ILP32__) && defined(__X32_SYSCALL_BIT) + SECCOMP_ALLOW(__NR_clock_gettime & ~__X32_SYSCALL_BIT), +#endif +#ifdef __NR_clock_gettime64 + SECCOMP_ALLOW(__NR_clock_gettime64), +#endif +#ifdef __NR_close + SECCOMP_ALLOW(__NR_close), +#endif +#ifdef __NR_exit_group + SECCOMP_ALLOW(__NR_exit_group), +#endif +#ifdef __NR_fcntl + SECCOMP_ALLOW(__NR_fcntl), +#endif +#ifdef __NR_fcntl64 + SECCOMP_ALLOW(__NR_fcntl64), +#endif +#ifdef __NR_fstat + SECCOMP_ALLOW(__NR_fstat), +#endif +#ifdef __NR_fstat64 + SECCOMP_ALLOW(__NR_fstat64), +#endif +#ifdef __NR_gettimeofday + SECCOMP_ALLOW(__NR_gettimeofday), +#endif +#ifdef __NR_getpid + SECCOMP_ALLOW(__NR_getpid), +#endif +#ifdef __NR_getsockopt + /* For route socket overflow */ + SECCOMP_ALLOW_ARG(__NR_getsockopt, 1, SOL_SOCKET), + SECCOMP_ALLOW_ARG(__NR_getsockopt, 2, SO_RCVBUF), +#endif +#ifdef __NR_ioctl + SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFFLAGS), + SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFHWADDR), + SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFINDEX), + SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFMTU), + SECCOMP_ALLOW_ARG(__NR_ioctl, 1, SIOCGIFVLAN), + /* printf over serial terminal requires this */ + SECCOMP_ALLOW_ARG(__NR_ioctl, 1, TCGETS), + /* SECCOMP BPF is newer than nl80211 so we don't need SIOCGIWESSID + * which lives in the impossible to include linux/wireless.h header */ +#endif +#ifdef __NR_mmap + SECCOMP_ALLOW(__NR_mmap), +#endif +#ifdef __NR_munmap + SECCOMP_ALLOW(__NR_munmap), +#endif +#ifdef __NR_nanosleep + SECCOMP_ALLOW(__NR_nanosleep), /* XXX should use ppoll instead */ +#endif +#ifdef __NR_ppoll + SECCOMP_ALLOW(__NR_ppoll), +#endif +#ifdef __NR_ppoll_time64 + SECCOMP_ALLOW(__NR_ppoll_time64), +#endif +#ifdef __NR_read + SECCOMP_ALLOW(__NR_read), +#endif +#ifdef __NR_readv + SECCOMP_ALLOW(__NR_readv), +#endif +#ifdef __NR_recv + SECCOMP_ALLOW(__NR_recv), +#endif +#ifdef __NR_recvfrom + SECCOMP_ALLOW(__NR_recvfrom), +#endif +#ifdef __NR_recvmsg + SECCOMP_ALLOW(__NR_recvmsg), +#endif +#ifdef __NR_rt_sigreturn + SECCOMP_ALLOW(__NR_rt_sigreturn), +#endif +#ifdef __NR_send + SECCOMP_ALLOW(__NR_send), +#endif +#ifdef __NR_sendmsg + SECCOMP_ALLOW(__NR_sendmsg), +#endif +#ifdef __NR_sendto + SECCOMP_ALLOW(__NR_sendto), +#endif +#ifdef __NR_socketcall + /* i386 needs this and demonstrates why SECCOMP + * is poor compared to OpenBSD pledge(2) and FreeBSD capsicum(4) + * as this is soooo tied to the kernel API which changes per arch + * and likely libc as well. */ + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_ACCEPT), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_ACCEPT4), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_LISTEN), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_GETSOCKOPT), /* overflow */ + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_RECV), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_RECVFROM), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_RECVMSG), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SEND), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SENDMSG), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SENDTO), + SECCOMP_ALLOW_ARG(__NR_socketcall, 0, SYS_SHUTDOWN), +#endif +#ifdef __NR_shutdown + SECCOMP_ALLOW(__NR_shutdown), +#endif +#ifdef __NR_time + SECCOMP_ALLOW(__NR_time), +#endif +#ifdef __NR_wait4 + SECCOMP_ALLOW(__NR_wait4), +#endif +#ifdef __NR_waitpid + SECCOMP_ALLOW(__NR_waitpid), +#endif +#ifdef __NR_write + SECCOMP_ALLOW(__NR_write), +#endif +#ifdef __NR_writev + SECCOMP_ALLOW(__NR_writev), +#endif +#ifdef __NR_uname + SECCOMP_ALLOW(__NR_uname), +#endif + + /* Deny everything else */ + BPF_STMT(BPF_RET + BPF_K, SECCOMP_FILTER_FAIL), +}; + +static struct sock_fprog ps_seccomp_prog = { + .len = (unsigned short)__arraycount(ps_seccomp_filter), + .filter = ps_seccomp_filter, +}; + +#ifdef SECCOMP_FILTER_DEBUG +static void +ps_seccomp_violation(__unused int signum, siginfo_t *si, __unused void *context) +{ + + logerrx("%s: unexpected syscall %d (arch=0x%x)", + __func__, si->si_syscall, si->si_arch); + _exit(EXIT_FAILURE); +} + +static int +ps_seccomp_debug(void) +{ + struct sigaction sa = { + .sa_flags = SA_SIGINFO, + .sa_sigaction = &ps_seccomp_violation, + }; + sigset_t mask; + + /* Install a signal handler to catch any issues with our filter. */ + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + if (sigaction(SIGSYS, &sa, NULL) == -1 || + sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) + return -1; + + return 0; +} +#endif + +int +ps_seccomp_enter(void) +{ + +#ifdef SECCOMP_FILTER_DEBUG + ps_seccomp_debug(); +#endif + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 || + prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &ps_seccomp_prog) == -1) + { + if (errno == EINVAL) + errno = ENOSYS; + return -1; + } + return 0; +} Index: src/privsep-root.h =================================================================== --- /dev/null +++ src/privsep-root.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * 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. + */ + +#ifndef PRIVSEP_ROOT_H +#define PRIVSEP_ROOT_H + +#include "if.h" + +#if defined(PRIVSEP) && (defined(HAVE_CAPSICUM) || defined(__linux__)) +#define PRIVSEP_GETIFADDRS +#endif + +pid_t ps_root_start(struct dhcpcd_ctx *ctx); +int ps_root_stop(struct dhcpcd_ctx *ctx); + +ssize_t ps_root_readerror(struct dhcpcd_ctx *, void *, size_t); +ssize_t ps_root_mreaderror(struct dhcpcd_ctx *, void **, size_t *); +ssize_t ps_root_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t); +ssize_t ps_root_ip6forwarding(struct dhcpcd_ctx *, const char *); +ssize_t ps_root_unlink(struct dhcpcd_ctx *, const char *); +ssize_t ps_root_filemtime(struct dhcpcd_ctx *, const char *, time_t *); +ssize_t ps_root_readfile(struct dhcpcd_ctx *, const char *, void *, size_t); +ssize_t ps_root_writefile(struct dhcpcd_ctx *, const char *, mode_t, + const void *, size_t); +ssize_t ps_root_logreopen(struct dhcpcd_ctx *); +ssize_t ps_root_script(struct dhcpcd_ctx *, const void *, size_t); +int ps_root_getauthrdm(struct dhcpcd_ctx *, uint64_t *); +#ifdef PRIVSEP_GETIFADDRS +int ps_root_getifaddrs(struct dhcpcd_ctx *, struct ifaddrs **); +#endif + +ssize_t ps_root_os(struct ps_msghdr *, struct msghdr *, void **, size_t *); +#if defined(BSD) || defined(__sun) +ssize_t ps_root_route(struct dhcpcd_ctx *, void *, size_t); +ssize_t ps_root_ioctllink(struct dhcpcd_ctx *, unsigned long, void *, size_t); +ssize_t ps_root_ioctl6(struct dhcpcd_ctx *, unsigned long, void *, size_t); +ssize_t ps_root_indirectioctl(struct dhcpcd_ctx *, unsigned long, const char *, + void *, size_t); +ssize_t ps_root_ifignoregroup(struct dhcpcd_ctx *, const char *); +#endif +#ifdef __linux__ +ssize_t ps_root_sendnetlink(struct dhcpcd_ctx *, int, struct msghdr *); +#endif + +#ifdef PLUGIN_DEV +int ps_root_dev_initialised(struct dhcpcd_ctx *, const char *); +int ps_root_dev_listening(struct dhcpcd_ctx *); +#endif + +#endif Index: src/privsep-root.c =================================================================== --- /dev/null +++ src/privsep-root.c @@ -0,0 +1,1069 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, privileged proxy + * Copyright (c) 2006-2021 Roy Marples + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "auth.h" +#include "common.h" +#include "dev.h" +#include "dhcpcd.h" +#include "dhcp6.h" +#include "eloop.h" +#include "if.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "sa.h" +#include "script.h" + +__CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long)); + +struct psr_error +{ + ssize_t psr_result; + int psr_errno; + char psr_pad[sizeof(ssize_t) - sizeof(int)]; + size_t psr_datalen; +}; + +struct psr_ctx { + struct dhcpcd_ctx *psr_ctx; + struct psr_error psr_error; + size_t psr_datalen; + void *psr_data; +}; + +static void +ps_root_readerrorcb(void *arg) +{ + struct psr_ctx *psr_ctx = arg; + struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx; + struct psr_error *psr_error = &psr_ctx->psr_error; + struct iovec iov[] = { + { .iov_base = psr_error, .iov_len = sizeof(*psr_error) }, + { .iov_base = psr_ctx->psr_data, + .iov_len = psr_ctx->psr_datalen }, + }; + ssize_t len; + int exit_code = EXIT_FAILURE; + +#define PSR_ERROR(e) \ + do { \ + psr_error->psr_result = -1; \ + psr_error->psr_errno = (e); \ + goto out; \ + } while (0 /* CONSTCOND */) + + len = readv(ctx->ps_root_fd, iov, __arraycount(iov)); + if (len == -1) + PSR_ERROR(errno); + else if ((size_t)len < sizeof(*psr_error)) + PSR_ERROR(EINVAL); + exit_code = EXIT_SUCCESS; + +out: + eloop_exit(ctx->ps_eloop, exit_code); +} + +ssize_t +ps_root_readerror(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + struct psr_ctx psr_ctx = { + .psr_ctx = ctx, + .psr_data = data, .psr_datalen = len, + }; + + if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd, + ps_root_readerrorcb, &psr_ctx) == -1) + return -1; + + eloop_enter(ctx->ps_eloop); + eloop_start(ctx->ps_eloop, &ctx->sigset); + + errno = psr_ctx.psr_error.psr_errno; + return psr_ctx.psr_error.psr_result; +} + +#ifdef PRIVSEP_GETIFADDRS +static void +ps_root_mreaderrorcb(void *arg) +{ + struct psr_ctx *psr_ctx = arg; + struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx; + struct psr_error *psr_error = &psr_ctx->psr_error; + struct iovec iov[] = { + { .iov_base = psr_error, .iov_len = sizeof(*psr_error) }, + { .iov_base = NULL, .iov_len = 0 }, + }; + ssize_t len; + int exit_code = EXIT_FAILURE; + + len = recv(ctx->ps_root_fd, psr_error, sizeof(*psr_error), MSG_PEEK); + if (len == -1) + PSR_ERROR(errno); + else if ((size_t)len < sizeof(*psr_error)) + PSR_ERROR(EINVAL); + + if (psr_error->psr_datalen > SSIZE_MAX) + PSR_ERROR(ENOBUFS); + else if (psr_error->psr_datalen != 0) { + psr_ctx->psr_data = malloc(psr_error->psr_datalen); + if (psr_ctx->psr_data == NULL) + PSR_ERROR(errno); + psr_ctx->psr_datalen = psr_error->psr_datalen; + iov[1].iov_base = psr_ctx->psr_data; + iov[1].iov_len = psr_ctx->psr_datalen; + } + + len = readv(ctx->ps_root_fd, iov, __arraycount(iov)); + if (len == -1) + PSR_ERROR(errno); + else if ((size_t)len != sizeof(*psr_error) + psr_ctx->psr_datalen) + PSR_ERROR(EINVAL); + exit_code = EXIT_SUCCESS; + +out: + eloop_exit(ctx->ps_eloop, exit_code); +} + +ssize_t +ps_root_mreaderror(struct dhcpcd_ctx *ctx, void **data, size_t *len) +{ + struct psr_ctx psr_ctx = { + .psr_ctx = ctx, + }; + + if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd, + ps_root_mreaderrorcb, &psr_ctx) == -1) + return -1; + + eloop_enter(ctx->ps_eloop); + eloop_start(ctx->ps_eloop, &ctx->sigset); + + errno = psr_ctx.psr_error.psr_errno; + *data = psr_ctx.psr_data; + *len = psr_ctx.psr_datalen; + return psr_ctx.psr_error.psr_result; +} +#endif + +static ssize_t +ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result, + void *data, size_t len) +{ + struct psr_error psr = { + .psr_result = result, + .psr_errno = errno, + .psr_datalen = len, + }; + struct iovec iov[] = { + { .iov_base = &psr, .iov_len = sizeof(psr) }, + { .iov_base = data, .iov_len = len }, + }; + +#ifdef PRIVSEP_DEBUG + logdebugx("%s: result %zd errno %d", __func__, result, errno); +#endif + + return writev(ctx->ps_root_fd, iov, __arraycount(iov)); +} + +static ssize_t +ps_root_doioctl(unsigned long req, void *data, size_t len) +{ + int s, err; + + /* Only allow these ioctls */ + switch(req) { +#ifdef SIOCAIFADDR + case SIOCAIFADDR: /* FALLTHROUGH */ + case SIOCDIFADDR: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFHWADDR + case SIOCSIFHWADDR: /* FALLTHROUGH */ +#endif +#ifdef SIOCGIFPRIORITY + case SIOCGIFPRIORITY: /* FALLTHROUGH */ +#endif + case SIOCSIFFLAGS: /* FALLTHROUGH */ + case SIOCGIFMTU: /* FALLTHROUGH */ + case SIOCSIFMTU: + break; + default: + errno = EPERM; + return -1; + } + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s != -1) +#ifdef IOCTL_REQUEST_TYPE + { + ioctl_request_t reqt; + + memcpy(&reqt, &req, sizeof(reqt)); + err = ioctl(s, reqt, data, len); + } +#else + err = ioctl(s, req, data, len); +#endif + else + err = -1; + if (s != -1) + close(s); + return err; +} + +static ssize_t +ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) +{ + const char *envbuf = data; + char * const argv[] = { ctx->script, NULL }; + pid_t pid; + int status; + + if (len == 0) + return 0; + + if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL) + return -1; + + pid = script_exec(argv, ctx->script_env); + if (pid == -1) + return -1; + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + logerr(__func__); + status = 0; + break; + } + } + return status; +} + +static bool +ps_root_validpath(const struct dhcpcd_ctx *ctx, uint16_t cmd, const char *path) +{ + + /* Avoid a previous directory attack to avoid /proc/../ + * dhcpcd should never use a path with double dots. */ + if (strstr(path, "..") != NULL) + return false; + + if (cmd == PS_READFILE) { +#ifdef EMBEDDED_CONFIG + if (strcmp(ctx->cffile, EMBEDDED_CONFIG) == 0) + return true; +#endif + if (strcmp(ctx->cffile, path) == 0) + return true; + } + if (strncmp(DBDIR, path, strlen(DBDIR)) == 0) + return true; + if (strncmp(RUNDIR, path, strlen(RUNDIR)) == 0) + return true; + +#ifdef __linux__ + if (strncmp("/proc/net/", path, strlen("/proc/net/")) == 0 || + strncmp("/proc/sys/net/", path, strlen("/proc/sys/net/")) == 0 || + strncmp("/sys/class/net/", path, strlen("/sys/class/net/")) == 0) + return true; +#endif + + errno = EPERM; + return false; +} + +static ssize_t +ps_root_dowritefile(const struct dhcpcd_ctx *ctx, + mode_t mode, void *data, size_t len) +{ + char *file = data, *nc; + + nc = memchr(file, '\0', len); + if (nc == NULL) { + errno = EINVAL; + return -1; + } + + if (!ps_root_validpath(ctx, PS_WRITEFILE, file)) + return -1; + nc++; + return writefile(file, mode, nc, len - (size_t)(nc - file)); +} + +#ifdef AUTH +static ssize_t +ps_root_monordm(uint64_t *rdm, size_t len) +{ + + if (len != sizeof(*rdm)) { + errno = EINVAL; + return -1; + } + return auth_get_rdm_monotonic(rdm); +} +#endif + +#ifdef PRIVSEP_GETIFADDRS +#define IFA_NADDRS 4 +static ssize_t +ps_root_dogetifaddrs(void **rdata, size_t *rlen) +{ + struct ifaddrs *ifaddrs, *ifa; + size_t len; + uint8_t *buf, *sap; + socklen_t salen; + + if (getifaddrs(&ifaddrs) == -1) + return -1; + if (ifaddrs == NULL) { + *rdata = NULL; + *rlen = 0; + return 0; + } + + /* Work out the buffer length required. + * Ensure everything is aligned correctly, which does + * create a larger buffer than what is needed to send, + * but makes creating the same structure in the client + * much easier. */ + len = 0; + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + len += ALIGN(sizeof(*ifa)); + len += ALIGN(IFNAMSIZ); + len += ALIGN(sizeof(salen) * IFA_NADDRS); + if (ifa->ifa_addr != NULL) + len += ALIGN(sa_len(ifa->ifa_addr)); + if (ifa->ifa_netmask != NULL) + len += ALIGN(sa_len(ifa->ifa_netmask)); + if (ifa->ifa_broadaddr != NULL) + len += ALIGN(sa_len(ifa->ifa_broadaddr)); +#ifdef BSD + /* + * On BSD we need to carry ifa_data so we can access + * if_data->ifi_link_state + */ + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_LINK) + len += ALIGN(sizeof(struct if_data)); +#endif + } + + /* Use calloc to set everything to zero. + * This satisfies memory sanitizers because don't write + * where we don't need to. */ + buf = calloc(1, len); + if (buf == NULL) { + freeifaddrs(ifaddrs); + return -1; + } + *rdata = buf; + *rlen = len; + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + memcpy(buf, ifa, sizeof(*ifa)); + buf += ALIGN(sizeof(*ifa)); + + strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ); + buf += ALIGN(IFNAMSIZ); + sap = buf; + buf += ALIGN(sizeof(salen) * IFA_NADDRS); + +#define COPYINSA(addr) \ + do { \ + if ((addr) != NULL) \ + salen = sa_len((addr)); \ + else \ + salen = 0; \ + if (salen != 0) { \ + memcpy(sap, &salen, sizeof(salen)); \ + memcpy(buf, (addr), salen); \ + buf += ALIGN(salen); \ + } \ + sap += sizeof(salen); \ + } while (0 /*CONSTCOND */) + + COPYINSA(ifa->ifa_addr); + COPYINSA(ifa->ifa_netmask); + COPYINSA(ifa->ifa_broadaddr); + +#ifdef BSD + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_LINK) + { + salen = (socklen_t)sizeof(struct if_data); + memcpy(buf, ifa->ifa_data, salen); + buf += ALIGN(salen); + } else +#endif + salen = 0; + memcpy(sap, &salen, sizeof(salen)); + } + + freeifaddrs(ifaddrs); + return 0; +} +#endif + +static ssize_t +ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + uint16_t cmd; + struct ps_process *psp; + struct iovec *iov = msg->msg_iov; + void *data = iov->iov_base, *rdata = NULL; + size_t len = iov->iov_len, rlen = 0; + uint8_t buf[PS_BUFLEN]; + time_t mtime; + ssize_t err; + bool free_rdata = false; + + cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); + psp = ps_findprocess(ctx, &psm->ps_id); + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + if (psp != NULL) { + if (psm->ps_cmd & PS_STOP) { + int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd); + + ps_freeprocess(psp); + return ret; + } else if (psm->ps_cmd & PS_START) { + /* Process has already started .... */ + return 0; + } + + err = ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg); + if (err == -1) { + logerr("%s: failed to send message to pid %d", + __func__, psp->psp_pid); + shutdown(psp->psp_fd, SHUT_RDWR); + close(psp->psp_fd); + psp->psp_fd = -1; + ps_freeprocess(psp); + } + return 0; + } + + if (psm->ps_cmd & PS_STOP && psp == NULL) + return 0; + + switch (cmd) { +#ifdef INET +#ifdef ARP + case PS_BPF_ARP: /* FALLTHROUGH */ +#endif + case PS_BPF_BOOTP: + return ps_bpf_cmd(ctx, psm, msg); +#endif +#ifdef INET + case PS_BOOTP: + return ps_inet_cmd(ctx, psm, msg); +#endif +#ifdef INET6 +#ifdef DHCP6 + case PS_DHCP6: /* FALLTHROUGH */ +#endif + case PS_ND: + return ps_inet_cmd(ctx, psm, msg); +#endif + default: + break; + } + + assert(msg->msg_iovlen == 0 || msg->msg_iovlen == 1); + + /* Reset errno */ + errno = 0; + + switch (psm->ps_cmd) { + case PS_IOCTL: + err = ps_root_doioctl(psm->ps_flags, data, len); + if (err != -1) { + rdata = data; + rlen = len; + } + break; + case PS_SCRIPT: + err = ps_root_run_script(ctx, data, len); + break; + case PS_UNLINK: + if (!ps_root_validpath(ctx, psm->ps_cmd, data)) { + err = -1; + break; + } + err = unlink(data); + break; + case PS_READFILE: + if (!ps_root_validpath(ctx, psm->ps_cmd, data)) { + err = -1; + break; + } + err = readfile(data, buf, sizeof(buf)); + if (err != -1) { + rdata = buf; + rlen = (size_t)err; + } + break; + case PS_WRITEFILE: + err = ps_root_dowritefile(ctx, (mode_t)psm->ps_flags, + data, len); + break; + case PS_FILEMTIME: + err = filemtime(data, &mtime); + if (err != -1) { + rdata = &mtime; + rlen = sizeof(mtime); + } + break; + case PS_LOGREOPEN: + err = logopen(ctx->logfile); + break; +#ifdef AUTH + case PS_AUTH_MONORDM: + err = ps_root_monordm(data, len); + if (err != -1) { + rdata = data; + rlen = len; + } + break; +#endif +#ifdef PRIVSEP_GETIFADDRS + case PS_GETIFADDRS: + err = ps_root_dogetifaddrs(&rdata, &rlen); + free_rdata = true; + break; +#endif +#if defined(INET6) && (defined(__linux__) || defined(HAVE_PLEDGE)) + case PS_IP6FORWARDING: + err = ip6_forwarding(data); + break; +#endif +#ifdef PLUGIN_DEV + case PS_DEV_INITTED: + err = dev_initialised(ctx, data); + break; + case PS_DEV_LISTENING: + err = dev_listening(ctx); + break; +#endif + default: + err = ps_root_os(psm, msg, &rdata, &rlen); + break; + } + + err = ps_root_writeerror(ctx, err, rlen != 0 ? rdata : 0, rlen); + if (free_rdata) + free(rdata); + return err; +} + +/* Receive from state engine, do an action. */ +static void +ps_root_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1) + logerr(__func__); +} + +#ifdef PLUGIN_DEV +static int +ps_root_handleinterface(void *arg, int action, const char *ifname) +{ + struct dhcpcd_ctx *ctx = arg; + unsigned long flag; + + if (action == 1) + flag = PS_DEV_IFADDED; + else if (action == -1) + flag = PS_DEV_IFREMOVED; + else if (action == 0) + flag = PS_DEV_IFUPDATED; + else { + errno = EINVAL; + return -1; + } + + return (int)ps_sendcmd(ctx, ctx->ps_data_fd, PS_DEV_IFCMD, flag, + ifname, strlen(ifname) + 1); +} +#endif + +static int +ps_root_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ctx->options & DHCPCD_MANAGER) + setproctitle("[privileged proxy]"); + else + setproctitle("[privileged proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + ctx->ps_root_pid = getpid(); + ctx->options |= DHCPCD_PRIVSEPROOT; + + /* Open network sockets for sending. + * This is a small bit wasteful for non sandboxed OS's + * but makes life very easy for unicasting DHCPv6 in non manager + * mode as we no longer care about address selection. + * We can't call shutdown SHUT_RD on the socket because it's + * not connectd. All we can do is try and set a zero sized + * receive buffer and just let it overflow. + * Reading from it just to drain it is a waste of CPU time. */ +#ifdef INET + if (ctx->options & DHCPCD_IPV4) { + int buflen = 1; + + ctx->udp_wfd = xsocket(PF_INET, + SOCK_RAW | SOCK_CXNB, IPPROTO_UDP); + if (ctx->udp_wfd == -1) + logerr("%s: dhcp_openraw", __func__); + else if (setsockopt(ctx->udp_wfd, SOL_SOCKET, SO_RCVBUF, + &buflen, sizeof(buflen)) == -1) + logerr("%s: setsockopt SO_RCVBUF DHCP", __func__); + } +#endif +#ifdef INET6 + if (ctx->options & DHCPCD_IPV6) { + int buflen = 1; + + ctx->nd_fd = ipv6nd_open(false); + if (ctx->nd_fd == -1) + logerr("%s: ipv6nd_open", __func__); + else if (setsockopt(ctx->nd_fd, SOL_SOCKET, SO_RCVBUF, + &buflen, sizeof(buflen)) == -1) + logerr("%s: setsockopt SO_RCVBUF ND", __func__); + } +#endif +#ifdef DHCP6 + if (ctx->options & DHCPCD_IPV6) { + int buflen = 1; + + ctx->dhcp6_wfd = dhcp6_openraw(); + if (ctx->dhcp6_wfd == -1) + logerr("%s: dhcp6_openraw", __func__); + else if (setsockopt(ctx->dhcp6_wfd, SOL_SOCKET, SO_RCVBUF, + &buflen, sizeof(buflen)) == -1) + logerr("%s: setsockopt SO_RCVBUF DHCP6", __func__); + } +#endif + +#ifdef PLUGIN_DEV + /* Start any dev listening plugin which may want to + * change the interface name provided by the kernel */ + if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_DEV)) == + (DHCPCD_MANAGER | DHCPCD_DEV)) + dev_start(ctx, ps_root_handleinterface); +#endif + + return 0; +} + +static void +ps_root_signalcb(int sig, __unused void *arg) +{ + + if (sig == SIGCHLD) { + while (waitpid(-1, NULL, WNOHANG) > 0) + ; + return; + } +} + +int (*handle_interface)(void *, int, const char *); + +#ifdef PLUGIN_DEV +static ssize_t +ps_root_devcb(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) +{ + int action; + struct iovec *iov = msg->msg_iov; + + if (msg->msg_iovlen != 1) { + errno = EINVAL; + return -1; + } + + switch(psm->ps_flags) { + case PS_DEV_IFADDED: + action = 1; + break; + case PS_DEV_IFREMOVED: + action = -1; + break; + case PS_DEV_IFUPDATED: + action = 0; + break; + default: + errno = EINVAL; + return -1; + } + + return dhcpcd_handleinterface(ctx, action, iov->iov_base); +} +#endif + +static ssize_t +ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t err; + + switch(psm->ps_cmd) { +#ifdef PLUGIN_DEV + case PS_DEV_IFCMD: + err = ps_root_devcb(ctx, psm, msg); + break; +#endif + default: +#ifdef INET + err = ps_bpf_dispatch(ctx, psm, msg); + if (err == -1 && errno == ENOTSUP) +#endif + err = ps_inet_dispatch(ctx, psm, msg); + } + return err; +} + +static void +ps_root_dispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1) + logerr(__func__); +} + +static void +ps_root_log(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (logreadfd(ctx->ps_log_fd) == -1) + logerr(__func__); +} + +pid_t +ps_root_start(struct dhcpcd_ctx *ctx) +{ + int logfd[2], datafd[2]; + pid_t pid; + + if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, logfd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(logfd) == -1) + return -1; +#endif + + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, datafd) == -1) + return -1; + if (ps_setbuf_fdpair(datafd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(datafd) == -1) + return -1; +#endif + + pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd, + ps_root_recvmsg, NULL, ctx, + ps_root_startcb, ps_root_signalcb, 0); + + if (pid == 0) { + ctx->ps_log_fd = logfd[1]; + if (eloop_event_add(ctx->eloop, ctx->ps_log_fd, + ps_root_log, ctx) == -1) + return -1; + close(logfd[0]); + ctx->ps_data_fd = datafd[1]; + close(datafd[0]); + return 0; + } else if (pid == -1) + return -1; + + logsetfd(logfd[0]); + close(logfd[1]); + + ctx->ps_data_fd = datafd[0]; + close(datafd[1]); + if (eloop_event_add(ctx->eloop, ctx->ps_data_fd, + ps_root_dispatch, ctx) == -1) + return -1; + + if ((ctx->ps_eloop = eloop_new()) == NULL) + return -1; + + eloop_signal_set_cb(ctx->ps_eloop, + dhcpcd_signals, dhcpcd_signals_len, + ps_root_signalcb, ctx); + + return pid; +} + +int +ps_root_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd); +} + +ssize_t +ps_root_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_SCRIPT, 0, data, len) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +ssize_t +ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, + size_t len) +{ +#ifdef IOCTL_REQUEST_TYPE + unsigned long ulreq = 0; + + memcpy(&ulreq, &req, sizeof(req)); + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1) + return -1; +#else + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1) + return -1; +#endif + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_unlink(struct dhcpcd_ctx *ctx, const char *file) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_UNLINK, 0, + file, strlen(file) + 1) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +ssize_t +ps_root_readfile(struct dhcpcd_ctx *ctx, const char *file, + void *data, size_t len) +{ + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_READFILE, 0, + file, strlen(file) + 1) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode, + const void *data, size_t len) +{ + char buf[PS_BUFLEN]; + size_t flen; + + flen = strlcpy(buf, file, sizeof(buf)); + flen += 1; + if (flen > sizeof(buf) || flen + len > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + memcpy(buf + flen, data, len); + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_WRITEFILE, mode, + buf, flen + len) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +ssize_t +ps_root_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_FILEMTIME, 0, + file, strlen(file) + 1) == -1) + return -1; + return ps_root_readerror(ctx, time, sizeof(*time)); +} + +ssize_t +ps_root_logreopen(struct dhcpcd_ctx *ctx) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_LOGREOPEN, 0, NULL, 0) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +#ifdef PRIVSEP_GETIFADDRS +int +ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead) +{ + struct ifaddrs *ifa; + void *buf = NULL; + char *bp, *sap; + socklen_t salen; + size_t len; + ssize_t err; + + if (ps_sendcmd(ctx, ctx->ps_root_fd, + PS_GETIFADDRS, 0, NULL, 0) == -1) + return -1; + err = ps_root_mreaderror(ctx, &buf, &len); + + if (err == -1) + return -1; + + /* Should be impossible - lo0 will always exist. */ + if (len == 0) { + *ifahead = NULL; + return 0; + } + + bp = buf; + *ifahead = (struct ifaddrs *)(void *)bp; + for (ifa = *ifahead; ifa != NULL; ifa = ifa->ifa_next) { + if (len < ALIGN(sizeof(*ifa)) + + ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS)) + goto err; + bp += ALIGN(sizeof(*ifa)); + ifa->ifa_name = bp; + bp += ALIGN(IFNAMSIZ); + sap = bp; + bp += ALIGN(sizeof(salen) * IFA_NADDRS); + len -= ALIGN(sizeof(*ifa)) + + ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS); + +#define COPYOUTSA(addr) \ + do { \ + memcpy(&salen, sap, sizeof(salen)); \ + if (len < salen) \ + goto err; \ + if (salen != 0) { \ + (addr) = (struct sockaddr *)bp; \ + bp += ALIGN(salen); \ + len -= ALIGN(salen); \ + } \ + sap += sizeof(salen); \ + } while (0 /* CONSTCOND */) + + COPYOUTSA(ifa->ifa_addr); + COPYOUTSA(ifa->ifa_netmask); + COPYOUTSA(ifa->ifa_broadaddr); + + memcpy(&salen, sap, sizeof(salen)); + if (len < salen) + goto err; + if (salen != 0) { + ifa->ifa_data = bp; + bp += ALIGN(salen); + len -= ALIGN(salen); + } else + ifa->ifa_data = NULL; + + if (len != 0) + ifa->ifa_next = (struct ifaddrs *)(void *)bp; + else + ifa->ifa_next = NULL; + } + return 0; + +err: + free(buf); + *ifahead = NULL; + errno = EINVAL; + return -1; +} +#endif + +#if defined(__linux__) || defined(HAVE_PLEDGE) +ssize_t +ps_root_ip6forwarding(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IP6FORWARDING, 0, + ifname, ifname != NULL ? strlen(ifname) + 1 : 0) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} +#endif + +#ifdef AUTH +int +ps_root_getauthrdm(struct dhcpcd_ctx *ctx, uint64_t *rdm) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_AUTH_MONORDM, 0, + rdm, sizeof(*rdm))== -1) + return -1; + return (int)ps_root_readerror(ctx, rdm, sizeof(*rdm)); +} +#endif + +#ifdef PLUGIN_DEV +int +ps_root_dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_INITTED, 0, + ifname, strlen(ifname) + 1)== -1) + return -1; + return (int)ps_root_readerror(ctx, NULL, 0); +} + +int +ps_root_dev_listening(struct dhcpcd_ctx * ctx) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_LISTENING, 0, NULL, 0)== -1) + return -1; + return (int)ps_root_readerror(ctx, NULL, 0); +} +#endif Index: src/privsep-sun.c =================================================================== --- /dev/null +++ src/privsep-sun.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, Solaris driver + * Copyright (c) 2006-2021 Roy Marples + * 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 + +#include +#include + +#include "dhcpcd.h" +#include "logerr.h" +#include "privsep.h" + +#warning Solaris privsep should compile but wont work, +#warning no DLPI support, ioctl support need rework +/* We should implement privileges(5) as well. + * https://illumos.org/man/5/privileges */ + +static ssize_t +ps_root_doioctl6(unsigned long req, void *data, size_t len) +{ + int s, err; + + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s != -1) + err = ioctl(s, req, data, len); + else + err = -1; + if (err == -1) + logerr(__func__); + if (s != -1) + close(s); + return err; +} + +static ssize_t +ps_root_doroute(void *data, size_t len) +{ + int s; + ssize_t err; + + s = socket(PF_ROUTE, SOCK_RAW, 0); + if (s != -1) + err = write(s, data, len); + else + err = -1; + if (err == -1) + logerr(__func__); + if (s != -1) + close(s); + return err; +} + +ssize_t +ps_root_os(struct ps_msghdr *psm, struct msghdr *msg, + void **rdata, size_t *rlen) +{ + struct iovec *iov = msg->msg_iov; + void *data = iov->iov_base; + size_t len = iov->iov_len; + ssize_t err; + + switch (psm->ps_cmd) { + case PS_IOCTL6: + err = ps_root_doioctl6(psm->ps_flags, data, len); + case PS_ROUTE: + return ps_root_doroute(data, len); + default: + errno = ENOTSUP; + return -1; + } + + if (err != -1) { + *rdata = data; + *rlen = len; + } + return err; +} + +ssize_t +ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request, void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL6, + request, data, len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_ROUTE, 0, data, len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} Index: src/privsep.h =================================================================== --- /dev/null +++ src/privsep.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * 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. + */ + +#ifndef PRIVSEP_H +#define PRIVSEP_H + +//#define PRIVSEP_DEBUG + +/* Start flags */ +#define PSF_DROPPRIVS 0x01 + +/* Protocols */ +#define PS_BOOTP 0x0001 +#define PS_ND 0x0002 +#define PS_DHCP6 0x0003 +#define PS_BPF_BOOTP 0x0004 +#define PS_BPF_ARP 0x0005 + +/* Generic commands */ +#define PS_IOCTL 0x0010 +#define PS_ROUTE 0x0011 /* Also used for NETLINK */ +#define PS_SCRIPT 0x0012 +#define PS_UNLINK 0x0013 +#define PS_READFILE 0x0014 +#define PS_WRITEFILE 0x0015 +#define PS_FILEMTIME 0x0016 +#define PS_AUTH_MONORDM 0x0017 +#define PS_CTL 0x0018 +#define PS_CTL_EOF 0x0019 +#define PS_LOGREOPEN 0x0020 + +/* BSD Commands */ +#define PS_IOCTLLINK 0x0101 +#define PS_IOCTL6 0x0102 +#define PS_IOCTLINDIRECT 0x0103 +#define PS_IP6FORWARDING 0x0104 +#define PS_GETIFADDRS 0x0105 +#define PS_IFIGNOREGRP 0x0106 + +/* Dev Commands */ +#define PS_DEV_LISTENING 0x1001 +#define PS_DEV_INITTED 0x1002 +#define PS_DEV_IFCMD 0x1003 + +/* Dev Interface Commands (via flags) */ +#define PS_DEV_IFADDED 0x0001 +#define PS_DEV_IFREMOVED 0x0002 +#define PS_DEV_IFUPDATED 0x0003 + +/* Control Type (via flags) */ +#define PS_CTL_PRIV 0x0004 +#define PS_CTL_UNPRIV 0x0005 + +/* Process commands */ +#define PS_START 0x4000 +#define PS_STOP 0x8000 + +/* Max INET message size + meta data for IPC */ +#define PS_BUFLEN ((64 * 1024) + \ + sizeof(struct ps_msghdr) + \ + sizeof(struct msghdr) + \ + CMSG_SPACE(sizeof(struct in6_pktinfo) + \ + sizeof(int))) + +/* Handy macro to work out if in the privsep engine or not. */ +#define IN_PRIVSEP(ctx) \ + ((ctx)->options & DHCPCD_PRIVSEP) +#define IN_PRIVSEP_SE(ctx) \ + (((ctx)->options & (DHCPCD_PRIVSEP | DHCPCD_FORKED)) == DHCPCD_PRIVSEP) + +#if defined(PRIVSEP) && defined(HAVE_CAPSICUM) +#define PRIVSEP_RIGHTS +#endif + +#ifdef __linux__ +# include +# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) +# define HAVE_SECCOMP +# endif +#endif + +#include "config.h" +#include "arp.h" +#include "dhcp.h" +#include "dhcpcd.h" + +struct ps_addr { + sa_family_t psa_family; + uint8_t psa_pad[4 - sizeof(sa_family_t)]; + union { + struct in_addr psau_in_addr; + struct in6_addr psau_in6_addr; + } psa_u; +#define psa_in_addr psa_u.psau_in_addr +#define psa_in6_addr psa_u.psau_in6_addr +}; + +/* Uniquely identify a process */ +struct ps_id { + struct ps_addr psi_addr; + unsigned int psi_ifindex; + uint16_t psi_cmd; + uint8_t psi_pad[2]; +}; + +struct ps_msghdr { + uint16_t ps_cmd; + uint8_t ps_pad[sizeof(unsigned long) - sizeof(uint16_t)]; + unsigned long ps_flags; + struct ps_id ps_id; + socklen_t ps_namelen; + socklen_t ps_controllen; + uint8_t ps_pad2[sizeof(size_t) - sizeof(socklen_t)]; + size_t ps_datalen; +}; + +struct ps_msg { + struct ps_msghdr psm_hdr; + uint8_t psm_data[PS_BUFLEN]; +}; + +struct bpf; +struct ps_process { + TAILQ_ENTRY(ps_process) next; + struct dhcpcd_ctx *psp_ctx; + struct ps_id psp_id; + pid_t psp_pid; + int psp_fd; + int psp_work_fd; + unsigned int psp_ifindex; + char psp_ifname[IF_NAMESIZE]; + uint16_t psp_proto; + const char *psp_protostr; + +#ifdef INET + int (*psp_filter)(const struct bpf *, const struct in_addr *); + struct interface psp_ifp; /* Move BPF gubbins elsewhere */ + struct bpf *psp_bpf; +#endif +}; +TAILQ_HEAD(ps_process_head, ps_process); + +#include "privsep-control.h" +#include "privsep-inet.h" +#include "privsep-root.h" +#ifdef INET +#include "privsep-bpf.h" +#endif + +int ps_init(struct dhcpcd_ctx *); +int ps_start(struct dhcpcd_ctx *); +int ps_stop(struct dhcpcd_ctx *); +int ps_entersandbox(const char *, const char **); +int ps_managersandbox(struct dhcpcd_ctx *, const char *); + +int ps_unrollmsg(struct msghdr *, struct ps_msghdr *, const void *, size_t); +ssize_t ps_sendpsmmsg(struct dhcpcd_ctx *, int, + struct ps_msghdr *, const struct msghdr *); +ssize_t ps_sendpsmdata(struct dhcpcd_ctx *, int, + struct ps_msghdr *, const void *, size_t); +ssize_t ps_sendmsg(struct dhcpcd_ctx *, int, uint16_t, unsigned long, + const struct msghdr *); +ssize_t ps_sendcmd(struct dhcpcd_ctx *, int, uint16_t, unsigned long, + const void *data, size_t len); +ssize_t ps_recvmsg(struct dhcpcd_ctx *, int, uint16_t, int); +ssize_t ps_recvpsmsg(struct dhcpcd_ctx *, int, + ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *), void *); + +/* Internal privsep functions. */ +int ps_setbuf_fdpair(int []); + +#ifdef PRIVSEP_RIGHTS +int ps_rights_limit_ioctl(int); +int ps_rights_limit_fd_fctnl(int); +int ps_rights_limit_fd_rdonly(int); +int ps_rights_limit_fd_sockopt(int); +int ps_rights_limit_fd(int); +int ps_rights_limit_fdpair(int []); +#endif + +#ifdef HAVE_SECCOMP +int ps_seccomp_enter(void); +#endif + +pid_t ps_dostart(struct dhcpcd_ctx * ctx, + pid_t *priv_pid, int *priv_fd, + void (*recv_msg)(void *), void (*recv_unpriv_msg), + void *recv_ctx, int (*callback)(void *), void (*)(int, void *), + unsigned int); +int ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd); + +struct ps_process *ps_findprocess(struct dhcpcd_ctx *, struct ps_id *); +struct ps_process *ps_newprocess(struct dhcpcd_ctx *, struct ps_id *); +void ps_freeprocess(struct ps_process *); +void ps_freeprocesses(struct dhcpcd_ctx *, struct ps_process *); +#endif Index: src/privsep.c =================================================================== --- /dev/null +++ src/privsep.c @@ -0,0 +1,1029 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * 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. + */ + +/* + * The current design is this: + * Spawn a priv process to carry out privileged actions and + * spawning unpriv process to initate network connections such as BPF + * or address specific listener. + * Spawn an unpriv process to send/receive common network data. + * Then drop all privs and start running. + * Every process aside from the privileged proxy is chrooted. + * All privsep processes ignore signals - only the manager process accepts them. + * + * dhcpcd will maintain the config file in the chroot, no need to handle + * this in a script or something. + */ + +#include +#include +#include +#include +#include + +#ifdef AF_LINK +#include +#endif + +#include +#include +#include +#include +#include +#include +#include /* For offsetof, struct padding debug */ +#include +#include +#include +#include + +#include "arp.h" +#include "common.h" +#include "control.h" +#include "dev.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +#ifdef HAVE_CAPSICUM +#include +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +/* CMSG_ALIGN is a Linux extension */ +#ifndef CMSG_ALIGN +#define CMSG_ALIGN(n) (CMSG_SPACE((n)) - CMSG_SPACE(0)) +#endif + +/* Calculate number of padding bytes to achieve 'struct cmsghdr' alignment */ +#define CALC_CMSG_PADLEN(has_cmsg, pos) \ + ((has_cmsg) ? (socklen_t)(CMSG_ALIGN((pos)) - (pos)) : 0) + +int +ps_init(struct dhcpcd_ctx *ctx) +{ + struct passwd *pw; + struct stat st; + + errno = 0; + if ((ctx->ps_user = pw = getpwnam(PRIVSEP_USER)) == NULL) { + ctx->options &= ~DHCPCD_PRIVSEP; + if (errno == 0) { + logerrx("no such user %s", PRIVSEP_USER); + /* Just incase logerrx caused an error... */ + errno = 0; + } else + logerr("getpwnam"); + return -1; + } + + if (stat(pw->pw_dir, &st) == -1 || !S_ISDIR(st.st_mode)) { + ctx->options &= ~DHCPCD_PRIVSEP; + logerrx("refusing chroot: %s: %s", + PRIVSEP_USER, pw->pw_dir); + errno = 0; + return -1; + } + + ctx->options |= DHCPCD_PRIVSEP; + return 0; +} + +static int +ps_dropprivs(struct dhcpcd_ctx *ctx) +{ + struct passwd *pw = ctx->ps_user; + + if (ctx->options & DHCPCD_LAUNCHER) + logdebugx("chrooting as %s to %s", pw->pw_name, pw->pw_dir); + if (chroot(pw->pw_dir) == -1 && + (errno != EPERM || ctx->options & DHCPCD_FORKED)) + logerr("%s: chroot: %s", __func__, pw->pw_dir); + if (chdir("/") == -1) + logerr("%s: chdir: /", __func__); + + if ((setgroups(1, &pw->pw_gid) == -1 || + setgid(pw->pw_gid) == -1 || + setuid(pw->pw_uid) == -1) && + (errno != EPERM || ctx->options & DHCPCD_FORKED)) + { + logerr("failed to drop privileges"); + return -1; + } + + struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 }; + + if (ctx->ps_control_pid != getpid()) { + /* Prohibit new files, sockets, etc */ +#if defined(__linux__) || defined(__sun) || defined(__OpenBSD__) + /* + * If poll(2) is called with nfds > RLIMIT_NOFILE + * then it returns EINVAL. + * This blows. + * Do the best we can and limit to what we need. + * An attacker could potentially close a file and + * open a new one still, but that cannot be helped. + */ + unsigned long maxfd; + maxfd = (unsigned long)eloop_event_count(ctx->eloop); + if (IN_PRIVSEP_SE(ctx)) + maxfd++; /* XXX why? */ + + struct rlimit rmaxfd = { + .rlim_cur = maxfd, + .rlim_max = maxfd + }; + if (setrlimit(RLIMIT_NOFILE, &rmaxfd) == -1) + logerr("setrlimit RLIMIT_NOFILE"); +#else + if (setrlimit(RLIMIT_NOFILE, &rzero) == -1) + logerr("setrlimit RLIMIT_NOFILE"); +#endif + } + +#define DHC_NOCHKIO (DHCPCD_STARTED | DHCPCD_DAEMONISE) + /* Prohibit writing to files. + * Obviously this won't work if we are using a logfile + * or redirecting stderr to a file. */ + if ((ctx->options & DHC_NOCHKIO) == DHC_NOCHKIO || + (ctx->logfile == NULL && + (!ctx->stderr_valid || isatty(STDERR_FILENO) == 1))) + { + if (setrlimit(RLIMIT_FSIZE, &rzero) == -1) + logerr("setrlimit RLIMIT_FSIZE"); + } + +#ifdef RLIMIT_NPROC + /* Prohibit forks */ + if (setrlimit(RLIMIT_NPROC, &rzero) == -1) + logerr("setrlimit RLIMIT_NPROC"); +#endif + + return 0; +} + +static int +ps_setbuf0(int fd, int ctl, int minlen) +{ + int len; + socklen_t slen; + + slen = sizeof(len); + if (getsockopt(fd, SOL_SOCKET, ctl, &len, &slen) == -1) + return -1; + +#ifdef __linux__ + len /= 2; +#endif + if (len >= minlen) + return 0; + + return setsockopt(fd, SOL_SOCKET, ctl, &minlen, sizeof(minlen)); +} + +static int +ps_setbuf(int fd) +{ + /* Ensure we can receive a fully sized privsep message. + * Double the send buffer. */ + int minlen = (int)sizeof(struct ps_msg); + + if (ps_setbuf0(fd, SO_RCVBUF, minlen) == -1 || + ps_setbuf0(fd, SO_SNDBUF, minlen * 2) == -1) + { + logerr(__func__); + return -1; + } + return 0; +} + +int +ps_setbuf_fdpair(int fd[]) +{ + + if (ps_setbuf(fd[0]) == -1 || ps_setbuf(fd[1]) == -1) + return -1; + return 0; +} + +#ifdef PRIVSEP_RIGHTS +int +ps_rights_limit_ioctl(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_IOCTL); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd_fctnl(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, + CAP_ACCEPT, CAP_FCNTL); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_SHUTDOWN); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd_sockopt(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, + CAP_GETSOCKOPT, CAP_SETSOCKOPT); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd_rdonly(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_EVENT); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fdpair(int fd[]) +{ + + if (ps_rights_limit_fd(fd[0]) == -1 || ps_rights_limit_fd(fd[1]) == -1) + return -1; + return 0; +} + +static int +ps_rights_limit_stdio(struct dhcpcd_ctx *ctx) +{ + const int iebadf = CAPH_IGNORE_EBADF; + int error = 0; + + if (ctx->stdin_valid && + caph_limit_stream(STDIN_FILENO, CAPH_READ | iebadf) == -1) + error = -1; + if (ctx->stdout_valid && + caph_limit_stream(STDOUT_FILENO, CAPH_WRITE | iebadf) == -1) + error = -1; + if (ctx->stderr_valid && + caph_limit_stream(STDERR_FILENO, CAPH_WRITE | iebadf) == -1) + error = -1; + + return error; +} +#endif + +pid_t +ps_dostart(struct dhcpcd_ctx *ctx, + pid_t *priv_pid, int *priv_fd, + void (*recv_msg)(void *), void (*recv_unpriv_msg), + void *recv_ctx, int (*callback)(void *), void (*signal_cb)(int, void *), + unsigned int flags) +{ + int fd[2]; + pid_t pid; + + if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1) { + logerr("%s: socketpair", __func__); + return -1; + } + if (ps_setbuf_fdpair(fd) == -1) { + logerr("%s: ps_setbuf_fdpair", __func__); + return -1; + } +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(fd) == -1) { + logerr("%s: ps_rights_limit_fdpair", __func__); + return -1; + } +#endif + + switch (pid = fork()) { + case -1: + logerr("fork"); + return -1; + case 0: + *priv_fd = fd[1]; + close(fd[0]); + break; + default: + *priv_pid = pid; + *priv_fd = fd[0]; + close(fd[1]); + if (recv_unpriv_msg == NULL) + ; + else if (eloop_event_add(ctx->eloop, *priv_fd, + recv_unpriv_msg, recv_ctx) == -1) + { + logerr("%s: eloop_event_add", __func__); + return -1; + } + return pid; + } + + ctx->options |= DHCPCD_FORKED; + if (ctx->fork_fd != -1) { + close(ctx->fork_fd); + ctx->fork_fd = -1; + } + pidfile_clean(); + + eloop_clear(ctx->eloop); + eloop_signal_set_cb(ctx->eloop, + dhcpcd_signals, dhcpcd_signals_len, signal_cb, ctx); + /* ctx->sigset aready has the initial sigmask set in main() */ + if (eloop_signal_mask(ctx->eloop, NULL) == -1) { + logerr("%s: eloop_signal_mask", __func__); + goto errexit; + } + + /* We are not root */ + if (priv_fd != &ctx->ps_root_fd) { + ps_freeprocesses(ctx, recv_ctx); + if (ctx->ps_root_fd != -1) { + close(ctx->ps_root_fd); + ctx->ps_root_fd = -1; + } + +#ifdef PRIVSEP_RIGHTS + /* We cannot limit the root process in any way. */ + if (ps_rights_limit_stdio(ctx) == -1) { + logerr("ps_rights_limit_stdio"); + goto errexit; + } +#endif + } + + if (priv_fd != &ctx->ps_inet_fd && ctx->ps_inet_fd != -1) { + close(ctx->ps_inet_fd); + ctx->ps_inet_fd = -1; + } + + if (eloop_event_add(ctx->eloop, *priv_fd, recv_msg, recv_ctx) == -1) + { + logerr("%s: eloop_event_add", __func__); + goto errexit; + } + + if (callback(recv_ctx) == -1) + goto errexit; + + if (flags & PSF_DROPPRIVS) + ps_dropprivs(ctx); + + return 0; + +errexit: + /* Failure to start root or inet processes is fatal. */ + if (priv_fd == &ctx->ps_root_fd || priv_fd == &ctx->ps_inet_fd) + (void)ps_sendcmd(ctx, *priv_fd, PS_STOP, 0, NULL, 0); + shutdown(*priv_fd, SHUT_RDWR); + *priv_fd = -1; + eloop_exit(ctx->eloop, EXIT_FAILURE); + return -1; +} + +int +ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd) +{ + int err = 0; + +#ifdef PRIVSEP_DEBUG + logdebugx("%s: pid=%d fd=%d", __func__, *pid, *fd); +#endif + + if (*fd != -1) { + eloop_event_delete(ctx->eloop, *fd); + if (ps_sendcmd(ctx, *fd, PS_STOP, 0, NULL, 0) == -1) { + logerr(__func__); + err = -1; + } + (void)shutdown(*fd, SHUT_RDWR); + close(*fd); + *fd = -1; + } + + /* Don't wait for the process as it may not respond to the shutdown + * request. We'll reap the process on receipt of SIGCHLD. */ + *pid = 0; + return err; +} + +int +ps_start(struct dhcpcd_ctx *ctx) +{ + pid_t pid; + + TAILQ_INIT(&ctx->ps_processes); + + switch (pid = ps_root_start(ctx)) { + case -1: + logerr("ps_root_start"); + return -1; + case 0: + return 0; + default: + logdebugx("spawned privileged proxy on PID %d", pid); + } + + /* No point in spawning the generic network listener if we're + * not going to use it. */ + if (!ps_inet_canstart(ctx)) + goto started_net; + + switch (pid = ps_inet_start(ctx)) { + case -1: + return -1; + case 0: + return 0; + default: + logdebugx("spawned network proxy on PID %d", pid); + } + +started_net: + if (!(ctx->options & DHCPCD_TEST)) { + switch (pid = ps_ctl_start(ctx)) { + case -1: + return -1; + case 0: + return 0; + default: + logdebugx("spawned controller proxy on PID %d", pid); + } + } + +#ifdef ARC4RANDOM_H + /* Seed the random number generator early incase it needs /dev/urandom + * which won't be available in the chroot. */ + arc4random(); +#endif + + return 1; +} + +int +ps_entersandbox(const char *_pledge, const char **sandbox) +{ + +#if !defined(HAVE_PLEDGE) + UNUSED(_pledge); +#endif + +#if defined(HAVE_CAPSICUM) + if (sandbox != NULL) + *sandbox = "capsicum"; + return cap_enter(); +#elif defined(HAVE_PLEDGE) + if (sandbox != NULL) + *sandbox = "pledge"; + return pledge(_pledge, NULL); +#elif defined(HAVE_SECCOMP) + if (sandbox != NULL) + *sandbox = "seccomp"; + return ps_seccomp_enter(); +#else + if (sandbox != NULL) + *sandbox = "posix resource limited"; + return 0; +#endif +} + +int +ps_managersandbox(struct dhcpcd_ctx *ctx, const char *_pledge) +{ + const char *sandbox = NULL; + bool forked; + int dropped; + + forked = ctx->options & DHCPCD_FORKED; + ctx->options &= ~DHCPCD_FORKED; + dropped = ps_dropprivs(ctx); + if (forked) + ctx->options |= DHCPCD_FORKED; + + /* + * If we don't have a root process, we cannot use syslog. + * If it cannot be opened before chrooting then syslog(3) will fail. + * openlog(3) does not return an error which doubly sucks. + */ + if (ctx->ps_root_fd == -1) { + unsigned int logopts = loggetopts(); + + logopts &= ~LOGERR_LOG; + logsetopts(logopts); + } + + if (dropped == -1) { + logerr("%s: ps_dropprivs", __func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if ((ctx->pf_inet_fd != -1 && + ps_rights_limit_ioctl(ctx->pf_inet_fd) == -1) || + ps_rights_limit_stdio(ctx) == -1) + { + logerr("%s: cap_rights_limit", __func__); + return -1; + } +#endif + + if (_pledge == NULL) + _pledge = "stdio"; + if (ps_entersandbox(_pledge, &sandbox) == -1) { + if (errno == ENOSYS) { + if (sandbox != NULL) + logwarnx("sandbox unavailable: %s", sandbox); + return 0; + } + logerr("%s: %s", __func__, sandbox); + return -1; + } else if (ctx->options & DHCPCD_LAUNCHER || + ((!(ctx->options & DHCPCD_DAEMONISE)) && + ctx->options & DHCPCD_MANAGER)) + logdebugx("sandbox: %s", sandbox); + return 0; +} + +int +ps_stop(struct dhcpcd_ctx *ctx) +{ + int r, ret = 0; + + if (!(ctx->options & DHCPCD_PRIVSEP) || + ctx->options & DHCPCD_FORKED || + ctx->eloop == NULL) + return 0; + + r = ps_ctl_stop(ctx); + if (r != 0) + ret = r; + + r = ps_inet_stop(ctx); + if (r != 0) + ret = r; + + /* We've been chrooted, so we need to tell the + * privileged proxy to remove the pidfile. */ + ps_root_unlink(ctx, ctx->pidfile); + + r = ps_root_stop(ctx); + if (r != 0) + ret = r; + + ctx->options &= ~DHCPCD_PRIVSEP; + return ret; +} + +void +ps_freeprocess(struct ps_process *psp) +{ + + TAILQ_REMOVE(&psp->psp_ctx->ps_processes, psp, next); + if (psp->psp_fd != -1) { + eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd); + close(psp->psp_fd); + } + if (psp->psp_work_fd != -1) { + eloop_event_delete(psp->psp_ctx->eloop, psp->psp_work_fd); + close(psp->psp_work_fd); + } +#ifdef INET + if (psp->psp_bpf != NULL) + bpf_close(psp->psp_bpf); +#endif + free(psp); +} + +static void +ps_free(struct dhcpcd_ctx *ctx) +{ + struct ps_process *psp; + bool stop = ctx->ps_root_pid == getpid(); + + while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) { + if (stop) + ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd); + ps_freeprocess(psp); + } +} + +int +ps_unrollmsg(struct msghdr *msg, struct ps_msghdr *psm, + const void *data, size_t len) +{ + uint8_t *datap, *namep, *controlp; + socklen_t cmsg_padlen = + CALC_CMSG_PADLEN(psm->ps_controllen, psm->ps_namelen); + + namep = UNCONST(data); + controlp = namep + psm->ps_namelen + cmsg_padlen; + datap = controlp + psm->ps_controllen; + + if (psm->ps_namelen != 0) { + if (psm->ps_namelen > len) { + errno = EINVAL; + return -1; + } + msg->msg_name = namep; + len -= psm->ps_namelen; + } else + msg->msg_name = NULL; + msg->msg_namelen = psm->ps_namelen; + + if (psm->ps_controllen != 0) { + if (psm->ps_controllen > len) { + errno = EINVAL; + return -1; + } + msg->msg_control = controlp; + len -= psm->ps_controllen + cmsg_padlen; + } else + msg->msg_control = NULL; + msg->msg_controllen = psm->ps_controllen; + + if (len != 0) { + msg->msg_iovlen = 1; + msg->msg_iov[0].iov_base = datap; + msg->msg_iov[0].iov_len = len; + } else { + msg->msg_iovlen = 0; + msg->msg_iov[0].iov_base = NULL; + msg->msg_iov[0].iov_len = 0; + } + return 0; +} + +ssize_t +ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd, + struct ps_msghdr *psm, const struct msghdr *msg) +{ + long padding[1] = { 0 }; + struct iovec iov[] = { + { .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) }, + { .iov_base = NULL, }, /* name */ + { .iov_base = NULL, }, /* control padding */ + { .iov_base = NULL, }, /* control */ + { .iov_base = NULL, }, /* payload 1 */ + { .iov_base = NULL, }, /* payload 2 */ + { .iov_base = NULL, }, /* payload 3 */ + }; + int iovlen; + ssize_t len; + + if (msg != NULL) { + struct iovec *iovp = &iov[1]; + int i; + socklen_t cmsg_padlen; + + psm->ps_namelen = msg->msg_namelen; + psm->ps_controllen = (socklen_t)msg->msg_controllen; + + iovp->iov_base = msg->msg_name; + iovp->iov_len = msg->msg_namelen; + iovp++; + + cmsg_padlen = + CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen); + assert(cmsg_padlen <= sizeof(padding)); + iovp->iov_len = cmsg_padlen; + iovp->iov_base = cmsg_padlen != 0 ? padding : NULL; + iovp++; + + iovp->iov_base = msg->msg_control; + iovp->iov_len = msg->msg_controllen; + iovlen = 4; + + for (i = 0; i < (int)msg->msg_iovlen; i++) { + if ((size_t)(iovlen + i) > __arraycount(iov)) { + errno = ENOBUFS; + return -1; + } + iovp++; + iovp->iov_base = msg->msg_iov[i].iov_base; + iovp->iov_len = msg->msg_iov[i].iov_len; + } + iovlen += i; + } else + iovlen = 1; + + len = writev(fd, iov, iovlen); + if (len == -1) { + logerr(__func__); + if (ctx->options & DHCPCD_FORKED && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + return len; +} + +ssize_t +ps_sendpsmdata(struct dhcpcd_ctx *ctx, int fd, + struct ps_msghdr *psm, const void *data, size_t len) +{ + struct iovec iov[] = { + { .iov_base = UNCONST(data), .iov_len = len }, + }; + struct msghdr msg = { + .msg_iov = iov, .msg_iovlen = 1, + }; + + return ps_sendpsmmsg(ctx, fd, psm, &msg); +} + + +ssize_t +ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags, + const struct msghdr *msg) +{ + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_flags = flags, + .ps_namelen = msg->msg_namelen, + .ps_controllen = (socklen_t)msg->msg_controllen, + }; + size_t i; + + for (i = 0; i < (size_t)msg->msg_iovlen; i++) + psm.ps_datalen += msg->msg_iov[i].iov_len; + +#if 0 /* For debugging structure padding. */ + logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family)); + logerrx("psa.pad %lu %zu", offsetof(struct ps_addr, psa_pad), sizeof(psm.ps_id.psi_addr.psa_pad)); + logerrx("psa.psa_u %lu %zu", offsetof(struct ps_addr, psa_u), sizeof(psm.ps_id.psi_addr.psa_u)); + logerrx("psa %zu", sizeof(psm.ps_id.psi_addr)); + + logerrx("psi.addr %lu %zu", offsetof(struct ps_id, psi_addr), sizeof(psm.ps_id.psi_addr)); + logerrx("psi.index %lu %zu", offsetof(struct ps_id, psi_ifindex), sizeof(psm.ps_id.psi_ifindex)); + logerrx("psi.cmd %lu %zu", offsetof(struct ps_id, psi_cmd), sizeof(psm.ps_id.psi_cmd)); + logerrx("psi.pad %lu %zu", offsetof(struct ps_id, psi_pad), sizeof(psm.ps_id.psi_pad)); + logerrx("psi %zu", sizeof(struct ps_id)); + + logerrx("ps_cmd %lu", offsetof(struct ps_msghdr, ps_cmd)); + logerrx("ps_pad %lu %zu", offsetof(struct ps_msghdr, ps_pad), sizeof(psm.ps_pad)); + logerrx("ps_flags %lu %zu", offsetof(struct ps_msghdr, ps_flags), sizeof(psm.ps_flags)); + + logerrx("ps_id %lu %zu", offsetof(struct ps_msghdr, ps_id), sizeof(psm.ps_id)); + + logerrx("ps_namelen %lu %zu", offsetof(struct ps_msghdr, ps_namelen), sizeof(psm.ps_namelen)); + logerrx("ps_controllen %lu %zu", offsetof(struct ps_msghdr, ps_controllen), sizeof(psm.ps_controllen)); + logerrx("ps_pad2 %lu %zu", offsetof(struct ps_msghdr, ps_pad2), sizeof(psm.ps_pad2)); + logerrx("ps_datalen %lu %zu", offsetof(struct ps_msghdr, ps_datalen), sizeof(psm.ps_datalen)); + logerrx("psm %zu", sizeof(psm)); +#endif + + return ps_sendpsmmsg(ctx, fd, &psm, msg); +} + +ssize_t +ps_sendcmd(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags, + const void *data, size_t len) +{ + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_flags = flags, + }; + struct iovec iov[] = { + { .iov_base = UNCONST(data), .iov_len = len } + }; + struct msghdr msg = { + .msg_iov = iov, .msg_iovlen = 1, + }; + + return ps_sendpsmmsg(ctx, fd, &psm, &msg); +} + +static ssize_t +ps_sendcmdmsg(int fd, uint16_t cmd, const struct msghdr *msg) +{ + struct ps_msghdr psm = { .ps_cmd = cmd }; + uint8_t data[PS_BUFLEN], *p = data; + struct iovec iov[] = { + { .iov_base = &psm, .iov_len = sizeof(psm) }, + { .iov_base = data, .iov_len = 0 }, + }; + size_t dl = sizeof(data); + socklen_t cmsg_padlen = + CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen); + + if (msg->msg_namelen != 0) { + if (msg->msg_namelen > dl) + goto nobufs; + psm.ps_namelen = msg->msg_namelen; + memcpy(p, msg->msg_name, msg->msg_namelen); + p += msg->msg_namelen; + dl -= msg->msg_namelen; + } + + if (msg->msg_controllen != 0) { + if (msg->msg_controllen + cmsg_padlen > dl) + goto nobufs; + if (cmsg_padlen != 0) { + memset(p, 0, cmsg_padlen); + p += cmsg_padlen; + dl -= cmsg_padlen; + } + psm.ps_controllen = (socklen_t)msg->msg_controllen; + memcpy(p, msg->msg_control, msg->msg_controllen); + p += msg->msg_controllen; + dl -= msg->msg_controllen; + } + + psm.ps_datalen = msg->msg_iov[0].iov_len; + if (psm.ps_datalen > dl) + goto nobufs; + + iov[1].iov_len = + psm.ps_namelen + psm.ps_controllen + psm.ps_datalen + cmsg_padlen; + if (psm.ps_datalen != 0) + memcpy(p, msg->msg_iov[0].iov_base, psm.ps_datalen); + return writev(fd, iov, __arraycount(iov)); + +nobufs: + errno = ENOBUFS; + return -1; +} + +ssize_t +ps_recvmsg(struct dhcpcd_ctx *ctx, int rfd, uint16_t cmd, int wfd) +{ + struct sockaddr_storage ss = { .ss_family = AF_UNSPEC }; + uint8_t controlbuf[sizeof(struct sockaddr_storage)] = { 0 }; + uint8_t databuf[64 * 1024]; + struct iovec iov[] = { + { .iov_base = databuf, .iov_len = sizeof(databuf) } + }; + struct msghdr msg = { + .msg_name = &ss, .msg_namelen = sizeof(ss), + .msg_control = controlbuf, .msg_controllen = sizeof(controlbuf), + .msg_iov = iov, .msg_iovlen = 1, + }; + + ssize_t len = recvmsg(rfd, &msg, 0); + + if (len == -1) + logerr("%s: recvmsg", __func__); + if (len == -1 || len == 0) { + if (ctx->options & DHCPCD_FORKED && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + eloop_exit(ctx->eloop, + len == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + return len; + } + + iov[0].iov_len = (size_t)len; + len = ps_sendcmdmsg(wfd, cmd, &msg); + if (len == -1) { + logerr("ps_sendcmdmsg"); + if (ctx->options & DHCPCD_FORKED && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + return len; +} + +ssize_t +ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd, + ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *), + void *cbctx) +{ + struct ps_msg psm; + ssize_t len; + size_t dlen; + struct iovec iov[1]; + struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 }; + bool stop = false; + + len = read(fd, &psm, sizeof(psm)); +#ifdef PRIVSEP_DEBUG + logdebugx("%s: %zd", __func__, len); +#endif + + if (len == -1 || len == 0) + stop = true; + else { + dlen = (size_t)len; + if (dlen < sizeof(psm.psm_hdr)) { + errno = EINVAL; + return -1; + } + + if (psm.psm_hdr.ps_cmd == PS_STOP) { + stop = true; + len = 0; + } + } + + if (stop) { +#ifdef PRIVSEP_DEBUG + logdebugx("process %d stopping", getpid()); +#endif + ps_free(ctx); +#ifdef PLUGIN_DEV + dev_stop(ctx); +#endif + eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE); + return len; + } + dlen -= sizeof(psm.psm_hdr); + + if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1) + return -1; + + if (callback == NULL) + return 0; + + errno = 0; + return callback(cbctx, &psm.psm_hdr, &msg); +} + +struct ps_process * +ps_findprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid) +{ + struct ps_process *psp; + + TAILQ_FOREACH(psp, &ctx->ps_processes, next) { + if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0) + return psp; + } + errno = ESRCH; + return NULL; +} + +struct ps_process * +ps_newprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid) +{ + struct ps_process *psp; + + psp = calloc(1, sizeof(*psp)); + if (psp == NULL) + return NULL; + psp->psp_ctx = ctx; + memcpy(&psp->psp_id, psid, sizeof(psp->psp_id)); + psp->psp_work_fd = -1; + TAILQ_INSERT_TAIL(&ctx->ps_processes, psp, next); + return psp; +} + +void +ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis) +{ + struct ps_process *psp, *psn; + + TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) { + if (psp == notthis) + continue; + ps_freeprocess(psp); + } +} Index: src/route.h =================================================================== --- /dev/null +++ src/route.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - route management + * 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. + */ + +#ifndef ROUTE_H +#define ROUTE_H + +#ifdef HAVE_SYS_RBTREE_H +#include +#endif + +#include +#include + +#include + +#include "dhcpcd.h" +#include "sa.h" + +/* + * Enable the route free list by default as + * memory usage is still reported as low/unchanged even + * when dealing with millions of routes. + */ +#if !defined(RT_FREE_ROUTE_TABLE) +#define RT_FREE_ROUTE_TABLE 1 +#elif RT_FREE_ROUTE_TABLE == 0 +#undef RT_FREE_ROUTE_TABLE +#endif + +/* Some systems have route metrics. + * OpenBSD route priority is not this. */ +#ifndef HAVE_ROUTE_METRIC +# if defined(__linux__) +# define HAVE_ROUTE_METRIC 1 +# endif +#endif + +#ifdef __linux__ +# include /* RTA_PREF is only an enum.... */ +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +# define HAVE_ROUTE_PREF +# endif +#endif + +#if defined(__OpenBSD__) || defined (__sun) +# define ROUTE_PER_GATEWAY +/* XXX dhcpcd doesn't really support this yet. + * But that's generally OK if only dhcpcd is managing routes. */ +#endif + +/* OpenBSD defines this as a "convienience" ..... we work around it. */ +#ifdef __OpenBSD__ +#undef rt_mtu +#endif + +struct rt { + union sa_ss rt_ss_dest; +#define rt_dest rt_ss_dest.sa + union sa_ss rt_ss_netmask; +#define rt_netmask rt_ss_netmask.sa + union sa_ss rt_ss_gateway; +#define rt_gateway rt_ss_gateway.sa + struct interface *rt_ifp; + union sa_ss rt_ss_ifa; +#define rt_ifa rt_ss_ifa.sa + unsigned int rt_flags; + unsigned int rt_mtu; +#ifdef HAVE_ROUTE_METRIC + unsigned int rt_metric; +#endif +/* Maximum interface index is generally USHORT_MAX or 65535. + * Add some padding for other stuff and we get offsets for the + * below that should work automatically. + * This is only an issue if the user defines higher metrics in + * their configuration, but then they might wish to override also. */ +#define RTMETRIC_BASE 1000U +#define RTMETRIC_WIRELESS 2000U +#define RTMETRIC_IPV4LL 1000000U +#define RTMETRIC_ROAM 2000000U +#ifdef HAVE_ROUTE_PREF + int rt_pref; +#endif +#define RTPREF_HIGH 1 +#define RTPREF_MEDIUM 0 /* has to be zero */ +#define RTPREF_LOW (-1) +#define RTPREF_RESERVED (-2) +#define RTPREF_INVALID (-3) /* internal */ + unsigned int rt_dflags; +#define RTDF_IFA_ROUTE 0x01 /* Address generated route */ +#define RTDF_FAKE 0x02 /* Maybe us on lease reboot */ +#define RTDF_IPV4LL 0x04 /* IPv4LL route */ +#define RTDF_RA 0x08 /* Router Advertisement */ +#define RTDF_DHCP 0x10 /* DHCP route */ +#define RTDF_STATIC 0x20 /* Configured in dhcpcd */ +#define RTDF_GATELINK 0x40 /* Gateway is on link */ + size_t rt_order; + rb_node_t rt_tree; +}; + +extern const rb_tree_ops_t rt_compare_list_ops; +extern const rb_tree_ops_t rt_compare_proto_ops; + +void rt_init(struct dhcpcd_ctx *); +void rt_dispose(struct dhcpcd_ctx *); +void rt_free(struct rt *); +void rt_freeif(struct interface *); +bool rt_is_default(const struct rt *); +void rt_headclear0(struct dhcpcd_ctx *, rb_tree_t *, int); +void rt_headclear(rb_tree_t *, int); +void rt_headfreeif(rb_tree_t *); +struct rt * rt_new0(struct dhcpcd_ctx *); +void rt_setif(struct rt *, struct interface *); +struct rt * rt_new(struct interface *); +struct rt * rt_proto_add_ctx(rb_tree_t *, struct rt *, struct dhcpcd_ctx *); +struct rt * rt_proto_add(rb_tree_t *, struct rt *); +int rt_cmp_dest(const struct rt *, const struct rt *); +void rt_recvrt(int, const struct rt *, pid_t); +void rt_build(struct dhcpcd_ctx *, int); + +#endif Index: src/route.c =================================================================== --- /dev/null +++ src/route.c @@ -0,0 +1,799 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - route management + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcpcd.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "ipv6.h" +#include "logerr.h" +#include "route.h" +#include "sa.h" + +/* Needed for NetBSD-6, 7 and 8. */ +#ifndef RB_TREE_FOREACH_SAFE +#ifndef RB_TREE_PREV +#define RB_TREE_NEXT(T, N) rb_tree_iterate((T), (N), RB_DIR_RIGHT) +#define RB_TREE_PREV(T, N) rb_tree_iterate((T), (N), RB_DIR_LEFT) +#endif +#define RB_TREE_FOREACH_SAFE(N, T, S) \ + for ((N) = RB_TREE_MIN(T); \ + (N) && ((S) = RB_TREE_NEXT((T), (N)), 1); \ + (N) = (S)) +#define RB_TREE_FOREACH_REVERSE_SAFE(N, T, S) \ + for ((N) = RB_TREE_MAX(T); \ + (N) && ((S) = RB_TREE_PREV((T), (N)), 1); \ + (N) = (S)) +#endif + +#ifdef RT_FREE_ROUTE_TABLE_STATS +static size_t croutes; +static size_t nroutes; +static size_t froutes; +static size_t mroutes; +#endif + +static void +rt_maskedaddr(struct sockaddr *dst, + const struct sockaddr *addr, const struct sockaddr *netmask) +{ + const char *addrp = addr->sa_data, *netmaskp = netmask->sa_data; + char *dstp = dst->sa_data; + const char *addre = (char *)dst + sa_len(addr); + const char *netmaske = (char *)dst + MIN(sa_len(addr), sa_len(netmask)); + + dst->sa_family = addr->sa_family; +#ifdef HAVE_SA_LEN + dst->sa_len = addr->sa_len; +#endif + + if (sa_is_unspecified(netmask)) { + if (addre > dstp) + memcpy(dstp, addrp, (size_t)(addre - dstp)); + return; + } + + while (dstp < netmaske) + *dstp++ = *addrp++ & *netmaskp++; + if (dstp < addre) + memset(dstp, 0, (size_t)(addre - dstp)); +} + +/* + * On some systems, host routes have no need for a netmask. + * However DHCP specifies host routes using an all-ones netmask. + * This handy function allows easy comparison when the two + * differ. + */ +static int +rt_cmp_netmask(const struct rt *rt1, const struct rt *rt2) +{ + + if (rt1->rt_flags & RTF_HOST && rt2->rt_flags & RTF_HOST) + return 0; + return sa_cmp(&rt1->rt_netmask, &rt2->rt_netmask); +} + +int +rt_cmp_dest(const struct rt *rt1, const struct rt *rt2) +{ + union sa_ss ma1 = { .sa.sa_family = AF_UNSPEC }; + union sa_ss ma2 = { .sa.sa_family = AF_UNSPEC }; + int c; + + rt_maskedaddr(&ma1.sa, &rt1->rt_dest, &rt1->rt_netmask); + rt_maskedaddr(&ma2.sa, &rt2->rt_dest, &rt2->rt_netmask); + c = sa_cmp(&ma1.sa, &ma2.sa); + if (c != 0) + return c; + + return rt_cmp_netmask(rt1, rt2); +} + +static int +rt_compare_os(__unused void *context, const void *node1, const void *node2) +{ + const struct rt *rt1 = node1, *rt2 = node2; + int c; + + /* Sort by masked destination. */ + c = rt_cmp_dest(rt1, rt2); + if (c != 0) + return c; + +#ifdef HAVE_ROUTE_METRIC + c = (int)(rt1->rt_ifp->metric - rt2->rt_ifp->metric); +#endif + return c; +} + +static int +rt_compare_list(__unused void *context, const void *node1, const void *node2) +{ + const struct rt *rt1 = node1, *rt2 = node2; + + if (rt1->rt_order > rt2->rt_order) + return 1; + if (rt1->rt_order < rt2->rt_order) + return -1; + return 0; +} + +static int +rt_compare_proto(void *context, const void *node1, const void *node2) +{ + const struct rt *rt1 = node1, *rt2 = node2; + int c; + struct interface *ifp1, *ifp2; + + assert(rt1->rt_ifp != NULL); + assert(rt2->rt_ifp != NULL); + ifp1 = rt1->rt_ifp; + ifp2 = rt2->rt_ifp; + + /* Prefer interfaces with a carrier. */ + c = ifp1->carrier - ifp2->carrier; + if (c != 0) + return -c; + + /* Prefer roaming over non roaming if both carriers are down. */ + if (ifp1->carrier == LINK_DOWN && ifp2->carrier == LINK_DOWN) { + bool roam1 = if_roaming(ifp1); + bool roam2 = if_roaming(ifp2); + + if (roam1 != roam2) + return roam1 ? 1 : -1; + } + +#ifdef INET + /* IPv4LL routes always come last */ + if (rt1->rt_dflags & RTDF_IPV4LL && !(rt2->rt_dflags & RTDF_IPV4LL)) + return -1; + else if (!(rt1->rt_dflags & RTDF_IPV4LL) && rt2->rt_dflags & RTDF_IPV4LL) + return 1; +#endif + + /* Lower metric interfaces come first. */ + c = (int)(ifp1->metric - ifp2->metric); + if (c != 0) + return c; + + /* Finally the order in which the route was given to us. */ + return rt_compare_list(context, rt1, rt2); +} + +static const rb_tree_ops_t rt_compare_os_ops = { + .rbto_compare_nodes = rt_compare_os, + .rbto_compare_key = rt_compare_os, + .rbto_node_offset = offsetof(struct rt, rt_tree), + .rbto_context = NULL +}; + +const rb_tree_ops_t rt_compare_list_ops = { + .rbto_compare_nodes = rt_compare_list, + .rbto_compare_key = rt_compare_list, + .rbto_node_offset = offsetof(struct rt, rt_tree), + .rbto_context = NULL +}; + +const rb_tree_ops_t rt_compare_proto_ops = { + .rbto_compare_nodes = rt_compare_proto, + .rbto_compare_key = rt_compare_proto, + .rbto_node_offset = offsetof(struct rt, rt_tree), + .rbto_context = NULL +}; + +#ifdef RT_FREE_ROUTE_TABLE +static int +rt_compare_free(__unused void *context, const void *node1, const void *node2) +{ + + return node1 == node2 ? 0 : node1 < node2 ? -1 : 1; +} + +static const rb_tree_ops_t rt_compare_free_ops = { + .rbto_compare_nodes = rt_compare_free, + .rbto_compare_key = rt_compare_free, + .rbto_node_offset = offsetof(struct rt, rt_tree), + .rbto_context = NULL +}; +#endif + +void +rt_init(struct dhcpcd_ctx *ctx) +{ + + rb_tree_init(&ctx->routes, &rt_compare_os_ops); +#ifdef RT_FREE_ROUTE_TABLE + rb_tree_init(&ctx->froutes, &rt_compare_free_ops); +#endif +} + +bool +rt_is_default(const struct rt *rt) +{ + + return sa_is_unspecified(&rt->rt_dest) && + sa_is_unspecified(&rt->rt_netmask); +} + +static void +rt_desc(const char *cmd, const struct rt *rt) +{ + char dest[INET_MAX_ADDRSTRLEN], gateway[INET_MAX_ADDRSTRLEN]; + int prefix; + const char *ifname; + bool gateway_unspec; + + assert(cmd != NULL); + assert(rt != NULL); + + sa_addrtop(&rt->rt_dest, dest, sizeof(dest)); + prefix = sa_toprefix(&rt->rt_netmask); + sa_addrtop(&rt->rt_gateway, gateway, sizeof(gateway)); + gateway_unspec = sa_is_unspecified(&rt->rt_gateway); + ifname = rt->rt_ifp == NULL ? "(null)" : rt->rt_ifp->name; + + if (rt->rt_flags & RTF_HOST) { + if (gateway_unspec) + loginfox("%s: %s host route to %s", + ifname, cmd, dest); + else + loginfox("%s: %s host route to %s via %s", + ifname, cmd, dest, gateway); + } else if (rt_is_default(rt)) { + if (gateway_unspec) + loginfox("%s: %s default route", + ifname, cmd); + else + loginfox("%s: %s default route via %s", + ifname, cmd, gateway); + } else if (gateway_unspec) + loginfox("%s: %s%s route to %s/%d", + ifname, cmd, + rt->rt_flags & RTF_REJECT ? " reject" : "", + dest, prefix); + else + loginfox("%s: %s%s route to %s/%d via %s", + ifname, cmd, + rt->rt_flags & RTF_REJECT ? " reject" : "", + dest, prefix, gateway); +} + +void +rt_headclear0(struct dhcpcd_ctx *ctx, rb_tree_t *rts, int af) +{ + struct rt *rt, *rtn; + + if (rts == NULL) + return; + assert(ctx != NULL); +#ifdef RT_FREE_ROUTE_TABLE + assert(&ctx->froutes != rts); +#endif + + RB_TREE_FOREACH_SAFE(rt, rts, rtn) { + if (af != AF_UNSPEC && + rt->rt_dest.sa_family != af && + rt->rt_gateway.sa_family != af) + continue; + rb_tree_remove_node(rts, rt); + rt_free(rt); + } +} + +void +rt_headclear(rb_tree_t *rts, int af) +{ + struct rt *rt; + + if (rts == NULL || (rt = RB_TREE_MIN(rts)) == NULL) + return; + rt_headclear0(rt->rt_ifp->ctx, rts, af); +} + +static void +rt_headfree(rb_tree_t *rts) +{ + struct rt *rt; + + while ((rt = RB_TREE_MIN(rts)) != NULL) { + rb_tree_remove_node(rts, rt); + free(rt); + } +} + +void +rt_dispose(struct dhcpcd_ctx *ctx) +{ + + assert(ctx != NULL); + rt_headfree(&ctx->routes); +#ifdef RT_FREE_ROUTE_TABLE + rt_headfree(&ctx->froutes); +#ifdef RT_FREE_ROUTE_TABLE_STATS + logdebugx("free route list used %zu times", froutes); + logdebugx("new routes from route free list %zu", nroutes); + logdebugx("maximum route free list size %zu", mroutes); +#endif +#endif +} + +struct rt * +rt_new0(struct dhcpcd_ctx *ctx) +{ + struct rt *rt; + + assert(ctx != NULL); +#ifdef RT_FREE_ROUTE_TABLE + if ((rt = RB_TREE_MIN(&ctx->froutes)) != NULL) { + rb_tree_remove_node(&ctx->froutes, rt); +#ifdef RT_FREE_ROUTE_TABLE_STATS + croutes--; + nroutes++; +#endif + } else +#endif + if ((rt = malloc(sizeof(*rt))) == NULL) { + logerr(__func__); + return NULL; + } + memset(rt, 0, sizeof(*rt)); + return rt; +} + +void +rt_setif(struct rt *rt, struct interface *ifp) +{ + + assert(rt != NULL); + assert(ifp != NULL); + rt->rt_ifp = ifp; +#ifdef HAVE_ROUTE_METRIC + rt->rt_metric = ifp->metric; + if (if_roaming(ifp)) + rt->rt_metric += RTMETRIC_ROAM; +#endif +} + +struct rt * +rt_new(struct interface *ifp) +{ + struct rt *rt; + + assert(ifp != NULL); + if ((rt = rt_new0(ifp->ctx)) == NULL) + return NULL; + rt_setif(rt, ifp); + return rt; +} + +struct rt * +rt_proto_add_ctx(rb_tree_t *tree, struct rt *rt, struct dhcpcd_ctx *ctx) +{ + + rt->rt_order = ctx->rt_order++; + if (rb_tree_insert_node(tree, rt) == rt) + return rt; + + rt_free(rt); + errno = EEXIST; + return NULL; +} + +struct rt * +rt_proto_add(rb_tree_t *tree, struct rt *rt) +{ + + assert (rt->rt_ifp != NULL); + return rt_proto_add_ctx(tree, rt, rt->rt_ifp->ctx); +} + +void +rt_free(struct rt *rt) +{ +#ifdef RT_FREE_ROUTE_TABLE + struct dhcpcd_ctx *ctx; + + assert(rt != NULL); + if (rt->rt_ifp == NULL) { + free(rt); + return; + } + + ctx = rt->rt_ifp->ctx; + rb_tree_insert_node(&ctx->froutes, rt); +#ifdef RT_FREE_ROUTE_TABLE_STATS + croutes++; + froutes++; + if (croutes > mroutes) + mroutes = croutes; +#endif +#else + free(rt); +#endif +} + +void +rt_freeif(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx; + struct rt *rt, *rtn; + + if (ifp == NULL) + return; + ctx = ifp->ctx; + RB_TREE_FOREACH_SAFE(rt, &ctx->routes, rtn) { + if (rt->rt_ifp == ifp) { + rb_tree_remove_node(&ctx->routes, rt); + rt_free(rt); + } + } +} + +/* If something other than dhcpcd removes a route, + * we need to remove it from our internal table. */ +void +rt_recvrt(int cmd, const struct rt *rt, pid_t pid) +{ + struct dhcpcd_ctx *ctx; + struct rt *f; + + assert(rt != NULL); + assert(rt->rt_ifp != NULL); + assert(rt->rt_ifp->ctx != NULL); + + ctx = rt->rt_ifp->ctx; + + switch(cmd) { + case RTM_DELETE: + f = rb_tree_find_node(&ctx->routes, rt); + if (f != NULL) { + char buf[32]; + + rb_tree_remove_node(&ctx->routes, f); + snprintf(buf, sizeof(buf), "pid %d deleted", pid); + rt_desc(buf, f); + rt_free(f); + } + break; + } + +#if defined(IPV4LL) && defined(HAVE_ROUTE_METRIC) + if (rt->rt_dest.sa_family == AF_INET) + ipv4ll_recvrt(cmd, rt); +#endif +} + +static bool +rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort) +{ + struct dhcpcd_ctx *ctx; + bool change, kroute, result; + + assert(nrt != NULL); + ctx = nrt->rt_ifp->ctx; + + /* + * Don't install a gateway if not asked to. + * This option is mainly for VPN users who want their VPN to be the + * default route. + * Because VPN's generally don't care about route management + * beyond their own, a longer term solution would be to remove this + * and get the VPN to inject the default route into dhcpcd somehow. + */ + if (((nrt->rt_ifp->active && + !(nrt->rt_ifp->options->options & DHCPCD_GATEWAY)) || + (!nrt->rt_ifp->active && !(ctx->options & DHCPCD_GATEWAY))) && + sa_is_unspecified(&nrt->rt_dest) && + sa_is_unspecified(&nrt->rt_netmask)) + return false; + + rt_desc(ort == NULL ? "adding" : "changing", nrt); + + change = kroute = result = false; + if (ort == NULL) { + ort = rb_tree_find_node(kroutes, nrt); + if (ort != NULL && + ((ort->rt_flags & RTF_REJECT && + nrt->rt_flags & RTF_REJECT) || + (ort->rt_ifp == nrt->rt_ifp && +#ifdef HAVE_ROUTE_METRIC + ort->rt_metric == nrt->rt_metric && +#endif + sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0))) + { + if (ort->rt_mtu == nrt->rt_mtu) + return true; + change = true; + kroute = true; + } + } else if (ort->rt_dflags & RTDF_FAKE && + !(nrt->rt_dflags & RTDF_FAKE) && + ort->rt_ifp == nrt->rt_ifp && +#ifdef HAVE_ROUTE_METRIC + ort->rt_metric == nrt->rt_metric && +#endif + sa_cmp(&ort->rt_dest, &nrt->rt_dest) == 0 && + rt_cmp_netmask(ort, nrt) == 0 && + sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0) + { + if (ort->rt_mtu == nrt->rt_mtu) + return true; + change = true; + } + +#ifdef RTF_CLONING + /* BSD can set routes to be cloning routes. + * Cloned routes inherit the parent flags. + * As such, we need to delete and re-add the route to flush children + * to correct the flags. */ + if (change && ort != NULL && ort->rt_flags & RTF_CLONING) + change = false; +#endif + + if (change) { + if (if_route(RTM_CHANGE, nrt) != -1) { + result = true; + goto out; + } + if (errno != ESRCH) + logerr("if_route (CHG)"); + } + +#ifdef HAVE_ROUTE_METRIC + /* With route metrics, we can safely add the new route before + * deleting the old route. */ + if (if_route(RTM_ADD, nrt) != -1) { + if (ort != NULL) { + if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH) + logerr("if_route (DEL)"); + } + result = true; + goto out; + } + + /* If the kernel claims the route exists we need to rip out the + * old one first. */ + if (errno != EEXIST || ort == NULL) + goto logerr; +#endif + + /* No route metrics, we need to delete the old route before + * adding the new one. */ +#ifdef ROUTE_PER_GATEWAY + errno = 0; +#endif + if (ort != NULL) { + if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH) + logerr("if_route (DEL)"); + else + kroute = false; + } +#ifdef ROUTE_PER_GATEWAY + /* The OS allows many routes to the same dest with different gateways. + * dhcpcd does not support this yet, so for the time being just keep on + * deleting the route until there is an error. */ + if (ort != NULL && errno == 0) { + for (;;) { + if (if_route(RTM_DELETE, ort) == -1) + break; + } + } +#endif + + /* Shouldn't need to check for EEXIST, but some kernels don't + * dump the subnet route just after we added the address. */ + if (if_route(RTM_ADD, nrt) != -1 || errno == EEXIST) { + result = true; + goto out; + } + +#ifdef HAVE_ROUTE_METRIC +logerr: +#endif + logerr("if_route (ADD)"); + +out: + if (kroute) { + rb_tree_remove_node(kroutes, ort); + rt_free(ort); + } + return result; +} + +static bool +rt_delete(struct rt *rt) +{ + int retval; + + rt_desc("deleting", rt); + retval = if_route(RTM_DELETE, rt) == -1 ? false : true; + if (!retval && errno != ENOENT && errno != ESRCH) + logerr(__func__); + return retval; +} + +static bool +rt_cmp(const struct rt *r1, const struct rt *r2) +{ + + return (r1->rt_ifp == r2->rt_ifp && +#ifdef HAVE_ROUTE_METRIC + r1->rt_metric == r2->rt_metric && +#endif + sa_cmp(&r1->rt_gateway, &r2->rt_gateway) == 0); +} + +static bool +rt_doroute(rb_tree_t *kroutes, struct rt *rt) +{ + struct dhcpcd_ctx *ctx; + struct rt *or; + + ctx = rt->rt_ifp->ctx; + /* Do we already manage it? */ + or = rb_tree_find_node(&ctx->routes, rt); + if (or != NULL) { + if (rt->rt_dflags & RTDF_FAKE) + return true; + if (or->rt_dflags & RTDF_FAKE || + !rt_cmp(rt, or) || + (rt->rt_ifa.sa_family != AF_UNSPEC && + sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) || + or->rt_mtu != rt->rt_mtu) + { + if (!rt_add(kroutes, rt, or)) + return false; + } + rb_tree_remove_node(&ctx->routes, or); + rt_free(or); + } else { + if (rt->rt_dflags & RTDF_FAKE) { + or = rb_tree_find_node(kroutes, rt); + if (or == NULL) + return false; + if (!rt_cmp(rt, or)) + return false; + } else { + if (!rt_add(kroutes, rt, NULL)) + return false; + } + } + + return true; +} + +void +rt_build(struct dhcpcd_ctx *ctx, int af) +{ + rb_tree_t routes, added, kroutes; + struct rt *rt, *rtn; + unsigned long long o; + + rb_tree_init(&routes, &rt_compare_proto_ops); + rb_tree_init(&added, &rt_compare_os_ops); + rb_tree_init(&kroutes, &rt_compare_os_ops); + if_initrt(ctx, &kroutes, af); + ctx->rt_order = 0; + ctx->options |= DHCPCD_RTBUILD; + +#ifdef INET + if (!inet_getroutes(ctx, &routes)) + goto getfail; +#endif +#ifdef INET6 + if (!inet6_getroutes(ctx, &routes)) + goto getfail; +#endif + +#ifdef BSD + /* Rewind the miss filter */ + ctx->rt_missfilterlen = 0; +#endif + + RB_TREE_FOREACH_SAFE(rt, &routes, rtn) { + if (rt->rt_ifp->active) { + if (!(rt->rt_ifp->options->options & DHCPCD_CONFIGURE)) + continue; + } else if (!(ctx->options & DHCPCD_CONFIGURE)) + continue; +#ifdef BSD + if (rt_is_default(rt) && + if_missfilter(rt->rt_ifp, &rt->rt_gateway) == -1) + logerr("if_missfilter"); +#endif + if ((rt->rt_dest.sa_family != af && + rt->rt_dest.sa_family != AF_UNSPEC) || + (rt->rt_gateway.sa_family != af && + rt->rt_gateway.sa_family != AF_UNSPEC)) + continue; + /* Is this route already in our table? */ + if (rb_tree_find_node(&added, rt) != NULL) + continue; + if (rt_doroute(&kroutes, rt)) { + rb_tree_remove_node(&routes, rt); + if (rb_tree_insert_node(&added, rt) != rt) { + errno = EEXIST; + logerr(__func__); + rt_free(rt); + } + } + } + +#ifdef BSD + if (if_missfilter_apply(ctx) == -1 && errno != ENOTSUP) + logerr("if_missfilter_apply"); +#endif + + /* Remove old routes we used to manage. */ + RB_TREE_FOREACH_REVERSE_SAFE(rt, &ctx->routes, rtn) { + if ((rt->rt_dest.sa_family != af && + rt->rt_dest.sa_family != AF_UNSPEC) || + (rt->rt_gateway.sa_family != af && + rt->rt_gateway.sa_family != AF_UNSPEC)) + continue; + rb_tree_remove_node(&ctx->routes, rt); + if (rb_tree_find_node(&added, rt) == NULL) { + o = rt->rt_ifp->options ? + rt->rt_ifp->options->options : + ctx->options; + if ((o & + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + rt_delete(rt); + } + rt_free(rt); + } + + /* XXX This needs to be optimised. */ + while ((rt = RB_TREE_MIN(&added)) != NULL) { + rb_tree_remove_node(&added, rt); + if (rb_tree_insert_node(&ctx->routes, rt) != rt) { + errno = EEXIST; + logerr(__func__); + rt_free(rt); + } + } + +getfail: + rt_headclear(&routes, AF_UNSPEC); + rt_headclear(&kroutes, AF_UNSPEC); +} Index: src/sa.h =================================================================== --- /dev/null +++ src/sa.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Socket Address handling for dhcpcd + * Copyright (c) 2015-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. + */ + +#ifndef SA_H +#define SA_H + +#include +#include + +union sa_ss { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +#ifdef BSD +#define HAVE_SA_LEN +#endif + +/* Allow for a sockaddr_dl being printed too. */ +#define INET_MAX_ADDRSTRLEN (20 * 3) + +#ifdef INET +#define satosin(sa) ((struct sockaddr_in *)(void *)(sa)) +#define satocsin(sa) ((const struct sockaddr_in *)(const void *)(sa)) +#endif +#ifdef INET6 +#define satosin6(sa) ((struct sockaddr_in6 *)(void *)(sa)) +#define satocsin6(sa) ((const struct sockaddr_in6 *)(const void *)(sa)) +#endif + +socklen_t sa_addroffset(const struct sockaddr *sa); +socklen_t sa_addrlen(const struct sockaddr *sa); +#ifdef HAVE_SA_LEN +#define sa_len(sa) ((sa)->sa_len) +#else +socklen_t sa_len(const struct sockaddr *sa); +#endif +bool sa_is_unspecified(const struct sockaddr *); +bool sa_is_allones(const struct sockaddr *); +bool sa_is_loopback(const struct sockaddr *); +void *sa_toaddr(struct sockaddr *); +int sa_toprefix(const struct sockaddr *); +int sa_fromprefix(struct sockaddr *, int); +const char *sa_addrtop(const struct sockaddr *, char *, socklen_t); +int sa_cmp(const struct sockaddr *, const struct sockaddr *); +void sa_in_init(struct sockaddr *, const struct in_addr *); +void sa_in6_init(struct sockaddr *, const struct in6_addr *); + +#endif Index: src/sa.c =================================================================== --- /dev/null +++ src/sa.c @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Socket Address handling for dhcpcd + * Copyright (c) 2015-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. + */ + +#include +#include + +#include +#ifdef AF_LINK +#include +#elif defined(AF_PACKET) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "sa.h" + +#ifndef NDEBUG +static bool sa_inprefix; +#endif + +socklen_t +sa_addroffset(const struct sockaddr *sa) +{ + + assert(sa != NULL); + switch(sa->sa_family) { +#ifdef INET + case AF_INET: + return offsetof(struct sockaddr_in, sin_addr) + + offsetof(struct in_addr, s_addr); +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + return offsetof(struct sockaddr_in6, sin6_addr) + + offsetof(struct in6_addr, s6_addr); +#endif /* INET6 */ + default: + errno = EAFNOSUPPORT; + return 0; + } +} + +socklen_t +sa_addrlen(const struct sockaddr *sa) +{ +#define membersize(type, member) sizeof(((type *)0)->member) + assert(sa != NULL); + switch(sa->sa_family) { +#ifdef INET + case AF_INET: + return membersize(struct in_addr, s_addr); +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + return membersize(struct in6_addr, s6_addr); +#endif /* INET6 */ + default: + errno = EAFNOSUPPORT; + return 0; + } +} + +#ifndef HAVE_SA_LEN +socklen_t +sa_len(const struct sockaddr *sa) +{ + + switch (sa->sa_family) { +#ifdef AF_LINK + case AF_LINK: + return sizeof(struct sockaddr_dl); +#endif +#ifdef AF_PACKET + case AF_PACKET: + return sizeof(struct sockaddr_ll); +#endif + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return sizeof(struct sockaddr); + } +} +#endif + +bool +sa_is_unspecified(const struct sockaddr *sa) +{ + + assert(sa != NULL); + switch(sa->sa_family) { + case AF_UNSPEC: + return true; +#ifdef INET + case AF_INET: + return satocsin(sa)->sin_addr.s_addr == INADDR_ANY; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + return IN6_IS_ADDR_UNSPECIFIED(&satocsin6(sa)->sin6_addr); +#endif /* INET6 */ + default: + errno = EAFNOSUPPORT; + return false; + } +} + +#ifdef INET6 +#ifndef IN6MASK128 +#define IN6MASK128 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}} +#endif +static const struct in6_addr in6allones = IN6MASK128; +#endif + +bool +sa_is_allones(const struct sockaddr *sa) +{ + + assert(sa != NULL); + switch(sa->sa_family) { + case AF_UNSPEC: + return false; +#ifdef INET + case AF_INET: + { + const struct sockaddr_in *sin; + + sin = satocsin(sa); + return sin->sin_addr.s_addr == INADDR_BROADCAST; + } +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + { + const struct sockaddr_in6 *sin6; + + sin6 = satocsin6(sa); + return IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &in6allones); + } +#endif /* INET6 */ + default: + errno = EAFNOSUPPORT; + return false; + } +} + +bool +sa_is_loopback(const struct sockaddr *sa) +{ + + assert(sa != NULL); + switch(sa->sa_family) { + case AF_UNSPEC: + return false; +#ifdef INET + case AF_INET: + { + const struct sockaddr_in *sin; + + sin = satocsin(sa); + return sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK); + } +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + { + const struct sockaddr_in6 *sin6; + + sin6 = satocsin6(sa); + return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr); + } +#endif /* INET6 */ + default: + errno = EAFNOSUPPORT; + return false; + } +} + +int +sa_toprefix(const struct sockaddr *sa) +{ + int prefix; + + assert(sa != NULL); + switch(sa->sa_family) { +#ifdef INET + case AF_INET: + { + const struct sockaddr_in *sin; + uint32_t mask; + + sin = satocsin(sa); + if (sin->sin_addr.s_addr == INADDR_ANY) { + prefix = 0; + break; + } + mask = ntohl(sin->sin_addr.s_addr); + prefix = 33 - ffs((int)mask); /* 33 - (1 .. 32) -> 32 .. 1 */ + if (prefix < 32) { /* more than 1 bit in mask */ + /* check for non-contig netmask */ + if ((mask^(((1U << prefix)-1) << (32 - prefix))) != 0) { + errno = EINVAL; + return -1; /* noncontig, no pfxlen */ + } + } + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + const struct sockaddr_in6 *sin6; + int x, y; + const uint8_t *lim, *p; + + sin6 = satocsin6(sa); + p = (const uint8_t *)sin6->sin6_addr.s6_addr; + lim = p + sizeof(sin6->sin6_addr.s6_addr); + for (x = 0; p < lim; x++, p++) { + if (*p != 0xff) + break; + } + y = 0; + if (p < lim) { + for (y = 0; y < NBBY; y++) { + if ((*p & (0x80 >> y)) == 0) + break; + } + } + + /* + * when the limit pointer is given, do a stricter check on the + * remaining bits. + */ + if (p < lim) { + if (y != 0 && (*p & (0x00ff >> y)) != 0) + return 0; + for (p = p + 1; p < lim; p++) + if (*p != 0) + return 0; + } + + prefix = x * NBBY + y; + break; + } +#endif + default: + errno = EAFNOSUPPORT; + return -1; + } + +#ifndef NDEBUG + /* Ensure the calculation is correct */ + if (!sa_inprefix) { + union sa_ss ss = { .sa = { .sa_family = sa->sa_family } }; + + sa_inprefix = true; + sa_fromprefix(&ss.sa, prefix); + assert(sa_cmp(sa, &ss.sa) == 0); + sa_inprefix = false; + } +#endif + + return prefix; +} + +int +sa_fromprefix(struct sockaddr *sa, int prefix) +{ + uint8_t *ap; + int max_prefix, bytes, bits, i; + + switch (sa->sa_family) { +#ifdef INET + case AF_INET: + max_prefix = 32; +#ifdef HAVE_SA_LEN + sa->sa_len = sizeof(struct sockaddr_in); +#endif + break; +#endif +#ifdef INET6 + case AF_INET6: + max_prefix = 128; +#ifdef HAVE_SA_LEN + sa->sa_len = sizeof(struct sockaddr_in6); +#endif + break; +#endif + default: + errno = EAFNOSUPPORT; + return -1; + } + + bytes = prefix / NBBY; + bits = prefix % NBBY; + + ap = (uint8_t *)sa + sa_addroffset(sa); + for (i = 0; i < bytes; i++) + *ap++ = 0xff; + if (bits) { + uint8_t a; + + a = 0xff; + a = (uint8_t)(a << (8 - bits)); + *ap++ = a; + } + bytes = (max_prefix - prefix) / NBBY; + for (i = 0; i < bytes; i++) + *ap++ = 0x00; + +#ifndef NDEBUG + /* Ensure the calculation is correct */ + if (!sa_inprefix) { + sa_inprefix = true; + assert(sa_toprefix(sa) == prefix); + sa_inprefix = false; + } +#endif + return 0; +} + +/* inet_ntop, but for sockaddr. */ +const char * +sa_addrtop(const struct sockaddr *sa, char *buf, socklen_t len) +{ + const void *addr; + + assert(buf != NULL); + assert(len > 0); + + if (sa->sa_family == 0) { + *buf = '\0'; + return NULL; + } + +#ifdef AF_LINK +#ifndef CLLADDR +#define CLLADDR(sdl) (const void *)((sdl)->sdl_data + (sdl)->sdl_nlen) +#endif + if (sa->sa_family == AF_LINK) { + const struct sockaddr_dl *sdl; + + sdl = (const void *)sa; + if (sdl->sdl_alen == 0) { + if (snprintf(buf, len, "link#%d", sdl->sdl_index) == -1) + return NULL; + return buf; + } + return hwaddr_ntoa(CLLADDR(sdl), sdl->sdl_alen, buf, len); + } +#elif defined(AF_PACKET) + if (sa->sa_family == AF_PACKET) { + const struct sockaddr_ll *sll; + + sll = (const void *)sa; + return hwaddr_ntoa(sll->sll_addr, sll->sll_halen, buf, len); + } +#endif + addr = (const char *)sa + sa_addroffset(sa); + return inet_ntop(sa->sa_family, addr, buf, len); +} + +int +sa_cmp(const struct sockaddr *sa1, const struct sockaddr *sa2) +{ + socklen_t offset, len; + + assert(sa1 != NULL); + assert(sa2 != NULL); + + /* Treat AF_UNSPEC as the unspecified address. */ + if ((sa1->sa_family == AF_UNSPEC || sa2->sa_family == AF_UNSPEC) && + sa_is_unspecified(sa1) && sa_is_unspecified(sa2)) + return 0; + + if (sa1->sa_family != sa2->sa_family) + return sa1->sa_family - sa2->sa_family; + +#ifdef HAVE_SA_LEN + len = MIN(sa1->sa_len, sa2->sa_len); +#endif + + switch (sa1->sa_family) { +#ifdef INET + case AF_INET: + offset = offsetof(struct sockaddr_in, sin_addr); +#ifdef HAVE_SA_LEN + len -= offset; + len = MIN(len, sizeof(struct in_addr)); +#else + len = sizeof(struct in_addr); +#endif + break; +#endif +#ifdef INET6 + case AF_INET6: + offset = offsetof(struct sockaddr_in6, sin6_addr); +#ifdef HAVE_SA_LEN + len -= offset; + len = MIN(len, sizeof(struct in6_addr)); +#else + len = sizeof(struct in6_addr); +#endif + break; +#endif + default: + offset = 0; +#ifndef HAVE_SA_LEN + len = sizeof(struct sockaddr); +#endif + break; + } + + return memcmp((const char *)sa1 + offset, + (const char *)sa2 + offset, + len); +} + +#ifdef INET +void +sa_in_init(struct sockaddr *sa, const struct in_addr *addr) +{ + struct sockaddr_in *sin; + + assert(sa != NULL); + assert(addr != NULL); + sin = satosin(sa); + sin->sin_family = AF_INET; +#ifdef HAVE_SA_LEN + sin->sin_len = sizeof(*sin); +#endif + sin->sin_addr.s_addr = addr->s_addr; +} +#endif + +#ifdef INET6 +void +sa_in6_init(struct sockaddr *sa, const struct in6_addr *addr) +{ + struct sockaddr_in6 *sin6; + + assert(sa != NULL); + assert(addr != NULL); + sin6 = satosin6(sa); + sin6->sin6_family = AF_INET6; +#ifdef HAVE_SA_LEN + sin6->sin6_len = sizeof(*sin6); +#endif + memcpy(&sin6->sin6_addr.s6_addr, &addr->s6_addr, + sizeof(sin6->sin6_addr.s6_addr)); +} +#endif Index: src/script.h =================================================================== --- /dev/null +++ src/script.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#ifndef SCRIPT_H +#define SCRIPT_H + +#include "control.h" + +__printflike(2, 3) int efprintf(FILE *, const char *, ...); +void if_printoptions(void); +char ** script_buftoenv(struct dhcpcd_ctx *, char *, size_t); +pid_t script_exec(char *const *, char *const *); +int send_interface(struct fd_list *, const struct interface *, int); +int script_dump(const char *, size_t); +int script_runreason(const struct interface *, const char *); +#endif Index: src/script.c =================================================================== --- /dev/null +++ src/script.c @@ -0,0 +1,788 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * 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. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4ll.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "script.h" + +#define DEFAULT_PATH "/usr/bin:/usr/sbin:/bin:/sbin" + +static const char * const if_params[] = { + "interface", + "protocol", + "reason", + "pid", + "ifcarrier", + "ifmetric", + "ifwireless", + "ifflags", + "ssid", + "profile", + "interface_order", + NULL +}; + +static const char * true_str = "true"; +static const char * false_str = "false"; + +void +if_printoptions(void) +{ + const char * const *p; + + for (p = if_params; *p; p++) + printf(" - %s\n", *p); +} + +pid_t +script_exec(char *const *argv, char *const *env) +{ + pid_t pid = 0; + posix_spawnattr_t attr; + int r; +#ifdef USE_SIGNALS + size_t i; + short flags; + sigset_t defsigs; +#else + UNUSED(ctx); +#endif + + /* posix_spawn is a safe way of executing another image + * and changing signals back to how they should be. */ + if (posix_spawnattr_init(&attr) == -1) + return -1; +#ifdef USE_SIGNALS + flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; + posix_spawnattr_setflags(&attr, flags); + sigemptyset(&defsigs); + posix_spawnattr_setsigmask(&attr, &defsigs); + for (i = 0; i < dhcpcd_signals_len; i++) + sigaddset(&defsigs, dhcpcd_signals[i]); + for (i = 0; i < dhcpcd_signals_ignore_len; i++) + sigaddset(&defsigs, dhcpcd_signals_ignore[i]); + posix_spawnattr_setsigdefault(&attr, &defsigs); +#endif + errno = 0; + r = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); + posix_spawnattr_destroy(&attr); + if (r) { + errno = r; + return -1; + } + return pid; +} + +#ifdef INET +static int +append_config(FILE *fp, const char *prefix, const char *const *config) +{ + size_t i; + + if (config == NULL) + return 0; + + /* Do we need to replace existing config rather than append? */ + for (i = 0; config[i] != NULL; i++) { + if (efprintf(fp, "%s_%s", prefix, config[i]) == -1) + return -1; + } + return 1; +} + +#endif + +#define PROTO_LINK 0 +#define PROTO_DHCP 1 +#define PROTO_IPV4LL 2 +#define PROTO_RA 3 +#define PROTO_DHCP6 4 +#define PROTO_STATIC6 5 +static const char *protocols[] = { + "link", + "dhcp", + "ipv4ll", + "ra", + "dhcp6", + "static6" +}; + +int +efprintf(FILE *fp, const char *fmt, ...) +{ + va_list args; + int r; + + va_start(args, fmt); + r = vfprintf(fp, fmt, args); + va_end(args); + if (r == -1) + return -1; + /* Write a trailing NULL so we can easily create env strings. */ + if (fputc('\0', fp) == EOF) + return -1; + return r; +} + +char ** +script_buftoenv(struct dhcpcd_ctx *ctx, char *buf, size_t len) +{ + char **env, **envp, *bufp, *endp; + size_t nenv; + + /* Count the terminated env strings. + * Assert that the terminations are correct. */ + nenv = 0; + endp = buf + len; + for (bufp = buf; bufp < endp; bufp++) { + if (*bufp == '\0') { +#ifndef NDEBUG + if (bufp + 1 < endp) + assert(*(bufp + 1) != '\0'); +#endif + nenv++; + } + } + assert(*(bufp - 1) == '\0'); + if (nenv == 0) + return NULL; + + if (ctx->script_envlen < nenv) { + env = reallocarray(ctx->script_env, nenv + 1, sizeof(*env)); + if (env == NULL) + return NULL; + ctx->script_env = env; + ctx->script_envlen = nenv; + } + + bufp = buf; + envp = ctx->script_env; + *envp++ = bufp++; + endp--; /* Avoid setting the last \0 to an invalid pointer */ + for (; bufp < endp; bufp++) { + if (*bufp == '\0') + *envp++ = bufp + 1; + } + *envp = NULL; + + return ctx->script_env; +} + +static long +make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, + const char *reason) +{ + FILE *fp; + long buf_pos, i; + char *path; + int protocol = PROTO_LINK; + const struct if_options *ifo; + const struct interface *ifp2; + int af; + bool is_stdin = ifp->name[0] == '\0'; + const char *if_up, *if_down; + rb_tree_t ifaces; + struct rt *rt; +#ifdef INET + const struct dhcp_state *state; +#ifdef IPV4LL + const struct ipv4ll_state *istate; +#endif +#endif +#ifdef DHCP6 + const struct dhcp6_state *d6_state; +#endif + +#ifdef HAVE_OPEN_MEMSTREAM + if (ctx->script_fp == NULL) { + fp = open_memstream(&ctx->script_buf, &ctx->script_buflen); + if (fp == NULL) + goto eexit; + ctx->script_fp = fp; + } else { + fp = ctx->script_fp; + rewind(fp); + } +#else + char tmpfile[] = "/tmp/dhcpcd-script-env-XXXXXX"; + int tmpfd; + + fp = NULL; + tmpfd = mkstemp(tmpfile); + if (tmpfd == -1) { + logerr("%s: mkstemp", __func__); + return -1; + } + unlink(tmpfile); + fp = fdopen(tmpfd, "w+"); + if (fp == NULL) { + close(tmpfd); + goto eexit; + } +#endif + + if (!(ifp->ctx->options & DHCPCD_DUMPLEASE)) { + /* Needed for scripts */ + path = getenv("PATH"); + if (efprintf(fp, "PATH=%s", + path == NULL ? DEFAULT_PATH : path) == -1) + goto eexit; + if (efprintf(fp, "pid=%d", getpid()) == -1) + goto eexit; + } + + if (!is_stdin) { + if (efprintf(fp, "reason=%s", reason) == -1) + goto eexit; + } + + ifo = ifp->options; +#ifdef INET + state = D_STATE(ifp); +#ifdef IPV4LL + istate = IPV4LL_CSTATE(ifp); +#endif +#endif +#ifdef DHCP6 + d6_state = D6_CSTATE(ifp); +#endif + if (strcmp(reason, "TEST") == 0) { + if (1 == 2) { + /* This space left intentionally blank + * as all the below statements are optional. */ + } +#ifdef INET6 +#ifdef DHCP6 + else if (d6_state && d6_state->new) + protocol = PROTO_DHCP6; +#endif + else if (ipv6nd_hasra(ifp)) + protocol = PROTO_RA; +#endif +#ifdef INET +#ifdef IPV4LL + else if (istate && istate->addr != NULL) + protocol = PROTO_IPV4LL; +#endif + else + protocol = PROTO_DHCP; +#endif + } +#ifdef INET6 + else if (strcmp(reason, "STATIC6") == 0) + protocol = PROTO_STATIC6; +#ifdef DHCP6 + else if (reason[strlen(reason) - 1] == '6') + protocol = PROTO_DHCP6; +#endif + else if (strcmp(reason, "ROUTERADVERT") == 0) + protocol = PROTO_RA; +#endif + else if (strcmp(reason, "PREINIT") == 0 || + strcmp(reason, "CARRIER") == 0 || + strcmp(reason, "NOCARRIER") == 0 || + strcmp(reason, "NOCARRIER_ROAMING") == 0 || + strcmp(reason, "UNKNOWN") == 0 || + strcmp(reason, "DEPARTED") == 0 || + strcmp(reason, "STOPPED") == 0) + protocol = PROTO_LINK; +#ifdef INET +#ifdef IPV4LL + else if (strcmp(reason, "IPV4LL") == 0) + protocol = PROTO_IPV4LL; +#endif + else + protocol = PROTO_DHCP; +#endif + + if (!is_stdin) { + if (efprintf(fp, "interface=%s", ifp->name) == -1) + goto eexit; + if (protocols[protocol] != NULL) { + if (efprintf(fp, "protocol=%s", + protocols[protocol]) == -1) + goto eexit; + } + } + if (ifp->ctx->options & DHCPCD_DUMPLEASE && protocol != PROTO_LINK) + goto dumplease; + if (efprintf(fp, "if_configured=%s", + ifo->options & DHCPCD_CONFIGURE ? "true" : "false") == -1) + goto eexit; + if (efprintf(fp, "ifcarrier=%s", + ifp->carrier == LINK_UNKNOWN ? "unknown" : + ifp->carrier == LINK_UP ? "up" : "down") == -1) + goto eexit; + if (efprintf(fp, "ifmetric=%d", ifp->metric) == -1) + goto eexit; + if (efprintf(fp, "ifwireless=%d", ifp->wireless) == -1) + goto eexit; + if (efprintf(fp, "ifflags=%u", ifp->flags) == -1) + goto eexit; + if (efprintf(fp, "ifmtu=%d", if_getmtu(ifp)) == -1) + goto eexit; + if (ifp->wireless) { + char pssid[IF_SSIDLEN * 4]; + + if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, + ifp->ssid, ifp->ssid_len) != -1) + { + if (efprintf(fp, "ifssid=%s", pssid) == -1) + goto eexit; + } + } + if (*ifp->profile != '\0') { + if (efprintf(fp, "profile=%s", ifp->profile) == -1) + goto eexit; + } + if (ifp->ctx->options & DHCPCD_DUMPLEASE) + goto dumplease; + + ifp->ctx->rt_order = 0; + rb_tree_init(&ifaces, &rt_compare_proto_ops); + TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { + if (!ifp2->active) + continue; + rt = rt_new(UNCONST(ifp2)); + if (rt == NULL) + goto eexit; + if (rt_proto_add(&ifaces, rt) != rt) + goto eexit; + } + if (fprintf(fp, "interface_order=") == -1) + goto eexit; + RB_TREE_FOREACH(rt, &ifaces) { + if (rt != RB_TREE_MIN(&ifaces) && + fprintf(fp, "%s", " ") == -1) + goto eexit; + if (fprintf(fp, "%s", rt->rt_ifp->name) == -1) + goto eexit; + } + rt_headclear(&ifaces, AF_UNSPEC); + if (fputc('\0', fp) == EOF) + goto eexit; + + if (strcmp(reason, "STOPPED") == 0) { + if_up = false_str; + if_down = ifo->options & DHCPCD_RELEASE ? true_str : false_str; + } else if (strcmp(reason, "TEST") == 0 || + strcmp(reason, "PREINIT") == 0 || + strcmp(reason, "CARRIER") == 0 || + strcmp(reason, "UNKNOWN") == 0) + { + if_up = false_str; + if_down = false_str; + } else if (strcmp(reason, "NOCARRIER") == 0) { + if_up = false_str; + if_down = true_str; + } else if (strcmp(reason, "NOCARRIER_ROAMING") == 0) { + if_up = true_str; + if_down = false_str; + } else if (1 == 2 /* appease ifdefs */ +#ifdef INET + || (protocol == PROTO_DHCP && state && state->new) +#ifdef IPV4LL + || (protocol == PROTO_IPV4LL && IPV4LL_STATE_RUNNING(ifp)) +#endif +#endif +#ifdef INET6 + || (protocol == PROTO_STATIC6 && IPV6_STATE_RUNNING(ifp)) +#ifdef DHCP6 + || (protocol == PROTO_DHCP6 && d6_state && d6_state->new) +#endif + || (protocol == PROTO_RA && ipv6nd_hasra(ifp)) +#endif + ) + { + if_up = true_str; + if_down = false_str; + } else { + if_up = false_str; + if_down = true_str; + } + if (efprintf(fp, "if_up=%s", if_up) == -1) + goto eexit; + if (efprintf(fp, "if_down=%s", if_down) == -1) + goto eexit; + + if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { + if (efprintf(fp, "if_afwaiting=%d", af) == -1) + goto eexit; + } + if ((af = dhcpcd_afwaiting(ifp->ctx)) != AF_MAX) { + TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { + if ((af = dhcpcd_ifafwaiting(ifp2)) != AF_MAX) + break; + } + } + if (af != AF_MAX) { + if (efprintf(fp, "af_waiting=%d", af) == -1) + goto eexit; + } + if (ifo->options & DHCPCD_DEBUG) { + if (efprintf(fp, "syslog_debug=true") == -1) + goto eexit; + } +#ifdef INET + if (protocol == PROTO_DHCP && state && state->old) { + if (dhcp_env(fp, "old", ifp, + state->old, state->old_len) == -1) + goto eexit; + if (append_config(fp, "old", + (const char *const *)ifo->config) == -1) + goto eexit; + } +#endif +#ifdef DHCP6 + if (protocol == PROTO_DHCP6 && d6_state && d6_state->old) { + if (dhcp6_env(fp, "old", ifp, + d6_state->old, d6_state->old_len) == -1) + goto eexit; + } +#endif + +dumplease: +#ifdef INET +#ifdef IPV4LL + if (protocol == PROTO_IPV4LL && istate) { + if (ipv4ll_env(fp, istate->down ? "old" : "new", ifp) == -1) + goto eexit; + } +#endif + if (protocol == PROTO_DHCP && state && state->new) { + if (dhcp_env(fp, "new", ifp, + state->new, state->new_len) == -1) + goto eexit; + if (append_config(fp, "new", + (const char *const *)ifo->config) == -1) + goto eexit; + } +#endif +#ifdef INET6 + if (protocol == PROTO_STATIC6) { + if (ipv6_env(fp, "new", ifp) == -1) + goto eexit; + } +#ifdef DHCP6 + if (protocol == PROTO_DHCP6 && D6_STATE_RUNNING(ifp)) { + if (dhcp6_env(fp, "new", ifp, + d6_state->new, d6_state->new_len) == -1) + goto eexit; + } +#endif + if (protocol == PROTO_RA) { + if (ipv6nd_env(fp, ifp) == -1) + goto eexit; + } +#endif + + /* Add our base environment */ + if (ifo->environ) { + for (i = 0; ifo->environ[i] != NULL; i++) + if (efprintf(fp, "%s", ifo->environ[i]) == -1) + goto eexit; + } + + /* Convert buffer to argv */ + fflush(fp); + + buf_pos = ftell(fp); + if (buf_pos == -1) { + logerr(__func__); + goto eexit; + } + +#ifndef HAVE_OPEN_MEMSTREAM + size_t buf_len = (size_t)buf_pos; + if (ctx->script_buflen < buf_len) { + char *buf = realloc(ctx->script_buf, buf_len); + if (buf == NULL) + goto eexit; + ctx->script_buf = buf; + ctx->script_buflen = buf_len; + } + rewind(fp); + if (fread(ctx->script_buf, sizeof(char), buf_len, fp) != buf_len) + goto eexit; + fclose(fp); + fp = NULL; +#endif + + if (is_stdin) + return buf_pos; + + if (script_buftoenv(ctx, ctx->script_buf, (size_t)buf_pos) == NULL) + goto eexit; + + return buf_pos; + +eexit: + logerr(__func__); +#ifndef HAVE_OPEN_MEMSTREAM + if (fp != NULL) + fclose(fp); +#endif + return -1; +} + +static int +send_interface1(struct fd_list *fd, const struct interface *ifp, + const char *reason) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + long len; + + len = make_env(ifp->ctx, ifp, reason); + if (len == -1) + return -1; + return control_queue(fd, ctx->script_buf, (size_t)len); +} + +int +send_interface(struct fd_list *fd, const struct interface *ifp, int af) +{ + int retval = 0; +#ifdef INET + const struct dhcp_state *d; +#endif +#ifdef DHCP6 + const struct dhcp6_state *d6; +#endif + +#ifndef AF_LINK +#define AF_LINK AF_PACKET +#endif + + if (af == AF_UNSPEC || af == AF_LINK) { + const char *reason; + + switch (ifp->carrier) { + case LINK_UP: + reason = "CARRIER"; + break; + case LINK_DOWN: + reason = "NOCARRIER"; + break; + default: + reason = "UNKNOWN"; + break; + } + if (fd != NULL) { + if (send_interface1(fd, ifp, reason) == -1) + retval = -1; + } else + retval++; + } + +#ifdef INET + if (af == AF_UNSPEC || af == AF_INET) { + if (D_STATE_RUNNING(ifp)) { + d = D_CSTATE(ifp); + if (fd != NULL) { + if (send_interface1(fd, ifp, d->reason) == -1) + retval = -1; + } else + retval++; + } +#ifdef IPV4LL + if (IPV4LL_STATE_RUNNING(ifp)) { + if (fd != NULL) { + if (send_interface1(fd, ifp, "IPV4LL") == -1) + retval = -1; + } else + retval++; + } +#endif + } +#endif + +#ifdef INET6 + if (af == AF_UNSPEC || af == AF_INET6) { + if (IPV6_STATE_RUNNING(ifp)) { + if (fd != NULL) { + if (send_interface1(fd, ifp, "STATIC6") == -1) + retval = -1; + } else + retval++; + } + if (RS_STATE_RUNNING(ifp)) { + if (fd != NULL) { + if (send_interface1(fd, ifp, + "ROUTERADVERT") == -1) + retval = -1; + } else + retval++; + } +#ifdef DHCP6 + if (D6_STATE_RUNNING(ifp)) { + d6 = D6_CSTATE(ifp); + if (fd != NULL) { + if (send_interface1(fd, ifp, d6->reason) == -1) + retval = -1; + } else + retval++; + } +#endif + } +#endif + + return retval; +} + +static int +script_run(struct dhcpcd_ctx *ctx, char **argv) +{ + pid_t pid; + int status = 0; + + pid = script_exec(argv, ctx->script_env); + if (pid == -1) + logerr("%s: %s", __func__, argv[0]); + else if (pid != 0) { + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + logerr("%s: waitpid", __func__); + status = 0; + break; + } + } + if (WIFEXITED(status)) { + if (WEXITSTATUS(status)) + logerrx("%s: %s: WEXITSTATUS %d", + __func__, argv[0], WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) + logerrx("%s: %s: %s", + __func__, argv[0], strsignal(WTERMSIG(status))); + } + + return WEXITSTATUS(status); +} + +int +script_dump(const char *env, size_t len) +{ + const char *ep = env + len; + + if (len == 0) + return 0; + + if (*(ep - 1) != '\0') { + errno = EINVAL; + return -1; + } + + for (; env < ep; env += strlen(env) + 1) { + if (strncmp(env, "new_", 4) == 0) + env += 4; + printf("%s\n", env); + } + return 0; +} + +int +script_runreason(const struct interface *ifp, const char *reason) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + char *argv[2]; + int status = 0; + struct fd_list *fd; + long buflen; + + if (ctx->script == NULL && + TAILQ_FIRST(&ifp->ctx->control_fds) == NULL) + return 0; + + /* Make our env */ + if ((buflen = make_env(ifp->ctx, ifp, reason)) == -1) { + logerr(__func__); + return -1; + } + + if (strncmp(reason, "DUMP", 4) == 0) + return script_dump(ctx->script_buf, (size_t)buflen); + + if (ctx->script == NULL) + goto send_listeners; + + argv[0] = ctx->script; + argv[1] = NULL; + logdebugx("%s: executing: %s %s", ifp->name, argv[0], reason); + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) { + if (ps_root_script(ctx, + ctx->script_buf, ctx->script_buflen) == -1) + logerr(__func__); + goto send_listeners; + } +#endif + + script_run(ctx, argv); + +send_listeners: + /* Send to our listeners */ + status = 0; + TAILQ_FOREACH(fd, &ctx->control_fds, next) { + if (!(fd->flags & FD_LISTEN)) + continue; + if (control_queue(fd, ctx->script_buf, ctx->script_buflen)== -1) + logerr("%s: control_queue", __func__); + else + status = 1; + } + + return status; +} Index: tests/Makefile =================================================================== --- /dev/null +++ tests/Makefile @@ -0,0 +1,20 @@ +SUBDIRS= crypt eloop-bench + +all: + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +install: + +proginstall: + +clean: + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +distclean: clean + rm -f *.diff *.patch *.orig *.rej + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +test: + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +tests: test Index: tests/crypt/.gitignore =================================================================== --- /dev/null +++ tests/crypt/.gitignore @@ -0,0 +1 @@ +run-test Index: tests/crypt/GNUmakefile =================================================================== --- /dev/null +++ tests/crypt/GNUmakefile @@ -0,0 +1,7 @@ +# GNU Make does not automagically include .depend +# Luckily it does read GNUmakefile over Makefile so we can work around it + +include Makefile +ifneq ($(wildcard .depend), ) +include .depend +endif Index: tests/crypt/Makefile =================================================================== --- /dev/null +++ tests/crypt/Makefile @@ -0,0 +1,36 @@ +TOP= ../.. +include ${TOP}/iconfig.mk + +PROG= run-test +SRCS= run-test.c +SRCS+= test_hmac_md5.c + +CFLAGS?= -O2 +CSTD?= c99 +CFLAGS+= -std=${CSTD} + +CPPFLAGS+= -I${TOP} -I${TOP}/src + +PCRYPT_SRCS= ${CRYPT_SRCS:compat/%=${TOP}/compat/%} +OBJS+= ${SRCS:.c=.o} ${PCRYPT_SRCS:.c=.o} + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +all: ${PROG} + +clean: + rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} + +distclean: clean + rm -f .depend + rm -f *.diff *.patch *.orig *.rej + +.depend: ${SRCS} ${PCRYPT_SRCS} + ${CC} ${CPPFLAGS} -MM ${SRCS} ${PCRYPT_SRCS} + +${PROG}: ${DEPEND} ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} + +test: ${PROG} + ./${PROG} Index: tests/crypt/README.md =================================================================== --- /dev/null +++ tests/crypt/README.md @@ -0,0 +1,8 @@ +# dhcpcd Test Suite + +Currently this just tests the RFC2202 MD5 implementation in dhcpcd. +This is important, because dhcpcd will either use the system MD5 +implementation if found, otherwise some compat code. + +This test suit ensures that it works in accordance with known standards +on your platform. Index: tests/crypt/run-test.c =================================================================== --- /dev/null +++ tests/crypt/run-test.c @@ -0,0 +1,38 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2018 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. + */ + +#include "test.h" + +int main(void) +{ + int r = 0; + + if (test_hmac_md5()) + r = -1; + + return r; +} Index: tests/crypt/test.h =================================================================== --- /dev/null +++ tests/crypt/test.h @@ -0,0 +1,32 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2018 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. + */ + +#ifndef TEST_H + +int test_hmac_md5(void); + +#endif Index: tests/crypt/test_hmac_md5.c =================================================================== --- /dev/null +++ tests/crypt/test_hmac_md5.c @@ -0,0 +1,209 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2018 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. + */ + +#include +#include +#include +#include + +#include "config.h" +#include "test.h" + +#ifdef HAVE_HMAC_H +#include +#endif + +static void +print_hmac(FILE *stream, const uint8_t *hmac) +{ + int i; + + fprintf(stream, "digest = 0x"); + for (i = 0; i < 16; i++) + fprintf(stream, "%02x", *hmac++); + fprintf(stream, "\n"); +} + +static void +test_hmac(const uint8_t *hmac, const uint8_t *tst) +{ + print_hmac(stdout, hmac); + if (memcmp(hmac, tst, 16) == 0) + return; + fprintf(stderr, "FAILED!\nExpected\t\t\t"); + print_hmac(stderr, tst); + exit(EXIT_FAILURE); +} + +static void +hmac_md5_test1(void) +{ + const uint8_t text[] = "Hi There"; + uint8_t key[16]; + const uint8_t expect[16] = { + 0x92, 0x94, 0x72, 0x7a, 0x36, 0x38, 0xbb, 0x1c, + 0x13, 0xf4, 0x8e, 0xf8, 0x15, 0x8b, 0xfc, 0x9d, + }; + uint8_t digest[16]; + int i; + + printf ("HMAC MD5 Test 1:\t\t"); + for (i = 0; i < 16; i++) + key[i] = 0x0b; + hmac("md5", key, 16, text, 8, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +static void +hmac_md5_test2(void) +{ + const uint8_t text[] = "what do ya want for nothing?"; + const uint8_t key[] = "Jefe"; + const uint8_t expect[16] = { + 0x75, 0x0c, 0x78, 0x3e, 0x6a, 0xb0, 0xb5, 0x03, + 0xea, 0xa8, 0x6e, 0x31, 0x0a, 0x5d, 0xb7, 0x38, + }; + uint8_t digest[16]; + + printf("HMAC MD5 Test 2:\t\t"); + hmac("md5", key, 4, text, 28, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +static void +hmac_md5_test3(void) +{ + const uint8_t expect[16] = { + 0x56, 0xbe, 0x34, 0x52, 0x1d, 0x14, 0x4c, 0x88, + 0xdb, 0xb8, 0xc7, 0x33, 0xf0, 0xe8, 0xb3, 0xf6, + }; + uint8_t digest[16]; + uint8_t text[50]; + uint8_t key[16]; + int i; + + printf ("HMAC MD5 Test 3:\t\t"); + for (i = 0; i < 50; i++) + text[i] = 0xdd; + for (i = 0; i < 16; i++) + key[i] = 0xaa; + hmac("md5", key, 16, text, 50, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +static void +hmac_md5_test4(void) +{ + const uint8_t expect[16] = { + 0x69, 0x7e, 0xaf, 0x0a, 0xca, 0x3a, 0x3a, 0xea, + 0x3a, 0x75, 0x16, 0x47, 0x46, 0xff, 0xaa, 0x79, + }; + uint8_t digest[16]; + uint8_t text[50]; + uint8_t key[25]; + uint8_t i; + + printf ("HMAC MD5 Test 4:\t\t"); + for (i = 0; i < 50; i++) + text[i] = 0xcd; + for (i = 0; i < 25; i++) + key[i] = (uint8_t)(i + 1); + hmac("md5", key, 25, text, 50, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +static void +hmac_md5_test5(void) +{ + const uint8_t text[] = "Test With Truncation"; + const uint8_t expect[] = { + 0x56, 0x46, 0x1e, 0xf2, 0x34, 0x2e, 0xdc, 0x00, + 0xf9, 0xba, 0xb9, 0x95, 0x69, 0x0e, 0xfd, 0x4c, + }; + uint8_t digest[16]; + uint8_t key[16]; + int i; + + printf ("HMAC MD5 Test 5:\t\t"); + for (i = 0; i < 16; i++) + key[i] = 0x0c; + hmac("md5", key, 16, text, 20, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +static void +hmac_md5_test6(void) +{ + const uint8_t text[] = "Test Using Larger Than Block-Size Key - Hash Key First"; + const uint8_t expect[] = { + 0x6b, 0x1a, 0xb7, 0xfe, 0x4b, 0xd7, 0xbf, 0x8f, + 0x0b, 0x62, 0xe6, 0xce, 0x61, 0xb9, 0xd0, 0xcd, + }; + uint8_t digest[16]; + uint8_t key[80]; + int i; + + printf ("HMAC MD5 Test 6:\t\t"); + for (i = 0; i < 80; i++) + key[i] = 0xaa; + hmac("md5", key, 80, text, 54, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +static void +hmac_md5_test7(void) +{ + const uint8_t text[] = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"; + const uint8_t expect[] = { + 0x6f, 0x63, 0x0f, 0xad, 0x67, 0xcd, 0xa0, 0xee, + 0x1f, 0xb1, 0xf5, 0x62, 0xdb, 0x3a, 0xa5, 0x3e, + }; + uint8_t digest[16]; + uint8_t key[80]; + int i; + + printf ("HMAC MD5 Test 7:\t\t"); + for (i = 0; i < 80; i++) + key[i] = 0xaa; + hmac("md5", key, 80, text, 73, digest, sizeof(digest)); + test_hmac(digest, expect); +} + +int test_hmac_md5(void) +{ + + printf ("Starting RFC2202 HMAC MD5 tests...\n\n"); + hmac_md5_test1(); + hmac_md5_test2(); + hmac_md5_test3(); + hmac_md5_test4(); + hmac_md5_test5(); + hmac_md5_test6(); + hmac_md5_test7(); + printf("\nAll tests pass.\n"); + return 0; +} Index: tests/eloop-bench/.gitignore =================================================================== --- /dev/null +++ tests/eloop-bench/.gitignore @@ -0,0 +1 @@ +eloop-bench Index: tests/eloop-bench/Makefile =================================================================== --- /dev/null +++ tests/eloop-bench/Makefile @@ -0,0 +1,45 @@ +TOP= ../.. +include ${TOP}/iconfig.mk + +PROG= eloop-bench +SRCS= eloop-bench.c +SRCS+= ${TOP}/src/eloop.c + +CFLAGS?= -O2 +CSTD?= c99 +CFLAGS+= -std=${CSTD} + +#CPPFLAGS+= -DNO_CONFIG_H +#CPPFLAGS+= -DQUEUE_H=../compat/queue.h +CPPFLAGS+= -I${TOP} -I${TOP}/src + +# Default is to let eloop decide +#CPPFLAGS+= -DHAVE_KQUEUE +#CPPFLAGS+= -DHAVE_POLLTS +#CPPFLAGS+= -DHAVE_PSELECT +#CPPFLAGS+= -DHAVE_EPOLL +#CPPFLAGS+= -DHAVE_PPOLL +CPPFLAGS+= -DWARN_SELECT + +PCOMPAT_SRCS= ${COMPAT_SRCS:compat/%=${TOP}/compat/%} +OBJS+= ${SRCS:.c=.o} ${PCOMPAT_SRCS:.c=.o} + +.c.o: Makefile + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +all: ${PROG} + +clean: + rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} + +distclean: clean + rm -f .depend + rm -f *.diff *.patch *.orig *.rej + +depend: + +${PROG}: ${DEPEND} ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} + +test: ${PROG} + ./${PROG} Index: tests/eloop-bench/README.md =================================================================== --- /dev/null +++ tests/eloop-bench/README.md @@ -0,0 +1,53 @@ +# eloop-bench + +eloop is a portable event loop designed to be dropped into the code of a +program. It is not in any library to date. +The basic requirement of eloop is a descriptor polling mechanism which +allows the safe delivery of signals. +As such, select(2) and poll(2) are not suitable. + +This is an eloop benchmark to test the performance of the various +polling mechanisms. It's inspired by libevent/bench. + +eloop needs to be compiled for a specific polling mechanism. +eloop will try and work out which one to use, but you can influence which one +by giving one of these CPPFLAGS to the Makefile: + * `HAVE_KQUEUE` + * `HAVE_EPOLL` + * `HAVE_PSELECT` + * `HAVE_POLLTS` + * `HAVE_PPOLL` + +kqueue(2) is found on modern BSD kernels. +epoll(7) is found on modern Linux and Solaris kernels. +These two *should* be the best performers. + +pselect(2) *should* be found on any POSIX libc. +This *should* be the worst performer. + +pollts(2) and ppoll(2) are NetBSD and Linux specific variants on poll(2), +but allow safe signal delivery like pselect(2). +Aside from the function name, the arguments and functionality are identical. +They are of little use as both platforms have kqueue(2) and epoll(2), +but there is an edge case where system doesn't have epoll(2) compiled hence +it's inclusion here. + +## using eloop-bench + +The benchmark runs by setting up npipes to read/write to and attaching +an eloop callback for each pipe reader. +Once setup, it will perform a run by writing to nactive pipes. +For each successful pipe read, if nwrites >0 then the reader will reduce +nwrites by one on successful write back to itself. +Once nwrites is 0, the timed run will end once the last write has been read. +At the end of run, the time taken in seconds and nanoseconds is printed. + +The following arguments can influence the benchmark: + * `-a active` + The number of active pipes, default 1. + * `-n pipes` + The number of pipes to create and attach an eloop callback to, defalt 100. + * `-r runs` + The number of timed runs to make, default 25. + * `-w writes` + The number of writes to make by the read callback, default 100. Index: tests/eloop-bench/eloop-bench.c =================================================================== --- /dev/null +++ tests/eloop-bench/eloop-bench.c @@ -0,0 +1,184 @@ +/* + * eloop benchmark + * Copyright (c) 2006-2018 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. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "eloop.h" + +#ifndef timespecsub +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (/* CONSTCOND */ 0) +#endif + +struct pipe { + int fd[2]; +}; + +static size_t good, bad, writes, fired; +static size_t npipes = 100, nwrites = 100, nactive = 1; +static struct pipe *pipes; +static struct eloop *e; + +static void +read_cb(void *arg) +{ + struct pipe *p = arg; + unsigned char buf[1]; + + if (read(p->fd[0], buf, 1) != 1) { + warn("%s: read", __func__); + bad++; + } else + good++; + + if (writes != 0) { + writes--; + if (write(p->fd[1], "e", 1) != 1) { + warn("%s: write", __func__); + bad++; + } else + fired++; + } + + if (writes == 0 && fired == good) { + //printf("fired %zu, good %zu, bad %zu\n", fired, good, bad); + eloop_exit(e, good == fired && bad == 0 ? + EXIT_SUCCESS : EXIT_FAILURE); + } +} + +static int +runone(struct timespec *t) +{ + size_t i; + struct pipe *p; + struct timespec ts, te; + int result; + + writes = nwrites; + fired = good = 0; + + for (i = 0, p = pipes; i < nactive; i++, p++) { + if (write(p->fd[1], "e", 1) != 1) + err(EXIT_FAILURE, "send"); + writes--; + fired++; + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + err(EXIT_FAILURE, "clock_gettime"); + result = eloop_start(e, NULL); + if (clock_gettime(CLOCK_MONOTONIC, &te) == -1) + err(EXIT_FAILURE, "clock_gettime"); + + timespecsub(&te, &ts, t); + return result; +} + +int +main(int argc, char **argv) +{ + int c, result, exit_code; + size_t i, nruns = 25; + struct pipe *p; + struct timespec ts, te, t; + + while ((c = getopt(argc, argv, "a:n:r:w:")) != -1) { + switch (c) { + case 'a': + nactive = (size_t)atoi(optarg); + break; + case 'n': + npipes = (size_t)atoi(optarg); + break; + case 'r': + nruns = (size_t)atoi(optarg); + break; + case 'w': + nwrites = (size_t)atoi(optarg); + break; + default: + errx(EXIT_FAILURE, "illegal argument `%c'", c); + } + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + err(EXIT_FAILURE, "clock_gettime"); + + if ((e = eloop_new()) == NULL) + err(EXIT_FAILURE, "eloop_init"); + + if (nactive > npipes) + nactive = npipes; + + pipes = calloc(npipes, sizeof(*p)); + if (pipes == NULL) + err(EXIT_FAILURE, "malloc"); + + for (i = 0, p = pipes; i < npipes; i++, p++) { + if (pipe2(p->fd, O_CLOEXEC | O_NONBLOCK) == -1) + err(EXIT_FAILURE, "pipe"); + if (eloop_event_add(e, p->fd[0], read_cb, p) == -1) + err(EXIT_FAILURE, "eloop_event_add"); + } + + printf("active = %zu, pipes = %zu, runs = %zu, writes = %zu\n", + nactive, npipes, nruns, nwrites); + + exit_code = EXIT_SUCCESS; + for (i = 0; i < nruns; i++) { + result = runone(&t); + if (result != EXIT_SUCCESS) + exit_code = result; + printf("run %zu took %lld.%.9ld seconds, result %d\n", + i + 1, (long long)t.tv_sec, t.tv_nsec, result); + } + + eloop_free(e); + free(pipes); + + if (clock_gettime(CLOCK_MONOTONIC, &te) == -1) + err(EXIT_FAILURE, "clock_gettime"); + timespecsub(&te, &ts, &t); + printf("total %lld.%.9ld seconds, result %d\n", + (long long)t.tv_sec, t.tv_nsec, exit_code); + exit(exit_code); +}