pax_global_header00006660000000000000000000000064147001464350014516gustar00rootroot0000000000000052 comment=159ef73463b05da2f8a8e3ad852026d5a344587a dhcpcd-10.1.0/000077500000000000000000000000001470014643500130225ustar00rootroot00000000000000dhcpcd-10.1.0/.github/000077500000000000000000000000001470014643500143625ustar00rootroot00000000000000dhcpcd-10.1.0/.github/workflows/000077500000000000000000000000001470014643500164175ustar00rootroot00000000000000dhcpcd-10.1.0/.github/workflows/build.yml000066400000000000000000000054421470014643500202460ustar00rootroot00000000000000name: Build on: push: branches: [ master ] pull_request: branches: [ master ] env: CC: clang jobs: ubuntu: strategy: matrix: os: [ ubuntu-latest, ubuntu-22.04 ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Configure run: ./configure --prefix=/usr - name: Build run: make - name: Tests run: make tests openbsd: runs-on: macos-12 steps: - name: Bootstrap OpenBSD-latest uses: mario-campos/emulate@v1 with: operating-system: openbsd-latest - name: Install Dependencies run: pkg_add git - name: Build run: | git clone --depth=1 "${{ github.server_url }}/${{ github.repository }}" build cd build [ "${{ github.event.pull_request.number }}" = "" ] || (echo "fetching PR ${{ github.event.pull_request.number }}"; git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-${{ github.event.pull_request.number }} && git checkout "pr-${{ github.event.pull_request.number }}") echo "configure" ./configure --prefix=/usr echo "building" make - name: Tests run: | ulimit -n 1024 cd build make tests freebsd: runs-on: macos-12 steps: - name: Bootstrap FreeBSD-latest uses: mario-campos/emulate@v1 with: operating-system: freebsd-latest - name: Install Dependencies run: pkg install -y git - name: Build run: | git clone --depth=1 "${{ github.server_url }}/${{ github.repository }}" build cd build [ "${{ github.event.pull_request.number }}" = "" ] || (echo "fetching PR ${{ github.event.pull_request.number }}"; git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-${{ github.event.pull_request.number }} && git checkout "pr-${{ github.event.pull_request.number }}") echo "configure" ./configure --prefix=/usr echo "building" make - name: Tests run: | cd build make tests netbsd: runs-on: macos-12 steps: - name: Bootstrap NetBSD-latest uses: mario-campos/emulate@v1 with: operating-system: netbsd-latest - name: Build run: | git clone --depth=1 "${{ github.server_url }}/${{ github.repository }}" build cd build [ "${{ github.event.pull_request.number }}" = "" ] || (echo "fetching PR ${{ github.event.pull_request.number }}"; git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-${{ github.event.pull_request.number }} && git checkout "pr-${{ github.event.pull_request.number }}") echo "configure" ./configure --prefix=/usr echo "building" make - name: Tests run: | cd build make tests dhcpcd-10.1.0/.gitignore000066400000000000000000000010161470014643500150100ustar00rootroot00000000000000# 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 # Ignore VSCode .vscode # Ignore macOS hidden files .DS_Store dhcpcd-10.1.0/BUILDING.md000066400000000000000000000205671470014643500145530ustar00rootroot00000000000000# Building dhcpcd This attempts to document various ways of building dhcpcd for your platform. `./configure` is a POSIX shell script that works in a similar way to GNU configure. This works fine provided you don't force any exotic options down which may or may not be silently discarded. Some build time warnings are expected - the only platforms with zero warnings are DragonFlyBSD and NetBSD. It is expected that the platforms be improvded to support dhcpcd better. There maybe some loss of functionality, but for the most part, dhcpcd can work around these deficiencies. ## 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. Linux SECCOMP is very dependant on libc vs kernel. When libc is changed and uses a syscall that dhcpcd is unaware of, SECCOMP may break dhcpcd. When this happens you can configure dhcpcd with --disable-seccomp so dhcpcd can use a POSIX resource limited sandbox with privilege separation still. If you do this, please report the issue so that we can adjust the SECCOMP filter so that dhcpcd can use SECCOMP once more. Or convince the libc/kernel people to adpot something more maintainable like FreeBSD's capsicum or OpenBSD's pledge. ## 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. ## crypto dhcpcd ships with some cryptographic routines taken from various upstreams. These are routinely monitored and try to be as up to date as possible. You can optionally configure dhcpcd with `--with-openssl` to use libcrypto to use these instead. This is not enabled by default, even if libcrypto is found because libcrypto generally lives in /usr and dhcpcd in /sbin which could be a separate filesystem. ## 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. dhcpcd-10.1.0/LICENSE000066400000000000000000000024261470014643500140330ustar00rootroot00000000000000Copyright (c) 2006-2024 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. dhcpcd-10.1.0/Makefile000066400000000000000000000061651470014643500144720ustar00rootroot00000000000000SUBDIRS= src hooks PACKAGE= dhcpcd 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?= ${PACKAGE}-${VERSION}${DISTSUFFIX} DISTFILE?= ${DISTPREFIX}.tar.xz DISTINFO= ${DISTFILE}.distinfo DISTINFOMD= ${DISTINFO}.md DISTSIGN= ${DISTFILE}.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} ${DISTINFO} ${DISTINFOMD} ${DISTSIGN} rm -f *.diff *.patch *.orig *.rej for x in ${SUBDIRS} tests; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done dist-git: git archive --prefix=${DISTPREFIX}/ v${VERSION} | 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} ${DISTSIGN} ${SHA256} ${DISTFILE} >${DISTINFO} wc -c <${DISTFILE} \ | xargs printf 'Size (${DISTFILE}) = %s\n' >>${DISTINFO} ${PGP} --armour --detach-sign ${DISTFILE} chmod 644 ${DISTSIGN} ls -l ${DISTFILE} ${DISTINFO} ${DISTSIGN} ${DISTINFOMD}: ${DISTINFO} echo '```' >${DISTINFOMD} cat ${DISTINFO} >>${DISTINFOMD} echo '```' >>${DISTINFOMD} release: distinfo ${DISTINFOMD} gh release create v${VERSION} \ --title "${PACKAGE} ${VERSION}" --draft --generate-notes \ --notes-file ${DISTINFOMD} \ ${DISTFILE} ${DISTSIGN} 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} "${PACKAGE}-${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} "${PACKAGE}-${VERSION} imported to ${DESTDIR}" import-src: ${MAKE} _import-src DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi` include Makefile.inc dhcpcd-10.1.0/Makefile.inc000066400000000000000000000020201470014643500152240ustar00rootroot00000000000000# 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 DragonFlyBSD SHA256?= sha256 PGP?= gpg # old NetBSD defs #SHA256?= 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' dhcpcd-10.1.0/README.md000066400000000000000000000063131470014643500143040ustar00rootroot00000000000000# dhcpcd dhcpcd is a [DHCP](https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) and a [DHCPv6](https://en.wikipedia.org/wiki/DHCPv6) client. It's also an IPv4LL (aka [ZeroConf](https://en.wikipedia.org/wiki/Zeroconf)) client. In layperson'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 therein. ## ChangeLog We no longer supply a ChangeLog. However, you're more than welcome to read the [commit log](https://github.com/NetworkConfiguration/dhcpcd/commits) and [release announcements](https://github.com/NetworkConfiguration/dhcpcd/releases). dhcpcd-10.1.0/compat/000077500000000000000000000000001470014643500143055ustar00rootroot00000000000000dhcpcd-10.1.0/compat/_strtoi.h000066400000000000000000000055511470014643500161470ustar00rootroot00000000000000/* $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 dhcpcd-10.1.0/compat/arc4random.c000066400000000000000000000163531470014643500165130ustar00rootroot00000000000000/* $OpenBSD: arc4random.c,v 1.58 2022/07/31 13:41:45 tb Exp $ */ /* * Copyright (c) 1996, David Mazieres * Copyright (c) 2008, Damien Miller * Copyright (c) 2013, Markus Friedl * Copyright (c) 2014, Theo de Raadt * * 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. */ /* * ChaCha based random number generator for OpenBSD. */ /* * OPENBSD ORIGINAL: lib/libc/crypt/arc4random.c * lib/libc/crypt/arc4random.h */ #include "config.h" #include #include #include #include #include #include #include #include #include #if defined(HAVE_OPENSSL) #include #endif #define KEYSTREAM_ONLY #include "chacha_private.h" #define minimum(a, b) ((a) < (b) ? (a) : (b)) #if defined(__GNUC__) || defined(_MSC_VER) #define inline __inline #else /* __GNUC__ || _MSC_VER */ #define inline #endif /* !__GNUC__ && !_MSC_VER */ #define KEYSZ 32 #define IVSZ 8 #define BLOCKSZ 64 #define RSBUFSZ (16*BLOCKSZ) #define REKEY_BASE (1024*1024) /* NB. should be a power of 2 */ /* Marked MAP_INHERIT_ZERO, so zero'd out in fork children. */ static struct _rs { size_t rs_have; /* valid bytes at end of rs_buf */ size_t rs_count; /* bytes till reseed */ } *rs; /* Maybe be preserved in fork children, if _rs_allocate() decides. */ static struct _rsx { chacha_ctx rs_chacha; /* chacha context for random keystream */ u_char rs_buf[RSBUFSZ]; /* keystream blocks */ } *rsx; static int _dhcpcd_rand_fd = -1; /* /dev/urandom fd */ static int _dhcpcd_getentropy(void *, size_t); static inline int _rs_allocate(struct _rs **, struct _rsx **); /* dhcpcd needs to hold onto the fd at fork due to privsep */ #if 0 static inline void _rs_forkdetect(void); #else #define _rs_forkdetect() #define _rs_forkhandler() #endif /* Inline "arc4random.h" */ #include #include static inline void _rs_rekey(u_char *dat, size_t datlen); /* dhcpcd isn't multithreaded */ #define _ARC4_LOCK() #define _ARC4_UNLOCK() static int _dhcpcd_getentropy(void *buf, size_t length) { struct timeval tv; uint8_t *rand = (uint8_t *)buf; #if defined (HAVE_OPENSSL) if (RAND_priv_bytes(buf, (int)length) == 1) return (0); #endif if (length < sizeof(tv)) { gettimeofday(&tv, NULL); memcpy(buf, &tv, sizeof(tv)); length -= sizeof(tv); rand += sizeof(tv); } if (_dhcpcd_rand_fd == -1) _dhcpcd_rand_fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK); if (_dhcpcd_rand_fd != -1) { /* coverity[check_return] */ (void)read(_dhcpcd_rand_fd, rand, length); } /* Never fail. If there is an error reading from /dev/urandom, * just use what is on the stack. */ return (0); } static inline void _getentropy_fail(void) { raise(SIGKILL); } #if 0 static volatile sig_atomic_t _rs_forked; static inline void _rs_forkhandler(void) { _rs_forked = 1; } static inline void _rs_forkdetect(void) { static pid_t _rs_pid = 0; pid_t pid = getpid(); /* XXX unusual calls to clone() can bypass checks */ if (_rs_pid == 0 || _rs_pid == 1 || _rs_pid != pid || _rs_forked) { _rs_pid = pid; _rs_forked = 0; if (rs) memset(rs, 0, sizeof(*rs)); } } #endif static inline int _rs_allocate(struct _rs **rsp, struct _rsx **rsxp) { if ((*rsp = mmap(NULL, sizeof(**rsp), PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) return (-1); if ((*rsxp = mmap(NULL, sizeof(**rsxp), PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { munmap(*rsp, sizeof(**rsp)); *rsp = NULL; return (-1); } _rs_forkhandler(); return (0); } static inline void _rs_init(u_char *buf, size_t n) { if (n < KEYSZ + IVSZ) return; if (rs == NULL) { if (_rs_allocate(&rs, &rsx) == -1) _exit(1); } chacha_keysetup(&rsx->rs_chacha, buf, KEYSZ * 8); chacha_ivsetup(&rsx->rs_chacha, buf + KEYSZ); } static void _rs_stir(void) { u_char rnd[KEYSZ + IVSZ]; uint32_t rekey_fuzz = 0; if (_dhcpcd_getentropy(rnd, sizeof rnd) == -1) _getentropy_fail(); if (!rs) _rs_init(rnd, sizeof(rnd)); else _rs_rekey(rnd, sizeof(rnd)); #if defined(HAVE_EXPLICIT_BZERO) explicit_bzero(rnd, sizeof(rnd)); /* discard source seed */ #elif defined(HAVE_MEMSET_EXPLICIT) (void)memset_explicit(rnd, 0, sizeof(rnd)); #elif defined(HAVE_MEMSET_S) (void)memset_s(rnd, sizeof(rnd), 0, sizeof(rnd)); #else #warning potentially insecure use of memset discarding the source seed (void)memset(rnd, 0, sizeof(rnd)); /* discard source seed */ #endif /* invalidate rs_buf */ rs->rs_have = 0; memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf)); /* rekey interval should not be predictable */ chacha_encrypt_bytes(&rsx->rs_chacha, (uint8_t *)&rekey_fuzz, (uint8_t *)&rekey_fuzz, sizeof(rekey_fuzz)); rs->rs_count = REKEY_BASE + (rekey_fuzz % REKEY_BASE); } static inline void _rs_stir_if_needed(size_t len) { _rs_forkdetect(); if (!rs || rs->rs_count <= len) _rs_stir(); if (rs->rs_count <= len) rs->rs_count = 0; else rs->rs_count -= len; } static inline void _rs_rekey(u_char *dat, size_t datlen) { #ifndef KEYSTREAM_ONLY memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf)); #endif /* fill rs_buf with the keystream */ chacha_encrypt_bytes(&rsx->rs_chacha, rsx->rs_buf, rsx->rs_buf, sizeof(rsx->rs_buf)); /* mix in optional user provided data */ if (dat) { size_t i, m; m = minimum(datlen, KEYSZ + IVSZ); for (i = 0; i < m; i++) rsx->rs_buf[i] ^= dat[i]; } /* immediately reinit for backtracking resistance */ _rs_init(rsx->rs_buf, KEYSZ + IVSZ); memset(rsx->rs_buf, 0, KEYSZ + IVSZ); rs->rs_have = sizeof(rsx->rs_buf) - KEYSZ - IVSZ; } static inline void _rs_random_buf(void *_buf, size_t n) { u_char *buf = (u_char *)_buf; u_char *keystream; size_t m; _rs_stir_if_needed(n); while (n > 0) { if (rs->rs_have > 0) { m = minimum(n, rs->rs_have); keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have; memcpy(buf, keystream, m); memset(keystream, 0, m); buf += m; n -= m; rs->rs_have -= m; } if (rs->rs_have == 0) _rs_rekey(NULL, 0); } } static inline void _rs_random_u32(uint32_t *val) { u_char *keystream; _rs_stir_if_needed(sizeof(*val)); if (rs->rs_have < sizeof(*val)) _rs_rekey(NULL, 0); keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have; memcpy(val, keystream, sizeof(*val)); memset(keystream, 0, sizeof(*val)); rs->rs_have -= sizeof(*val); } uint32_t arc4random(void) { uint32_t val; _ARC4_LOCK(); _rs_random_u32(&val); _ARC4_UNLOCK(); return val; } void arc4random_buf(void *buf, size_t n) { _ARC4_LOCK(); _rs_random_buf(buf, n); _ARC4_UNLOCK(); } dhcpcd-10.1.0/compat/arc4random.h000066400000000000000000000006651470014643500165170ustar00rootroot00000000000000/* * 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); void arc4random_buf(void *, size_t); #endif dhcpcd-10.1.0/compat/arc4random_uniform.c000066400000000000000000000035721470014643500202510ustar00rootroot00000000000000/* $OpenBSD: arc4random_uniform.c,v 1.3 2019/01/20 02:59:07 bcook Exp $ */ /* * 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. */ for (;;) { r = arc4random(); if (r >= min) break; } return r % upper_bound; } dhcpcd-10.1.0/compat/arc4random_uniform.h000066400000000000000000000016261470014643500202540ustar00rootroot00000000000000/* * 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 dhcpcd-10.1.0/compat/bitops.h000066400000000000000000000071561470014643500157670ustar00rootroot00000000000000/* $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_ */ dhcpcd-10.1.0/compat/chacha_private.h000066400000000000000000000124061470014643500174220ustar00rootroot00000000000000/* chacha-merged.c version 20080118 D. J. Bernstein Public domain. */ /* $OpenBSD: chacha_private.h,v 1.3 2022/02/28 21:56:29 dtucker Exp $ */ typedef unsigned char u8; typedef unsigned int u32; typedef struct { u32 input[16]; /* could be compressed */ } chacha_ctx; #define U8C(v) (v##U) #define U32C(v) (v##U) #define U8V(v) ((u8)(v) & U8C(0xFF)) #define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF)) #define ROTL32(v, n) \ (U32V((v) << (n)) | ((v) >> (32 - (n)))) #define U8TO32_LITTLE(p) \ (((u32)((p)[0]) ) | \ ((u32)((p)[1]) << 8) | \ ((u32)((p)[2]) << 16) | \ ((u32)((p)[3]) << 24)) #define U32TO8_LITTLE(p, v) \ do { \ (p)[0] = U8V((v) ); \ (p)[1] = U8V((v) >> 8); \ (p)[2] = U8V((v) >> 16); \ (p)[3] = U8V((v) >> 24); \ } while (0) #define ROTATE(v,c) (ROTL32(v,c)) #define XOR(v,w) ((v) ^ (w)) #define PLUS(v,w) (U32V((v) + (w))) #define PLUSONE(v) (PLUS((v),1)) #define QUARTERROUND(a,b,c,d) \ a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); static const char sigma[16] = "expand 32-byte k"; static const char tau[16] = "expand 16-byte k"; static void chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits) { const char *constants; x->input[4] = U8TO32_LITTLE(k + 0); x->input[5] = U8TO32_LITTLE(k + 4); x->input[6] = U8TO32_LITTLE(k + 8); x->input[7] = U8TO32_LITTLE(k + 12); if (kbits == 256) { /* recommended */ k += 16; constants = sigma; } else { /* kbits == 128 */ constants = tau; } x->input[8] = U8TO32_LITTLE(k + 0); x->input[9] = U8TO32_LITTLE(k + 4); x->input[10] = U8TO32_LITTLE(k + 8); x->input[11] = U8TO32_LITTLE(k + 12); x->input[0] = U8TO32_LITTLE(constants + 0); x->input[1] = U8TO32_LITTLE(constants + 4); x->input[2] = U8TO32_LITTLE(constants + 8); x->input[3] = U8TO32_LITTLE(constants + 12); } static void chacha_ivsetup(chacha_ctx *x,const u8 *iv) { x->input[12] = 0; x->input[13] = 0; x->input[14] = U8TO32_LITTLE(iv + 0); x->input[15] = U8TO32_LITTLE(iv + 4); } static void chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes) { u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; u8 *ctarget = NULL; u8 tmp[64]; u_int i; if (!bytes) return; j0 = x->input[0]; j1 = x->input[1]; j2 = x->input[2]; j3 = x->input[3]; j4 = x->input[4]; j5 = x->input[5]; j6 = x->input[6]; j7 = x->input[7]; j8 = x->input[8]; j9 = x->input[9]; j10 = x->input[10]; j11 = x->input[11]; j12 = x->input[12]; j13 = x->input[13]; j14 = x->input[14]; j15 = x->input[15]; for (;;) { if (bytes < 64) { for (i = 0;i < bytes;++i) tmp[i] = m[i]; m = tmp; ctarget = c; c = tmp; } x0 = j0; x1 = j1; x2 = j2; x3 = j3; x4 = j4; x5 = j5; x6 = j6; x7 = j7; x8 = j8; x9 = j9; x10 = j10; x11 = j11; x12 = j12; x13 = j13; x14 = j14; x15 = j15; for (i = 20;i > 0;i -= 2) { QUARTERROUND( x0, x4, x8,x12) QUARTERROUND( x1, x5, x9,x13) QUARTERROUND( x2, x6,x10,x14) QUARTERROUND( x3, x7,x11,x15) QUARTERROUND( x0, x5,x10,x15) QUARTERROUND( x1, x6,x11,x12) QUARTERROUND( x2, x7, x8,x13) QUARTERROUND( x3, x4, x9,x14) } x0 = PLUS(x0,j0); x1 = PLUS(x1,j1); x2 = PLUS(x2,j2); x3 = PLUS(x3,j3); x4 = PLUS(x4,j4); x5 = PLUS(x5,j5); x6 = PLUS(x6,j6); x7 = PLUS(x7,j7); x8 = PLUS(x8,j8); x9 = PLUS(x9,j9); x10 = PLUS(x10,j10); x11 = PLUS(x11,j11); x12 = PLUS(x12,j12); x13 = PLUS(x13,j13); x14 = PLUS(x14,j14); x15 = PLUS(x15,j15); #ifndef KEYSTREAM_ONLY x0 = XOR(x0,U8TO32_LITTLE(m + 0)); x1 = XOR(x1,U8TO32_LITTLE(m + 4)); x2 = XOR(x2,U8TO32_LITTLE(m + 8)); x3 = XOR(x3,U8TO32_LITTLE(m + 12)); x4 = XOR(x4,U8TO32_LITTLE(m + 16)); x5 = XOR(x5,U8TO32_LITTLE(m + 20)); x6 = XOR(x6,U8TO32_LITTLE(m + 24)); x7 = XOR(x7,U8TO32_LITTLE(m + 28)); x8 = XOR(x8,U8TO32_LITTLE(m + 32)); x9 = XOR(x9,U8TO32_LITTLE(m + 36)); x10 = XOR(x10,U8TO32_LITTLE(m + 40)); x11 = XOR(x11,U8TO32_LITTLE(m + 44)); x12 = XOR(x12,U8TO32_LITTLE(m + 48)); x13 = XOR(x13,U8TO32_LITTLE(m + 52)); x14 = XOR(x14,U8TO32_LITTLE(m + 56)); x15 = XOR(x15,U8TO32_LITTLE(m + 60)); #endif j12 = PLUSONE(j12); if (!j12) { j13 = PLUSONE(j13); /* stopping at 2^70 bytes per nonce is user's responsibility */ } U32TO8_LITTLE(c + 0,x0); U32TO8_LITTLE(c + 4,x1); U32TO8_LITTLE(c + 8,x2); U32TO8_LITTLE(c + 12,x3); U32TO8_LITTLE(c + 16,x4); U32TO8_LITTLE(c + 20,x5); U32TO8_LITTLE(c + 24,x6); U32TO8_LITTLE(c + 28,x7); U32TO8_LITTLE(c + 32,x8); U32TO8_LITTLE(c + 36,x9); U32TO8_LITTLE(c + 40,x10); U32TO8_LITTLE(c + 44,x11); U32TO8_LITTLE(c + 48,x12); U32TO8_LITTLE(c + 52,x13); U32TO8_LITTLE(c + 56,x14); U32TO8_LITTLE(c + 60,x15); if (bytes <= 64) { if (bytes < 64) { for (i = 0;i < bytes;++i) ctarget[i] = c[i]; } x->input[12] = j12; x->input[13] = j13; return; } bytes -= 64; c += 64; #ifndef KEYSTREAM_ONLY m += 64; #endif } } dhcpcd-10.1.0/compat/closefrom.c000066400000000000000000000037351470014643500164520ustar00rootroot00000000000000/* * SPDX-License-Identifier: ISC * * Copyright (c) 2004-2005, 2007, 2010, 2012-2015, 2017-2018 * 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 "config.h" #ifdef __linux__ # include # if defined(__NR_close_range) && !defined(SYS_close_range) # define SYS_close_range __NR_close_range # endif #endif #include #include #if defined(__linux__) && defined(SYS_close_range) static inline int sys_close_range(unsigned int fd, unsigned int max_fd, unsigned int flags) { return (int)syscall(SYS_close_range, fd, max_fd, flags); } #endif /* * Close all file descriptors greater than or equal to lowfd. * This is the expensive (fallback) method. */ static int closefrom_fallback(int lowfd) { int fd, maxfd; #ifdef _SC_OPEN_MAX maxfd = (int)sysconf(_SC_OPEN_MAX); #else maxfd = getdtablesize(); #endif if (maxfd == -1) return -1; for (fd = lowfd; fd < maxfd; fd++) close(fd); return 0; } /* * * Close all file descriptors greater than or equal to lowfd. * * We try the fast way first, falling back on the slow method. * */ void closefrom(int lowfd) { #if defined(__linux__) && defined(SYS_close_range) if (sys_close_range((unsigned int)lowfd, UINT_MAX, 0) == 0) return; #endif closefrom_fallback(lowfd); } dhcpcd-10.1.0/compat/closefrom.h000066400000000000000000000016561470014643500164570ustar00rootroot00000000000000/* * SPDX-License-Identifier: ISC * * Copyright (c) 2004-2005, 2007, 2010, 2012-2015, 2017-2018 * 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 CLOSEFROM_H #define CLOSEFROM_H void closefrom(int); #endif dhcpcd-10.1.0/compat/consttime_memequal.h000066400000000000000000000012741470014643500203550ustar00rootroot00000000000000/* * 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 */ dhcpcd-10.1.0/compat/crypt/000077500000000000000000000000001470014643500154465ustar00rootroot00000000000000dhcpcd-10.1.0/compat/crypt/hmac.c000066400000000000000000000116441470014643500165300ustar00rootroot00000000000000/* $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; } dhcpcd-10.1.0/compat/crypt/hmac.h000066400000000000000000000032521470014643500165310ustar00rootroot00000000000000/* $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 dhcpcd-10.1.0/compat/crypt/md5.c000066400000000000000000000174451470014643500163120ustar00rootroot00000000000000/* * 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 */ } dhcpcd-10.1.0/compat/crypt/md5.h000066400000000000000000000021721470014643500163060ustar00rootroot00000000000000/* * 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 64 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 dhcpcd-10.1.0/compat/crypt/sha256.c000066400000000000000000000201651470014643500166260ustar00rootroot00000000000000/*- * 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)); } dhcpcd-10.1.0/compat/crypt/sha256.h000066400000000000000000000032701470014643500166310ustar00rootroot00000000000000/*- * 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 dhcpcd-10.1.0/compat/crypt_openssl/000077500000000000000000000000001470014643500172115ustar00rootroot00000000000000dhcpcd-10.1.0/compat/crypt_openssl/hmac.c000066400000000000000000000035361470014643500202740ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2023 Canonical Ltd. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this 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 "config.h" #include "openssl/hmac.h" ssize_t hmac(const char *name, const void *key, size_t klen, const void *text, size_t tlen, void *digest, size_t dlen) { const EVP_MD *md; unsigned int outlen; if (strcmp(name, "md5") == 0) md = EVP_md5(); else if (strcmp(name, "sha256") == 0) md = EVP_sha1(); else return -1; HMAC(md, key, (int)klen, text, tlen, digest, &outlen); if (dlen != outlen) return -1; return (ssize_t)outlen; } dhcpcd-10.1.0/compat/crypt_openssl/hmac.h000066400000000000000000000027731470014643500203030ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2023 Canonical Ltd. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this 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 HMAC_H #define HMAC_H #include ssize_t hmac(const char *, const void *, size_t, const void *, size_t, void *, size_t); #endif dhcpcd-10.1.0/compat/crypt_openssl/sha256.c000066400000000000000000000037431470014643500203740ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2023 Canonical Ltd. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this 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 "config.h" #include "sha256.h" #include "openssl/evp.h" /* SHA-256 initialization. Begins a SHA-256 operation. */ void dhcpcd_SHA256_Init(SHA256_CTX *ctx) { ctx->c = EVP_MD_CTX_new(); EVP_DigestInit_ex2(ctx->c, EVP_sha256(), NULL); } /* Add bytes into the hash */ void dhcpcd_SHA256_Update(SHA256_CTX *ctx, const void *in, size_t len) { EVP_DigestUpdate(ctx->c, in, len); } /* * SHA-256 finalization. Pads the input data, exports the hash value, * and clears the context state. */ void dhcpcd_SHA256_Final(unsigned char digest[32], SHA256_CTX *ctx) { EVP_DigestFinal_ex(ctx->c, digest, NULL); EVP_MD_CTX_free(ctx->c); } dhcpcd-10.1.0/compat/crypt_openssl/sha256.h000066400000000000000000000035541470014643500204010ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2023 Canonical Ltd. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this 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 SHA256_H_ #define SHA256_H_ #define SHA256_DIGEST_LENGTH 32 #include "openssl/evp.h" typedef struct dhcpcd_SHA256Context { EVP_MD_CTX *c; } dhcpcd_SHA256_CTX; void dhcpcd_SHA256_Init(dhcpcd_SHA256_CTX *); void dhcpcd_SHA256_Update(dhcpcd_SHA256_CTX *, const void *, size_t); void dhcpcd_SHA256_Final(unsigned char [32], dhcpcd_SHA256_CTX *); #define SHA256_Init dhcpcd_SHA256_Init #define SHA256_Update dhcpcd_SHA256_Update #define SHA256_Final dhcpcd_SHA256_Final #define SHA256_CTX dhcpcd_SHA256_CTX #endif dhcpcd-10.1.0/compat/dprintf.c000066400000000000000000000035611470014643500161240ustar00rootroot00000000000000/* * 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; } dhcpcd-10.1.0/compat/dprintf.h000066400000000000000000000034041470014643500161250ustar00rootroot00000000000000/* * 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 dhcpcd-10.1.0/compat/endian.h000066400000000000000000000042101470014643500157110ustar00rootroot00000000000000/* * 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 dhcpcd-10.1.0/compat/pidfile.c000066400000000000000000000150601470014643500160670ustar00rootroot00000000000000/* $NetBSD: pidfile.c,v 1.16 2021/08/01 15:29:29 andvar 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, otherwise 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 successful, 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; } dhcpcd-10.1.0/compat/pidfile.h000066400000000000000000000032541470014643500160760ustar00rootroot00000000000000/*- * 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 dhcpcd-10.1.0/compat/queue.h000066400000000000000000000151241470014643500156050ustar00rootroot00000000000000/* $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 */ dhcpcd-10.1.0/compat/rb.c000066400000000000000000001166151470014643500150660ustar00rootroot00000000000000/* $NetBSD: rb.c,v 1.16 2021/09/16 21:29:41 andvar 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 HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif #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.16 2021/09/16 21:29:41 andvar Exp $"); #else #include __KERNEL_RCSID(0, "$NetBSD: rb.c,v 1.16 2021/09/16 21:29:41 andvar 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)); } /* Successfully 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 */ dhcpcd-10.1.0/compat/rbtree.h000066400000000000000000000172351470014643500157510ustar00rootroot00000000000000/* $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_*/ dhcpcd-10.1.0/compat/reallocarray.c000066400000000000000000000042161470014643500171340ustar00rootroot00000000000000/* $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); } dhcpcd-10.1.0/compat/reallocarray.h000066400000000000000000000030541470014643500171400ustar00rootroot00000000000000/* $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 dhcpcd-10.1.0/compat/setproctitle.c000066400000000000000000000154351470014643500172020ustar00rootroot00000000000000/* * 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); dhcpcd-10.1.0/compat/setproctitle.h000066400000000000000000000036631470014643500172070ustar00rootroot00000000000000/* * 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 dhcpcd-10.1.0/compat/strlcpy.c000066400000000000000000000031041470014643500161470ustar00rootroot00000000000000/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert 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 */ } dhcpcd-10.1.0/compat/strlcpy.h000066400000000000000000000017071470014643500161630ustar00rootroot00000000000000/* $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 dhcpcd-10.1.0/compat/strtoi.c000066400000000000000000000043631470014643500160030ustar00rootroot00000000000000/* $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 dhcpcd-10.1.0/compat/strtoi.h000066400000000000000000000041541470014643500160060ustar00rootroot00000000000000/*- * 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 dhcpcd-10.1.0/compat/strtou.c000066400000000000000000000043641470014643500160200ustar00rootroot00000000000000/* $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 dhcpcd-10.1.0/config-null.mk000066400000000000000000000001071470014643500155660ustar00rootroot00000000000000# This space left intentionally blank DHCPCD_SRCS+= dhcpcd-embedded.c dhcpcd-10.1.0/configure000077500000000000000000001370101470014643500147330ustar00rootroot00000000000000#!/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= SECCOMP= ARC4RANDOM= CLOSEFROM= RBTREE= CONSTTIME_MEMEQUAL= OPEN_MEMSTREAM= STRLCPY= UDEV= OS= BUILD= HOST= HOSTCC= TARGET= INCLUDEDIR= DEBUG= FORK= STATIC= DEVS= EMBEDDED= AUTH= NTP= POLL= SMALL= SANITIZE=no STATUSARG= OPENSSL= 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;; --disable-seccomp) SECCOMP=no;; --enable-seccomp) SECCOMP=yes;; --disable-ntp) NTP=no;; --enable-ntp) NTP=yes;; --privsepuser) PRIVSEP_USER=$var;; --prefix) PREFIX=$var;prefix=$var;; # prefix is set for autotools compat --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";; --with-openssl) OPENSSL=yes;; --without-openssl) OPENSSL=no;; --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-option-checking|--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 echo >>$CONFIG_H echo "#ifndef CONFIG_H">>$CONFIG_H echo "#define CONFIG_H">>$CONFIG_H echo >>$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*|dragonfly*|freebsd*) ;; *) # 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 if [ -n "$SECCOMP" ] && [ "$SECCOMP" = no ]; then echo "#define DISABLE_SECCOMP" >>$CONFIG_H fi ;; 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 if [ -z "$CLOSEFROM" ]; then printf "Testing for closefrom ... " cat <_closefrom.c #include int main(void) { closefrom(3); return 0; } EOF if $XCC _closefrom.c -o _closefrom 2>&3; then CLOSEFROM=yes else CLOSEFROM=no fi echo "$CLOSEFROM" fi rm -f _closefrom.c _closefrom if [ "$CLOSEFROM" = no ]; then echo "COMPAT_SRCS+= compat/closefrom.c" >>$CONFIG_MK echo "#include \"compat/closefrom.h\"" >>$CONFIG_H fi 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) { return (int)arc4random(); } 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) { return (int)arc4random_uniform(100); } 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 # Our arc4random compat needs memset_explicit, explicit_bzero or memset_s if [ -z "$MEMSET_EXPLICIT" ]; then printf "Testing for memset_explicit ... " cat <_memset_explicit.c #include int main(void) { int a; (void)memset_explicit(&a, 0, sizeof(a)); return 0; } EOF if $XCC _memset_explicit.c -o _memset_explicit 2>&3; then MEMSET_EXPLICIT=yes else MEMSET_EXPLICIT=no fi echo "$MEMSET_EXPLICIT" rm -f _memset_explicit.c _memset_explicit fi if [ "$MEMSET_EXPLICIT" = yes ]; then echo "#define HAVE_MEMSET_EXPLICIT" >>$CONFIG_H fi if [ -z "$EXPLICIT_BZERO" ]; then printf "Testing for explicit_bzero ... " cat <_explicit_bzero.c #define _BSD_SOURCE // musl, will be added for Linux in config.h #include int main(void) { int a; explicit_bzero(&a, sizeof(a)); return 0; } EOF if $XCC _explicit_bzero.c -o _explicit_bzero 2>&3; then EXPLICIT_BZERO=yes else EXPLICIT_BZERO=no fi echo "$EXPLICIT_BZERO" rm -f _explicit_bzero.c _explicit_bzero fi if [ "$EXPLICIT_BZERO" = yes ]; then echo "#define HAVE_EXPLICIT_BZERO" >>$CONFIG_H fi if [ -z "$MEMSET_S" ]; then printf "Testing for memset_s ... " cat <_memset_s.c #define __STDC_WANT_LIB_EXT1__ 1 #include int main(void) { int a; memset_s(&a, sizeof(a), 0, sizeof(a)); return 0; } EOF if $XCC _memset_s.c -o _memset_s 2>&3; then MEMSET_S=yes else MEMSET_S=no fi echo "$MEMSET_S" rm -f _memset_s.c _memset_s fi if [ "$MEMSET_S" = yes ]; then echo "#define __STDC_WANT_LIB_EXT1__ 1" >>$CONFIG_H echo "#define HAVE_MEMSET_S" >>$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) { return (int)pidfile_lock(NULL); } 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]; return (int)strlcpy(s2, s1, sizeof(s2)); } 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; return (int)strtoi("1234", NULL, 0, 0, INT32_MAX, &e); } 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 HAVE_NBTOOL_CONFIG_H 0" >>$CONFIG_H 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 # Detect a polling mechanism. # See src/eloop.c as to why we only detect ppoll, pollts and pselect and # not others like epoll or kqueue. 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) { return pselect(0, NULL, NULL, NULL, NULL, NULL); } 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 kqueue1) echo "#define HAVE_KQUEUE" >>$CONFIG_H echo "#define HAVE_KQUEUE1" >>$CONFIG_H POLL=kqueue ;; kqueue) echo "#define HAVE_KQUEUE" >>$CONFIG_H ;; epoll) echo "#define HAVE_EPOLL" >>$CONFIG_H ;; 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 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 [ -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 [ -z "$HMAC" ]; then HMAC_LIB= printf "Testing for hmac ... " cat <_hmac.c #include #include int main(void) { return (int)hmac(NULL, NULL, 0, NULL, 0, NULL, 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) { return (int)hmac(NULL, NULL, 0, NULL, 0, NULL, 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 [ "$OPENSSL" = yes ] || { [ -z "$OPENSSL" ] && [ "$ALLOW_USR_LIBS" = true ] && [ "$SHA2" = no ] && [ "$HMAC" = no ]; }; then printf "Testing for openssl ... " if type "$PKG_CONFIG" >/dev/null 2>&1; then LIBCRYPTO_CFLAGS=$("$PKG_CONFIG" --cflags libcrypto 2>&3) LIBCRYPTO_LIBS=$("$PKG_CONFIG" --libs libcrypto 2>&3) fi cat <_openssl.c #include #include int main(void) { return OPENSSL_init_crypto(0, NULL) == 1; } EOF if $XCC $LIBCRYPTO_CFLAGS _openssl.c -o _openssl $LIBCRYPTO_LIBS 2>&3; then OPENSSL=yes MD5=yes MD5_LIB= if [ -n "$LIBCRYPTO_CFLAGS" ]; then echo "CFLAGS+= $LIBCRYPTO_CFLAGS" >>$CONFIG_MK fi echo "LDADD+= $LIBCRYPTO_LIBS" >>$CONFIG_MK echo "#define HAVE_OPENSSL" >>$CONFIG_H else OPENSSL=no fi echo "$OPENSSL" rm -f _openssl.c _openssl fi if [ "$OPENSSL" = yes ]; then printf "Testing for openssl/sha.h ... " cat <_openssl_sha.c #include #include int main(void) { SHA256_CTX ctx; SHA256_Init(&ctx); return 0; } EOF if $XCC $LIBCRYPTO_CFLAGS _openssl_sha.c -o _openssl_sha \ $LIBCRYPTO_LIBS 2>&3; then SHA2_H=openssl/sha.h SHA2="yes (-lcrypto)" else SHA2=no fi SHA2_LIB= SHA2_RENAMED= echo "$SHA2" rm -f _openssl_sha.c _openssl_sha 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 [ "$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 [ "$OPENSSL" != yes ] && echo "#define HAVE_MD5_H" >>$CONFIG_H [ -n "$MD5_LIB" ] && echo "LDADD+= $MD5_LIB" >>$CONFIG_MK fi if [ "$OPENSSL" = yes ] && [ "$SHA2" = no ]; then echo "#include \"compat/crypt_openssl/sha256.h\"" >>$CONFIG_H echo "SHA256_SRC= compat/crypt_openssl/sha256.c" >>$CONFIG_MK elif [ "$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 [ "$OPENSSL" = yes ]; then echo "#include \"compat/crypt_openssl/hmac.h\"" >>$CONFIG_H echo "HMAC_SRC= compat/crypt_openssl/hmac.c" >>$CONFIG_MK elif [ "$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) { return udev_monitor_filter_add_match_subsystem_devtype(NULL, NULL, NULL); } 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) { return udev_device_get_is_initialized(NULL); } 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 timesyncd ... " TIMESYNCD= for x in /usr/lib/systemd/systemd-timesyncd; do if [ -x "$x" ]; then TIMESYNCD=$x break fi done if [ -n "$TIMESYNCD" ]; then echo "$TIMESYNCD" HOOKS="$HOOKS${HOOKS:+ }50-timesyncd.conf" else echo "not found" 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 if [ "$NTP" = yes ]; then # --enable-ntp echo "UNCOMMENT_NTP= yes" >>$CONFIG_MK fi echo >>$CONFIG_H echo "#endif /*CONFIG_H*/">>$CONFIG_H 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 dhcpcd-10.1.0/hooks/000077500000000000000000000000001470014643500141455ustar00rootroot00000000000000dhcpcd-10.1.0/hooks/01-test000066400000000000000000000013361470014643500152700ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/hooks/10-wpa_supplicant000066400000000000000000000054321470014643500173430ustar00rootroot00000000000000# 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" ] && \ command -v wpa_supplicant >/dev/null 2>&1 && \ command -v wpa_cli >/dev/null 2>&1 then case "$reason" in PREINIT) wpa_supplicant_start;; RECONFIGURE) wpa_supplicant_reconfigure;; DEPARTED|STOPPED) wpa_supplicant_stop;; esac fi dhcpcd-10.1.0/hooks/15-timezone000066400000000000000000000015651470014643500161540ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/hooks/20-resolv.conf000066400000000000000000000140241470014643500165460ustar00rootroot00000000000000# 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 command -v "$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 dhcpcd-10.1.0/hooks/29-lookup-hostname000066400000000000000000000014561470014643500174530ustar00rootroot00000000000000# 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 command -v dig >/dev/null 2>&1; then h=$(dig +short -x $new_ip_address) if [ $? = 0 ]; then echo "$h" | sed 's/\.$//' return 0 fi elif command -v 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 command -v 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 dhcpcd-10.1.0/hooks/30-hostname.in000066400000000000000000000073001470014643500165330ustar00rootroot00000000000000# 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" ] && command -v 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 0 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 dhcpcd-10.1.0/hooks/50-dhcpcd-compat000066400000000000000000000017661470014643500170320ustar00rootroot00000000000000# 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" dhcpcd-10.1.0/hooks/50-ntp.conf000066400000000000000000000067341470014643500160510ustar00rootroot00000000000000# 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 ] && command -v 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 $(uniqify $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 $new_dhcp6_ntp_server_addr $new_dhcp6_ntp_server_fqdn" ;; esac if $if_configured; then if $if_up; then add_ntp_conf elif $if_down; then remove_ntp_conf fi fi dhcpcd-10.1.0/hooks/50-timesyncd.conf000066400000000000000000000023541470014643500172410ustar00rootroot00000000000000: ${timesyncd_conf_d:=/run/systemd/timesyncd.conf.d} timesyncd_conf="${timesyncd_conf_d}/dhcpcd-$ifname.conf" timesyncd_tmp_d="$state_dir/timesyncd" timesyncd_tmp="$timesyncd_tmp_d/$ifname" NL=" " remove_timesyncd_conf() { if [ -e "$timesyncd_conf" ]; then rm "$timesyncd_conf" systemctl try-reload-or-restart --no-block systemd-timesyncd fi } add_timesyncd_conf() { if [ -z "$new_ntp_servers" ]; then remove_timesyncd_conf return $? fi mkdir -p "$timesyncd_tmp_d" "$timesyncd_conf_d" conf="$signature$NL" conf="${conf}[Time]$NL" conf="${conf}NTP=" # Trim spaces space=false for ntp_server in $(uniqify $new_ntp_servers); do if ! $space; then space=true else conf="$conf " fi conf="$conf$ntp_server" done conf="$conf$NL" printf %s "$conf" > "$timesyncd_tmp" if change_file "$timesyncd_conf" "$timesyncd_tmp"; then systemctl try-reload-or-restart --no-block systemd-timesyncd fi } # 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 $new_dhcp6_ntp_server_addr $new_dhcp6_ntp_server_fqdn" ;; esac if $if_configured; then if $if_up; then add_timesyncd_conf elif $if_down; then remove_timesyncd_conf fi fi dhcpcd-10.1.0/hooks/50-yp.conf000066400000000000000000000022131470014643500156640ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/hooks/50-ypbind.in000066400000000000000000000034451470014643500162120ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/hooks/Makefile000066400000000000000000000040301470014643500156020ustar00rootroot00000000000000TOP= ../ 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: ${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 dhcpcd-10.1.0/hooks/dhcpcd-run-hooks.8.in000066400000000000000000000156701470014643500200240ustar00rootroot00000000000000.\" Copyright (c) 2006-2024 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 31, 2022 .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 set to the reason why .Nm was invoked. DHCP information to be configured is held in variables starting with the word new_ and old DHCP information to be removed is held in variables starting with the word old_. .Nm dhcpcd can display the full list of variables it knows 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 required should be performed now. .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 Persistence 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 its 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 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 , any scripts found in .Pa @HOOKDIR@ in lexical order, 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 https://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. dhcpcd-10.1.0/hooks/dhcpcd-run-hooks.in000066400000000000000000000200741470014643500176500ustar00rootroot00000000000000#!/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 command -v 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 command -v 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 command -v cmp >/dev/null 2>&1; then cmp -s "$1" "$2" elif command -v 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 command -v 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 --no-block \$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 --no-block \$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 case "$hook" in */*~) continue;; esac for skip in $skip_hooks; do case "$hook" in */"$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 dhcpcd-10.1.0/iconfig.mk000066400000000000000000000004711470014643500147730ustar00rootroot00000000000000# 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} dhcpcd-10.1.0/src/000077500000000000000000000000001470014643500136115ustar00rootroot00000000000000dhcpcd-10.1.0/src/GNUmakefile000066400000000000000000000005561470014643500156710ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/src/Makefile000066400000000000000000000103551470014643500152550ustar00rootroot00000000000000# 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: ${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: ${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} ${PCRYPT_SRCS} ${PCOMPAT_SRC} ${CC} ${CPPFLAGS} -MM ${SRCS} ${PCRYPT_SRCS} ${PCOMPAT_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 if ! [ -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.conf ]; then \ ${INSTALL} -m ${CONFMODE} dhcpcd.conf ${DESTDIR}${SYSCONFDIR}; \ if [ "${UNCOMMENT_NTP}" = yes ]; then \ ${SED} -i \ -e 's/#option ntp_servers/option ntp_servers/' \ ${DESTDIR}/${SYSCONFDIR}/dhcpcd.conf; \ fi; \ fi 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 dhcpcd-10.1.0/src/arp.c000066400000000000000000000403131470014643500145400ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - ARP handler * Copyright (c) 2006-2024 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. * If DHCPCD_ARP_PERSISTDEFENCE is set, that enables * RFC5227 section 2.4.c behaviour. Upon conflict * detection, the host records the time that the * conflicting ARP packet was received, and then * broadcasts one single ARP Announcement. The host then * continues to use the address normally. All further * conflict notifications within the DEFEND_INTERVAL are * ignored. */ 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)); if (ifp->options->options & DHCPCD_ARP_PERSISTDEFENCE) return; } 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; struct arphdr ar; struct arp_msg arm; const struct iarp_state *state; struct arp_state *astate, *astaten; uint8_t *hw_s, *hw_t; #ifndef KERNEL_RFC5227 bool is_probe; #endif /* KERNEL_RFC5227 */ /* 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 */ if (ar.ar_hln == ifp->hwlen && memcmp(hw_s, ifp->hwaddr, ifp->hwlen) == 0) { #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); #ifndef KERNEL_RFC5227 /* During ARP probe the 'sender hardware address' MUST contain the hardware * address of the interface sending the packet. RFC5227, 1.1 */ is_probe = ar.ar_op == htons(ARPOP_REQUEST) && IN_IS_ADDR_UNSPECIFIED(&arm.sip) && bpf_flags & BPF_BCAST; if (is_probe && falen > 0 && (falen != ar.ar_hln || memcmp(&arm.sha, &arm.fsha, ar.ar_hln))) { char abuf[HWADDR_LEN * 3]; char fbuf[HWADDR_LEN * 3]; hwaddr_ntoa(&arm.sha, ar.ar_hln, abuf, sizeof(abuf)); hwaddr_ntoa(&arm.fsha, falen, fbuf, sizeof(fbuf)); logwarnx("%s: invalid ARP probe, sender hw address mismatch (%s, %s)", ifp->name, abuf, fbuf); return; } #endif /* KERNEL_RFC5227 */ /* 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, unsigned short events) { 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; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); /* 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; } #ifndef KERNEL_RFC5227 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; } #endif 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; } if (eloop_event_add(ifp->ctx->eloop, astate->bpf->bpf_fd, ELE_READ, arp_read, astate) == -1) logerr("%s: eloop_event_add", __func__); } 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); } dhcpcd-10.1.0/src/arp.h000066400000000000000000000067171470014643500145570ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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_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 */ dhcpcd-10.1.0/src/auth.c000066400000000000000000000430501470014643500147200ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 */ } dhcpcd-10.1.0/src/auth.h000066400000000000000000000054641470014643500147340ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/bpf.c000066400000000000000000000443351470014643500145350ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd: BPF arp and bootp filtering * Copyright (c) 2006-2024 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 "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 O_CLOEXEC #define BPF_OPEN_FLAGS O_RDWR | O_NONBLOCK | O_CLOEXEC #else #define BPF_OPEN_FLAGS O_RDWR | O_NONBLOCK #endif #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; /* /dev/bpf is a cloner on modern kernels */ bpf->bpf_fd = open("/dev/bpf", BPF_OPEN_FLAGS); /* Support older kernels where /dev/bpf is not a cloner */ if (bpf->bpf_fd == -1) { char device[32]; int n = 0; do { snprintf(device, sizeof(device), "/dev/bpf%d", n++); bpf->bpf_fd = open(device, BPF_OPEN_FLAGS); } while (bpf->bpf_fd == -1 && errno == EBUSY); } 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 to the right port. * RFC2131 makes no mention of enforcing a source port. */ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, uh_dport)), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 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. * RFC2131 makes no mention of encforcing a source port, * but dhcpcd does enforce it for sending. */ 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 } dhcpcd-10.1.0/src/bpf.h000066400000000000000000000063731470014643500145420ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd: BPF arp and bootp filtering * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/common.c000066400000000000000000000113521470014643500152470ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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; /* Allow a hwlen of 0 to be an empty string. */ if (buf == NULL || buflen == 0) { errno = ENOBUFS; return NULL; } if (hwlen * 3 > buflen) { /* We should still terminate the string just in case. */ buf[0] = '\0'; 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 } dhcpcd-10.1.0/src/common.h000066400000000000000000000112021470014643500152460ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 __unused # define __unused __attribute__((__unused__)) # endif #else # 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 dhcpcd-10.1.0/src/control.c000066400000000000000000000325631470014643500154460ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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_handle_data(void *, unsigned short); 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 eloop_event_delete(fd->ctx->eloop, fd->fd); close(fd->fd); TAILQ_REMOVE(&fd->ctx->control_fds, fd, next); control_queue_free(fd); free(fd); } static void control_hangup(struct fd_list *fd) { #ifdef PRIVSEP if (IN_PRIVSEP(fd->ctx)) { if (ps_ctl_sendeof(fd) == -1) logerr(__func__); } #endif control_free(fd); } static int control_handle_read(struct fd_list *fd) { char buffer[1024]; ssize_t bytes; bytes = read(fd->fd, buffer, sizeof(buffer) - 1); if (bytes == -1) logerr(__func__); if (bytes == -1 || bytes == 0) { control_hangup(fd); return -1; } #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 0; } if (err == 1 && ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { logerr(__func__); control_free(fd); return -1; } return 0; } #endif control_recvdata(fd, buffer, (size_t)bytes); return 0; } static int control_handle_write(struct fd_list *fd) { struct iovec iov[2]; int iov_len; struct fd_data *data; 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) { if (errno != EPIPE && errno != ENOTCONN) { // We don't get ELE_HANGUP for some reason logerr("%s: write", __func__); } control_hangup(fd); return -1; } 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 0; #ifdef PRIVSEP if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) { if (ps_ctl_sendeof(fd) == -1) logerr(__func__); } #endif /* Done sending data, stop watching write to fd */ if (eloop_event_add(fd->ctx->eloop, fd->fd, ELE_READ, control_handle_data, fd) == -1) logerr("%s: eloop_event_add", __func__); return 0; } static void control_handle_data(void *arg, unsigned short events) { struct fd_list *fd = arg; if (!(events & (ELE_READ | ELE_WRITE | ELE_HANGUP))) logerrx("%s: unexpected event 0x%04x", __func__, events); if (events & ELE_WRITE && !(events & ELE_HANGUP)) { if (control_handle_write(fd) == -1) return; } if (events & ELE_READ) { if (control_handle_read(fd) == -1) return; } if (events & ELE_HANGUP) control_hangup(fd); } 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_free(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, unsigned short events) { struct sockaddr_un run; socklen_t len; struct fd_list *l; int fd, flags; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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, ELE_READ, control_handle_data, l) == -1) logerr("%s: eloop_event_add", __func__); return; error: logerr(__func__); if (fd != -1) close(fd); } static void control_handle(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; control_handle1(ctx, ctx->control_fd, 0, events); } static void control_handle_unpriv(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV, events); } 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; if (eloop_event_add(ctx->eloop, fd, ELE_READ, control_handle, ctx) == -1) logerr("%s: eloop_event_add", __func__); if ((fd = control_start1(ctx, ifname, family, S_UNPRIV)) != -1) { ctx->control_unpriv_fd = fd; if (eloop_event_add(ctx->eloop, fd, ELE_READ, control_handle_unpriv, ctx) == -1) logerr("%s: eloop_event_add", __func__); } 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 (ctx->control_sock[0] != '\0' && ps_root_unlink(ctx, ctx->control_sock) == -1) retval = -1; if (ctx->control_sock_unpriv[0] != '\0' && 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); } int control_queue(struct fd_list *fd, void *data, size_t data_len) { struct fd_data *d; unsigned short events; 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); events = ELE_WRITE; if (fd->flags & FD_LISTEN) events |= ELE_READ; return eloop_event_add(fd->ctx->eloop, fd->fd, events, control_handle_data, fd); } dhcpcd-10.1.0/src/control.h000066400000000000000000000051231470014643500154430ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/defs.h000066400000000000000000000045171470014643500147120ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 DEFS_H #define DEFS_H #define PACKAGE "dhcpcd" #define VERSION "10.1.0" #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 dhcpcd-10.1.0/src/dev.c000066400000000000000000000113511470014643500145340ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 -1; } 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, unsigned short events) { struct dhcpcd_ctx *ctx; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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, ELE_READ, dev_handle_data, ctx) == -1) { logerr(__func__); dev_stop1(ctx, 1); return -1; } } return ctx->dev_fd; } dhcpcd-10.1.0/src/dev.h000066400000000000000000000040101470014643500145330ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/dev/000077500000000000000000000000001470014643500143675ustar00rootroot00000000000000dhcpcd-10.1.0/src/dev/Makefile000066400000000000000000000014141470014643500160270ustar00rootroot00000000000000TOP= ../../ 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: ${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} dhcpcd-10.1.0/src/dev/udev.c000066400000000000000000000107641470014643500155060ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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; } dhcpcd-10.1.0/src/dhcp-common.c000066400000000000000000000554231470014643500161720ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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"); else if (opt->type & OT_URI) printf(" uri"); 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 ssize_t valid_domainname(char *lbl, int type) { char *slbl = lbl, *lst = NULL; unsigned char c; int len = 0; bool start = true, errset = false; if (lbl == NULL || *lbl == '\0') { errno = EINVAL; return 0; } for (;;) { c = (unsigned char)*lbl++; if (c == '\0') return lbl - slbl - 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 = true; 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 = true; break; } } else break; if (start) start = false; } if (!errset) errno = EINVAL; if (lst) { /* At least one valid domain, return it */ *lst = '\0'; return lst - slbl; } 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_URI && isspace(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)) 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 /* Produces a space separated list of URIs. * This is valid as a URI cannot contain a space. */ if ((opt->type & (OT_ARRAY | OT_URI)) == (OT_ARRAY | OT_URI)) { #ifdef SMALL errno = ENOTSUP; return -1; #else char buf[UINT16_MAX + 1]; uint16_t sz; bool first = true; while (dl) { if (dl < 2) { errno = EINVAL; goto err; } memcpy(&u16, data, sizeof(u16)); sz = ntohs(u16); data += sizeof(u16); dl -= sizeof(u16); if (sz == 0) continue; if (sz > dl) { errno = EINVAL; goto err; } if (print_string(buf, sizeof(buf), opt->type, data, sz) == -1) goto err; if (first) first = false; else if (fputc(' ', fp) == EOF) goto err; if (fprintf(fp, "%s", buf) == -1) goto err; data += sz; dl -= sz; } if (fputc('\0', fp) == EOF) goto err; return 0; #endif } if (opt->type & (OT_STRING | OT_URI)) { 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; } dhcpcd-10.1.0/src/dhcp-common.h000066400000000000000000000115731470014643500161750ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 #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 OT_URI (1 << 29) #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 dhcpcd-10.1.0/src/dhcp.c000066400000000000000000003174571470014643500147140ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 #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); #define IP_UDP_SIZE sizeof(struct ip) + sizeof(struct udphdr) #define BOOTP_MIN_MTU IP_UDP_SIZE + sizeof(struct bootp) 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 *, unsigned short); 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 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; 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)) != -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; if (mtu < IPV4_MMTU) return IPV4_MMTU; 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 /* We could take the DHCPv6 approach and work out the * message length up front rather than this big hammer approach. */ if ((mtu = if_getmtu(ifp)) == -1) { logerr("%s: if_getmtu", ifp->name); return -1; } if ((size_t)mtu < BOOTP_MIN_MTU) { logerr("%s: interface mtu is too small (%d<%zu)", ifp->name, mtu, BOOTP_MIN_MTU); return -1; } 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 == INADDR_ANY && 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 + ((size_t)mtu - IP_UDP_SIZE - 1/* 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)((size_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); if (ia == NULL) { lease->mask.s_addr = ipv4_getnetmask(lease->addr.s_addr); lease->brd.s_addr = lease->addr.s_addr | ~lease->mask.s_addr; } else { 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 == INADDR_ANY) 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 && ifo->fallback_time) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->fallback_time, dhcp_fallback, ifp); #ifdef IPV4LL else if (ifo->options & DHCPCD_IPV4LL) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->ipv4ll_time, 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_requestfailed(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); logwarnx("%s: failed to request the lease", ifp->name); free(state->offer); state->offer = NULL; state->offer_len = 0; state->interval = 0; dhcp_discover(ifp); } static void dhcp_request(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; state->state = DHS_REQUEST; // Handle the server being silent to our request. if (ifo->request_time != 0) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->request_time, dhcp_requestfailed, ifp); 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) { struct dhcp_state *state = D_STATE(ifp); // Set the expired state so we send over BPF as this could be // an address defence failure. state->added |= STATE_EXPIRED; 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_BOUND) 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; struct dhcp_state *state = D_STATE(ifp); if (!(ifp->options->options & (DHCPCD_INFORM | DHCPCD_STATIC))) dhcp_decline(ifp); dhcp_drop(ifp, "EXPIRED"); dhcp_unlink(ifp->ctx, state->leasefile); 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; } if (eloop_event_add(ctx->eloop, state->udp_rfd, ELE_READ, dhcp_handleifudp, ifp) == -1) logerr("%s: eloop_event_add", __func__); } 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"); } } static void dhcp_reboot(struct interface *ifp) { struct if_options *ifo; struct dhcp_state *state = D_STATE(ifp); 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)); #if defined(ARP) && !defined(KERNEL_RFC5227) /* Create the DHCP ARP state so we can defend it. */ (void)dhcp_arp_new(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->ipv4ll_time, 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 = 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 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 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); /* Close DHCP ports so a changed interface family is picked * up by a new BPF state. */ dhcp_close(ifp); } 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, has_auto_conf = 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(ifp->ctx, bootp, bootp_len, (uint8_t)i, NULL)) { 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(ifp->ctx, bootp, bootp_len, (uint8_t)i, NULL)) { /* 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 || state->state == DHS_NONE)) { 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 == INADDR_ANY) { LOGDHCP(LOG_WARNING, "no address offered"); 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) { has_auto_conf = true; 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; } } #endif } if (use_v6only) { dhcp_drop(ifp, "EXPIRE"); dhcp_unlink(ifp->ctx, state->leasefile); } if (use_v6only || has_auto_conf) { eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, use_v6only ? v6only_time : DHCP_MAX, dhcp_discover, ifp); return; } /* No hints as what to do with no address? * All we can do is continue. */ if (type == DHCP_OFFER && bootp->yiaddr == INADDR_ANY) 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); if (state->bpf) 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 the right port. */ if (udp.uh_dport != htons(BOOTPC)) return false; return true; } /* IPv4 pseudo header used for computing TCP and UDP checksums. */ struct ip_pseudo { struct in_addr ipp_src; struct in_addr ipp_dst; uint8_t ipp_pad; /* must be zero */ uint8_t ipp_p; uint16_t ipp_len; }; /* Lengths have already been checked. */ static bool checksums_valid(const void *packet, struct in_addr *from, unsigned int flags) { const struct ip *ip = packet; size_t ip_hlen; struct udphdr udp; const char *udpp; uint32_t csum; struct ip_pseudo ip_pseudo; /* We create a buffer to copy ip_pseudo into and send that to * in_cksum() to avoid memory issues. */ uint8_t ip_pseudo_buf[sizeof(struct ip_pseudo)]; if (from != NULL) from->s_addr = ip->ip_src.s_addr; ip_hlen = (size_t)ip->ip_hl * 4; /* RFC 1071 states that the check of the checksum is equal to 0. */ if (in_cksum(ip, ip_hlen, NULL) != 0) return false; if (flags & BPF_PARTIALCSUM) return true; udpp = (const char *)ip + ip_hlen; memcpy(&udp, udpp, sizeof(udp)); /* RFC 768 states that zero means no checksum to verify. */ if (udp.uh_sum == 0) return true; /* UDP checksum is based on a pseudo IP header alongside * the UDP header and payload. */ ip_pseudo.ipp_src = ip->ip_src; ip_pseudo.ipp_dst = ip->ip_dst; ip_pseudo.ipp_pad = 0; ip_pseudo.ipp_p = ip->ip_p; ip_pseudo.ipp_len = udp.uh_ulen; memcpy(ip_pseudo_buf, &ip_pseudo, sizeof(ip_pseudo_buf)); /* Checksum pseudo header and then UDP + payload. */ csum = 0; in_cksum(ip_pseudo_buf, sizeof(ip_pseudo_buf), &csum); csum = in_cksum(udpp, ntohs(udp.uh_ulen), &csum); /* RFC 1071 states that the check of the checksum is equal to 0. */ return csum == 0; } static void dhcp_handlebootp(struct interface *ifp, struct bootp *bootp, size_t len, struct in_addr *from) { size_t v; /* 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, unsigned short events) { struct interface *ifp = arg; uint8_t buf[FRAMELEN_MAX]; ssize_t bytes; struct dhcp_state *state = D_STATE(ifp); struct bpf *bpf = state->bpf; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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; } if (iov->iov_len < offsetof(struct bootp, vend)) { logerrx("%s: truncated packet (%zu) from %s", ifp->name, iov->iov_len, inet_ntoa(from->sin_addr)); 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, unsigned short events) { 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 (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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, unsigned short events) { struct dhcpcd_ctx *ctx = arg; dhcp_readudp(ctx, NULL, events); } static void dhcp_handleifudp(void *arg, unsigned short events) { struct interface *ifp = arg; dhcp_readudp(ifp->ctx, ifp, events); } 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; } if (eloop_event_add(ifp->ctx->eloop, state->bpf->bpf_fd, ELE_READ, dhcp_readbpf, ifp) == -1) logerr("%s: eloop_event_add", __func__); 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; ctx->opt_buffer_len = 0; } } 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; } if (eloop_event_add(ctx->eloop, ctx->udp_rfd, ELE_READ, dhcp_handleudp, ctx) == -1) logerr("%s: eloop_event_add", __func__); } 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); } 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 dhcpcd-10.1.0/src/dhcp.h000066400000000000000000000203031470014643500146760ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 */ dhcpcd-10.1.0/src/dhcp6.c000066400000000000000000003170541470014643500147730ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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); /* Some compilers do not support packed structures. * We manually decode this. */ #if 0 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); #endif #define DHCP6_PD_ADDR_SIZE (8 + 1 + 16) #define DHCP6_PD_ADDR_PLTIME 0 #define DHCP6_PD_ADDR_VLTIME 4 #define DHCP6_PD_ADDR_PLEN 8 #define DHCP6_PD_ADDR_PREFIX 9 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; }; /* * RFC 5908 deprecates OPTION_SNTP_SERVERS. * But we can support both as the hook scripts will uniqify the * results if the server returns both options. */ 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_NTPSERVER, D6_OPTION_NTP_SERVER }, { 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 *, unsigned short); static void dhcp6_startdecline(struct interface *); static void dhcp6_startrequest(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) + DHCP6_PD_ADDR_SIZE; 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)); /* RFC 8415 21.4 and 21.21 state that T1 and T2 should be zero. * An RFC compliant server MUST ignore them anyway. */ 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 uint8_t pdp[DHCP6_PD_ADDR_SIZE]; memset(pdp, 0, DHCP6_PD_ADDR_PLEN); pdp[DHCP6_PD_ADDR_PLEN] = (uint8_t)ap->prefix_len; memcpy(pdp + DHCP6_PD_ADDR_PREFIX, &ap->prefix, DHCP6_PD_ADDR_SIZE - DHCP6_PD_ADDR_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--; n = (size_t)(ep - exb); 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 = { .addr = ap->addr, /* * RFC 8415 21.6 states that the * valid and preferred lifetimes sent by * the client SHOULD be zero and MUST * be ignored by the server. */ }; 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, unsigned int notflags, const struct interface *ifd) { struct dhcp6_state *state; state = D6_STATE(ifp); if (state) { ipv6_freedrop_addrs(&state->addrs, drop, notflags, 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, 0, 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 multicast = 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: multicast = false; inet_ntop(AF_INET6, &state->unicast, uaddr, sizeof(uaddr)); break; } } dst.sin6_addr = multicast ? alldhcp : state->unicast; if (!callback) { logdebugx("%s: %s %s with xid 0x%02x%02x%02x%s%s", ifp->name, multicast ? "multicasting" : "unicasting", dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], state->send->xid[2], !multicast ? " " : "", !multicast ? 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" : multicast ? "multicasting" : "unicasting", dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], state->send->xid[2], state->IMD == 0 && !multicast ? " " : "", state->IMD == 0 && !multicast ? 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 (multicast) { 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_senddiscover2(void *arg) { dhcp6_sendmessage(arg, dhcp6_senddiscover2); } static void dhcp6_senddiscover1(void *arg) { /* * So the initial RT has elapsed. * If we have any ADVERTs we can now REQUEST them. * RFC 8415 15 and 18.2.1 */ struct interface *ifp = arg; struct dhcp6_state *state = D6_STATE(ifp); if (state->recv == NULL || state->recv->type != DHCP6_ADVERTISE) dhcp6_sendmessage(arg, dhcp6_senddiscover2); else dhcp6_startrequest(ifp); } static void dhcp6_senddiscover(void *arg) { struct interface *ifp = arg; struct dhcp6_state *state = D6_STATE(ifp); dhcp6_sendmessage(arg, state->IMD != 0 ? dhcp6_senddiscover : dhcp6_senddiscover1); } 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); 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; struct ipv6_addr *ia; 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; /* If we fail to renew or confirm, our requested addreses will * be marked as stale. To re-request them, just mark them as not stale. */ TAILQ_FOREACH(ia, &state->addrs, next) { if (ia->flags & IPV6_AF_REQUEST) ia->flags &= ~IPV6_AF_STALE; } 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); llevel = state->failed ? LOG_DEBUG : LOG_INFO; 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; 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_fail(struct interface *ifp, bool drop) { struct dhcp6_state *state = D6_STATE(ifp); state->failed = true; if (drop) { dhcp6_freedrop_addrs(ifp, 1, IPV6_AF_DELEGATED | IPV6_AF_PFXDELEGATION, 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); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); } else if (state->new) { script_runreason(ifp, "TIMEOUT6"); // We need to keep the expire timeout alive } if (!dhcp6_startdiscoinform(ifp)) { logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name); state->state = DH6S_INIT; } } 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); eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendconfirm, ifp); /* RFC8415 18.2.3 says that prior addresses SHOULD be used on failure. */ dhcp6_fail(ifp, false); } 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, true); } 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, true); } #ifndef SMALL static void dhcp6_failrebindpd(void *arg) { struct interface *ifp = arg; logerrx("%s: failed to rebind prior DHCPv6 delegation", ifp->name); eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp); /* RFC8415 18.2.3 says that prior addresses SHOULD be used on failure. * 18.2 says REBIND rather than CONFIRM with PD but use CONFIRM timings. */ dhcp6_fail(ifp, false); } 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; ifp = arg; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp); state = D6_STATE(ifp); state->IMD = REB_MAX_DELAY; state->IRT = REB_TIMEOUT; state->MRT = REB_MAX_RT; state->RTC = 0; state->MRC = 0; if (state->state == DH6S_RENEW) logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name); else { loginfox("%s: rebinding prior DHCPv6 lease", ifp->name); #ifndef SMALL /* RFC 8415 18.2.5 */ if (dhcp6_hasprefixdelegation(ifp)) { state->IMD = CNF_MAX_DELAY; state->IRT = CNF_TIMEOUT; state->MRT = CNF_MAX_RT; eloop_timeout_add_sec(ifp->ctx->eloop, CNF_MAX_RD, dhcp6_failrebindpd, ifp); } #endif } state->state = DH6S_REBIND; if (dhcp6_makemessage(ifp) == -1) logerr("%s: %s", __func__, ifp->name); else dhcp6_sendrebind(ifp); } static void dhcp6_startrequest(struct interface *ifp) { struct dhcp6_state *state; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp); state = D6_STATE(ifp); state->state = DH6S_REQUEST; state->RTC = 0; state->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, true); } static void dhcp6_faildecline(void *arg) { struct interface *ifp = arg; logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name); dhcp6_fail(ifp, true); } 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; uint32_t pdp_vltime, pdp_pltime; uint8_t pdp_plen; 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 < DHCP6_PD_ADDR_SIZE) { errno = EINVAL; logerrx("%s: IA Prefix option truncated", ifp->name); continue; } memcpy(&pdp_pltime, o, sizeof(pdp_pltime)); o += sizeof(pdp_pltime); memcpy(&pdp_vltime, o, sizeof(pdp_vltime)); o += sizeof(pdp_vltime); memcpy(&pdp_plen, o, sizeof(pdp_plen)); o += sizeof(pdp_plen); 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; } memcpy(&pdp_prefix, o, sizeof(pdp_prefix)); o += sizeof(pdp_prefix); ol = (uint16_t)(ol - sizeof(pdp_pltime) - sizeof(pdp_vltime) - sizeof(pdp_plen) - 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_plen, IPV6_AF_PFXDELEGATION); 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_PFXDELEGATION)) a->flags |= IPV6_AF_NEW | IPV6_AF_PFXDELEGATION; a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED); 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) { /* Anything not from a lease for this interface should be * marked as stale. */ 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_PFXDELEGATION) 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 && state->state == DH6S_CONFIRM && ok_errno == 0 && state->new && state->new_len) { state->has_no_binding = false; nia = dhcp6_findia(ifp, state->new, state->new_len, sfrom, acquired); } if (nia == 0) { logerrx("%s: no useable IA found in lease", ifp->name); return -1; } 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) { bytes = 0; /* We have already reported the error */ goto ex; } if (state->expire != ND6_INFINITE_LIFETIME && (time_t)state->expire < now - mtime) { 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, IPV6_AF_DELEGATED, 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_PFXDELEGATION)) 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_PFXDELEGATION)) 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); 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) { 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->recv, state->recv_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, true); return; default: valid_op = false; break; } break; case DHCP6_ADVERTISE: if (state->state != DH6S_DISCOVER) { valid_op = false; break; } if (state->recv_len && state->recv->type == DHCP6_ADVERTISE) { /* We already have an advertismemnt. * RFC 8415 says we have to wait for the IRT to elapse. * To keep the same behaviour we won't do anything with * this. In the future we should make a lists of * ADVERTS and pick the "best" one. */ logdebugx("%s: discarding ADVERTISEMENT from %s", ifp->name, sfrom); return; } /* 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); // We will request when the IRT elapses 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, unsigned short events) { 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; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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, unsigned short events) { struct ipv6_addr *ia = arg; dhcp6_recv(ia->iface->ctx, ia, events); } static void dhcp6_recvctx(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; dhcp6_recv(ctx, NULL, events); } int dhcp6_openraw(void) { int fd, v; fd = xsocket(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) { if (*sla->ifname != '-') 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; } if (eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, ELE_READ, dhcp6_recvctx, ctx) == -1) logerr("%s: eloop_event_add", __func__); } 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: /* RFC 8415 21.23 * If D6_OPTION_INFO_REFRESH_TIME does not exist * then we MUST refresh by IRT_DEFAULT seconds * and should not be influenced by only the * pl/vl time of the RA changing. */ if (state->state == DH6S_INIT || (state->state == DH6S_DISCOVER && !(ifp->options->options & DHCPCD_IA_FORCED) && !ipv6nd_hasradhcp(ifp, 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: /* * CONFIRM a prior lease from a RA. * This could be triggered by a roaming interface. * We could also get here if we are delegated to. * Now that we don't remove delegated addresses when * reading the lease file this is the safe path. */ 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, 0, 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; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp); state = D6_STATE(ifp); if (state == NULL) return; 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, ELE_READ, dhcp6_recvaddr, ia) == -1) logerr("%s: eloop_event_add", __func__); } } 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; bool first; #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; first = true; TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->delegating_prefix == NULL) continue; if (first) first = false; else { 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 dhcpcd-10.1.0/src/dhcp6.h000066400000000000000000000170141470014643500147710ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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_NTP_SERVER 56 #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 */ dhcpcd-10.1.0/src/dhcpcd-definitions-small.conf000066400000000000000000000073131470014643500213300ustar00rootroot00000000000000# Copyright (c) 2006-2024 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 array 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 dhcpcd-10.1.0/src/dhcpcd-definitions.conf000066400000000000000000000504101470014643500202160ustar00rootroot00000000000000# Copyright (c) 2006-2024 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 int32 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 Captive Portal, RFC8910 define 114 string captive_portal_uri # 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 # DHCP SZTP Redirect, RFC8572 define 143 array uri sztp_redirect # 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 # Option 149 is unused, RFC3942 # DHCP DOTS, DDoS Open Threat Signaling (DOTS) Agent Discovery RFC8973 define 147 domain dots_ri define 148 array ipaddress dots_address # 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, RFC8520 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 site-specific use by RFC3942. # For historical reasons, some of these options have well known # definitions and we implement those definitions here. # Site-specific options are designed to be configured by the end user # if needed and any configuration here may change in the future. # Option 245 is an IANA assigned private number used by Azure DHCP # servers to provide the IPv4 address of the Azure WireServer endpoint # to virtual machines hosted in Azure. define 245 ipaddress azureendpoint # 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 uri wpad_url define 224 binhex site_specific_224 define 225 binhex site_specific_225 define 226 binhex site_specific_226 define 227 binhex site_specific_227 define 228 binhex site_specific_228 define 229 binhex site_specific_229 define 230 binhex site_specific_230 define 231 binhex site_specific_231 define 232 binhex site_specific_232 define 233 binhex site_specific_233 define 234 binhex site_specific_234 define 235 binhex site_specific_235 define 236 binhex site_specific_236 define 237 binhex site_specific_237 define 238 binhex site_specific_238 define 239 binhex site_specific_239 define 240 binhex site_specific_240 define 241 binhex site_specific_241 define 242 binhex site_specific_242 define 243 binhex site_specific_243 define 244 binhex site_specific_244 #Option 245 has a custom definition above. define 246 binhex site_specific_246 define 247 binhex site_specific_247 define 248 binhex site_specific_248 #Option 249 has a custom definition above. define 250 binhex site_specific_250 define 251 binhex site_specific_251 #Option 252 has a custom definition above. define 253 binhex site_specific_253 define 254 binhex site_specific_254 # 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 array 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 uri 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, RFC8520 define6 112 string mudurl # DHCP Captive Portal, RFC8910 define6 103 uri captive_portal_uri # DHCP SZTP Redirect, RFC8572 define6 136 array uri sztp_redirect # DHCP DDoS Open Threat Signaling (DOTS) Agent Discovery, RFC8973 define6 141 domain dots_ri define6 142 array ip6address dots_address # DHCP ANDSF, RFC6153 define6 143 array ip6address andsf6 # 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 dhcpcd-10.1.0/src/dhcpcd-embedded.c.in000066400000000000000000000031211470014643500173330ustar00rootroot00000000000000/* * 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-2024 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[] = dhcpcd-10.1.0/src/dhcpcd-embedded.h.in000066400000000000000000000032541470014643500173470ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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[]; dhcpcd-10.1.0/src/dhcpcd.8.in000066400000000000000000000635221470014643500155440ustar00rootroot00000000000000.\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright (c) 2006-2024 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .Dd June 17, 2024 .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 is 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. The .Fl w , Fl Fl waitip option is enabled in this instance to maintain compatibility with older versions. Using a single interface, optionally further limited to an address protocol, also affects the .Fl k , .Fl N , .Fl n and .Fl x options, where the same interface and any address protocol 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 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, otherwise 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 update 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 de-configures the .Ar interface when it exits unless this option is enabled. 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 command-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 use the special interface named .Ar - to dump it. 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 Berkeley 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 https://roy.marples.name/projects/dhcpcd dhcpcd-10.1.0/src/dhcpcd.c000066400000000000000000001736721470014643500152220ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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-2024 Roy Marples"; #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_OPENSSL #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_INET6; 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; } #ifndef THERE_IS_NO_FORK void dhcpcd_daemonised(struct dhcpcd_ctx *ctx) { unsigned int logopts = loggetopts(); /* * Stop writing to stderr. * On the happy path, only the manager process writes to stderr, * so this just stops wasting fprintf calls to nowhere. */ logopts &= ~LOGERR_ERR; logsetopts(logopts); /* * We need to do something with stdout/stderr to avoid SIGPIPE. * We know that stdin is already mapped to /dev/null. * TODO: Capture script output and log it to the logfile and/or syslog. */ dup2(STDIN_FILENO, STDOUT_FILENO); dup2(STDIN_FILENO, STDERR_FILENO); ctx->options |= DHCPCD_DAEMONISED; } #endif /* 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 exit_code; 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; #ifdef PRIVSEP if (IN_PRIVSEP(ctx)) ps_daemonised(ctx); else #endif dhcpcd_daemonised(ctx); eloop_event_delete(ctx->eloop, ctx->fork_fd); exit_code = EXIT_SUCCESS; if (write(ctx->fork_fd, &exit_code, sizeof(exit_code)) == -1) logerr(__func__); close(ctx->fork_fd); ctx->fork_fd = -1; #endif } static void dhcpcd_drop_af(struct interface *ifp, int stop, int af) { if (af == AF_UNSPEC || af == AF_INET6) { #ifdef DHCP6 dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); #endif #ifdef INET6 ipv6nd_drop(ifp); ipv6_drop(ifp); #endif } if (af == AF_UNSPEC || af == AF_INET) { #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 dhcpcd_drop(struct interface *ifp, int stop) { dhcpcd_drop_af(ifp, stop, AF_UNSPEC); } 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_initstate1(struct interface *ifp, int argc, char **argv, unsigned long long options) { struct if_options *ifo; configure_interface(ifp, argc, argv, options); if (!ifp->active) return; ifo = ifp->options; ifo->options |= options; #ifdef INET6 if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == -1) { logerr(__func__); ifo->options &= ~DHCPCD_IPV6; } #endif } 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 /* CONFIRM lease triggered from RA */ 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; /* IF_ACTIVE_USER will start protocols when the interface is started. * IF_ACTIVE will ask the protocols for setup, * such as any delegated prefixes. */ ifp->active = IF_ACTIVE; dhcpcd_initstate(ifp, options); /* It's possible we might not have been able to load * a config. */ if (!ifp->active) return; 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); if_freeifaddrs(ctx, &ifaddrs); return e; } static void dhcpcd_handlelink(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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); if_freeifaddrs(ctx, &ifaddrs); } 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 volatile bool dhcpcd_exiting = false; 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: #ifdef PRIVSEP ps_root_signalcb(sig, ctx); #else while (waitpid(-1, NULL, WNOHANG) > 0) ; #endif return; default: logerrx("received signal %d but don't know what to do with it", sig); return; } /* * Privsep has a mini-eloop for reading data from other processes. * This mini-eloop processes signals as well so we can reap children. * During teardown we don't want to process SIGTERM or SIGINT again, * as that could trigger memory issues. */ if (dhcpcd_exiting) return; dhcpcd_exiting = true; if (!(ctx->options & DHCPCD_TEST)) stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, exit_code); dhcpcd_exiting = false; } #endif int dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, char **argv) { struct interface *ifp; struct if_options *ifo; unsigned long long opts, orig_opts; int opt, oi, oifind, 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) { oifind = 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; } } /* store the index; the optind will change when a getopt get called */ oifind = optind; if (opts & DHCPCD_DUMPLEASE) { ctx->options |= DHCPCD_DUMPLEASE; dumplease: nifaces = 0; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; for (oi = oifind; oi < argc; oi++) { if (strcmp(ifp->name, argv[oi]) == 0) break; } if (oifind == 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 = oifind; oi < argc; oi++) { if (strcmp(ifp->name, argv[oi]) == 0) break; } if (oifind == 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 (oifind == argc && af == AF_UNSPEC) { stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, EXIT_SUCCESS); return 0; } TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; for (oi = oifind; oi < argc; oi++) { if (strcmp(ifp->name, argv[oi]) == 0) break; } if (oi == argc) continue; ifo = ifp->options; orig_opts = ifo->options; ifo->options |= opts; if (opts & DHCPCD_RELEASE) ifo->options &= ~DHCPCD_PERSISTENT; switch (af) { case AF_INET: ifo->options &= ~DHCPCD_IPV4; break; case AF_INET6: ifo->options &= ~DHCPCD_IPV6; break; } if (af != AF_UNSPEC) dhcpcd_drop_af(ifp, 1, af); else stop_interface(ifp, NULL); ifo->options = orig_opts; } return 0; } if (do_renew) { if (oifind == argc) { dhcpcd_renew(ctx); return 0; } for (oi = oifind; 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, oifind); return 0; } static void dhcpcd_readdump1(void *, unsigned short); static void dhcpcd_readdump2(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; ssize_t len; int exit_code = EXIT_FAILURE; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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'); if (eloop_event_add(ctx->eloop, ctx->control_fd, ELE_READ, dhcpcd_readdump1, ctx) == -1) logerr("%s: eloop_event_add", __func__); return; } exit_code = EXIT_SUCCESS; finished: shutdown(ctx->control_fd, SHUT_RDWR); eloop_exit(ctx->eloop, exit_code); } static void dhcpcd_readdump1(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; ssize_t len; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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; if (eloop_event_add(ctx->eloop, ctx->control_fd, ELE_READ, dhcpcd_readdump2, ctx) == -1) logerr("%s: eloop_event_add", __func__); return; err: logerr(__func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } static void dhcpcd_readdump0(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; ssize_t len; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); 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; } if (eloop_event_add(ctx->eloop, ctx->control_fd, ELE_READ, dhcpcd_readdump1, ctx) == -1) logerr("%s: eloop_event_add", __func__); } 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, ELE_READ, dhcpcd_readdump0, ctx); } static void dhcpcd_fork_cb(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; int exit_code; ssize_t len; if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); len = read(ctx->fork_fd, &exit_code, sizeof(exit_code)); if (len == -1) { logerr(__func__); eloop_exit(ctx->eloop, EXIT_FAILURE); return; } if (len == 0) { if (ctx->options & DHCPCD_FORKED) { logerrx("%s: dhcpcd manager hungup", __func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } else { // Launcher exited eloop_event_delete(ctx->eloop, ctx->fork_fd); close(ctx->fork_fd); ctx->fork_fd = -1; } return; } if ((size_t)len < sizeof(exit_code)) { logerrx("%s: truncated read %zd (expected %zu)", __func__, len, sizeof(exit_code)); eloop_exit(ctx->eloop, EXIT_FAILURE); return; } if (ctx->options & DHCPCD_FORKED) { if (exit_code == EXIT_SUCCESS) logdebugx("forked to background"); eloop_exit(ctx->eloop, exit_code); } else dhcpcd_signal_cb(exit_code, ctx); } static void dhcpcd_pidfile_timeout(void *arg) { struct dhcpcd_ctx *ctx = arg; pid_t pid; pid = pidfile_read(ctx->pidfile); if(pid == -1) eloop_exit(ctx->eloop, EXIT_SUCCESS); else if (++ctx->duid_len >= 100) { /* overload duid_len */ logerrx("pid %d failed to exit", pid); eloop_exit(ctx->eloop, EXIT_FAILURE); } else eloop_timeout_add_msec(ctx->eloop, 100, dhcpcd_pidfile_timeout, ctx); } static int dup_null(int fd) { int fd_null = open(_PATH_DEVNULL, O_WRONLY); int err; if (fd_null == -1) { logwarn("open %s", _PATH_DEVNULL); return -1; } if ((err = dup2(fd_null, fd)) == -1) logwarn("dup2 %d", fd); close(fd_null); return err; } 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]; #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)); closefrom(STDERR_FILENO + 1); 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_log_fd = ctx.ps_log_root_fd = -1; TAILQ_INIT(&ctx.ps_processes); #endif logopts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID; /* Ensure we have stdin, stdout and stderr file descriptors. * This is important as we do run scripts which expect these. */ if (fcntl(STDIN_FILENO, F_GETFD) == -1) dup_null(STDIN_FILENO); if (fcntl(STDOUT_FILENO, F_GETFD) == -1) dup_null(STDOUT_FILENO); if (fcntl(STDERR_FILENO, F_GETFD) == -1) dup_null(STDERR_FILENO); else 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 '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 we are given any interfaces or a family, we * cannot send a signal as that would impact * other interfaces. */ if (optind != argc || family != AF_UNSPEC) sig = 0; } 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 (pid != 0 && pid != -1 && errno != ESRCH) { logerr("kill"); goto exit_failure; } unlink(ctx.pidfile); /* We can still continue and send the command * via the control socket. */ } else { if (sig == SIGHUP || sig == SIGUSR1) goto exit_success; /* Spin until it exits */ loginfox("waiting for pid %d to exit", pid); dhcpcd_pidfile_timeout(&ctx); goto run_loop; } } #endif #ifdef HAVE_OPENSSL OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS | OPENSSL_INIT_LOAD_CONFIG, NULL); #endif #ifdef PRIVSEP ps_init(&ctx); #endif #ifndef SMALL if (ctx.options & DHCPCD_DUMPLEASE && ctx.ifc == 1 && ctx.ifv[0][0] == '-' && ctx.ifv[0][1] == '\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 /* Try and contact the manager process to send the instruction. */ if (!(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 asking dhcpcd to exit and we failed to * send a signal or a message then we * don't proceed past here. */ if (ctx.options & DHCPCD_DUMPLEASE || sig == SIGTERM || sig == SIGALRM) { if (errno == ENOENT) logerrx(PACKAGE" 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"); // We don't need stdin past this point dup_null(STDIN_FILENO); #if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK) if (!(ctx.options & DHCPCD_DAEMONISE)) goto start_manager; if (xsocketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CXNB, 0, fork_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 if (eloop_event_add(ctx.eloop, ctx.fork_fd, ELE_READ, dhcpcd_fork_cb, &ctx) == -1) logerr("%s: eloop_event_add", __func__); 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: eloop_forked(ctx.eloop); 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 if (eloop_event_add(ctx.eloop, ctx.fork_fd, ELE_READ, dhcpcd_fork_cb, &ctx) == -1) logerr("%s: eloop_event_add", __func__); #ifdef PRIVSEP if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) goto exit_failure; #endif goto run_loop; } #ifdef DEBUG_FD loginfox("forkfd %d", ctx.fork_fd); #endif /* 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. */ if (eloop_event_add(ctx.eloop, ctx.link_fd, ELE_READ, dhcpcd_handlelink, &ctx) == -1) logerr("%s: eloop_event_add", __func__); #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_freeifaddrs(&ctx, &ifaddrs); ifaddrs = NULL; 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_freeifaddrs(&ctx, &ifaddrs); #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_closesockets(&ctx); free_globals(&ctx); #ifdef INET6 ipv6_ctxfree(&ctx); #endif #ifdef PLUGIN_DEV dev_stop(&ctx); #endif if (ctx.script != dhcpcd_default_script) free(ctx.script); #ifdef PRIVSEP if (ps_stopwait(&ctx) != EXIT_SUCCESS) i = EXIT_FAILURE; #endif if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) loginfox(PACKAGE " exited"); #ifdef PRIVSEP if (ps_root_stop(&ctx) == -1) i = EXIT_FAILURE; eloop_free(ctx.ps_eloop); #endif #ifdef USE_SIGNALS /* If still attached, detach from the launcher */ if (ctx.options & DHCPCD_STARTED && ctx.fork_fd != -1) { if (write(ctx.fork_fd, &i, sizeof(i)) == -1) logerr("%s: write", __func__); } #endif eloop_free(ctx.eloop); logclose(); free(ctx.logfile); free(ctx.ctl_buf); #ifdef SETPROCTITLE_H setproctitle_fini(); #endif #ifdef USE_SIGNALS if (ctx.options & (DHCPCD_FORKED | DHCPCD_PRIVSEP)) _exit(i); /* so atexit won't remove our pidfile */ #endif return i; } dhcpcd-10.1.0/src/dhcpcd.conf000066400000000000000000000026251470014643500157120ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/src/dhcpcd.conf.5.in000066400000000000000000000752141470014643500164660ustar00rootroot00000000000000.\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright (c) 2006-2024 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 May 24, 2024 .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, otherwise 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 fallback_time Ar seconds Start fallback after .Ar seconds . The default is 5 seconds. .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 . To avoid delegating to any interface, use - as the invalid interface name. 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 ia_pd 4 - # request a PD but don't assign it .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 ipv4ll_time Ar seconds Wait for .Ar seconds before starting IPv4LL. The default is 5 seconds. .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 DHCP 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 is not possible to request a DHCPv6 lease time as this is not RFC compliant. See RFC 8415 21.4, 21.6, 21.21 and 21.22. .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 arp_persistdefence Keep the IP address even if defence fails upon IP Address conflict. .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 request_time Ar seconds Request the lease for .Ar seconds before going back to DISCOVER. The default is 180 seconds. .It Ic ssid Ar ssid Subsequent options are only parsed for this wireless .Ar ssid . .It Ic slaac Ic hwaddr | Ic private | Ic token Ar token Op Ic temp | Ic temporary Selects the interface identifier used for SLAAC generated IPv6 addresses. If .Ic private is used, a RFC 7217 address is generated. If .Ic token Ar token is used then the token is combined with the prefix to make the final address. The .Ic 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 an empty value this removes all prior static allocations to the same value. This is useful when using profiles and in the case of .Ic ip_address it will remove the static allocation. Note that setting 0.0.0.0 keeps the static allocation but waits for a 3rdparty to configure the address. 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=0.0.0.0 .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 uri If an array then the first two bytes are the URI length inside the option data. Otherwise, the whole option data is the URI. As a space is not allowed in the URI encoding, the URIs are space separated. .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 https://roy.marples.name/projects/dhcpcd dhcpcd-10.1.0/src/dhcpcd.h000066400000000000000000000161671470014643500152220ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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]; 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 */ struct ps_process_head ps_processes; /* List of spawned processes */ struct ps_process *ps_root; struct ps_process *ps_inet; struct ps_process *ps_ctl; int ps_data_fd; /* data returned from processes */ int ps_log_fd; /* chroot logging */ int ps_log_root_fd; /* outside chroot log reader */ struct eloop *ps_eloop; /* eloop for polling root data */ 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_daemonised(struct dhcpcd_ctx *); void dhcpcd_daemonise(struct dhcpcd_ctx *); void dhcpcd_signal_cb(int, void *); 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 dhcpcd-10.1.0/src/duid.c000066400000000000000000000141041470014643500147020ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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" /* * Machine, system or product UUIDs are not guaranteed unique. * Let's not use them by default. */ #ifdef USE_MACHINE_UUID 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; } #endif 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) { #ifdef USE_MACHINE_UUID 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; #else free(data); return 0; #endif } /* 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; } dhcpcd-10.1.0/src/duid.h000066400000000000000000000032721470014643500147130ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/eloop.c000066400000000000000000000672621470014643500151100ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * eloop - portable event based main loop. * Copyright (c) 2006-2024 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. */ /* NOTES: * Basically for a small number of fd's (total, not max fd) * of say a few hundred, ppoll(2) performs just fine, if not faster than others. * It also has the smallest memory and binary size footprint. * ppoll(2) is available on all modern OS my software runs on and should be * an up and coming POSIX standard interface. * If ppoll is not available, then pselect(2) can be used instead which has * even smaller memory and binary size footprint. * However, this difference is quite tiny and the ppoll API is superior. * pselect cannot return error conditions such as EOF for example. * * Both epoll(7) and kqueue(2) require an extra fd per process to manage * their respective list of interest AND syscalls to manage it. * So for a small number of fd's, these are more resource intensive, * especially when used with more than one process. * * epoll avoids the resource limit RLIMIT_NOFILE Linux poll stupidly applies. * kqueue avoids the same limit on OpenBSD. * ppoll can still be secured in both by using SEECOMP or pledge. * * kqueue can avoid the signal trick we use here so that we function calls * other than those listed in sigaction(2) in our signal handlers which is * probably more robust than ours at surviving a signal storm. * signalfd(2) is available for Linux which probably works in a similar way * but it's yet another fd to use. * * Taking this all into account, ppoll(2) is the default mechanism used here. */ #if (defined(__unix__) || defined(unix)) && !defined(USG) #include #endif #include #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 /* Prioritise which mechanism we want to use.*/ #if defined(HAVE_PPOLL) #undef HAVE_EPOLL #undef HAVE_KQUEUE #undef HAVE_PSELECT #elif defined(HAVE_POLLTS) #define HAVE_PPOLL #define ppoll pollts #undef HAVE_EPOLL #undef HAVE_KQUEUE #undef HAVE_PSELECT #elif defined(HAVE_KQUEUE) #undef HAVE_EPOLL #undef HAVE_PSELECT #elif defined(HAVE_EPOLL) #undef HAVE_KQUEUE #undef HAVE_PSELECT #elif !defined(HAVE_PSELECT) #define HAVE_PPOLL #endif #if defined(HAVE_KQUEUE) #include #if defined(__DragonFly__) || defined(__FreeBSD__) #define _kevent(kq, cl, ncl, el, nel, t) \ kevent((kq), (cl), (int)(ncl), (el), (int)(nel), (t)) #else #define _kevent kevent #endif #define NFD 2 #elif defined(HAVE_EPOLL) #include #define NFD 1 #elif defined(HAVE_PPOLL) #include #define NFD 1 #elif defined(HAVE_PSELECT) #include #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 /* 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 #ifndef __arraycount # define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) #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 (*cb)(void *, unsigned short); void *cb_arg; unsigned short events; #ifdef HAVE_PPOLL struct pollfd *pollfd; #endif }; 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; struct timespec now; TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; struct timeout_head free_timeouts; const int *signals; size_t nsignals; void (*signal_cb)(int, void *); void *signal_cb_ctx; #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) int fd; #endif #if defined(HAVE_KQUEUE) struct kevent *fds; #elif defined(HAVE_EPOLL) struct epoll_event *fds; #elif defined(HAVE_PPOLL) struct pollfd *fds; #endif #if !defined(HAVE_PSELECT) size_t nfds; #endif int exitcode; bool exitnow; bool events_need_setup; bool cleared; }; #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 static int eloop_event_setup_fds(struct eloop *eloop) { struct eloop_event *e, *ne; #if defined(HAVE_KQUEUE) struct kevent *pfd; size_t nfds = eloop->nsignals; #elif defined(HAVE_EPOLL) struct epoll_event *pfd; size_t nfds = 0; #elif defined(HAVE_PPOLL) struct pollfd *pfd; size_t nfds = 0; #endif #ifndef HAVE_PSELECT nfds += eloop->nevents * NFD; if (eloop->nfds < nfds) { pfd = eloop_realloca(eloop->fds, nfds, sizeof(*pfd)); if (pfd == NULL) return -1; eloop->fds = pfd; eloop->nfds = nfds; } #endif #ifdef HAVE_PPOLL pfd = eloop->fds; #endif 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 HAVE_PPOLL e->pollfd = pfd; pfd->fd = e->fd; pfd->events = 0; if (e->events & ELE_READ) pfd->events |= POLLIN; if (e->events & ELE_WRITE) pfd->events |= POLLOUT; pfd->revents = 0; pfd++; #endif } eloop->events_need_setup = false; return 0; } size_t eloop_event_count(const struct eloop *eloop) { return eloop->nevents; } int eloop_event_add(struct eloop *eloop, int fd, unsigned short events, void (*cb)(void *, unsigned short), void *cb_arg) { struct eloop_event *e; bool added; #if defined(HAVE_KQUEUE) struct kevent ke[2], *kep = &ke[0]; size_t n; #elif defined(HAVE_EPOLL) struct epoll_event epe; int op; #endif assert(eloop != NULL); assert(cb != NULL && cb_arg != NULL); if (fd == -1 || !(events & (ELE_READ | ELE_WRITE | ELE_HANGUP))) { errno = EINVAL; return -1; } TAILQ_FOREACH(e, &eloop->events, next) { if (e->fd == fd) break; } if (e == NULL) { added = true; 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->events = 0; } else added = false; e->cb = cb; e->cb_arg = cb_arg; #if defined(HAVE_KQUEUE) n = 2; if (events & ELE_READ && !(e->events & ELE_READ)) EV_SET(kep++, (uintptr_t)fd, EVFILT_READ, EV_ADD, 0, 0, e); else if (!(events & ELE_READ) && e->events & ELE_READ) EV_SET(kep++, (uintptr_t)fd, EVFILT_READ, EV_DELETE, 0, 0, e); else n--; if (events & ELE_WRITE && !(e->events & ELE_WRITE)) EV_SET(kep++, (uintptr_t)fd, EVFILT_WRITE, EV_ADD, 0, 0, e); else if (!(events & ELE_WRITE) && e->events & ELE_WRITE) EV_SET(kep++, (uintptr_t)fd, EVFILT_WRITE, EV_DELETE, 0, 0, e); else n--; #ifdef EVFILT_PROCDESC if (events & ELE_HANGUP) EV_SET(kep++, (uintptr_t)fd, EVFILT_PROCDESC, EV_ADD, NOTE_EXIT, 0, e); else n--; #endif if (n != 0 && _kevent(eloop->fd, ke, n, NULL, 0, NULL) == -1) { if (added) { TAILQ_REMOVE(&eloop->events, e, next); TAILQ_INSERT_TAIL(&eloop->free_events, e, next); } return -1; } #elif defined(HAVE_EPOLL) memset(&epe, 0, sizeof(epe)); epe.data.ptr = e; if (events & ELE_READ) epe.events |= EPOLLIN; if (events & ELE_WRITE) epe.events |= EPOLLOUT; op = added ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; if (epe.events != 0 && epoll_ctl(eloop->fd, op, fd, &epe) == -1) { if (added) { TAILQ_REMOVE(&eloop->events, e, next); TAILQ_INSERT_TAIL(&eloop->free_events, e, next); } return -1; } #elif defined(HAVE_PPOLL) e->pollfd = NULL; UNUSED(added); #else UNUSED(added); #endif e->events = events; eloop->events_need_setup = true; return 0; } int eloop_event_delete(struct eloop *eloop, int fd) { struct eloop_event *e; #if defined(HAVE_KQUEUE) struct kevent ke[2], *kep = &ke[0]; size_t n; #endif 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 defined(HAVE_KQUEUE) n = 0; if (e->events & ELE_READ) { EV_SET(kep++, (uintptr_t)fd, EVFILT_READ, EV_DELETE, 0, 0, e); n++; } if (e->events & ELE_WRITE) { EV_SET(kep++, (uintptr_t)fd, EVFILT_WRITE, EV_DELETE, 0, 0, e); n++; } if (n != 0 && _kevent(eloop->fd, ke, n, NULL, 0, NULL) == -1) return -1; #elif defined(HAVE_EPOLL) if (epoll_ctl(eloop->fd, EPOLL_CTL_DEL, fd, NULL) == -1) return -1; #endif e->fd = -1; eloop->nevents--; eloop->events_need_setup = true; return 1; } 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; } /* * 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 = true; } void eloop_enter(struct eloop *eloop) { assert(eloop != NULL); eloop->exitnow = false; } /* Must be called after fork(2) */ int eloop_forked(struct eloop *eloop) { #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) struct eloop_event *e; #if defined(HAVE_KQUEUE) struct kevent *pfds, *pfd; size_t i; #elif defined(HAVE_EPOLL) struct epoll_event epe = { .events = 0 }; #endif assert(eloop != NULL); #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) if (eloop->fd != -1) close(eloop->fd); if (eloop_open(eloop) == -1) return -1; #endif #ifdef HAVE_KQUEUE pfds = malloc((eloop->nsignals + (eloop->nevents * NFD)) * sizeof(*pfds)); pfd = pfds; if (eloop->signal_cb != NULL) { for (i = 0; i < eloop->nsignals; i++) { EV_SET(pfd++, (uintptr_t)eloop->signals[i], EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); } } else i = 0; #endif TAILQ_FOREACH(e, &eloop->events, next) { if (e->fd == -1) continue; #if defined(HAVE_KQUEUE) if (e->events & ELE_READ) { EV_SET(pfd++, (uintptr_t)e->fd, EVFILT_READ, EV_ADD, 0, 0, e); i++; } if (e->events & ELE_WRITE) { EV_SET(pfd++, (uintptr_t)e->fd, EVFILT_WRITE, EV_ADD, 0, 0, e); i++; } #elif defined(HAVE_EPOLL) memset(&epe, 0, sizeof(epe)); epe.data.ptr = e; if (e->events & ELE_READ) epe.events |= EPOLLIN; if (e->events & ELE_WRITE) epe.events |= EPOLLOUT; if (epoll_ctl(eloop->fd, EPOLL_CTL_ADD, e->fd, &epe) == -1) return -1; #endif } #if defined(HAVE_KQUEUE) if (i == 0) return 0; return _kevent(eloop->fd, pfds, i, NULL, 0, NULL); #else return 0; #endif #else UNUSED(eloop); return 0; #endif } int eloop_open(struct eloop *eloop) { #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) int fd; assert(eloop != NULL); #if defined(HAVE_KQUEUE1) fd = kqueue1(O_CLOEXEC); #elif defined(HAVE_KQUEUE) int flags; fd = kqueue(); flags = fcntl(fd, F_GETFD, 0); if (!(flags != -1 && !(flags & FD_CLOEXEC) && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0)) { close(fd); return -1; } #elif defined(HAVE_EPOLL) fd = epoll_create1(EPOLL_CLOEXEC); #endif eloop->fd = fd; return fd; #else UNUSED(eloop); return 0; #endif } int eloop_signal_set_cb(struct eloop *eloop, const int *signals, size_t nsignals, void (*signal_cb)(int, void *), void *signal_cb_ctx) { #ifdef HAVE_KQUEUE size_t i; struct kevent *ke, *kes; #endif int error = 0; assert(eloop != NULL); #ifdef HAVE_KQUEUE ke = kes = malloc(MAX(eloop->nsignals, nsignals) * sizeof(*kes)); if (kes == NULL) return -1; for (i = 0; i < eloop->nsignals; i++) { EV_SET(ke++, (uintptr_t)eloop->signals[i], EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL); } if (i != 0 && _kevent(eloop->fd, kes, i, NULL, 0, NULL) == -1) { error = -1; goto out; } #endif eloop->signals = signals; eloop->nsignals = nsignals; eloop->signal_cb = signal_cb; eloop->signal_cb_ctx = signal_cb_ctx; #ifdef HAVE_KQUEUE if (signal_cb == NULL) goto out; ke = kes; for (i = 0; i < eloop->nsignals; i++) { EV_SET(ke++, (uintptr_t)eloop->signals[i], EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); } if (i != 0 && _kevent(eloop->fd, kes, i, NULL, 0, NULL) == -1) error = -1; out: free(kes); #endif return error; } #ifndef HAVE_KQUEUE 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; } #endif int eloop_signal_mask(struct eloop *eloop, sigset_t *oldset) { sigset_t newset; size_t i; #ifndef HAVE_KQUEUE struct sigaction sa = { .sa_sigaction = eloop_signal3, .sa_flags = SA_SIGINFO, }; #endif assert(eloop != NULL); sigemptyset(&newset); for (i = 0; i < eloop->nsignals; i++) sigaddset(&newset, eloop->signals[i]); if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) return -1; #ifndef HAVE_KQUEUE sigemptyset(&sa.sa_mask); for (i = 0; i < eloop->nsignals; i++) { if (sigaction(eloop->signals[i], &sa, NULL) == -1) return -1; } #endif 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; #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) if (eloop_open(eloop) == -1) { eloop_free(eloop); return NULL; } #endif return eloop; } void eloop_clear(struct eloop *eloop, ...) { va_list va1, va2; int except_fd; struct eloop_event *e, *ne; struct eloop_timeout *t; if (eloop == NULL) return; va_start(va1, eloop); TAILQ_FOREACH_SAFE(e, &eloop->events, next, ne) { va_copy(va2, va1); do except_fd = va_arg(va2, int); while (except_fd != -1 && except_fd != e->fd); va_end(va2); if (e->fd == except_fd && e->fd != -1) continue; TAILQ_REMOVE(&eloop->events, e, next); if (e->fd != -1) { close(e->fd); eloop->nevents--; } free(e); } va_end(va1); #if !defined(HAVE_PSELECT) /* Free the pollfd buffer and ensure it's re-created before * the next run. This allows us to shrink it incase we use a lot less * signals and fds to respond to after forking. */ free(eloop->fds); eloop->fds = NULL; eloop->nfds = 0; eloop->events_need_setup = true; #endif 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); } eloop->cleared = true; } void eloop_free(struct eloop *eloop) { eloop_clear(eloop, -1); #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) if (eloop != NULL && eloop->fd != -1) close(eloop->fd); #endif free(eloop); } #if defined(HAVE_KQUEUE) static int eloop_run_kqueue(struct eloop *eloop, const struct timespec *ts) { int n, nn; struct kevent *ke; struct eloop_event *e; unsigned short events; n = _kevent(eloop->fd, NULL, 0, eloop->fds, eloop->nevents, ts); if (n == -1) return -1; for (nn = n, ke = eloop->fds; nn != 0; nn--, ke++) { if (eloop->cleared || eloop->exitnow) break; e = (struct eloop_event *)ke->udata; if (ke->filter == EVFILT_SIGNAL) { eloop->signal_cb((int)ke->ident, eloop->signal_cb_ctx); continue; } if (ke->filter == EVFILT_READ) events = ELE_READ; else if (ke->filter == EVFILT_WRITE) events = ELE_WRITE; #ifdef EVFILT_PROCDESC else if (ke->filter == EVFILT_PROCDESC && ke->fflags & NOTE_EXIT) /* exit status is in ke->data. * As we default to using ppoll anyway * we don't have to do anything with it right now. */ events = ELE_HANGUP; #endif else continue; /* assert? */ if (ke->flags & EV_EOF) events |= ELE_HANGUP; if (ke->flags & EV_ERROR) events |= ELE_ERROR; e->cb(e->cb_arg, events); } return n; } #elif defined(HAVE_EPOLL) static int eloop_run_epoll(struct eloop *eloop, const struct timespec *ts, const sigset_t *signals) { int timeout, n, nn; struct epoll_event *epe; struct eloop_event *e; unsigned short events; if (ts != NULL) { if (ts->tv_sec > INT_MAX / 1000 || (ts->tv_sec == INT_MAX / 1000 && ((ts->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000))) timeout = INT_MAX; else timeout = (int)(ts->tv_sec * 1000 + (ts->tv_nsec + 999999) / 1000000); } else timeout = -1; if (signals != NULL) n = epoll_pwait(eloop->fd, eloop->fds, (int)eloop->nevents, timeout, signals); else n = epoll_wait(eloop->fd, eloop->fds, (int)eloop->nevents, timeout); if (n == -1) return -1; for (nn = n, epe = eloop->fds; nn != 0; nn--, epe++) { if (eloop->cleared || eloop->exitnow) break; e = (struct eloop_event *)epe->data.ptr; if (e->fd == -1) continue; events = 0; if (epe->events & EPOLLIN) events |= ELE_READ; if (epe->events & EPOLLOUT) events |= ELE_WRITE; if (epe->events & EPOLLHUP) events |= ELE_HANGUP; if (epe->events & EPOLLERR) events |= ELE_ERROR; e->cb(e->cb_arg, events); } return n; } #elif defined(HAVE_PPOLL) static int eloop_run_ppoll(struct eloop *eloop, const struct timespec *ts, const sigset_t *signals) { int n, nn; struct eloop_event *e; struct pollfd *pfd; unsigned short events; n = ppoll(eloop->fds, (nfds_t)eloop->nevents, ts, signals); if (n == -1 || n == 0) return n; nn = n; TAILQ_FOREACH(e, &eloop->events, next) { if (eloop->cleared || eloop->exitnow) break; /* Skip freshly added events */ if ((pfd = e->pollfd) == NULL) continue; if (e->pollfd->revents) { nn--; events = 0; if (pfd->revents & POLLIN) events |= ELE_READ; if (pfd->revents & POLLOUT) events |= ELE_WRITE; if (pfd->revents & POLLHUP) events |= ELE_HANGUP; if (pfd->revents & POLLERR) events |= ELE_ERROR; if (pfd->revents & POLLNVAL) events |= ELE_NVAL; if (events) e->cb(e->cb_arg, events); } if (nn == 0) break; } return n; } #elif defined(HAVE_PSELECT) static int eloop_run_pselect(struct eloop *eloop, const struct timespec *ts, const sigset_t *sigmask) { fd_set read_fds, write_fds; int maxfd, n; struct eloop_event *e; unsigned short events; FD_ZERO(&read_fds); FD_ZERO(&write_fds); maxfd = 0; TAILQ_FOREACH(e, &eloop->events, next) { if (e->fd == -1) continue; if (e->events & ELE_READ) { FD_SET(e->fd, &read_fds); if (e->fd > maxfd) maxfd = e->fd; } if (e->events & ELE_WRITE) { FD_SET(e->fd, &write_fds); if (e->fd > maxfd) maxfd = e->fd; } } /* except_fd's is for STREAMS devices which we don't use. */ n = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask); if (n == -1 || n == 0) return n; TAILQ_FOREACH(e, &eloop->events, next) { if (eloop->cleared || eloop->exitnow) break; if (e->fd == -1) continue; events = 0; if (FD_ISSET(e->fd, &read_fds)) events |= ELE_READ; if (FD_ISSET(e->fd, &write_fds)) events |= ELE_WRITE; if (events) e->cb(e->cb_arg, events); } return n; } #endif int eloop_start(struct eloop *eloop, sigset_t *signals) { int error; struct eloop_timeout *t; struct timespec ts, *tsp; assert(eloop != NULL); #ifdef HAVE_KQUEUE UNUSED(signals); #endif for (;;) { if (eloop->exitnow) break; #ifndef HAVE_KQUEUE if (_eloop_nsig != 0) { int n = _eloop_sig[--_eloop_nsig]; if (eloop->signal_cb != NULL) eloop->signal_cb(n, eloop->signal_cb_ctx); continue; } #endif 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; eloop->cleared = false; if (eloop->events_need_setup) eloop_event_setup_fds(eloop); #if defined(HAVE_KQUEUE) UNUSED(signals); error = eloop_run_kqueue(eloop, tsp); #elif defined(HAVE_EPOLL) error = eloop_run_epoll(eloop, tsp, signals); #elif defined(HAVE_PPOLL) error = eloop_run_ppoll(eloop, tsp, signals); #elif defined(HAVE_PSELECT) error = eloop_run_pselect(eloop, tsp, signals); #else #error no polling mechanism to run! #endif if (error == -1) { if (errno == EINTR) continue; return -errno; } } return eloop->exitcode; } dhcpcd-10.1.0/src/eloop.h000066400000000000000000000074001470014643500151010ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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; #define ELE_READ 0x0001 #define ELE_WRITE 0x0002 #define ELE_ERROR 0x0100 #define ELE_HANGUP 0x0200 #define ELE_NVAL 0x0400 size_t eloop_event_count(const struct eloop *); int eloop_event_add(struct eloop *, int, unsigned short, void (*)(void *, unsigned short), void *); int eloop_event_delete(struct eloop *, int); unsigned long long eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp, unsigned int *nsp); #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 *); int eloop_signal_set_cb(struct eloop *, const int *, size_t, void (*)(int, void *), void *); int eloop_signal_mask(struct eloop *, sigset_t *oldset); struct eloop * eloop_new(void); void eloop_clear(struct eloop *, ...); void eloop_free(struct eloop *); void eloop_exit(struct eloop *, int); void eloop_enter(struct eloop *); int eloop_forked(struct eloop *); int eloop_open(struct eloop *); int eloop_start(struct eloop *, sigset_t *); #endif dhcpcd-10.1.0/src/genembedc000077500000000000000000000007711470014643500154550ustar00rootroot00000000000000#!/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";' dhcpcd-10.1.0/src/genembedh000077500000000000000000000015451470014643500154620ustar00rootroot00000000000000#!/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 dhcpcd-10.1.0/src/if-bsd.c000066400000000000000000001366411470014643500151340ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * BSD interface driver for dhcpcd * Copyright (c) 2006-2024 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", "epair", /* Virtual patch cable */ "fwe", /* Firewire */ "fwip", /* Firewire */ "tap", "vether", "xvif", /* XEN DOM0 -> guest interface */ NULL }; 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 #ifdef RTM_DESYNC RTM_DESYNC, #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); /* 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. */ #ifdef PRIVSEP_RIGHTS if (priv->pf_inet6_fd != -1 && IN_PRIVSEP(ctx)) ps_rights_limit_ioctl(priv->pf_inet6_fd); #endif #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__); #ifdef PRIVSEP if (ctx->options & DHCPCD_PRIVSEPROOT) { /* We only want to write to this socket, so set * a small as possible buffer size. */ socklen_t smallbuf = 1; if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RCVBUF, &smallbuf, (socklen_t)sizeof(smallbuf)) == -1) logerr("%s: setsockopt(SO_RCVBUF)", __func__); } #endif #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 #if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ priv->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM, 0); if (priv->pf_link_fd == -1) logerr("%s: socket(PF_LINK)", __func__); #endif return 0; } void if_closesockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; priv = (struct priv *)ctx->priv; if (priv == NULL) return; #ifdef INET6 if (priv->pf_inet6_fd != -1) { close(priv->pf_inet6_fd); priv->pf_inet6_fd = -1; } #endif #if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ if (priv->pf_link_fd != -1) { close(priv->pf_link_fd); priv->pf_link_fd = -1; } #endif 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) { struct priv *priv = (struct priv *)ctx->priv; #ifdef PRIVSEP if (ctx->options & DHCPCD_PRIVSEP) return (int)ps_root_ioctllink(ctx, req, data, len); #endif return ioctl(priv->pf_link_fd, req, data, len); } #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; } static int if_sysctl(struct dhcpcd_ctx *ctx, const int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { #if defined(PRIVSEP) && defined(HAVE_CAPSICUM) if (IN_PRIVSEP(ctx)) return (int)ps_root_sysctl(ctx, name, namelen, oldp, oldlenp, newp, newlen); #else UNUSED(ctx); #endif return sysctl(name, namelen, oldp, oldlenp, newp, newlen); } int if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af) { struct rt_msghdr *rtm; int mib[6] = { CTL_NET, PF_ROUTE, 0, af, NET_RT_DUMP, 0 }; size_t bufl; char *buf, *p, *end; struct rt rt, *rtn; if (if_sysctl(ctx, mib, __arraycount(mib), NULL, &bufl, NULL, 0) == -1) return -1; if (bufl == 0) return 0; if ((buf = malloc(bufl)) == NULL) return -1; if (if_sysctl(ctx, mib, __arraycount(mib), buf, &bufl, NULL, 0) == -1) { free(buf); return -1; } end = buf + bufl; for (p = buf; p < end; p += rtm->rtm_msglen) { rtm = (void *)p; if (p + sizeof(*rtm) > end || 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 != NULL) { if (rtm->rtm_pid == ctx->ps_root->psp_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 != NULL) { if (ifam->ifam_pid == ctx->ps_root->psp_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; /* All BSD's set IFF_UP on the interface when adding an address. * But not all BSD's emit this via RTM_IFINFO when they do this ... */ if (ifam->ifam_type == RTM_NEWADDR && !(ifp->flags & IFF_UP)) dhcpcd_handlecarrier(ifp, ifp->carrier, ifp->flags | IFF_UP); 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, __arraycount(mib), NULL, 0, &val, size) == -1) return -1; return 0; } if (sysctl(mib, __arraycount(mib), &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) { #ifdef IPV6CTL_FORWARDING return get_inet6_sysctl(IPV6CTL_FORWARDING); #else return get_inet6_sysctlbyname("net.inet6.ip6.forwarding"); #endif } #ifdef SIOCIFAFATTACH static int if_af_attach(const struct interface *ifp, int af) { struct if_afreq ifar = { .ifar_af = af }; strlcpy(ifar.ifar_name, ifp->name, sizeof(ifar.ifar_name)); 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) { #ifdef ND6_NDI_FLAGS struct priv *priv; int s; struct in6_ndireq nd; int flags; priv = (struct priv *)ifp->ctx->priv; s = priv->pf_inet6_fd; 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; #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 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 /* ND6_NDI_FLAGS */ /* 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 dhcpcd-10.1.0/src/if-linux-wext.c000066400000000000000000000057131470014643500165030ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2009-2024 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 = xsocket(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 } dhcpcd-10.1.0/src/if-linux.c000066400000000000000000001511371470014643500155200ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Linux interface driver for dhcpcd * Copyright (c) 2006-2024 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 #ifndef IFF_DORMANT /* Inlcude this *after* net/if.h so we get IFF_DORMANT */ #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 /* 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))) /* 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_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(__hppa__) "model" #elif defined(__i386__) || defined(__x86_64__) "vendor_id" #elif defined(__ia64__) "vendor" #elif defined(__m68k__) "MMU" #elif defined(__mips__) "system type" #elif defined(__powerpc__) || defined(__powerpc64__) "machine" #elif defined(__riscv) "uarch" #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 #ifdef PRIVSEP if (ctx->options & DHCPCD_PRIVSEPROOT) { ctx->link_fd = -1; goto setup_priv; } #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 #ifdef PRIVSEP setup_priv: #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 = ctx->priv; if (priv == NULL) return; if (priv->route_fd != -1) { close(priv->route_fd); priv->route_fd = -1; } if (priv->generic_fd != -1) { close(priv->generic_fd); priv->generic_fd = -1; } free(priv); ctx->priv = NULL; } 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)); } static int if_carrier_from_flags(unsigned int flags) { #ifdef IFF_LOWER_UP return ((flags & (IFF_LOWER_UP | IFF_RUNNING)) == (IFF_LOWER_UP | IFF_RUNNING)) #ifdef IFF_DORMANT /* * IFF_DORMANT means L1 is up but waiting for an external * event, for example 802.1X * We treat this as DOWN, but then return true for if_roaming() * so that the interface status is persisted. */ && !(flags & IFF_DORMANT) #endif ? LINK_UP : LINK_DOWN; #else return flags & IFF_RUNNING ? LINK_UP : LINK_DOWN; #endif } int if_carrier(struct interface *ifp, __unused const void *ifadata) { return if_carrier_from_flags(ifp->flags); } bool if_roaming(struct interface *ifp) { return #ifdef IFF_DORMANT ifp->flags & IFF_DORMANT || #endif #ifdef IFF_LOWER_UP /* * IFF_DORMANT only occurs for supplicant initiated roaming. * For firmware initiated roaming we don't get IFF_DORMANT. * Seems weird that the driver can't set it though. * We can check that IFF_RUNNING is not set but UP and L1 are * to get the same effect. */ (ifp->flags & (IFF_UP | IFF_LOWER_UP | IFF_RUNNING)) == (IFF_UP | IFF_LOWER_UP) || #endif false; } 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, }; size_t len; struct nlmsghdr *nlm; int r = 0; unsigned int again; bool terminated; recv_again: len = (size_t)recvmsg(fd, &msg, flags); if (len == 0 || (ssize_t)len == -1) 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, 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 != NULL && nlm->nlmsg_pid == (uint32_t)ctx->ps_root->psp_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 *local6 = NULL, *address6 = NULL; 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 != NULL && nlm->nlmsg_pid == (uint32_t)ctx->ps_root->psp_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: for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch (rta->rta_type) { case IFA_ADDRESS: address6 = (struct in6_addr *)RTA_DATA(rta); break; case IFA_LOCAL: local6 = (struct in6_addr *)RTA_DATA(rta); break; } } if (local6 != NULL) address6 = local6; if (address6 == NULL) break; /* should be impossible */ /* Validate RTM_DELADDR really means address deleted * and anything else really means address exists. */ flags = if_addrflags6(ifp, address6, 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, address6, 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, if_carrier_from_flags(ifi->ifi_flags), 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 *)(void *)(((char *)(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_FRAME_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); } struct ifiaddr6 { unsigned int ifa_ifindex; struct in6_addr ifa_addr; uint32_t ifa_flags; bool ifa_found; }; static int _if_addrflags6(__unused struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm) { struct ifiaddr6 *ia = arg; size_t len; struct rtattr *rta; struct ifaddrmsg *ifa; struct in6_addr *local = NULL, *address = NULL; uint32_t *flags = NULL; ifa = NLMSG_DATA(nlm); if (ifa->ifa_index != ia->ifa_ifindex || ifa->ifa_family != AF_INET6) 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_ADDRESS: address = (struct in6_addr *)RTA_DATA(rta); break; case IFA_LOCAL: local = (struct in6_addr *)RTA_DATA(rta); break; case IFA_FLAGS: flags = (uint32_t *)RTA_DATA(rta); break; } } if (local) { if (IN6_ARE_ADDR_EQUAL(&ia->ifa_addr, local)) ia->ifa_found = true; } else if (address) { if (IN6_ARE_ADDR_EQUAL(&ia->ifa_addr, address)) ia->ifa_found = true; } if (flags && ia->ifa_found) memcpy(&ia->ifa_flags, flags, sizeof(ia->ifa_flags)); return 0; } int if_addrflags6(const struct interface *ifp, const struct in6_addr *addr, __unused const char *alias) { struct ifiaddr6 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_INET6, .ifa.ifa_index = ifp->index, }; int error = if_sendnetlink(ifp->ctx, NETLINK_ROUTE, &nlm.hdr, &_if_addrflags6, &ia); if (error == -1) return -1; if (!ia.ifa_found) { errno = ESRCH; return -1; } return (int)ia.ifa_flags; } 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]; /* Modern linux kernels can make a stable private address. * However, a lot of distros ship newer kernel headers than * the kernel itself so we sweep that error under the table * from old kernels and just make them ourself regardless. */ 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) { /* 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); } 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 -1; i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error); if (error != 0 && error != ENOTSUP) return -1; return i; } #endif /* INET6 */ dhcpcd-10.1.0/src/if-options.c000066400000000000000000002077301470014643500160550ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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}, {"arp_persistdefence", no_argument, NULL, O_ARP_PERSISTDEFENCE}, {"request_time", required_argument, NULL, O_REQUEST_TIME}, {"fallback_time", required_argument, NULL, O_FALLBACK_TIME}, {"ipv4ll_time", required_argument, NULL, O_IPV4LL_TIME}, {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]) { /* We know that it must contain '=' due to the above test */ size_t listl = (size_t)(strchr(list[i], '=') - list[i]); if (l == listl && 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 (l > 0) { if ((ssize_t)l == -1) { errno = ENOBUFS; return -1; } if (sbuf == NULL) return (ssize_t)l; 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; p = UNCONST(arg); // Generally it's --waitip=46, but some expect // --waitip="4 6" to work as well. // It's easier to allow it rather than have confusing docs. while (p != NULL && p[0] != '\0') { if (p[0] == '4' || p[1] == '4') ifo->options |= DHCPCD_WAITIP4; if (p[0] == '6' || p[1] == '6') ifo->options |= DHCPCD_WAITIP6; p = strskipwhite(++p); } 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 = strskipwhite(++p); if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { if (p == NULL) { ifo->options &= ~DHCPCD_STATIC; ifo->req_addr.s_addr = INADDR_ANY; break; } 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 (p == NULL) { ifo->req_mask.s_addr = INADDR_ANY; break; } if (parse_addr(&ifo->req_mask, NULL, p) != 0) return -1; } else if (strncmp(arg, "broadcast_address=", strlen("broadcast_address=")) == 0) { if (p == NULL) { ifo->req_brd.s_addr = INADDR_ANY; break; } 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; if (p == NULL) { rt_headclear(&ifo->routes, AF_INET); add_environ(&ifo->config, arg, 1); break; } 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 (p == NULL) { rt_headclear(&ifo->routes, AF_INET); add_environ(&ifo->config, arg, 1); break; } 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) { if (p == NULL) break; ifo->mtu = (unsigned int)strtou(p, NULL, 0, IPV4_MMTU, UINT_MAX, &e); if (e) { logerrx("invalid MTU %s", p); return -1; } } else if (strncmp(arg, "ip6_address=", strlen("ip6_address=")) == 0) { if (p == NULL) { memset(&ifo->req_addr6, 0, sizeof(ifo->req_addr6)); break; } 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, p == NULL ? 1 : 0); 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, "uri") == 0) t |= OT_URI; 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 requires a realm"); 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 requires 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; #ifdef INET6 if (strcmp(arg, "token") == 0) { if (np == NULL) { logerrx("slaac token: no token specified"); return -1; } arg = np; np = strwhite(np); if (np != NULL) { *np++ = '\0'; np = strskipwhite(np); } if (inet_pton(AF_INET6, arg, &ifo->token) != 1) { logerrx("slaac token: invalid token"); return -1; } } #endif 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; case O_ARP_PERSISTDEFENCE: ifo->options |= DHCPCD_ARP_PERSISTDEFENCE; break; case O_REQUEST_TIME: ARG_REQUIRED; ifo->request_time = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerrx("invalid request time: %s", arg); return -1; } break; #ifdef INET case O_FALLBACK_TIME: ARG_REQUIRED; ifo->request_time = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerrx("invalid fallback time: %s", arg); return -1; } break; case O_IPV4LL_TIME: ARG_REQUIRED; ifo->ipv4ll_time = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerrx("invalid ipv4ll time: %s", arg); return -1; } break; #endif 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); } static 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->request_time = DEFAULT_REQUEST; #ifdef INET ifo->fallback_time = DEFAULT_FALLBACK; ifo->ipv4ll_time = DEFAULT_IPV4LL; #endif 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 = xsocket(PF_INET, SOCK_DGRAM, 0); if (skip != -1) { close(skip); default_options |= DHCPCD_IPV4 | DHCPCD_ARP | DHCPCD_DHCP | DHCPCD_IPV4LL; } #endif #ifdef INET6 skip = xsocket(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); } dhcpcd-10.1.0/src/if-options.h000066400000000000000000000225131470014643500160540ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 #define DEFAULT_REQUEST 180 /* secs to request, mirror DHCP6 */ #define DEFAULT_FALLBACK 5 /* secs until fallback */ #define DEFAULT_IPV4LL 5 /* secs until ipv4ll */ #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_ARP_PERSISTDEFENCE (1ULL << 6) #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 #define O_ARP_PERSISTDEFENCE O_BASE + 53 #define O_REQUEST_TIME O_BASE + 54 #define O_FALLBACK_TIME O_BASE + 55 #define O_IPV4LL_TIME O_BASE + 56 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; uint32_t request_time; uint32_t fallback_time; uint32_t ipv4ll_time; 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; #ifdef INET6 struct in6_addr token; #endif struct dhcp_opt *dhcp_override; size_t dhcp_override_len; struct dhcp_opt *nd_override; size_t nd_override_len; struct dhcp_opt *dhcp6_override; size_t dhcp6_override_len; uint32_t vivco_en; struct vivco *vivco; size_t vivco_len; struct dhcp_opt *vivso_override; size_t vivso_override_len; struct auth auth; }; struct if_options *read_config(struct dhcpcd_ctx *, const char *, const char *, const char *); int add_options(struct dhcpcd_ctx *, const char *, struct if_options *, int, char **); void free_dhcp_opt_embenc(struct dhcp_opt *); void free_options(struct dhcpcd_ctx *, struct if_options *); #endif dhcpcd-10.1.0/src/if-sun.c000066400000000000000000001156531470014643500151710ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Solaris interface driver for dhcpcd * Copyright (c) 2016-2024 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 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 = xsocket(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 && priv->pf_inet6_fd != -1) close(priv->pf_inet6_fd); #endif /* each interface should have closed itself */ free(ctx->priv); ctx->priv = NULL; } 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 = xsocket(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 dhcpcd-10.1.0/src/if.c000066400000000000000000000565231470014643500143660ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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->link_fd != -1) { eloop_event_delete(ctx->eloop, ctx->link_fd); close(ctx->link_fd); ctx->link_fd = -1; } if (ctx->pf_inet_fd != -1) { close(ctx->pf_inet_fd); ctx->pf_inet_fd = -1; } if_closesockets_os(ctx); } 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 } } } void if_freeifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifaddrs) { #ifndef PRIVSEP_GETIFADDRS UNUSED(ctx); #endif if (ifaddrs == NULL) return; #ifdef PRIVSEP_GETIFADDRS if (IN_PRIVSEP(ctx)) free(*ifaddrs); else #endif freeifaddrs(*ifaddrs); } 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_getmtu(const struct interface *ifp) { #ifdef __sun return if_mtu_os(ifp); #else struct ifreq ifr = { .ifr_mtu = 0 }; strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); if (pioctl(ifp->ctx, SIOCGIFMTU, &ifr, sizeof(ifr)) == -1) return -1; return ifr.ifr_mtu; #endif } #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; #ifdef DEBUG_FD logerrx("pid %d fd=%d domain=%d type=%d protocol=%d", getpid(), s, domain, type, protocol); #endif #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; #ifdef DEBUG_FD logerrx("pid %d fd[0]=%d fd[1]=%d", getpid(), fd[0], fd[1]); #endif #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 } dhcpcd-10.1.0/src/if.h000066400000000000000000000212501470014643500143600ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 #endif /* Private structures specific to an OS */ #ifdef BSD struct priv { #ifdef INET6 int pf_inet6_fd; #endif #if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ int pf_link_fd; #endif }; #endif #ifdef __linux__ struct priv { int route_fd; int generic_fd; uint32_t route_pid; }; #endif #ifdef __sun struct priv { #ifdef INET6 int pf_inet6_fd; #endif }; #endif #ifdef __sun /* 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_freeifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **); 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_getmtu(const struct interface *); 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); /* used by udev */ 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 dhcpcd-10.1.0/src/ipv4.c000066400000000000000000000542621470014643500146500ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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; } 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); #if defined(ARP) && !defined(KERNEL_RFC5227) arp_ifannounceaddr(ifp, &ia->addr); #endif } 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, *old_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) { ipv4_deladdr(state->addr, 0); rt_build(ifp->ctx, AF_INET); } script_runreason(ifp, state->reason); } else rt_build(ifp->ctx, AF_INET); return NULL; } /* ipv4_dadaddr() will overwrite this, we need it to purge later */ old_ia = state->addr; 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 (old_ia && old_ia->addr.s_addr != lease->addr.s_addr) ipv4_deladdr(old_ia, 0); state->addr = ia; state->added = STATE_ADDED; rt_build(ifp->ctx, AF_INET); 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; ia->addr_flags = addrflags; 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); } dhcpcd-10.1.0/src/ipv4.h000066400000000000000000000122411470014643500146440ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 */ #ifndef IPV4_MMTU #define IPV4_MMTU 68 #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 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 */ dhcpcd-10.1.0/src/ipv4ll.c000066400000000000000000000334761470014643500152040ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 void ipv4ll_start_arp(void *arg); 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; } #ifndef KERNEL_RFC5227 static void ipv4ll_announced_arp(struct arp_state *astate) { struct ipv4ll_state *state = IPV4LL_STATE(astate->iface); state->conflicts = 0; } /* 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; 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 (ia == NULL) { if (ifp->ctx->options & DHCPCD_TEST) { ia = malloc(sizeof(*ia)); if (ia == NULL) { logerr(__func__); return; } ia->iface = ifp; ia->addr = state->pickedaddr; } else if (!(ifp->options->options & DHCPCD_CONFIGURE)) { logwarnx("%s: refusing to add IPv4LL address %s", ifp->name, inet_ntoa(state->pickedaddr)); return; } 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 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); 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_arp, 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_arp(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; 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; } } if (state->running) return; state->running = true; /* 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 if (ifp->options->options & DHCPCD_CONFIGURE) { loginfox("%s: probing for an IPv4LL address", ifp->name); if (repick || state->pickedaddr.s_addr == INADDR_ANY) ipv4ll_pickaddr(ifp); } else { logwarnx("%s: refusing to configure IPv4LL", ifp->name); return; } ipv4ll_start_arp(ifp); } static void ipv4ll_start_arp(void *arg) { struct interface *ifp = arg; #ifdef KERNEL_RFC5227 ipv4ll_not_found(ifp); #else struct ipv4ll_state *state; struct arp_state *astate; state = IPV4LL_STATE(ifp); 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->running = false; if (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; } dhcpcd-10.1.0/src/ipv4ll.h000066400000000000000000000054721470014643500152040ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 running; 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 dhcpcd-10.1.0/src/ipv6.c000066400000000000000000001577651470014643500146660ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 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); } 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; const struct if_options *ifo = ifp->options; 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 (ifo->options & DHCPCD_SLAACPRIVATE) { dad = 0; if (ipv6_makestableprivate(addr, prefix, prefix_len, ifp, &dad) == -1) return -1; return dad; } else if (!IN6_IS_ADDR_UNSPECIFIED(&ifo->token)) { int bytes = prefix_len / NBBY; int bits = prefix_len % NBBY; // Copy the token into the address. *addr = ifo->token; // If we have any dangling bits, just copy that in also. // XXX Can we preserve part of the token still? if (bits != 0) bytes++; // Copy the prefix in. if (bytes > 0) memcpy(addr->s6_addr, prefix->s6_addr, (size_t)bytes); return 0; } 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 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); #endif #ifndef SMALL /* 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 #endif #if !defined(DHCP6) || (!defined(PRIVSEP) && defined(SMALL)) 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; } } } static int ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now) { struct interface *ifp; uint32_t pltime, vltime; int loglevel; struct ipv6_state *state; struct ipv6_addr *ia2; #ifdef __sun /* 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); return 0; } #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 /* Take a copy of the address and add it to our state if * it does not exist. * This is important if route overflow loses the message. */ 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); } 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_PFXDELEGATION) 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_PFXDELEGATION) { 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, unsigned int notflags, 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) { if (ap->flags & notflags) continue; #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; } static struct ipv6_addr * ipv6_ifanyglobal(struct interface *ifp) { struct ipv6_state *state; struct ipv6_addr *ia; state = IPV6_STATE(ifp); if (state == NULL) return NULL; TAILQ_FOREACH(ia, &state->addrs, next) { if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) || IN6_IS_ADDR_LOOPBACK(&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; } struct ipv6_addr * ipv6_anyglobal(struct interface *sifp) { struct ipv6_addr *ia; struct interface *ifp; bool forwarding; ia = ipv6_ifanyglobal(sifp); if (ia != NULL) return ia; /* 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. */ #ifdef PRIVSEP_SYSCTL if (IN_PRIVSEP(sifp->ctx)) forwarding = ps_root_ip6forwarding(sifp->ctx, NULL) > 0; else #endif forwarding = ip6_forwarding(NULL) > 0; if (!forwarding) return NULL; TAILQ_FOREACH(ifp, sifp->ctx->ifaces, next) { if (ifp == sifp) continue; ia = ipv6_ifanyglobal(ifp); if (ia != NULL) 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)) { ia->addr_flags = addrflags; break; } } switch (cmd) { case RTM_DELADDR: if (ia != NULL) { TAILQ_REMOVE(&state->addrs, ia, next); /* 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; ia->addr_flags = addrflags; TAILQ_INSERT_TAIL(&state->addrs, ia, next); } ia->flags &= ~IPV6_AF_STALE; #ifdef IPV6_MANAGETEMPADDR if (ia->addr_flags & IN6_IFF_TEMPORARY) ia->flags |= IPV6_AF_TEMPORARY; #endif #ifdef IPV6_POLLADDRFLAG if ((IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) && ia->addr_flags & IN6_IFF_TENTATIVE) { eloop_timeout_add_msec( ia->iface->ctx->eloop, RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); } #endif break; default: return; } if (ia == NULL) return; if (ia->dadcallback && ((ia->addr_flags & (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) == 0 || ia->addr_flags & IN6_IFF_DUPLICATED)) 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); } } 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) return 0; } 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_PFXDELEGATION)) { 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, 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; int found = 0, alldadcompleted = 1; if (cmd != RTM_NEWADDR && cmd != RTM_DELADDR) { errno = EINVAL; return -1; } 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; } ia->addr_flags = addr->addr_flags; if (cmd == RTM_DELADDR && ia->flags & IPV6_AF_ADDED) logwarnx("%s: pid %d deleted address %s", ia->iface->name, pid, ia->saddr); /* Check DAD. * On Linux we can get IN6_IFF_DUPLICATED via RTM_DELADDR. */ if (((ia->addr_flags & (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) == 0 || ia->addr_flags & IN6_IFF_DUPLICATED) && (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; } if (cmd == RTM_DELADDR) { ia->flags &= ~IPV6_AF_ADDED; ipv6_deletedaddr(ia); if (ia->flags & IPV6_AF_DELEGATED) { TAILQ_REMOVE(addrs, ia, next); ipv6_freeaddr(ia); } } } 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_PFXDELEGATION) { 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_PFXDELEGATION) { 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 routeinfo *rinfo; const struct ipv6_addr *addr; struct in6_addr netmask; if (ctx->ra_routers == NULL) return 0; TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (rap->expired) continue; /* add rfc4191 route information routes */ TAILQ_FOREACH (rinfo, &rap->rinfos, next) { if(rinfo->lifetime == 0) continue; if ((rt = inet6_makeroute(rap->iface, rap)) == NULL) continue; in6_addr_fromprefix(&netmask, rinfo->prefix_len); sa_in6_init(&rt->rt_dest, &rinfo->prefix); sa_in6_init(&rt->rt_netmask, &netmask); sa_in6_init(&rt->rt_gateway, &rap->from); #ifdef HAVE_ROUTE_PREF rt->rt_pref = ipv6nd_rtpref(rinfo->flags); #endif rt_proto_add(routes, rt); } /* add subnet routes */ 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->flags); #endif rt_proto_add(routes, rt); } } /* add default route */ 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->flags); #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 *ia; struct rt *rt; TAILQ_FOREACH(ifp, ctx->ifaces, next) { d6_state = D6_CSTATE(ifp); if (d6_state == NULL) continue; // Don't test the actual state as we could // be between states with still valid routes TAILQ_FOREACH(ia, &d6_state->addrs, next) { if (dstate == DH6S_DELEGATED) { // Reject route won't have IPV6_AF_ADDED if (!(ia->flags & IPV6_AF_PFXDELEGATION)) continue; } else if (!(ia->flags & IPV6_AF_ADDED)) continue; rt = inet6_makeprefix(ifp, NULL, ia); 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; } dhcpcd-10.1.0/src/ipv6.h000066400000000000000000000241141470014643500146500ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 #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_PFXDELEGATION (1U << 6) #define IPV6_AF_DELEGATED (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) #define IPV6_AF_ADVERTISED (1U << 16) #ifdef IPV6_MANAGETEMPADDR #define IPV6_AF_TEMPORARY (1U << 17) #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, unsigned 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 */ dhcpcd-10.1.0/src/ipv6nd.c000066400000000000000000001463501470014643500151740ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - IPv6 ND handling * Copyright (c) 2006-2024 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_RI #define ND_OPT_RI 24 struct nd_opt_ri { /* Route Information option RFC4191 */ uint8_t nd_opt_ri_type; uint8_t nd_opt_ri_len; uint8_t nd_opt_ri_prefixlen; uint8_t nd_opt_ri_flags_reserved; uint32_t nd_opt_ri_lifetime; struct in6_addr nd_opt_ri_prefix; }; __CTASSERT(sizeof(struct nd_opt_ri) == 24); #define OPT_RI_FLAG_PREFERENCE(flags) ((flags & 0x18) >> 3) #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_dnssl) == 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 *, unsigned short); static struct routeinfo *routeinfo_findalloc(struct ra *, const struct in6_addr *, uint8_t); static void routeinfohead_free(struct routeinfohead *); /* * 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; } if (eloop_event_add(ifp->ctx->eloop, fd, ELE_READ, ipv6nd_handledata, ifp) == -1) { close(fd); return -1; } state->nd_fd = fd; 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; } if (eloop_event_add(ctx->eloop, ctx->nd_fd, ELE_READ, ipv6nd_handledata, ctx) == -1) logerr("%s: eloop_event_add", __func__); } 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); } 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(uint8_t flags) { switch (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__, 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->flags) <= ipv6nd_rtpref(ra2->flags)) 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, 0, NULL); routeinfohead_free(&rap->rinfos); 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); } } } } 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; struct nd_opt_ri ri; struct routeinfo *rinfo; 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); TAILQ_INIT(&rap->rinfos); 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 (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: { uint32_t vltime, pltime; 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; } vltime = ntohl(pi.nd_opt_pi_valid_time); pltime = ntohl(pi.nd_opt_pi_preferred_time); if (pltime > vltime) { 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; ia->created = ia->acquired = rap->acquired; ia->prefix_vltime = vltime; ia->prefix_pltime = pltime; if (flags & IPV6_AF_AUTOCONF) ia->dadcallback = ipv6nd_dadcallback; 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 { uint32_t rmtime; /* * RFC 4862 5.5.3.e * Don't terminate existing connections. * This means that to actually remove the * existing prefix, the RA needs to stop * broadcasting the prefix and just let it * expire in 2 hours. * It might want to broadcast it to reduce * the vltime if it was greater than 2 hours * to start with/ */ ia->prefix_pltime = pltime; if (ia->prefix_vltime) { uint32_t elapsed; elapsed = (uint32_t)eloop_timespec_diff( &rap->acquired, &ia->acquired, NULL); rmtime = ia->prefix_vltime - elapsed; if (rmtime > ia->prefix_vltime) rmtime = 0; } else rmtime = 0; if (vltime > MIN_EXTENDED_VLTIME || vltime > rmtime) ia->prefix_vltime = vltime; else if (rmtime <= MIN_EXTENDED_VLTIME) /* No SEND support from RFC 3971 so * leave vltime alone */ ia->prefix_vltime = rmtime; else ia->prefix_vltime = MIN_EXTENDED_VLTIME; /* Ensure pltime still fits */ if (pltime < ia->prefix_vltime) ia->prefix_pltime = pltime; else ia->prefix_pltime = ia->prefix_vltime; ia->flags |= flags; ia->flags &= ~IPV6_AF_STALE; ia->acquired = rap->acquired; #ifdef IPV6_MANAGETEMPADDR new_ia = false; #endif } 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; case ND_OPT_RI: if (ndo.nd_opt_len > 3) { logmessage(loglevel, "%s: invalid route info option", ifp->name); break; } memset(&ri, 0, sizeof(ri)); memcpy(&ri, p, olen); /* may be smaller than sizeof(ri), pad with zero */ if(ri.nd_opt_ri_prefixlen > 128) { logmessage(loglevel, "%s: invalid route info prefix length", ifp->name); break; } /* rfc4191 3.1 - RI for ::/0 applies to default route */ if(ri.nd_opt_ri_prefixlen == 0) { rap->lifetime = ntohl(ri.nd_opt_ri_lifetime); /* Update preference leaving other flags intact */ rap->flags = ((rap->flags & (~ (unsigned int)ND_RA_FLAG_RTPREF_MASK)) | ri.nd_opt_ri_flags_reserved) & 0xff; break; } /* Update existing route info instead of rebuilding all routes so that previously announced but now absent routes can stay alive. To kill a route early, an RI with lifetime=0 needs to be received (rfc4191 3.1)*/ rinfo = routeinfo_findalloc(rap, &ri.nd_opt_ri_prefix, ri.nd_opt_ri_prefixlen); if(rinfo == NULL) { logerr(__func__); break; } /* Update/initialize other route info params */ rinfo->flags = ri.nd_opt_ri_flags_reserved; rinfo->lifetime = ntohl(ri.nd_opt_ri_lifetime); rinfo->acquired = rap->acquired; 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_rap && rap->lifetime == 0 && old_lifetime != 0) logwarnx("%s: %s: no longer a default router (lifetime = 0)", ifp->name, rap->sfrom); if (new_data && !has_address && rap->lifetime && ifp->options->options & DHCPCD_GATEWAY && !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->flags); 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; struct routeinfo *rinfo, *rinfob; 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; /* lifetime may be set to infinite by rfc4191 route information */ if (rap->lifetime && rap->lifetime != ND6_INFINITE_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; } } /* Expire route information */ TAILQ_FOREACH_SAFE(rinfo, &rap->rinfos, next, rinfob) { if (rinfo->lifetime == ND6_INFINITE_LIFETIME && !rap->doexpire) continue; elapsed = (uint32_t)eloop_timespec_diff(&now, &rinfo->acquired, NULL); if (elapsed >= rinfo->lifetime || rap->doexpire) { logwarnx("%s: expired route %s", rap->iface->name, rinfo->sprefix); TAILQ_REMOVE(&rap->rinfos, rinfo, next); } } /* 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, unsigned short events) { 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 if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); len = recvmsg(fd, &msg, 0); if (len == -1) { logerr(__func__); return; } iov.iov_len = (size_t)len; ipv6nd_recvmsg(ctx, &msg); } static void ipv6nd_startrs2(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); } static void ipv6nd_startrs1(void *arg) { struct interface *ifp = arg; unsigned int delay; if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) { ipv6nd_startrs2(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_startrs2, ifp); } void ipv6nd_startrs(struct interface *ifp) { if (ipv6_linklocal(ifp) == NULL) { logdebugx("%s: " "delaying IPv6 Router Solicitation for LL address", ifp->name); ipv6_addlinklocalcallback(ifp, ipv6nd_startrs1, ifp); } else ipv6nd_startrs1(ifp); } static struct routeinfo *routeinfo_findalloc(struct ra *rap, const struct in6_addr *prefix, uint8_t prefix_len) { struct routeinfo *ri; char buf[INET6_ADDRSTRLEN]; const char *p; TAILQ_FOREACH(ri, &rap->rinfos, next) { if (ri->prefix_len == prefix_len && IN6_ARE_ADDR_EQUAL(&ri->prefix, prefix)) return ri; } ri = malloc(sizeof(struct routeinfo)); if (ri == NULL) return NULL; memcpy(&ri->prefix, prefix, sizeof(ri->prefix)); ri->prefix_len = prefix_len; p = inet_ntop(AF_INET6, prefix, buf, sizeof(buf)); if (p) snprintf(ri->sprefix, sizeof(ri->sprefix), "%s/%d", p, prefix_len); else ri->sprefix[0] = '\0'; TAILQ_INSERT_TAIL(&rap->rinfos, ri, next); return ri; } static void routeinfohead_free(struct routeinfohead *head) { struct routeinfo *ri; while ((ri = TAILQ_FIRST(head))) { TAILQ_REMOVE(head, ri, next); free(ri); } } dhcpcd-10.1.0/src/ipv6nd.h000066400000000000000000000111631470014643500151720ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - IPv6 ND handling * Copyright (c) 2006-2024 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" /* rfc4191 */ struct routeinfo { TAILQ_ENTRY(routeinfo) next; struct in6_addr prefix; uint8_t prefix_len; uint32_t lifetime; uint8_t flags; struct timespec acquired; char sprefix[INET6_ADDRSTRLEN]; }; TAILQ_HEAD(routeinfohead, routeinfo); 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; uint8_t flags; uint32_t lifetime; uint32_t reachable; uint32_t retrans; uint32_t mtu; uint8_t hoplimit; struct ipv6_addrhead addrs; struct routeinfohead rinfos; 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 */ #define MIN_EXTENDED_VLTIME 7200 /* seconds */ int ipv6nd_open(bool); #ifdef __sun int ipv6nd_openif(struct interface *); #endif void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *); int ipv6nd_rtpref(uint8_t); 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 */ dhcpcd-10.1.0/src/logerr.c000066400000000000000000000240531470014643500152530ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * logerr: errx with logging * Copyright (c) 2006-2024 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; if (fd != -1) closelog(); #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 } dhcpcd-10.1.0/src/logerr.h000066400000000000000000000076731470014643500152710ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * logerr: errx with logging * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/privsep-bpf.c000066400000000000000000000221321470014643500162120ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation BPF Initiator * Copyright (c) 2006-2024 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" /* We expect to have open 3 SEQPACKET and one RAW fd */ static void ps_bpf_recvbpf(void *arg, unsigned short events) { 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, }; if (!(events & (ELE_READ | ELE_ERROR))) logerrx("%s: unexpected event 0x%04x", __func__, events); 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, unsigned short events) { struct ps_process *psp = arg; if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, ps_bpf_recvmsgcb, arg) == -1) logerr(__func__); } static int ps_bpf_start_bpf(struct ps_process *psp) { 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); #ifdef DEBUG_FD logdebugx("pid %d bpf_fd=%d", getpid(), psp->psp_bpf->bpf_fd); #endif 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, ELE_READ, 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; struct in_addr *ia = &psm->ps_id.psi_addr.psa_in_addr; const char *addr; 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; } if (ia->s_addr == INADDR_ANY) addr = NULL; else addr = inet_ntoa(*ia); snprintf(psp->psp_name, sizeof(psp->psp_name), "BPF %s%s%s", psp->psp_protostr, addr != NULL ? " " : "", addr != NULL ? addr : ""); start = ps_startprocess(psp, ps_bpf_recvmsg, NULL, 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 %s on PID %d", psp->psp_ifname, psp->psp_name, psp->psp_pid); 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, PS_ROOT_FD(ctx), &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); } dhcpcd-10.1.0/src/privsep-bpf.h000066400000000000000000000044061470014643500162230ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/privsep-bsd.c000066400000000000000000000232121470014643500162130ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, BSD driver * Copyright (c) 2006-2024 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 /* 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 #include "dhcpcd.h" #include "if.h" #include "logerr.h" #include "privsep.h" static ssize_t ps_root_doioctldom(struct dhcpcd_ctx *ctx, int domain, unsigned long req, void *data, size_t len) { #if defined(INET6) || (defined(SIOCALIFADDR) && defined(IFLR_ACTIVE)) struct priv *priv = (struct priv *)ctx->priv; #endif int s; switch(domain) { #ifdef INET case PF_INET: s = ctx->pf_inet_fd; break; #endif #ifdef INET6 case PF_INET6: s = priv->pf_inet6_fd; break; #endif #if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ case PF_LINK: s = priv->pf_link_fd; break; #endif default: errno = EPFNOSUPPORT; return -1; } /* 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; } return ioctl(s, req, data, len); } static ssize_t ps_root_doroute(struct dhcpcd_ctx *ctx, void *data, size_t len) { return write(ctx->link_fd, data, len); } #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) static ssize_t ps_root_doindirectioctl(struct dhcpcd_ctx *ctx, 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(ctx, PF_INET, req, &ifr, sizeof(ifr)); } #endif #ifdef HAVE_PLEDGE static ssize_t ps_root_doifignoregroup(struct dhcpcd_ctx *ctx, void *data, size_t len) { if (len == 0 || ((const char *)data)[len - 1] != '\0') { errno = EINVAL; return -1; } return if_ignoregroup(ctx->pf_inet_fd, data); } #endif #ifdef HAVE_CAPSICUM static ssize_t ps_root_dosysctl(unsigned long flags, void *data, size_t len, void **rdata, size_t *rlen) { char *p = data, *e = p + len; int name[10]; unsigned int namelen; void *oldp; size_t *oldlenp, oldlen, nlen; void *newp; size_t newlen; int err; if (sizeof(namelen) >= len) { errno = EINVAL; return -1; } memcpy(&namelen, p, sizeof(namelen)); p += sizeof(namelen); nlen = sizeof(*name) * namelen; if (namelen > __arraycount(name)) { errno = ENOBUFS; return -1; } if (p + nlen > e) { errno = EINVAL; return -1; } memcpy(name, p, nlen); p += nlen; if (p + sizeof(oldlen) > e) { errno = EINVAL; return -1; } memcpy(&oldlen, p, sizeof(oldlen)); p += sizeof(oldlen); if (p + sizeof(newlen) > e) { errno = EINVAL; return -1; } memcpy(&newlen, p, sizeof(newlen)); p += sizeof(newlen); if (p + newlen > e) { errno = EINVAL; return -1; } newp = newlen ? p : NULL; if (flags & PS_SYSCTL_OLEN) { *rlen = sizeof(oldlen) + oldlen; *rdata = malloc(*rlen); if (*rdata == NULL) return -1; oldlenp = (size_t *)*rdata; *oldlenp = oldlen; if (flags & PS_SYSCTL_ODATA) oldp = (char *)*rdata + sizeof(oldlen); else oldp = NULL; } else { oldlenp = NULL; oldp = NULL; } err = sysctl(name, namelen, oldp, oldlenp, newp, newlen); return err; } #endif ssize_t ps_root_os(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg, void **rdata, size_t *rlen, bool *free_rdata) { 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(ctx, PF_LINK, psm->ps_flags, data, len); break; case PS_IOCTL6: err = ps_root_doioctldom(ctx, PF_INET6, psm->ps_flags, data, len); break; case PS_ROUTE: return ps_root_doroute(ctx, data, len); #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) case PS_IOCTLINDIRECT: err = ps_root_doindirectioctl(ctx, psm->ps_flags, data, len); break; #endif #ifdef HAVE_PLEDGE case PS_IFIGNOREGRP: return ps_root_doifignoregroup(ctx, data, len); #endif #ifdef HAVE_CAPSICUM case PS_SYSCTL: *free_rdata = true; return ps_root_dosysctl(psm->ps_flags, data, len, rdata, rlen); #else UNUSED(free_rdata); #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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), PS_IOCTLINDIRECT, request, buf, IFNAMSIZ + len) == -1) return -1; return ps_root_readerror(ctx, data, len); } #endif #ifdef HAVE_PLEDGE ssize_t ps_root_ifignoregroup(struct dhcpcd_ctx *ctx, const char *ifname) { if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_IFIGNOREGRP, 0, ifname, strlen(ifname) + 1) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } #endif #ifdef HAVE_CAPSICUM ssize_t ps_root_sysctl(struct dhcpcd_ctx *ctx, const int *name, unsigned int namelen, void *oldp, size_t *oldlenp, const void *newp, size_t newlen) { char buf[PS_BUFLEN], *p = buf; unsigned long flags = 0; size_t olen = (oldp && oldlenp) ? *oldlenp : 0, nolen; if (sizeof(namelen) + (sizeof(*name) * namelen) + sizeof(oldlenp) + sizeof(newlen) + newlen > sizeof(buf)) { errno = ENOBUFS; return -1; } if (oldlenp) flags |= PS_SYSCTL_OLEN; if (oldp) flags |= PS_SYSCTL_ODATA; memcpy(p, &namelen, sizeof(namelen)); p += sizeof(namelen); memcpy(p, name, sizeof(*name) * namelen); p += sizeof(*name) * namelen; memcpy(p, &olen, sizeof(olen)); p += sizeof(olen); memcpy(p, &newlen, sizeof(newlen)); p += sizeof(newlen); if (newlen) { memcpy(p, newp, newlen); p += newlen; } if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_SYSCTL, flags, buf, (size_t)(p - buf)) == -1) return -1; if (ps_root_readerror(ctx, buf, sizeof(buf)) == -1) return -1; p = buf; memcpy(&nolen, p, sizeof(nolen)); p += sizeof(nolen); if (oldlenp) { *oldlenp = nolen; if (oldp && nolen <= olen) memcpy(oldp, p, nolen); } return 0; } #endif dhcpcd-10.1.0/src/privsep-control.c000066400000000000000000000171401470014643500171260ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, control proxy * Copyright (c) 2006-2024 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" /* We expect to have open 2 SEQPACKET, 2 STREAM and 2 file STREAM fds */ static int ps_ctl_startcb(struct ps_process *psp) { struct dhcpcd_ctx *ctx = psp->psp_ctx; 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; } return control_start(ctx, ctx->options & DHCPCD_MANAGER ? NULL : *ctx->ifv, af); } static void ps_ctl_recvmsg(void *arg, unsigned short events) { struct ps_process *psp = arg; if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, NULL, NULL) == -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_ctl->psp_work_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: ctx->ps_control_client = NULL; break; default: errno = ENOTSUP; return -1; } return 0; } static void ps_ctl_dodispatch(void *arg, unsigned short events) { struct ps_process *psp = arg; if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, ps_ctl_dispatch, psp->psp_ctx) == -1) logerr(__func__); } static void ps_ctl_recv(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; char buf[BUFSIZ]; ssize_t len; if (!(events & (ELE_READ | ELE_HANGUP))) logerrx("%s: unexpected event 0x%04x", __func__, events); if (events & ELE_READ) { len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf)); if (len == -1) logerr("%s: read", __func__); else if (len == 0) // FIXME: Why does this happen? ; else if (ctx->ps_control_client == NULL) logerrx("%s: clientfd #%d disconnected (len=%zd)", __func__, ctx->ps_ctl->psp_work_fd, len); else { 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, unsigned short events) { struct dhcpcd_ctx *ctx = arg; char buf[BUFSIZ]; ssize_t len; struct fd_list *fd; if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); len = read(ctx->ps_control->fd, buf, sizeof(buf)); if (len == -1) { 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) { struct ps_id id = { .psi_ifindex = 0, .psi_cmd = PS_CTL, }; struct ps_process *psp; int work_fd[2], listen_fd[2]; pid_t pid; if_closesockets(ctx); if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, work_fd) == -1 || xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1) return -1; #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fdpair(work_fd) == -1 || ps_rights_limit_fdpair(listen_fd) == -1) return -1; #endif psp = ctx->ps_ctl = ps_newprocess(ctx, &id); strlcpy(psp->psp_name, "control proxy", sizeof(psp->psp_name)); pid = ps_startprocess(psp, ps_ctl_recvmsg, ps_ctl_dodispatch, ps_ctl_startcb, NULL, PSF_DROPPRIVS); if (pid == -1) return -1; else if (pid != 0) { psp->psp_work_fd = work_fd[0]; close(work_fd[1]); close(listen_fd[1]); ctx->ps_control = control_new(ctx, listen_fd[0], FD_SENDLEN | FD_LISTEN); if (ctx->ps_control == NULL) return -1; return pid; } close(work_fd[0]); close(listen_fd[0]); psp->psp_work_fd = work_fd[1]; if (eloop_event_add(ctx->eloop, psp->psp_work_fd, ELE_READ, ps_ctl_recv, ctx) == -1) return -1; ctx->ps_control = control_new(ctx, listen_fd[1], 0); if (ctx->ps_control == NULL) return -1; if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, ELE_READ, ps_ctl_listen, ctx) == -1) return -1; ps_entersandbox("stdio inet", NULL); return 0; } int ps_ctl_stop(struct dhcpcd_ctx *ctx) { return ps_stopprocess(ctx->ps_ctl); } 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_ctl->psp_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_ctl->psp_fd, PS_CTL_EOF, 0, NULL, 0); } dhcpcd-10.1.0/src/privsep-control.h000066400000000000000000000034701470014643500171340ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/privsep-inet.c000066400000000000000000000371051470014643500164100ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, network proxy * Copyright (c) 2006-2024 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" /* We expect to have open 2 SEQPACKET, 1 udp, 1 udp6 and 1 raw6 fds */ #ifdef INET static void ps_inet_recvbootp(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; if (ps_recvmsg(ctx->udp_rfd, events, PS_BOOTP, ctx->ps_inet->psp_fd) == -1) logerr(__func__); } #endif #ifdef INET6 static void ps_inet_recvra(void *arg, unsigned short events) { #ifdef __sun struct interface *ifp = arg; struct rs_state *state = RS_STATE(ifp); struct dhcpcd_ctx *ctx = ifp->ctx; if (ps_recvmsg(state->nd_fd, events, PS_ND, ctx->ps_inet->psp_fd) == -1) logerr(__func__); #else struct dhcpcd_ctx *ctx = arg; if (ps_recvmsg(ctx->nd_fd, events, PS_ND, ctx->ps_inet->psp_fd) == -1) logerr(__func__); #endif } #endif #ifdef DHCP6 static void ps_inet_recvdhcp6(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; if (ps_recvmsg(ctx->dhcp6_rfd, events, PS_DHCP6, ctx->ps_inet->psp_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(struct ps_process *psp) { struct dhcpcd_ctx *ctx = psp->psp_ctx; int ret = 0; if (ctx->options & DHCPCD_MANAGER) setproctitle("[network proxy]"); else setproctitle("[network proxy] %s%s%s", ctx->ifc != 0 ? 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, ELE_READ, 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, ELE_READ, 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, ELE_READ, 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, unsigned short events) { struct ps_process *psp = arg; /* Receive shutdown */ if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, 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, unsigned short events) { struct ps_process *psp = arg; if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, ps_inet_dispatch, psp->psp_ctx) == -1) logerr(__func__); } pid_t ps_inet_start(struct dhcpcd_ctx *ctx) { struct ps_id id = { .psi_ifindex = 0, .psi_cmd = PS_INET, }; struct ps_process *psp; pid_t pid; psp = ctx->ps_inet = ps_newprocess(ctx, &id); if (psp == NULL) return -1; strlcpy(psp->psp_name, "network proxy", sizeof(psp->psp_name)); pid = ps_startprocess(psp, ps_inet_recvmsg, ps_inet_dodispatch, 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_stopprocess(ctx->ps_inet); } #ifdef INET static void ps_inet_recvinbootp(void *arg, unsigned short events) { struct ps_process *psp = arg; if (ps_recvmsg(psp->psp_work_fd, events, PS_BOOTP, psp->psp_ctx->ps_data_fd) == -1) logerr(__func__); } static int ps_inet_listenin(struct ps_process *psp) { 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("[%s proxy] %s", psp->psp_protostr, 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, ELE_READ, ps_inet_recvinbootp, psp) == -1) { logerr("%s: eloop_event_add DHCP", __func__); return -1; } 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_work_fd, PS_ND, psp->psp_ctx->ps_data_fd) == -1) logerr(__func__); } static int ps_inet_listennd(struct ps_process *psp) { 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; } return 0; } #endif #ifdef DHCP6 static void ps_inet_recvin6dhcp6(void *arg, unsigned short events) { struct ps_process *psp = arg; if (ps_recvmsg(psp->psp_work_fd, events, PS_DHCP6, psp->psp_ctx->ps_data_fd) == -1) logerr(__func__); } static int ps_inet_listenin6(struct ps_process *psp) { 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("[%s proxy] %s", psp->psp_protostr, 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, ELE_READ, ps_inet_recvin6dhcp6, psp) == -1) { logerr("%s: eloop_event_add DHCP", __func__); return -1; } return 0; } #endif static void ps_inet_recvmsgpsp(void *arg, unsigned short events) { struct ps_process *psp = arg; /* Receive shutdown. */ if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, 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)(struct ps_process *); pid_t start; struct ps_addr *psa = &psm->ps_id.psi_addr; void *ia; char buf[INET_MAX_ADDRSTRLEN]; 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; } 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; switch (cmd) { #ifdef INET case PS_BOOTP: start_func = ps_inet_listenin; psp->psp_protostr = "BOOTP"; ia = &psa->psa_in_addr; break; #endif #ifdef INET6 #ifdef __sun case PS_ND: start_func = ps_inet_listennd; psp->psp_protostr = "ND"; ia = &psa->psa_in6_addr; break; #endif #ifdef DHCP6 case PS_DHCP6: start_func = ps_inet_listenin6; psp->psp_protostr = "DHCP6"; ia = &psa->psa_in6_addr; break; #endif #endif default: logerrx("%s: unknown command %x", __func__, psm->ps_cmd); errno = ENOTSUP; return -1; } snprintf(psp->psp_name, sizeof(psp->psp_name), "%s proxy %s", psp->psp_protostr, inet_ntop(psa->psa_family, ia, buf, sizeof(buf))); start = ps_startprocess(psp, ps_inet_recvmsgpsp, NULL, start_func, NULL, PSF_DROPPRIVS); switch (start) { case -1: ps_freeprocess(psp); return -1; case 0: ps_entersandbox("stdio", NULL); break; default: logdebugx("%s: spawned %s on PID %d", psp->psp_ifname, psp->psp_name, psp->psp_pid); 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_family = AF_INET, .psi_addr.psa_in_addr = ia->addr, }, }; return ps_sendpsmmsg(ctx, PS_ROOT_FD(ctx), &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) { struct dhcpcd_ctx *ctx = ifp->ctx; return ps_sendmsg(ctx, PS_ROOT_FD(ctx), 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, .psi_addr.psa_family = AF_INET6, }, }; return ps_sendpsmmsg(ctx, PS_ROOT_FD(ctx), &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) { struct dhcpcd_ctx *ctx = ifp->ctx; return ps_sendmsg(ctx, PS_ROOT_FD(ctx), 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_family = AF_INET6, .psi_addr.psa_in6_addr = ia->addr, }, }; return ps_sendpsmmsg(ctx, PS_ROOT_FD(ctx), &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) { struct dhcpcd_ctx *ctx = ifp->ctx; return ps_sendmsg(ctx, PS_ROOT_FD(ctx), PS_DHCP6, 0, msg); } #endif /* DHCP6 */ #endif /* INET6 */ dhcpcd-10.1.0/src/privsep-inet.h000066400000000000000000000045571470014643500164220ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/privsep-linux.c000066400000000000000000000337611470014643500166140ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, Linux driver * Copyright (c) 2006-2024 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 /* For TCGETS */ #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(struct dhcpcd_ctx *ctx, int protocol, struct msghdr *msg) { struct priv *priv = (struct priv *)ctx->priv; int s; unsigned char buf[16 * 1024]; struct iovec riov = { .iov_base = buf, .iov_len = sizeof(buf), }; switch(protocol) { case NETLINK_GENERIC: s = priv->generic_fd; break; case NETLINK_ROUTE: s = priv->route_fd; break; default: errno = EPFNOSUPPORT; return -1; } if (sendmsg(s, msg, 0) == -1) return -1; return if_getnetlink(NULL, &riov, s, 0, NULL, NULL); } ssize_t ps_root_os(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg, __unused void **rdata, __unused size_t *rlen, __unused bool *free_data) { switch (psm->ps_cmd) { case PS_ROUTE: return ps_root_dosendnetlink(ctx, (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, PS_ROOT_FD(ctx), PS_ROUTE, (unsigned long)protocol, msg) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } #ifdef DISABLE_SECCOMP #warning SECCOMP has been disabled #else #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(__ARCV3__) # if defined(__ARC64__) # if (BYTE_ORDER == LITTLE_ENDIAN) # define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCV3 # else # define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARCV3BE # 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(__loongarch__) # if defined(__LP64__) # define SECCOMP_AUDIT_ARCH AUDIT_ARCH_LOONGARCH64 # else # define SECCOMP_AUDIT_ARCH AUDIT_ARCH_LOONGARCH32 # endif #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__) # if (BYTE_ORDER == LITTLE_ENDIAN) # define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PPC64LE # else # define SECCOMP_AUDIT_ARCH AUDIT_ARCH_PPC64 # endif #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_gettime32 SECCOMP_ALLOW(__NR_clock_gettime32), #endif #ifdef __NR_clock_gettime64 SECCOMP_ALLOW(__NR_clock_gettime64), #endif #ifdef __NR_close SECCOMP_ALLOW(__NR_close), #endif #ifdef __NR_dup2 SECCOMP_ALLOW(__NR_dup2), // daemonising dups stderr to stdin(/dev/null) #endif #ifdef __NR_dup3 SECCOMP_ALLOW(__NR_dup3), #endif #ifdef __NR_epoll_ctl SECCOMP_ALLOW(__NR_epoll_ctl), #endif #ifdef __NR_epoll_wait SECCOMP_ALLOW(__NR_epoll_wait), #endif #ifdef __NR_epoll_pwait SECCOMP_ALLOW(__NR_epoll_pwait), #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_getrandom SECCOMP_ALLOW(__NR_getrandom), #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), /* dumping leases on musl requires this */ SECCOMP_ALLOW_ARG(__NR_ioctl, 1, TIOCGWINSZ), /* 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_madvise /* needed for musl */ SECCOMP_ALLOW(__NR_madvise), #endif #ifdef __NR_mmap SECCOMP_ALLOW(__NR_mmap), #endif #ifdef __NR_mmap2 SECCOMP_ALLOW(__NR_mmap2), #endif #ifdef __NR_munmap SECCOMP_ALLOW(__NR_munmap), #endif #ifdef __NR_newfstatat SECCOMP_ALLOW(__NR_newfstatat), #endif #ifdef __NR_ppoll SECCOMP_ALLOW(__NR_ppoll), #endif #ifdef __NR_ppoll_time64 SECCOMP_ALLOW(__NR_ppoll_time64), #endif #ifdef __NR_pselect6 SECCOMP_ALLOW(__NR_pselect6), #endif #ifdef __NR_pselect6_time64 SECCOMP_ALLOW(__NR_pselect6_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_statx SECCOMP_ALLOW(__NR_statx), #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; } #endif /* !DISABLE_SECCOMP */ dhcpcd-10.1.0/src/privsep-root.c000066400000000000000000000664531470014643500164440ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, privileged proxy * Copyright (c) 2006-2024 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, unsigned short events) { 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; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); #define PSR_ERROR(e) \ do { \ psr_error->psr_result = -1; \ psr_error->psr_errno = (e); \ goto out; \ } while (0 /* CONSTCOND */) len = readv(PS_ROOT_FD(ctx), 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, }; int fd = PS_ROOT_FD(ctx); if (eloop_event_add(ctx->ps_eloop, fd, ELE_READ, ps_root_readerrorcb, &psr_ctx) == -1) return -1; eloop_enter(ctx->ps_eloop); eloop_start(ctx->ps_eloop, &ctx->sigset); eloop_event_delete(ctx->ps_eloop, fd); errno = psr_ctx.psr_error.psr_errno; return psr_ctx.psr_error.psr_result; } #ifdef PRIVSEP_GETIFADDRS static void ps_root_mreaderrorcb(void *arg, unsigned short events) { 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; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); len = recv(PS_ROOT_FD(ctx), 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(PS_ROOT_FD(ctx), 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, }; int fd = PS_ROOT_FD(ctx); if (eloop_event_add(ctx->ps_eloop, fd, ELE_READ, ps_root_mreaderrorcb, &psr_ctx) == -1) return -1; eloop_enter(ctx->ps_eloop); eloop_start(ctx->ps_eloop, &ctx->sigset); eloop_event_delete(ctx->ps_eloop, fd); 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 }, }; ssize_t err; int fd = PS_ROOT_FD(ctx); #ifdef PRIVSEP_DEBUG logdebugx("%s: result %zd errno %d", __func__, result, errno); #endif err = writev(fd, iov, __arraycount(iov)); /* Error sending the message? Try sending the error of sending. */ if (err == -1) { logerr("%s: result=%zd, data=%p, len=%zu", __func__, result, data, len); psr.psr_result = err; psr.psr_errno = errno; iov[1].iov_base = NULL; iov[1].iov_len = 0; err = writev(fd, iov, __arraycount(iov)); } return err; } 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 = xsocket(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) { return ps_stopprocess(psp); } else if (psm->ps_cmd & PS_START) { /* Process has already started .... */ logdebugx("%s%sprocess %s already started on pid %d", psp->psp_ifname, psp->psp_ifname[0] != '\0' ? ": " : "", psp->psp_name, psp->psp_pid); 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); 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_STOPPROCS: ctx->options |= DHCPCD_EXITING; TAILQ_FOREACH(psp, &ctx->ps_processes, next) { if (psp != ctx->ps_root) ps_stopprocess(psp); } err = ps_stopwait(ctx); 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(PRIVSEP_SYSCTL) 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(ctx, psm, msg, &rdata, &rlen, &free_rdata); 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, unsigned short events) { struct ps_process *psp = arg; if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, ps_root_recvmsgcb, psp->psp_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(struct ps_process *psp) { struct dhcpcd_ctx *ctx = psp->psp_ctx; 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->options |= DHCPCD_PRIVSEPROOT; if (if_opensockets(ctx) == -1) logerr("%s: if_opensockets", __func__); /* 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 connected. 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; } void ps_root_signalcb(int sig, void *arg) { struct dhcpcd_ctx *ctx = arg; int status; pid_t pid; const char *ifname, *name; struct ps_process *psp; if (sig != SIGCHLD) return; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { psp = ps_findprocesspid(ctx, pid); if (psp != NULL) { ifname = psp->psp_ifname; name = psp->psp_name; } else { /* Ignore logging the double fork */ if (ctx->options & DHCPCD_LAUNCHER) continue; ifname = ""; name = "unknown process"; } if (WIFEXITED(status) && WEXITSTATUS(status) != 0) logerrx("%s%s%s exited unexpectedly from PID %d," " code=%d", ifname, ifname[0] != '\0' ? ": " : "", name, pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) logerrx("%s%s%s exited unexpectedly from PID %d," " signal=%s", ifname, ifname[0] != '\0' ? ": " : "", name, pid, strsignal(WTERMSIG(status))); else logdebugx("%s%s%s exited from PID %d", ifname, ifname[0] != '\0' ? ": " : "", name, pid); if (psp != NULL) ps_freeprocess(psp); } if (!(ctx->options & DHCPCD_EXITING)) return; if (!(ps_waitforprocs(ctx))) eloop_exit(ctx->ps_eloop, EXIT_SUCCESS); } 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, unsigned short events) { struct dhcpcd_ctx *ctx = arg; if (ps_recvpsmsg(ctx, ctx->ps_data_fd, events, ps_root_dispatchcb, ctx) == -1) logerr(__func__); } static void ps_root_log(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); if (logreadfd(ctx->ps_log_root_fd) == -1) logerr(__func__); } pid_t ps_root_start(struct dhcpcd_ctx *ctx) { struct ps_id id = { .psi_ifindex = 0, .psi_cmd = PS_ROOT, }; struct ps_process *psp; int logfd[2], datafd[2]; pid_t pid; if (xsocketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CXNB, 0, logfd) == -1) return -1; #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fdpair(logfd) == -1) return -1; #endif if (xsocketpair(AF_UNIX, SOCK_SEQPACKET | 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 psp = ctx->ps_root = ps_newprocess(ctx, &id); strlcpy(psp->psp_name, "privileged proxy", sizeof(psp->psp_name)); pid = ps_startprocess(psp, ps_root_recvmsg, NULL, ps_root_startcb, ps_root_signalcb, PSF_ELOOP); if (pid == 0) { ctx->ps_log_fd = logfd[0]; /* Keep open to pass to processes */ ctx->ps_log_root_fd = logfd[1]; if (eloop_event_add(ctx->eloop, ctx->ps_log_root_fd, ELE_READ, ps_root_log, ctx) == -1) return -1; 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, ELE_READ, ps_root_dispatch, ctx) == -1) return 1; return pid; } void ps_root_close(struct dhcpcd_ctx *ctx) { if_closesockets(ctx); #ifdef INET if (ctx->udp_wfd != -1) { close(ctx->udp_wfd); ctx->udp_wfd = -1; } #endif #ifdef INET6 if (ctx->nd_fd != -1) { close(ctx->nd_fd); ctx->nd_fd = -1; } #endif #ifdef DHCP6 if (ctx->dhcp6_wfd != -1) { close(ctx->dhcp6_wfd); ctx->dhcp6_wfd = -1; } #endif } int ps_root_stop(struct dhcpcd_ctx *ctx) { struct ps_process *psp = ctx->ps_root; if (!(ctx->options & DHCPCD_PRIVSEP) || ctx->eloop == NULL) return 0; /* If we are the root process then remove the pidfile */ if (ctx->options & DHCPCD_PRIVSEPROOT && !(ctx->options & DHCPCD_TEST)) { if (unlink(ctx->pidfile) == -1) logerr("%s: unlink: %s", __func__, ctx->pidfile); } /* Only the manager process gets past this point. */ if (ctx->options & DHCPCD_FORKED) return 0; /* We cannot log the root process exited before we * log dhcpcd exits because the latter requires the former. * So we just log the intent to exit. * Even sending this will be a race to exit. */ if (psp) { logdebugx("%s%s%s will exit from PID %d", psp->psp_ifname, psp->psp_ifname[0] != '\0' ? ": " : "", psp->psp_name, psp->psp_pid); if (ps_stopprocess(psp) == -1) return -1; } /* else the root process has already exited :( */ return ps_stopwait(ctx); } ssize_t ps_root_stopprocesses(struct dhcpcd_ctx *ctx) { if (!(IN_PRIVSEP_SE(ctx))) return 0; if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_STOPPROCS, 0, NULL, 0) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } ssize_t ps_root_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) { if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), 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) { int fd = PS_ROOT_FD(ctx); #ifdef IOCTL_REQUEST_TYPE unsigned long ulreq = 0; memcpy(&ulreq, &req, sizeof(req)); if (ps_sendcmd(ctx, fd, PS_IOCTL, ulreq, data, len) == -1) return -1; #else if (ps_sendcmd(ctx, 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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 *)(void *)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 #ifdef PRIVSEP_SYSCTL ssize_t ps_root_ip6forwarding(struct dhcpcd_ctx *ctx, const char *ifname) { if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), PS_DEV_LISTENING, 0, NULL, 0) == -1) return -1; return (int)ps_root_readerror(ctx, NULL, 0); } #endif dhcpcd-10.1.0/src/privsep-root.h000066400000000000000000000067771470014643500164540ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd * Copyright (c) 2006-2024 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); void ps_root_close(struct dhcpcd_ctx *ctx); int ps_root_stop(struct dhcpcd_ctx *ctx); void ps_root_signalcb(int, void *); 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); ssize_t ps_root_stopprocesses(struct dhcpcd_ctx *); 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 dhcpcd_ctx *, struct ps_msghdr *, struct msghdr *, void **, size_t *, bool *); #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 *); ssize_t ps_root_sysctl(struct dhcpcd_ctx *, const int *, unsigned int, void *, size_t *, const void *, size_t); #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 dhcpcd-10.1.0/src/privsep-sun.c000066400000000000000000000062621470014643500162560ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, Solaris driver * Copyright (c) 2006-2024 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 = xsocket(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 = xsocket(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, __unused bool *free_rdata) { 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, PS_ROOT_FD(ctx), 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, PS_ROOT_FD(ctx), PS_ROUTE, 0, data, len) == -1) return -1; return ps_root_readerror(ctx, data, len); } dhcpcd-10.1.0/src/privsep.c000066400000000000000000000712301470014643500154500ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd * Copyright (c) 2006-2024 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 #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 }; /* Prohibit new files, sockets, etc */ /* * If poll(2) is called with nfds>RLIMIT_NOFILE then it returns EINVAL. * We don't know the final value of nfds at this point *easily*. * Sadly, this is a POSIX limitation and most platforms adhere to it. * However, some are not that strict and are whitelisted below. * Also, if we're not using poll then we can be restrictive. * * For the non whitelisted platforms there should be a sandbox to * fallback to where we don't allow new files, etc: * Linux:seccomp, FreeBSD:capsicum, OpenBSD:pledge * Solaris users are sadly out of luck on both counts. */ #if defined(__NetBSD__) || defined(__DragonFly__) || \ defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) /* The control proxy *does* need to create new fd's via accept(2). */ if (ctx->ps_ctl == NULL || ctx->ps_ctl->psp_pid != getpid()) { 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 && 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() { const int iebadf = CAPH_IGNORE_EBADF; int error = 0; if (caph_limit_stream(STDIN_FILENO, CAPH_READ | iebadf) == -1) error = -1; if (caph_limit_stream(STDOUT_FILENO, CAPH_WRITE | iebadf) == -1) error = -1; if (caph_limit_stream(STDERR_FILENO, CAPH_WRITE | iebadf) == -1) error = -1; return error; } #endif #ifdef HAVE_CAPSICUM static void ps_processhangup(void *arg, unsigned short events) { struct ps_process *psp = arg; struct dhcpcd_ctx *ctx = psp->psp_ctx; if (!(events & ELE_HANGUP)) logerrx("%s: unexpected event 0x%04x", __func__, events); logdebugx("%s%s%s exited from PID %d", psp->psp_ifname, psp->psp_ifname[0] != '\0' ? ": " : "", psp->psp_name, psp->psp_pid); ps_freeprocess(psp); if (!(ctx->options & DHCPCD_EXITING)) return; if (!(ps_waitforprocs(ctx))) eloop_exit(ctx->ps_eloop, EXIT_SUCCESS); } #endif pid_t ps_startprocess(struct ps_process *psp, void (*recv_msg)(void *, unsigned short), void (*recv_unpriv_msg)(void *, unsigned short), int (*callback)(struct ps_process *), void (*signal_cb)(int, void *), unsigned int flags) { struct dhcpcd_ctx *ctx = psp->psp_ctx; int fd[2]; pid_t pid; if (xsocketpair(AF_UNIX, SOCK_SEQPACKET | 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 #ifdef HAVE_CAPSICUM pid = pdfork(&psp->psp_pfd, PD_CLOEXEC); #else pid = fork(); #endif switch (pid) { case -1: #ifdef HAVE_CAPSICUM logerr("pdfork"); #else logerr("fork"); #endif return -1; case 0: psp->psp_pid = getpid(); psp->psp_fd = fd[1]; close(fd[0]); break; default: psp->psp_pid = pid; psp->psp_fd = fd[0]; close(fd[1]); if (recv_unpriv_msg == NULL) ; else if (eloop_event_add(ctx->eloop, psp->psp_fd, ELE_READ, recv_unpriv_msg, psp) == -1) { logerr("%s: eloop_event_add fd %d", __func__, psp->psp_fd); return -1; } #ifdef HAVE_CAPSICUM if (eloop_event_add(ctx->eloop, psp->psp_pfd, ELE_HANGUP, ps_processhangup, psp) == -1) { logerr("%s: eloop_event_add pfd %d", __func__, psp->psp_pfd); return -1; } #endif psp->psp_started = true; return pid; } /* If we are not the root process, close un-needed stuff. */ if (ctx->ps_root != psp) { ps_root_close(ctx); #ifdef PLUGIN_DEV dev_stop(ctx); #endif } ctx->options |= DHCPCD_FORKED; if (ctx->ps_log_fd != -1) logsetfd(ctx->ps_log_fd); #ifdef DEBUG_FD logerrx("pid %d log_fd=%d data_fd=%d psp_fd=%d", getpid(), ctx->ps_log_fd, ctx->ps_data_fd, psp->psp_fd); #endif eloop_clear(ctx->eloop, -1); eloop_forked(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; } if (ctx->fork_fd != -1) { /* Already removed from eloop thanks to above clear. */ close(ctx->fork_fd); ctx->fork_fd = -1; } /* This process has no need of the blocking inner eloop. */ if (!(flags & PSF_ELOOP)) { eloop_free(ctx->ps_eloop); ctx->ps_eloop = NULL; } else eloop_forked(ctx->ps_eloop); pidfile_clean(); ps_freeprocesses(ctx, psp); if (ctx->ps_root != psp) { ctx->options &= ~DHCPCD_PRIVSEPROOT; ctx->ps_root = NULL; if (ctx->ps_log_root_fd != -1) { /* Already removed from eloop thanks to above clear. */ close(ctx->ps_log_root_fd); ctx->ps_log_root_fd = -1; } #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_stdio() == -1) { logerr("ps_rights_limit_stdio"); goto errexit; } #endif } if (eloop_event_add(ctx->eloop, psp->psp_fd, ELE_READ, recv_msg, psp) == -1) { logerr("%d %s: eloop_event_add XX fd %d", getpid(), __func__, psp->psp_fd); goto errexit; } if (callback(psp) == -1) goto errexit; if (flags & PSF_DROPPRIVS) ps_dropprivs(ctx); psp->psp_started = true; return 0; errexit: if (psp->psp_fd != -1) { close(psp->psp_fd); psp->psp_fd = -1; } eloop_exit(ctx->eloop, EXIT_FAILURE); return -1; } void ps_process_timeout(void *arg) { struct dhcpcd_ctx *ctx = arg; logerrx("%s: timed out", __func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } int ps_stopprocess(struct ps_process *psp) { int err = 0; if (psp == NULL) return 0; psp->psp_started = false; #ifdef PRIVSEP_DEBUG logdebugx("%s: me=%d pid=%d fd=%d %s", __func__, getpid(), psp->psp_pid, psp->psp_fd, psp->psp_name); #endif if (psp->psp_fd != -1) { eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd); #if 0 if (ps_sendcmd(psp->psp_ctx, psp->psp_fd, PS_STOP, 0, NULL, 0) == -1) { logerr("%d %d %s %s", getpid(), psp->psp_pid, psp->psp_name, __func__); err = -1; } shutdown(psp->psp_fd, SHUT_WR); #else if (shutdown(psp->psp_fd, SHUT_WR) == -1) { logerr(__func__); err = -1; } #endif } /* Don't wait for the process as it may not respond to the shutdown * request. We'll reap the process on receipt of SIGCHLD where we * also close the fd. */ return err; } int ps_start(struct dhcpcd_ctx *ctx) { pid_t pid; TAILQ_INIT(&ctx->ps_processes); /* We need an inner eloop to block with. */ if ((ctx->ps_eloop = eloop_new()) == NULL) return -1; eloop_signal_set_cb(ctx->ps_eloop, dhcpcd_signals, dhcpcd_signals_len, dhcpcd_signal_cb, ctx); 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"; // There is no need to use unveil(2) because we are in an empty chroot // This is encouraged by Theo de Raadt himself: // https://www.mail-archive.com/misc@openbsd.org/msg171655.html 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 == NULL) { 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() == -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; if (ctx->ps_ctl != NULL) { r = ps_ctl_stop(ctx); if (r != 0) ret = r; } if (ctx->ps_inet != NULL) { r = ps_inet_stop(ctx); if (r != 0) ret = r; } if (ctx->ps_root != NULL) { if (ps_root_stopprocesses(ctx) == -1) ret = -1; } return ret; } bool ps_waitforprocs(struct dhcpcd_ctx *ctx) { struct ps_process *psp = TAILQ_FIRST(&ctx->ps_processes); if (psp == NULL) return false; /* Different processes */ if (psp != TAILQ_LAST(&ctx->ps_processes, ps_process_head)) return true; return !psp->psp_started; } int ps_stopwait(struct dhcpcd_ctx *ctx) { int error = EXIT_SUCCESS; if (ctx->ps_eloop == NULL || !ps_waitforprocs(ctx)) return 0; ctx->options |= DHCPCD_EXITING; if (eloop_timeout_add_sec(ctx->ps_eloop, PS_PROCESS_TIMEOUT, ps_process_timeout, ctx) == -1) logerr("%s: eloop_timeout_add_sec", __func__); eloop_enter(ctx->ps_eloop); #ifdef HAVE_CAPSICUM struct ps_process *psp; TAILQ_FOREACH(psp, &ctx->ps_processes, next) { if (psp->psp_pfd == -1) continue; if (eloop_event_add(ctx->ps_eloop, psp->psp_pfd, ELE_HANGUP, ps_processhangup, psp) == -1) logerr("%s: eloop_event_add pfd %d", __func__, psp->psp_pfd); } #endif error = eloop_start(ctx->ps_eloop, &ctx->sigset); if (error != EXIT_SUCCESS) logerr("%s: eloop_start", __func__); eloop_timeout_delete(ctx->ps_eloop, ps_process_timeout, ctx); return error; } void ps_freeprocess(struct ps_process *psp) { struct dhcpcd_ctx *ctx = psp->psp_ctx; TAILQ_REMOVE(&ctx->ps_processes, psp, next); if (psp->psp_fd != -1) { eloop_event_delete(ctx->eloop, psp->psp_fd); close(psp->psp_fd); } if (psp->psp_work_fd != -1) { eloop_event_delete(ctx->eloop, psp->psp_work_fd); close(psp->psp_work_fd); } #ifdef HAVE_CAPSICUM if (psp->psp_pfd != -1) { eloop_event_delete(ctx->eloop, psp->psp_pfd); if (ctx->ps_eloop != NULL) eloop_event_delete(ctx->ps_eloop, psp->psp_pfd); close(psp->psp_pfd); } #endif if (ctx->ps_root == psp) ctx->ps_root = NULL; if (ctx->ps_inet == psp) ctx->ps_inet = NULL; if (ctx->ps_ctl == psp) ctx->ps_ctl = NULL; #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 *ppsp, *psp; bool stop; if (ctx->ps_root != NULL) ppsp = ctx->ps_root; else if (ctx->ps_ctl != NULL) ppsp = ctx->ps_ctl; else ppsp = NULL; if (ppsp != NULL) stop = ppsp->psp_pid == getpid(); else stop = false; while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) { if (stop && psp != ppsp) ps_stopprocess(psp); 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) { 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(int rfd, unsigned short events, 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; if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); len = recvmsg(rfd, &msg, 0); if (len == -1) { logerr("%s: recvmsg", __func__); return len; } iov[0].iov_len = (size_t)len; len = ps_sendcmdmsg(wfd, cmd, &msg); if (len == -1) logerr("%s: ps_sendcmdmsg", __func__); return len; } ssize_t ps_daemonised(struct dhcpcd_ctx *ctx) { struct ps_process *psp; ssize_t err = 0; dhcpcd_daemonised(ctx); /* Echo the message to all processes */ TAILQ_FOREACH(psp, &ctx->ps_processes, next) { if (psp->psp_pid == getpid()) continue; if (ps_sendcmd(psp->psp_ctx, psp->psp_fd, PS_DAEMONISED, 0, NULL, 0) == -1) err = -1; } return err; } ssize_t ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd, unsigned short events, 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; if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); len = read(fd, &psm, sizeof(psm)); #ifdef PRIVSEP_DEBUG logdebugx("%s: fd=%d %zd", __func__, fd, 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; } else if (psm.psm_hdr.ps_cmd == PS_DAEMONISED) { ps_daemonised(ctx); return 0; } } if (stop) { ctx->options |= DHCPCD_EXITING; #ifdef PRIVSEP_DEBUG logdebugx("process %d stopping", getpid()); #endif ps_free(ctx); 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 (!(psp->psp_started)) continue; if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0) return psp; } errno = ESRCH; return NULL; } struct ps_process * ps_findprocesspid(struct dhcpcd_ctx *ctx, pid_t pid) { struct ps_process *psp; TAILQ_FOREACH(psp, &ctx->ps_processes, next) { if (psp->psp_pid == pid) 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_fd = -1; psp->psp_work_fd = -1; #ifdef HAVE_CAPSICUM psp->psp_pfd = -1; #endif if (!(ctx->options & DHCPCD_MANAGER)) strlcpy(psp->psp_ifname, ctx->ifv[0], sizeof(psp->psp_ifname)); 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); } } dhcpcd-10.1.0/src/privsep.h000066400000000000000000000166101470014643500154560ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd * Copyright (c) 2006-2024 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 #define PSF_ELOOP 0x02 /* 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 #define PS_STOPPROCS 0x0021 #define PS_DAEMONISED 0x0022 /* Domains */ #define PS_ROOT 0x0101 #define PS_INET 0x0102 #define PS_CONTROL 0x0103 /* BSD Commands */ #define PS_IOCTLLINK 0x0201 #define PS_IOCTL6 0x0202 #define PS_IOCTLINDIRECT 0x0203 #define PS_IP6FORWARDING 0x0204 #define PS_GETIFADDRS 0x0205 #define PS_IFIGNOREGRP 0x0206 #define PS_SYSCTL 0x0207 /* 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 /* Sysctl Needs (via flags) */ #define PS_SYSCTL_OLEN 0x0001 #define PS_SYSCTL_ODATA 0x0002 /* 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))) #define PSP_NAMESIZE 16 + INET_MAX_ADDRSTRLEN /* 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) #define PS_PROCESS_TIMEOUT 5 /* seconds to stop all processes */ #ifdef PRIVSEP # ifdef HAVE_CAPSICUM # define PRIVSEP_RIGHTS # endif /* Pledge and Capsicum deny nearly all sysctls. * Linux needs directory access to sysctls. */ # if defined(HAVE_CAPSICUM) ||defined(HAVE_PLEDGE) || defined(__linux__) # define PRIVSEP_SYSCTL # endif #endif #define PS_ROOT_FD(ctx) ((ctx)->ps_root ? (ctx)->ps_root->psp_fd : -1) #if !defined(DISABLE_SECCOMP) && defined(__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]; char psp_name[PSP_NAMESIZE]; uint16_t psp_proto; const char *psp_protostr; bool psp_started; #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 #ifdef HAVE_CAPSICUM int psp_pfd; #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_stopwait(struct dhcpcd_ctx *); int ps_entersandbox(const char *, const char **); int ps_managersandbox(struct dhcpcd_ctx *, const char *); ssize_t ps_daemonised(struct dhcpcd_ctx *); 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(int, unsigned short, uint16_t, int); ssize_t ps_recvpsmsg(struct dhcpcd_ctx *, int, unsigned short, 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_startprocess(struct ps_process *, void (*recv_msg)(void *, unsigned short), void (*recv_unpriv_msg)(void *, unsigned short), int (*callback)(struct ps_process *), void (*)(int, void *), unsigned int); int ps_stopprocess(struct ps_process *); struct ps_process *ps_findprocess(struct dhcpcd_ctx *, struct ps_id *); struct ps_process *ps_findprocesspid(struct dhcpcd_ctx *, pid_t); struct ps_process *ps_newprocess(struct dhcpcd_ctx *, struct ps_id *); bool ps_waitforprocs(struct dhcpcd_ctx *ctx); void ps_process_timeout(void *); void ps_freeprocess(struct ps_process *); void ps_freeprocesses(struct dhcpcd_ctx *, struct ps_process *); #endif dhcpcd-10.1.0/src/route.c000066400000000000000000000450451470014643500151230ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - route management * Copyright (c) 2006-2024 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 (if_initrt(ctx, &kroutes, af) != 0) logerr("%s: if_initrt", __func__); 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); } dhcpcd-10.1.0/src/route.h000066400000000000000000000113621470014643500151230ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - route management * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/src/sa.c000066400000000000000000000241021470014643500143570ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Socket Address handling for dhcpcd * Copyright (c) 2015-2024 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; } static void ipbytes_fromprefix(uint8_t *ap, int prefix, int max_prefix) { int bytes, bits, i; bytes = prefix / NBBY; bits = prefix % NBBY; 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; } void in6_addr_fromprefix(struct in6_addr *addr, int prefix) { ipbytes_fromprefix((uint8_t *)addr, prefix, 128); } int sa_fromprefix(struct sockaddr *sa, int prefix) { uint8_t *ap; int max_prefix; 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; } ap = (uint8_t *)sa + sa_addroffset(sa); ipbytes_fromprefix(ap, prefix, max_prefix); #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 dhcpcd-10.1.0/src/sa.h000066400000000000000000000054171470014643500143740ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Socket Address handling for dhcpcd * Copyright (c) 2015-2024 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); void in6_addr_fromprefix(struct in6_addr *, 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 dhcpcd-10.1.0/src/script.c000066400000000000000000000422201470014643500152610ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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_status(const char *script, int status) { if (WIFEXITED(status)) { if (WEXITSTATUS(status)) logerrx("%s: %s: WEXITSTATUS %d", __func__, script, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) logerrx("%s: %s: %s", __func__, script, strsignal(WTERMSIG(status))); return WEXITSTATUS(status); } static int script_run(struct dhcpcd_ctx *ctx, char **argv) { pid_t pid; int status; pid = script_exec(argv, ctx->script_env); if (pid == -1) { logerr("%s: %s", __func__, argv[0]); return -1; } else if (pid == 0) return 0; /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { logerr("%s: waitpid", __func__); status = 0; break; } } return script_status(argv[0], 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 (IN_PRIVSEP(ctx)) { ssize_t err; err = ps_root_script(ctx, ctx->script_buf, (size_t)buflen); if (err == -1) logerr(__func__); else script_status(ctx->script, (int)err); 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; } dhcpcd-10.1.0/src/script.h000066400000000000000000000035321470014643500152710ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2024 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 dhcpcd-10.1.0/tests/000077500000000000000000000000001470014643500141645ustar00rootroot00000000000000dhcpcd-10.1.0/tests/Makefile000066400000000000000000000006371470014643500156320ustar00rootroot00000000000000SUBDIRS= 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 dhcpcd-10.1.0/tests/crypt/000077500000000000000000000000001470014643500153255ustar00rootroot00000000000000dhcpcd-10.1.0/tests/crypt/.gitignore000066400000000000000000000000111470014643500173050ustar00rootroot00000000000000run-test dhcpcd-10.1.0/tests/crypt/GNUmakefile000066400000000000000000000003021470014643500173720ustar00rootroot00000000000000# 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 dhcpcd-10.1.0/tests/crypt/Makefile000066400000000000000000000012121470014643500167610ustar00rootroot00000000000000TOP= ../.. include ${TOP}/iconfig.mk PROG= run-test SRCS= run-test.c SRCS+= test_hmac_md5.c test_sha256.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} dhcpcd-10.1.0/tests/crypt/README.md000066400000000000000000000004521470014643500166050ustar00rootroot00000000000000# 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. dhcpcd-10.1.0/tests/crypt/run-test.c000066400000000000000000000027721470014643500172620ustar00rootroot00000000000000/* * 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; if (test_sha256()) r = -1; return r; } dhcpcd-10.1.0/tests/crypt/test.h000066400000000000000000000027051470014643500164610ustar00rootroot00000000000000/* * 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); int test_sha256(void); #endif dhcpcd-10.1.0/tests/crypt/test_hmac_md5.c000066400000000000000000000124051470014643500202070ustar00rootroot00000000000000/* * 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; } dhcpcd-10.1.0/tests/crypt/test_sha256.c000066400000000000000000000075241470014643500175500ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2023 Tobias Heider * 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 SHA2_H #include SHA2_H #endif # ifndef SHA256_DIGEST_LENGTH # define SHA256_DIGEST_LENGTH 32 # endif static void print_md(FILE *stream, const uint8_t *md) { int i; fprintf(stream, "digest = 0x"); for (i = 0; i < SHA256_DIGEST_LENGTH; i++) fprintf(stream, "%02x", *md++); fprintf(stream, "\n"); } static void test_md(const uint8_t *md, const uint8_t *tst) { print_md(stdout, md); if (memcmp(md, tst, SHA256_DIGEST_LENGTH) == 0) return; fprintf(stderr, "FAILED!\nExpected\t\t\t"); print_md(stderr, tst); exit(EXIT_FAILURE); } static void sha256_test1(void) { const uint8_t text[] = "Hi There"; const uint8_t expect[SHA256_DIGEST_LENGTH] = { 0xcc, 0x6d, 0x58, 0x96, 0xd7, 0x70, 0x10, 0x1e, 0xf0, 0x28, 0x0c, 0x94, 0x3a, 0x2d, 0x3c, 0x3f, 0x24, 0xcd ,0x5b, 0x11, 0x46, 0x4a, 0x51, 0x86, 0xda, 0xf7, 0xa2, 0x38, 0x47, 0x71, 0x62, 0xac }; uint8_t digest[SHA256_DIGEST_LENGTH]; SHA256_CTX ctx; printf ("SHA256 Test 1:\t\t"); SHA256_Init(&ctx); SHA256_Update(&ctx, text, 8); SHA256_Final(digest, &ctx); test_md(digest, expect); } static void sha256_test2(void) { const uint8_t text[] = "what do ya want for nothing?"; const uint8_t expect[SHA256_DIGEST_LENGTH] = { 0xb3, 0x81, 0xe7, 0xfe, 0xc6, 0x53, 0xfc, 0x3a, 0xb9, 0xb1, 0x78, 0x27, 0x23, 0x66, 0xb8, 0xac, 0x87, 0xfe, 0xd8, 0xd3, 0x1c, 0xb2, 0x5e, 0xd1, 0xd0, 0xe1, 0xf3, 0x31, 0x86, 0x44, 0xc8, 0x9c, }; uint8_t digest[SHA256_DIGEST_LENGTH]; SHA256_CTX ctx; printf ("SHA256 Test 2:\t\t"); SHA256_Init(&ctx); SHA256_Update(&ctx, text, 28); SHA256_Final(digest, &ctx); test_md(digest, expect); } static void sha256_test3(void) { const uint8_t expect[SHA256_DIGEST_LENGTH] = { 0x5c, 0xf6, 0x18, 0xb5, 0xb6, 0xd3, 0x8b, 0xd1, 0x6c, 0x2e, 0x55, 0x8e, 0xef, 0x4d, 0x4b, 0x6d, 0x52, 0x82, 0x84, 0x54, 0x7f, 0xd4, 0xa0, 0x9d, 0xa2, 0xab, 0xb6, 0xf0, 0x98, 0xec, 0x61, 0x93, }; uint8_t digest[SHA256_DIGEST_LENGTH]; uint8_t text[50]; int i; SHA256_CTX ctx; printf ("SHA256 Test 3:\t\t"); for (i = 0; i < 50; i++) text[i] = 0xdd; SHA256_Init(&ctx); SHA256_Update(&ctx, text, 50); SHA256_Final(digest, &ctx); test_md(digest, expect); } int test_sha256(void) { printf ("Starting SHA256 tests...\n\n"); sha256_test1(); sha256_test2(); sha256_test3(); printf("\nAll tests pass.\n"); return 0; } dhcpcd-10.1.0/tests/eloop-bench/000077500000000000000000000000001470014643500163575ustar00rootroot00000000000000dhcpcd-10.1.0/tests/eloop-bench/.gitignore000066400000000000000000000000141470014643500203420ustar00rootroot00000000000000eloop-bench dhcpcd-10.1.0/tests/eloop-bench/Makefile000066400000000000000000000015231470014643500200200ustar00rootroot00000000000000TOP= ../.. 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} ${LDADD} test: ${PROG} ./${PROG} dhcpcd-10.1.0/tests/eloop-bench/README.md000066400000000000000000000041201470014643500176330ustar00rootroot00000000000000# 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. dhcpcd-10.1.0/tests/eloop-bench/eloop-bench.c000066400000000000000000000120611470014643500207160ustar00rootroot00000000000000/* * eloop benchmark * Copyright (c) 2006-2024 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, unsigned short events) { struct pipe *p = arg; unsigned char buf[1]; if (events != ELE_READ) warn("%s: unexpected events 0x%04x", __func__, events); 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], ELE_READ, 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); }