pax_global_header00006660000000000000000000000064134216271710014516gustar00rootroot0000000000000052 comment=1cec7d4a17cf245afbf0165f7258b64b2675f576 dhcpcd5-7.1.0/000077500000000000000000000000001342162717100130355ustar00rootroot00000000000000dhcpcd5-7.1.0/.gitignore000066400000000000000000000006121342162717100150240ustar00rootroot00000000000000# 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/50-ypbind # Ignore distribution dhcpcd*.xz* # Ignore swap files *.swp # Ignore Coverity cov-int dhcpcd5-7.1.0/BUILDING.md000066400000000000000000000173661342162717100145710ustar00rootroot00000000000000# Building dhcpcd This attempts to document various ways of building dhcpcd for your platform. Building for distribution (ie making a dhcpcd source tarball) now requires gmake-4 or any BSD make. ## 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 and support for DHCPv6 Prefix Delegation. 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` 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. ## OS specific issues Some BSD systems do not allow the manipulation of automatically added subnet routes. You can find discussion here: http://mail-index.netbsd.org/tech-net/2008/12/03/msg000896.html BSD systems where this has been fixed or is known to work are: NetBSD-5.0 FreeBSD-10.0 Some BSD systems protect against IPv6 NS/NA messages by ensuring that the source address matches a prefix on the recieved by a RA message. This is an error as the correct check is for on-link prefixes as the kernel may not be handling RA itself. BSD systems where this has been fixed or is known to work are: NetBSD-7.0 OpenBSD-5.0 patch submitted against FreeBSD-10.0 Some BSD systems do not announce IPv6 address flag changes, such as `IN6_IFF_TENTATIVE`, `IN6_IFF_DUPLICATED`, etc. On these systems, dhcpcd will poll a freshly added address until either `IN6_IFF_TENTATIVE` is cleared or `IN6_IFF_DUPLICATED` is set and take action accordingly. BSD systems where this has been fixed or is known to work are: NetBSD-7.0 OpenBSD will always add it's own link-local address if no link-local address exists, because it doesn't check if the address we are adding is a link-local address or not. Some BSD systems do not announce cached neighbour route changes based on reachability to userland. For such systems, IPv6 routers will always be assumed to be reachable until they either stop being a router or expire. BSD systems where this has been fixed or is known to work are: NetBSD-7.99.3 Linux prior to 3.17 won't allow userland to manage IPv6 temporary addresses. Either upgrade or don't allow dhcpcd to manage the RA, so don't set either `ipv6ra_own` or `slaac private` in `dhcpcd.conf` if you want to have working IPv6 temporary addresses. SLAAC private addresses are just as private, just stable. ## Init systems We try and detect how dhcpcd should interact with system services at runtime. If we cannot auto-detect how do to this, or it is wrong then you can change this by passing shell commands to `--serviceexists`, `--servicecmd` and optionally `--servicestatus` to `./configure` or overriding the service variables in a hook. ## /dev management Some systems have `/dev` management systems and some of these like to rename interfaces. As this system would listen in the same way as dhcpcd to new interface arrivals, dhcpcd needs to listen to the `/dev` management sytem instead of the kernel. However, if the `/dev` management system breaks, stops working, or changes to a new one, dhcpcd should still try and continue to work. To facilitate this, dhcpcd allows a plugin to load to instruct dhcpcd when it can use an interface. As of the time of writing only udev support is included. You can disable this with `--without-dev`, or `without-udev`. NOTE: in Gentoo at least, `sys-fs/udev` as provided by systemd leaks memory `sys-fs/eudev`, the fork of udev does not and as such is recommended. ## select dhcpcd uses eloop.c, which is a portable main event loop with timeouts and signal handling. Unlike libevent and similar, it can be transplanted directly within the application - the only caveat outside of POSIX calls is that you provide queue.h based on a recent BSD (glibc sys/queue.h is not enough). eloop supports the following polling mechanisms, listed in order of preference: kqueue, epoll, pollts, ppoll and pselect. If signal handling is disabled (ie in RTEMS or other single process OS's) then eloop can use poll. You can decide which polling mechanism dhcpcd will select in eloop like so `./configure --with-poll=[kqueue|epoll|pselect|pollts|ppoll]` ## Importing into another source control system To prepare dhcpcd for import into a platform source tree (like NetBSD) you can use the make import target to create /tmp/dhcpcd-$version and populate it with all the source files and hooks needed. In this instance, you may wish to disable some configured tests when the binary has to run on older versions which lack support, such as getline. `./configure --without-getline` ## Hooks Not all the hooks in dhcpcd-hooks are installed by default. By default we install `01-test`, `02-dump`, `10-mtu`, `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` Some system services expose the name of the service we are in, by default dhcpcd will pick `RC_SVCNAME` from the environment. You can override this in `CPPFLAGS+= -DRC_SVCNAME="YOUR_SVCNAME"`. This is important because dhcpcd will scrub the environment aside from `$PATH` before running hooks. This variable could be used to facilitate service re-entry so this chain could happen in a custom OS hook: dhcpcd service marked inactive && dhcpcd service starts dependant services are not started because dhcpcd is inactive (not stopped) dhcpcd hook tests if `$if_up = true` and `$af_waiting` is empty or unset. if true, mark the dhcpcd service as started and then start dependencies if false and the dhcpcd service was previously started, mark as inactive and stop any dependant services. dhcpcd5-7.1.0/LICENSE000066400000000000000000000024261342162717100140460ustar00rootroot00000000000000Copyright (c) 2006-2019 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. dhcpcd5-7.1.0/Makefile000066400000000000000000000042161342162717100145000ustar00rootroot00000000000000SUBDIRS= src hooks VERSION!= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' src/defs.h DIST!= if test -f .fslckout; then echo "dist-fossil"; \ elif test -d .git; then echo "dist-git"; \ else echo "dist-inst"; fi FOSSILID?= current GITREF?= HEAD DISTSUFFIX= DISTPREFIX?= dhcpcd-${VERSION}${DISTSUFFIX} DISTFILEGZ?= ${DISTPREFIX}.tar.gz DISTFILE?= ${DISTPREFIX}.tar.xz DISTINFO= ${DISTFILE}.distinfo DISTINFOSIGN= ${DISTINFO}.asc CLEANFILES+= *.tar.xz .PHONY: hooks import import-bsd tests .SUFFIXES: .in all: config.h for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done depend: config.h for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done tests: cd $@; ${MAKE} $@ test: tests hooks: cd $@; ${MAKE} eginstall: for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done install: for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done proginstall: for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done clean: rm -rf cov-int dhcpcd.xz for x in ${SUBDIRS} tests; do cd $$x; ${MAKE} $@; cd ..; done distclean: clean rm -f config.h config.mk config.log \ ${DISTFILE} ${DISTFILEGZ} ${DISTINFO} ${DISTINFOSIGN} dist-fossil: fossil tarball --name ${DISTPREFIX} ${FOSSILID} ${DISTFILEGZ} gunzip -c ${DISTFILEGZ} | xz >${DISTFILE} rm ${DISTFILEGZ} dist-git: git archive --prefix=${DISTPREFIX}/ ${GITREF} | xz >${DISTFILE} dist-inst: mkdir /tmp/${DISTPREFIX} cp -RPp * /tmp/${DISTPREFIX} (cd /tmp/${DISTPREFIX}; make clean) tar -cvjpf ${DISTFILE} -C /tmp ${DISTPREFIX} rm -rf /tmp/${DISTPREFIX} dist: ${DIST} distinfo: dist rm -f ${DISTINFO} ${DISTINFOSIGN} ${CKSUM} ${DISTFILE} >${DISTINFO} #printf "SIZE (${DISTFILE}) = %s\n" $$(wc -c <${DISTFILE}) >>${DISTINFO} ${PGP} --clearsign --output=${DISTINFOSIGN} ${DISTINFO} chmod 644 ${DISTINFOSIGN} ls -l ${DISTFILE} ${DISTINFO} ${DISTINFOSIGN} snapshot: rm -rf /tmp/${DISTPREFIX} ${INSTALL} -d /tmp/${DISTPREFIX} cp -RPp * /tmp/${DISTPREFIX} ${MAKE} -C /tmp/${DISTPREFIX} distclean tar cf - -C /tmp ${DISTPREFIX} | xz >${DISTFILE} ls -l ${DISTFILE} import: dist rm -rf /tmp/${DISTPREFIX} ${INSTALL} -d /tmp/${DISTPREFIX} tar xvJpf ${DISTFILE} -C /tmp include Makefile.inc dhcpcd5-7.1.0/Makefile.inc000066400000000000000000000015711342162717100152510ustar00rootroot00000000000000# System definitions PICFLAG?= -fPIC BINMODE?= 0555 NONBINMODE?= 0444 MANMODE?= ${NONBINMODE} CONFMODE?= 0644 CC?= cc INSTALL?= install LINT?= lint SED?= sed HOST_SH?= /bin/sh # This isn't very portable, but I generaly make releases from NetBSD CKSUM?= cksum -a SHA256 PGP?= netpgp SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks SED_RUNDIR= -e 's:@RUNDIR@:${RUNDIR}:g' SED_DBDIR= -e 's:@DBDIR@:${DBDIR}:g' SED_LIBDIR= -e 's:@LIBDIR@:${LIBDIR}:g' SED_DATADIR= -e 's:@DATADIR@:${DATADIR}:g' SED_HOOKDIR= -e 's:@HOOKDIR@:${HOOKDIR}:g' SED_SERVICEEXISTS= -e 's:@SERVICEEXISTS@:${SERVICEEXISTS}:g' SED_SERVICECMD= -e 's:@SERVICECMD@:${SERVICECMD}:g' SED_SERVICESTATUS= -e 's:@SERVICESTATUS@:${SERVICESTATUS}:g' SED_STATUSARG= -e 's:@STATUSARG@:${STATUSARG}:g' SED_SCRIPT= -e 's:@SCRIPT@:${SCRIPT}:g' SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' dhcpcd5-7.1.0/README.md000066400000000000000000000070501342162717100143160ustar00rootroot00000000000000# dhcpcd dhcpcd is a [DHCP](http://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) and a [DHCPv6](http://en.wikipedia.org/wiki/DHCPv6) client. It's also an IPv4LL (aka [ZeroConf](http://en.wikipedia.org/wiki/Zeroconf)) client. In layman's terms, dhcpcd runs on your machine and silently configures your computer to work on the attached networks without trouble and mostly without configuration. If you're a desktop user then you may also be interested in [Network Configurator (dhcpcd-ui)](http://roy.marples.name/projects/dhcpcd-ui) which sits in the notification area and monitors the state of the network via dhcpcd. It also has a nice configuration dialog and the ability to enter a pass phrase for wireless networks. dhcpcd may not be the only daemon running that wants to configure DNS on the host, so it uses [openresolv](http://roy.marples.name/projects/openresolv) to ensure they can co-exist. See [BUILDING.md](BUILDING.md) for how to build dhcpcd. If you wish to file a support ticket or help out with development, please [visit the Development Area](https://dev.marples.name/project/profile/101/) or join the mailing list below. ## Configuration You should read the [dhcpcd.conf man page](http://roy.marples.name/man/html5/dhcpcd.conf.html) 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](/man/html8/dhcpcd.html) 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. The Makefile `_confinstall` target will attempt to move the files correctly from the old locations to the new locations. Of course this won't work if dhcpcd-7 is packaged up, so packagers will need to install similar logic into their dhcpcd package. ## ChangeLog We no longer supply a ChangeLog. However, you're more than welcome to read the [commit log](http://roy.marples.name/git/dhcpcd.git/log/) and [archived release announcements](http://roy.marples.name/archives/dhcpcd-discuss/). dhcpcd5-7.1.0/compat/000077500000000000000000000000001342162717100143205ustar00rootroot00000000000000dhcpcd5-7.1.0/compat/_strtoi.h000066400000000000000000000054751342162717100161670ustar00rootroot00000000000000/* $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 */ /* * 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; } dhcpcd5-7.1.0/compat/arc4random.c000066400000000000000000000072431342162717100165240ustar00rootroot00000000000000/* * Arc4 random number generator for OpenBSD. * Copyright 1996 David Mazieres . * * Modification and redistribution in source and binary forms is * permitted provided that due credit is given to the author and the * OpenBSD project by leaving this copyright notice intact. */ /* * This code is derived from section 17.1 of Applied Cryptography, * second edition, which describes a stream cipher allegedly * compatible with RSA Labs "RC4" cipher (the actual description of * which is a trade secret). The same algorithm is used as a stream * cipher called "arcfour" in Tatu Ylonen's ssh package. * * Here the stream cipher has been modified always to include the time * when initializing the state. That makes it impossible to * regenerate the same random sequence twice, so this can't be used * for encryption, but will generate good random numbers. * * RC4 is a registered trademark of RSA Laboratories. */ #include #include #include #include #include #include "arc4random.h" struct arc4_stream { uint8_t i; uint8_t j; uint8_t s[256]; size_t count; pid_t stir_pid; }; #define S(n) (n) #define S4(n) S(n), S(n + 1), S(n + 2), S(n + 3) #define S16(n) S4(n), S4(n + 4), S4(n + 8), S4(n + 12) #define S64(n) S16(n), S16(n + 16), S16(n + 32), S16(n + 48) #define S256 S64(0), S64(64), S64(128), S64(192) static struct arc4_stream rs = { .i = 0xff, .j = 0, .s = { S256 }, .count = 0, .stir_pid = 0 }; #undef S #undef S4 #undef S16 #undef S64 #undef S256 static void arc4_addrandom(struct arc4_stream *as, unsigned char *dat, int datlen) { int n; uint8_t si; as->i--; for (n = 0; n < 256; n++) { as->i = (uint8_t)(as->i + 1); si = as->s[as->i]; as->j = (uint8_t)(as->j + si + dat[n % datlen]); as->s[as->i] = as->s[as->j]; as->s[as->j] = si; } as->j = as->i; } static uint8_t arc4_getbyte(struct arc4_stream *as) { uint8_t si, sj; as->i = (uint8_t)(as->i + 1); si = as->s[as->i]; as->j = (uint8_t)(as->j + si); sj = as->s[as->j]; as->s[as->i] = sj; as->s[as->j] = si; return (as->s[(si + sj) & 0xff]); } static uint32_t arc4_getword(struct arc4_stream *as) { int val; val = arc4_getbyte(as) << 24; val |= arc4_getbyte(as) << 16; val |= arc4_getbyte(as) << 8; val |= arc4_getbyte(as); return (uint32_t)val; } /* We don't care about any error on read, just use what we have * on the stack. So mask off this GCC warning. */ #pragma GCC diagnostic ignored "-Wunused-result" static void arc4_stir(struct arc4_stream *as) { int fd; struct { struct timeval tv; unsigned int rnd[(128 - sizeof(struct timeval)) / sizeof(unsigned int)]; } rdat; size_t n; gettimeofday(&rdat.tv, NULL); fd = open("/dev/urandom", O_RDONLY); if (fd != -1) { /* If there is an error reading, just use what is * on the stack. */ /* coverity[check_return] */ (void)read(fd, rdat.rnd, sizeof(rdat.rnd)); close(fd); } /* fd < 0? Ah, what the heck. We'll just take * whatever was on the stack... */ /* coverity[uninit_use_in_call] */ arc4_addrandom(as, (void *) &rdat, sizeof(rdat)); /* * Throw away the first N words of output, as suggested in the * paper "Weaknesses in the Key Scheduling Algorithm of RC4" * by Fluher, Mantin, and Shamir. (N = 256 in our case.) */ for (n = 0; n < 256 * sizeof(uint32_t); n++) arc4_getbyte(as); as->count = 1600000; } static void arc4_stir_if_needed(struct arc4_stream *as) { pid_t pid; pid = getpid(); if (as->count <= sizeof(uint32_t) || as->stir_pid != pid) { as->stir_pid = pid; arc4_stir(as); } else as->count -= sizeof(uint32_t); } uint32_t arc4random() { arc4_stir_if_needed(&rs); return arc4_getword(&rs); } dhcpcd5-7.1.0/compat/arc4random.h000066400000000000000000000006171342162717100165270ustar00rootroot00000000000000/* * Arc4 random number generator for OpenBSD. * Copyright 1996 David Mazieres . * * Modification and redistribution in source and binary forms is * permitted provided that due credit is given to the author and the * OpenBSD project by leaving this copyright notice intact. */ #ifndef ARC4RANDOM_H #define ARC4RANDOM_H #include uint32_t arc4random(void); #endif dhcpcd5-7.1.0/compat/arc4random_uniform.c000066400000000000000000000034331342162717100202600ustar00rootroot00000000000000/* * Copyright (c) 2008, Damien Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* We need to include config.h so we pickup either the system arc4random * or our compat one. */ #include "config.h" /* * Calculate a uniformly distributed random number less than upper_bound * avoiding "modulo bias". * * Uniformity is achieved by generating new random numbers until the one * returned is outside the range [0, 2**32 % upper_bound). This * guarantees the selected random number will be inside * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound) * after reduction modulo upper_bound. */ uint32_t arc4random_uniform(uint32_t upper_bound) { uint32_t r, min; if (upper_bound < 2) return 0; /* 2**32 % x == (2**32 - x) % x */ min = -upper_bound % upper_bound; /* * This could theoretically loop forever but each retry has * p > 0.5 (worst case, usually far better) of selecting a * number inside the range we need, so it should rarely need * to re-roll. */ do r = arc4random(); while (r < min); return r % upper_bound; } dhcpcd5-7.1.0/compat/arc4random_uniform.h000066400000000000000000000016261342162717100202670ustar00rootroot00000000000000/* * 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 dhcpcd5-7.1.0/compat/bitops.h000066400000000000000000000071561342162717100160020ustar00rootroot00000000000000/* $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_ */ dhcpcd5-7.1.0/compat/crypt/000077500000000000000000000000001342162717100154615ustar00rootroot00000000000000dhcpcd5-7.1.0/compat/crypt/hmac.c000066400000000000000000000116441342162717100165430ustar00rootroot00000000000000/* $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; } dhcpcd5-7.1.0/compat/crypt/hmac.h000066400000000000000000000032521342162717100165440ustar00rootroot00000000000000/* $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 dhcpcd5-7.1.0/compat/crypt/md5.c000066400000000000000000000174451342162717100163250ustar00rootroot00000000000000/* * 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 */ } dhcpcd5-7.1.0/compat/crypt/md5.h000066400000000000000000000021751342162717100163240ustar00rootroot00000000000000/* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ #ifndef MD5_H_ #define MD5_H_ #define MD5_DIGEST_LENGTH 16 #define MD5_BLOCK_LENGTH 64ULL typedef struct MD5Context { uint32_t state[4]; /* state (ABCD) */ uint64_t count; /* number of bits, modulo 2^64 (lsb first) */ unsigned char buffer[MD5_BLOCK_LENGTH]; /* input buffer */ } MD5_CTX; void MD5Init(MD5_CTX *); void MD5Update(MD5_CTX *, const unsigned char *, size_t); void MD5Final(unsigned char[MD5_DIGEST_LENGTH], MD5_CTX *); #endif dhcpcd5-7.1.0/compat/crypt/sha256.c000066400000000000000000000201651342162717100166410ustar00rootroot00000000000000/*- * 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 "common.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)); } dhcpcd5-7.1.0/compat/crypt/sha256.h000066400000000000000000000032701342162717100166440ustar00rootroot00000000000000/*- * 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 dhcpcd5-7.1.0/compat/dprintf.c000066400000000000000000000035611342162717100161370ustar00rootroot00000000000000/* * 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; } dhcpcd5-7.1.0/compat/dprintf.h000066400000000000000000000034041342162717100161400ustar00rootroot00000000000000/* * 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 dhcpcd5-7.1.0/compat/endian.h000066400000000000000000000042101342162717100157240ustar00rootroot00000000000000/* * 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 dhcpcd5-7.1.0/compat/pidfile.c000066400000000000000000000147501342162717100161070ustar00rootroot00000000000000/* $NetBSD: pidfile.c,v 1.14 2016/04/12 20:40:43 roy Exp $ */ /*- * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include /* for flock(2) */ #include "config.h" #include "defs.h" static pid_t pidfile_pid; static char pidfile_path[PATH_MAX]; static int pidfile_fd = -1; /* Closes pidfile resources. * * Returns 0 on success, otherwise -1. */ static int pidfile_close(void) { int error; pidfile_pid = 0; error = close(pidfile_fd); pidfile_fd = -1; pidfile_path[0] = '\0'; return error; } /* Truncate, close and unlink an existent pidfile, * if and only if it was created by this process. * The pidfile is truncated because we may have dropped permissions * or entered a chroot and thus unable to unlink it. * * Returns 0 on truncation success, otherwise -1. */ int pidfile_clean(void) { int error; if (pidfile_fd == -1) { errno = EBADF; return -1; } if (pidfile_pid != getpid()) error = EPERM; else if (ftruncate(pidfile_fd, 0) == -1) error = errno; else { (void) unlink(pidfile_path); error = 0; } (void) pidfile_close(); if (error != 0) { errno = error; return -1; } return 0; } /* atexit shim for pidfile_clean */ static void pidfile_cleanup(void) { pidfile_clean(); } /* Constructs a name for a pidfile in the default location (/var/run). * If 'bname' is NULL, uses the name of the current program for the name of * the pidfile. * * Returns 0 on success, otherwise -1. */ static int pidfile_varrun_path(char *path, size_t len, const char *bname) { if (bname == NULL) bname = PACKAGE; /* _PATH_VARRUN includes trailing / */ if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len) { errno = ENAMETOOLONG; return -1; } return 0; } /* Returns the process ID inside path on success, otherwise -1. * If no path is given, use the last pidfile path, othewise the default one. */ pid_t pidfile_read(const char *path) { char dpath[PATH_MAX], buf[16], *eptr; int fd, error; ssize_t n; pid_t pid; if (path == NULL && pidfile_path[0] != '\0') path = pidfile_path; if (path == NULL || strchr(path, '/') == NULL) { if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1) return -1; path = dpath; } if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1) return -1; n = read(fd, buf, sizeof(buf) - 1); error = errno; (void) close(fd); if (n == -1) { errno = error; return -1; } buf[n] = '\0'; pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error); if (error && !(error == ENOTSUP && *eptr == '\n')) { errno = error; return -1; } return pid; } /* Locks the pidfile specified by path and writes the process pid to it. * The new pidfile is "registered" in the global variables pidfile_fd, * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3) * can check if we are recreating the same file or a new one. * * Returns 0 on success, otherwise the pid of the process who owns the * lock if it can be read, otherwise -1. */ pid_t pidfile_lock(const char *path) { char dpath[PATH_MAX]; static bool registered_atexit = false; /* Register for cleanup with atexit. */ if (!registered_atexit) { if (atexit(pidfile_cleanup) == -1) return -1; registered_atexit = true; } if (path == NULL || strchr(path, '/') == NULL) { if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1) return -1; path = dpath; } /* If path has changed (no good reason), clean up the old pidfile. */ if (pidfile_fd != -1 && strcmp(pidfile_path, path) != 0) pidfile_clean(); if (pidfile_fd == -1) { int fd, opts; opts = O_WRONLY | O_CREAT | O_NONBLOCK; #ifdef O_CLOEXEC opts |= O_CLOEXEC; #endif #ifdef O_EXLOCK opts |= O_EXLOCK; #endif if ((fd = open(path, opts, 0644)) == -1) goto return_pid; #ifndef O_CLOEXEC if ((opts = fcntl(fd, F_GETFD)) == -1 || fctnl(fd, F_SETFL, opts | FD_CLOEXEC) == -1) { int error = errno; (void) close(fd); errno = error; return -1; } #endif #ifndef O_EXLOCK if (flock(fd, LOCK_EX | LOCK_NB) == -1) { int error = errno; (void) close(fd); if (error != EAGAIN) { errno = error; return -1; } fd = -1; } #endif return_pid: if (fd == -1) { pid_t pid; if (errno == EAGAIN) { /* The pidfile is locked, return the process ID * it contains. * If sucessful, set errno to EEXIST. */ if ((pid = pidfile_read(path)) != -1) errno = EEXIST; } else pid = -1; return pid; } pidfile_fd = fd; strlcpy(pidfile_path, path, sizeof(pidfile_path)); } pidfile_pid = getpid(); /* Truncate the file, as we could be re-writing it. * Then write the process ID. */ if (ftruncate(pidfile_fd, 0) == -1 || lseek(pidfile_fd, 0, SEEK_SET) == -1 || dprintf(pidfile_fd, "%d\n", pidfile_pid) == -1) { int error = errno; pidfile_cleanup(); errno = error; return -1; } /* Hold the fd open to persist the lock. */ return 0; } dhcpcd5-7.1.0/compat/pidfile.h000066400000000000000000000032541342162717100161110ustar00rootroot00000000000000/*- * 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 dhcpcd5-7.1.0/compat/queue.h000066400000000000000000000151241342162717100156200ustar00rootroot00000000000000/* $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 */ dhcpcd5-7.1.0/compat/reallocarray.c000066400000000000000000000042161342162717100171470ustar00rootroot00000000000000/* $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); } dhcpcd5-7.1.0/compat/reallocarray.h000066400000000000000000000030541342162717100171530ustar00rootroot00000000000000/* $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 dhcpcd5-7.1.0/compat/strlcpy.c000066400000000000000000000031121342162717100161610ustar00rootroot00000000000000/* $OpenBSD: strlcpy.c,v 1.15 2016/10/16 17:37:39 dtucker Exp $ */ /* * Copyright (c) 1998, 2015 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "strlcpy.h" /* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; /* Copy as many bytes as will fit. */ if (nleft != 0) { while (--nleft != 0) { if ((*dst++ = *src++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) *dst = '\0'; /* NUL-terminate dst */ while (*src++) ; } return (size_t)(src - osrc - 1); /* count does not include NUL */ } dhcpcd5-7.1.0/compat/strlcpy.h000066400000000000000000000017071342162717100161760ustar00rootroot00000000000000/* $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 dhcpcd5-7.1.0/compat/strtoi.c000066400000000000000000000043271342162717100160160ustar00rootroot00000000000000/* $NetBSD: strtoi.c,v 1.2 2015/05/01 14:17:56 christos 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 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 dhcpcd5-7.1.0/compat/strtoi.h000066400000000000000000000041541342162717100160210ustar00rootroot00000000000000/*- * 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 dhcpcd5-7.1.0/compat/strtou.c000066400000000000000000000043301342162717100160240ustar00rootroot00000000000000/* $NetBSD: strtou.c,v 1.2 2015/05/01 14:17:56 christos 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 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 dhcpcd5-7.1.0/config-null.mk000066400000000000000000000001071342162717100156010ustar00rootroot00000000000000# This space left intentionally blank DHCPCD_SRCS+= dhcpcd-embedded.c dhcpcd5-7.1.0/configure000077500000000000000000001066431342162717100147560ustar00rootroot00000000000000#!/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= ARC4RANDOM= CLOSEFROM= STRLCPY= UDEV= OS= BUILD= HOST= HOSTCC= TARGET= INCLUDEDIR= DEBUG= FORK= STATIC= DEVS= EMBEDDED= AUTH= POLL= SMALL= STATUSARG= DHCPCD_DEFS=dhcpcd-definitions.conf for x do opt=${x%%=*} var=${x#*=} case "$opt" in --os|OS) OS=$var;; --debug) DEBUG=$var;; --disable-debug) DEBUG=no;; --enable-debug) DEBUG=yes;; --fork) FORK=$var;; --disable-fork) FORK=no;; --enable-fork) FORK=yes;; --disable-static) STATIC=no;; --enable-static) STATIC=yes;; --disable-ipv4|--disable-inet) INET=no; ARP=no; ARPING=no; IPV4LL=no;; --enable-ipv4|--enable-inet) INET=yes;; --disable-arp) ARP=no; ARPING=no; IPV4LL=no;; --enable-arp) ARP=yes; INET=yes;; --disable-arping) ARPING=no;; --enable-arping) ARPING=yes; ARP=yes; INET=yes;; --disable-ipv4ll) IPV4LL=no;; --enable-ipv4ll) IPV4LL=yes; ARP=yes; INET=yes;; --disable-ipv6|--disable-inet6) INET6=no; DHCP6=no;; --enable-ipv6|--enable-inet6) INET6=yes;; --disable-dhcp6) DHCP6=no;; --enable-dhcp6) DHCP6=yes;; --disable-embedded) EMBEDDED=no;; --enable-embedded) EMBEDDED=yes;; --disable-auth) AUTH=no;; --enable-auth) AUTH=yes;; --prefix) PREFIX=$var;; --sysconfdir) SYSCONFDIR=$var;; --bindir|--sbindir) SBINDIR=$var;; --libexecdir) LIBEXECDIR=$var;; --statedir|--localstatedir) STATEDIR=$var;; --dbdir) DBDIR=$var;; --rundir) RUNDIR=$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;; --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";; --serviceexists) SERVICEEXISTS=$var;; --servicecmd) SERVICECMD=$var;; --servicestatus) SERVICESTATUS=$var;; --small) SMALL=yes;; --statusarg) STATUSARG=$var;; --infodir) ;; # ignore autotools --disable-maintainer-mode|--disable-dependency-tracking) ;; --disable-silent-rules) ;; -V|--version) v=$(sed -ne 's/.*VERSION[[:space:]]*"\([^"]*\).*/\1/p' defs.h); c=$(sed -ne 's/^.*copyright\[\] = "\([^"]*\).*/\1/p' dhcpcd.c); echo "dhcpcd-$v $c"; exit 0;; -h|--help) cat < if you have libraries in a nonstandard directory CPPFLAGS C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor PKG_CONFIG pkg-config executable Use these variables to override the choices made by \`configure' or to help it to find libraries and programs with nonstandard names/locations. EOF exit 0 ;; *) echo "$0: WARNING: unknown option $opt" >&2;; esac done : ${SED:=sed} : ${GREP:=grep} : ${PKG_CONFIG:=pkg-config} : ${WC:=wc} : ${FORK:=yes} : ${SYSCONFDIR:=$PREFIX/etc} : ${SBINDIR:=$PREFIX/sbin} : ${LIBDIR:=$PREFIX/lib} : ${LIBEXECDIR:=$PREFIX/libexec} : ${STATEDIR:=/var} : ${DBDIR:=$STATEDIR/db/dhcpcd} : ${RUNDIR:=$STATEDIR/run} : ${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" _which() { x="$(which "$1" 2>/dev/null)" if [ $? = 0 -a -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 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 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 "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" -a "$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" -o "$PREFIX" = / ]; then ALLOW_USR_LIBS=false else ALLOW_USR_LIBS=true fi case "$OS" in linux*|sunos*|kfreebsd*) ;; *) # There might be more than one ... for LDELFN in /libexec/ld-elf.so.[0-9]*; do [ -x "$LDELFN" ] && break done if ! [ -x "$LDELF" -o -x "$LDELFN" ] && \ [ -z "$PREFIX" -o "$PREFIX" = "/" ] then echo "Forcing a static build for $OS and \$PREFIX of /" STATIC=yes ALLOW_USR_LIBS=true fi ;; esac if [ "$STATIC" = yes ]; then echo "LDFLAGS+= -static" >>$CONFIG_MK fi if [ -z "$DEBUG" -a -f .fslckout ]; then printf "Found fossil checkout ... " DEBUG=yes fi if [ -z "$DEBUG" -a -d .git ]; then printf "Found git checkout ... " DEBUG=yes fi if [ -n "$DEBUG" -a "$DEBUG" != no -a "$DEBUG" != false ]; then echo "Adding debugging CFLAGS" cat <>$CONFIG_MK CFLAGS+= -g -Wall -Wextra 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*|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 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 else echo "CPPFLAGS+= -DNDEBUG" >>$CONFIG_MK fi if [ -n "$FORK" -a "$FORK" != yes -a "$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 ;; sunos*) echo "WARNING!!! Solaris support is at early development stage!" >&2 echo "so don't expect it to work just yet, patches welcome" >&2 echo "CPPFLAGS+= -D_XPG4_2 -D__EXTENSIONS__ -DBSD_COMP" \ >>$CONFIG_MK echo "DHCPCD_SRCS+= if-sun.c" >>$CONFIG_MK echo "LDADD+= -ldlpi" >>$CONFIG_MK ;; *) echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK ;; esac if [ -z "$INET" -o "$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" -o "$ARP" = yes ]; then echo "Enabling ARP support" echo "CPPFLAGS+= -DARP" >>$CONFIG_MK echo "DHCPCD_SRCS+= arp.c" >>$CONFIG_MK fi if [ -z "$ARPING" -o "$ARPING" = yes ]; then echo "Enabling ARPing support" echo "CPPFLAGS+= -DARPING" >>$CONFIG_MK fi if [ -z "$IPV4LL" -o "$IPV4LL" = yes ]; then echo "Enabling IPv4LL support" echo "CPPFLAGS+= -DIPV4LL" >>$CONFIG_MK echo "DHCPCD_SRCS+= ipv4ll.c" >>$CONFIG_MK fi fi if [ -z "$INET6" -o "$INET6" = yes ]; then echo "Enabling INET6 support" echo "CPPFLAGS+= -DINET6" >>$CONFIG_MK echo "DHCPCD_SRCS+= ipv6.c ipv6nd.c" >>$CONFIG_MK if [ -z "$DHCP6" -o "$DHCP6" = yes ]; then echo "Enabling DHCPv6 support" echo "CPPFLAGS+= -DDHCP6" >>$CONFIG_MK echo "DHCPCD_SRCS+= dhcp6.c" >>$CONFIG_MK fi fi if [ -z "$AUTH" -o "$AUTH" = yes ]; then echo "Enabling Authentication" echo "CPPFLAGS+= -DAUTH" >>$CONFIG_MK echo "SRCS+= auth.c" >>$CONFIG_MK echo "CRYPT_SRCS+= \${HMAC_SRC}" >>$CONFIG_MK fi if [ -z "$INET6" -o "$INET6" = yes -o -z "$AUTH" -o "$AUTH" = yes ]; then echo "CRYPT_SRCS+= \${MD5_SRC} \${SHA256_SRC}" >>$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 "$CC does not create executables" >&2 exit 1 fi [ "$CC" != cc ] && echo "CC= $CC" >>$CONFIG_MK $CC --version | $SED -e '1!d' if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then echo "$DHCPCD_DEFS will be embedded in dhcpcd itself" echo "DHCPCD_SRCS+= dhcpcd-embedded.c" >>$CONFIG_MK else echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR" echo "CPPFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK echo "EMBEDDEDINSTALL= _embeddedinstall" >>$CONFIG_MK fi if [ "$OS" = linux ]; then printf "Testing for nl80211 ... " cat <_nl80211.c #include int main(void) { return 0; } EOF if $XCC _nl80211.c -o _nl80211 2>&3; then echo "yes" echo "#define HAVE_NL80211_H" >>$CONFIG_H else echo "no" echo "DHCPCD_SRCS+= if-linux-wext.c" >>$CONFIG_MK fi rm -f _nl80211.c _nl80211 printf "Testing for IN6_ADDR_GEN_MODE_NONE ... " cat <_IN6_ADDR_GEN_MODE_NONE.c #include int main(void) { int x = IN6_ADDR_GEN_MODE_NONE; return x; } EOF if $XCC _IN6_ADDR_GEN_MODE_NONE.c -o _IN6_ADDR_GEN_MODE_NONE 2>&3; then echo "yes" echo "#define HAVE_IN6_ADDR_GEN_MODE_NONE" >>$CONFIG_H else echo "no" fi rm -f _IN6_ADDR_GEN_MODE_NONE.c _IN6_ADDR_GEN_MODE_NONE else printf "Testing for ifam_pid ... " cat <_ifam_pid.c #include int main(void) { struct ifa_msghdr ifam = { }; return (int)ifam.ifam_pid; } EOF if $XCC _ifam_pid.c -o _ifam_pid 2>&3; then echo "yes" echo "#define HAVE_IFAM_PID" >>$CONFIG_H else echo "no" fi rm -f _ifam_pid.c _ifam_pid printf "Testing for ifam_addrflags ... " cat <_ifam_addrflags.c #include int main(void) { struct ifa_msghdr ifam = { }; return (int)ifam.ifam_addrflags; } EOF if $XCC _ifam_addrflags.c -o _ifam_addrflags 2>&3; then echo "yes" echo "#define HAVE_IFAM_ADDRFLAGS" >>$CONFIG_H else echo "no" fi rm -f _ifam_addrflags.c _ifam_addrflags fi abort=false # We require the libc to support non standard functions, like getifaddrs printf "Testing for getifaddrs ... " cat <_getifaddrs.c #include #include int main(void) { struct ifaddrs *ifap; return getifaddrs(&ifap); } EOF LIBSOCKET= if $XCC _getifaddrs.c -o _getifaddrs 2>&3; then echo "yes" elif $XCC _getifaddrs.c -o _getifaddrs -lsocket 2>&3; then LIBSOCKET=-lsocket echo "yes (-lsocket)" echo "LDADD+= -lsocket" >>$CONFIG_MK else echo "no" echo "libc support for getifaddrs is required - aborting" >&2 abort=true fi rm -f _getifaddrs.c _getifaddrs $abort && exit 1 printf "Testing for ifaddrs.ifa_addrflags ... " cat <_getifaddrs_addrflags.c #include #include int main(void) { struct ifaddrs *ifap; getifaddrs(&ifap); return (int)ifap->ifa_addrflags; } EOF if $XCC _getifaddrs_addrflags.c -o _getifaddrs_addrflags $LIBSOCKET 2>&3; then echo "yes" echo "#define HAVE_IFADDRS_ADDRFLAGS" >>$CONFIG_H else echo "no" fi rm -f _getifaddrs_addrflags.c _getifaddrs_addrflags printf "Testing for clock_gettime ... " cat <_clock_gettime.c #include int main(void) { struct timespec ts; return clock_gettime(CLOCK_MONOTONIC, &ts); } EOF if $XCC _clock_gettime.c -o _clock_gettime 2>&3; then echo "yes" elif $XCC _clock_gettime.c -lrt -o _clock_gettime 2>&3; then echo "yes (-lrt)" echo "LDADD+= -lrt" >>$CONFIG_MK else echo "no" echo "libc support for clock_getttime is required - aborting" >&2 abort=true fi rm -f _clock_gettime.c _clock_gettime $abort && exit 1 printf "Testing for inet_ntoa ... " cat <_inet_ntoa.c #include #include int main(void) { struct in_addr in = { .s_addr = 0 }; inet_ntoa(in); return 0; } EOF if $XCC _inet_ntoa.c -o _inet_ntoa 2>&3; then echo "yes" elif $XCC _inet_ntoa.c -lnsl -o _inet_ntoa 2>&3; then echo "yes (-lnsl)" echo "LDADD+= -lnsl" >>$CONFIG_MK elif $XCC _inet_ntoa.c -lsocket -o _inet_ntoa 2>&3; then echo "yes (-lsocket)" echo "LDADD+= -lsocket" >>$CONFIG_MK else echo "no" echo "libc support for inet_ntoa is required - aborting" >&2 abort=true fi rm -f _inet_ntoa.c _inet_ntoa $abort && exit 1 if [ -z "$ARC4RANDOM" ]; then printf "Testing for arc4random ... " cat <_arc4random.c #include int main(void) { arc4random(); return 0; } EOF if $XCC _arc4random.c -o _arc4random 2>&3; then ARC4RANDOM=yes else ARC4RANDOM=no fi echo "$ARC4RANDOM" rm -f _arc4random.c _arc4random fi if [ "$ARC4RANDOM" = no ]; then echo "COMPAT_SRCS+= compat/arc4random.c" >>$CONFIG_MK echo "#include \"compat/arc4random.h\"" >>$CONFIG_H fi if [ -z "$ARC4RANDOM_UNIFORM" ]; then printf "Testing for arc4random_uniform ... " cat <_arc4random_uniform.c #include int main(void) { arc4random_uniform(100); return 0; } EOF if $XCC _arc4random_uniform.c -o _arc4random_uniform 2>&3; then ARC4RANDOM_UNIFORM=yes else ARC4RANDOM_UNIFORM=no fi echo "$ARC4RANDOM_UNIFORM" rm -f _arc4random_uniform.c _arc4random_uniform fi if [ "$ARC4RANDOM_UNIFORM" = no ]; then echo "COMPAT_SRCS+= compat/arc4random_uniform.c" >>$CONFIG_MK echo "#include \"compat/arc4random_uniform.h\"" >>$CONFIG_H fi if [ -z "$STRLCPY" ]; then printf "Testing for strlcpy ... " cat <_strlcpy.c #include int main(void) { const char s1[] = "foo"; char s2[10]; strlcpy(s2, s1, sizeof(s2)); return 0; } EOF if $XCC _strlcpy.c -o _strlcpy 2>&3; then STRLCPY=yes else STRLCPY=no fi echo "$STRLCPY" rm -f _strlcpy.c _strlcpy fi if [ "$STRLCPY" = no ]; then echo "COMPAT_SRCS+= compat/strlcpy.c" >>$CONFIG_MK echo "#include \"compat/strlcpy.h\"" >>$CONFIG_H fi if [ -z "$PIDFILE_LOCK" ]; then printf "Testing for pidfile_lock ... " cat <_pidfile.c #include #include int main(void) { pidfile_lock(NULL); return 0; } EOF # We only want to link to libutil if it exists in /lib if $ALLOW_USR_LIBS; then set -- / else set -- $(ls /lib/libutil.so.* 2>/dev/null) fi if $XCC _pidfile.c -o _pidfile 2>&3; then PIDFILE_LOCK=yes elif [ -e "$1" ] && $XCC _pidfile.c -o _pidfile -lutil 2>&3; then PIDFILE_LOCK="yes (-lutil)" LIBUTIL="-lutil" else PIDFILE_LOCK=no fi echo "$PIDFILE_LOCK" rm -f _pidfile.c _pidfile fi if [ "$PIDFILE_LOCK" = no ]; then echo "COMPAT_SRCS+= compat/pidfile.c" >>$CONFIG_MK echo "#include \"compat/pidfile.h\"" >>$CONFIG_H else echo "#define HAVE_UTIL_H" >>$CONFIG_H if [ -n "$LIBUTIL" ]; then echo "LDADD+= $LIBUTIL" >>$CONFIG_MK fi fi if [ -z "$SETPROCTITLE" ]; then printf "Testing for setproctitle ... " cat << EOF >_setproctitle.c #include 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" = yes ]; then echo "#define HAVE_SETPROCTITLE" >>$CONFIG_H fi if [ -z "$STRTOI" ]; then printf "Testing for strtoi ... " cat <_strtoi.c #include #include #include int main(void) { int e; strtoi("1234", NULL, 0, 0, INT32_MAX, &e); return 0; } EOF if $XCC _strtoi.c -o _strtoi 2>&3; then STRTOI=yes else STRTOI=no fi echo "$STRTOI" rm -f _strtoi.c _strtoi fi if [ "$STRTOI" = no ]; then echo "COMPAT_SRCS+= compat/strtoi.c compat/strtou.c" >>$CONFIG_MK echo "#include \"compat/strtoi.h\"" >>$CONFIG_H fi if [ -z "$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 [ -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 -o "$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 "$REALLOCARRAY" ]; then printf "Testing for reallocarray ... " cat <_reallocarray.c #include int main(void) { void *foo = reallocarray(NULL, 0, 0); return foo == NULL ? 1 : 0; } EOF if $XCC _reallocarray.c -o _reallocarray 2>&3; then REALLOCARRAY=yes else REALLOCARRAY=no fi echo "$REALLOCARRAY" rm -f _reallocarray.c _reallocarray fi if [ "$REALLOCARRAY" = no ]; then echo "COMPAT_SRCS+= compat/reallocarray.c" >>$CONFIG_MK echo "#include \"compat/reallocarray.h\"">>$CONFIG_H fi # Set this for eloop echo "#define HAVE_REALLOCARRAY" >>$CONFIG_H if [ -z "$POLL" ]; then printf "Testing for kqueue1 ... " cat <_kqueue.c #include #include int main(void) { return kqueue1(0); } EOF if $XCC _kqueue.c -o _kqueue 2>&3; then POLL=kqueue1 echo "yes" else echo "no" fi rm -f _kqueue.c _kqueue fi if [ -z "$POLL" ]; then printf "Testing for kqueue ... " cat <_kqueue.c #include #include int main(void) { return kqueue(); } EOF if $XCC _kqueue.c -o _kqueue 2>&3; then POLL=kqueue echo "yes" else echo "no" fi rm -f _kqueue.c _kqueue fi if [ -z "$POLL" ]; then printf "Testing for epoll ... " cat <_epoll.c #ifdef __linux__ #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) #error kernel has buggy epoll_wait timeout #endif #endif #include #include int main(void) { epoll_create1(EPOLL_CLOEXEC); epoll_pwait(-1, NULL, 0, 0, NULL); return 0; } EOF if $XCC _epoll.c -o _epoll 2>&3; then POLL=epoll echo "#define HAVE_EPOLL" >>$CONFIG_MK echo "yes" else echo "no" fi rm -f _epoll.c _epoll fi if [ -z "$POLL" ]; then printf "Testing for pselect ... " cat <_pselect.c #include #include int main(void) { pselect(0, NULL, NULL, NULL, NULL, NULL); return 0; } EOF if $XCC _pselect.c -o _pselect 2>&3; then POLL=pselect echo "yes" else echo "no" fi rm -f _pselect.c _pselect fi case "$POLL" in kqueue1) echo "#define HAVE_KQUEUE" >>$CONFIG_H echo "#define HAVE_KQUEUE1" >>$CONFIG_H ;; kqueue) echo "#define HAVE_KQUEUE" >>$CONFIG_H ;; epoll) echo "#define HAVE_EPOLL" >>$CONFIG_H ;; pollts) echo "#define HAVE_POLLTS" >>$CONFIG_H ;; ppoll) echo "#define HAVE_PPOLL" >>$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 [ "$MD5" = no ]; then echo "#include \"compat/crypt/md5.h\"" >>$CONFIG_H echo "MD5_SRC= compat/crypt/md5.c" >>$CONFIG_MK else echo "MD5_SRC=" >>$CONFIG_MK echo "#define HAVE_MD5_H" >>$CONFIG_H [ -n "$MD5_LIB" ] && echo "LDADD+= $MD5_LIB" >>$CONFIG_MK fi if [ -z "$SHA2_H" -a -z "$SHA2" -o "$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 if [ -z "$SHA2" ]; then SHA2_LIB= SHA2_RENAMED= printf "Testing for SHA256_Init ... " cat <_sha256.c #include EOF [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c cat <>_sha256.c #include int main(void) { SHA256_CTX context; SHA256_Init(&context); return 0; } EOF # We only want to link to libmd if it exists in /lib if $ALLOW_USR_LIBS; then set -- / else set -- $(ls /lib/libmd.so.* 2>/dev/null) fi if $XCC _sha256.c -o _sha256 2>&3; then SHA2=yes elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3; then SHA2="yes (-lmd)" SHA2_LIB=-lmd else SHA2=no fi echo "$SHA2" rm -f _sha256.c _sha256 if [ "$SHA2" = no ]; then # Did OpenBSD really change this? grrrr printf "Testing for SHA256Init ... " cat <_sha256.c #include EOF [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c cat <>_sha256.c #include int main(void) { SHA2_CTX context; SHA256Init(&context); return 0; } EOF # We only want to link to libmd if it exists in /lib if $ALLOW_USR_LIBS; then set -- / else set -- $(ls /lib/libmd.so.* 2>/dev/null) fi if $XCC _sha256.c -o _sha256 2>&3; then SHA2=yes SHA2_RENAMED=yes elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3 then SHA2="yes (-lmd)" SHA2_LIB=-lmd SHA2_RENAMED=yes else SHA2=no fi echo "$SHA2" rm -f _sha256.c _sha256 fi fi if [ "$SHA2" = no ]; then echo "#include \"compat/crypt/sha256.h\"" >>$CONFIG_H echo "SHA256_SRC= compat/crypt/sha256.c" >>$CONFIG_MK else echo "SHA256_SRC=" >>$CONFIG_MK echo "#define SHA2_H <$SHA2_H>" >>$CONFIG_H if [ "$SHA2_RENAMED" = yes ]; then echo "#define SHA256_CTX SHA2_CTX" >>$CONFIG_H echo "#define SHA256_Init SHA256Init" >>$CONFIG_H echo "#define SHA256_Update SHA256Update" >>$CONFIG_H echo "#define SHA256_Final SHA256Final" >>$CONFIG_H fi [ -n "$SHA2_LIB" ] && echo "LDADD+= $SHA2_LIB" >>$CONFIG_MK fi if [ -z "$HMAC" ]; then HMAC_LIB= printf "Testing for hmac ... " cat <_hmac.c #include #include int main(void) { hmac(NULL, NULL, 0, NULL, 0, NULL, 0); return 0; } EOF if $XCC _hmac.c $MD5_LIB -o _hmac 2>&3; then HMAC=yes echo "#define HAVE_HMAC_H" >>$CONFIG_H else # Remove this test if NetBSD-8 ships with # hmac in it's own header and not stdlib.h cat <_hmac.c #include int main(void) { hmac(NULL, NULL, 0, NULL, 0, NULL, 0); return 0; } EOF if $XCC _hmac.c $MD5_LIB -o _hmac 2>&3; then HMAC=yes else HMAC=no fi fi echo "$HMAC" rm -f _hmac.c _hmac fi if [ "$HMAC" = no ]; then echo "#include \"compat/crypt/hmac.h\"" >>$CONFIG_H echo "HMAC_SRC= compat/crypt/hmac.c" >>$CONFIG_MK else # echo "#define HAVE_HMAC_H" >>$CONFIG_H echo "HMAC_SRC=" >>$CONFIG_MK fi if [ "$DEV" != no -a "$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 fi if [ "$DEV" != no -a "$UDEV" != no -a -n "$LIBUDEV_LIBS" ]; then echo "yes" [ -z "$DEV" ] && DEV=yes echo "DEV_PLUGINS+= udev" >>$CONFIG_MK if [ -n "$LIBUDEV_CFLAGS" ]; then echo "LIBUDEV_CFLAGS= $LIBUDEV_CFLAGS" >>$CONFIG_MK fi echo "LIBUDEV_LIBS= $LIBUDEV_LIBS" >>$CONFIG_MK printf "Checking udev_monitor_filter_add_match_subsystem_devtype ... " cat <_udev.c #include #include int main(void) { udev_monitor_filter_add_match_subsystem_devtype(NULL, NULL, NULL); return 0; } EOF if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3 then echo "yes" else echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOFILTER" >>$CONFIG_MK echo "no" fi rm -f _udev.c _udev printf "Checking udev_device_get_is_initialized ... " cat <_udev.c #include #include int main(void) { udev_device_get_is_initialized(NULL); return 0; } EOF if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3 then echo "yes" else echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOINIT" >>$CONFIG_MK echo "no" fi rm -f _udev.c _udev elif [ "$DEV" != no -a "$UDEV" != no ]; then echo "no" if [ -n "$UDEV" ]; then echo "udev has been explicity requested ... aborting" >&2 exit 1 fi 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) { dlopen(NULL, 0); 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 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" -o -n "$CHRONYD" ]; then HOOKS="$HOOKS${HOOKS:+ }50-ntp.conf" fi # Warn if both are detected if [ -n "$NTPD" -a -n "$CHRONYD" ]; then echo "NTP will default to $NTPD" fi printf "Checking for ypbind ... " YPBIND=$(_which ypbind) if [ -n "$YPBIND" ]; then YPHOOK="50-ypbind" if strings "$YPBIND" | $GREP -q yp\\.conf; then YPHOOK="50-yp.conf" YPOS="Linux" elif strings "$YPBIND" | $GREP -q \\.ypservers; then YPOS="NetBSD" echo "YPDOMAIN_DIR= /var/yp" >>$CONFIG_MK echo "YPDOMAIN_SUFFIX=.ypservers" >>$CONFIG_MK elif strings "$YPBIND" | $GREP -q /etc/yp; then YPOS="OpenBSD" echo "YPDOMAIN_DIR= /etc/yp" >>$CONFIG_MK echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK else YPOS="FreeBSD" echo "YPDOMAIN_DIR=" >>$CONFIG_MK echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK fi echo "$YPBIND ($YPHOOK${YPOS:+ }$YPOS)" EGHOOKS="$EGHOOKS${EGHOOKS:+ }$YPHOOK" else echo "not found" fi fi if cd hooks; then for x in $HOOKSCRIPTS; do printf "Finding hook $x ... " for h in [0-9][0-9]"-$x" \ [0-9][0-9]"-$x.sh" \ [0-9][0-9]"-$x.conf" do [ -e "$h" ] && break done if [ ! -e "$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 ... " for h in [0-9][0-9]"-$x" \ [0-9][0-9]"-$x.sh" \ [0-9][0-9]"-$x.conf" do [ -e "$h" ] && break done if [ ! -e "$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" echo rm -f dhcpcd tests/test dhcpcd5-7.1.0/hooks/000077500000000000000000000000001342162717100141605ustar00rootroot00000000000000dhcpcd5-7.1.0/hooks/01-test000066400000000000000000000004541342162717100153030ustar00rootroot00000000000000# Echo the interface flags, reason and message options if [ "$reason" = "TEST" ]; then set | grep \ "^\(interface\|pid\|reason\|protocol\|profile\|skip_hooks\)=" | sort set | grep "^if\(carrier\|flags\|mtu\|wireless\|ssid\)=" | sort set | grep "^\(new_\|old_\|nd[0-9]*_\)" | sort exit 0 fi dhcpcd5-7.1.0/hooks/02-dump000066400000000000000000000001701342162717100152650ustar00rootroot00000000000000# Just echo our DHCP options we have case "$reason" in DUMP|DUMP6) set | sed -ne 's/^new_//p' | sort exit 0 ;; esac dhcpcd5-7.1.0/hooks/10-wpa_supplicant000066400000000000000000000053471342162717100173630ustar00rootroot00000000000000# Start, reconfigure and stop wpa_supplicant per wireless interface. # This is needed because wpa_supplicant lacks hotplugging of any kind # and the user should not be expected to have to wire it into their system # if the base system doesn't do this itself. if [ -z "$wpa_supplicant_conf" ]; then for x in \ /etc/wpa_supplicant/wpa_supplicant-"$interface".conf \ /etc/wpa_supplicant/wpa_supplicant.conf \ /etc/wpa_supplicant-"$interface".conf \ /etc/wpa_supplicant.conf \ ; do if [ -s "$x" ]; then wpa_supplicant_conf="$x" break fi done fi : ${wpa_supplicant_conf:=/etc/wpa_supplicant.conf} wpa_supplicant_ctrldir() { 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 start wpa_supplicant" syslog err "$err" fi return $errn } if [ "$ifwireless" = "1" ] && \ type wpa_supplicant >/dev/null 2>&1 && \ type wpa_cli >/dev/null 2>&1 then case "$reason" in PREINIT) wpa_supplicant_start;; RECONFIGURE) wpa_supplicant_reconfigure;; DEPARTED) wpa_supplicant_stop;; esac fi dhcpcd5-7.1.0/hooks/15-timezone000066400000000000000000000015431342162717100161630ustar00rootroot00000000000000# 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_up; then set_zoneinfo fi dhcpcd5-7.1.0/hooks/20-resolv.conf000066400000000000000000000125521342162717100165650ustar00rootroot00000000000000# Generate /etc/resolv.conf # Support resolvconf(8) if available # We can merge other dhcpcd resolv.conf files into one like resolvconf, # but resolvconf is preferred as other applications like VPN clients # can readily hook into it. # Also, resolvconf can configure local nameservers such as bind # or dnsmasq. This is important as the libc resolver isn't that powerful. resolv_conf_dir="$state_dir/resolv.conf" NL=" " : ${resolvconf:=resolvconf} build_resolv_conf() { cf="$state_dir/resolv.conf.$ifname" # Build a list of interfaces interfaces=$(list_interfaces "$resolv_conf_dir") # Build the resolv.conf if [ -n "$interfaces" ]; then # Build the header for x in ${interfaces}; do header="$header${header:+, }$x" done # Build the search list domain=$(cd "$resolv_conf_dir"; \ key_get_value "domain " ${interfaces}) search=$(cd "$resolv_conf_dir"; \ key_get_value "search " ${interfaces}) set -- ${domain} domain="$1" [ -n "$2" ] && search="$search $*" [ -n "$search" ] && search="$(uniqify $search)" [ "$domain" = "$search" ] && search= [ -n "$domain" ] && domain="domain $domain$NL" [ -n "$search" ] && search="search $search$NL" # Build the nameserver list srvs=$(cd "$resolv_conf_dir"; \ key_get_value "nameserver " ${interfaces}) for x in $(uniqify ${srvs}); do servers="${servers}nameserver $x$NL" done fi header="$signature_base${header:+ $from }$header" # Assemble resolv.conf using our head and tail files [ -f "$cf" ] && rm -f "$cf" [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" echo "$header" > "$cf" if [ -f /etc/resolv.conf.head ]; then cat /etc/resolv.conf.head >> "$cf" else echo "# /etc/resolv.conf.head can replace this line" >> "$cf" fi printf %s "$domain$search$servers" >> "$cf" if [ -f /etc/resolv.conf.tail ]; then cat /etc/resolv.conf.tail >> "$cf" else echo "# /etc/resolv.conf.tail can replace this line" >> "$cf" fi if change_file /etc/resolv.conf "$cf"; then chmod 644 /etc/resolv.conf fi rm -f "$cf" } # Extract any ND DNS options from the RA # For now, we ignore the lifetime of the DNS options unless they # are absent or zero. # In this case they are removed from consideration. # See draft-gont-6man-slaac-dns-config-issues-01 for issues # regarding DNS option lifetime in ND messages. eval_nd_dns() { eval ltime=\$nd${i}_rdnss${j}_lifetime if [ -z "$ltime" ] || [ "$ltime" = 0 ]; then rdnss= else eval rdnss=\$nd${i}_rdnss${j}_servers fi eval ltime=\$nd${i}_dnssl${j}_lifetime if [ -z "$ltime" ] || [ "$ltime" = 0 ]; then dnssl= else eval dnssl=\$nd${i}_dnssl${j}_search fi [ -z "${rdnss}${dnssl}" ] && return 1 [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" [ -n "$dnssl" ] && new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" 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 while true; do eval_nd_dns || break done i=$(($i + 1)) j=1 eval_nd_dns || break 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 if valid_domainname_list $new_domain_search; then conf="${conf}search $new_domain_search$NL" elif ! $warn; then syslog err "Invalid domain name in list:" \ "$new_domain_search" fi fi for x in ${new_domain_name_servers}; do conf="${conf}nameserver $x$NL" done if type "$resolvconf" >/dev/null 2>&1; then [ -n "$ifmetric" ] && export IF_METRIC="$ifmetric" printf %s "$conf" | "$resolvconf" -a "$ifname" return $? fi if [ -e "$resolv_conf_dir/$ifname" ]; then rm -f "$resolv_conf_dir/$ifname" fi [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" printf %s "$conf" > "$resolv_conf_dir/$ifname" build_resolv_conf } remove_resolv_conf() { if type "$resolvconf" >/dev/null 2>&1; then "$resolvconf" -d "$ifname" -f else if [ -e "$resolv_conf_dir/$ifname" ]; then rm -f "$resolv_conf_dir/$ifname" fi build_resolv_conf fi } # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) new_domain_name_servers="$new_dhcp6_name_servers" new_domain_search="$new_dhcp6_domain_search" ;; esac if $if_up || [ "$reason" = ROUTERADVERT ]; then add_resolv_conf elif $if_down; then remove_resolv_conf fi dhcpcd5-7.1.0/hooks/29-lookup-hostname000066400000000000000000000014341342162717100174620ustar00rootroot00000000000000# Lookup the hostname in DNS if not set lookup_hostname() { [ -z "$new_ip_address" ] && return 1 # Silly ISC programs love to send error text to stdout if type dig >/dev/null 2>&1; then h=$(dig +short -x $new_ip_address) if [ $? = 0 ]; then echo "$h" | sed 's/\.$//' return 0 fi elif type host >/dev/null 2>&1; then h=$(host $new_ip_address) if [ $? = 0 ]; then echo "$h" \ | sed 's/.* domain name pointer \(.*\)./\1/' return 0 fi elif type getent >/dev/null 2>&1; then h=$(getent hosts $new_ip_address) if [ $? = 0 ]; then echo "$h" | sed 's/[^ ]* *\([^ ]*\).*/\1/' return 0 fi fi return 1 } set_hostname() { if [ -z "${new_host_name}${new_fqdn_name}" ]; then export new_host_name="$(lookup_hostname)" fi } if $if_up; then set_hostname fi dhcpcd5-7.1.0/hooks/30-hostname000066400000000000000000000072271342162717100161510ustar00rootroot00000000000000# 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=localhost} # Some systems don't have hostname(1) _hostname() { if [ -z "${1+x}" ]; then if type hostname >/dev/null 2>&1; then hostname elif [ -r /proc/sys/kernel/hostname ]; then read name /dev/null 2>&1; then sysctl -n kern.hostname elif sysctl kernel.hostname >/dev/null 2>&1; then sysctl -n kernel.hostname else return 1 fi return $? fi # Always prefer hostname(1) if we have it if type hostname >/dev/null 2>&1; then hostname "$1" elif [ -w /proc/sys/kernel/hostname ]; then echo "$1" >/proc/sys/kernel/hostname elif sysctl kern.hostname >/dev/null 2>&1; then sysctl -w "kern.hostname=$1" elif sysctl kernel.hostname >/dev/null 2>&1; then sysctl -w "kernel.hostname=$1" else # We know this will fail, but it will now fail # with an error to stdout hostname "$1" fi } set_hostname_vars() { 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() { # Always load the hostname variable for future use hostname="$(_hostname)" case "$hostname" in ""|"(none)"|localhost|localhost.localdomain|"$hostname_default") return 0;; esac case "$force_hostname" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;; esac set_hostname_vars if [ -n "$old_fqdn" ]; then if ${hfqdn} || ! ${hsort}; then [ "$hostname" = "$old_fqdn" ] else [ "$hostname" = "${old_fqdn%%.*}" ] fi elif [ -n "$old_host_name" ]; then if ${hfqdn}; then if [ -n "$old_domain_name" ] && [ "$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() { need_hostname || return set_hostname_vars 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 [ -n "${hostname_default+x}" ]; 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_up; then set_hostname fi dhcpcd5-7.1.0/hooks/50-dhcpcd-compat000066400000000000000000000017661342162717100170450ustar00rootroot00000000000000# 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" dhcpcd5-7.1.0/hooks/50-ntp.conf000066400000000000000000000065541342162717100160640ustar00rootroot00000000000000# 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 seperate file for DHCP config to avoid stamping on # the master. if [ "$ntp_service" = ntpd ] && type invoke-rc.d >/dev/null 2>&1; then [ -e /var/lib/ntp ] || mkdir /var/lib/ntp : ${ntp_service:=ntp} : ${NTP_DHCP_CONF:=/var/lib/ntp/ntp.conf.dhcp} fi : ${ntp_restart_cmd:=service_condcommand $ntp_service restart} ntp_conf=${NTP_CONF} NL=" " build_ntp_conf() { cf="$state_dir/ntp.conf.$ifname" # Build a list of interfaces interfaces=$(list_interfaces "$ntp_conf_dir") servers= if [ -n "$interfaces" ]; then # Build the header for x in ${interfaces}; do header="$header${header:+, }$x" done # Build a server list srvs=$(cd "$ntp_conf_dir"; key_get_value "server " $interfaces) if [ -n "$srvs" ]; then for x in $(uniqify $srvs); do servers="${servers}server $x$NL" done fi fi # Merge our config into ntp.conf [ -e "$cf" ] && rm -f "$cf" [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" if [ -n "$NTP_DHCP_CONF" ]; then [ -e "$ntp_conf" ] && cp "$ntp_conf" "$cf" ntp_conf="$NTP_DHCP_CONF" elif [ -e "$ntp_conf" ]; then remove_markers "$signature_base" "$signature_base_end" \ "$ntp_conf" > "$cf" fi if [ -n "$servers" ]; then echo "$signature_base${header:+ $from }$header" >> "$cf" printf %s "$servers" >> "$cf" echo "$signature_base_end${header:+ $from }$header" >> "$cf" else [ -e "$ntp_conf" ] && [ -e "$cf" ] || return fi # If we changed anything, restart ntpd if change_file "$ntp_conf" "$cf"; then [ -n "$ntp_restart_cmd" ] && eval $ntp_restart_cmd fi } add_ntp_conf() { cf="$ntp_conf_dir/$ifname" [ -e "$cf" ] && rm "$cf" [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" if [ -n "$new_ntp_servers" ]; then for x in $new_ntp_servers; do echo "server $x" >> "$cf" done fi build_ntp_conf } remove_ntp_conf() { if [ -e "$ntp_conf_dir/$ifname" ]; then rm "$ntp_conf_dir/$ifname" fi build_ntp_conf } # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) new_ntp_servers="$new_dhcp6_sntp_servers" ;; esac if $if_up; then add_ntp_conf elif $if_down; then remove_ntp_conf fi dhcpcd5-7.1.0/hooks/50-yp.conf000066400000000000000000000021531342162717100157020ustar00rootroot00000000000000# 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_up; then make_yp_conf elif $if_down; then restore_yp_conf fi dhcpcd5-7.1.0/hooks/50-ypbind.in000066400000000000000000000034071342162717100162230ustar00rootroot00000000000000# 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 [ "$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 dhcpcd5-7.1.0/hooks/Makefile000066400000000000000000000033701342162717100156230ustar00rootroot00000000000000TOP?= ../ 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 02-dump SCRIPTS+= 20-resolv.conf SCRIPTS+= 30-hostname SCRIPTS+= ${HOOKSCRIPTS} # Some hooks should not be installed by default FILESDIR= ${DATADIR}/dhcpcd/hooks FILES= 10-wpa_supplicant FILES+= 15-timezone FILES+= 29-lookup-hostname FILES+= ${EGHOOKSCRIPTS} .SUFFIXES: .in .in: Makefile ${TOP}/config.mk ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \ ${SED_SYS} ${SED_SCRIPT} ${SED_DATADIR} \ ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \ ${SED_STATUSARG} \ -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 depend: proginstall: ${PROG} ${HOOKSCRIPTS} ${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: ${EGHOOKSCRIPTS} ${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: ${HOOKSCRIPTS} ${INSTALL} -d /tmp/${DISTPREFIX}/dhcpcd-hooks ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} /tmp/${DISTPREFIX}/dhcpcd-hooks ${INSTALL} -m ${NONBINMODE} ${FILES} /tmp/${DISTPREFIX}/dhcpcd-hooks include ${TOP}/Makefile.inc dhcpcd5-7.1.0/hooks/dhcpcd-run-hooks.8.in000066400000000000000000000154201342162717100200300ustar00rootroot00000000000000.\" 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. .\" .Dd February 20, 2018 .Dt DHCPCD-RUN-HOOKS 8 .Os .Sh NAME .Nm dhcpcd-run-hooks .Nd DHCP client configuration script .Sh DESCRIPTION .Nm is used by .Xr dhcpcd 8 to run any system and user defined hook scripts. System hook scripts are found in .Pa @HOOKDIR@ and the user defined hooks are .Pa @SYSCONFDIR@/dhcpcd.enter-hook . and .Pa @SYSCONFDIR@/dhcpcd.exit-hook . The default install supplies hook scripts for configuring .Pa /etc/resolv.conf and the hostname. Your distribution may have included other hook scripts to say configure ntp or ypbind. A test hook is also supplied that simply echos the dhcp variables to the console from DISCOVER message. .Pp The hooks scripts are loaded into the current shell rather than executed in their own process. This allows each hook script, such as .Pa @SYSCONFDIR@/dhcpcd.enter-hook to customise environment variables or provide alternative functions to hooks further down the chain. As such, using the shell builtins .Ic exit , .Ic exec or similar will cause .Nm to exit at that point. .Pp Each time .Nm is invoked, .Ev $interface is set to the interface that .Nm dhcpcd is run on and .Ev $reason is to the reason why .Nm was invoked. DHCP information to be configured is held in variables starting with the word new_ and old DHCP information to be removed is held in variables starting with the word old_. .Nm dhcpcd can display the full list of variables it knows how about by using the .Fl V , -variables argument. .Pp Here's a list of reasons why .Nm could be invoked: .Bl -tag -width EXPIREXXXEXPIRE6 .It Dv PREINIT dhcpcd is starting up and any pre-initialisation should be done. .It Dv CARRIER dhcpcd has detected the carrier is up. This is generally just a notification and no action need be taken. .It Dv NOCARRIER dhcpcd lost the carrier. The cable may have been unplugged or association to the wireless point lost. .It Dv INFORM | Dv INFORM6 dhcpcd informed a DHCP server about it's address and obtained other configuration details. .It Dv BOUND | Dv BOUND6 dhcpcd obtained a new lease from a DHCP server. .It Dv RENEW | Dv RENEW6 dhcpcd renewed it's lease. .It Dv REBIND | Dv REBIND6 dhcpcd has rebound to a new DHCP server. .It Dv REBOOT | Dv REBOOT6 dhcpcd successfully requested a lease from a DHCP server. .It Dv DELEGATED6 dhcpcd assigned a delegated prefix to the interface. .It Dv IPV4LL dhcpcd 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 DUMP dhcpcd has been asked to dump the last lease for the interface. .It Dv TEST dhcpcd received an OFFER from a DHCP server but will not configure the interface. This is primarily used to test the variables are filled correctly for the script to process them. .El .Sh ENVIRONMENT .Nm dhcpcd will clear the environment variables aside from .Ev $PATH and .Ev $RC_SVCNAME . The following variables will then be set, along with any protocol supplied ones. .Bl -tag -width xnew_delegated_dhcp6_prefix .It Ev $interface the name of the interface. .It Ev $protocol the protocol that triggered the event. .It Ev $reason as described above. .It Ev $pid the pid of .Nm dhcpcd . .It Ev $ifcarrier the link status of .Ev $interface : .Dv unknown , .Dv up or .Dv down . .It Ev $ifmetric .Ev $interface preference, lower is better. .It Ev $ifwireless .Dv 1 if .Ev $interface is wireless, otherwise .Dv 0 . .It Ev $ifflags .Ev $interface flags. .It Ev $ifmtu .Ev $interface MTU. .It Ev $ifssid the name of the SSID the .Ev interface is connected to. .It Ev $interface_order A list of interfaces, in order of preference. .It Ev $if_up .Dv true if the .Ev interface is up, otherwise .Dv false . .It Ev $if_down .Dv true if the .Ev interface is down, otherwise .Dv false . .It Ev $af_waiting Address family waiting for, as defined in .Xr dhcpcd.conf 5 . .It Ev $profile the name of the profile selected from .Xr dhcpcd.conf 5 . .It Ev $new_delegated_dhcp6_prefix space separated list of delegated prefixes. .El .Sh FILES When .Nm runs, it loads .Pa @SYSCONFDIR@/dhcpcd.enter-hook and any scripts found in .Pa @HOOKDIR@ in a lexical order and then finally .Pa @SYSCONFDIR@/dhcpcd.exit-hook .Sh SEE ALSO .Xr dhcpcd 8 .Sh AUTHORS .An Roy Marples Aq Mt roy@marples.name .Sh BUGS Please report them to .Lk http://roy.marples.name/projects/dhcpcd .Sh SECURITY CONSIDERATIONS .Nm dhcpcd will validate the content of each option against its encoding. For string, ascii, raw or binhex encoding it's up to the user to validate it for the intended purpose. .Pp When used in a shell script, each variable must be quoted correctly. dhcpcd5-7.1.0/hooks/dhcpcd-run-hooks.in000066400000000000000000000177631342162717100176760ustar00rootroot00000000000000#!/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@/dhcpcd _detected_init=false : ${if_up:=false} : ${if_down:=false} : ${syslog_debug:=false} # Ensure that all arguments are unique uniqify() { result= for i do case " $result " in *" $i "*);; *) result="$result${result:+ }$i";; esac done echo "$result" } # List interface config files in a directory. # If dhcpcd is running as a single instance then it will have a list of # interfaces in the preferred order. # Otherwise we just use what we have. list_interfaces() { ifaces= for i in $interface_order; do for x in "$1"/$i.*; do [ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}" done done for x in "$1"/*; do [ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}" done uniqify $ifaces } # Trim function trim() { var="$*" var=${var#"${var%%[![:space:]]*}"} var=${var%"${var##*[![:space:]]}"} if [ -z "$var" ]; then # So it seems our shell doesn't support wctype(3) patterns # Fall back to sed var=$(echo "$*" | sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//') fi printf %s "$var" } # We normally use sed to extract values using a key from a list of files # but sed may not always be available at the time. key_get_value() { key="$1" shift if type sed >/dev/null 2>&1; then sed -n "s/^$key//p" $@ else for x do while read line; do case "$line" in "$key"*) echo "${line##$key}";; esac done < "$x" done fi } # We normally use sed to remove markers from a configuration file # but sed may not always be available at the time. remove_markers() { m1="$1" m2="$2" in_marker=0 shift; shift if type sed >/dev/null 2>&1; then sed "/^$m1/,/^$m2/d" $@ else for x do while read line; do case "$line" in "$m1"*) in_marker=1;; "$m2"*) in_marker=0;; *) [ $in_marker = 0 ] && echo "$line";; esac done < "$x" done fi } # Compare two files. comp_file() { [ -e "$1" ] && [ -e "$2" ] || return 1 if type cmp >/dev/null 2>&1; then cmp -s "$1" "$2" elif type diff >/dev/null 2>&1; then diff -q "$1" "$2" >/dev/null else # Hopefully we're only working on small text files ... [ "$(cat "$1")" = "$(cat "$2")" ] fi } # Compare two files. # If different, replace first with second otherwise remove second. change_file() { if [ -e "$1" ]; then if comp_file "$1" "$2"; then rm -f "$2" return 1 fi fi cat "$2" > "$1" rm -f "$2" return 0 } # Compare two files. # If different, copy or link depending on target type copy_file() { if [ -h "$2" ]; then [ "$(readlink "$2")" = "$1" ] && return 1 ln -sf "$1" "$2" else comp_file "$1" "$2" && return 1 cat "$1" >"$2" fi } # Save a config file save_conf() { if [ -f "$1" ]; then rm -f "$1-pre.$interface" cat "$1" > "$1-pre.$interface" fi } # Restore a config file restore_conf() { [ -f "$1-pre.$interface" ] || return 1 cat "$1-pre.$interface" > "$1" rm -f "$1-pre.$interface" } # Write a syslog entry syslog() { lvl="$1" if [ "$lvl" = debug ]; then ${syslog_debug} || return 0 fi [ -n "$lvl" ] && shift [ -n "$*" ] || return 0 case "$lvl" in err|error) echo "$interface: $*" >&2;; *) echo "$interface: $*";; esac if type logger >/dev/null 2>&1; then logger -i -p daemon."$lvl" -t dhcpcd-run-hooks "$interface: $*" fi } # Check for a valid name as per RFC952 and RFC1123 section 2.1 valid_domainname() { name="$1" [ -z "$name" ] || [ ${#name} -gt 255 ] && return 1 while [ -n "$name" ]; do label="${name%%.*}" [ -z "$label" ] || [ ${#label} -gt 63 ] && return 1 case "$label" in -*|_*|*-|*_) return 1;; *[![:alnum:]_-]*) return 1;; "$name") return 0;; esac name="${name#*.}" done return 0 } valid_domainname_list() { for name do valid_domainname "$name" || return $? done return 0 } # With the advent of alternative init systems, it's possible to have # more than one installed. So we need to try and guess what one we're # using unless overriden by configure. detect_init() { _service_exists="@SERVICEEXISTS@" _service_cmd="@SERVICECMD@" _service_status="@SERVICESTATUS@" [ -n "$_service_cmd" ] && return 0 if $_detected_init; then [ -n "$_service_cmd" ] return $? fi # Detect the running init system. # As systemd and OpenRC can be installed on top of legacy init # systems we try to detect them first. status="@STATUSARG@" : ${status:=status} if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then _service_exists="/bin/systemctl --quiet is-enabled \$1.service" _service_status="/bin/systemctl --quiet is-active \$1.service" _service_cmd="/bin/systemctl \$2 \$1.service" elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then _service_exists="/usr/bin/systemctl --quiet is-enabled \$1.service" _service_status="/usr/bin/systemctl --quiet is-active \$1.service" _service_cmd="/usr/bin/systemctl \$2 \$1.service" elif [ -x /sbin/rc-service ] && { [ -s /libexec/rc/init.d/softlevel ] || [ -s /run/openrc/softlevel ]; } then _service_exists="/sbin/rc-service -e \$1" _service_cmd="/sbin/rc-service \$1 -- -D \$2" elif [ -x /usr/sbin/invoke-rc.d ]; then _service_exists="/usr/sbin/invoke-rc.d --query --quiet \$1 start >/dev/null 2>&1 || [ \$? = 104 ]" _service_cmd="/usr/sbin/invoke-rc.d \$1 \$2" elif [ -x /sbin/service ]; then _service_exists="/sbin/service \$1 >/dev/null 2>&1" _service_cmd="/sbin/service \$1 \$2" elif [ -x /usr/sbin/service ]; then _service_exists="/usr/sbin/service \$1 $status >/dev/null 2>&1" _service_cmd="/usr/sbin/service \$1 \$2" elif [ -x /bin/sv ]; then _service_exists="/bin/sv status \$1 >/dev/null 2>&1" _service_cmd="/bin/sv \$2 \$1" elif [ -x /usr/bin/sv ]; then _service_exists="/usr/bin/sv status \$1 >/dev/null 2>&1" _service_cmd="/usr/bin/sv \$2 \$1" elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then _service_exists="[ -x /etc/rc.d/rc.\$1 ]" _service_cmd="/etc/rc.d/rc.\$1 \$2" _service_status="/etc/rc.d/rc.\$1 status >/dev/null 2>&1" else for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do if [ -d $x ]; then _service_exists="[ -x $x/\$1 ]" _service_cmd="$x/\$1 \$2" _service_status="$x/\$1 $status >/dev/null 2>&1" break fi done if [ -e /etc/arch-release ]; then _service_status="[ -e /var/run/daemons/\$1 ]" elif [ "$x" = "/etc/rc.d" ] && [ -e /etc/rc.d/rc.subr ]; then _service_status="$x/\$1 check >/dev/null 2>&1" fi fi _detected_init=true if [ -z "$_service_cmd" ]; then syslog err "could not detect a useable init system" return 1 fi return 0 } # Check a system service exists service_exists() { if [ -z "$_service_exists" ]; then detect_init || return 1 fi eval $_service_exists } # Send a command to a system service service_cmd() { if [ -z "$_service_cmd" ]; then detect_init || return 1 fi eval $_service_cmd } # Send a command to a system service if it is running service_status() { if [ -z "$_service_cmd" ]; then detect_init || return 1 fi if [ -n "$_service_status" ]; then eval $_service_status else service_command $1 status >/dev/null 2>&1 fi } # Handy macros for our hooks service_command() { service_exists $1 && service_cmd $1 $2 } service_condcommand() { service_exists $1 && service_status $1 && service_cmd $1 $2 } # We source each script into this one so that scripts run earlier can # remove variables from the environment so later scripts don't see them. # Thus, the user can create their dhcpcd.enter/exit-hook script to configure # /etc/resolv.conf how they want and stop the system scripts ever updating it. for hook in \ @SYSCONFDIR@/dhcpcd.enter-hook \ @HOOKDIR@/* \ @SYSCONFDIR@/dhcpcd.exit-hook do for skip in $skip_hooks; do case "$hook" in */*~) continue 2;; */"$skip") continue 2;; */[0-9][0-9]"-$skip") continue 2;; */[0-9][0-9]"-$skip.sh") continue 2;; esac done if [ -f "$hook" ]; then . "$hook" fi done dhcpcd5-7.1.0/iconfig.mk000066400000000000000000000003531342162717100150050ustar00rootroot00000000000000# Nasty hack so that make clean works without configure being run # Requires gmake4 TOP?= . _CONFIG_MK!= test -e ${TOP}/config.mk && \ echo config.mk || echo config-null.mk CONFIG_MK?= ${_CONFIG_MK} include ${TOP}/${CONFIG_MK} dhcpcd5-7.1.0/src/000077500000000000000000000000001342162717100136245ustar00rootroot00000000000000dhcpcd5-7.1.0/src/GNUmakefile000066400000000000000000000005561342162717100157040ustar00rootroot00000000000000# 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 dhcpcd5-7.1.0/src/Makefile000066400000000000000000000072671342162717100153000ustar00rootroot00000000000000# 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} DHCPCD_DEFS?= dhcpcd-definitions.conf PCOMPAT_SRCS= ${COMPAT_SRCS:compat/%=${TOP}/compat/%} PCRYPT_SRCS= ${CRYPT_SRCS:compat/%=${TOP}/compat/%} OBJS+= ${SRCS:.c=.o} ${PCRYPT_SRCS:.c=.o} ${PCOMPAT_SRCS:.c=.o} MAN5= dhcpcd.conf.5 MAN8= dhcpcd.8 CLEANFILES= dhcpcd.conf.5 dhcpcd.8 FILES= dhcpcd.conf FILESDIR= ${SYSCONFDIR} DEPEND!= test -e .depend && echo ".depend" || echo "" CLEANFILES+= *.tar.xz .PHONY: import import-bsd dev test .SUFFIXES: .in .in: Makefile ${TOP}/config.mk ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \ ${SED_SYS} ${SED_SCRIPT} ${SED_DATADIR} \ ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \ ${SED_STATUSARG} \ $< > $@ all: ${TOP}/config.h ${PROG} ${SCRIPTS} ${MAN5} ${MAN8} for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done dev: cd dev && ${MAKE} .c.o: Makefile ${TOP}/config.mk ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ CLEANFILES+= dhcpcd-embedded.h dhcpcd-embedded.c dhcpcd-embedded.h: genembedh ${DHCPCD_DEFS} dhcpcd-embedded.h.in ${HOST_SH} ${.ALLSRC} $^ > $@ dhcpcd-embedded.c: genembedc ${DHCPCD_DEFS} dhcpcd-embedded.c.in ${HOST_SH} ${.ALLSRC} $^ > $@ if-options.c: dhcpcd-embedded.h .depend: ${SRCS} ${COMPAT_SRCS} ${CRYPT_SRCS} ${CC} ${CPPFLAGS} -MM ${SRCS} ${COMPAT_SRCS} ${CRYPT_SRCS} > .depend depend: .depend ${PROG}: ${DEPEND} ${OBJS} ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} lint: ${LINT} -Suz ${CPPFLAGS} ${SRCS} ${PCRYPT_SRCS} ${PCOMPAT_SRCS} _embeddedinstall: ${DHCPCD_DEFS} ${INSTALL} -d ${DESTDIR}${LIBEXECDIR} ${INSTALL} -m ${CONFMODE} ${DHCPCD_DEFS} ${DESTDIR}${LIBEXECDIR} _proginstall: ${PROG} ${INSTALL} -d ${DESTDIR}${SBINDIR} ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${SBINDIR} ${INSTALL} -d ${DESTDIR}${DBDIR} proginstall: _proginstall ${EMBEDDEDINSTALL} for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done _maninstall: ${MAN5} ${MAN8} ${INSTALL} -d ${DESTDIR}${MANDIR}/man5 ${INSTALL} -m ${MANMODE} ${MAN5} ${DESTDIR}${MANDIR}/man5 ${INSTALL} -d ${DESTDIR}${MANDIR}/man8 ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}/man8 _confinstall: ${INSTALL} -d ${DESTDIR}${SYSCONFDIR} # Install a new default config if not present test -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.conf || \ ${INSTALL} -m ${CONFMODE} dhcpcd.conf ${DESTDIR}${SYSCONFDIR} # Attempt to move files from sysconfig to dbdir if [ ! -e ${DESTDIR}${DBDIR}/duid -a \ -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.duid ]; \ then \ mv ${DESTDIR}${SYSCONFDIR}/dhcpcd.duid \ ${DESTDIR}${DBDIR}/duid; \ fi if [ ! -e ${DESTDIR}${DBDIR}/secret -a \ -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.secret ]; \ then \ mv ${DESTDIR}${SYSCONFDIR}/dhcpcd.secret \ ${DESTDIR}${DBDIR}/secret; \ fi # Move leases to new location for lease in ${DESTDIR}${DBDIR}/../dhcpcd-*.lease*; do \ [ -f "$$lease" ] || continue; \ newlease=$$(basename "$$lease" | ${SED} -e "s/dhcpcd-//"); \ mv "$$lease" ${DESTDIR}${DBDIR}/"$$newlease"; \ done # Move RDM monotonic to new location if [ ! -e ${DESTDIR}${DBDIR}/rdm_monotonic -a \ -e ${DESTDIR}${DBDIR}/../dhcpcd-rdm.monotonic ]; \ then \ mv ${DESTDIR}${DBDIR}/../dhcpcd-rdm.monotonic \ ${DESTDIR}${DBDIR}/rdm_monotonic; \ fi eginstall: install: proginstall _maninstall _confinstall eginstall clean: rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done distclean: clean rm -f .depend include ${TOP}/Makefile.inc dhcpcd5-7.1.0/src/arp.c000066400000000000000000000335201342162717100145550ustar00rootroot00000000000000/* * dhcpcd - ARP handler * Copyright (c) 2006-2019 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 #define ELOOP_QUEUE 5 #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" #if defined(ARP) #define ARP_LEN \ (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); ssize_t arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip) { uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; size_t len; uint8_t *p; const struct iarp_state *state; ar.ar_hrd = htons(ifp->family); ar.ar_pro = htons(ETHERTYPE_IP); ar.ar_hln = ifp->hwlen; ar.ar_pln = sizeof(sip); ar.ar_op = htons(ARPOP_REQUEST); p = arp_buffer; len = 0; #define CHECK(fun, b, l) \ do { \ if (len + (l) > sizeof(arp_buffer)) \ goto eexit; \ fun(p, (b), (l)); \ p += (l); \ len += (l); \ } while (/* CONSTCOND */ 0) #define APPEND(b, l) CHECK(memcpy, b, l) #define ZERO(l) CHECK(memset, 0, l) APPEND(&ar, sizeof(ar)); APPEND(ifp->hwaddr, ifp->hwlen); APPEND(&sip, sizeof(sip)); ZERO(ifp->hwlen); APPEND(&tip, sizeof(tip)); state = ARP_CSTATE(ifp); return bpf_send(ifp, state->bpf_fd, ETHERTYPE_ARP, arp_buffer, len); eexit: errno = ENOBUFS; return -1; } static void arp_packet(struct interface *ifp, uint8_t *data, size_t len) { const struct interface *ifn; struct arphdr ar; struct arp_msg arm; const struct iarp_state *state; struct arp_state *astate, *astaten; uint8_t *hw_s, *hw_t; /* We must have a full ARP header */ if (len < sizeof(ar)) return; memcpy(&ar, data, sizeof(ar)); /* These checks are enforced in the BPF filter. */ #if 0 /* Families must match */ if (ar.ar_hrd != htons(ifp->family)) return; /* Protocol must be IP. */ if (ar.ar_pro != htons(ETHERTYPE_IP)) continue; /* lladdr length matches */ if (ar.ar_hln != ifp->hwlen) continue; /* Protocol length must match in_addr_t */ if (ar.ar_pln != sizeof(arm.sip.s_addr)) return; /* Only these types are recognised */ if (ar.ar_op != htons(ARPOP_REPLY) && ar.ar_op != htons(ARPOP_REQUEST)) continue; #endif /* Get pointers to the hardware addresses */ hw_s = data + sizeof(ar); hw_t = hw_s + ar.ar_hln + ar.ar_pln; /* Ensure we got all the data */ if ((size_t)((hw_t + ar.ar_hln + ar.ar_pln) - data) > len) return; /* Ignore messages from ourself */ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { if (ar.ar_hln == ifn->hwlen && memcmp(hw_s, ifn->hwaddr, ifn->hwlen) == 0) break; } if (ifn) { #ifdef ARP_DEBUG logdebugx("%s: ignoring ARP from self", ifp->name); #endif return; } /* Copy out the HW and IP addresses */ memcpy(&arm.sha, hw_s, ar.ar_hln); memcpy(&arm.sip.s_addr, hw_s + ar.ar_hln, ar.ar_pln); memcpy(&arm.tha, hw_t, ar.ar_hln); memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln); /* Run the conflicts */ state = ARP_CSTATE(ifp); TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) { if (arm.sip.s_addr != astate->addr.s_addr && arm.tip.s_addr != astate->addr.s_addr) continue; if (astate->conflicted_cb) astate->conflicted_cb(astate, &arm); } } void arp_close(struct interface *ifp) { struct iarp_state *state; if ((state = ARP_STATE(ifp)) != NULL && state->bpf_fd != -1) { eloop_event_delete(ifp->ctx->eloop, state->bpf_fd); bpf_close(ifp, state->bpf_fd); state->bpf_fd = -1; state->bpf_flags |= BPF_EOF; } } static void arp_tryfree(struct interface *ifp) { struct iarp_state *state = ARP_STATE(ifp); /* If there are no more ARP states, close the socket. */ if (TAILQ_FIRST(&state->arp_states) == NULL) { arp_close(ifp); if (state->bpf_flags & BPF_READING) state->bpf_flags |= BPF_EOF; else { free(state); ifp->if_data[IF_DATA_ARP] = NULL; } } else { if (bpf_arp(ifp, state->bpf_fd) == -1) logerr(__func__); } } static void arp_read(void *arg) { struct interface *ifp = arg; struct iarp_state *state; uint8_t buf[ARP_LEN]; ssize_t bytes; /* 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. */ state = ARP_STATE(ifp); state->bpf_flags &= ~BPF_EOF; state->bpf_flags |= BPF_READING; while (!(state->bpf_flags & BPF_EOF)) { bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf), &state->bpf_flags); if (bytes == -1) { logerr("%s: %s", __func__, ifp->name); arp_close(ifp); break; } arp_packet(ifp, buf, (size_t)bytes); /* Check we still have a state after processing. */ if ((state = ARP_STATE(ifp)) == NULL) break; } if (state != NULL) { state->bpf_flags &= ~BPF_READING; /* Try and free the state if nothing left to do. */ arp_tryfree(ifp); } } int arp_open(struct interface *ifp) { struct iarp_state *state; state = ARP_STATE(ifp); if (state->bpf_fd == -1) { state->bpf_fd = bpf_open(ifp, bpf_arp); if (state->bpf_fd == -1) { logerr("%s: %s", __func__, ifp->name); return -1; } eloop_event_add(ifp->ctx->eloop, state->bpf_fd, arp_read, ifp); } return state->bpf_fd; } static void arp_probed(void *arg) { struct arp_state *astate = arg; astate->probed_cb(astate); } static void arp_probe1(void *arg) { struct arp_state *astate = arg; struct interface *ifp = astate->iface; struct timespec tv; if (++astate->probes < PROBE_NUM) { tv.tv_sec = PROBE_MIN; tv.tv_nsec = (suseconds_t)arc4random_uniform( (PROBE_MAX - PROBE_MIN) * NSEC_PER_SEC); timespecnorm(&tv); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe1, astate); } else { tv.tv_sec = ANNOUNCE_WAIT; tv.tv_nsec = 0; eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probed, astate); } 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, timespec_to_double(&tv)); if (arp_request(ifp, 0, astate->addr.s_addr) == -1) logerr(__func__); } void arp_probe(struct arp_state *astate) { if (arp_open(astate->iface) == -1) { logerr(__func__); return; } else { const struct iarp_state *state = ARP_CSTATE(astate->iface); if (bpf_arp(astate->iface, state->bpf_fd) == -1) logerr(__func__); } astate->probes = 0; logdebugx("%s: probing for %s", astate->iface->name, inet_ntoa(astate->addr)); arp_probe1(astate); } #endif /* ARP */ 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; 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); if (arp_request(ifp, astate->addr.s_addr, astate->addr.s_addr) == -1) logerr(__func__); eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT, astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced, astate); } /* * XXX FIXME * Kernels supporting RFC5227 will announce the address when it's * added. * dhcpcd should not announce when this happens, nor need to open * a BPF socket for it. * Also, an address might be added to a non preferred inteface when * the same address exists on a preferred one so we need to instruct * the kernel not to announce the address somehow. */ void arp_announce(struct arp_state *astate) { struct iarp_state *state; struct interface *ifp; struct arp_state *a2; int r; if (arp_open(astate->iface) == -1) { logerr(__func__); return; } /* 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)); } } astate->claims = 0; arp_announce1(astate); } void arp_announceaddr(struct dhcpcd_ctx *ctx, struct in_addr *ia) { struct interface *ifp; struct ipv4_addr *iaf; struct arp_state *astate; TAILQ_FOREACH(ifp, ctx->ifaces, next) { iaf = ipv4_iffindaddr(ifp, ia, NULL); #ifdef IN_IFF_NOTUSEABLE if (iaf && !(iaf->addr_flags & IN_IFF_NOTUSEABLE)) #else if (iaf) #endif break; } if (ifp == NULL) return; astate = arp_find(ifp, ia); if (astate != NULL) arp_announce(astate); } void arp_ifannounceaddr(struct interface *ifp, struct in_addr *ia) { struct arp_state *astate; astate = arp_new(ifp, ia); if (astate != NULL) arp_announce(astate); } void arp_report_conflicted(const struct arp_state *astate, const struct arp_msg *amsg) { if (amsg != NULL) { char buf[HWADDR_LEN * 3]; logerrx("%s: hardware address %s claims %s", astate->iface->name, hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)), inet_ntoa(astate->failed)); } else logerrx("%s: DAD detected %s", astate->iface->name, inet_ntoa(astate->failed)); } struct arp_state * arp_find(struct interface *ifp, const struct in_addr *addr) { struct iarp_state *state; struct arp_state *astate; if ((state = ARP_STATE(ifp)) == NULL) goto out; TAILQ_FOREACH(astate, &state->arp_states, next) { if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp) return astate; } out: errno = ESRCH; return NULL; } struct arp_state * arp_new(struct interface *ifp, const struct in_addr *addr) { struct iarp_state *state; struct arp_state *astate; if ((state = ARP_STATE(ifp)) == NULL) { ifp->if_data[IF_DATA_ARP] = malloc(sizeof(*state)); state = ARP_STATE(ifp); if (state == NULL) { logerr(__func__); return NULL; } state->bpf_fd = -1; state->bpf_flags = 0; TAILQ_INIT(&state->arp_states); } else { if (addr && (astate = arp_find(ifp, addr))) return astate; } if ((astate = calloc(1, sizeof(*astate))) == NULL) { logerr(__func__); return NULL; } astate->iface = ifp; if (addr) astate->addr = *addr; state = ARP_STATE(ifp); TAILQ_INSERT_TAIL(&state->arp_states, astate, next); if (bpf_arp(ifp, state->bpf_fd) == -1) logerr(__func__); /* try and continue */ return astate; } void arp_cancel(struct arp_state *astate) { eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate); } void arp_free(struct arp_state *astate) { struct interface *ifp; struct iarp_state *state; if (astate == NULL) return; ifp = astate->iface; eloop_timeout_delete(ifp->ctx->eloop, NULL, astate); state = ARP_STATE(ifp); TAILQ_REMOVE(&state->arp_states, astate, next); if (astate->free_cb) astate->free_cb(astate); free(astate); arp_tryfree(ifp); } static void arp_free_but1(struct interface *ifp, struct arp_state *astate) { struct iarp_state *state; if ((state = ARP_STATE(ifp)) != NULL) { struct arp_state *p, *n; TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) { if (p != astate) arp_free(p); } } } void arp_free_but(struct arp_state *astate) { arp_free_but1(astate->iface, astate); } void arp_drop(struct interface *ifp) { arp_free_but1(ifp, NULL); arp_close(ifp); } void arp_handleifa(int cmd, struct ipv4_addr *addr) { struct iarp_state *state; struct arp_state *astate, *asn; state = ARP_STATE(addr->iface); if (state == NULL) return; TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, asn) { if (astate->addr.s_addr != addr->addr.s_addr) continue; if (cmd == RTM_DELADDR) arp_free(astate); #ifdef IN_IFF_DUPLICATED if (cmd != RTM_NEWADDR) continue; if (addr->addr_flags & IN_IFF_DUPLICATED) { if (astate->conflicted_cb) astate->conflicted_cb(astate, NULL); } else if (!(addr->addr_flags & IN_IFF_NOTUSEABLE)) { if (astate->probed_cb) astate->probed_cb(astate); } #endif } } dhcpcd5-7.1.0/src/arp.h000066400000000000000000000070301342162717100145570ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "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. */ #if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 799003900 #define KERNEL_RFC5227 #endif #endif struct arp_msg { uint16_t op; unsigned char sha[HWADDR_LEN]; struct in_addr sip; unsigned char tha[HWADDR_LEN]; struct in_addr tip; }; struct arp_state { TAILQ_ENTRY(arp_state) next; struct interface *iface; void (*probed_cb)(struct arp_state *); void (*announced_cb)(struct arp_state *); void (*conflicted_cb)(struct arp_state *, const struct arp_msg *); void (*free_cb)(struct arp_state *); struct in_addr addr; int probes; int claims; struct in_addr failed; }; TAILQ_HEAD(arp_statehead, arp_state); struct iarp_state { int bpf_fd; unsigned int bpf_flags; 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 int arp_open(struct interface *); ssize_t arp_request(const struct interface *, in_addr_t, in_addr_t); void arp_probe(struct arp_state *); void arp_close(struct interface *); void arp_report_conflicted(const struct arp_state *, const struct arp_msg *); struct arp_state *arp_new(struct interface *, const struct in_addr *); struct arp_state *arp_find(struct interface *, const struct in_addr *); void arp_announce(struct arp_state *); void arp_announceaddr(struct dhcpcd_ctx *, struct in_addr *); void arp_ifannounceaddr(struct interface *, struct in_addr *); void arp_cancel(struct arp_state *); void arp_free(struct arp_state *); void arp_free_but(struct arp_state *); void arp_drop(struct interface *); void arp_handleifa(int, struct ipv4_addr *); #endif /* ARP */ #endif /* ARP_H */ dhcpcd5-7.1.0/src/auth.c000066400000000000000000000420111342162717100147270ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "auth.h" #include "dhcp.h" #include "dhcp6.h" #include "dhcpcd.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 DHPCPv4 */ if (data < m || data > m + mlen || data + dlen > m + mlen) { errno = ERANGE; return NULL; } d = data; protocol = *d++; algorithm = *d++; rdm = *d++; if (!(auth->options & DHCPCD_AUTH_SEND)) { /* If we didn't send any authorisation, it can only be a * reconfigure key */ if (protocol != AUTH_PROTO_RECONFKEY) { errno = EINVAL; return NULL; } } else if (protocol != auth->protocol || algorithm != auth->algorithm || rdm != auth->rdm) { /* As we don't require authentication, we should still * accept a reconfigure key */ if (protocol != AUTH_PROTO_RECONFKEY || auth->options & DHCPCD_AUTH_REQUIRE) { errno = EPERM; return NULL; } } dlen -= 3; memcpy(&replay, d, sizeof(replay)); replay = ntohll(replay); /* * 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 (memcmp(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; } static uint64_t get_next_rdm_monotonic_counter(struct auth *auth) { FILE *fp; uint64_t rdm; #ifdef LOCK_EX int flocked; #endif fp = fopen(RDM_MONOFILE, "r+"); if (fp == NULL) { if (errno != ENOENT) return ++auth->last_replay; /* report error? */ fp = fopen(RDM_MONOFILE, "w"); if (fp == NULL) return ++auth->last_replay; /* report error? */ #ifdef LOCK_EX flocked = flock(fileno(fp), LOCK_EX); #endif rdm = 0; } else { #ifdef LOCK_EX flocked = flock(fileno(fp), LOCK_EX); #endif if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1) rdm = 0; /* truncated? report error? */ } rdm++; if (fseek(fp, 0, SEEK_SET) == -1 || ftruncate(fileno(fp), 0) == -1 || fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 || fflush(fp) == EOF) { if (!auth->last_replay_set) { auth->last_replay = rdm; auth->last_replay_set = 1; } else rdm = ++auth->last_replay; /* report error? */ } #ifdef LOCK_EX if (flocked == 0) flock(fileno(fp), LOCK_UN); #endif fclose(fp); return rdm; } #define 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 auth *auth) { if (auth->options & DHCPCD_AUTH_RDM_COUNTER) return get_next_rdm_monotonic_counter(auth); return get_next_rdm_monotonic_clock(auth); } /* * Encode a DHCP message. * Either we know which token to use from the server response * or we are using a basic configuration token. * token is the token to encrypt with. * m and mlen refer to the whole message. * mp is the DHCP type, pass it 4 or 6. * mt is the DHCP message type. * data and dlen refer to the authentication option within the message. */ ssize_t dhcp_auth_encode(struct auth *auth, const struct token *t, 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(auth); break; default: /* This block appeases gcc, clang doesn't need it */ rdm = get_next_rdm_monotonic(auth); break; } rdm = htonll(rdm); memcpy(data, &rdm, 8); } 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 */ } dhcpcd5-7.1.0/src/auth.h000066400000000000000000000052601342162717100147410ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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); ssize_t dhcp_auth_encode(struct auth *, const struct token *, void *, size_t, int, int, void *, size_t); #endif dhcpcd5-7.1.0/src/bpf.c000066400000000000000000000434211342162717100145430ustar00rootroot00000000000000/* * dhcpcd: BPF arp and bootp filtering * Copyright (c) 2006-2019 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #ifdef __linux__ /* Special BPF snowflake. */ #include #define bpf_insn sock_filter #else #include #endif #include #include #include #include #include #include #include "common.h" #include "arp.h" #include "bpf.h" #include "dhcp.h" #include "if.h" #include "logerr.h" #define ARP_ADDRS_MAX 3 /* 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->family) { case ARPHRD_ETHER: return sizeof(struct ether_header); default: return 0; } } #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"; int bpf_open(struct interface *ifp, int (*filter)(struct interface *, int)) { struct ipv4_state *state; int fd = -1; struct ifreq ifr; int ibuf_len = 0; size_t buf_len; struct bpf_version pv; #ifdef BIOCIMMEDIATE unsigned int flags; #endif #ifndef O_CLOEXEC int fd_opts; #endif #ifdef _PATH_BPF fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK #ifdef O_CLOEXEC | O_CLOEXEC #endif ); #else char device[32]; int n = 0; do { snprintf(device, sizeof(device), "/dev/bpf%d", n++); fd = open(device, O_RDWR | O_NONBLOCK #ifdef O_CLOEXEC | O_CLOEXEC #endif ); } while (fd == -1 && errno == EBUSY); #endif if (fd == -1) return -1; #ifndef O_CLOEXEC if ((fd_opts = fcntl(fd, F_GETFD)) == -1 || fcntl(fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1) { close(fd); return -1; } #endif memset(&pv, 0, sizeof(pv)); if (ioctl(fd, BIOCVERSION, &pv) == -1) goto eexit; if (pv.bv_major != BPF_MAJOR_VERSION || pv.bv_minor < BPF_MINOR_VERSION) { logerrx("BPF version mismatch - recompile"); goto eexit; } if (filter(ifp, fd) != 0) goto eexit; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) == -1) goto eexit; /* Get the required BPF buffer length from the kernel. */ if (ioctl(fd, BIOCGBLEN, &ibuf_len) == -1) goto eexit; buf_len = (size_t)ibuf_len; state = ipv4_getstate(ifp); if (state == NULL) goto eexit; if (state->buffer_size != buf_len) { void *nb; if ((nb = realloc(state->buffer, buf_len)) == NULL) goto eexit; state->buffer = nb; state->buffer_size = buf_len; } #ifdef BIOCIMMEDIATE flags = 1; if (ioctl(fd, BIOCIMMEDIATE, &flags) == -1) goto eexit; #endif return fd; eexit: close(fd); return -1; } /* 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 interface *ifp, int fd, void *data, size_t len, unsigned int *flags) { ssize_t fl = (ssize_t)bpf_frame_header_len(ifp); ssize_t bytes; struct ipv4_state *state = IPV4_STATE(ifp); struct bpf_hdr packet; const char *payload; *flags &= ~BPF_EOF; for (;;) { if (state->buffer_len == 0) { bytes = read(fd, state->buffer, state->buffer_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(fd, 0, SEEK_SET); continue; } #endif if (bytes == -1 || bytes == 0) return bytes; state->buffer_len = (size_t)bytes; state->buffer_pos = 0; } bytes = -1; memcpy(&packet, state->buffer + state->buffer_pos, sizeof(packet)); if (state->buffer_pos + packet.bh_caplen + packet.bh_hdrlen > state->buffer_len) goto next; /* Packet beyond buffer, drop. */ payload = state->buffer + state->buffer_pos + packet.bh_hdrlen + fl; bytes = (ssize_t)packet.bh_caplen - fl; if ((size_t)bytes > len) bytes = (ssize_t)len; memcpy(data, payload, (size_t)bytes); next: state->buffer_pos += BPF_WORDALIGN(packet.bh_hdrlen + packet.bh_caplen); if (state->buffer_pos >= state->buffer_len) { state->buffer_len = state->buffer_pos = 0; *flags |= BPF_EOF; } if (bytes != -1) return bytes; } /* NOTREACHED */ } int bpf_attach(int fd, void *filter, unsigned int filter_len) { struct bpf_program pf; /* Install the filter. */ memset(&pf, 0, sizeof(pf)); pf.bf_insns = filter; pf.bf_len = filter_len; return ioctl(fd, BIOCSETF, &pf); } #endif #ifndef __sun /* SunOS is special too - sending via BPF goes nowhere. */ ssize_t bpf_send(const struct interface *ifp, int fd, uint16_t protocol, const void *data, size_t len) { struct iovec iov[2]; struct ether_header eh; switch(ifp->family) { case ARPHRD_ETHER: memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost)); memcpy(&eh.ether_shost, 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(fd, iov, 2); } #endif int bpf_close(struct interface *ifp, int fd) { struct ipv4_state *state = IPV4_STATE(ifp); /* Rewind the buffer on closing. */ state->buffer_len = state->buffer_pos = 0; return close(fd); } /* Normally this is needed by bootp. * Once that uses this again, the ARP guard here can be removed. */ #ifdef ARP static unsigned int bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off, bool equal, 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 [] = { /* Ensure packet is at least correct size. */ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), /* 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 family 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, 1), 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) #define bpf_arp_extra ((((ARP_ADDRS_MAX + 1) * 2) * 2) + 2) #define bpf_arp_hw ((((HWADDR_LEN / 4) + 2) * 2) + 1) int bpf_arp(struct interface *ifp, int fd) { struct bpf_insn bpf[3+ bpf_arp_filter_len + bpf_arp_hw + bpf_arp_extra]; struct bpf_insn *bp; struct iarp_state *state; uint16_t arp_len; if (fd == -1) return 0; bp = bpf; /* Check frame header. */ switch(ifp->family) { 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_arp_hw, sizeof(struct arphdr), false, ifp->hwaddr, ifp->hwlen); state = ARP_STATE(ifp); if (TAILQ_FIRST(&state->arp_states)) { struct arp_state *astate; size_t naddrs; /* Match sender protocol address */ BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, sizeof(struct arphdr) + ifp->hwlen); bp++; naddrs = 0; TAILQ_FOREACH(astate, &state->arp_states, next) { if (++naddrs > ARP_ADDRS_MAX) { errno = ENOBUFS; logerr(__func__); break; } BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(astate->addr.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++; naddrs = 0; TAILQ_FOREACH(astate, &state->arp_states, next) { if (++naddrs > ARP_ADDRS_MAX) { /* Already logged error above. */ break; } BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(astate->addr.s_addr), 0, 1); bp++; BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); bp++; } /* Return nothing, no protocol address match. */ BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); bp++; } return bpf_attach(fd, bpf, (unsigned int)(bp - bpf)); } #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), /* Load frame header length into X. */ BPF_STMT(BPF_LDX + BPF_W + BPF_IMM, sizeof(struct ether_header)), /* Copy to M0. */ BPF_STMT(BPF_STX, 0), }; #define BPF_BOOTP_ETHER_LEN __arraycount(bpf_bootp_ether) static const struct bpf_insn bpf_bootp_filter[] = { /* Make sure it's an optionless IPv4 packet. */ BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x45, 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), /* Store IP location in M1. */ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct ip, ip_len)), BPF_STMT(BPF_ST, 1), /* Store IP length in M2. */ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct ip, ip_len)), BPF_STMT(BPF_ST, 2), /* Advance to the UDP header. */ BPF_STMT(BPF_MISC + BPF_TXA, 0), BPF_STMT(BPF_ALU + BPF_ADD + BPF_K, sizeof(struct ip)), BPF_STMT(BPF_MISC + BPF_TAX, 0), /* Store X in M3. */ BPF_STMT(BPF_STX, 3), /* Make sure it's from and to the right port. */ BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), /* Store UDP length in X. */ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, uh_ulen)), BPF_STMT(BPF_MISC + BPF_TAX, 0), /* Copy IP length in M2 to A. */ BPF_STMT(BPF_LD + BPF_MEM, 2), /* Ensure IP length - IP header size == UDP length. */ BPF_STMT(BPF_ALU + BPF_SUB + BPF_K, sizeof(struct ip)), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), /* Advance to the BOOTP packet (UDP X is in M3). */ BPF_STMT(BPF_LD + BPF_MEM, 3), BPF_STMT(BPF_ALU + BPF_ADD + BPF_K, sizeof(struct udphdr)), BPF_STMT(BPF_MISC + BPF_TAX, 0), /* Make sure it's BOOTREPLY. */ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct bootp, op)), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), }; #define BPF_BOOTP_FILTER_LEN __arraycount(bpf_bootp_filter) #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_FILTER_LEN \ + BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4 int bpf_bootp(struct interface *ifp, int fd) { #if 0 const struct dhcp_state *state = D_CSTATE(ifp); #endif struct bpf_insn bpf[BPF_BOOTP_LEN]; struct bpf_insn *bp; if (fd == -1) return 0; bp = bpf; /* Check frame header. */ switch(ifp->family) { 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_filter, sizeof(bpf_bootp_filter)); bp += BPF_BOOTP_FILTER_LEN; /* These checks won't work when same IP exists on other interfaces. */ #if 0 if (ifp->hwlen <= sizeof(((struct bootp *)0)->chaddr)) bp += bpf_cmp_hwaddr(bp, BPF_BOOTP_CHADDR_LEN, offsetof(struct bootp, chaddr), true, ifp->hwaddr, ifp->hwlen); /* Make sure the BOOTP packet is for us. */ if (state->state == DHS_BOUND) { /* If bound, we only expect FORCERENEW messages * and they need to be unicast to us. * Move back to the IP header in M0 and check dst. */ BPF_SET_STMT(bp, BPF_LDX + BPF_W + BPF_MEM, 0); bp++; BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, offsetof(struct ip, ip_dst)); bp++; BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(state->lease.addr.s_addr), 1, 0); bp++; BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); bp++; } else { /* As we're not bound, we need to check xid to ensure * it's a reply to our transaction. */ BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, offsetof(struct bootp, xid)); bp++; BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, state->xid, 1, 0); bp++; BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); bp++; } #endif /* All passed, return the packet * (Frame length in M0, IP length in M2). */ BPF_SET_STMT(bp, BPF_LD + BPF_MEM, 0); bp++; BPF_SET_STMT(bp, BPF_LDX + BPF_MEM, 2); bp++; BPF_SET_STMT(bp, BPF_ALU + BPF_ADD + BPF_X, 0); bp++; BPF_SET_STMT(bp, BPF_RET + BPF_A, 0); bp++; return bpf_attach(fd, bpf, (unsigned int)(bp - bpf)); } dhcpcd5-7.1.0/src/bpf.h000066400000000000000000000037741342162717100145570ustar00rootroot00000000000000/* * dhcpcd: BPF arp and bootp filtering * Copyright (c) 2006-2019 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_READING (1U << 0) #define BPF_EOF (1U << 1) #define BPF_PARTIALCSUM (1U << 2) #include "dhcpcd.h" extern const char *bpf_name; size_t bpf_frame_header_len(const struct interface *); int bpf_open(struct interface *, int (*)(struct interface *, int)); int bpf_close(struct interface *, int); int bpf_attach(int, void *, unsigned int); ssize_t bpf_send(const struct interface *, int, uint16_t, const void *, size_t); ssize_t bpf_read(struct interface *, int, void *, size_t, unsigned int *); int bpf_arp(struct interface *, int); int bpf_bootp(struct interface *, int); #endif dhcpcd5-7.1.0/src/common.c000066400000000000000000000130751342162717100152660ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 #ifdef __sun #include #endif #include #include #include #include #include #include #ifdef BSD # include #endif #include #include #include #include #include #include #include #include "common.h" #include "dhcpcd.h" #include "if-options.h" #include "logerr.h" /* Most route(4) messages are less than 256 bytes. */ #define IOVEC_BUFSIZ 256 ssize_t setvar(char **e, const char *prefix, const char *var, const char *value) { size_t len = strlen(var) + strlen(value) + 3; if (prefix) len += strlen(prefix) + 1; if ((*e = malloc(len)) == NULL) { logerr(__func__); return -1; } if (prefix) snprintf(*e, len, "%s_%s=%s", prefix, var, value); else snprintf(*e, len, "%s=%s", var, value); return (ssize_t)len; } ssize_t setvard(char **e, const char *prefix, const char *var, size_t value) { char buffer[32]; snprintf(buffer, sizeof(buffer), "%zu", value); return setvar(e, prefix, var, buffer); } ssize_t addvar(char ***e, const char *prefix, const char *var, const char *value) { ssize_t len; len = setvar(*e, prefix, var, value); if (len != -1) (*e)++; return (ssize_t)len; } ssize_t addvard(char ***e, const char *prefix, const char *var, size_t value) { char buffer[32]; snprintf(buffer, sizeof(buffer), "%zu", value); return addvar(e, prefix, var, buffer); } const char * hwaddr_ntoa(const void *hwaddr, size_t hwlen, char *buf, size_t buflen) { const unsigned char *hp, *ep; char *p; if (buf == NULL) return NULL; if (hwlen * 3 > buflen) { errno = ENOBUFS; return NULL; } hp = hwaddr; ep = hp + hwlen; p = buf; while (hp < ep) { if (hp != hwaddr) *p ++= ':'; p += snprintf(p, 3, "%.2x", *hp++); } *p ++= '\0'; return buf; } size_t hwaddr_aton(uint8_t *buffer, const char *addr) { char c[3]; const char *p = addr; uint8_t *bp = buffer; size_t len = 0; c[2] = '\0'; while (*p != '\0') { /* Skip separators */ c[0] = *p++; switch (c[0]) { case '\n': /* long duid split on lines */ case ':': /* typical mac address */ case '-': /* uuid */ continue; } c[1] = *p++; /* Ensure that digits are hex */ if (isxdigit((unsigned char)c[0]) == 0 || isxdigit((unsigned char)c[1]) == 0) { errno = EINVAL; return 0; } /* We should have at least two entries 00:01 */ if (len == 0 && *p == '\0') { errno = EINVAL; return 0; } if (bp) *bp++ = (uint8_t)strtol(c, NULL, 16); len++; } return len; } size_t read_hwaddr_aton(uint8_t **data, const char *path) { FILE *fp; char *buf; size_t buf_len, len; if ((fp = fopen(path, "r")) == NULL) return 0; buf = NULL; buf_len = len = 0; *data = NULL; while (getline(&buf, &buf_len, fp) != -1) { if ((len = hwaddr_aton(NULL, buf)) != 0) { if (buf_len >= len) *data = (uint8_t *)buf; else { if ((*data = malloc(len)) == NULL) len = 0; } if (len != 0) (void)hwaddr_aton(*data, buf); if (buf_len < len) free(buf); break; } } fclose(fp); return len; } ssize_t recvmsg_realloc(int fd, struct msghdr *msg, int flags) { struct iovec *iov; ssize_t slen; size_t len; void *n; assert(msg != NULL); assert(msg->msg_iov != NULL && msg->msg_iovlen > 0); assert((flags & (MSG_PEEK | MSG_TRUNC)) == 0); /* Assume we are reallocing the last iovec. */ iov = &msg->msg_iov[msg->msg_iovlen - 1]; for (;;) { /* Passing MSG_TRUNC should return the actual size needed. */ slen = recvmsg(fd, msg, flags | MSG_PEEK | MSG_TRUNC); if (slen == -1) return -1; if (!(msg->msg_flags & MSG_TRUNC)) break; len = (size_t)slen; /* Some kernels return the size of the receive buffer * on truncation, not the actual size needed. * So grow the buffer and try again. */ if (iov->iov_len == len) len++; else if (iov->iov_len > len) break; len = roundup(len, IOVEC_BUFSIZ); if ((n = realloc(iov->iov_base, len)) == NULL) return -1; iov->iov_base = n; iov->iov_len = len; } slen = recvmsg(fd, msg, flags); if (slen != -1 && msg->msg_flags & MSG_TRUNC) { /* This should not be possible ... */ errno = ENOBUFS; return -1; } return slen; } dhcpcd5-7.1.0/src/common.h000066400000000000000000000150401342162717100152650ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "config.h" #include "defs.h" #include "dhcpcd.h" #ifndef HOSTNAME_MAX_LEN #define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ #endif #ifndef MIN #define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) #define MAX(a,b) ((/*CONSTCOND*/(a)>(b))?(a):(b)) #endif #define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) #define STRINGIFY(a) #a #define TOSTRING(a) STRINGIFY(a) #define UNUSED(a) (void)(a) #define ROUNDUP4(a) (1 + (((a) - 1) | 3)) #define ROUNDUP8(a) (1 + (((a) - 1) | 7)) #define USEC_PER_SEC 1000000L #define USEC_PER_NSEC 1000L #define NSEC_PER_SEC 1000000000L #define NSEC_PER_MSEC 1000000L #define MSEC_PER_SEC 1000L #define CSEC_PER_SEC 100L #define NSEC_PER_CSEC 10000000L /* Some systems don't define timespec macros */ #ifndef timespecclear #define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L) #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) #define timespeccmp(tsp, usp, cmp) \ (((tsp)->tv_sec == (usp)->tv_sec) ? \ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ ((tsp)->tv_sec cmp (usp)->tv_sec)) #define timespecadd(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ if ((vsp)->tv_nsec >= 1000000000L) { \ (vsp)->tv_sec++; \ (vsp)->tv_nsec -= 1000000000L; \ } \ } while (/* CONSTCOND */ 0) #define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (/* CONSTCOND */ 0) #endif #define timespec_to_double(tv) \ ((double)(tv)->tv_sec + (double)((tv)->tv_nsec) / 1000000000.0) #define timespecnorm(tv) do { \ while ((tv)->tv_nsec >= NSEC_PER_SEC) { \ (tv)->tv_sec++; \ (tv)->tv_nsec -= NSEC_PER_SEC; \ } \ } while (0 /* CONSTCOND */); #define ts_to_ms(ms, tv) do { \ ms = (tv)->tv_sec * MSEC_PER_SEC; \ ms += (tv)->tv_nsec / NSEC_PER_MSEC; \ } while (0 /* CONSTCOND */); #define ms_to_ts(tv, ms) do { \ (tv)->tv_sec = ms / MSEC_PER_SEC; \ (tv)->tv_nsec = (suseconds_t)(ms - ((tv)->tv_sec * MSEC_PER_SEC)) \ * NSEC_PER_MSEC; \ } while (0 /* CONSTCOND */); #ifndef TIMEVAL_TO_TIMESPEC #define TIMEVAL_TO_TIMESPEC(tv, ts) do { \ (ts)->tv_sec = (tv)->tv_sec; \ (ts)->tv_nsec = (tv)->tv_usec * USEC_PER_NSEC; \ } while (0 /* CONSTCOND */) #endif #if __GNUC__ > 2 || defined(__INTEL_COMPILER) # ifndef __packed # define __packed __attribute__((__packed__)) # endif # ifndef __unused # define __unused __attribute__((__unused__)) # endif #else # ifndef __packed # define __packed # endif # ifndef __unused # define __unused # endif #endif /* * 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 void get_line_free(void); extern int clock_monotonic; int get_monotonic(struct timespec *); ssize_t setvar(char **, const char *, const char *, const char *); ssize_t setvard(char **, const char *, const char *, size_t); ssize_t addvar(char ***, const char *, const char *, const char *); ssize_t addvard(char ***, const char *, const char *, size_t); const char *hwaddr_ntoa(const void *, size_t, char *, size_t); size_t hwaddr_aton(uint8_t *, const char *); size_t read_hwaddr_aton(uint8_t **, const char *); ssize_t recvmsg_realloc(int, struct msghdr *, int); #endif dhcpcd5-7.1.0/src/control.c000066400000000000000000000226131342162717100154540ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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" #ifndef SUN_LEN #define SUN_LEN(su) \ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) #endif static void control_queue_purge(struct dhcpcd_ctx *ctx, char *data) { int found; struct fd_list *fp; struct fd_data *fpd; /* If no other fd queue has the same data, free it */ found = 0; TAILQ_FOREACH(fp, &ctx->control_fds, next) { TAILQ_FOREACH(fpd, &fp->queue, next) { if (fpd->data == data) { found = 1; break; } } } if (!found) free(data); } static void control_queue_free(struct fd_list *fd) { struct fd_data *fdp; while ((fdp = TAILQ_FIRST(&fd->queue))) { TAILQ_REMOVE(&fd->queue, fdp, next); if (fdp->freeit) control_queue_purge(fd->ctx, fdp->data); free(fdp); } while ((fdp = TAILQ_FIRST(&fd->free_queue))) { TAILQ_REMOVE(&fd->free_queue, fdp, next); free(fdp); } } static void control_delete(struct fd_list *fd) { TAILQ_REMOVE(&fd->ctx->control_fds, fd, next); eloop_event_delete(fd->ctx->eloop, fd->fd); close(fd->fd); control_queue_free(fd); free(fd); } static void control_handle_data(void *arg) { struct fd_list *fd = arg; char buffer[1024], *e, *p, *argvp[255], **ap, *a; ssize_t bytes; size_t len; int argc; bytes = read(fd->fd, buffer, sizeof(buffer) - 1); if (bytes == -1 || bytes == 0) { /* Control was closed or there was an error. * Remove it from our list. */ control_delete(fd); return; } buffer[bytes] = '\0'; p = buffer; e = buffer + bytes; /* Each command is \n terminated * Each argument is NULL separated */ while (p < e) { argc = 0; ap = argvp; while (p < e) { argc++; if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) { errno = ENOBUFS; return; } a = *ap++ = p; len = strlen(p); p += len + 1; if (len && a[len - 1] == '\n') { a[len - 1] = '\0'; break; } } *ap = NULL; if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { logerr(__func__); if (errno != EINTR && errno != EAGAIN) { control_delete(fd); return; } } } } static void control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags) { struct sockaddr_un run; socklen_t len; struct fd_list *l; int fd, flags; len = sizeof(run); if ((fd = accept(lfd, (struct sockaddr *)&run, &len)) == -1) return; if ((flags = fcntl(fd, F_GETFD, 0)) == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { close(fd); return; } if ((flags = fcntl(fd, F_GETFL, 0)) == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { close(fd); return; } l = malloc(sizeof(*l)); if (l) { l->ctx = ctx; l->fd = fd; l->flags = fd_flags; TAILQ_INIT(&l->queue); TAILQ_INIT(&l->free_queue); TAILQ_INSERT_TAIL(&ctx->control_fds, l, next); eloop_event_add(ctx->eloop, l->fd, control_handle_data, l); } else close(fd); } static void control_handle(void *arg) { struct dhcpcd_ctx *ctx = arg; control_handle1(ctx, ctx->control_fd, 0); } static void control_handle_unpriv(void *arg) { struct dhcpcd_ctx *ctx = arg; control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV); } static int make_sock(struct sockaddr_un *sa, const char *ifname, int unpriv) { int fd; #define SOCK_FLAGS SOCK_CLOEXEC | SOCK_NONBLOCK if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_FLAGS, 0)) == -1) return -1; #undef SOCK_FLAGS memset(sa, 0, sizeof(*sa)); sa->sun_family = AF_UNIX; if (unpriv) strlcpy(sa->sun_path, UNPRIVSOCKET, sizeof(sa->sun_path)); else { snprintf(sa->sun_path, sizeof(sa->sun_path), CONTROLSOCKET, ifname ? "-" : "", ifname ? ifname : ""); } return fd; } #define S_PRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) #define S_UNPRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) static int control_start1(struct dhcpcd_ctx *ctx, const char *ifname, mode_t fmode) { struct sockaddr_un sa; int fd; socklen_t len; if ((fd = make_sock(&sa, ifname, (fmode & S_UNPRIV) == S_UNPRIV)) == -1) return -1; len = (socklen_t)SUN_LEN(&sa); unlink(sa.sun_path); if (bind(fd, (struct sockaddr *)&sa, len) == -1 || chmod(sa.sun_path, fmode) == -1 || (ctx->control_group && chown(sa.sun_path, geteuid(), ctx->control_group) == -1) || listen(fd, sizeof(ctx->control_fds)) == -1) { close(fd); unlink(sa.sun_path); return -1; } if ((fmode & S_UNPRIV) != S_UNPRIV) strlcpy(ctx->control_sock, sa.sun_path, sizeof(ctx->control_sock)); return fd; } int control_start(struct dhcpcd_ctx *ctx, const char *ifname) { int fd; if ((fd = control_start1(ctx, ifname, S_PRIV)) == -1) return -1; ctx->control_fd = fd; eloop_event_add(ctx->eloop, fd, control_handle, ctx); if (ifname == NULL && (fd = control_start1(ctx, NULL, S_UNPRIV)) != -1){ /* We must be in master mode, so create an unpriviledged socket * to allow normal users to learn the status of dhcpcd. */ ctx->control_unpriv_fd = fd; eloop_event_add(ctx->eloop, fd, control_handle_unpriv, ctx); } return ctx->control_fd; } int control_stop(struct dhcpcd_ctx *ctx) { int retval = 0; struct fd_list *l; if (ctx->options & DHCPCD_FORKED) goto freeit; if (ctx->control_fd == -1) return 0; eloop_event_delete(ctx->eloop, ctx->control_fd); close(ctx->control_fd); ctx->control_fd = -1; if (unlink(ctx->control_sock) == -1) retval = -1; if (ctx->control_unpriv_fd != -1) { eloop_event_delete(ctx->eloop, ctx->control_unpriv_fd); close(ctx->control_unpriv_fd); ctx->control_unpriv_fd = -1; if (unlink(UNPRIVSOCKET) == -1) retval = -1; } freeit: while ((l = TAILQ_FIRST(&ctx->control_fds))) { TAILQ_REMOVE(&ctx->control_fds, l, next); eloop_event_delete(ctx->eloop, l->fd); close(l->fd); control_queue_free(l); free(l); } return retval; } int control_open(const char *ifname) { struct sockaddr_un sa; int fd; if ((fd = make_sock(&sa, ifname, 0)) != -1) { socklen_t len; len = (socklen_t)SUN_LEN(&sa); if (connect(fd, (struct sockaddr *)&sa, len) == -1) { close(fd); fd = -1; } } return fd; } ssize_t control_send(struct dhcpcd_ctx *ctx, int argc, char * const *argv) { char buffer[1024]; int i; size_t len, l; if (argc > 255) { errno = ENOBUFS; return -1; } len = 0; for (i = 0; i < argc; i++) { l = strlen(argv[i]) + 1; if (len + l > sizeof(buffer)) { errno = ENOBUFS; return -1; } memcpy(buffer + len, argv[i], l); len += l; } return write(ctx->control_fd, buffer, len); } static void control_writeone(void *arg) { struct fd_list *fd; struct iovec iov[2]; struct fd_data *data; fd = arg; data = TAILQ_FIRST(&fd->queue); iov[0].iov_base = &data->data_len; iov[0].iov_len = sizeof(size_t); iov[1].iov_base = data->data; iov[1].iov_len = data->data_len; if (writev(fd->fd, iov, 2) == -1) { logerr(__func__); if (errno != EINTR && errno != EAGAIN) control_delete(fd); return; } TAILQ_REMOVE(&fd->queue, data, next); if (data->freeit) control_queue_purge(fd->ctx, data->data); data->data = NULL; /* safety */ data->data_len = 0; TAILQ_INSERT_TAIL(&fd->free_queue, data, next); if (TAILQ_FIRST(&fd->queue) == NULL) eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); } int control_queue(struct fd_list *fd, char *data, size_t data_len, uint8_t fit) { struct fd_data *d; size_t n; d = TAILQ_FIRST(&fd->free_queue); if (d) { TAILQ_REMOVE(&fd->free_queue, d, next); } else { n = 0; TAILQ_FOREACH(d, &fd->queue, next) { if (++n == CONTROL_QUEUE_MAX) { errno = ENOBUFS; return -1; } } d = malloc(sizeof(*d)); if (d == NULL) return -1; } d->data = data; d->data_len = data_len; d->freeit = fit; TAILQ_INSERT_TAIL(&fd->queue, d, next); eloop_event_add_w(fd->ctx->eloop, fd->fd, control_writeone, fd); return 0; } void control_close(struct dhcpcd_ctx *ctx) { if (ctx->control_fd != -1) { close(ctx->control_fd); ctx->control_fd = -1; } } dhcpcd5-7.1.0/src/control.h000066400000000000000000000042651342162717100154640ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "dhcpcd.h" /* Limit queue size per fd */ #define CONTROL_QUEUE_MAX 100 struct fd_data { TAILQ_ENTRY(fd_data) next; char *data; size_t data_len; uint8_t freeit; }; TAILQ_HEAD(fd_data_head, fd_data); struct fd_list { TAILQ_ENTRY(fd_list) next; struct dhcpcd_ctx *ctx; int fd; unsigned int flags; struct fd_data_head queue; struct fd_data_head free_queue; }; TAILQ_HEAD(fd_list_head, fd_list); #define FD_LISTEN (1<<0) #define FD_UNPRIV (1<<1) int control_start(struct dhcpcd_ctx *, const char *); int control_stop(struct dhcpcd_ctx *); int control_open(const char *); ssize_t control_send(struct dhcpcd_ctx *, int, char * const *); int control_queue(struct fd_list *fd, char *data, size_t data_len, uint8_t fit); void control_close(struct dhcpcd_ctx *ctx); #endif dhcpcd5-7.1.0/src/defs.h000066400000000000000000000045171342162717100147250ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef CONFIG_H #define CONFIG_H #define PACKAGE "dhcpcd" #define VERSION "7.1.0" #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 "/" PACKAGE "%s%s%s.pid" #endif #ifndef CONTROLSOCKET # define CONTROLSOCKET RUNDIR "/" PACKAGE "%s%s.sock" #endif #ifndef UNPRIVSOCKET # define UNPRIVSOCKET RUNDIR "/" PACKAGE ".unpriv.sock" #endif #ifndef RDM_MONOFILE # define RDM_MONOFILE DBDIR "/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 dhcpcd5-7.1.0/src/dev.c000066400000000000000000000103161342162717100145470ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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_initialized(struct dhcpcd_ctx *ctx, const char *ifname) { if (ctx->dev == NULL) return 1; return ctx->dev->initialized(ifname); } int dev_listening(struct dhcpcd_ctx *ctx) { 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 char *name) { char file[PATH_MAX]; void *h; void (*fptr)(struct dev *, const struct dev_dhcpcd *); int r; struct dev_dhcpcd dev_dhcpcd; snprintf(file, sizeof(file), DEVDIR "/%s", name); h = dlopen(file, RTLD_LAZY); if (h == NULL) { logerrx("dlopen: %s", dlerror()); return -1; } fptr = (void (*)(struct dev *, const struct dev_dhcpcd *)) 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; } dev_dhcpcd.handle_interface = &dhcpcd_handleinterface; 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) { 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, ctx->dev_load); dp = opendir(DEVDIR); if (dp == NULL) { logdebug("dev: %s", DEVDIR); return 0; } r = 0; while ((d = readdir(dp))) { if (d->d_name[0] == '.') continue; r = dev_start2(ctx, d->d_name); if (r != -1) break; } closedir(dp); return r; } static void dev_handle_data(void *arg) { struct dhcpcd_ctx *ctx; ctx = arg; if (ctx->dev->handle_device(arg) == -1) { /* XXX: an error occured. should we restart dev? */ } } int dev_start(struct dhcpcd_ctx *ctx) { 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); if (ctx->dev_fd != -1) { if (eloop_event_add(ctx->eloop, ctx->dev_fd, dev_handle_data, ctx) == -1) { logerr(__func__); dev_stop1(ctx, 1); return -1; } } return ctx->dev_fd; } dhcpcd5-7.1.0/src/dev.h000066400000000000000000000040541342162717100145560ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 (*initialized)(const char *); int (*listening)(void); int (*handle_device)(void *); int (*start)(void); void (*stop)(void); }; struct dev_dhcpcd { int (*handle_interface)(void *, int, const char *); }; int dev_init(struct dev *, const struct dev_dhcpcd *); // hooks for dhcpcd #ifdef PLUGIN_DEV #include "dhcpcd.h" int dev_initialized(struct dhcpcd_ctx *, const char *); int dev_listening(struct dhcpcd_ctx *); int dev_start(struct dhcpcd_ctx *); void dev_stop(struct dhcpcd_ctx *); #else #define dev_initialized(a, b) (1) #define dev_listening(a) (0) #define dev_start(a) {} #define dev_stop(a) {} #endif #endif dhcpcd5-7.1.0/src/dev/000077500000000000000000000000001342162717100144025ustar00rootroot00000000000000dhcpcd5-7.1.0/src/dev/Makefile000066400000000000000000000014261342162717100160450ustar00rootroot00000000000000TOP?= ../../ include ${TOP}/Makefile.inc include ${TOP}/config.mk CFLAGS?= -O2 CSTD?= c99 CFLAGS+= -std=${CSTD} CPPFLAGS+= -I${TOP} -I${TOP}/src DEVDIR= ${LIBDIR}/dhcpcd/dev DSRC= ${DEV_PLUGINS:=.c} DOBJ= ${DSRC:.c=.o} DSOBJ= ${DOBJ:.o=.So} DPLUGS= ${DEV_PLUGINS:=.so} CLEANFILES+= ${DSOBJ} ${DPLUGS} .SUFFIXES: .So .so .c.So: ${CC} ${PICFLAG} -DPIC ${CPPFLAGS} ${CFLAGS} -c $< -o $@ .So.so: ${DSOBJ} ${CC} ${LDFLAGS} -shared -Wl,-x -o $@ -Wl,-soname,$@ \ $< ${LIBS} all: ${DPLUGS} udev.So: CFLAGS+= ${LIBUDEV_CFLAGS} CPPFLAGS+= ${LIBUDEV_CPPFLAGS} udev.so: LIBS+= ${LIBUDEV_LIBS} proginstall: ${DPLUGS} ${INSTALL} -d ${DESTDIR}${DEVDIR} ${INSTALL} -m ${BINMODE} ${PROG} ${DPLUGS} ${DESTDIR}${DEVDIR} eginstall: install: proginstall clean: rm -f ${CLEANFILES} dhcpcd5-7.1.0/src/dev/udev.c000066400000000000000000000104261342162717100155140ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "../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_initialized(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) { int fd; 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->initialized = udev_initialized; dev->listening = udev_listening; dev->handle_device = udev_handle_device; dev->stop = udev_stop; dev->start = udev_start; dhcpcd = *dev_dhcpcd; return 0; } dhcpcd5-7.1.0/src/dhcp-common.c000066400000000000000000000554471342162717100162130ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 /* after normal includes for sunos */ #include "config.h" #include "common.h" #include "dhcp-common.h" #include "dhcp.h" #include "if.h" #include "ipv6.h" #include "logerr.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 const char * dhcp_get_hostname(char *buf, size_t buf_len, const struct if_options *ifo) { if (ifo->hostname[0] == '\0') { if (gethostname(buf, buf_len) != 0) return NULL; buf[buf_len - 1] = '\0'; } else strlcpy(buf, ifo->hostname, buf_len); /* Deny sending of these local hostnames */ if (buf[0] == '\0' || buf[0] == '.' || strcmp(buf, "(none)") == 0 || strcmp(buf, "localhost") == 0 || strncmp(buf, "localhost.", strlen("localhost.")) == 0) return NULL; /* Shorten the hostname if required */ if (ifo->options & DHCPCD_HOSTNAME_SHORT) { char *hp; hp = strchr(buf, '.'); if (hp != NULL) *hp = '\0'; } return buf; } void dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols) { while (cols < 40) { putchar(' '); cols++; } putchar('\t'); if (opt->type & OT_EMBED) printf(" embed"); if (opt->type & OT_ENCAP) printf(" encap"); if (opt->type & OT_INDEX) printf(" index"); if (opt->type & OT_ARRAY) printf(" array"); if (opt->type & OT_UINT8) printf(" uint8"); else if (opt->type & OT_INT8) printf(" int8"); else if (opt->type & OT_UINT16) printf(" uint16"); else if (opt->type & OT_INT16) printf(" int16"); else if (opt->type & OT_UINT32) printf(" uint32"); else if (opt->type & OT_INT32) printf(" int32"); else if (opt->type & OT_ADDRIPV4) printf(" ipaddress"); else if (opt->type & OT_ADDRIPV6) printf(" ip6address"); else if (opt->type & OT_FLAG) printf(" flag"); else if (opt->type & OT_BITFLAG) printf(" bitflags"); else if (opt->type & OT_RFC1035) printf(" domain"); else if (opt->type & OT_DOMAIN) printf(" dname"); else if (opt->type & OT_ASCII) printf(" ascii"); else if (opt->type & OT_RAW) printf(" raw"); else if (opt->type & OT_BINHEX) printf(" binhex"); else if (opt->type & OT_STRING) printf(" string"); if (opt->type & OT_RFC3361) printf(" rfc3361"); if (opt->type & OT_RFC3442) printf(" rfc3442"); if (opt->type & OT_REQUEST) printf(" request"); if (opt->type & OT_NOREQ) printf(" norequest"); putchar('\n'); } struct dhcp_opt * vivso_find(uint32_t iana_en, const void *arg) { const struct interface *ifp; size_t i; struct dhcp_opt *opt; ifp = arg; for (i = 0, opt = ifp->options->vivso_override; i < ifp->options->vivso_override_len; i++, opt++) if (opt->option == iana_en) return opt; for (i = 0, opt = ifp->ctx->vivso; i < ifp->ctx->vivso_len; i++, opt++) if (opt->option == iana_en) return opt; return NULL; } ssize_t dhcp_vendor(char *str, size_t len) { struct utsname utn; char *p; int l; if (uname(&utn) == -1) return (ssize_t)snprintf(str, len, "%s-%s", PACKAGE, VERSION); p = str; l = snprintf(p, len, "%s-%s:%s-%s:%s", PACKAGE, VERSION, utn.sysname, utn.release, utn.machine); if (l == -1 || (size_t)(l + 1) > len) return -1; p += l; len -= (size_t)l; l = if_machinearch(p, len); if (l == -1 || (size_t)(l + 1) > len) return -1; p += l; return p - str; } int make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len, const struct dhcp_opt *odopts, size_t odopts_len, uint8_t *mask, const char *opts, int add) { char *token, *o, *p; const struct dhcp_opt *opt; int match, e; unsigned int n; size_t i; if (opts == NULL) return -1; o = p = strdup(opts); while ((token = strsep(&p, ", "))) { if (*token == '\0') continue; match = 0; for (i = 0, opt = odopts; i < odopts_len; i++, opt++) { if (opt->var == NULL || opt->option == 0) continue; /* buggy dhcpcd-definitions.conf */ if (strcmp(opt->var, token) == 0) match = 1; else { n = (unsigned int)strtou(token, NULL, 0, 0, UINT_MAX, &e); if (e == 0 && opt->option == n) match = 1; } if (match) break; } if (match == 0) { for (i = 0, opt = dopts; i < dopts_len; i++, opt++) { if (strcmp(opt->var, token) == 0) match = 1; else { n = (unsigned int)strtou(token, NULL, 0, 0, UINT_MAX, &e); if (e == 0 && opt->option == n) match = 1; } if (match) break; } } if (!match || !opt->option) { free(o); errno = ENOENT; return -1; } if (add == 2 && !(opt->type & OT_ADDRIPV4)) { free(o); errno = EINVAL; return -1; } if (add == 1 || add == 2) add_option_mask(mask, opt->option); else del_option_mask(mask, opt->option); } free(o); return 0; } size_t encode_rfc1035(const char *src, uint8_t *dst) { uint8_t *p; uint8_t *lp; size_t len; uint8_t has_dot; if (src == NULL || *src == '\0') return 0; if (dst) { p = dst; lp = p++; } /* Silence bogus GCC warnings */ else p = lp = NULL; len = 1; has_dot = 0; for (; *src; src++) { if (*src == '\0') break; if (*src == '.') { /* Skip the trailing . */ if (src[1] == '\0') break; has_dot = 1; if (dst) { *lp = (uint8_t)(p - lp - 1); if (*lp == '\0') return len; lp = p++; } } else if (dst) *p++ = (uint8_t)*src; len++; } if (dst) { *lp = (uint8_t)(p - lp - 1); if (has_dot) *p++ = '\0'; } if (has_dot) len++; return len; } /* Decode an RFC1035 DNS search order option into a space * separated string. Returns length of string (including * terminating zero) or zero on error. out may be NULL * to just determine output length. */ ssize_t decode_rfc1035(char *out, size_t len, const uint8_t *p, size_t pl) { const char *start; size_t start_len, l, d_len, o_len; const uint8_t *r, *q = p, *e; int hops; uint8_t ltype; o_len = 0; start = out; start_len = len; q = p; e = p + pl; while (q < e) { r = NULL; d_len = 0; hops = 0; /* Check we are inside our length again in-case * the name isn't fully qualified (ie, not terminated) */ while (q < e && (l = (size_t)*q++)) { ltype = l & 0xc0; if (ltype == 0x80 || ltype == 0x40) { /* Currently reserved for future use as noted * in RFC1035 4.1.4 as the 10 and 01 * combinations. */ errno = ENOTSUP; return -1; } else if (ltype == 0xc0) { /* pointer */ if (q == e) { errno = ERANGE; return -1; } l = (l & 0x3f) << 8; l |= *q++; /* save source of first jump. */ if (!r) r = q; hops++; if (hops > 255) { errno = ERANGE; return -1; } q = p + l; if (q >= e) { errno = ERANGE; return -1; } } else { /* straightforward name segment, add with '.' */ if (q + l > e) { errno = ERANGE; return -1; } if (l > NS_MAXLABEL) { errno = EINVAL; return -1; } d_len += l + 1; if (out) { if (l + 1 > len) { errno = ENOBUFS; return -1; } memcpy(out, q, l); out += l; *out++ = '.'; len -= l; len--; } q += l; } } /* Don't count the trailing NUL */ if (d_len > NS_MAXDNAME + 1) { errno = E2BIG; return -1; } o_len += d_len; /* change last dot to space */ if (out && out != start) *(out - 1) = ' '; if (r) q = r; } /* change last space to zero terminator */ if (out) { if (out != start) *(out - 1) = '\0'; else if (start_len > 0) *out = '\0'; } /* Remove the trailing NUL */ if (o_len != 0) o_len--; return (ssize_t)o_len; } /* Check for a valid name as per RFC952 and RFC1123 section 2.1 */ static int valid_domainname(char *lbl, int type) { char *slbl, *lst; unsigned char c; int start, len, errset; if (lbl == NULL || *lbl == '\0') { errno = EINVAL; return 0; } slbl = lbl; lst = NULL; start = 1; len = errset = 0; for (;;) { c = (unsigned char)*lbl++; if (c == '\0') return 1; if (c == ' ') { if (lbl - 1 == slbl) /* No space at start */ break; if (!(type & OT_ARRAY)) break; /* Skip to the next label */ if (!start) { start = 1; lst = lbl - 1; } if (len) len = 0; continue; } if (c == '.') { if (*lbl == '.') break; len = 0; continue; } if (((c == '-' || c == '_') && !start && *lbl != ' ' && *lbl != '\0') || isalnum(c)) { if (++len > NS_MAXLABEL) { errno = ERANGE; errset = 1; break; } } else break; if (start) start = 0; } if (!errset) errno = EINVAL; if (lst) { /* At least one valid domain, return it */ *lst = '\0'; return 1; } return 0; } /* * Prints a chunk of data to a string. * PS_SHELL goes as it is these days, it's upto the target to validate it. * PS_SAFE has all non ascii and non printables changes to escaped octal. */ static const char hexchrs[] = "0123456789abcdef"; ssize_t print_string(char *dst, size_t len, int type, const uint8_t *data, size_t dl) { char *odst; uint8_t c; const uint8_t *e; size_t bytes; odst = dst; bytes = 0; e = data + dl; while (data < e) { c = *data++; if (type & OT_BINHEX) { if (dst) { if (len == 0 || len == 1) { errno = ENOBUFS; return -1; } *dst++ = hexchrs[(c & 0xF0) >> 4]; *dst++ = hexchrs[(c & 0x0F)]; len -= 2; } bytes += 2; continue; } if (type & OT_ASCII && (!isascii(c))) { errno = EINVAL; break; } if (!(type & (OT_ASCII | OT_RAW | OT_ESCSTRING | OT_ESCFILE)) && (!isascii(c) && !isprint(c))) { errno = EINVAL; break; } if ((type & (OT_ESCSTRING | OT_ESCFILE) && (c == '\\' || !isascii(c) || !isprint(c))) || (type & OT_ESCFILE && (c == '/' || c == ' '))) { errno = EINVAL; if (c == '\\') { if (dst) { if (len == 0 || len == 1) { errno = ENOBUFS; return -1; } *dst++ = '\\'; *dst++ = '\\'; len -= 2; } bytes += 2; continue; } if (dst) { if (len < 5) { errno = ENOBUFS; return -1; } *dst++ = '\\'; *dst++ = (char)(((c >> 6) & 03) + '0'); *dst++ = (char)(((c >> 3) & 07) + '0'); *dst++ = (char)(( c & 07) + '0'); len -= 4; } bytes += 4; } else { if (dst) { if (len == 0) { errno = ENOBUFS; return -1; } *dst++ = (char)c; len--; } bytes++; } } /* NULL */ if (dst) { if (len == 0) { errno = ENOBUFS; return -1; } *dst = '\0'; /* Now we've printed it, validate the domain */ if (type & OT_DOMAIN && !valid_domainname(odst, type)) { *odst = '\0'; return 1; } } return (ssize_t)bytes; } #define ADDR6SZ 16 static ssize_t dhcp_optlen(const struct dhcp_opt *opt, size_t dl) { size_t sz; if (opt->type & OT_ADDRIPV6) sz = ADDR6SZ; else if (opt->type & (OT_INT32 | OT_UINT32 | OT_ADDRIPV4)) sz = sizeof(uint32_t); else if (opt->type & (OT_INT16 | OT_UINT16)) sz = sizeof(uint16_t); else if (opt->type & (OT_INT8 | OT_UINT8 | OT_BITFLAG)) sz = sizeof(uint8_t); else if (opt->type & OT_FLAG) return 0; else { /* All other types are variable length */ if (opt->len) { if ((size_t)opt->len > dl) { errno = EOVERFLOW; return -1; } return (ssize_t)opt->len; } return (ssize_t)dl; } if (dl < sz) { errno = EOVERFLOW; return -1; } /* Trim any extra data. * Maybe we need a settng to reject DHCP options with extra data? */ if (opt->type & OT_ARRAY) return (ssize_t)(dl - (dl % sz)); return (ssize_t)sz; } /* It's possible for DHCPv4 to contain an IPv6 address */ static ssize_t ipv6_printaddr(char *s, size_t sl, const uint8_t *d, const char *ifname) { char buf[INET6_ADDRSTRLEN]; const char *p; size_t l; p = inet_ntop(AF_INET6, d, buf, sizeof(buf)); if (p == NULL) return -1; l = strlen(p); if (d[0] == 0xfe && (d[1] & 0xc0) == 0x80) l += 1 + strlen(ifname); if (s == NULL) return (ssize_t)l; if (sl < l) { errno = ENOMEM; return -1; } s += strlcpy(s, p, sl); if (d[0] == 0xfe && (d[1] & 0xc0) == 0x80) { *s++ = '%'; s += strlcpy(s, ifname, sl); } *s = '\0'; return (ssize_t)l; } static ssize_t print_option(char *s, size_t len, const struct dhcp_opt *opt, const uint8_t *data, size_t dl, const char *ifname) { const uint8_t *e, *t; uint16_t u16; int16_t s16; uint32_t u32; int32_t s32; struct in_addr addr; ssize_t bytes = 0, sl; size_t l; #ifdef INET char *tmp; #endif if (opt->type & OT_RFC1035) { sl = decode_rfc1035(s, len, data, dl); if (sl == 0 || sl == -1) return sl; if (s != NULL) { if (valid_domainname(s, opt->type) == -1) return -1; } return sl; } #ifdef INET if (opt->type & OT_RFC3361) { if ((tmp = decode_rfc3361(data, dl)) == NULL) return -1; l = strlen(tmp); sl = print_string(s, len, opt->type, (uint8_t *)tmp, l); free(tmp); return sl; } if (opt->type & OT_RFC3442) return decode_rfc3442(s, len, data, dl); #endif if (opt->type & OT_STRING) return print_string(s, len, opt->type, data, dl); if (opt->type & OT_FLAG) { if (s) { *s++ = '1'; *s = '\0'; } return 1; } if (opt->type & OT_BITFLAG) { /* bitflags are a string, MSB first, such as ABCDEFGH * where A is 10000000, B is 01000000, etc. */ bytes = 0; for (l = 0, sl = sizeof(opt->bitflags) - 1; l < sizeof(opt->bitflags); l++, sl--) { /* Don't print NULL or 0 flags */ if (opt->bitflags[l] != '\0' && opt->bitflags[l] != '0' && *data & (1 << sl)) { if (s) *s++ = opt->bitflags[l]; bytes++; } } if (s) *s = '\0'; return bytes; } if (!s) { if (opt->type & OT_UINT8) l = 3; else if (opt->type & OT_INT8) l = 4; else if (opt->type & OT_UINT16) { l = 5; dl /= 2; } else if (opt->type & OT_INT16) { l = 6; dl /= 2; } else if (opt->type & OT_UINT32) { l = 10; dl /= 4; } else if (opt->type & OT_INT32) { l = 11; dl /= 4; } else if (opt->type & OT_ADDRIPV4) { l = 16; dl /= 4; } else if (opt->type & OT_ADDRIPV6) { e = data + dl; l = 0; while (data < e) { if (l) l++; /* space */ sl = ipv6_printaddr(NULL, 0, data, ifname); if (sl == -1) return l == 0 ? -1 : (ssize_t)l; l += (size_t)sl; data += 16; } return (ssize_t)l; } else { errno = EINVAL; return -1; } return (ssize_t)(l * dl); } t = data; e = data + dl; while (data < e) { if (data != t) { *s++ = ' '; bytes++; len--; } if (opt->type & OT_UINT8) { sl = snprintf(s, len, "%u", *data); data++; } else if (opt->type & OT_INT8) { sl = snprintf(s, len, "%d", *data); data++; } else if (opt->type & OT_UINT16) { memcpy(&u16, data, sizeof(u16)); u16 = ntohs(u16); sl = snprintf(s, len, "%u", u16); data += sizeof(u16); } else if (opt->type & OT_INT16) { memcpy(&u16, data, sizeof(u16)); s16 = (int16_t)ntohs(u16); sl = snprintf(s, len, "%d", s16); data += sizeof(u16); } else if (opt->type & OT_UINT32) { memcpy(&u32, data, sizeof(u32)); u32 = ntohl(u32); sl = snprintf(s, len, "%u", u32); data += sizeof(u32); } else if (opt->type & OT_INT32) { memcpy(&u32, data, sizeof(u32)); s32 = (int32_t)ntohl(u32); sl = snprintf(s, len, "%d", s32); data += sizeof(u32); } else if (opt->type & OT_ADDRIPV4) { memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); sl = snprintf(s, len, "%s", inet_ntoa(addr)); data += sizeof(addr.s_addr); } else if (opt->type & OT_ADDRIPV6) { sl = ipv6_printaddr(s, len, data, ifname); data += 16; } else { errno = EINVAL; return -1; } if (sl == -1) return bytes == 0 ? -1 : bytes; len -= (size_t)sl; bytes += sl; s += sl; } return bytes; } int dhcp_set_leasefile(char *leasefile, size_t len, int family, const struct interface *ifp) { char ssid[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); } static size_t dhcp_envoption1(char **env, const char *prefix, const struct dhcp_opt *opt, int vname, const uint8_t *od, size_t ol, const char *ifname) { ssize_t len; size_t e; char *v, *val; int r; /* Ensure a valid length */ ol = (size_t)dhcp_optlen(opt, ol); if ((ssize_t)ol == -1) return 0; len = print_option(NULL, 0, opt, od, ol, ifname); if (len < 0) return 0; if (vname) e = strlen(opt->var) + 1; else e = 0; if (prefix) e += strlen(prefix); e += (size_t)len + 2; if (env == NULL) return e; v = val = *env = malloc(e); if (v == NULL) return 0; if (vname) r = snprintf(val, e, "%s_%s=", prefix, opt->var); else r = snprintf(val, e, "%s=", prefix); if (r != -1 && len != 0) { v += r; if (print_option(v, (size_t)len + 1, opt, od, ol, ifname) == -1) r = -1; } if (r == -1) { free(val); return 0; } return e; } size_t dhcp_envoption(struct dhcpcd_ctx *ctx, char **env, const char *prefix, const char *ifname, struct dhcp_opt *opt, const uint8_t *(*dgetopt)(struct dhcpcd_ctx *, size_t *, unsigned int *, size_t *, const uint8_t *, size_t, struct dhcp_opt **), const uint8_t *od, size_t ol) { size_t e, i, n, eos, eol; ssize_t eo; unsigned int eoc; const uint8_t *eod; int ov; struct dhcp_opt *eopt, *oopt; char *pfx; /* If no embedded or encapsulated options, it's easy */ if (opt->embopts_len == 0 && opt->encopts_len == 0) { if (!(opt->type & OT_RESERVED)) { if (dhcp_envoption1(env == NULL ? NULL : &env[0], prefix, opt, 1, od, ol, ifname)) return 1; else logerr("%s: %s %d", ifname, __func__, opt->option); } return 0; } /* Create a new prefix based on the option */ if (env) { if (opt->type & OT_INDEX) { if (opt->index > 999) { errno = ENOBUFS; logerr(__func__); return 0; } } e = strlen(prefix) + strlen(opt->var) + 2 + (opt->type & OT_INDEX ? 3 : 0); pfx = malloc(e); if (pfx == NULL) { logerr(__func__); return 0; } if (opt->type & OT_INDEX) snprintf(pfx, e, "%s_%s%d", prefix, opt->var, ++opt->index); else snprintf(pfx, e, "%s_%s", prefix, opt->var); } else pfx = NULL; /* Embedded options are always processed first as that * is a fixed layout */ n = 0; for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) { eo = dhcp_optlen(eopt, ol); if (eo == -1) { if (env == NULL) 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 (env == NULL && (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 (dhcp_envoption1(env == NULL ? NULL : &env[n], pfx, eopt, ov, od, (size_t)eo, ifname)) n++; else if (env == NULL) 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) { if (eopt->type & OT_OPTION) { if (oopt == NULL) /* Report error? */ continue; } n += dhcp_envoption(ctx, env == NULL ? NULL : &env[n], pfx, ifname, eopt->type & OT_OPTION ? oopt:eopt, dgetopt, eod, eol); break; } } od += eos + eol; ol -= eos + eol; } } out: if (env) free(pfx); /* Return number of options found */ return n; } void dhcp_zero_index(struct dhcp_opt *opt) { size_t i; struct dhcp_opt *o; opt->index = 0; for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++) dhcp_zero_index(o); for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++) dhcp_zero_index(o); } size_t dhcp_read_lease_fd(int fd, void **lease) { struct stat st; size_t sz; void *buf; ssize_t len; if (fstat(fd, &st) != 0) goto out; if (!S_ISREG(st.st_mode)) { errno = EINVAL; goto out; } if (st.st_size > UINT32_MAX) { errno = E2BIG; goto out; } sz = (size_t)st.st_size; if ((buf = malloc(sz)) == NULL) goto out; if ((len = read(fd, buf, sz)) == -1) { free(buf); goto out; } *lease = buf; return (size_t)len; out: *lease = NULL; return 0; } dhcpcd5-7.1.0/src/dhcp-common.h000066400000000000000000000102111342162717100161740ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "common.h" #include "dhcpcd.h" /* Max MTU - defines dhcp option length */ #define IP_UDP_SIZE 28 #define MTU_MAX 1500 - IP_UDP_SIZE #define MTU_MIN 576 + IP_UDP_SIZE #define OT_REQUEST (1 << 0) #define OT_UINT8 (1 << 1) #define OT_INT8 (1 << 2) #define OT_UINT16 (1 << 3) #define OT_INT16 (1 << 4) #define OT_UINT32 (1 << 5) #define OT_INT32 (1 << 6) #define OT_ADDRIPV4 (1 << 7) #define OT_STRING (1 << 8) #define OT_ARRAY (1 << 9) #define OT_RFC3361 (1 << 10) #define OT_RFC1035 (1 << 11) #define OT_RFC3442 (1 << 12) #define OT_OPTIONAL (1 << 13) #define OT_ADDRIPV6 (1 << 14) #define OT_BINHEX (1 << 15) #define OT_FLAG (1 << 16) #define OT_NOREQ (1 << 17) #define OT_EMBED (1 << 18) #define OT_ENCAP (1 << 19) #define OT_INDEX (1 << 20) #define OT_OPTION (1 << 21) #define OT_DOMAIN (1 << 22) #define OT_ASCII (1 << 23) #define OT_RAW (1 << 24) #define OT_ESCSTRING (1 << 25) #define OT_ESCFILE (1 << 26) #define OT_BITFLAG (1 << 27) #define OT_RESERVED (1 << 28) 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 *); size_t dhcp_envoption(struct dhcpcd_ctx *, char **, const char *, const char *, struct dhcp_opt *, const uint8_t *(*dgetopt)(struct dhcpcd_ctx *, size_t *, unsigned int *, size_t *, const uint8_t *, size_t, struct dhcp_opt **), const uint8_t *od, size_t ol); void dhcp_zero_index(struct dhcp_opt *); size_t dhcp_read_lease_fd(int, void **); #endif dhcpcd5-7.1.0/src/dhcp.c000066400000000000000000002751361342162717100147240ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 #define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ #include #undef __FAVOR_BSD #include #include #include #include #include #include #include #include #include #include #define ELOOP_QUEUE 2 #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 "sa.h" #include "script.h" #define DAD "Duplicate address detected" #define DHCP_MIN_LEASE 20 #define IPV4A ADDRIPV4 | ARRAY #define IPV4R ADDRIPV4 | REQUEST /* We should define a maximum for the NAK exponential backoff */ #define NAKOFF_MAX 60 /* Wait N nanoseconds between sending a RELEASE and dropping the address. * This gives the kernel enough time to actually send it. */ #define RELEASE_DELAY_S 0 #define RELEASE_DELAY_NS 10000000 #ifndef IPDEFTTL #define IPDEFTTL 64 /* RFC1340 */ #endif /* NetBSD-7 has an incomplete IP_PKTINFO implementation. */ #if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000 #undef IP_PKTINFO #endif /* Assert the correct structure size for on wire */ __CTASSERT(sizeof(struct ip) == 20); __CTASSERT(sizeof(struct udphdr) == 8); __CTASSERT(sizeof(struct bootp) == 300); struct dhcp_op { uint8_t value; const char *name; }; static const struct dhcp_op dhcp_ops[] = { { DHCP_DISCOVER, "DISCOVER" }, { DHCP_OFFER, "OFFER" }, { DHCP_REQUEST, "REQUEST" }, { DHCP_DECLINE, "DECLINE" }, { DHCP_ACK, "ACK" }, { DHCP_NAK, "NAK" }, { DHCP_RELEASE, "RELEASE" }, { DHCP_INFORM, "INFORM" }, { DHCP_FORCERENEW, "FORCERENEW"}, { 0, NULL } }; static const char * const dhcp_params[] = { "ip_address", "subnet_cidr", "network_number", "filename", "server_name", NULL }; static int dhcp_openbpf(struct interface *); #ifdef ARP static void dhcp_arp_conflicted(struct arp_state *, const struct arp_msg *); #endif static void dhcp_handledhcp(struct interface *, struct bootp *, size_t, const struct in_addr *); 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); } } #define get_option_raw(ctx, bootp, bootp_len, opt) \ get_option((ctx), (bootp), (bootp_len), NULL) 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; /* 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++; 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; if (p + ol >= e) { errno = EINVAL; return NULL; } 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 decode_rfc3442(char *out, size_t len, const uint8_t *p, size_t pl) { const uint8_t *e; size_t bytes = 0, ocets; int b; uint8_t cidr; struct in_addr addr; char *o = out; /* Minimum is 5 -first is CIDR and a router length of 4 */ if (pl < 5) { errno = EINVAL; return -1; } e = p + pl; while (p < e) { cidr = *p++; if (cidr > 32) { errno = EINVAL; return -1; } ocets = (size_t)(cidr + 7) / NBBY; if (p + 4 + ocets > e) { errno = ERANGE; return -1; } if (!out) { p += 4 + ocets; bytes += ((4 * 4) * 2) + 4; continue; } if ((((4 * 4) * 2) + 4) > len) { errno = ENOBUFS; return -1; } if (o != out) { *o++ = ' '; len--; } /* If we have ocets then we have a destination and netmask */ if (ocets > 0) { addr.s_addr = 0; memcpy(&addr.s_addr, p, ocets); b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr); p += ocets; } else b = snprintf(o, len, "0.0.0.0/0"); o += b; len -= (size_t)b; /* Finally, snag the router */ memcpy(&addr.s_addr, p, 4); p += 4; b = snprintf(o, len, " %s", inet_ntoa(addr)); o += b; len -= (size_t)b; } if (out) return o - out; return (ssize_t)bytes; } static int decode_rfc3442_rt(struct rt_head *routes, struct interface *ifp, const uint8_t *data, size_t dl, const struct bootp *bootp) { const uint8_t *p = data; const uint8_t *e; uint8_t cidr; size_t ocets; struct rt *rt = NULL; struct in_addr dest, netmask, gateway; int n; /* Minimum is 5 -first is CIDR and a router length of 4 */ if (dl < 5) { errno = EINVAL; return -1; } n = 0; e = p + dl; while (p < e) { cidr = *p++; if (cidr > 32) { errno = EINVAL; return -1; } ocets = (size_t)(cidr + 7) / NBBY; if (p + 4 + ocets > e) { errno = ERANGE; return -1; } if ((rt = rt_new(ifp)) == NULL) return -1; /* If we have ocets then we have a destination and netmask */ dest.s_addr = 0; if (ocets > 0) { memcpy(&dest.s_addr, p, ocets); p += ocets; netmask.s_addr = htonl(~0U << (32 - cidr)); } else netmask.s_addr = 0; /* Finally, snag the router */ memcpy(&gateway.s_addr, p, 4); p += 4; /* A 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; 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 CIDR is 32 then it's a host route. */ if (cidr == 32) rt->rt_flags = RTF_HOST; TAILQ_INSERT_TAIL(routes, rt, rt_next); n++; } return n; } char * decode_rfc3361(const uint8_t *data, size_t dl) { uint8_t enc; size_t l; ssize_t r; char *sip = NULL; struct in_addr addr; char *p; if (dl < 2) { errno = EINVAL; return 0; } enc = *data++; dl--; switch (enc) { case 0: if ((r = decode_rfc1035(NULL, 0, data, dl)) > 0) { l = (size_t)r + 1; sip = malloc(l); if (sip == NULL) return 0; decode_rfc1035(sip, l, data, dl); } break; case 1: if (dl == 0 || dl % 4 != 0) { errno = EINVAL; break; } addr.s_addr = INADDR_BROADCAST; l = ((dl / sizeof(addr.s_addr)) * ((4 * 4) + 1)) + 1; sip = p = malloc(l); if (sip == NULL) return 0; while (dl != 0) { memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); data += sizeof(addr.s_addr); p += snprintf(p, l - (size_t)(p - sip), "%s ", inet_ntoa(addr)); dl -= sizeof(addr.s_addr); } *--p = '\0'; break; default: errno = EINVAL; return 0; } return sip; } 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(struct rt_head *routes, struct interface *ifp, const struct bootp *bootp, size_t bootp_len) { struct if_options *ifo = ifp->options; const uint8_t *p; const uint8_t *e; struct rt *rt = NULL; struct in_addr dest, netmask, gateway; size_t len; const char *csr = ""; int n; /* If we have CSR's then we MUST use these only */ if (!has_option_mask(ifo->nomask, DHO_CSR)) p = get_option(ifp->ctx, bootp, bootp_len, DHO_CSR, &len); else p = NULL; /* Check for crappy MS option */ if (!p && !has_option_mask(ifo->nomask, DHO_MSCSR)) { p = get_option(ifp->ctx, bootp, bootp_len, DHO_MSCSR, &len); if (p) csr = "MS "; } if (p && (n = decode_rfc3442_rt(routes, ifp, p, len, bootp)) != -1) { const struct dhcp_state *state; state = D_CSTATE(ifp); if (!(ifo->options & DHCPCD_CSR_WARNED) && !(state->added & STATE_FAKE)) { logdebugx("%s: using %sClassless Static Routes", ifp->name, csr); ifo->options |= DHCPCD_CSR_WARNED; } return n; } n = 0; /* OK, get our static routes first. */ if (!has_option_mask(ifo->nomask, DHO_STATICROUTE)) p = get_option(ifp->ctx, bootp, bootp_len, DHO_STATICROUTE, &len); else p = NULL; /* RFC 2131 Section 5.8 states length MUST be in multiples of 8 */ if (p && len % 8 == 0) { e = p + len; while (p < e) { memcpy(&dest.s_addr, p, sizeof(dest.s_addr)); p += 4; memcpy(&gateway.s_addr, p, sizeof(gateway.s_addr)); p += 4; /* RFC 2131 Section 5.8 states default route is * illegal */ if (gateway.s_addr == INADDR_ANY) continue; if ((rt = rt_new(ifp)) == NULL) return -1; /* A 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; rt->rt_flags = RTF_HOST; } else netmask.s_addr = route_netmask(dest.s_addr); sa_in_init(&rt->rt_dest, &dest); sa_in_init(&rt->rt_netmask, &netmask); sa_in_init(&rt->rt_gateway, &gateway); TAILQ_INSERT_TAIL(routes, rt, rt_next); 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) { 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); TAILQ_INSERT_TAIL(routes, rt, rt_next); n++; } } return n; } uint16_t dhcp_get_mtu(const struct interface *ifp) { const struct dhcp_state *state; uint16_t mtu; if (ifp->options->mtu) return (uint16_t)ifp->options->mtu; mtu = 0; /* bogus gcc warning */ if ((state = D_CSTATE(ifp)) == NULL || has_option_mask(ifp->options->nomask, DHO_MTU) || get_option_uint16(ifp->ctx, &mtu, state->new, state->new_len, DHO_MTU) == -1) return 0; return mtu; } /* Grab our routers from the DHCP message and apply any MTU value * the message contains */ int dhcp_get_routes(struct rt_head *routes, struct interface *ifp) { const struct dhcp_state *state; if ((state = D_CSTATE(ifp)) == NULL || !(state->added & STATE_ADDED)) return 0; return get_option_routes(routes, ifp, state->new, state->new_len); } /* Assumes DHCP options */ static int dhcp_message_add_addr(struct bootp *bootp, uint8_t type, struct in_addr addr) { uint8_t *p; size_t len; p = bootp->vend; while (*p != DHO_END) { p++; p += *p + 1; } len = (size_t)(p - bootp->vend); if (len + 6 > sizeof(bootp->vend)) { errno = ENOMEM; return -1; } *p++ = type; *p++ = 4; memcpy(p, &addr.s_addr, 4); p += 4; *p = DHO_END; return 0; } static ssize_t make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) { struct bootp *bootp; uint8_t *lp, *p, *e; uint8_t *n_params = NULL; uint32_t ul; uint16_t sz; size_t len, i; const struct dhcp_opt *opt; struct if_options *ifo = ifp->options; const struct dhcp_state *state = D_CSTATE(ifp); const struct dhcp_lease *lease = &state->lease; char hbuf[HOSTNAME_MAX_LEN + 1]; const char *hostname; const struct vivco *vivco; int mtu; #ifdef AUTH uint8_t *auth, auth_len; #endif if ((mtu = if_getmtu(ifp)) == -1) logerr("%s: if_getmtu", ifp->name); else if (mtu < MTU_MIN) { if (if_setmtu(ifp, MTU_MIN) == -1) logerr("%s: if_setmtu", ifp->name); mtu = MTU_MIN; } if (ifo->options & DHCPCD_BOOTP) bootp = calloc(1, sizeof (*bootp)); else /* Make the maximal message we could send */ bootp = calloc(1, (size_t)(mtu - IP_UDP_SIZE)); if (bootp == NULL) return -1; *bootpm = bootp; if (state->addr != NULL && (type == DHCP_INFORM || type == DHCP_RELEASE || (type == DHCP_REQUEST && state->addr->mask.s_addr == lease->mask.s_addr && (state->new == NULL || IS_DHCP(state->new)) && !(state->added & STATE_FAKE)))) bootp->ciaddr = state->addr->addr.s_addr; bootp->op = BOOTREQUEST; bootp->htype = (uint8_t)ifp->family; switch (ifp->family) { case ARPHRD_ETHER: case ARPHRD_IEEE802: bootp->hlen = (uint8_t)ifp->hwlen; memcpy(&bootp->chaddr, &ifp->hwaddr, ifp->hwlen); break; } if (ifo->options & DHCPCD_BROADCAST && bootp->ciaddr == 0 && type != DHCP_DECLINE && type != DHCP_RELEASE) bootp->flags = htons(BROADCAST_FLAG); if (type != DHCP_DECLINE && type != DHCP_RELEASE) { struct timespec tv; clock_gettime(CLOCK_MONOTONIC, &tv); timespecsub(&tv, &state->started, &tv); if (tv.tv_sec < 0 || tv.tv_sec > (time_t)UINT16_MAX) bootp->secs = htons((uint16_t)UINT16_MAX); else bootp->secs = htons((uint16_t)tv.tv_sec); } bootp->xid = htonl(state->xid); if (ifo->options & DHCPCD_BOOTP) return sizeof(*bootp); p = bootp->vend; e = (uint8_t *)bootp + (mtu - IP_UDP_SIZE) - 1; /* -1 for DHO_END */ ul = htonl(MAGIC_COOKIE); memcpy(p, &ul, sizeof(ul)); p += sizeof(ul); *p++ = DHO_MESSAGETYPE; *p++ = 1; *p++ = type; #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 */) 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 (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { if (type == DHCP_DECLINE || (type == DHCP_REQUEST && (state->addr == NULL || state->added & STATE_FAKE || lease->addr.s_addr != state->addr->addr.s_addr))) { PUT_ADDR(DHO_IPADDRESS, &lease->addr); if (lease->server.s_addr) PUT_ADDR(DHO_SERVERID, &lease->server); } if (type == DHCP_RELEASE) { 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; } } if (type == DHCP_DISCOVER && !(ifp->ctx->options & DHCPCD_TEST) && has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT)) { /* RFC 4039 Section 3 */ AREA_CHECK(0); *p++ = DHO_RAPIDCOMMIT; *p++ = 0; } if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) PUT_ADDR(DHO_IPADDRESS, &ifo->req_addr); /* RFC 2563 Auto Configure */ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL) { AREA_CHECK(1); *p++ = DHO_AUTOCONFIGURE; *p++ = 1; *p++ = 1; } if (type == DHCP_DISCOVER || type == DHCP_INFORM || type == DHCP_REQUEST) { if (mtu != -1) { AREA_CHECK(2); *p++ = DHO_MAXMESSAGESIZE; *p++ = 2; sz = htons((uint16_t)(mtu - IP_UDP_SIZE)); memcpy(p, &sz, 2); p += 2; } if (ifo->userclass[0]) { AREA_CHECK(ifo->userclass[0]); *p++ = DHO_USERCLASS; memcpy(p, ifo->userclass, (size_t)ifo->userclass[0] + 1); p += ifo->userclass[0] + 1; } if (ifo->vendorclassid[0]) { AREA_CHECK(ifo->vendorclassid[0]); *p++ = DHO_VENDORCLASSID; memcpy(p, ifo->vendorclassid, (size_t)ifo->vendorclassid[0] + 1); p += ifo->vendorclassid[0] + 1; } 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 (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; } } 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; } /* 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; } #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE) { /* We support HMAC-MD5 */ AREA_CHECK(1); *p++ = DHO_FORCERENEW_NONCE; *p++ = 1; *p++ = AUTH_ALG_HMAC_MD5; } #endif if (ifo->vivco_len) { 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); } } 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 (!(opt->type & OT_REQUEST || has_option_mask(ifo->requestmask, opt->option))) continue; if (opt->type & OT_NOREQ) 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 (!(opt->type & OT_REQUEST || has_option_mask(ifo->requestmask, opt->option))) continue; if (opt->type & OT_NOREQ) 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); } #ifdef AUTH auth = NULL; /* appease GCC */ auth_len = 0; if (ifo->auth.options & DHCPCD_AUTH_SEND) { ssize_t alen = dhcp_auth_encode(&ifo->auth, state->auth.token, NULL, 0, 4, type, NULL, 0); if (alen != -1 && alen > UINT8_MAX) { errno = ERANGE; alen = -1; } if (alen == -1) 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 *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(&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 ssize_t write_lease(const struct interface *ifp, const struct bootp *bootp, size_t len) { int fd; ssize_t bytes; const struct dhcp_state *state = D_CSTATE(ifp); logdebugx("%s: writing lease `%s'", ifp->name, state->leasefile); fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) return -1; bytes = write(fd, bootp, len); close(fd); return bytes; } static size_t read_lease(struct interface *ifp, struct bootp **bootp) { int fd; bool fd_opened; struct dhcp_state *state = D_STATE(ifp); struct bootp *lease; 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') { fd = fileno(stdin); fd_opened = false; } else { fd = open(state->leasefile, O_RDONLY); fd_opened = true; } if (fd == -1) { if (errno != ENOENT) logerr("%s: open `%s'", ifp->name, state->leasefile); return 0; } if (state->leasefile[0] == '\0') logdebugx("reading standard input"); else logdebugx("%s: reading lease `%s'", ifp->name, state->leasefile); bytes = dhcp_read_lease_fd(fd, (void **)&lease); if (fd_opened) close(fd); if (bytes == 0) { free(lease); logerr("%s: dhcp_read_lease_fd", __func__); return 0; } /* 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 < offsetof(struct bootp, vend) + 4) { free(lease); 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, (struct bootp *)lease, bytes, DHO_MESSAGETYPE) == -1) type = 0; #ifdef AUTH /* Authenticate the message */ auth = get_option(ifp->ctx, (struct bootp *)lease, bytes, DHO_AUTHENTICATION, &auth_len); if (auth) { if (dhcp_auth_validate(&state->auth, &ifp->options->auth, lease, bytes, 4, type, auth, auth_len) == NULL) { logerr("%s: authentication failed", ifp->name); free(lease); 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); free(lease); return 0; } #endif out: *bootp = (struct bootp *)lease; 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(char **env, const char *prefix, const struct bootp *bootp, size_t bootp_len, const struct interface *ifp) { const struct if_options *ifo; const uint8_t *p; struct in_addr addr; struct in_addr net; struct in_addr brd; struct dhcp_opt *opt, *vo; size_t e, i, pl; char **ep; char cidr[4], safe[(BOOTP_FILE_LEN * 4) + 1]; uint8_t overl = 0; uint32_t en; e = 0; ifo = ifp->options; if (get_option_uint8(ifp->ctx, &overl, bootp, bootp_len, DHO_OPTSOVERLOADED) == -1) overl = 0; if (env == NULL) { if (bootp->yiaddr || bootp->ciaddr) e += 5; if (*bootp->file && !(overl & 1)) e++; if (*bootp->sname && !(overl & 2)) e++; for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->dhcp_opts_len; i++, opt++) { if (has_option_mask(ifo->nomask, opt->option)) continue; if (dhcp_getoverride(ifo, opt->option)) continue; p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); if (!p) continue; e += dhcp_envoption(ifp->ctx, NULL, NULL, ifp->name, opt, dhcp_getoption, p, pl); } for (i = 0, opt = ifo->dhcp_override; i < ifo->dhcp_override_len; i++, opt++) { if (has_option_mask(ifo->nomask, opt->option)) continue; p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); if (!p) continue; e += dhcp_envoption(ifp->ctx, NULL, NULL, ifp->name, opt, dhcp_getoption, p, pl); } return (ssize_t)e; } ep = env; if (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; addvar(&ep, prefix, "ip_address", inet_ntoa(addr)); if (get_option_addr(ifp->ctx, &net, bootp, bootp_len, DHO_SUBNETMASK) == -1) { net.s_addr = ipv4_getnetmask(addr.s_addr); addvar(&ep, prefix, "subnet_mask", inet_ntoa(net)); } snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); addvar(&ep, prefix, "subnet_cidr", cidr); if (get_option_addr(ifp->ctx, &brd, bootp, bootp_len, DHO_BROADCAST) == -1) { brd.s_addr = addr.s_addr | ~net.s_addr; addvar(&ep, prefix, "broadcast_address", inet_ntoa(brd)); } addr.s_addr = bootp->yiaddr & net.s_addr; addvar(&ep, prefix, "network_number", inet_ntoa(addr)); } if (*bootp->file && !(overl & 1)) { print_string(safe, sizeof(safe), OT_STRING, bootp->file, sizeof(bootp->file)); addvar(&ep, prefix, "filename", safe); } if (*bootp->sname && !(overl & 2)) { print_string(safe, sizeof(safe), OT_STRING | OT_DOMAIN, bootp->sname, sizeof(bootp->sname)); addvar(&ep, prefix, "server_name", safe); } /* Zero our indexes */ if (env) { for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->dhcp_opts_len; i++, opt++) dhcp_zero_index(opt); for (i = 0, opt = ifp->options->dhcp_override; i < ifp->options->dhcp_override_len; i++, opt++) dhcp_zero_index(opt); for (i = 0, opt = ifp->ctx->vivso; i < ifp->ctx->vivso_len; i++, opt++) dhcp_zero_index(opt); } for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->dhcp_opts_len; i++, opt++) { if (has_option_mask(ifo->nomask, opt->option)) continue; if (dhcp_getoverride(ifo, opt->option)) continue; p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); if (p == NULL) continue; ep += dhcp_envoption(ifp->ctx, ep, 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; ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name, vo, dhcp_getoption, p, pl); } for (i = 0, opt = ifo->dhcp_override; i < ifo->dhcp_override_len; i++, opt++) { if (has_option_mask(ifo->nomask, opt->option)) continue; p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); if (p == NULL) continue; ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name, opt, dhcp_getoption, p, pl); } return ep - env; } static void get_lease(struct interface *ifp, struct dhcp_lease *lease, const struct bootp *bootp, size_t len) { struct dhcpcd_ctx *ctx; assert(bootp != NULL); memcpy(&lease->cookie, bootp->vend, sizeof(lease->cookie)); /* BOOTP does not set yiaddr for replies when ciaddr is set. */ lease->addr.s_addr = bootp->yiaddr ? bootp->yiaddr : bootp->ciaddr; ctx = ifp->ctx; if (ifp->options->options & (DHCPCD_STATIC | DHCPCD_INFORM)) { if (ifp->options->req_addr.s_addr != INADDR_ANY) { lease->mask = ifp->options->req_mask; if (ifp->options->req_brd.s_addr != INADDR_ANY) lease->brd = ifp->options->req_brd; else lease->brd.s_addr = lease->addr.s_addr | ~lease->mask.s_addr; } else { const struct ipv4_addr *ia; ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); assert(ia != NULL); lease->mask = ia->mask; lease->brd = ia->brd; } } else { if (get_option_addr(ctx, &lease->mask, bootp, len, DHO_SUBNETMASK) == -1) lease->mask.s_addr = ipv4_getnetmask(lease->addr.s_addr); if (get_option_addr(ctx, &lease->brd, bootp, len, DHO_BROADCAST) == -1) lease->brd.s_addr = lease->addr.s_addr | ~lease->mask.s_addr; } if (get_option_uint32(ctx, &lease->leasetime, bootp, len, DHO_LEASETIME) != 0) lease->leasetime = ~0U; /* Default to infinite lease */ 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 } void dhcp_close(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); if (state == NULL) return; if (state->bpf_fd != -1) { eloop_event_delete(ifp->ctx->eloop, state->bpf_fd); bpf_close(ifp, state->bpf_fd); state->bpf_fd = -1; state->bpf_flags |= BPF_EOF; } state->interval = 0; } static int dhcp_openudp(struct interface *ifp) { int s; struct sockaddr_in sin; int n; if ((s = xsocket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP)) == -1) return -1; n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) goto eexit; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(BOOTPC); if (ifp) { struct dhcp_state *state = D_STATE(ifp); if (state->addr) sin.sin_addr.s_addr = state->addr->addr.s_addr; } if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) goto eexit; return s; eexit: close(s); return -1; } static uint16_t checksum(const void *data, size_t len) { const uint8_t *addr = data; uint32_t sum = 0; while (len > 1) { sum += (uint32_t)(addr[0] * 256 + addr[1]); addr += 2; len -= 2; } if (len == 1) sum += (uint32_t)(*addr * 256); sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (uint16_t)~htons((uint16_t)sum); } static struct bootp_pkt * dhcp_makeudppacket(size_t *sz, const uint8_t *data, size_t length, struct in_addr source, struct in_addr dest) { struct bootp_pkt *udpp; struct ip *ip; struct udphdr *udp; if ((udpp = calloc(1, sizeof(*ip) + sizeof(*udp) + length)) == NULL) return NULL; ip = &udpp->ip; udp = &udpp->udp; /* OK, this is important :) * We copy the data to our packet and then create a small part of the * ip structure and an invalid ip_len (basically udp length). * We then fill the udp structure and put the checksum * of the whole packet into the udp checksum. * Finally we complete the ip structure and ip checksum. * If we don't do the ordering like so then the udp checksum will be * broken, so find another way of doing it! */ memcpy(&udpp->bootp, data, length); ip->ip_p = IPPROTO_UDP; ip->ip_src.s_addr = source.s_addr; if (dest.s_addr == 0) ip->ip_dst.s_addr = INADDR_BROADCAST; else ip->ip_dst.s_addr = dest.s_addr; udp->uh_sport = htons(BOOTPC); udp->uh_dport = htons(BOOTPS); udp->uh_ulen = htons((uint16_t)(sizeof(*udp) + length)); ip->ip_len = udp->uh_ulen; udp->uh_sum = checksum(udpp, sizeof(*ip) + sizeof(*udp) + length); ip->ip_v = IPVERSION; ip->ip_hl = sizeof(*ip) >> 2; ip->ip_id = (uint16_t)arc4random_uniform(UINT16_MAX); ip->ip_ttl = IPDEFTTL; ip->ip_len = htons((uint16_t)(sizeof(*ip) + sizeof(*udp) + length)); ip->ip_sum = checksum(ip, sizeof(*ip)); *sz = sizeof(*ip) + sizeof(*udp) + length; return udpp; } static ssize_t dhcp_sendudp(struct interface *ifp, struct in_addr *to, void *data, size_t len) { int s; struct msghdr msg; struct sockaddr_in sin; struct iovec iov[1]; ssize_t r; #ifdef IP_PKTINFO uint8_t cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))]; struct cmsghdr *cm; struct in_pktinfo ipi; #endif iov[0].iov_base = data; iov[0].iov_len = len; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr = *to; sin.sin_port = htons(BOOTPS); #ifdef HAVE_SA_LEN sin.sin_len = sizeof(sin); #endif memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&sin; msg.msg_namelen = sizeof(sin); msg.msg_iov = iov; msg.msg_iovlen = 1; #ifdef IP_PKTINFO /* Set the outbound interface */ msg.msg_control = cmsg; msg.msg_controllen = sizeof(cmsg); memset(&ipi, 0, sizeof(ipi)); ipi.ipi_ifindex = ifp->index; cm = CMSG_FIRSTHDR(&msg); if (cm == NULL) { errno = ESRCH; return -1; } cm->cmsg_level = IPPROTO_IP; cm->cmsg_type = IP_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(ipi)); memcpy(CMSG_DATA(cm), &ipi, sizeof(ipi)); #endif s = dhcp_openudp(ifp); if (s == -1) return -1; r = sendmsg(s, &msg, 0); close(s); return r; } 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; struct timespec tv; if (!callback) { /* No carrier? Don't bother sending the packet. */ if (ifp->carrier == LINK_DOWN) return; logdebugx("%s: sending %s with xid 0x%x", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), state->xid); } else { if (state->interval == 0) state->interval = 4; else { state->interval *= 2; if (state->interval > 64) state->interval = 64; } tv.tv_sec = state->interval + DHCP_RAND_MIN; tv.tv_nsec = (suseconds_t)arc4random_uniform( (DHCP_RAND_MAX - DHCP_RAND_MIN) * NSEC_PER_SEC); timespecnorm(&tv); /* No carrier? Don't bother sending the packet. * However, we do need to advance the timeout. */ if (ifp->carrier == LINK_DOWN) goto fail; 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, timespec_to_double(&tv)); } r = make_message(&bootp, ifp, type); if (r == -1) goto fail; len = (size_t)r; from.s_addr = bootp->ciaddr; if (from.s_addr != INADDR_ANY) to.s_addr = state->lease.server.s_addr; else to.s_addr = INADDR_ANY; /* If unicasting, try and void sending by BPF so we don't * use a L2 broadcast. */ if (to.s_addr != INADDR_ANY && 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; } else { r = bpf_send(ifp, state->bpf_fd, ETHERTYPE_IP, (uint8_t *)udp, ulen); free(udp); } /* If we failed to send a raw packet this normally means * we don't have the ability to work beneath the IP layer * for this interface. * As such we remove it from consideration without actually * stopping the interface. */ if (r == -1) { logerr("%s: if_sendraw", 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) eloop_timeout_add_tv(ifp->ctx->eloop, &tv, callback, ifp); } static void send_inform(void *arg) { send_message((struct interface *)arg, DHCP_INFORM, send_inform); } static void send_discover(void *arg) { send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); } static void send_request(void *arg) { send_message((struct interface *)arg, DHCP_REQUEST, send_request); } static void send_renew(void *arg) { send_message((struct interface *)arg, DHCP_REQUEST, send_renew); } static void send_rebind(void *arg) { send_message((struct interface *)arg, DHCP_REQUEST, send_rebind); } void dhcp_discover(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; state->state = DHS_DISCOVER; dhcp_new_xid(ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (ifo->fallback) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, dhcp_fallback, ifp); #ifdef IPV4LL else if (ifo->options & DHCPCD_IPV4LL) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, ipv4ll_start, ifp); #endif if (ifo->options & DHCPCD_REQUEST) loginfox("%s: soliciting a DHCP lease (requesting %s)", ifp->name, inet_ntoa(ifo->req_addr)); else loginfox("%s: soliciting a %s lease", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : "DHCP"); send_discover(ifp); } static void dhcp_request(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); state->state = DHS_REQUEST; send_request(ifp); } static int dhcp_leaseextend(struct interface *ifp) { #ifdef ARP if (ifp->options->options & DHCPCD_ARP) { const struct dhcp_state *state; struct arp_state *astate; state = D_CSTATE(ifp); if ((astate = arp_new(ifp, &state->lease.addr)) == NULL) return -1; astate->conflicted_cb = dhcp_arp_conflicted; #ifndef KERNEL_RFC5227 if (arp_open(ifp) == -1) return -1; #endif logwarnx("%s: extending lease until DaD failure or DHCP", ifp->name); return 0; } #endif logwarnx("%s: extending lease", ifp->name); return 0; } static void dhcp_expire1(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); dhcp_drop(ifp, "EXPIRE"); unlink(state->leasefile); state->interval = 0; if (!(ifp->options->options & DHCPCD_LINK) || ifp->carrier != LINK_DOWN) dhcp_discover(ifp); } static void dhcp_expire(void *arg) { struct interface *ifp = arg; logerrx("%s: DHCP lease expired", ifp->name); if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { if (dhcp_leaseextend(ifp) == 0) return; logerr(__func__); } dhcp_expire1(ifp); } #if defined(ARP) || defined(IN_IFF_DUPLICATED) static void dhcp_decline(struct interface *ifp) { send_message(ifp, DHCP_DECLINE, NULL); } #endif static void dhcp_startrenew(void *arg) { struct interface *ifp = arg; struct dhcp_state *state; struct dhcp_lease *lease; if ((state = D_STATE(ifp)) == NULL) return; /* Only renew in the bound or renew states */ if (state->state != DHS_BOUND && state->state != DHS_RENEW) return; /* Remove the timeout as the renew may have been forced. */ eloop_timeout_delete(ifp->ctx->eloop, dhcp_startrenew, ifp); lease = &state->lease; logdebugx("%s: renewing lease of %s", ifp->name, inet_ntoa(lease->addr)); state->state = DHS_RENEW; dhcp_new_xid(ifp); state->interval = 0; send_renew(ifp); } void dhcp_renew(struct interface *ifp) { dhcp_startrenew(ifp); } static void dhcp_rebind(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct dhcp_lease *lease = &state->lease; logwarnx("%s: failed to renew DHCP, rebinding", ifp->name); logdebugx("%s: expire in %"PRIu32" seconds", ifp->name, lease->leasetime - lease->rebindtime); state->state = DHS_REBIND; eloop_timeout_delete(ifp->ctx->eloop, send_renew, ifp); state->lease.server.s_addr = INADDR_ANY; state->interval = 0; ifp->options->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED); send_rebind(ifp); } #ifdef ARP static void dhcp_arp_probed(struct arp_state *astate) { struct interface *ifp; struct dhcp_state *state; struct if_options *ifo; ifp = astate->iface; state = D_STATE(ifp); ifo = ifp->options; #ifdef ARPING if (ifo->arping_len && state->arping_index < ifo->arping_len) { /* We didn't find a profile for this * address or hwaddr, so move to the next * arping profile */ if (++state->arping_index < ifo->arping_len) { astate->addr.s_addr = ifo->arping[state->arping_index]; arp_probe(astate); return; } arp_free(astate); #ifdef KERNEL_RFC5227 /* As arping is finished, close the ARP socket. * The kernel will handle ACD from here. */ arp_close(ifp); #endif dhcpcd_startinterface(ifp); return; } #endif /* Already bound so DAD has worked */ if (state->state == DHS_BOUND) return; logdebugx("%s: DAD completed for %s", ifp->name, inet_ntoa(astate->addr)); if (!(ifo->options & DHCPCD_INFORM)) dhcp_bind(ifp); #ifndef IN_IFF_TENTATIVE 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(astate->iface); state->new = bootp; state->new_len = len; } #endif /* If we forked, stop here. */ if (ifp->ctx->options & DHCPCD_FORKED) return; #ifdef IPV4LL /* Stop IPv4LL now we have a working DHCP address */ ipv4ll_drop(ifp); #endif if (ifo->options & DHCPCD_INFORM) dhcp_inform(ifp); } static void dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg) { struct interface *ifp; struct dhcp_state *state; #ifdef ARPING struct if_options *ifo; #endif ifp = astate->iface; state = D_STATE(ifp); #ifdef ARPING 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]; astate->failed.s_addr = ifo->arping[state->arping_index]; arp_report_conflicted(astate, amsg); hwaddr_ntoa(amsg->sha, ifp->hwlen, buf, sizeof(buf)); if (dhcpcd_selectprofile(ifp, buf) == -1 && dhcpcd_selectprofile(ifp, inet_ntoa(astate->failed)) == -1) { /* We didn't find a profile for this * address or hwaddr, so move to the next * arping profile */ dhcp_arp_probed(astate); return; } arp_free(astate); #ifdef KERNEL_RFC5227 /* As arping is finished, close the ARP socket. * The kernel will handle ACD from here. */ arp_close(ifp); #endif eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); dhcpcd_startinterface(ifp); return; } #endif /* RFC 2131 3.1.5, Client-server interaction * NULL amsg means IN_IFF_DUPLICATED */ if (amsg == NULL || (state->offer && (amsg->sip.s_addr == state->offer->yiaddr || (amsg->sip.s_addr == 0 && amsg->tip.s_addr == state->offer->yiaddr)))) { #ifdef IN_IFF_DUPLICATED struct ipv4_addr *ia; #endif if (amsg) astate->failed.s_addr = state->offer->yiaddr; else astate->failed = astate->addr; arp_report_conflicted(astate, amsg); unlink(state->leasefile); #ifdef ARP if (!(ifp->options->options & DHCPCD_STATIC) && !state->lease.frominfo) dhcp_decline(ifp); #endif #ifdef IN_IFF_DUPLICATED if ((ia = ipv4_iffindaddr(ifp, &astate->addr, NULL)) != NULL) ipv4_deladdr(ia, 1); #endif arp_free(astate); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, DHCP_RAND_MAX, dhcp_discover, ifp); return; } /* Bound address */ if (amsg && state->addr && amsg->sip.s_addr == state->addr->addr.s_addr) { astate->failed = state->addr->addr; arp_report_conflicted(astate, amsg); if (state->state == DHS_BOUND) { /* For now, just report the duplicated address */ } else { arp_free(astate); dhcp_expire1(ifp); } return; } } #endif void dhcp_bind(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; struct dhcp_lease *lease = &state->lease; state->reason = NULL; /* 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 = ~0U; state->reason = "STATIC"; } else if (ifo->options & DHCPCD_INFORM) { loginfox("%s: received approval for %s", ifp->name, inet_ntoa(lease->addr)); lease->leasetime = ~0U; state->reason = "INFORM"; } else { if (lease->frominfo) state->reason = "TIMEOUT"; if (lease->leasetime == ~0U) { lease->renewaltime = lease->rebindtime = lease->leasetime; 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->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 (ifp->ctx->options & DHCPCD_TEST) { state->reason = "TEST"; script_runreason(ifp, state->reason); eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } if (state->reason == NULL) { if (state->old && !(state->added & STATE_FAKE)) { if (state->old->yiaddr == state->new->yiaddr && lease->server.s_addr && state->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 == ~0U) lease->renewaltime = lease->rebindtime = lease->leasetime; else { eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)lease->renewaltime, dhcp_startrenew, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)lease->rebindtime, dhcp_rebind, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)lease->leasetime, dhcp_expire, ifp); logdebugx("%s: renew in %"PRIu32" seconds, rebind in %"PRIu32 " seconds", ifp->name, lease->renewaltime, lease->rebindtime); } state->state = DHS_BOUND; /* Re-apply the filter because we need to accept any XID anymore. */ if (bpf_bootp(ifp, state->bpf_fd) == -1) logerr(__func__); /* try to continue */ if (!state->lease.frominfo && !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) if (write_lease(ifp, state->new, state->new_len) == -1) logerr(__func__); ipv4_applyaddr(ifp); } 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); dhcp_bind(ifp); /* If we forked, stop here. */ if (ifp->ctx->options & DHCPCD_FORKED) return; state->interval = 0; if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND && dhcp_leaseextend(ifp) == -1) { logerr("%s: %s", ifp->name, __func__); dhcp_expire(ifp); } dhcp_discover(ifp); } static size_t dhcp_message_new(struct bootp **bootp, const struct in_addr *addr, const struct in_addr *mask) { uint8_t *p; uint32_t cookie; if ((*bootp = calloc(1, sizeof(**bootp))) == NULL) return 0; (*bootp)->yiaddr = addr->s_addr; p = (*bootp)->vend; cookie = htonl(MAGIC_COOKIE); memcpy(p, &cookie, sizeof(cookie)); p += sizeof(cookie); if (mask->s_addr != INADDR_ANY) { *p++ = DHO_SUBNETMASK; *p++ = sizeof(mask->s_addr); memcpy(p, &mask->s_addr, sizeof(mask->s_addr)); p+= sizeof(mask->s_addr); } *p = DHO_END; return sizeof(**bootp); } #ifdef ARP static int dhcp_arp_address(struct interface *ifp) { struct dhcp_state *state; struct in_addr addr; struct ipv4_addr *ia; struct arp_state *astate; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); state = D_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); if ((astate = arp_new(ifp, &addr)) == NULL) return -1; astate->probed_cb = dhcp_arp_probed; astate->conflicted_cb = dhcp_arp_conflicted; #ifdef IN_IFF_TENTATIVE 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); } else loginfox("%s: waiting for DAD on %s", ifp->name, inet_ntoa(addr)); return 0; } #else if (ifp->options->options & DHCPCD_ARP && ia == NULL) { struct dhcp_lease l; 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 (dhcp_arp_address(ifp) == 1) dhcp_bind(ifp); } #endif 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) #ifdef ARP 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; state->state = DHS_INFORM; 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) == 0) return; #endif ia = ipv4_iffindaddr(ifp, &ifo->req_addr, &ifo->req_mask); assert(ia != NULL); } } state->addr = ia; state->offer_len = dhcp_message_new(&state->offer, &ia->addr, &ia->mask); if (state->offer_len) { dhcp_new_xid(ifp); get_lease(ifp, &state->lease, state->offer, state->offer_len); send_inform(ifp); } } void dhcp_reboot_newopts(struct interface *ifp, unsigned long long oldopts) { struct if_options *ifo; struct dhcp_state *state = D_STATE(ifp); if (state == NULL || state->state == DHS_NONE) return; ifo = ifp->options; if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) && (state->addr == NULL || state->addr->addr.s_addr != ifo->req_addr.s_addr)) || (oldopts & (DHCPCD_INFORM | DHCPCD_STATIC) && !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) { dhcp_drop(ifp, "EXPIRE"); } } #ifdef ARP static int dhcp_activeaddr(const struct interface *ifp, const struct in_addr *addr) { const struct interface *ifp1; const struct dhcp_state *state; TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) { if (ifp1 == ifp) continue; if ((state = D_CSTATE(ifp1)) == NULL) continue; switch(state->state) { case DHS_REBOOT: case DHS_RENEW: case DHS_REBIND: case DHS_BOUND: case DHS_INFORM: break; default: continue; } if (state->lease.addr.s_addr == addr->s_addr) return 1; } return 0; } #endif static void dhcp_reboot(struct interface *ifp) { struct if_options *ifo; struct dhcp_state *state = D_STATE(ifp); #ifdef ARP struct ipv4_addr *ia; #endif if (state == NULL || state->state == DHS_NONE) return; ifo = ifp->options; state->state = DHS_REBOOT; state->interval = 0; if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) { loginfox("%s: waiting for carrier", ifp->name); return; } if (ifo->options & DHCPCD_STATIC) { dhcp_static(ifp); return; } if (ifo->options & DHCPCD_INFORM) { loginfox("%s: informing address of %s", ifp->name, inet_ntoa(state->lease.addr)); dhcp_inform(ifp); return; } if (ifo->reboot == 0 || state->offer == NULL) { dhcp_discover(ifp); return; } if (!IS_DHCP(state->offer)) return; loginfox("%s: rebinding lease of %s", ifp->name, inet_ntoa(state->lease.addr)); #ifdef ARP /* If the address exists on the interface and no other interface * is currently using it then announce it to ensure this * interface gets the reply. */ ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL); if (ia != NULL && !(ifp->ctx->options & DHCPCD_TEST) && #ifdef IN_IFF_NOTUSEABLE !(ia->addr_flags & IN_IFF_NOTUSEABLE) && #endif dhcp_activeaddr(ifp, &state->lease.addr) == 0) arp_ifannounceaddr(ifp, &state->lease.addr); #endif dhcp_new_xid(ifp); state->lease.server.s_addr = INADDR_ANY; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); #ifdef IPV4LL /* Need to add this before dhcp_expire and friends. */ if (!ifo->fallback && ifo->options & DHCPCD_IPV4LL) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, ipv4ll_start, ifp); #endif if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, dhcp_lastlease, ifp); else if (!(ifo->options & DHCPCD_INFORM)) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, dhcp_expire, ifp); /* Don't bother ARP checking as the server could NAK us first. * Don't call dhcp_request as that would change the state */ send_request(ifp); } void dhcp_drop(struct interface *ifp, const char *reason) { struct dhcp_state *state; #ifdef RELEASE_SLOW struct timespec ts; #endif state = D_STATE(ifp); /* dhcp_start may just have been called and we don't yet have a state * but we do have a timeout, so punt it. */ if (state == NULL || state->state == DHS_NONE) { eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); return; } #ifdef 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; unlink(state->leasefile); if (ifp->carrier != LINK_DOWN && state->new != NULL && state->lease.server.s_addr != INADDR_ANY) { loginfox("%s: releasing lease of %s", ifp->name, inet_ntoa(state->lease.addr)); dhcp_new_xid(ifp); send_message(ifp, DHCP_RELEASE, NULL); #ifdef RELEASE_SLOW /* Give the packet a chance to go */ ts.tv_sec = RELEASE_DELAY_S; ts.tv_nsec = RELEASE_DELAY_NS; nanosleep(&ts, NULL); #endif } } 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; ipv4_applyaddr(ifp); free(state->old); state->old = NULL; state->old_len = 0; state->lease.addr.s_addr = 0; ifp->options->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED); } static int blacklisted_ip(const struct if_options *ifo, in_addr_t addr) { size_t i; for (i = 0; i < ifo->blacklist_len; i += 2) if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1])) return 1; return 0; } #define WHTLST_NONE 0 #define WHTLST_MATCH 1 #define WHTLST_NOMATCH 2 static unsigned int whitelisted_ip(const struct if_options *ifo, in_addr_t addr) { size_t i; if (ifo->whitelist_len == 0) return WHTLST_NONE; for (i = 0; i < ifo->whitelist_len; i += 2) if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) return WHTLST_MATCH; return WHTLST_NOMATCH; } static void log_dhcp(logfunc_t *logfunc, 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) logfunc("%s: %s %s %s `%s'", ifp->name, msg, tfrom, inet_ntoa(addr), sname); else logfunc("%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) logfunc("%s: %s %s %s", ifp->name, msg, tfrom, inet_ntoa(addr)); else logfunc("%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) { 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; #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) /* Handled in our BPF filter. */ #if 0 if (bootp->op != BOOTREPLY) { logdebugx("%s: op (%d) is not BOOTREPLY", ifp->name, bootp->op); return; } #endif if (state->xid != ntohl(bootp->xid)) { if (state->state != DHS_BOUND && state->state != DHS_NONE) 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)) { 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(logerrx, "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(logerrx, "no authentication"); return; } LOGDHCP0(logwarnx, "no authentication"); } #endif /* RFC 3203 */ if (type == DHCP_FORCERENEW) { if (from->s_addr == INADDR_ANY || from->s_addr == INADDR_BROADCAST) { LOGDHCP(logerrx, "discarding Force Renew"); return; } #ifdef AUTH if (auth == NULL) { LOGDHCP(logerrx, "unauthenticated Force Renew"); if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) return; } if (state->state != DHS_BOUND && state->state != DHS_INFORM) { LOGDHCP(logdebugx, "not bound, ignoring Force Renew"); return; } LOGDHCP(loginfox, "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(logerrx, "unauthenticated Force Renew"); #endif return; } if (state->state == DHS_BOUND) { /* Before we supported FORCERENEW we closed off the raw * port so we effectively ignored all messages. * As such we'll not log by default here. */ //LOGDHCP(logdebugx, "bound, ignoring"); return; } if (state->state == DHS_PROBE) { /* Ignore any DHCP messages whilst probing a lease to bind. */ LOGDHCP(logdebugx, "probing, ignoring"); return; } /* reset the message counter */ state->interval = 0; /* Ensure that no reject options are present */ for (i = 1; i < 255; i++) { if (has_option_mask(ifo->rejectmask, i) && get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len, (uint8_t)i) == 0) { LOGDHCP(logwarnx, "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(logwarnx, "reject NAK"); return; } /* We should restart on a NAK */ LOGDHCP(logwarnx, "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"); unlink(state->leasefile); } /* If we constantly get NAKS then we should slowly back off */ eloop_timeout_add_sec(ifp->ctx->eloop, state->nakoff, dhcp_discover, ifp); if (state->nakoff == 0) state->nakoff = 1; else { state->nakoff *= 2; if (state->nakoff > NAKOFF_MAX) state->nakoff = NAKOFF_MAX; } return; } /* Ensure that all required options are present */ for (i = 1; i < 255; i++) { if (has_option_mask(ifo->requiremask, i) && get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len, (uint8_t)i) != 0) { /* If we are BOOTP, then ignore the need for serverid. * To ignore BOOTP, require dhcp_message_type. * However, nothing really stops BOOTP from providing * DHCP style options as well so the above isn't * always true. */ if (type == 0 && i == DHO_SERVERID) continue; LOGDHCP(logwarnx, "reject DHCP"); return; } } /* DHCP Auto-Configure, RFC 2563 */ if (type == DHCP_OFFER && bootp->yiaddr == 0) { LOGDHCP(logwarnx, "no address given"); if ((msg = get_option_string(ifp->ctx, bootp, bootp_len, DHO_MESSAGE))) { logwarnx("%s: message: %s", ifp->name, msg); free(msg); } #ifdef IPV4LL if (state->state == DHS_DISCOVER && get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len, DHO_AUTOCONFIGURE) == 0) { switch (tmp) { case 0: LOGDHCP(logwarnx, "IPv4LL disabled from"); ipv4ll_drop(ifp); #ifdef ARP arp_drop(ifp); #endif break; case 1: LOGDHCP(logwarnx, "IPv4LL enabled from"); ipv4ll_start(ifp); break; default: logerrx("%s: unknown auto configuration " "option %d", ifp->name, tmp); break; } eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, DHCP_MAX, dhcp_discover, ifp); } #endif 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(logwarnx, "reject invalid address"); return; } #ifdef IN_IFF_DUPLICATED ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); if (ia && ia->addr_flags & IN_IFF_DUPLICATED) { LOGDHCP(logwarnx, "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(loginfox, "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); 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(logwarnx, "ignoring offer of"); return; } /* We should only be dealing with acks */ if (type != DHCP_ACK) { LOGDHCP(logerr, "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(logdebugx, "ignoring ack of"); return; } } rapidcommit: if (!(ifo->options & DHCPCD_INFORM)) LOGDHCP(logdebugx, "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); #ifdef ARP dhcp_arp_bind(ifp); #else dhcp_bind(ifp); #endif } static void * get_udp_data(void *udp, size_t *len) { struct bootp_pkt *p; p = (struct bootp_pkt *)udp; *len = (size_t)ntohs(p->ip.ip_len) - sizeof(p->ip) - sizeof(p->udp); return (char *)udp + offsetof(struct bootp_pkt, bootp); } static int valid_udp_packet(void *data, size_t data_len, struct in_addr *from, int noudpcsum) { struct bootp_pkt *p; uint16_t bytes; if (data_len < sizeof(p->ip)) { if (from) from->s_addr = INADDR_ANY; errno = ERANGE; return -1; } p = (struct bootp_pkt *)data; if (from) from->s_addr = p->ip.ip_src.s_addr; if (checksum(&p->ip, sizeof(p->ip)) != 0) { errno = EINVAL; return -1; } bytes = ntohs(p->ip.ip_len); /* Check we have a payload */ if (bytes <= sizeof(p->ip) + sizeof(p->udp)) { errno = ERANGE; return -1; } /* Check we don't go beyond the payload */ if (bytes > data_len) { errno = ENOBUFS; return -1; } if (noudpcsum == 0) { uint16_t udpsum, iplen; /* This does scribble on the packet, but at this point * we don't care to keep it. */ iplen = p->ip.ip_len; udpsum = p->udp.uh_sum; p->udp.uh_sum = 0; p->ip.ip_hl = 0; p->ip.ip_v = 0; p->ip.ip_tos = 0; p->ip.ip_len = p->udp.uh_ulen; p->ip.ip_id = 0; p->ip.ip_off = 0; p->ip.ip_ttl = 0; p->ip.ip_sum = 0; if (udpsum && checksum(p, bytes) != udpsum) { errno = EINVAL; return -1; } p->ip.ip_len = iplen; } return 0; } static void dhcp_handlepacket(struct interface *ifp, uint8_t *data, size_t len) { struct bootp *bootp; struct in_addr from; size_t udp_len; const struct dhcp_state *state = D_CSTATE(ifp); if (valid_udp_packet(data, len, &from, state->bpf_flags & RAW_PARTIALCSUM) == -1) { if (errno == EINVAL) logerrx("%s: checksum failure from %s", ifp->name, inet_ntoa(from)); else logerr("%s: invalid UDP packet from %s", ifp->name, inet_ntoa(from)); return; } if (ifp->flags & IFF_POINTOPOINT && (state->addr == NULL || state->addr->brd.s_addr != from.s_addr)) { logwarnx("%s: server %s is not destination", ifp->name, inet_ntoa(from)); } /* * 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); /* udp_len must be correct because the values are checked in * valid_udp_packet(). */ if (udp_len < offsetof(struct bootp, vend)) { logerrx("%s: truncated packet (%zu) from %s", ifp->name, udp_len, inet_ntoa(from)); return; } /* To make our IS_DHCP macro easy, ensure the vendor * area has at least 4 octets. */ len = udp_len - offsetof(struct bootp, vend); while (len < 4) { bootp->vend[len++] = '\0'; udp_len++; } dhcp_handledhcp(ifp, bootp, udp_len, &from); } static void dhcp_readpacket(void *arg) { struct interface *ifp = arg; uint8_t buf[MTU_MAX]; ssize_t bytes; struct dhcp_state *state = D_STATE(ifp); /* 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. */ state->bpf_flags &= ~BPF_EOF; state->bpf_flags |= BPF_READING; while (!(state->bpf_flags & BPF_EOF)) { bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf), &state->bpf_flags); if (bytes == -1) { if (state->state != DHS_NONE) { logerr("%s: %s", __func__, ifp->name); dhcp_close(ifp); } break; } dhcp_handlepacket(ifp, buf, (size_t)bytes); /* Check we still have a state after processing. */ if ((state = D_STATE(ifp)) == NULL) break; } if (state != NULL) state->bpf_flags &= ~BPF_READING; } static void dhcp_handleudp(void *arg) { struct dhcpcd_ctx *ctx; uint8_t buffer[MTU_MAX]; ctx = arg; /* Just read what's in the UDP fd and discard it as we always read * from the raw fd */ if (read(ctx->udp_fd, buffer, sizeof(buffer)) == -1) { logerr(__func__); eloop_event_delete(ctx->eloop, ctx->udp_fd); close(ctx->udp_fd); ctx->udp_fd = -1; } } static int dhcp_openbpf(struct interface *ifp) { struct dhcp_state *state; state = D_STATE(ifp); if (state->bpf_fd != -1) return 0; state->bpf_fd = bpf_open(ifp, bpf_bootp); if (state->bpf_fd == -1) { if (errno == ENOENT) { logerrx("%s not found", bpf_name); /* May as well disable IPv4 entirely at * this point as we really need it. */ ifp->options->options &= ~DHCPCD_IPV4; } else logerr("%s: %s", __func__, ifp->name); return -1; } eloop_event_add(ifp->ctx->eloop, state->bpf_fd, dhcp_readpacket, ifp); return 0; } int dhcp_dump(struct interface *ifp) { struct dhcp_state *state; ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state)); if (state == NULL) goto eexit; state->bpf_fd = -1; dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), AF_INET, ifp); state->new_len = read_lease(ifp, &state->new); if (state->new == NULL) { logerr("%s: %s", *ifp->name ? ifp->name : state->leasefile, __func__); return -1; } state->reason = "DUMP"; return script_runreason(ifp, state->reason); eexit: logerr(__func__); return -1; } 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_fd != -1) { eloop_event_delete(ctx->eloop, ctx->udp_fd); close(ctx->udp_fd); ctx->udp_fd = -1; } free(ctx->opt_buffer); ctx->opt_buffer = NULL; } } static int dhcp_initstate(struct interface *ifp) { struct dhcp_state *state; state = D_STATE(ifp); if (state != NULL) return 0; ifp->if_data[IF_DATA_DHCP] = calloc(1, sizeof(*state)); state = D_STATE(ifp); if (state == NULL) return -1; state->state = DHS_NONE; /* 0 is a valid fd, so init to -1 */ state->bpf_fd = -1; #ifdef ARPING state->arping_index = -1; #endif return 1; } static int dhcp_init(struct interface *ifp) { struct dhcp_state *state; const struct if_options *ifo; uint8_t len; char buf[(sizeof(ifo->clientid) - 1) * 3]; int r; r = dhcp_initstate(ifp); if (r == -1) return -1; else if (r == 1) { /* Now is a good time to find IPv4 routes */ if_initrt(ifp->ctx, AF_INET); } 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) unlink(state->leasefile); free(state->clientid); state->clientid = NULL; if (*ifo->clientid) { state->clientid = malloc((size_t)(ifo->clientid[0] + 1)); if (state->clientid == NULL) goto eexit; memcpy(state->clientid, ifo->clientid, (size_t)(ifo->clientid[0]) + 1); } else if (ifo->options & DHCPCD_CLIENTID) { if (ifo->options & DHCPCD_DUID) { state->clientid = malloc(ifp->ctx->duid_len + 6); if (state->clientid == NULL) goto eexit; state->clientid[0] =(uint8_t)(ifp->ctx->duid_len + 5); state->clientid[1] = 255; /* RFC 4361 */ memcpy(state->clientid + 2, ifo->iaid, 4); memcpy(state->clientid + 6, ifp->ctx->duid, ifp->ctx->duid_len); } else { len = (uint8_t)(ifp->hwlen + 1); state->clientid = malloc((size_t)len + 1); if (state->clientid == NULL) goto eexit; state->clientid[0] = len; state->clientid[1] = (uint8_t)ifp->family; memcpy(state->clientid + 2, ifp->hwaddr, ifp->hwlen); } } if (ifo->options & DHCPCD_DUID) /* Don't bother logging as DUID and IAID are reported * at device start. */ return 0; if (ifo->options & DHCPCD_CLIENTID) 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 if_options *ifo = ifp->options; struct dhcp_state *state; struct stat st; uint32_t l; int nolease; if (!(ifo->options & DHCPCD_IPV4)) return; /* Listen on *.*.*.*:bootpc so that the kernel never sends an * ICMP port unreachable message back to the DHCP server */ if (ifp->ctx->udp_fd == -1) { ifp->ctx->udp_fd = dhcp_openudp(NULL); if (ifp->ctx->udp_fd == -1) { /* Don't log an error if some other process * is handling this. */ if (errno != EADDRINUSE) logerr("%s: dhcp_openudp", __func__); } else eloop_event_add(ifp->ctx->eloop, ifp->ctx->udp_fd, dhcp_handleudp, ifp->ctx); } 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) { struct arp_state *astate; astate = arp_new(ifp, NULL); if (astate) { astate->probed_cb = dhcp_arp_probed; astate->conflicted_cb = dhcp_arp_conflicted; dhcp_arp_probed(astate); } return; } #endif if (ifo->options & DHCPCD_STATIC) { dhcp_static(ifp); return; } if (ifo->options & DHCPCD_DHCP && dhcp_openbpf(ifp) == -1) return; if (ifo->options & DHCPCD_INFORM) { dhcp_inform(ifp); return; } if (ifp->hwlen == 0 && ifo->clientid[0] == '\0') { logwarnx("%s: needs a clientid to configure", ifp->name); dhcp_drop(ifp, "FAIL"); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); return; } /* We don't want to read the old lease if we NAK an old test */ nolease = state->offer && ifp->ctx->options & DHCPCD_TEST; if (!nolease && ifo->options & DHCPCD_DHCP) { state->offer_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; 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 != ~0U && stat(state->leasefile, &st) == 0) { time_t now; /* Offset lease times and check expiry */ now = time(NULL); if (now == -1 || (time_t)state->lease.leasetime < now - st.st_mtime) { 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 - st.st_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)) dhcp_discover(ifp); else dhcp_reboot(ifp); } void dhcp_start(struct interface *ifp) { struct timespec tv; #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 family 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->family) { 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 tv.tv_sec = DHCP_MIN_DELAY; tv.tv_nsec = (suseconds_t)arc4random_uniform( (DHCP_MAX_DELAY - DHCP_MIN_DELAY) * NSEC_PER_SEC); timespecnorm(&tv); logdebugx("%s: delaying IPv4 for %0.1f seconds", ifp->name, timespec_to_double(&tv)); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcp_start1, ifp); } void dhcp_abort(struct interface *ifp) { struct dhcp_state *state; state = D_STATE(ifp); #ifdef ARPING if (state != NULL) state->arping_index = -1; #endif eloop_timeout_delete(ifp->ctx->eloop, dhcp_start1, ifp); if (state != NULL && state->added) { rt_build(ifp->ctx, AF_INET); #ifdef ARP arp_announceaddr(ifp->ctx, &state->addr->addr); #endif } } void 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; if (cmd == RTM_DELADDR) { if (state->addr == ia) { loginfox("%s: pid %d deleted IP address %s", ifp->name, pid, ia->saddr); state->addr = NULL; /* Don't clear the added state as we need * to drop the lease. */ dhcp_drop(ifp, "EXPIRE"); dhcp_start1(ifp); } return; } if (cmd != RTM_NEWADDR) return; #ifdef IN_IFF_NOTUSEABLE if (ia->addr_flags & IN_IFF_NOTUSEABLE) return; #endif ifo = ifp->options; if (ifo->options & DHCPCD_INFORM) { if (state->state != DHS_INFORM) dhcp_inform(ifp); return; } if (!(ifo->options & DHCPCD_STATIC)) return; if (ifo->req_addr.s_addr != INADDR_ANY) return; free(state->old); state->old = state->new; state->new_len = dhcp_message_new(&state->new, &ia->addr, &ia->mask); if (state->new == NULL) return; 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); if (ifo->options & DHCPCD_INFORM) { state->state = DHS_INFORM; dhcp_new_xid(ifp); state->lease.server.s_addr = INADDR_ANY; state->addr = ia; dhcp_inform(ifp); } } dhcpcd5-7.1.0/src/dhcp.h000066400000000000000000000176661342162717100147330ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "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_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 }; /* 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 */ }; 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; }; 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; time_t interval; time_t nakoff; uint32_t xid; int socket; int bpf_fd; unsigned int bpf_flags; 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 }; #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" #ifdef INET char *decode_rfc3361(const uint8_t *, size_t); ssize_t decode_rfc3442(char *, size_t, const uint8_t *p, size_t); 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(struct rt_head *, struct interface *); ssize_t dhcp_env(char **, const char *, const struct bootp *, size_t, const struct interface *); void 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 *); #else #define dhcp_start(a) {} #define dhcp_abort(a) {} #define dhcp_renew(a) {} #define dhcp_reboot(a, b) (b = b) #define dhcp_reboot_newopts(a, b) (b = b) #define dhcp_close(a) {} #define dhcp_free(a) {} #define dhcp_dump(a) (-1) #endif #endif dhcpcd5-7.1.0/src/dhcp6.c000066400000000000000000003004701342162717100150000ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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. */ /* TODO: We should decline dupliate addresses detected */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ELOOP_QUEUE 4 #include "config.h" #include "common.h" #include "dhcp.h" #include "dhcp6.h" #include "duid.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv6nd.h" #include "logerr.h" #include "script.h" #ifdef HAVE_SYS_BITOPS_H #include #else #include "compat/bitops.h" #endif /* DHCPCD Project has been assigned an IANA PEN of 40712 */ #define DHCPCD_IANA_PEN 40712 /* Unsure if I want this */ //#define VENDOR_SPLIT /* Support older systems with different defines */ #if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) #define IPV6_RECVPKTINFO IPV6_PKTINFO #endif #ifdef DHCP6 /* Assert the correct structure size for on wire */ struct dhcp6_message { uint8_t type; uint8_t xid[3]; /* followed by options */ }; __CTASSERT(sizeof(struct dhcp6_message) == 4); struct dhcp6_option { uint16_t code; uint16_t len; /* followed by data */ }; __CTASSERT(sizeof(struct dhcp6_option) == 4); struct dhcp6_ia_na { uint8_t iaid[4]; uint32_t t1; uint32_t t2; }; __CTASSERT(sizeof(struct dhcp6_ia_na) == 12); struct dhcp6_ia_ta { uint8_t iaid[4]; }; __CTASSERT(sizeof(struct dhcp6_ia_ta) == 4); struct dhcp6_ia_addr { struct in6_addr addr; uint32_t pltime; uint32_t vltime; }; __CTASSERT(sizeof(struct dhcp6_ia_addr) == 16 + 8); /* XXX FIXME: This is the only packed structure and it does not align. * Maybe manually decode it? */ struct dhcp6_pd_addr { uint32_t pltime; uint32_t vltime; uint8_t prefix_len; struct in6_addr prefix; } __packed; __CTASSERT(sizeof(struct dhcp6_pd_addr) == 8 + 1 + 16); struct dhcp6_op { uint16_t type; const char *name; }; static const struct dhcp6_op dhcp6_ops[] = { { DHCP6_SOLICIT, "SOLICIT6" }, { DHCP6_ADVERTISE, "ADVERTISE6" }, { DHCP6_REQUEST, "REQUEST6" }, { DHCP6_REPLY, "REPLY6" }, { DHCP6_RENEW, "RENEW6" }, { DHCP6_REBIND, "REBIND6" }, { DHCP6_CONFIRM, "CONFIRM6" }, { DHCP6_INFORMATION_REQ, "INFORM6" }, { DHCP6_RELEASE, "RELEASE6" }, { DHCP6_RECONFIGURE, "RECONFIGURE6" }, { 0, NULL } }; struct dhcp_compat { uint8_t dhcp_opt; uint16_t dhcp6_opt; }; const struct dhcp_compat dhcp_compats[] = { { DHO_DNSSERVER, D6_OPTION_DNS_SERVERS }, { DHO_HOSTNAME, D6_OPTION_FQDN }, { DHO_DNSDOMAIN, D6_OPTION_FQDN }, { DHO_NISSERVER, D6_OPTION_NIS_SERVERS }, { DHO_NTPSERVER, D6_OPTION_SNTP_SERVERS }, { DHO_RAPIDCOMMIT, D6_OPTION_RAPID_COMMIT }, { DHO_FQDN, D6_OPTION_FQDN }, { DHO_VIVCO, D6_OPTION_VENDOR_CLASS }, { DHO_VIVSO, D6_OPTION_VENDOR_OPTS }, { DHO_DNSSEARCH, D6_OPTION_DOMAIN_LIST }, { 0, 0 } }; static const char * const dhcp6_statuses[] = { "Success", "Unspecified Failure", "No Addresses Available", "No Binding", "Not On Link", "Use Multicast", "No Prefix Available" }; static void dhcp6_bind(struct interface *, const char *); static void dhcp6_failinform(void *); static int dhcp6_listen(struct dhcpcd_ctx *, struct ipv6_addr *); static void dhcp6_recvaddr(void *); 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, i; uint8_t *p; ssize_t vlen; const struct vivco *vivco; char vendor[VENDORCLASSID_MAX_LEN]; struct dhcp6_option o; ifo = ifp->options; len = sizeof(uint32_t); /* IANA PEN */ if (ifo->vivco_en) { for (i = 0, vivco = ifo->vivco; i < ifo->vivco_len; i++, vivco++) len += sizeof(uint16_t) + vivco->len; vlen = 0; /* silence bogus gcc warning */ } else { vlen = dhcp_vendor(vendor, sizeof(vendor)); if (vlen == -1) vlen = 0; else len += sizeof(uint16_t) + (size_t)vlen; } if (len > UINT16_MAX) { 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 (vlen) { hvlen = htons((uint16_t)vlen); memcpy(p, &hvlen, sizeof(hvlen)); p += sizeof(hvlen); memcpy(p, vendor, (size_t)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; time_t 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 { timespecsub(&tv, &state->started, &tv); /* Elapsed time is measured in centiseconds. * We need to be sure it will not potentially overflow. */ if (tv.tv_sec >= (UINT16_MAX / CSEC_PER_SEC) + 1) hsec = UINT16_MAX; else { hsec = (tv.tv_sec * CSEC_PER_SEC) + (tv.tv_nsec / NSEC_PER_CSEC); if (hsec > UINT16_MAX) hsec = UINT16_MAX; } } 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 == 0) { /* No SLA set, so make an assumption of * desired SLA and prefix length. */ asla.sla = ifp->index; asla.prefix_len = 0; asla.sla_set = 0; sla = &asla; } else if (sla->sla == 0 && sla->prefix_len == 0) { /* An SLA of 0 was set with no prefix length specified. * This means we delegate the whole prefix. */ asla.sla = sla->sla; asla.prefix_len = prefix->prefix_len; asla.sla_set = 0; 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 = 0; sla = &asla; } if (sla->prefix_len == 0) { uint32_t sla_max; int bits; if (ia->sla_max == 0) { const struct interface *ifi; sla_max = 0; TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) { if (ifi->index > sla_max) sla_max = ifi->index; } } else sla_max = ia->sla_max; 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 */ ia->sla_max = (uint32_t)BIT_MASK(asla.prefix_len - prefix->prefix_len); } if (ipv6_userprefix(&prefix->prefix, prefix->prefix_len, sla->sla, addr, sla->prefix_len) == -1) { sa = inet_ntop(AF_INET6, &prefix->prefix, sabuf, sizeof(sabuf)); 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; 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 state = D6_STATE(ifp); if (state->send) { free(state->send); state->send = NULL; } ifo = ifp->options; fqdn = ifo->fqdn; if (fqdn == FQDN_DISABLE && ifo->options & DHCPCD_HOSTNAME) { /* We're sending the DHCPv4 hostname option, so send FQDN as * DHCPv6 has no FQDN option and DHCPv4 must not send * hostname and FQDN according to RFC4702 */ fqdn = FQDN_BOTH; } if (fqdn != FQDN_DISABLE) 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) { for (l = 0, opt = ifp->ctx->dhcp6_opts; l < ifp->ctx->dhcp6_opts_len; l++, opt++) { for (n = 0, opt2 = ifo->dhcp6_override; n < ifo->dhcp6_override_len; n++, opt2++) { if (opt->option == opt2->option) break; } if (n < ifo->dhcp6_override_len) continue; if (!(opt->type & OT_NOREQ) && (opt->type & OT_REQUEST || has_option_mask(ifo->requestmask6, opt->option))) { n_options++; len += sizeof(o.len); } } #ifndef SMALL for (l = 0, opt = ifo->dhcp6_override; l < ifo->dhcp6_override_len; l++, opt++) { if (!(opt->type & OT_NOREQ) && (opt->type & OT_REQUEST || has_option_mask(ifo->requestmask6, opt->option))) { 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 (ifo->mudurl[0]) len += sizeof(o) + ifo->mudurl[0]; #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE) len += sizeof(o); /* Reconfigure Accept */ #endif } len += sizeof(*state->send); len += sizeof(o) + ifp->ctx->duid_len; len += sizeof(o) + sizeof(uint16_t); /* elapsed */ 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_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->prefix_vltime == 0 && !(ap->flags & IPV6_AF_REQUEST)) continue; if (ap->ia_type == D6_OPTION_IA_PD) { #ifndef SMALL len += sizeof(o) + sizeof(struct dhcp6_pd_addr); if (ap->prefix_exclude_len) len += sizeof(o) + 1 + (uint8_t)((ap->prefix_exclude_len - ap->prefix_len - 1) / NBBY) + 1; #endif } else len += sizeof(o) + sizeof(struct dhcp6_ia_addr); } /* FALLTHROUGH */ case DH6S_INIT: for (l = 0; l < ifo->ia_len; l++) { len += sizeof(o) + sizeof(uint32_t); /* IAID */ /* IA_TA does not have T1 or T2 timers */ if (ifo->ia[l].ia_type != D6_OPTION_IA_TA) len += sizeof(uint32_t) + sizeof(uint32_t); } IA = 1; break; default: IA = 0; } if (state->state == DH6S_DISCOVER && !(ifp->ctx->options & DHCPCD_TEST) && has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT)) len += sizeof(o); if (m == NULL) { m = state->new; ml = state->new_len; } unicast = NULL; /* Depending on state, get the unicast address */ switch(state->state) { case DH6S_INIT: /* FALLTHROUGH */ case DH6S_DISCOVER: type = DHCP6_SOLICIT; break; case DH6S_REQUEST: type = DHCP6_REQUEST; unicast = dhcp6_findmoption(m, ml, D6_OPTION_UNICAST, &uni_len); break; case DH6S_CONFIRM: type = DHCP6_CONFIRM; break; case DH6S_REBIND: type = DHCP6_REBIND; break; case DH6S_RENEW: type = DHCP6_RENEW; unicast = dhcp6_findmoption(m, ml, D6_OPTION_UNICAST, &uni_len); break; case DH6S_INFORM: type = DHCP6_INFORMATION_REQ; break; case DH6S_RELEASE: type = DHCP6_RELEASE; unicast = dhcp6_findmoption(m, ml, D6_OPTION_UNICAST, &uni_len); break; default: errno = EINVAL; return -1; } /* In non master 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_MASTER)) { logdebugx("%s: ignoring unicast option as not master", ifp->name); unicast = NULL; } #ifdef AUTH auth_len = 0; if (ifo->auth.options & DHCPCD_AUTH_SEND) { ssize_t alen = dhcp_auth_encode(&ifo->auth, state->auth.token, NULL, 0, 6, type, NULL, 0); if (alen != -1 && alen > UINT16_MAX) { errno = ERANGE; alen = -1; } if (alen == -1) 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) { \ COPYIN1((_code), (_len)); \ if ((_len) != 0) { \ memcpy(p, (_data), (_len)); \ p += (_len); \ } \ } #define NEXTLEN (p + offsetof(struct dhcp6_option, len)) p = (uint8_t *)state->send + sizeof(*state->send); COPYIN(D6_OPTION_CLIENTID, ifp->ctx->duid, (uint16_t)ifp->ctx->duid_len); if (si != NULL) COPYIN(D6_OPTION_SERVERID, si, si_len); si_len = 0; COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len)); 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->state == DH6S_DISCOVER && !(ifp->ctx->options & DHCPCD_TEST) && has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT)) COPYIN1(D6_OPTION_RAPID_COMMIT, 0); for (l = 0; IA && l < ifo->ia_len; l++) { ifia = &ifo->ia[l]; o_lenp = NEXTLEN; /* TA structure is the same as the others, * it just lacks the T1 and T2 timers. * These happen to be at the end of the struct, * so we just don't copy them in. */ if (ifia->ia_type == D6_OPTION_IA_TA) ia_na_len = sizeof(struct dhcp6_ia_ta); else ia_na_len = sizeof(ia_na); memcpy(ia_na.iaid, ifia->iaid, sizeof(ia_na.iaid)); ia_na.t1 = 0; ia_na.t2 = 0; COPYIN(ifia->ia_type, &ia_na, ia_na_len); TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->flags & IPV6_AF_STALE) continue; if (ap->prefix_vltime == 0 && !(ap->flags & IPV6_AF_REQUEST)) continue; if (ap->ia_type != ifia->ia_type) continue; if (memcmp(ap->iaid, ifia->iaid, sizeof(ap->iaid))) continue; if (ap->ia_type == D6_OPTION_IA_PD) { #ifndef SMALL struct dhcp6_pd_addr pdp; pdp.pltime = htonl(ap->prefix_pltime); pdp.vltime = htonl(ap->prefix_vltime); pdp.prefix_len = ap->prefix_len; /* pdp.prefix is not aligned, so copy it in. */ memcpy(&pdp.prefix, &ap->prefix, sizeof(pdp.prefix)); COPYIN(D6_OPTION_IAPREFIX, &pdp, sizeof(pdp)); ia_na_len = (uint16_t) (ia_na_len + sizeof(o) + sizeof(pdp)); /* RFC6603 Section 4.2 */ if (ap->prefix_exclude_len) { uint8_t exb[16], *ep, u8; const uint8_t *pp; n = (size_t)((ap->prefix_exclude_len - ap->prefix_len - 1) / NBBY) + 1; ep = exb; *ep++ = (uint8_t)ap->prefix_exclude_len; pp = ap->prefix_exclude.s6_addr; pp += (size_t) ((ap->prefix_len - 1) / NBBY) + (n - 1); u8 = ap->prefix_len % NBBY; if (u8) n--; while (n-- > 0) *ep++ = *pp--; if (u8) *ep = (uint8_t)(*pp << u8); n++; COPYIN(D6_OPTION_PD_EXCLUDE, exb, (uint16_t)n); ia_na_len = (uint16_t) (ia_na_len + sizeof(o) + n); } #endif } else { struct dhcp6_ia_addr ia; ia.addr = ap->addr; ia.pltime = htonl(ap->prefix_pltime); ia.vltime = htonl(ap->prefix_vltime); COPYIN(D6_OPTION_IA_ADDR, &ia, sizeof(ia)); ia_na_len = (uint16_t) (ia_na_len + sizeof(o) + sizeof(ia)); } } /* Update the total option lenth. */ ia_na_len = htons(ia_na_len); memcpy(o_lenp, &ia_na_len, sizeof(ia_na_len)); } if (state->send->type != DHCP6_RELEASE) { 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 (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) COPYIN1(D6_OPTION_RECONF_ACCEPT, 0); #endif if (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 (!(opt->type & OT_NOREQ) && (opt->type & OT_REQUEST || has_option_mask(ifo->requestmask6, opt->option))) { 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 (!(opt->type & OT_NOREQ) && (opt->type & OT_REQUEST || has_option_mask(ifo->requestmask6, opt->option))) { 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)); } } #ifdef AUTH /* This has to be the last option */ if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) { COPYIN1(D6_OPTION_AUTH, auth_len); /* data will be filled at send message time */ } #endif return 0; } static const char * dhcp6_get_op(uint16_t type) { const struct dhcp6_op *d; for (d = dhcp6_ops; d->name; d++) if (d->type == type) return d->name; return NULL; } static void dhcp6_freedrop_addrs(struct interface *ifp, int drop, const struct interface *ifd) { struct dhcp6_state *state; state = D6_STATE(ifp); if (state) { ipv6_freedrop_addrs(&state->addrs, drop, ifd); if (drop) rt_build(ifp->ctx, AF_INET6); } } #ifndef SMALL static void dhcp6_delete_delegates(struct interface *ifp) { struct interface *ifp0; if (ifp->ctx->ifaces) { TAILQ_FOREACH(ifp0, ifp->ctx->ifaces, next) { if (ifp0 != ifp) dhcp6_freedrop_addrs(ifp0, 1, ifp); } } } #endif #ifdef AUTH static ssize_t dhcp6_update_auth(struct interface *ifp, struct dhcp6_message *m, size_t len) { struct dhcp6_state *state; uint8_t *opt; uint16_t opt_len; opt = dhcp6_findmoption(m, len, D6_OPTION_AUTH, &opt_len); if (opt == NULL) return -1; state = D6_STATE(ifp); return dhcp_auth_encode(&ifp->options->auth, state->auth.token, (uint8_t *)state->send, state->send_len, 6, state->send->type, opt, opt_len); } #endif static int dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) { struct dhcp6_state *state; struct dhcpcd_ctx *ctx; struct sockaddr_in6 dst; struct timespec RTprev; double rnd; time_t ms; uint8_t neg; const char *broad_uni; const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT; struct ipv6_addr *lla; int s; if (!callback && ifp->carrier == LINK_DOWN) return 0; memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; dst.sin6_port = htons(DHCP6_SERVER_PORT); #ifdef HAVE_SA_LEN dst.sin6_len = sizeof(dst); #endif state = D6_STATE(ifp); lla = ipv6_linklocal(ifp); /* We need to ensure we have sufficient scope to unicast the address */ /* XXX FIXME: We should check any added addresses we have like from * a Router Advertisement */ if (IN6_IS_ADDR_UNSPECIFIED(&state->unicast) || (state->state == DH6S_REQUEST && (!IN6_IS_ADDR_LINKLOCAL(&state->unicast) || lla == NULL))) { dst.sin6_addr = alldhcp; broad_uni = "broadcasting"; } else { dst.sin6_addr = state->unicast; broad_uni = "unicasting"; } if (!callback) logdebugx("%s: %s %s with xid 0x%02x%02x%02x", ifp->name, broad_uni, dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], state->send->xid[2]); else { if (state->IMD && !(ifp->options->options & DHCPCD_INITIAL_DELAY)) state->IMD = 0; if (state->IMD) { /* Some buggy PPP servers close the link too early * after sending an invalid status in their reply * which means this host won't see it. * 1 second grace seems to be the sweet spot. */ if (ifp->flags & IFF_POINTOPOINT) state->RT.tv_sec = 1; else state->RT.tv_sec = 0; state->RT.tv_nsec = (suseconds_t)arc4random_uniform( (uint32_t)(state->IMD * NSEC_PER_SEC)); timespecnorm(&state->RT); broad_uni = "delaying"; goto logsend; } if (state->RTC == 0) { RTprev.tv_sec = state->IRT; RTprev.tv_nsec = 0; state->RT.tv_sec = RTprev.tv_sec; state->RT.tv_nsec = 0; } else { RTprev = state->RT; timespecadd(&state->RT, &state->RT, &state->RT); } rnd = DHCP6_RAND_MIN; rnd += (suseconds_t)arc4random_uniform( DHCP6_RAND_MAX - DHCP6_RAND_MIN); rnd /= MSEC_PER_SEC; neg = (rnd < 0.0); if (neg) rnd = -rnd; ts_to_ms(ms, &RTprev); ms = (time_t)((double)ms * rnd); ms_to_ts(&RTprev, ms); if (neg) timespecsub(&state->RT, &RTprev, &state->RT); else timespecadd(&state->RT, &RTprev, &state->RT); if (state->MRT != 0 && state->RT.tv_sec > state->MRT) { RTprev.tv_sec = state->MRT; RTprev.tv_nsec = 0; state->RT.tv_sec = state->MRT; state->RT.tv_nsec = 0; ts_to_ms(ms, &RTprev); ms = (time_t)((double)ms * rnd); ms_to_ts(&RTprev, ms); if (neg) timespecsub(&state->RT, &RTprev, &state->RT); else timespecadd(&state->RT, &RTprev, &state->RT); } logsend: if (ifp->carrier != LINK_DOWN) logdebugx("%s: %s %s (xid 0x%02x%02x%02x)," " next in %0.1f seconds", ifp->name, broad_uni, dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], state->send->xid[2], timespec_to_double(&state->RT)); /* This sometimes happens when we delegate to this interface * AND run DHCPv6 on it normally. */ assert(timespec_to_double(&state->RT) != 0); /* Wait the initial delay */ if (state->IMD != 0) { state->IMD = 0; eloop_timeout_add_tv(ifp->ctx->eloop, &state->RT, callback, ifp); return 0; } } if (ifp->carrier == LINK_DOWN) return 0; /* Update the elapsed time */ dhcp6_updateelapsed(ifp, state->send, state->send_len); #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 ctx = ifp->ctx; ctx->sndhdr.msg_name = (void *)&dst; ctx->sndhdr.msg_iov[0].iov_base = state->send; ctx->sndhdr.msg_iov[0].iov_len = state->send_len; /* Set the outbound interface */ if (IN6_ARE_ADDR_EQUAL(&dst.sin6_addr, &alldhcp)) { struct cmsghdr *cm; struct in6_pktinfo pi; dst.sin6_scope_id = ifp->index; cm = CMSG_FIRSTHDR(&ctx->sndhdr); if (cm == NULL) /* unlikely */ return -1; cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(pi)); memset(&pi, 0, sizeof(pi)); pi.ipi6_ifindex = ifp->index; memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); } else { /* Remove the control buffer as we're not dictating * which interface to use for outgoing messages. */ ctx->sndhdr.msg_control = NULL; ctx->sndhdr.msg_controllen = 0; } if (ctx->dhcp6_fd != -1) s = ctx->dhcp6_fd; else if (lla != NULL && lla->dhcp6_fd != -1) s = lla->dhcp6_fd; else { logerrx("%s: no socket to send from", ifp->name); return -1; } if (sendmsg(s, &ctx->sndhdr, 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. */ } /* Restore the control buffer assignment. */ if (!IN6_ARE_ADDR_EQUAL(&dst.sin6_addr, &alldhcp)) { ctx->sndhdr.msg_control = ctx->sndbuf; ctx->sndhdr.msg_controllen = sizeof(ctx->sndbuf); } state->RTC++; if (callback) { if (state->MRC == 0 || state->RTC < state->MRC) eloop_timeout_add_tv(ifp->ctx->eloop, &state->RT, callback, ifp); else if (state->MRC != 0 && state->MRCcallback) eloop_timeout_add_tv(ifp->ctx->eloop, &state->RT, state->MRCcallback, ifp); else logwarnx("%s: sent %d times with no reply", ifp->name, state->RTC); } return 0; } static void dhcp6_sendinform(void *arg) { dhcp6_sendmessage(arg, dhcp6_sendinform); } static void dhcp6_senddiscover(void *arg) { dhcp6_sendmessage(arg, dhcp6_senddiscover); } static void dhcp6_sendrequest(void *arg) { dhcp6_sendmessage(arg, dhcp6_sendrequest); } static void dhcp6_sendrebind(void *arg) { dhcp6_sendmessage(arg, dhcp6_sendrebind); } static void dhcp6_sendrenew(void *arg) { dhcp6_sendmessage(arg, dhcp6_sendrenew); } static void dhcp6_sendconfirm(void *arg) { dhcp6_sendmessage(arg, dhcp6_sendconfirm); } static void dhcp6_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); } int dhcp6_dadcompleted(const struct interface *ifp) { const struct dhcp6_state *state; const struct ipv6_addr *ap; state = D6_CSTATE(ifp); TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->flags & IPV6_AF_ADDED && !(ap->flags & IPV6_AF_DADCOMPLETED)) return 0; } return 1; } static void dhcp6_dadcallback(void *arg) { struct ipv6_addr *ia = arg; struct interface *ifp; struct dhcp6_state *state; int wascompleted, valid; wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); ia->flags |= IPV6_AF_DADCOMPLETED; if (ia->flags & IPV6_AF_DUPLICATED) { /* XXX FIXME * We should decline the address */ logwarnx("%s: DAD detected %s", ia->iface->name, ia->saddr); } if (!wascompleted) { ifp = ia->iface; state = D6_STATE(ifp); if (state->state == DH6S_BOUND || state->state == DH6S_DELEGATED) { struct ipv6_addr *ia2; #ifdef SMALL valid = true; #else valid = (ia->delegating_prefix == NULL); #endif TAILQ_FOREACH(ia2, &state->addrs, next) { if (ia2->flags & IPV6_AF_ADDED && !(ia2->flags & IPV6_AF_DADCOMPLETED)) { wascompleted = 1; break; } } if (!wascompleted) { logdebugx("%s: DHCPv6 DAD completed", ifp->name); script_runreason(ifp, #ifndef SMALL ia->delegating_prefix ? "DELEGATED6" : #endif state->reason); if (valid) dhcpcd_daemonise(ifp->ctx); } ipv6nd_advertise(ia); } } } 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; ifp = arg; #ifndef SMALL dhcp6_delete_delegates(ifp); #endif loginfox("%s: soliciting a DHCPv6 lease", ifp->name); state = D6_STATE(ifp); state->state = DH6S_DISCOVER; state->RTC = 0; state->IMD = SOL_MAX_DELAY; state->IRT = SOL_TIMEOUT; state->MRT = state->sol_max_rt; state->MRC = SOL_MAX_RC; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); free(state->new); state->new = NULL; state->new_len = 0; if (dhcp6_makemessage(ifp) == -1) logerr("%s: %s", __func__, ifp->name); else dhcp6_senddiscover(ifp); } static void dhcp6_startinform(void *arg) { struct interface *ifp; struct dhcp6_state *state; ifp = arg; state = D6_STATE(ifp); if (state->new == NULL || ifp->options->options & DHCPCD_DEBUG) loginfox("%s: requesting DHCPv6 information", ifp->name); state->state = DH6S_INFORM; state->RTC = 0; state->IMD = INF_MAX_DELAY; state->IRT = INF_TIMEOUT; state->MRT = state->inf_max_rt; state->MRC = 0; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (dhcp6_makemessage(ifp) == -1) { logerr("%s: %s", __func__, ifp->name); return; } dhcp6_sendinform(ifp); /* RFC3315 18.1.2 says that if CONFIRM failed then the prior addresses * SHOULD be used. The wording here is poor, because the addresses are * merely one facet of the lease as a whole. * This poor wording might explain the lack of similar text for INFORM * in 18.1.5 because there are no addresses in the INFORM message. */ eloop_timeout_add_sec(ifp->ctx->eloop, INF_MAX_RD, dhcp6_failinform, ifp); } static void dhcp6_fail(struct interface *ifp) { struct dhcp6_state *state = D6_STATE(ifp); /* RFC3315 18.1.2 says that prior addresses SHOULD be used on failure. * RFC2131 3.2.3 says that MAY chose to use the prior address. * Because dhcpcd was written first for RFC2131, we have the LASTLEASE * option which defaults to off as that makes the most sense for * mobile clients. * dhcpcd also has LASTLEASE_EXTEND to extend this lease past it's * expiry, but this is strictly not RFC compliant in any way or form. */ if (state->new == NULL || !(ifp->options->options & DHCPCD_LASTLEASE)) { #ifndef SMALL dhcp6_delete_delegates(ifp); #endif if (state->state != DH6S_INFORM) dhcp6_startdiscover(ifp); return; } switch (state->state) { case DH6S_INFORM: case DH6S_INFORMED: state->state = DH6S_ITIMEDOUT; break; default: state->state = DH6S_TIMEDOUT; break; } dhcp6_bind(ifp, NULL); switch (state->state) { case DH6S_BOUND: case DH6S_INFORMED: break; default: dhcp6_startdiscover(ifp); break; } } static void dhcp6_failconfirm(void *arg) { struct interface *ifp; ifp = arg; logerrx("%s: failed to confirm prior address", ifp->name); dhcp6_fail(ifp); } static void dhcp6_failrequest(void *arg) { struct interface *ifp; ifp = arg; logerrx("%s: failed to request address", ifp->name); dhcp6_fail(ifp); } static void dhcp6_failinform(void *arg) { struct interface *ifp; ifp = arg; logerrx("%s: failed to request information", ifp->name); dhcp6_fail(ifp); } #ifdef SMALL #define dhcp6_hasprefixdelegation(a) (0) #else static void dhcp6_failrebind(void *arg) { struct interface *ifp; ifp = arg; logerrx("%s: failed to rebind prior delegation", ifp->name); dhcp6_fail(ifp); } static int dhcp6_hasprefixdelegation(struct interface *ifp) { size_t i; uint16_t t; t = 0; for (i = 0; i < ifp->options->ia_len; i++) { if (t && t != ifp->options->ia[i].ia_type) { if (t == D6_OPTION_IA_PD || ifp->options->ia[i].ia_type == D6_OPTION_IA_PD) return 2; } t = ifp->options->ia[i].ia_type; } return t == D6_OPTION_IA_PD ? 1 : 0; } #endif static void dhcp6_startrebind(void *arg) { struct interface *ifp; struct dhcp6_state *state; #ifndef SMALL int pd; #endif ifp = arg; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp); state = D6_STATE(ifp); if (state->state == DH6S_RENEW) logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name); else loginfox("%s: rebinding prior DHCPv6 lease", ifp->name); state->state = DH6S_REBIND; state->RTC = 0; state->MRC = 0; #ifndef SMALL /* RFC 3633 section 12.1 */ pd = dhcp6_hasprefixdelegation(ifp); if (pd) { state->IMD = CNF_MAX_DELAY; state->IRT = CNF_TIMEOUT; state->MRT = CNF_MAX_RT; } else #endif { state->IMD = REB_MAX_DELAY; state->IRT = REB_TIMEOUT; state->MRT = REB_MAX_RT; } if (dhcp6_makemessage(ifp) == -1) logerr("%s: %s", __func__, ifp->name); else dhcp6_sendrebind(ifp); #ifndef SMALL /* RFC 3633 section 12.1 */ if (pd) eloop_timeout_add_sec(ifp->ctx->eloop, CNF_MAX_RD, dhcp6_failrebind, ifp); #endif } static void dhcp6_startrequest(struct interface *ifp) { struct dhcp6_state *state; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp); state = D6_STATE(ifp); state->state = DH6S_REQUEST; state->RTC = 0; state->IMD = 0; state->IRT = REQ_TIMEOUT; state->MRT = REQ_MAX_RT; state->MRC = REQ_MAX_RC; state->MRCcallback = dhcp6_failrequest; if (dhcp6_makemessage(ifp) == -1) { logerr("%s: %s", __func__, ifp->name); return; } dhcp6_sendrequest(ifp); } static void dhcp6_startconfirm(struct interface *ifp) { struct dhcp6_state *state; state = D6_STATE(ifp); state->state = DH6S_CONFIRM; state->RTC = 0; state->IMD = CNF_MAX_DELAY; state->IRT = CNF_TIMEOUT; state->MRT = CNF_MAX_RT; state->MRC = 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_leaseextend(struct interface *ifp) { struct dhcp6_state *state = D6_STATE(ifp); struct ipv6_addr *ia; logwarnx("%s: extending DHCPv6 lease", ifp->name); TAILQ_FOREACH(ia, &state->addrs, next) { ia->flags |= IPV6_AF_EXTENDED; /* Set infinite lifetimes. */ ia->prefix_pltime = ND6_INFINITE_LIFETIME; ia->prefix_vltime = ND6_INFINITE_LIFETIME; } } static void dhcp6_startexpire(void *arg) { struct interface *ifp; ifp = arg; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp); logerrx("%s: DHCPv6 lease expired", ifp->name); if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { struct dhcp6_state *state = D6_STATE(ifp); dhcp6_leaseextend(ifp); ipv6_addaddrs(&state->addrs); } else { dhcp6_freedrop_addrs(ifp, 1, NULL); #ifndef SMALL dhcp6_delete_delegates(ifp); #endif script_runreason(ifp, "EXPIRE6"); } if (!(ifp->options->options & DHCPCD_IPV6RS) || ipv6nd_hasradhcp(ifp) || dhcp6_hasprefixdelegation(ifp)) dhcp6_startdiscover(ifp); else logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name); } static void dhcp6_finishrelease(void *arg) { struct interface *ifp; struct dhcp6_state *state; ifp = (struct interface *)arg; 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) { 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; 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); 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) return 1; /* 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; } logerrx("%s: DHCPv6 REPLY: %s", ifp->name, status); free(sbuf); return -1; } 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 < 24) { errno = EINVAL; logerrx("%s: IA Address option truncated", ifp->name); continue; } memcpy(&ia, o, ol); ia.pltime = ntohl(ia.pltime); ia.vltime = ntohl(ia.vltime); /* RFC 3315 22.6 */ if (ia.pltime > ia.vltime) { errno = EINVAL; logerr("%s: IA Address pltime %"PRIu32 " > vltime %"PRIu32, ifp->name, ia.pltime, ia.vltime); continue; } TAILQ_FOREACH(a, &state->addrs, next) { if (ipv6_findaddrmatch(a, &ia.addr, 0)) break; } if (a == NULL) { /* * RFC 5942 Section 5 * We cannot assume any prefix length, nor tie the * address to an existing one as it could expire * before the address. * As such we just give it a 128 prefix. */ a = ipv6_newaddr(ifp, &ia.addr, 128, IPV6_AF_ONLINK); a->dadcallback = dhcp6_dadcallback; a->ia_type = ot; memcpy(a->iaid, iaid, sizeof(a->iaid)); a->created = *acquired; TAILQ_INSERT_TAIL(&state->addrs, a, next); } else { if (!(a->flags & IPV6_AF_ONLINK)) a->flags |= IPV6_AF_ONLINK | IPV6_AF_NEW; a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED); } a->acquired = *acquired; a->prefix_pltime = ia.pltime; if (a->prefix_vltime != ia.vltime) { a->flags |= IPV6_AF_NEW; a->prefix_vltime = ia.vltime; } if (a->prefix_pltime && a->prefix_pltime < state->lowpl) state->lowpl = a->prefix_pltime; if (a->prefix_vltime && a->prefix_vltime > state->expire) state->expire = a->prefix_vltime; i++; } return i; } #ifndef SMALL static int dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, uint8_t *d, size_t l, const struct timespec *acquired) { struct dhcp6_state *state; uint8_t *o, *nd; struct ipv6_addr *a; int i; uint8_t nb, *pw; uint16_t ol; struct dhcp6_pd_addr pdp; struct in6_addr pdp_prefix; i = 0; state = D6_STATE(ifp); while ((o = dhcp6_findoption(d, l, D6_OPTION_IAPREFIX, &ol))) { /* Set d and l first to ensure we find the next option. */ nd = o + ol; l -= (size_t)(nd - d); d = nd; if (ol < sizeof(pdp)) { errno = EINVAL; logerrx("%s: IA Prefix option truncated", ifp->name); continue; } memcpy(&pdp, o, sizeof(pdp)); pdp.pltime = ntohl(pdp.pltime); pdp.vltime = ntohl(pdp.vltime); /* RFC 3315 22.6 */ if (pdp.pltime > pdp.vltime) { errno = EINVAL; logerrx("%s: IA Prefix pltime %"PRIu32 " > vltime %"PRIu32, ifp->name, pdp.pltime, pdp.vltime); continue; } o += sizeof(pdp); ol = (uint16_t)(ol - sizeof(pdp)); /* pdp.prefix is not aligned so copy it out. */ memcpy(&pdp_prefix, &pdp.prefix, sizeof(pdp_prefix)); TAILQ_FOREACH(a, &state->addrs, next) { if (IN6_ARE_ADDR_EQUAL(&a->prefix, &pdp_prefix)) break; } if (a == NULL) { a = ipv6_newaddr(ifp, &pdp_prefix, pdp.prefix_len, IPV6_AF_DELEGATEDPFX); if (a == NULL) break; a->created = *acquired; a->dadcallback = dhcp6_dadcallback; a->ia_type = D6_OPTION_IA_PD; memcpy(a->iaid, iaid, sizeof(a->iaid)); TAILQ_INIT(&a->pd_pfxs); TAILQ_INSERT_TAIL(&state->addrs, a, next); } else { if (!(a->flags & IPV6_AF_DELEGATEDPFX)) { a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; TAILQ_INIT(&a->pd_pfxs); } a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED | IPV6_AF_REQUEST); if (a->prefix_vltime != pdp.vltime) a->flags |= IPV6_AF_NEW; } a->acquired = *acquired; a->prefix_pltime = pdp.pltime; a->prefix_vltime = pdp.vltime; if (a->prefix_pltime && a->prefix_pltime < state->lowpl) state->lowpl = a->prefix_pltime; if (a->prefix_vltime && a->prefix_vltime > state->expire) state->expire = a->prefix_vltime; i++; o = dhcp6_findoption(o, ol, D6_OPTION_PD_EXCLUDE, &ol); a->prefix_exclude_len = 0; memset(&a->prefix_exclude, 0, sizeof(a->prefix_exclude)); #if 0 if (ex == NULL) { struct dhcp6_option *w; uint8_t *wp; w = calloc(1, 128); w->len = htons(2); wp = D6_OPTION_DATA(w); *wp++ = 64; *wp++ = 0x78; ex = w; } #endif if (o == NULL) continue; if (ol < 2) { logerrx("%s: truncated PD Exclude", ifp->name); continue; } a->prefix_exclude_len = *o++; ol--; if (((a->prefix_exclude_len - a->prefix_len - 1) / NBBY) + 1 != ol) { logerrx("%s: PD Exclude length mismatch", ifp->name); a->prefix_exclude_len = 0; continue; } nb = a->prefix_len % NBBY; memcpy(&a->prefix_exclude, &a->prefix, sizeof(a->prefix_exclude)); 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; size_t j; uint16_t nl; uint8_t iaid[4]; char buf[sizeof(iaid) * 3]; struct ipv6_addr *ap; struct if_ia *ifia; if (l < sizeof(*m)) { /* Should be impossible with guards at packet in * and reading leases */ errno = EINVAL; return -1; } ifo = ifp->options; i = e = 0; state = D6_STATE(ifp); TAILQ_FOREACH(ap, &state->addrs, next) { if (!(ap->flags & IPV6_AF_DELEGATED)) ap->flags |= IPV6_AF_STALE; } d = (uint8_t *)m + sizeof(*m); l -= sizeof(*m); while (l > sizeof(o)) { memcpy(&o, d, sizeof(o)); o.len = ntohs(o.len); if (o.len > l || sizeof(o) + o.len > l) { errno = EINVAL; logerrx("%s: option overflow", ifp->name); break; } p = d + sizeof(o); d = p + o.len; l -= sizeof(o) + o.len; o.code = ntohs(o.code); switch(o.code) { case D6_OPTION_IA_TA: nl = 4; break; case D6_OPTION_IA_NA: case D6_OPTION_IA_PD: nl = 12; break; default: continue; } if (o.len < nl) { errno = EINVAL; logerrx("%s: IA option truncated", ifp->name); continue; } memcpy(&ia, p, nl); p += nl; o.len = (uint16_t)(o.len - nl); for (j = 0; j < ifo->ia_len; j++) { ifia = &ifo->ia[j]; if (ifia->ia_type == o.code && memcmp(ifia->iaid, ia.iaid, sizeof(ia.iaid)) == 0) break; } if (j == ifo->ia_len && !(ifo->ia_len == 0 && ifp->ctx->options & DHCPCD_DUMPLEASE)) { logdebugx("%s: ignoring unrequested IAID %s", ifp->name, hwaddr_ntoa(ia.iaid, sizeof(ia.iaid), buf, sizeof(buf))); continue; } if (o.code != D6_OPTION_IA_TA) { ia.t1 = ntohl(ia.t1); ia.t2 = ntohl(ia.t2); /* RFC 3315 22.4 */ if (ia.t2 > 0 && ia.t1 > ia.t2) { logwarnx("%s: IAID %s T1(%d) > T2(%d) from %s", ifp->name, hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf)), ia.t1, ia.t2, sfrom); continue; } } else ia.t1 = ia.t2 = 0; /* appease gcc */ if (dhcp6_checkstatusok(ifp, NULL, p, o.len) == -1) { 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; } 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); ia->prefix_pltime = 0; } else if (ia->prefix_vltime == 0) loginfox("%s: %s: no valid lifetime", ia->iface->name, ia->saddr); else continue; #ifndef SMALL /* If we delegated from this prefix, deprecate or remove * the delegations. */ if (ia->flags & IPV6_AF_DELEGATEDPFX) { struct ipv6_addr *da; bool touched = false; TAILQ_FOREACH(da, &ia->pd_pfxs, pd_next) { if (ia->prefix_vltime == 0) { if (da->prefix_vltime != 0) { da->prefix_vltime = 0; touched = true; } } else if (da->prefix_pltime != 0) { da->prefix_pltime = 0; touched = true; } } if (touched) ipv6_addaddrs(&ia->pd_pfxs); } #endif if (ia->flags & IPV6_AF_REQUEST) { ia->prefix_vltime = ia->prefix_pltime = 0; eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, 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 ok, nia; struct timespec aq; if (len <= sizeof(*m)) { logerrx("%s: DHCPv6 lease truncated", ifp->name); return -1; } state = D6_STATE(ifp); if ((ok = dhcp6_checkstatusok(ifp, m, NULL, len) == -1)) return -1; state->renew = state->rebind = state->expire = 0; state->lowpl = ND6_INFINITE_LIFETIME; if (!acquired) { clock_gettime(CLOCK_MONOTONIC, &aq); acquired = &aq; } nia = dhcp6_findia(ifp, m, len, sfrom, acquired); if (nia == 0) { if (state->state != DH6S_CONFIRM && ok != 1) { logerrx("%s: no useable IA found in lease", ifp->name); return -1; } /* We are confirming and have an OK, * so look for ia's in our old lease. * IA's must have existed here otherwise we would * have rejected it earlier. */ assert(state->new != NULL && state->new_len != 0); nia = dhcp6_findia(ifp, state->new, state->new_len, sfrom, acquired); } return nia; } static ssize_t dhcp6_writelease(const struct interface *ifp) { const struct dhcp6_state *state; int fd; ssize_t bytes; state = D6_CSTATE(ifp); logdebugx("%s: writing lease `%s'", ifp->name, state->leasefile); fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { logerr(__func__); return -1; } bytes = write(fd, state->new, state->new_len); close(fd); return bytes; } static int dhcp6_readlease(struct interface *ifp, int validate) { struct dhcp6_state *state; struct stat st; int fd; struct dhcp6_message *lease; time_t now; int retval; bool fd_opened; #ifdef AUTH uint8_t *o; uint16_t ol; #endif state = D6_STATE(ifp); if (state->leasefile[0] == '\0') { logdebugx("reading standard input"); fd = fileno(stdin); fd_opened = false; } else { logdebugx("%s: reading lease `%s'", ifp->name, state->leasefile); fd = open(state->leasefile, O_RDONLY); if (fd != -1 && fstat(fd, &st) == -1) { close(fd); fd = -1; } fd_opened = true; } if (fd == -1) return -1; retval = -1; lease = NULL; free(state->new); state->new_len = dhcp_read_lease_fd(fd, (void **)&lease); state->new = lease; if (fd_opened) close(fd); if (state->new_len == 0) goto ex; if (ifp->ctx->options & DHCPCD_DUMPLEASE || state->leasefile[0] == '\0') return 0; /* If not validating IA's and if they have expired, * skip to the auth check. */ if (!validate) { fd = 0; goto auth; } clock_gettime(CLOCK_MONOTONIC, &state->acquired); if ((now = time(NULL)) == -1) goto ex; state->acquired.tv_sec -= now - st.st_mtime; /* Check to see if the lease is still valid */ fd = dhcp6_validatelease(ifp, state->new, state->new_len, NULL, &state->acquired); if (fd == -1) goto ex; if (state->expire != ND6_INFINITE_LIFETIME && state->leasefile[0] != '\0') { if ((time_t)state->expire < now - st.st_mtime && !(ifp->options->options & DHCPCD_LASTLEASE_EXTEND)) { logdebugx("%s: discarding expired lease", ifp->name); retval = 0; goto ex; } } auth: retval = 0; #ifdef AUTH /* Authenticate the message */ o = dhcp6_findmoption(state->new, state->new_len, D6_OPTION_AUTH, &ol); if (o) { if (dhcp_auth_validate(&state->auth, &ifp->options->auth, (uint8_t *)state->new, state->new_len, 6, state->new->type, o, ol) == NULL) { logerr("%s: authentication failed", ifp->name); 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 return fd; ex: dhcp6_freedrop_addrs(ifp, 0, NULL); free(state->new); state->new = NULL; state->new_len = 0; if (!(ifp->ctx->options & DHCPCD_DUMPLEASE) && state->leasefile[0] != '\0') unlink(state->leasefile); return retval; } static void dhcp6_startinit(struct interface *ifp) { struct dhcp6_state *state; int r; uint8_t has_ta, has_non_ta; size_t i; state = D6_STATE(ifp); state->state = DH6S_INIT; state->expire = ND6_INFINITE_LIFETIME; state->lowpl = ND6_INFINITE_LIFETIME; dhcp6_addrequestedaddrs(ifp); has_ta = has_non_ta = 0; for (i = 0; i < ifp->options->ia_len; i++) { switch (ifp->options->ia[i].ia_type) { case D6_OPTION_IA_TA: has_ta = 1; break; default: has_non_ta = 1; } } if (!(ifp->ctx->options & DHCPCD_TEST) && !(has_ta && !has_non_ta) && ifp->options->reboot != 0) { r = dhcp6_readlease(ifp, 1); if (r == -1) { if (errno != ENOENT) logerr("%s: %s", __func__, state->leasefile); } else if (r != 0) { /* RFC 3633 section 12.1 */ #ifndef SMALL if (dhcp6_hasprefixdelegation(ifp)) dhcp6_startrebind(ifp); else #endif dhcp6_startconfirm(ifp); return; } } dhcp6_startdiscover(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); 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; k = 0; carrier_warned = false; TAILQ_FOREACH(ap, &state->addrs, next) { if (!(ap->flags & IPV6_AF_DELEGATEDPFX)) continue; if (!(ap->flags & IPV6_AF_DELEGATEDLOG)) { logfunc_t *logfunc; if (ap->flags & IPV6_AF_NEW) logfunc = loginfox; else logfunc = logdebugx; /* We only want to log this the once as we loop * through many interfaces first. */ ap->flags |= IPV6_AF_DELEGATEDLOG; logfunc("%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 (ifd->carrier != LINK_UP) { 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 (ifd->carrier != LINK_UP) { 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); /* * Can't add routes here because that will trigger * interface sorting which may break the current * enumeration. * This doesn't really matter thanks to DaD because * calling the script will be delayed and routes * will get re-built if needed first. * This only cause minor confusion when dhcpcd is * restarted and confirms a lease where prior delegation * has already been assigned, because it will log it * added routes after the script has run. * The routes should still be there and fine though. */ dhcp6_script_try_run(ifd, 1); } } /* Now all addresses have been added, rebuild the routing table. */ if_initrt(ifp->ctx, AF_INET6); 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; k = 0; TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) { ifo = ifd->options; state = D6_STATE(ifd); if (state == NULL || state->state != DH6S_BOUND) continue; TAILQ_FOREACH(ap, &state->addrs, next) { if (!(ap->flags & IPV6_AF_DELEGATEDPFX)) continue; for (i = 0; i < ifo->ia_len; i++) { ia = &ifo->ia[i]; if (ia->ia_type != D6_OPTION_IA_PD) continue; if (memcmp(ia->iaid, ap->iaid, sizeof(ia->iaid))) continue; for (j = 0; j < ia->sla_len; j++) { sla = &ia->sla[j]; if (strcmp(ifp->name, sla->ifname)) continue; if (ipv6_linklocal(ifp) == NULL) { logdebugx( "%s: delaying adding" " delegated addresses for" " LL address", ifp->name); ipv6_addlinklocalcallback(ifp, dhcp6_find_delegates1, ifp); return 1; } if (dhcp6_ifdelegateaddr(ifp, ap, sla, ia)) k++; } } } } if (k) { loginfox("%s: adding delegated prefixes", ifp->name); state = D6_STATE(ifp); state->state = DH6S_DELEGATED; ipv6_addaddrs(&state->addrs); if_initrt(ifp->ctx, AF_INET6); 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) { struct dhcp6_state *state = D6_STATE(ifp); bool has_new = false; struct ipv6_addr *ia; logfunc_t *lognewinfo; struct timespec now; TAILQ_FOREACH(ia, &state->addrs, next) { if (ia->flags & IPV6_AF_NEW) { has_new = true; break; } } lognewinfo = has_new ? loginfox : logdebugx; if (op != NULL) lognewinfo("%s: %s received from %s", ifp->name, op, ifp->ctx->sfrom); state->reason = NULL; if (state->state != DH6S_ITIMEDOUT) eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); switch(state->state) { case DH6S_INFORM: if (state->reason == NULL) state->reason = "INFORM6"; /* FALLTHROUGH */ case DH6S_ITIMEDOUT: { struct dhcp6_option *o; uint16_t ol; if (state->reason == NULL) state->reason = "ITIMEDOUT"; o = dhcp6_findmoption(state->new, state->new_len, D6_OPTION_INFO_REFRESH_TIME, &ol); if (o == NULL || ol != sizeof(uint32_t)) state->renew = IRT_DEFAULT; else { memcpy(&state->renew, o, ol); state->renew = ntohl(state->renew); if (state->renew < IRT_MINIMUM) state->renew = IRT_MINIMUM; } state->rebind = 0; state->expire = ND6_INFINITE_LIFETIME; state->lowpl = ND6_INFINITE_LIFETIME; } break; case DH6S_REQUEST: if (state->reason == NULL) state->reason = "BOUND6"; /* FALLTHROUGH */ case DH6S_RENEW: if (state->reason == NULL) state->reason = "RENEW6"; /* FALLTHROUGH */ case DH6S_REBIND: if (state->reason == NULL) state->reason = "REBIND6"; /* FALLTHROUGH */ case DH6S_CONFIRM: if (state->reason == NULL) state->reason = "REBOOT6"; /* FALLTHROUGH */ case DH6S_TIMEDOUT: if (state->reason == NULL) state->reason = "TIMEOUT6"; if (state->renew != 0) { bool all_expired = true; TAILQ_FOREACH(ia, &state->addrs, next) { if (ia->flags & IPV6_AF_STALE) continue; if (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 " to due 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; } clock_gettime(CLOCK_MONOTONIC, &now); if (state->state == DH6S_TIMEDOUT || state->state == DH6S_ITIMEDOUT) { struct timespec diff; uint32_t diffsec; /* Reduce timers */ timespecsub(&now, &state->acquired, &diff); diffsec = (uint32_t)diff.tv_sec; if (state->renew && state->renew != ND6_INFINITE_LIFETIME) { if (state->renew > diffsec) state->renew -= diffsec; else state->renew = 0; } if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) { if (state->rebind > diffsec) state->rebind -= diffsec; else state->rebind = 0; } if (state->expire && state->expire != ND6_INFINITE_LIFETIME) { if (state->expire > diffsec) state->expire -= diffsec; else { if (!(ifp->options->options & DHCPCD_LASTLEASE_EXTEND)) return; state->expire = ND6_INFINITE_LIFETIME; } } if (state->expire == ND6_INFINITE_LIFETIME && ifp->options->options & DHCPCD_LASTLEASE_EXTEND) dhcp6_leaseextend(ifp); /* Restart rebind or renew phases in a second. */ if (state->expire != ND6_INFINITE_LIFETIME) { if (state->rebind == 0 && state->rebind != ND6_INFINITE_LIFETIME) state->rebind = 1; else if (state->renew == 0 && state->renew != ND6_INFINITE_LIFETIME) state->renew = 1; } } else state->acquired = now; switch (state->state) { case DH6S_CONFIRM: case DH6S_TIMEDOUT: case DH6S_ITIMEDOUT: break; default: 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; break; } if (ifp->ctx->options & DHCPCD_TEST) script_runreason(ifp, "TEST"); else { bool timed_out; switch(state->state) { case DH6S_TIMEDOUT: case DH6S_ITIMEDOUT: timed_out = true; break; default: timed_out = false; break; } switch(state->state) { case DH6S_INFORM: case DH6S_ITIMEDOUT: state->state = DH6S_INFORMED; break; default: state->state = DH6S_BOUND; break; } if (state->renew && state->renew != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)state->renew, state->state == DH6S_INFORMED ? dhcp6_startinform : dhcp6_startrenew, ifp); if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)state->rebind, dhcp6_startrebind, ifp); if (state->expire != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)state->expire, dhcp6_startexpire, ifp); else if (timed_out) eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)state->expire, dhcp6_startdiscover, ifp); ipv6_addaddrs(&state->addrs); dhcp6_deprecateaddrs(&state->addrs); if (state->state == DH6S_INFORMED) lognewinfo("%s: refresh in %"PRIu32" seconds", ifp->name, state->renew); else if (state->renew || state->rebind) lognewinfo("%s: renew in %"PRIu32", " "rebind in %"PRIu32", " "expire in %"PRIu32" seconds", ifp->name, state->renew, state->rebind, state->expire); else if (state->expire == 0) lognewinfo("%s: will expire", ifp->name); else lognewinfo("%s: expire in %"PRIu32" seconds", ifp->name, state->expire); if_initrt(ifp->ctx, AF_INET6); rt_build(ifp->ctx, AF_INET6); if (!timed_out) dhcp6_writelease(ifp); #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_MASTER))) { eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); } } static void dhcp6_recvif(struct interface *ifp, 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, ctx->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, ctx->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, ctx->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, ctx->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) { logerr("%s: no authentication from %s", ifp->name, ctx->sfrom); return; } logwarnx("%s: no authentication from %s", ifp->name, ctx->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) == -1) return; break; case DH6S_CONFIRM: if (dhcp6_validatelease(ifp, r, len, ctx->sfrom, NULL) == -1) { dhcp6_startdiscover(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, ctx->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_startdiscover(ifp); return; } if (state->state == DH6S_DISCOVER) state->state = DH6S_REQUEST; break; default: valid_op = false; break; } break; case DHCP6_ADVERTISE: if (state->state != DH6S_DISCOVER) { valid_op = false; break; } /* RFC7083 */ o = dhcp6_findmoption(r, len, D6_OPTION_SOL_MAX_RT, &ol); if (o && ol == sizeof(uint32_t)) { uint32_t max_rt; memcpy(&max_rt, o, sizeof(max_rt)); max_rt = ntohl(max_rt); if (max_rt >= 60 && max_rt <= 86400) { logdebugx("%s: SOL_MAX_RT %llu -> %u", ifp->name, (unsigned long long)state->sol_max_rt, max_rt); state->sol_max_rt = (time_t)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 = (time_t)max_rt; } else logerrx("%s: invalid INF_MAX_RT %u", ifp->name, max_rt); } if (dhcp6_validatelease(ifp, r, len, ctx->sfrom, NULL) == -1) return; break; case DHCP6_RECONFIGURE: #ifdef AUTH if (auth == NULL) { #endif logerrx("%s: unauthenticated %s from %s", ifp->name, op, ctx->sfrom); if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) return; #ifdef AUTH } loginfox("%s: %s from %s", ifp->name, op, ctx->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; #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; switch (r->type) { case DHCP6_ADVERTISE: { struct ipv6_addr *ia; if (state->state == DH6S_REQUEST) /* rapid commit */ break; 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, ctx->sfrom); else loginfox("%s: ADV %s from %s", ifp->name, ia->saddr, ctx->sfrom); if (ifp->ctx->options & DHCPCD_TEST) break; dhcp6_startrequest(ifp); return; } } dhcp6_bind(ifp, op); } static void dhcp6_recv(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia) { int s; size_t len; ssize_t bytes; struct interface *ifp; struct dhcp6_message *r; const struct dhcp6_state *state; uint8_t *o; uint16_t ol; ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_fd; bytes = recvmsg_realloc(s, &ctx->rcvhdr, 0); if (bytes == -1) { logerr("%s: recvmsg_realloc", __func__); return; } len = (size_t)bytes; ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr, ctx->ntopbuf, sizeof(ctx->ntopbuf)); if (len < sizeof(struct dhcp6_message)) { logerrx("DHCPv6 packet too short from %s", ctx->sfrom); return; } if (ia != NULL) ifp = ia->iface; else { struct cmsghdr *cm; struct in6_pktinfo pi; pi.ipi6_ifindex = 0; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&ctx->rcvhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(&ctx->rcvhdr, cm)) { if (cm->cmsg_level != IPPROTO_IPV6) continue; switch(cm->cmsg_type) { case IPV6_PKTINFO: if (cm->cmsg_len == CMSG_LEN(sizeof(pi))) memcpy(&pi, CMSG_DATA(cm), sizeof(pi)); break; } } if (pi.ipi6_ifindex == 0) { logerrx("DHCPv6 reply did not contain index from %s", ctx->sfrom); return; } TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (ifp->index == (unsigned int)pi.ipi6_ifindex) break; } if (ifp == NULL) { logerrx("DHCPv6 reply for unexpected interface from %s", ctx->sfrom); return; } } r = (struct dhcp6_message *)ctx->rcvhdr.msg_iov[0].iov_base; o = dhcp6_findmoption(r, len, D6_OPTION_CLIENTID, &ol); if (o == NULL || ol != ctx->duid_len || memcmp(o, ctx->duid, ol) != 0) { logdebugx("%s: incorrect client ID from %s", ifp->name, ctx->sfrom); return; } if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) { logdebugx("%s: no DHCPv6 server ID from %s", ifp->name, ctx->sfrom); return; } if (r->type == DHCP6_RECONFIGURE) { logdebugx("%s: RECONFIGURE6 recv from %s," " sending to all interfaces", ifp->name, ctx->sfrom); TAILQ_FOREACH(ifp, ctx->ifaces, next) { state = D6_CSTATE(ifp); if (state != NULL && state->send != NULL) dhcp6_recvif(ifp, r, len); } return; } 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], ctx->sfrom); return; } logdebugx("%s: redirecting DHCP6 message to %s", ifp->name, ifp1->name); ifp = ifp1; } dhcp6_recvif(ifp, r, len); } static void dhcp6_recvaddr(void *arg) { struct ipv6_addr *ia = arg; dhcp6_recv(ia->iface->ctx, ia); } static void dhcp6_recvctx(void *arg) { struct dhcpcd_ctx *ctx = arg; dhcp6_recv(ctx, NULL); } static int dhcp6_listen(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia) { struct sockaddr_in6 sa; int n, s; #define SOCK_FLAGS SOCK_CLOEXEC | SOCK_NONBLOCK s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_FLAGS, IPPROTO_UDP); #undef SOCK_FLAGS if (s == -1) goto errexit; n = 1; if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n)) == -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->addr, sizeof(sa.sin6_addr)); sa.sin6_scope_id = ia->iface->index; } 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; if (ia != NULL) { ia->dhcp6_fd = s; eloop_event_add(ctx->eloop, s, dhcp6_recvaddr, ia); } return s; errexit: logerr(__func__); if (s != -1) close(s); return -1; } #ifndef SMALL static void dhcp6_activateinterfaces(struct interface *ifp) { struct interface *ifd; size_t i, j; struct if_ia *ia; struct if_sla *sla; for (i = 0; i < ifp->options->ia_len; i++) { ia = &ifp->options->ia[i]; if (ia->ia_type != D6_OPTION_IA_PD) continue; for (j = 0; j < ia->sla_len; j++) { sla = &ia->sla[j]; ifd = if_find(ifp->ctx->ifaces, sla->ifname); if (ifd == NULL) { logwarn("%s: cannot delegate to %s", ifp->name, sla->ifname); continue; } if (!ifd->active) { loginfox("%s: activating for delegation", sla->ifname); dhcpcd_activateinterface(ifd, DHCPCD_IPV6 | DHCPCD_DHCP6); } } } } #endif static void dhcp6_start1(void *arg) { struct interface *ifp = arg; struct dhcpcd_ctx *ctx = ifp->ctx; struct if_options *ifo = ifp->options; struct dhcp6_state *state; size_t i; const struct dhcp_compat *dhc; if (ctx->dhcp6_fd == -1 && ctx->options & DHCPCD_MASTER) { ctx->dhcp6_fd = dhcp6_listen(ctx, NULL); if (ctx->dhcp6_fd == -1) return; eloop_event_add(ctx->eloop, ctx->dhcp6_fd, dhcp6_recvctx, ctx); } state = D6_STATE(ifp); /* If no DHCPv6 options are configured, match configured DHCPv4 options to DHCPv6 equivalents. */ for (i = 0; i < sizeof(ifo->requestmask6); i++) { if (ifo->requestmask6[i] != '\0') break; } if (i == sizeof(ifo->requestmask6)) { for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) { if (has_option_mask(ifo->requestmask, dhc->dhcp_opt)) add_option_mask(ifo->requestmask6, dhc->dhcp6_opt); } if (ifo->fqdn != FQDN_DISABLE || ifo->options & DHCPCD_HOSTNAME) add_option_mask(ifo->requestmask6, D6_OPTION_FQDN); } #ifndef SMALL /* Rapid commit won't work with Prefix Delegation Exclusion */ if (dhcp6_findselfsla(ifp)) del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT); #endif if (state->state == DH6S_INFORM) { add_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME); dhcp6_startinform(ifp); } else { del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME); dhcp6_startinit(ifp); } #ifndef SMALL dhcp6_activateinterfaces(ifp); #endif } int dhcp6_start(struct interface *ifp, enum DH6S init_state) { struct dhcp6_state *state; state = D6_STATE(ifp); if (state != NULL) { switch (init_state) { case DH6S_INIT: goto gogogo; case DH6S_INFORM: if (state->state == DH6S_INFORMED) dhcp6_startinform(ifp); break; case DH6S_REQUEST: if (ifp->options->options & DHCPCD_DHCP6 && (state->state == DH6S_INFORM || state->state == DH6S_INFORMED || state->state == DH6S_DELEGATED)) { /* Change from stateless to stateful */ init_state = DH6S_INIT; goto gogogo; } break; case DH6S_CONFIRM: init_state = DH6S_INIT; goto gogogo; default: /* Not possible, but sushes some compiler warnings. */ break; } return 0; } else { switch (init_state) { case DH6S_CONFIRM: /* No DHCPv6 config, no existing state * so nothing to do. */ return 0; 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->state = init_state; dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), AF_INET6, ifp); if (ipv6_linklocal(ifp) == NULL) { logdebugx("%s: delaying DHCPv6 soliciation for LL address", ifp->name); ipv6_addlinklocalcallback(ifp, dhcp6_start1, ifp); return 0; } dhcp6_start1(ifp); return 0; } void dhcp6_reboot(struct interface *ifp) { struct dhcp6_state *state; state = D6_STATE(ifp); if (state) { switch (state->state) { case DH6S_BOUND: dhcp6_startrebind(ifp); break; case DH6S_INFORMED: dhcp6_startinform(ifp); break; default: dhcp6_startdiscover(ifp); break; } } } static void dhcp6_freedrop(struct interface *ifp, int drop, const char *reason) { struct dhcp6_state *state; struct dhcpcd_ctx *ctx; unsigned long long options; 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 (ifp->carrier == LINK_UP && state->state != DH6S_RELEASED && state->state != DH6S_INFORMED) { dhcp6_startrelease(ifp); return; } unlink(state->leasefile); } dhcp6_freedrop_addrs(ifp, drop, NULL); free(state->old); state->old = state->new; state->old_len = state->new_len; state->new = NULL; state->new_len = 0; if (drop && state->old && (options & DHCPCD_NODROP) != DHCPCD_NODROP) { if (reason == NULL) reason = "STOP6"; script_runreason(ifp, reason); } free(state->old); free(state->send); free(state->recv); free(state); ifp->if_data[IF_DATA_DHCP6] = NULL; } /* If we don't have any more DHCP6 enabled interfaces, * close the global socket and release resources */ ctx = ifp->ctx; if (ctx->ifaces) { TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (D6_STATE(ifp)) break; } } if (ifp == NULL && ctx->dhcp6_fd != -1) { eloop_event_delete(ctx->eloop, ctx->dhcp6_fd); close(ctx->dhcp6_fd); ctx->dhcp6_fd = -1; } } void dhcp6_drop(struct interface *ifp, const char *reason) { dhcp6_freedrop(ifp, 1, reason); } void dhcp6_free(struct interface *ifp) { dhcp6_freedrop(ifp, 0, NULL); } void dhcp6_dropnondelegates(struct interface *ifp) { #ifndef SMALL if (dhcp6_hasprefixdelegation(ifp)) return; #endif if (D6_CSTATE(ifp) == NULL) return; loginfox("%s: dropping DHCPv6 due to no valid routers", ifp->name); dhcp6_drop(ifp, "EXPIRE6"); } void dhcp6_abort(struct interface *ifp) { struct dhcp6_state *state; struct ipv6_addr *ia; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp); state = D6_STATE(ifp); if (state == NULL) return; TAILQ_FOREACH(ia, &state->addrs, next) { ipv6nd_advertise(ia); } } 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 master mode, listen to this address */ if (cmd == RTM_NEWADDR && !(ia->addr_flags & IN6_IFF_NOTUSEABLE) && ifp->active == IF_ACTIVE_USER && !(ifp->ctx->options & DHCPCD_MASTER) && ifp->options->options & DHCPCD_DHCP6 && ia->dhcp6_fd == -1) dhcp6_listen(ia->iface->ctx, ia); if ((state = D6_STATE(ifp)) != NULL) ipv6_handleifa_addrs(cmd, &state->addrs, ia, pid); } ssize_t dhcp6_env(char **env, const char *prefix, const struct interface *ifp, const struct dhcp6_message *m, size_t len) { const struct if_options *ifo; struct dhcp_opt *opt, *vo; const uint8_t *p; struct dhcp6_option o; size_t i, n; char *pfx; uint32_t en; const struct dhcpcd_ctx *ctx; #ifndef SMALL const struct dhcp6_state *state; const struct ipv6_addr *ap; char *v, *val; #endif n = 0; if (m == NULL) goto delegated; if (len < sizeof(*m)) { /* Should be impossible with guards at packet in * and reading leases */ errno = EINVAL; return -1; } ifo = ifp->options; ctx = ifp->ctx; /* Zero our indexes */ if (env) { for (i = 0, opt = ctx->dhcp6_opts; i < ctx->dhcp6_opts_len; i++, opt++) dhcp_zero_index(opt); for (i = 0, opt = ifp->options->dhcp6_override; i < ifp->options->dhcp6_override_len; i++, opt++) dhcp_zero_index(opt); for (i = 0, opt = ctx->vivso; i < ctx->vivso_len; i++, opt++) dhcp_zero_index(opt); i = strlen(prefix) + strlen("_dhcp6") + 1; pfx = malloc(i); if (pfx == NULL) { logerr(__func__); return -1; } snprintf(pfx, i, "%s_dhcp6", prefix); } else pfx = NULL; /* Unlike DHCP, DHCPv6 options *may* occur more than once. * There is also no provision for option concatenation unlike DHCP. */ 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) { n += dhcp_envoption(ifp->ctx, env == NULL ? NULL : &env[n], pfx, ifp->name, opt, dhcp6_getoption, p, o.len); } if (vo) { n += dhcp_envoption(ifp->ctx, env == NULL ? NULL : &env[n], 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); i = 0; TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->delegating_prefix) { i += strlen(ap->saddr) + 1; } } if (env && i) { i += strlen(prefix) + strlen("_delegated_dhcp6_prefix="); v = val = env[n] = malloc(i); if (v == NULL) { logerr(__func__); return -1; } v += snprintf(val, i, "%s_delegated_dhcp6_prefix=", prefix); TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->delegating_prefix) { /* Can't use stpcpy(3) due to "security" */ const char *sap = ap->saddr; do *v++ = *sap; while (*++sap != '\0'); *v++ = ' '; } } *--v = '\0'; } if (i) n++; #endif return (ssize_t)n; } int dhcp6_dump(struct interface *ifp) { struct dhcp6_state *state; ifp->if_data[IF_DATA_DHCP6] = state = calloc(1, sizeof(*state)); if (state == NULL) { logerr(__func__); return -1; } TAILQ_INIT(&state->addrs); dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), AF_INET6, ifp); if (dhcp6_readlease(ifp, 0) == -1) { logerr("%s: %s", __func__, *ifp->name ? ifp->name : state->leasefile); return -1; } state->reason = "DUMP6"; return script_runreason(ifp, state->reason); } #endif dhcpcd5-7.1.0/src/dhcp6.h000066400000000000000000000164671342162717100150170ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 #define D6_OPTION_CLIENTID 1 #define D6_OPTION_SERVERID 2 #define D6_OPTION_IA_NA 3 #define D6_OPTION_IA_TA 4 #define D6_OPTION_ORO 6 #define D6_OPTION_IA_ADDR 5 #define D6_OPTION_PREFERENCE 7 #define D6_OPTION_ELAPSED 8 #define D6_OPTION_AUTH 11 #define D6_OPTION_UNICAST 12 #define D6_OPTION_STATUS_CODE 13 #define D6_OPTION_RAPID_COMMIT 14 #define D6_OPTION_USER_CLASS 15 #define D6_OPTION_VENDOR_CLASS 16 #define D6_OPTION_VENDOR_OPTS 17 #define D6_OPTION_INTERFACE_ID 18 #define D6_OPTION_RECONF_MSG 19 #define D6_OPTION_RECONF_ACCEPT 20 #define D6_OPTION_SIP_SERVERS_NAME 21 #define D6_OPTION_SIP_SERVERS_ADDRESS 22 #define D6_OPTION_DNS_SERVERS 23 #define D6_OPTION_DOMAIN_LIST 24 #define D6_OPTION_IA_PD 25 #define D6_OPTION_IAPREFIX 26 #define D6_OPTION_NIS_SERVERS 27 #define D6_OPTION_NISP_SERVERS 28 #define D6_OPTION_NIS_DOMAIN_NAME 29 #define D6_OPTION_NISP_DOMAIN_NAME 30 #define D6_OPTION_SNTP_SERVERS 31 #define D6_OPTION_INFO_REFRESH_TIME 32 #define D6_OPTION_BCMS_SERVER_D 33 #define D6_OPTION_BCMS_SERVER_A 34 #define D6_OPTION_FQDN 39 #define D6_OPTION_POSIX_TIMEZONE 41 #define D6_OPTION_TZDB_TIMEZONE 42 #define D6_OPTION_PD_EXCLUDE 67 #define D6_OPTION_SOL_MAX_RT 82 #define D6_OPTION_INF_MAX_RT 83 #define D6_OPTION_MUDURL 112 #define D6_FQDN_PTR 0x00 #define D6_FQDN_BOTH 0x01 #define D6_FQDN_NONE 0x04 #include "dhcp.h" #include "ipv6.h" #define D6_STATUS_OK 0 #define D6_STATUS_FAIL 1 #define D6_STATUS_NOADDR 2 #define D6_STATUS_NOBINDING 3 #define D6_STATUS_NOTONLINK 4 #define D6_STATUS_USEMULTICAST 5 #define SOL_MAX_DELAY 1 #define SOL_TIMEOUT 1 #define SOL_MAX_RT 3600 /* RFC7083 */ #define SOL_MAX_RC 0 #define REQ_MAX_DELAY 0 #define REQ_TIMEOUT 1 #define REQ_MAX_RT 30 #define REQ_MAX_RC 10 #define CNF_MAX_DELAY 1 #define CNF_TIMEOUT 1 #define CNF_MAX_RT 4 #define CNF_MAX_RC 0 #define CNF_MAX_RD 10 #define REN_MAX_DELAY 0 #define REN_TIMEOUT 10 #define REN_MAX_RT 600 #define REB_MAX_DELAY 0 #define REB_TIMEOUT 10 #define REB_MAX_RT 600 #define INF_MAX_DELAY 1 #define INF_TIMEOUT 1 #define INF_MAX_RD CNF_MAX_RD /* NOT RFC defined */ #define INF_MAX_RT 3600 /* RFC7083 */ #define REL_MAX_DELAY 0 #define REL_TIMEOUT 1 #define REL_MAX_RT 0 #define REL_MAX_RC 5 #define DEC_MAX_DELAY 0 #define DEC_TIMEOUT 1 #define DEC_MAX_RC 5 #define REC_MAX_DELAY 0 #define REC_TIMEOUT 2 #define REC_MAX_RC 8 #define HOP_COUNT_LIMIT 32 /* RFC4242 3.1 */ #define IRT_DEFAULT 86400 #define IRT_MINIMUM 600 #define DHCP6_RAND_MIN -100 #define DHCP6_RAND_MAX 100 enum DH6S { DH6S_INIT, DH6S_DISCOVER, DH6S_REQUEST, DH6S_BOUND, DH6S_RENEW, DH6S_REBIND, DH6S_CONFIRM, DH6S_INFORM, DH6S_INFORMED, DH6S_RENEW_REQUESTED, DH6S_PROBE, DH6S_DELEGATED, DH6S_TIMEDOUT, DH6S_ITIMEDOUT, DH6S_RELEASE, DH6S_RELEASED }; struct dhcp6_state { enum DH6S state; struct timespec started; /* Message retransmission timings */ struct timespec RT; unsigned int IMD; unsigned int RTC; time_t IRT; unsigned int MRC; time_t MRT; void (*MRCcallback)(void *); time_t sol_max_rt; time_t inf_max_rt; struct dhcp6_message *send; size_t send_len; struct dhcp6_message *recv; size_t recv_len; struct dhcp6_message *new; size_t new_len; struct dhcp6_message *old; size_t old_len; 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; struct authstate auth; }; #define D6_STATE(ifp) \ ((struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) #define D6_CSTATE(ifp) \ ((const struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) #define D6_STATE_RUNNING(ifp) \ (D6_CSTATE((ifp)) && \ D6_CSTATE((ifp))->reason && dhcp6_dadcompleted((ifp))) #ifdef DHCP6 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(char **, 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); int dhcp6_dadcompleted(const struct interface *); void dhcp6_abort(struct interface *); void dhcp6_drop(struct interface *, const char *); void dhcp6_dropnondelegates(struct interface *ifp); int dhcp6_dump(struct interface *); #else #define dhcp6_printoptions(a, b, c) {} #define dhcp6_iffindaddr(a, b, c) (NULL) #define dhcp6_findaddr(a, b, c) (NULL) #define dhcp6_find_delegates(a) {} #define dhcp6_start(a, b) (0) #define dhcp6_reboot(a) {} #define dhcp6_renew(a) {} #define dhcp6_env(a, b, c, d, e) (0) #define dhcp6_free(a) {} #define dhcp6_dadcompleted(a) (0) #define dhcp6_drop(a, b) {} #define dhcp6_dropnondelegates(a) {} #define dhcp6_dump(a) (-1) #endif #endif dhcpcd5-7.1.0/src/dhcpcd-definitions-small.conf000066400000000000000000000072611342162717100213450ustar00rootroot00000000000000# Copyright (c) 2006-2017 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 # Option 249 is an IANA assigned private number used by Windows DHCP servers # to provide the exact same information as option 121, classless static routes define 249 rfc3442 ms_classless_static_routes define 33 request array ipaddress static_routes define 3 request array ipaddress routers define 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 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 ############################################################################## # ND6 options, RFC4861 definend 1 binhex source_address definend 2 binhex target_address definend 3 index embed prefix_information embed byte length embed bitflags=LA flags embed uint32 vltime embed uint32 pltime embed uint32 reserved embed array ip6address prefix # option 4 is only for Redirect messages definend 5 embed mtu embed uint16 reserved embed uint32 mtu # ND6 options, RFC6101 definend 25 index embed rdnss embed uint16 reserved embed uint32 lifetime embed array ip6address servers definend 31 index embed dnssl embed uint16 reserved embed uint32 lifetime embed domain search ############################################################################## # DHCPv6 options, RFC3315 define6 1 binhex client_id define6 2 binhex server_id define6 3 norequest index embed ia_na embed binhex:4 iaid embed uint32 t1 embed uint32 t2 encap 5 option encap 13 option define6 4 norequest index embed ia_ta embed uint32 iaid encap 5 option encap 13 option define6 5 norequest index embed ia_addr embed ip6address ia_addr embed uint32 pltime embed uint32 vltime encap 13 option define6 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 dhcpcd5-7.1.0/src/dhcpcd-definitions.conf000066400000000000000000000427001342162717100202340ustar00rootroot00000000000000# Copyright (c) 2006-2017 Roy Marples # All rights reserved # DHCP option definitions for dhcpcd(8) # These are used to translate DHCP options into shell variables # for use in dhcpcd-run-hooks(8) # See dhcpcd.conf(5) for details ############################################################################## # DHCP RFC2132 options unless otheriwse stated define 1 request ipaddress subnet_mask # RFC3442 states that the CSR has to come before all other routes # For completeness we also specify static routes then routers define 121 rfc3442 classless_static_routes # Option 249 is an IANA assigned private number used by Windows DHCP servers # to provide the exact same information as option 121, classless static routes define 249 rfc3442 ms_classless_static_routes define 33 request array ipaddress static_routes define 3 request array ipaddress routers define 2 uint32 time_offset define 4 array ipaddress time_servers define 5 array ipaddress ien116_name_servers define 6 array ipaddress domain_name_servers define 7 array ipaddress log_servers define 8 array ipaddress cookie_servers define 9 array ipaddress lpr_servers define 10 array ipaddress impress_servers define 11 array ipaddress resource_location_servers define 12 dname host_name define 13 uint16 boot_size define 14 string merit_dump # Technically domain_name is not an array, but many servers expect clients # to treat it as one. define 15 array dname domain_name define 16 ipaddress swap_server define 17 string root_path define 18 string extensions_path define 19 byte ip_forwarding define 20 byte non_local_source_routing define 21 array ipaddress policy_filter define 22 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 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 Auto-Configuration, RFC2563 define 116 byte auto_configure # DHCP Name Service Search, RFC2937 define 117 array uint16 name_service_search # DHCP Subnet Selection, RFC3011 define 118 ipaddress subnet_selection # DHCP Domain Search, RFC3397 define 119 array domain domain_search # DHCP Session Initiated Protocol Servers, RFC3361 define 120 rfc3361 sip_server # Option 121 is defined at the top of this file # DHCP CableLabs Client, RFC3495 define 122 encap tsp encap 1 ipaddress dhcp_server encap 2 ipaddress dhcp_secondary_server encap 3 rfc3361 provisioning_server encap 4 embed as_req_as_rep_backoff embed uint32 nominal embed uint32 maximum embed uint32 retry encap 5 embed ap_req_ap_rep_backoff embed uint32 nominal embed uint32 maximum embed uint32 retry encap 6 domain kerberos_realm encap 7 byte ticket_granting_server_utilization encap 8 byte provisioning_timer # DHCP Coordinate LCI, RFC6225 # We have no means of expressing 6 bit lengths define 123 binhex geoconf # DHCP Vendor-Identifying Vendor Options, RFC3925 define 124 binhex vivco define 125 embed vivso embed uint32 enterprise_number # Vendor options are shared between DHCP/DHCPv6 # Their code is matched to the enterprise number defined above # see the end of this file for an example # Options 126 and 127 are unused, RFC3679 # DHCP Options for Intel Preboot eXecution Environent (PXE), RFC4578 # Options 128-135 are used but of no use to dhcpcd # DHCP PANA Authentication Agent, RFC5192 define 136 array ipaddress pana_agent # DHCP Lost Server, RFC5223 define 137 domain lost_server # DHCP CAPWAP, RFC5417 define 138 array ipaddress capwap_ac # DHCP Mobility Services, RFC5678 define 139 encap mos_ip encap 1 array ipaddress is encap 2 array ipaddress cs encap 3 array ipaddress es define 140 encap mos_domain encap 1 domain is encap 2 domain cs encap 3 domain es # DHCP SIP UA, RFC6011 define 141 array domain sip_ua_cs_list # DHCP ANDSF, RFC6153 define 142 array ipaddress andsf define 143 array ip6address andsf6 # DHCP Coordinate LCI, RFC6225 # We have no means of expressing 6 bit lengths define 144 binhex geoloc # DHCP FORCERENEW Nonce Capability, RFC6704 define 145 array byte forcerenew_nonce_capable # DHCP RDNSS Selection for MIF Nodes, RFC6731 define 146 embed rdnss_selection embed byte prf embed ipaddress primary embed ipaddress secondary embed array domain domains # Options 147, 148 and 149 are unused, RFC3942 # DHCP TFTP Server Address, RFC5859 define 150 array ipaddress tftp_servers # DHCP MUD URL, draft-ietf-opsawg-mud define 161 string mudurl # Apart from 161... # Options 151-157 are used for Lease Query, RFC6926 and not for dhcpcd # Options 158-174 are unused, RFC3942 # Options 175-177 are tentativel assigned for Etherboot # Options 178-207 are unused, RFC3942 # DHCP PXELINUX, RFC5071 define 208 binhex pxelinux_magic define 209 string config_file define 210 string path_prefix define 211 uint32 reboot_time # DHCP IPv6 Rapid Deployment on IPv4 Infrastructures, RFC5969 define 212 embed sixrd embed byte mask_len embed byte prefix_len embed ip6address prefix embed array ipaddress brip_address # DHCP Access Network Domain Name, RFC5986 define 213 domain access_domain # Options 214-219 are unused, RFC3942 # DHCP Subnet Allocation, RFC6656 # Option 220 looks specific to Cisco hardware. # DHCP Virtual Subnet Selection, RFC6607 define 221 encap vss encap 0 string nvt encap 1 binhex vpn_id encap 255 flag global # Options 222 and 223 are unused, RFC3942 # Options 224-254 are reserved for Private Use # However, an expired RFC for Web Proxy Auto Discovery Protocol does define # Option 252 which is commonly used by major browsers. # Apparently the code was assigned by agreement of the DHC working group chair. define 252 string wpad_url # Option 255 End ############################################################################## # ND6 options, RFC4861 definend 1 binhex source_address definend 2 binhex target_address definend 3 index embed prefix_information embed byte length embed bitflags=LA flags embed uint32 vltime embed uint32 pltime embed uint32 reserved embed array ip6address prefix # option 4 is only for Redirect messages definend 5 embed mtu embed uint16 reserved embed uint32 mtu # ND6 options, RFC6101 definend 25 index embed rdnss embed uint16 reserved embed uint32 lifetime embed array ip6address servers definend 31 index embed dnssl embed uint16 reserved embed uint32 lifetime embed domain search ############################################################################## # DHCPv6 options, RFC3315 define6 1 binhex client_id define6 2 binhex server_id define6 3 norequest index embed ia_na embed binhex:4 iaid embed uint32 t1 embed uint32 t2 encap 5 option encap 13 option define6 4 norequest index embed ia_ta embed uint32 iaid encap 5 option encap 13 option define6 5 norequest index embed ia_addr embed ip6address ia_addr embed uint32 pltime embed uint32 vltime encap 13 option define6 6 array uint16 option_request define6 7 byte preference define6 8 uint16 elased_time define6 9 binhex dhcp_relay_msg # Option 10 is unused define6 11 embed auth embed byte protocol embed byte algorithm embed byte rdm embed binhex:8 replay embed binhex information define6 12 ip6address unicast define6 13 norequest embed status_code embed uint16 status_code embed 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 ip6address fqdn # DHCPv6 LIS Discovery, RFC5986 define6 57 domain access_domain # DHCPv6 SIP UA, RFC6011 define6 58 array domain sip_ua_cs_list # DHCPv6 Network Boot, RFC5970 define6 59 string bootfile_url # We presently cannot decode bootfile_param define6 60 binhex bootfile_param define6 61 array uint16 architecture_types define6 62 embed nii embed byte type embed byte major embed byte minor # DHCPv6 Coordinate LCI, RFC6225 # We have no means of expressing 6 bit lengths define6 63 binhex geoloc # DHCPv6 AFTR-Name, RFC6334 define6 64 domain aftr_name # DHCPv6 Prefix Exclude Option, RFC6603 define6 67 embed pd_exclude embed byte prefix_len embed binhex subnetID # DHCPv6 Home Info Discovery in MIPv6, RFC6610 define6 69 encap mip6_idinf encap 71 option encap 72 option encap 73 option define6 70 encap mip6_udinf encap 71 option encap 72 option encap 73 option define6 71 embed mip6_hnp embed byte prefix_len embed ip6address prefix define6 72 ip6address mip6_haa define6 73 domain mip6_haf # DHCPv6 RDNSS Selection for MIF Nodes, RFC6731 define6 74 embed rdnss_selection embed ip6address server embed byte prf embed array domain domains # DHCPv6 Kerberos, RFC6784 define6 75 string krb_principal_name define6 76 string krb_realm_name define6 78 embed krb_kdc embed uint16 priority embed uint16 weight embed byte transport_type embed uint16 port embed ip6address address embed string realm_name # DHCPv6 Client Link-Layer Address, RFC6939 # Section 7 states that clients MUST ignore the option 79 # DHCPv6 Relay-Triggered Reconfiguraion, RFC6977 define6 80 ip6address link_address # DHCPv6 Radius, RFC7037 # Section 7 states that clients MUST ignore the option 81 # DHCPv6 SOL_MAX_RT, RFC7083 define6 82 request uint32 sol_max_rt define6 83 request uint32 inf_max_rt # DHCPv6 Softwire Address and Port-Mapped Clients, RFC7598 define6 89 embed s46_rule embed bitflags=0000000F flags embed byte ea_len embed byte prefix4_len embed ipaddress ipv4_prefix embed ip6address ipv6_prefix define6 90 ip6address s64_br define6 91 embed s46_dmr embed byte prefix_len embed binhex prefix define6 92 embed s46_v4v6bind embed ipaddress ipv4_address embed byte ipv6_prefix_len embed binhex ipv6_prefix_and_options # Cannot decode options after variable length address ... #encap 93 option define6 93 embed s46_portparams embed byte offset embed byte psid_len embed uint16 psid define6 94 embed s46_cont_mape encap 89 option encap 90 option define6 95 embed s46_cont_mapt encap 89 option encap 91 option define6 96 embed s46_cont_lw encap 90 option encap 92 option # DHCPv6 Address Selection Policy # Currently not supported # DHCPv6 MUD URL, draft-ietf-opsawg-mud define6 112 string mudurl # Options 86-65535 are unasssinged ############################################################################## # Vendor-Identifying Vendor Options # An example: #vendopt 12345 encap frobozzco #encap 1 string maze_location #encap 2 byte grue_probability dhcpcd5-7.1.0/src/dhcpcd-embedded.c.in000066400000000000000000000031331342162717100173510ustar00rootroot00000000000000/* * 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-2019 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 * const dhcpcd_embedded_conf[] = { dhcpcd5-7.1.0/src/dhcpcd-embedded.h.in000066400000000000000000000030341342162717100173560ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 INITDEFINES @INITDEFINES@ #define INITDEFINENDS @INITDEFINENDS@ #define INITDEFINE6S @INITDEFINE6S@ extern const char * const dhcpcd_embedded_conf[]; dhcpcd5-7.1.0/src/dhcpcd.8.in000066400000000000000000000605151342162717100155560ustar00rootroot00000000000000.\" Copyright (c) 2006-2019 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 January 2, 2019 .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 interface .Op ... .Nm .Fl n , Fl Fl rebind .Op interface .Nm .Fl k , Fl Fl release .Op interface .Nm .Fl U, Fl Fl dumplease .Ar interface .Nm .Fl Fl version .Nm .Fl x , Fl Fl exit .Op interface .Sh DESCRIPTION .Nm is an implementation of the DHCP client specified in .Li RFC 2131 . .Nm gets the host information .Po IP address, routes, etc .Pc from a DHCP server and configures the network .Ar interface of the machine on which it is running. .Nm then runs the configuration script which writes DNS information to .Xr resolvconf 8 , if available, otherwise directly to .Pa /etc/resolv.conf . If the hostname is currently blank, (null) or localhost, or .Va force_hostname is YES or TRUE or 1 then .Nm sets the hostname to the one supplied by the DHCP server. .Nm then daemonises and waits for the lease renewal time to lapse. It will then attempt to renew its lease and reconfigure if the new lease changes when the lease 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 and obtain a lease before forking to the background, otherwise it will fork right away. This behaviour can be modified with the .Fl b , Fl Fl background and .Fl w , Fl Fl waitip options. .Pp .Nm is also an implementation of the BOOTP client specified in .Li RFC 951 . .Pp .Nm is also an implementation of the IPv6 Router Solicitor as specified in .Li RFC 4861 and .Li RFC 6106 . .Pp .Nm is also an implementation of the IPv6 Privacy Extensions to AutoConf as specified in .Li RFC 4941 . This feature needs to be enabled in the kernel and .Nm will start using it. .Pp .Nm is also an implementation of the DHCPv6 client as specified in .Li RFC 3315 . By default, .Nm only starts DHCPv6 when instructed to do so by an IPV6 Router Advertisement. If no Identity Association is configured, then a Non-temporary Address is requested. .Ss Local Link configuration If .Nm failed to obtain a lease, it probes for a valid IPv4LL address .Po aka ZeroConf, aka APIPA .Pc . Once obtained it restarts the process of looking for a DHCP server to get a proper address. .Pp When using IPv4LL, .Nm nearly always succeeds and returns an exit code of 0. In the rare case it fails, it normally means that there is a reverse ARP proxy installed which always defeats IPv4LL probing. To disable this behaviour, you can use the .Fl L , Fl Fl noipv4ll option. .Ss Multiple interfaces If a list of interfaces are given on the command line, then .Nm only works with those interfaces, otherwise .Nm discovers available Ethernet interfaces that can be configured. When .Nm not limited to one interface on the command line, it is running in Master mode. The .Nm dhcpcd-ui project expects dhcpcd to be running this way. .Pp If a single interface is given then .Nm only works for that interface and runs as a separate instance to other .Nm processes. .Fl w , Fl Fl waitip option is enabled in this instance to maintain compatibility with older versions. Using a single interface also affects the .Fl k , .Fl N , .Fl n and .Fl x options, where the same interface will need to be specified, as a lack of an interface will imply Master mode which this is not. To force starting in Master mode with only one interface, the .Fl M , Fl Fl master 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. .Ss Hooking into events .Nm runs .Pa @SCRIPT@ , or the script specified by the .Fl c , Fl Fl script option. This script runs each script found in .Pa @HOOKDIR@ in a lexical order. The default installation supplies the scripts .Pa 01-test , .Pa 02-dump , .Pa 20-resolv.conf and .Pa 30-hostname . You can disable each script by using the .Fl C , Fl Fl nohook option. See .Xr dhcpcd-run-hooks 8 for details on how these scripts work. .Nm currently ignores the exit code of the script. .Pp More scripts are supplied in .Pa @DATADIR@/dhcpcd/hooks and need to be copied to .Pa @HOOKDIR@ if you intend to use them. For example, you could install .Pa 10-wpa_supplicant so that .Nm can ensure that .Xr wpa_supplicant 8 is always running on a hot-plugged wireless interface. .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 Use a DHCP Unique Identifier. If a system UUID is available, that will be used to create a DUID-UUID, otheriwse if persistent storage is available then a DUID-LLT (link local address + time) is generated, otherwise DUID-LL is generated (link local address). 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. .It Fl d , Fl Fl debug Echo debug messages to the stderr and syslog. .It Fl E , Fl Fl lastlease If .Nm cannot obtain a lease, then try to use the last lease acquired for the interface. .It Fl Fl lastleaseextend Same as the above, but the lease will be retained even if it expires. .Nm will give it up if any other host tries to claim it for their own via ARP. This violates RFC 2131, section 3.7, which states the lease should be dropped once it has expired. .It Fl e , Fl Fl env Ar value Push .Ar value to the environment for use in .Xr dhcpcd-run-hooks 8 . For example, you can force the hostname hook to always set the hostname with .Fl e .Va force_hostname=YES . .It Fl g , Fl Fl reconfigure .Nm will re-apply IP address, routing and run .Xr dhcpcd-run-hooks 8 for each interface. This is useful so that a 3rd party such as PPP or VPN can change the routing table and / or DNS, etc and then instruct .Nm to put things back afterwards. .Nm does not read a new configuration when this happens - you should rebind if you need that functionality. .It Fl F , Fl Fl fqdn Ar fqdn Requests that the DHCP server updates DNS using FQDN instead of just a hostname. Valid values for .Ar fqdn are disable, none, ptr and both. .Nm itself never does any DNS updates. .Nm encodes the FQDN hostname as specified in .Li RFC 1035 . .It Fl f , Fl Fl config Ar file Specify a config to load instead of .Pa @SYSCONFDIR@/dhcpcd.conf . .Nm always processes the config file before any command line options. .It Fl h , Fl Fl hostname Ar hostname Sends .Ar hostname to the DHCP server so it can be registered in DNS. If .Ar hostname is an empty string then the current system hostname is sent. If .Ar hostname is a FQDN (i.e., contains a .) then it will be encoded as such. .It Fl I , Fl Fl clientid Ar clientid Send the .Ar clientid . If the string is of the format 01:02:03 then it is encoded as hex. For interfaces whose hardware address is longer than 8 bytes, or if the .Ar clientid is an empty string then .Nm sends a default .Ar clientid of the hardware family and the hardware address. .It Fl i , Fl Fl vendorclassid Ar vendorclassid Override the DHCPv4 .Ar vendorclassid field sent. The default is dhcpcd-:::. For example .D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 If not set then none is sent. Some badly configured DHCP servers reject unknown vendorclassids. To work around it, try and impersonate Windows by using the MSFT vendorclassid. .It Fl j , Fl Fl logfile Ar logfile Writes to the specified .Ar logfile rather than .Xr syslog 3 . The .Ar logfile is s 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 Master mode. If no interfaces are left running, .Nm will exit. .It Fl l , Fl Fl leasetime Ar seconds Request a specific lease time in .Ar seconds . By default .Nm does not request any lease time and leaves it in the hands of the DHCP server. .It Fl M , Fl Fl master Start .Nm in Master mode even if only one interface specified on the command line. See the Multiple Interfaces section above. .It Fl m , Fl Fl metric Ar metric Metrics are used to prefer an interface over another one, lowest wins. .Nm will supply a default metic of 200 + .Xr if_nametoindex 3 . An extra 100 will be added for wireless interfaces. .It Fl n , Fl Fl rebind Op Ar interface Notifies .Nm to reload its configuration and rebind the specified .Ar interface . If no .Ar interface is specified then this applies to all interfaces in Master mode. If .Nm is not running, then it starts up as normal. This may also cause .Xr wpa_supplicant 8 to reload its configuration for each interface as well if the relevant hook script has been installed. .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 Master mode. If .Nm is not running, then it starts up as normal. Unlike the .Fl n , Fl Fl rebind option above, the configuration for .Nm is not reloaded. .It Fl o , Fl Fl option Ar option Request the DHCP .Ar option variable for use in .Pa @SCRIPT@ . .It Fl p , Fl Fl persistent .Nm normally de-configures the .Ar interface and configuration when it exits. Sometimes, this isn't desirable if, for example, you have root mounted over NFS or SSH clients connect to this host and they need to be notified of the host shutting down. You can use this option to stop this from happening. .It Fl r , Fl Fl request Ar address Request the .Ar address in the DHCP DISCOVER message. There is no guarantee this is the address the DHCP server will actually give. If no .Ar address is given then the first address currently assigned to the .Ar interface is used. .It Fl s , Fl Fl inform Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address Behaves like .Fl r , Fl Fl request as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST. This does not get a lease as such, just notifies the DHCP server of the .Ar address in use. You should also include the optional .Ar cidr network number in case the address is not already configured on the interface. .Nm remains running and pretends it has an infinite lease. .Nm will not de-configure the interface when it exits. If .Nm fails to contact a DHCP server then it returns a failure instead of falling back on IPv4LL. .It Fl Fl inform6 Performs a DHCPv6 Information Request. No address is requested or specified, but all other DHCPv6 options are allowed. This is normally performed automatically when the IPv6 Router Advertises that the client should perform this operation. This option is only needed when .Nm is not processing IPv6RA messages and the need for DHCPv6 Information Request exists. .It Fl S, Fl Fl static Ar value Configures a static DHCP .Ar value . If you set .Ic ip_address then .Nm will not attempt to obtain a lease and just use the value for the address with an infinite lease time. .Pp Here is an example which configures a static address, routes and DNS. .D1 dhcpcd -S ip_address=192.168.0.10/24 \e .D1 -S routers=192.168.0.1 \e .D1 -S domain_name_servers=192.168.0.1 \e .D1 eth0 .Pp You cannot presently set static DHCPv6 values. Use the .Fl e , Fl Fl env option instead. .It Fl t , Fl Fl timeout Ar seconds Timeout after .Ar seconds , instead of the default 30. A setting of 0 .Ar seconds causes .Nm to wait forever to get a lease. If .Nm is working on a single interface then .Nm will exit when a timeout occurs, otherwise .Nm will fork into the background. .It Fl u , Fl Fl userclass Ar class Tags the DHCPv4 message with the userclass .Ar class . DHCP servers use this to give members of the class DHCP options other than the default, without having to know things like hardware address or hostname. .It Fl v , Fl Fl vendor Ar code , Ns Ar value Add an encapsulated vendor option. .Ar code should be between 1 and 254 inclusive. To add a raw vendor string, omit .Ar code but keep the comma. Examples. .Pp Set the vendor option 01 with an IP address. .D1 dhcpcd \-v 01,192.168.0.2 eth0 Set the vendor option 02 with a hex code. .D1 dhcpcd \-v 02,01:02:03:04:05 eth0 Set the vendor option 03 with an IP address as a string. .D1 dhcpcd \-v 03,\e"192.168.0.2\e" eth0 Set un-encapsulated vendor option to hello world. .D1 dhcpcd \-v ,"hello world" eth0 .It Fl Fl version Display both program version and copyright information. .Nm then exits before doing any configuration. .It Fl w Wait for an address to be assigned before forking to the background. Does not take an argument, unlike the below option. .It Fl Fl waitip Ns = Ns Op 4 | 6 Wait for an address to be assigned before forking to the background. 4 means wait for an IPv4 address to be assigned. 6 means wait for an IPv6 address to be assigned. If no argument is given, .Nm will wait for any address protocol to be assigned. It is possible to wait for more than one address protocol and .Nm will only fork to the background when all waiting conditions are satisfied. .It Fl x , Fl Fl exit Op Ar interface This will signal an existing .Nm process running on the .Ar interface to exit. If no .Ar interface is specified, then the above is applied to all interfaces in Master 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 the DHCP server wants. Here are some options that deal with turning these bits off. .Pp Note that when .Nm is restricted to a single interface then the interface also needs to be specified when asking .Nm to exit using the commandline. If the protocol is restricted as well then the protocol needs to be included with the exit instruction. .Bl -tag -width indent .It Fl 1 , Fl Fl oneshot Exit after configuring an interface. Use the .Fl w , Fl Fl waitip option to specify which protocol(s) to configure before exiting. .It Fl 4 , Fl Fl ipv4only Configure IPv4 only. .It Fl 6 , Fl Fl ipv6only Configure IPv6 only. .It Fl A , Fl Fl noarp Don't request or claim the address by ARP. This also disables IPv4LL. .It Fl B , Fl Fl nobackground Don't run in the background when we acquire a lease. This is mainly useful for running under the control of another process, such as a debugger or a network manager. .It Fl C , Fl Fl nohook Ar script Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with .Pa .sh . .Pp So to stop .Nm from touching your DNS settings you would do:- .D1 dhcpcd -C resolv.conf eth0 .It Fl G , Fl Fl nogateway Don't set any default routes. .It Fl H , Fl Fl xidhwaddr Use the last four bytes of the hardware address as the DHCP xid instead of a randomly generated number. .It Fl J , Fl Fl broadcast Instructs the DHCP server to broadcast replies back to the client. Normally this is only set for non-Ethernet interfaces, such as FireWire and InfiniBand. In most instances, .Nm will set this automatically. .It Fl K , Fl Fl nolink Don't receive link messages for carrier status. You should only have to use this with buggy device drivers or running .Nm through a network manager. .It Fl L , Fl Fl noipv4ll Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). .It Fl O , Fl Fl nooption Ar option Removes the .Ar option from the DHCP message before processing. .It Fl P , Fl Fl printpidfile Print the .Pa pidfile .Nm will use based on commmand-line arguments to stdout. .It Fl Q , Fl Fl require Ar option Requires the .Ar option to be present in all DHCP messages, otherwise the message is ignored. To enforce that .Nm only responds to DHCP servers and not BOOTP servers, you can .Fl Q .Ar dhcp_message_type . .It Fl q , Fl Fl quiet Quiet .Nm on the command line, only warnings and errors will be displayed. The messages are still logged though. .It Fl T, Fl Fl test On receipt of DHCP messages just call .Pa @SCRIPT@ with the reason of TEST which echos the DHCP variables found in the message to the console. The interface configuration isn't touched and neither are any configuration files. The .Ar rapid_commit option is not sent in TEST mode so that the server does not lease an address. To test INFORM the interface needs to be configured with the desired address before starting .Nm . .It Fl U, Fl Fl dumplease Ar interface Dumps the last lease for the .Ar interface to stdout. If omitted, standard input is used to read a DHCP wire formatted message. Use the .Fl 4 or .Fl 6 flags to specify an address family. .It Fl V, Fl Fl variables Display a list of option codes, the associated variable and encoding for use in .Xr dhcpcd-run-hooks 8 . Variables are prefixed with new_ and old_ unless the option number is -. Variables without an option are part of the DHCP message and cannot be directly requested. .It Fl W, Fl Fl whitelist Ar address Ns Op /cidr Only accept packets from .Ar address Ns Op /cidr . .Fl X, Fl Fl blacklist is ignored if .Fl W, Fl Fl whitelist is set. .It Fl X, Fl Fl blacklist Ar address Ns Op Ar /cidr Ignore all packets from .Ar address Ns Op Ar /cidr . .It Fl Z , Fl Fl denyinterfaces Ar pattern When discovering interfaces, the interface name must not match .Ar pattern which is a space or comma separated list of patterns passed to .Xr fnmatch 3 . .It Fl z , Fl Fl allowinterfaces Ar pattern When discovering interfaces, the interface name must match .Ar pattern which is a space or comma separated list of patterns passed to .Xr fnmatch 3 . If the same interface is matched in .Fl Z , Fl Fl denyinterfaces then it is still denied. .It Fl Fl inactive Don't start any interfaces other than those specified on the command line. This allows .Nm to be started in Master mode and then wait for subsequent .Nm commands to start each interface as required. .It Fl Fl nodev Don't load any .Pa /dev management modules. .El .Sh 3RDPARTY LINK MANAGEMENT Some interfaces require configuration by 3rd parties, such as PPP or VPN. When an interface configuration in .Nm is marked as STATIC or INFORM without an address then .Nm will monitor the interface until an address is added or removed from it and act accordingly. For point to point interfaces (like PPP), a default route to its destination is automatically added to the configuration. If the point to point interface is configured for INFORM, then .Nm unicasts INFORM to the destination, otherwise it defaults to STATIC. .Sh NOTES .Nm requires a Berkley Packet Filter, or BPF device on BSD based systems and a Linux Socket Filter, or LPF device on Linux based systems for all IPv4 configuration. .Pp If restricting .Nm to a single interface and optionally address family via the command-line then all further calls to .Nm to rebind, reconfigure or exit need to include the same restrictive flags so that .Nm knows which process to signal. .Pp Some DHCP servers implement ClientID filtering. If .Nm is replacing an in-use DHCP client then you might need to adjust the clientid option .Nm sends to match. If using a DUID in place of the ClientID, edit .Pa @DBDIR@/duid accordingly. .Sh FILES .Bl -ohang .It Pa @SYSCONFDIR@/dhcpcd.conf Configuration file for dhcpcd. If you always use the same options, put them here. .It Pa @SCRIPT@ Bourne shell script that is run to configure or de-configure an interface. .It Pa @LIBDIR@/dhcpcd/dev .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@/dhcpcd.pid Stores the PID of .Nm running on all interfaces. .It Pa @RUNDIR@/dhcpcd\- Ns Ar interface Ns .pid Stores the PID of .Nm running on the .Ar interface . .It Pa @RUNDIR@/dhcpcd.sock Control socket to the master daemon. .It Pa @RUNDIR@/dhcpcd.unpriv.sock Unprivileged socket to the master daemon, only allows state retrieval. .It Pa @RUNDIR@/dhcpcd\- Ns Ar interface Ns .sock Control socket to per interface daemon. .El .Sh SEE ALSO .Xr fnmatch 3 , .Xr if_nametoindex 3 , .Xr dhcpcd.conf 5 , .Xr resolv.conf 5 , .Xr dhcpcd-run-hooks 8 , .Xr resolvconf 8 .Sh STANDARDS RFC\ 951, RFC\ 1534, RFC\ 2104, RFC\ 2131, RFC\ 2132, RFC\ 2563, RFC\ 2855, RFC\ 3004, RFC\ 3118, RFC\ 3203, RFC\ 3315, RFC\ 3361, RFC\ 3633, RFC\ 3396, RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833, RFC\ 4941, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106, RFC\ 6334, RFC\ 6355, RFC\ 6603, RFC\ 6704, RFC\ 7217, RFC\ 7550. .Sh AUTHORS .An Roy Marples Aq Mt roy@marples.name .Sh BUGS Please report them to .Lk http://roy.marples.name/projects/dhcpcd dhcpcd5-7.1.0/src/dhcpcd.c000066400000000000000000001407451342162717100152300ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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. */ const char dhcpcd_copyright[] = "Copyright (c) 2006-2019 Roy Marples"; #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 "dhcpcd.h" #include "dhcp6.h" #include "duid.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv4ll.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" #include "script.h" #ifdef HAVE_UTIL_H #include #endif #ifdef USE_SIGNALS const int dhcpcd_signals[] = { SIGTERM, SIGINT, SIGALRM, SIGHUP, SIGUSR1, SIGUSR2, SIGPIPE }; const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals); #endif 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; } if (ctx->dhcp6_opts) { for (opt = ctx->dhcp6_opts; ctx->dhcp6_opts_len > 0; opt++, ctx->dhcp6_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->dhcp6_opts); ctx->dhcp6_opts = NULL; } #endif if (ctx->vivso) { for (opt = ctx->vivso; ctx->vivso_len > 0; opt++, ctx->vivso_len--) free_dhcp_opt_embenc(opt); free(ctx->vivso); ctx->vivso = NULL; } } static void handle_exit_timeout(void *arg) { struct dhcpcd_ctx *ctx; ctx = arg; logerrx("timed out"); if (!(ctx->options & DHCPCD_MASTER)) { eloop_exit(ctx->eloop, EXIT_FAILURE); return; } ctx->options |= DHCPCD_NOWAITIP; dhcpcd_daemonise(ctx); } static const char * dhcpcd_af(int af) { switch (af) { case AF_UNSPEC: return "IP"; case AF_INET: return "IPv4"; case AF_INET6: return "IPv6"; default: return NULL; } } int dhcpcd_ifafwaiting(const struct interface *ifp) { unsigned long long opts; if (ifp->active != IF_ACTIVE_USER) return AF_MAX; opts = ifp->options->options; if (opts & DHCPCD_WAITIP4 && !ipv4_hasaddr(ifp)) return AF_INET; if (opts & DHCPCD_WAITIP6 && !ipv6_hasaddr(ifp)) return AF_INET6; if (opts & DHCPCD_WAITIP && !(opts & (DHCPCD_WAITIP4 | DHCPCD_WAITIP6)) && !ipv4_hasaddr(ifp) && !ipv6_hasaddr(ifp)) return AF_UNSPEC; return AF_MAX; } int dhcpcd_afwaiting(const struct dhcpcd_ctx *ctx) { unsigned long long opts; const struct interface *ifp; int af; if (!(ctx->options & DHCPCD_WAITOPTS)) return AF_MAX; opts = ctx->options; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP4) && ipv4_hasaddr(ifp)) opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP4); if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP6) && ipv6_hasaddr(ifp)) opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP6); if (!(opts & DHCPCD_WAITOPTS)) break; } if (opts & DHCPCD_WAITIP) af = AF_UNSPEC; else if (opts & DHCPCD_WAITIP4) af = AF_INET; else if (opts & DHCPCD_WAITIP6) af = AF_INET6; else return AF_MAX; return af; } static int dhcpcd_ipwaited(struct dhcpcd_ctx *ctx) { struct interface *ifp; int af; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { logdebugx("%s: waiting for an %s address", ifp->name, dhcpcd_af(af)); return 0; } } if ((af = dhcpcd_afwaiting(ctx)) != AF_MAX) { logdebugx("waiting for an %s address", dhcpcd_af(af)); return 0; } return 1; } /* Returns the pid of the child, otherwise 0. */ pid_t dhcpcd_daemonise(struct dhcpcd_ctx *ctx) { #ifdef THERE_IS_NO_FORK eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); errno = ENOSYS; return 0; #else pid_t pid, lpid; char buf = '\0'; int sidpipe[2], fd; if (ctx->options & DHCPCD_DAEMONISE && !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) { if (!dhcpcd_ipwaited(ctx)) return 0; } if (ctx->options & DHCPCD_ONESHOT) { loginfox("exiting due to oneshot"); eloop_exit(ctx->eloop, EXIT_SUCCESS); return 0; } eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); if (ctx->options & DHCPCD_DAEMONISED || !(ctx->options & DHCPCD_DAEMONISE)) return 0; logdebugx("forking to background"); /* Setup a signal pipe so parent knows when to exit. */ if (pipe(sidpipe) == -1) { logerr("%s: pipe", __func__); return 0; } switch (pid = fork()) { case -1: logerr("%s: fork", __func__); return 0; case 0: if ((lpid = pidfile_lock(ctx->pidfile)) != 0) logerr("%s: pidfile_lock %d", __func__, lpid); setsid(); /* Notify parent it's safe to exit as we've detached. */ close(sidpipe[0]); if (write(sidpipe[1], &buf, 1) == -1) logerr("%s: write", __func__); close(sidpipe[1]); /* Some polling methods don't survive after forking, * so ensure we can requeue all our events. */ if (eloop_requeue(ctx->eloop) == -1) { logerr("%s: eloop_requeue", __func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); close(fd); } ctx->options |= DHCPCD_DAEMONISED; return 0; default: /* Wait for child to detach */ close(sidpipe[1]); if (read(sidpipe[0], &buf, 1) == -1) logerr("%s: read", __func__); close(sidpipe[0]); loginfox("forked to background, child pid %d", pid); ctx->options |= DHCPCD_FORKED; eloop_exit(ctx->eloop, EXIT_SUCCESS); return pid; } #endif } static void dhcpcd_drop(struct interface *ifp, int stop) { #ifdef DHCP6 dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); #endif #ifdef INET6 ipv6nd_drop(ifp); ipv6_drop(ifp); #endif #ifdef IPV4LL ipv4ll_drop(ifp); #endif #ifdef INET dhcp_drop(ifp, stop ? "STOP" : "EXPIRE"); #endif #ifdef ARP arp_drop(ifp); #endif #if !defined(DHCP6) && !defined(DHCP) UNUSED(stop); #endif } static void stop_interface(struct interface *ifp) { struct dhcpcd_ctx *ctx; ctx = ifp->ctx; loginfox("%s: removing interface", ifp->name); ifp->options->options |= DHCPCD_STOPPING; dhcpcd_drop(ifp, 1); if (ifp->options->options & DHCPCD_DEPARTED) script_runreason(ifp, "DEPARTED"); else script_runreason(ifp, "STOPPED"); /* Delete all timeouts for the interfaces */ eloop_q_timeout_delete(ctx->eloop, 0, NULL, ifp); /* De-activate the interface */ ifp->active = IF_INACTIVE; ifp->options->options &= ~DHCPCD_STOPPING; /* Set the link state to unknown as we're no longer tracking it. */ ifp->carrier = LINK_UNKNOWN; if (!(ctx->options & (DHCPCD_MASTER | DHCPCD_TEST))) eloop_exit(ctx->eloop, EXIT_FAILURE); } static void configure_interface1(struct interface *ifp) { struct if_options *ifo = ifp->options; /* 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)) ifo->options |= DHCPCD_STATIC; } if (ifp->flags & IFF_NOARP || !(ifo->options & DHCPCD_ARP) || ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) ifo->options &= ~DHCPCD_IPV4LL; if (ifo->metric != -1) ifp->metric = (unsigned int)ifo->metric; if (!(ifo->options & DHCPCD_IPV4)) ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL | DHCPCD_WAITIP4); #ifdef INET6 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); /* 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 (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 INET6 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 } 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: confile file changed, expiring leases", ifp->name); dhcpcd_drop(ifp, 0); } } static void dhcpcd_pollup(void *arg) { struct interface *ifp = arg; int carrier; carrier = if_carrier(ifp); /* will set ifp->flags */ if (carrier == LINK_UP && !(ifp->flags & IFF_UP)) { struct timespec tv; tv.tv_sec = 0; tv.tv_nsec = IF_POLL_UP * NSEC_PER_MSEC; eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcpcd_pollup, ifp); return; } dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); } static void dhcpcd_initstate2(struct interface *ifp, unsigned long long options) { struct if_options *ifo; if (options) { if ((ifo = default_config(ifp->ctx)) == NULL) { logerr(__func__); return; } ifo->options |= options; free(ifp->options); ifp->options = ifo; } else ifo = ifp->options; #ifdef INET6 if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == -1) { logerr(__func__); ifo->options &= ~DHCPCD_IPV6; } #endif } static void dhcpcd_initstate1(struct interface *ifp, int argc, char **argv, unsigned long long options) { configure_interface(ifp, argc, argv, options); if (ifp->active) dhcpcd_initstate2(ifp, 0); } static void dhcpcd_initstate(struct interface *ifp, unsigned long long options) { dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); } void dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, const char *ifname) { struct interface *ifp; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL || ifp->options == NULL || !(ifp->options->options & DHCPCD_LINK) || !ifp->active) return; switch(carrier) { case LINK_UNKNOWN: carrier = if_carrier(ifp); /* will set ifp->flags */ break; case LINK_UP: /* we have a carrier! Still need to check for IFF_UP */ if (flags & IFF_UP) ifp->flags = flags; else { /* So we need to poll for IFF_UP as there is no * kernel notification when it's set. */ dhcpcd_pollup(ifp); return; } break; default: ifp->flags = flags; } /* If we here, we don't need to poll for IFF_UP any longer * if generated by a kernel event. */ eloop_timeout_delete(ifp->ctx->eloop, dhcpcd_pollup, ifp); if (carrier == LINK_UNKNOWN) { if (errno != ENOTTY && errno != ENXIO) { /* Don't log an error if interface departed */ logerr("%s: %s", ifp->name, __func__); } } else if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) { if (ifp->carrier != LINK_DOWN) { if (ifp->carrier == LINK_UP) loginfox("%s: carrier lost", ifp->name); ifp->carrier = LINK_DOWN; script_runreason(ifp, "NOCARRIER"); #ifdef NOCARRIER_PRESERVE_IP #ifdef ARP arp_drop(ifp); #endif dhcp_abort(ifp); ipv6nd_expire(ifp, 0); dhcp6_abort(ifp); #else dhcpcd_drop(ifp, 0); #endif } } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { if (ifp->carrier != LINK_UP) { loginfox("%s: carrier acquired", ifp->name); ifp->carrier = LINK_UP; #if !defined(__linux__) && !defined(__NetBSD__) /* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the * hardware address changes so we have to go * through the disovery process to work it out. */ dhcpcd_handleinterface(ctx, 0, ifp->name); #endif if (ifp->wireless) { uint8_t ossid[IF_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)) { #ifdef NOCARRIER_PRESERVE_IP dhcpcd_drop(ifp, 0); #endif ipv4ll_reset(ifp); } } dhcpcd_initstate(ifp, 0); script_runreason(ifp, "CARRIER"); #ifdef NOCARRIER_PRESERVE_IP /* Set any IPv6 Routers we remembered to expire * faster than they would normally as we * maybe on a new network. */ ipv6nd_expire(ifp, RTR_CARRIER_EXPIRE); #endif /* RFC4941 Section 3.5 */ ipv6_gentempifid(ifp); 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 (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); } void dhcpcd_startinterface(void *arg) { struct interface *ifp = arg; struct if_options *ifo = ifp->options; char buf[DUID_LEN * 3]; int carrier; struct timespec tv; if (ifo->options & DHCPCD_LINK) { switch (ifp->carrier) { case LINK_UP: break; case LINK_DOWN: loginfox("%s: waiting for carrier", ifp->name); return; case LINK_UNKNOWN: /* No media state available. * Loop until both IFF_UP and IFF_RUNNING are set */ if ((carrier = if_carrier(ifp)) == LINK_UNKNOWN) { tv.tv_sec = 0; tv.tv_nsec = IF_POLL_UP * NSEC_PER_MSEC; eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcpcd_startinterface, ifp); } else dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); return; } } if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) { /* Report client DUID */ if (ifp->ctx->duid == NULL) { if (duid_init(ifp) == 0) return; loginfox("DUID %s", hwaddr_ntoa(ifp->ctx->duid, ifp->ctx->duid_len, buf, sizeof(buf))); } } if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) { #ifdef INET6 size_t i; struct if_ia *ia; #endif /* 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 } 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); } if (ifo->options & DHCPCD_DHCP6) { dhcp6_find_delegates(ifp); if (ifp->active == IF_ACTIVE_USER) { enum DH6S d6_state; if (ifo->options & DHCPCD_IA_FORCED) d6_state = DH6S_INIT; else if (ifo->options & DHCPCD_INFORM6) d6_state = DH6S_INFORM; else d6_state = DH6S_CONFIRM; if (dhcp6_start(ifp, d6_state) == -1) logerr("%s: dhcp6_start", ifp->name); } } } #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; if ((!(ifp->ctx->options & DHCPCD_MASTER) || ifp->options->options & DHCPCD_IF_UP) && if_up(ifp) == -1) logerr("%s: %s", __func__, ifp->name); if (ifp->options->options & DHCPCD_LINK && ifp->carrier == LINK_UNKNOWN) { int carrier; if ((carrier = if_carrier(ifp)) != LINK_UNKNOWN) { dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); return; } loginfox("%s: unknown carrier, waiting for interface flags", ifp->name); } dhcpcd_startinterface(ifp); } static void run_preinit(struct interface *ifp) { if (ifp->ctx->options & DHCPCD_TEST) return; script_runreason(ifp, "PREINIT"); if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN) script_runreason(ifp, ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER"); } void dhcpcd_activateinterface(struct interface *ifp, unsigned long long options) { if (!ifp->active) { ifp->active = IF_ACTIVE; dhcpcd_initstate2(ifp, options); /* It's possible we might not have been able to load * a config. */ if (ifp->active) { configure_interface1(ifp); run_preinit(ifp); dhcpcd_prestartinterface(ifp); } } } int dhcpcd_handleinterface(void *arg, int action, const char *ifname) { struct dhcpcd_ctx *ctx; struct ifaddrs *ifaddrs; struct if_head *ifs; struct interface *ifp, *iff; const char * const argv[] = { ifname }; ctx = arg; 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); ifp->options->options |= DHCPCD_DEPARTED; stop_interface(ifp); } 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; return -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); } /* Free our discovered list */ while ((ifp = TAILQ_FIRST(ifs))) { TAILQ_REMOVE(ifs, ifp, next); if_free(ifp); } free(ifs); return 1; } static void dhcpcd_handlelink(void *arg) { struct dhcpcd_ctx *ctx = arg; if (if_handlelink(ctx) == -1) { if (errno == ENOBUFS || errno == ENOMEM) { dhcpcd_linkoverflow(ctx); return; } logerr(__func__); } } static void dhcpcd_checkcarrier(void *arg) { struct interface *ifp = arg; dhcpcd_handlecarrier(ifp->ctx, LINK_UNKNOWN, ifp->flags, ifp->name); } void dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx) { struct if_head *ifaces; struct ifaddrs *ifaddrs; struct interface *ifp, *ifn, *ifp1; logerrx("route socket overflowed - learning interface state"); /* Close the existing socket and open a new one. * This is easier than draining the kernel buffer of an * in-determinate size. */ eloop_event_delete(ctx->eloop, ctx->link_fd); close(ctx->link_fd); if_closesockets_os(ctx); if (if_opensockets_os(ctx) == -1) { logerr("%s: if_opensockets", __func__); eloop_exit(ctx->eloop, EXIT_FAILURE); return; } eloop_event_add(ctx->eloop, ctx->link_fd, dhcpcd_handlelink, ctx); /* Work out the current interfaces. */ ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv); /* 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 */ TAILQ_FOREACH_SAFE(ifp, ifaces, next, ifn) { ifp1 = if_find(ctx->ifaces, ifp->name); if (ifp1 != NULL) { /* If the interface already exists, * check carrier state. */ eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_checkcarrier, ifp1); continue; } TAILQ_REMOVE(ifaces, ifp, next); TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); if (ifp->active) eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_prestartinterface, ifp); } /* Update address state. */ if_markaddrsstale(ctx->ifaces); if_learnaddrs(ctx, ctx->ifaces, &ifaddrs); if_deletestaleaddrs(ctx->ifaces); } void dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname, const void *hwaddr, uint8_t hwlen) { struct interface *ifp; char buf[sizeof(ifp->hwaddr) * 3]; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL) return; if (!if_valid_hwaddr(hwaddr, hwlen)) hwlen = 0; if (hwlen > sizeof(ifp->hwaddr)) { errno = ENOBUFS; logerr("%s: %s", __func__, ifp->name); return; } if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0) return; loginfox("%s: new hardware address: %s", ifp->name, hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf))); ifp->hwlen = hwlen; memcpy(ifp->hwaddr, hwaddr, hwlen); } static void if_reboot(struct interface *ifp, int argc, char **argv) { unsigned long long oldopts; oldopts = ifp->options->options; script_runreason(ifp, "RECONFIGURE"); dhcpcd_initstate1(ifp, argc, argv, 0); dhcp_reboot_newopts(ifp, oldopts); dhcp6_reboot(ifp); dhcpcd_prestartinterface(ifp); } static void reload_config(struct dhcpcd_ctx *ctx) { struct if_options *ifo; free_globals(ctx); if ((ifo = read_config(ctx, NULL, NULL, NULL)) == NULL) return; add_options(ctx, NULL, ifo, ctx->argc, ctx->argv); /* We need to preserve these two options. */ if (ctx->options & DHCPCD_MASTER) ifo->options |= DHCPCD_MASTER; if (ctx->options & DHCPCD_DAEMONISED) ifo->options |= DHCPCD_DAEMONISED; ctx->options = ifo->options; free_options(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); else ipv4_applyaddr(ifp); } 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; /* Drop the last interface first */ TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { if (ifp->active) { ifp->options->options |= opts; if (ifp->options->options & DHCPCD_RELEASE) ifp->options->options &= ~DHCPCD_PERSISTENT; ifp->options->options |= DHCPCD_EXITING; stop_interface(ifp); } } } static void dhcpcd_ifrenew(struct interface *ifp) { if (!ifp->active) return; if (ifp->options->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) return; dhcp_renew(ifp); #define DHCPCD_RARENEW (DHCPCD_IPV6 | DHCPCD_IPV6RS) if ((ifp->options->options & DHCPCD_RARENEW) == DHCPCD_RARENEW) ipv6nd_startrs(ifp); dhcp6_renew(ifp); } static void dhcpcd_renew(struct dhcpcd_ctx *ctx) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { dhcpcd_ifrenew(ifp); } } #ifdef USE_SIGNALS #define sigmsg "received %s, %s" static void signal_cb(int sig, void *arg) { struct dhcpcd_ctx *ctx = arg; unsigned long long opts; int exit_code; 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"); logclose(); if (logopen(ctx->logfile) == -1) logerr(__func__); return; case SIGPIPE: logwarnx("received SIGPIPE"); return; default: logerrx("received signal %d but don't know what to do with it", sig); return; } if (!(ctx->options & DHCPCD_TEST)) stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, exit_code); } #endif static void dhcpcd_getinterfaces(void *arg) { struct fd_list *fd = arg; struct interface *ifp; size_t len; len = 0; TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { if (!ifp->active) continue; len++; if (D_STATE_RUNNING(ifp)) len++; if (IPV4LL_STATE_RUNNING(ifp)) len++; if (IPV6_STATE_RUNNING(ifp)) len++; if (RS_STATE_RUNNING(ifp)) len++; if (D6_STATE_RUNNING(ifp)) len++; } if (write(fd->fd, &len, sizeof(len)) != sizeof(len)) return; eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { if (!ifp->active) continue; if (send_interface(fd, ifp) == -1) logerr(__func__); } } int dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, char **argv) { struct interface *ifp; unsigned long long opts; int opt, oi, do_reboot, do_renew; size_t len, l; char *tmp, *p; /* Special commands for our control socket * as the other end should be blocking until it gets the * expected reply we should be safely able just to change the * write callback on the fd */ if (strcmp(*argv, "--version") == 0) { return control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1, 0); } else if (strcmp(*argv, "--getconfigfile") == 0) { return control_queue(fd, UNCONST(fd->ctx->cffile), strlen(fd->ctx->cffile) + 1, 0); } else if (strcmp(*argv, "--getinterfaces") == 0) { eloop_event_add_w(fd->ctx->eloop, fd->fd, dhcpcd_getinterfaces, fd); return 0; } else if (strcmp(*argv, "--listen") == 0) { fd->flags |= FD_LISTEN; return 0; } /* Only priviledged users can control dhcpcd via the socket. */ if (fd->flags & FD_UNPRIV) { errno = EPERM; return -1; } /* Log the command */ len = 1; for (opt = 0; opt < argc; opt++) len += strlen(argv[opt]) + 1; tmp = malloc(len); if (tmp == NULL) return -1; p = tmp; for (opt = 0; opt < argc; opt++) { l = strlen(argv[opt]); strlcpy(p, argv[opt], len); len -= l + 1; p += l; *p++ = ' '; } *--p = '\0'; 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; } } if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { if (optind == argc) { stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, EXIT_SUCCESS); return 0; } for (oi = optind; oi < argc; oi++) { if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) continue; if (!ifp->active) continue; ifp->options->options |= opts; if (opts & DHCPCD_RELEASE) ifp->options->options &= ~DHCPCD_PERSISTENT; stop_interface(ifp); } return 0; } if (do_renew) { if (optind == argc) { dhcpcd_renew(ctx); return 0; } for (oi = optind; oi < argc; oi++) { if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) continue; dhcpcd_ifrenew(ifp); } return 0; } reload_config(ctx); /* XXX: Respect initial commandline options? */ reconf_reboot(ctx, do_reboot, argc, argv, optind - 1); return 0; } int main(int argc, char **argv) { struct dhcpcd_ctx ctx; struct ifaddrs *ifaddrs = NULL; struct if_options *ifo; struct interface *ifp; uint16_t family = 0; int opt, oi = 0, i; unsigned int logopts; time_t t; ssize_t len; #if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) pid_t pid; #endif #ifdef USE_SIGNALS int sig = 0; const char *siga = NULL; #endif /* Test for --help and --version */ if (argc > 1) { if (strcmp(argv[1], "--help") == 0) { usage(); return EXIT_SUCCESS; } else if (strcmp(argv[1], "--version") == 0) { printf(""PACKAGE" "VERSION"\n%s\n", dhcpcd_copyright); 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 "\n"); return EXIT_SUCCESS; } } memset(&ctx, 0, sizeof(ctx)); ifo = NULL; ctx.cffile = CONFIG; ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; ctx.pf_inet_fd = -1; #ifdef IFLR_ACTIVE ctx.pf_link_fd = -1; #endif TAILQ_INIT(&ctx.control_fds); #ifdef PLUGIN_DEV ctx.dev_fd = -1; #endif #ifdef INET ctx.udp_fd = -1; #endif rt_init(&ctx); logopts = LOGERR_ERR|LOGERR_LOG|LOGERR_LOG_DATE|LOGERR_LOG_PID; i = 0; while ((opt = getopt_long(argc, argv, ctx.options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { case '4': family = AF_INET; break; case '6': family = AF_INET6; break; case 'f': ctx.cffile = optarg; break; case 'j': free(ctx.logfile); ctx.logfile = strdup(optarg); break; #ifdef USE_SIGNALS case 'k': sig = SIGALRM; siga = "ALRM"; break; case 'n': sig = SIGHUP; siga = "HUP"; break; case 'g': case 'p': /* Force going via command socket as we're * out of user definable signals. */ i = 4; break; case 'q': 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; } } logsetopts(logopts); logopen(ctx.logfile); ctx.argv = argv; ctx.argc = argc; ctx.ifc = argc - optind; ctx.ifv = argv + optind; 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); printf("\nDHCPv6 options:\n"); dhcp6_printoptions(&ctx, ifo->dhcp6_override, ifo->dhcp6_override_len); } #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_MASTER)) { 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_MASTER; } 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 /* Save signal mask, block and redirect signals to our handler */ if (eloop_signal_set_cb(ctx.eloop, dhcpcd_signals, dhcpcd_signals_len, signal_cb, &ctx) == -1) { logerr("%s: eloop_signal_set_cb", __func__); goto exit_failure; } if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) { logerr("%s: eloop_signal_mask", __func__); goto exit_failure; } #endif if (ctx.options & DHCPCD_DUMPLEASE) { /* Open sockets so we can dump something about * valid interfaces. */ if (if_opensockets(&ctx) == -1) { logerr("%s: if_opensockets", __func__); goto exit_failure; } if (optind != argc) { /* We need to try and find the interface so we can load * the hardware address to compare automated IAID */ ctx.ifaces = if_discover(&ctx, &ifaddrs, argc - optind, argv + optind); } else { if ((ctx.ifaces = malloc(sizeof(*ctx.ifaces))) != NULL) TAILQ_INIT(ctx.ifaces); } if (ctx.ifaces == NULL) { logerr("%s: if_discover", __func__); goto exit_failure; } ifp = if_find(ctx.ifaces, argv[optind]); if (ifp == NULL) { ifp = calloc(1, sizeof(*ifp)); if (ifp == NULL) { logerr(__func__); goto exit_failure; } if (optind != argc) strlcpy(ctx.pidfile, argv[optind], sizeof(ctx.pidfile)); ifp->ctx = &ctx; TAILQ_INSERT_HEAD(ctx.ifaces, ifp, next); if (family == 0) { if (ctx.pidfile[0] != '\0' && ctx.pidfile[strlen(ctx.pidfile) - 1] == '6') family = AF_INET6; else family = AF_INET; } } configure_interface(ifp, ctx.argc, ctx.argv, 0); i = 0; if (family == 0 || family == AF_INET) { if (dhcp_dump(ifp) == -1) i = -1; } if (family == 0 || family == AF_INET6) { if (dhcp6_dump(ifp) == -1) i = -1; } if (i == -1) goto exit_failure; goto exit_success; } #ifdef USE_SIGNALS /* Test against siga instead of sig to avoid gcc * warning about a bogus potential signed overflow. * The end result will be the same. */ if ((siga == NULL || i == 4 || ctx.ifc != 0) && !(ctx.options & DHCPCD_TEST)) { #endif if (!(ctx.options & DHCPCD_MASTER)) ctx.control_fd = control_open(argv[optind]); if (ctx.control_fd == -1) ctx.control_fd = control_open(NULL); if (ctx.control_fd != -1) { loginfox("sending commands to master dhcpcd process"); len = control_send(&ctx, argc, argv); control_close(&ctx); if (len > 0) { logdebugx("send OK"); goto exit_success; } else { logerr("%s: control_send", __func__); goto exit_failure; } } else { if (errno != ENOENT) logerr("%s: control_open", __func__); } #ifdef USE_SIGNALS } #endif #ifdef USE_SIGNALS if (sig != 0) { pid = pidfile_read(ctx.pidfile); if (pid != 0 && pid != -1) loginfox("sending signal %s to pid %d", siga, pid); if (pid == 0 || pid == -1 || kill(pid, sig) != 0) { if (sig != SIGHUP && sig != SIGUSR1 && errno != EPERM) logerrx(PACKAGE" not running"); if (pid != 0 && pid != -1 && errno != ESRCH) { logerr("kill"); goto exit_failure; } unlink(ctx.pidfile); if (sig != SIGHUP && sig != SIGUSR1) goto exit_failure; } else { struct timespec ts; if (sig == SIGHUP || sig == SIGUSR1) goto exit_success; /* Spin until it exits */ loginfox("waiting for pid %d to exit", pid); ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 10th of a second */ for(i = 0; i < 100; i++) { nanosleep(&ts, NULL); if (pidfile_read(ctx.pidfile) == -1) goto exit_success; } logerrx("pid %d failed to exit", pid); goto exit_failure; } } if (!(ctx.options & DHCPCD_TEST)) { /* Ensure we have the needed directories */ if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) logerr("%s: mkdir `%s'", __func__, RUNDIR); if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) logerr("%s: mkdir `%s'", __func__, DBDIR); if ((pid = pidfile_lock(ctx.pidfile)) != 0) { if (pid == -1) logerr("%s: pidfile_lock", __func__); else logerrx(PACKAGE " already running on pid %d (%s)", pid, ctx.pidfile); goto exit_failure; } } if (ctx.options & DHCPCD_MASTER) { if (control_start(&ctx, NULL) == -1) logerr("%s: control_start", __func__); } #else if (control_start(&ctx, ctx.options & DHCPCD_MASTER ? NULL : argv[optind]) == -1) { logerr("%s: control_start", __func__); goto exit_failure; } #endif logdebugx(PACKAGE "-" VERSION " starting"); ctx.options |= DHCPCD_STARTED; #ifdef HAVE_SETPROCTITLE setproctitle("%s%s%s", ctx.options & DHCPCD_MASTER ? "[master]" : argv[optind], ctx.options & DHCPCD_IPV4 ? " [ip4]" : "", ctx.options & DHCPCD_IPV6 ? " [ip6]" : ""); #endif if (if_opensockets(&ctx) == -1) { logerr("%s: if_opensockets", __func__); goto exit_failure; } /* 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; /* Start handling kernel messages for interfaces, addresses and * routes. */ eloop_event_add(ctx.eloop, ctx.link_fd, dhcpcd_handlelink, &ctx); /* Start any dev listening plugin which may want to * change the interface name provided by the kernel */ if ((ctx.options & (DHCPCD_MASTER | DHCPCD_DEV)) == (DHCPCD_MASTER | DHCPCD_DEV)) dev_start(&ctx); ctx.ifaces = if_discover(&ctx, &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 || !ifp->active) logerrx("%s: interface not found or invalid", ctx.ifv[i]); } TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active == IF_ACTIVE_USER) break; } if (ifp == NULL) { if (ctx.ifc == 0) { logfunc_t *logfunc; logfunc = ctx.options & DHCPCD_INACTIVE ? logdebugx : logerrx; logfunc("no valid interfaces found"); } else goto exit_failure; if (!(ctx.options & DHCPCD_LINK)) { logerrx("aborting as link detection is disabled"); goto exit_failure; } } TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) dhcpcd_initstate1(ifp, argc, argv, 0); } if_learnaddrs(&ctx, ctx.ifaces, &ifaddrs); if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx)) goto exit_success; opt = 0; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) { run_preinit(ifp); if (!(ifp->options->options & DHCPCD_LINK) || ifp->carrier != LINK_DOWN) opt = 1; } } if (!(ctx.options & DHCPCD_BACKGROUND)) { if (ctx.options & DHCPCD_MASTER) 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)) { logfunc_t *logfunc; logfunc = ctx.options & DHCPCD_INACTIVE ? logdebugx : logwarnx; logfunc("no interfaces have a carrier"); if (dhcpcd_daemonise(&ctx)) goto exit_success; } else if (t > 0 && /* Test mode removes the daemonise bit, so check for both */ ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) { eloop_timeout_add_sec(ctx.eloop, t, handle_exit_timeout, &ctx); } } free_options(&ctx, ifo); ifo = NULL; if_sortinterfaces(&ctx); TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) eloop_timeout_add_sec(ctx.eloop, 0, dhcpcd_prestartinterface, ifp); } 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 (ifaddrs != NULL) freeifaddrs(ifaddrs); if (control_stop(&ctx) == -1) logerr("%s: control_stop", __func__); /* Free memory and close fd's */ if (ctx.ifaces) { while ((ifp = TAILQ_FIRST(ctx.ifaces))) { TAILQ_REMOVE(ctx.ifaces, ifp, next); if_free(ifp); } free(ctx.ifaces); } free_options(&ctx, ifo); rt_dispose(&ctx); free(ctx.duid); if (ctx.link_fd != -1) { eloop_event_delete(ctx.eloop, ctx.link_fd); close(ctx.link_fd); } if_closesockets(&ctx); free_globals(&ctx); ipv6_ctxfree(&ctx); dev_stop(&ctx); eloop_free(ctx.eloop); free(ctx.iov[0].iov_base); if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) loginfox(PACKAGE " exited"); logclose(); free(ctx.logfile); #ifdef USE_SIGNALS if (ctx.options & DHCPCD_FORKED) _exit(i); /* so atexit won't remove our pidfile */ #endif return i; } dhcpcd5-7.1.0/src/dhcpcd.conf000066400000000000000000000023531342162717100157230ustar00rootroot00000000000000# 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 dhcpcd5-7.1.0/src/dhcpcd.conf.5.in000066400000000000000000000671431342162717100165030ustar00rootroot00000000000000.\" Copyright (c) 2006-2019 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 September 15, 2018 .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 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 recieve 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@/dhcpcd.sock so that users other than root can connect to .Nm dhcpcd . .It Ic debug Echo debug messages to the stderr and syslog. .It Ic dev Ar value Load the .Ar value .Pa /dev management module. .Nm dhcpcd will load the first one found to work, if any. .It Ic env Ar value Push .Ar value to the environment for use in .Xr dhcpcd-run-hooks 8 . For example, you can force the hostname hook to always set the hostname with .Ic env .Va force_hostname=YES . Or set which driver .Xr wpa_supplicant 8 should use with .Ic env .Va wpa_supplicant_driver=nl80211 .Pp If the hostname is set, 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 Use a DHCP Unique Identifier. If a system UUID is available, that will be used to create a DUID-UUID, otheriwse if persistent storage is available then a DUID-LLT (link local address + time) is generated, otherwise DUID-LL is generated (link local address). 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. .It Ic iaid Ar iaid Set the Interface Association Identifier to .Ar iaid . This option must be used in an .Ic interface block. This defaults to the VLANID (prefixed with 0xff) for the interface if set, otherwise the last 4 bytes of the hardware address assigned to the interface. Each instance of this should be unique within the scope of the client and .Nm dhcpcd warns if a conflict is detected. If there is a conflict, it is only a problem if the conflicted IAIDs are used on the same network. .It Ic dhcp Enable DHCP on the interface, on by default. .It Ic dhcp6 Enable DHCPv6 on the interface, on by default. .It Ic ipv4 Enable IPv4 on the interface, on by default. .It Ic ipv6 Enable IPv6 on the interface, on by default. .It Ic request Op Ar address Request the .Ar address in the DHCP DISCOVER message. There is no guarantee this is the address the DHCP server will actually give. If no .Ar address is given then the first address currently assigned to the .Ar interface is used. .It Ic inform Op Ar address Ns Op Ar /cidr Ns Op Ar /broadcast_address Behaves like .Ic request as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST. This does not get a lease as such, just notifies the DHCP server of the .Ar address in use. You should also include the optional .Ar cidr network number in case the address is not already configured on the interface. .Nm dhcpcd remains running and pretends it has an infinite lease. .Nm dhcpcd will not de-configure the interface when it exits. If .Nm dhcpcd fails to contact a DHCP server then it returns a failure instead of falling back on IPv4LL. .It Ic inform6 Performs a DHCPv6 Information Request. No address is requested or specified, but all other DHCPv6 options are allowed. This is normally performed automatically when an IPv6 Router Advertisement indicates that the client should perform this operation. This option is only needed when .Nm dhcpcd is not processing IPv6 RA messages and the need for a DHCPv6 Information Request exists. .It Ic persistent .Nm dhcpcd normally de-configures the interface and configuration when it exits. Sometimes, this isn't desirable if, for example, you have root mounted over NFS or SSH clients connect to this host and they need to be notified of the host shutting down. You can use this option to stop this from happening. .It Ic fallback Ar profile Fall back to using this profile if DHCP fails. This allows you to configure a static profile instead of using ZeroConf. .It Ic hostname Ar name Sends the hostname .Ar name to the DHCP server so it can be registered in DNS. If .Ar name is an empty string then the current system hostname is sent. If .Ar name is a FQDN (i.e., contains a .) then it will be encoded as such. .It Ic hostname_short Sends the short hostname to the DHCP server instead of the FQDN. This is useful because DHCP servers will not register the FQDN in their DNS if the domain part does not match theirs. .Pp Also, see the .Ic env option above to control how the hostname is set on the host. .It Ic ia_na Op Ar iaid Op / address Request a DHCPv6 Normal Address for .Ar iaid . .Ar iaid defaults to the .Ic iaid option as described above. You can request more than one ia_na by specifying a unique .Ar iaid for each one. .It Ic ia_ta Op Ar iaid Request a DHCPv6 Temporary Address for .Ar iaid . You can request more than one ia_ta by specifying a unique .Ar iaid for each one. .It Ic ia_pd Op Ar iaid Oo / Ar prefix / Ar prefix_len Oc Op Ar interface Op / Ar sla_id Op / Ar prefix_len Op / Ar suffix Request a DHCPv6 Delegated Prefix for .Ar iaid . This option must be used in an .Ic interface block. Unless a .Ar sla_id of 0 is assigned with the same resultant prefix length as the delegation, a reject route is installed for the Delegated Prefix to stop unallocated addresses being resolved upstream. If no .Ar interface is given then we will assign a prefix to every other interface with a .Ar sla_id equivalent to the interface index assigned by the OS. Otherwise addresses are only assigned for each .Ar interface and .Ar sla_id . Each assigned address will have a .Ar suffix , defaulting to 1. If the .Ar suffix is 0 then a SLAAC address is assigned. You cannot assign a prefix to the requesting interface unless the DHCPv6 server supports the .Li RFC 6603 Prefix Exclude Option. .Nm dhcpcd has to be running for all the interfaces it is delegating to. A default .Ar prefix_len of 64 is assumed, unless the maximum .Ar sla_id does not fit. In this case .Ar prefix_len is increased to the highest multiple of 8 that can accommodate the .Ar sla_id . .Ar sla_id is an integer which must be unique inside the .Ar iaid and is added to the prefix which must fit inside .Ar prefix_len less the length of the delegated prefix. You can specify multiple .Ar interface / .Ar sla_id / .Ar prefix_len per .Ic ia_pd , space separated. IPv6RS should be disabled globally when requesting a Prefix Delegation. .Pp In the following example eth0 is the externally facing interface to be configured for both IPv4 and IPv6. The DHCPv4 server will provide us with an IPv4 address and a default route. The DHCPv6 server is going to provide us with an IPv6 address, a default route and a /64 subnet to be delegated to the internal interface. The eth1 interface will be automatically configured for IPv6 using the first address (::1) from the delegated prefix. A second prefix is requested and assigned to two other interfaces. .Xr rtadvd 8 can be used with an empty configuration file on eth1, eth2 and eth3, to provide automatic IPv6 address configuration for the internal network. .Bd -literal noipv6rs # disable routing solicitation denyinterfaces eth2 # Don't touch eth2 at all interface eth0 ipv6rs # enable routing solicitation get the # default IPv6 route ia_na 1 # request an IPv6 address ia_pd 2 eth1/0 # request a PD and assign it to eth1 ia_pd 3 eth2/1 eth3/2 # req a PD and assign it to eth2 and eth3 .Ed .It Ic ipv4only Only configure IPv4. .It Ic ipv6only Only configure IPv6. .It Ic fqdn Op disable | none | ptr | both .Ar none will not ask the DHCP server to update DNS. .Ar ptr just asks the DHCP server to update the PTR record of the host in DNS, whereas .Ar both also updates the A record. .Ar disable will disable the FQDN option. The default is .Ar both . .Nm dhcpcd itself never does any DNS updates. .Nm dhcpcd encodes the FQDN hostname as specified in .Li RFC 1035 . .It Ic interface Ar interface Subsequent options are only parsed for this .Ar interface . .It Ic ipv6ra_autoconf Generate SLAAC addresses for each Prefix advertised by an IPv6 Router Advertisement message with the Auto flag set. On by default. .It Ic ipv6ra_noautoconf Disables the above option. .It Ic ipv6ra_fork By default, when .Nm dhcpcd receives an IPv6 Router Advertisement, .Nm dhcpcd will only fork to the background if the RA contains at least one unexpired RDNSS option and a valid prefix or no DHCPv6 instruction. Set this option so to make .Nm dhcpcd always fork on an RA. .It Ic ipv6rs Enables IPv6 Router Advertisement solicitation. This is on by default, but is documented here in the case where it is disabled globally but needs to be enabled for one interface. .It Ic leasetime Ar seconds Request a leasetime of .Ar seconds . .It Ic logfile Ar logfile Writes to the specified .Ar logfile rather than .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 200 + .Xr if_nametoindex 3 . An extra 100 will be added for wireless interfaces. .It Ic mudurl Ar url Specifies the URL for a Manufacturer Usage Description (MUD). The description is used by upstream network devices to instantiate any desired access lists. See draft-ietf-opsawg-mud for more information. .It Ic noalias Any pre-existing IPv4 addresses will be removed from the interface when adding a new IPv4 address. .It Ic noarp Don't send any ARP requests. This also disables IPv4LL. .It Ic noauthrequired Don't require authentication even though we requested it. Also allows FORCERENEW and RECONFIGURE messages without authentication. .It Ic nodelay Don't delay for an initial randomised time when starting protocols. .It Ic nodev Don't load .Pa /dev management modules. .It Ic nodhcp Don't start DHCP or listen to DHCP messages. This is only useful when allowing IPv4LL. .It Ic nodhcp6 Don't start DHCPv6 or listen to DHCPv6 messages. Normally DHCPv6 is started by an IPv6 Router Advertisement instruction or configuration. .It Ic nogateway Don't install any default routes. .It Ic gateway Install a default route if available (default). .It Ic nohook Ar script Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with .Pa .sh . .Pp So to stop .Nm dhcpcd from touching your DNS settings or starting wpa_supplicant you would do:- .D1 nohook resolv.conf, wpa_supplicant .It Ic noipv4 Don't attempt to configure an IPv4 address. .It Ic noipv4ll Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP. See .Rs .%T "RFC 3927" .Re .It Ic noipv6 Don't solicit or accept IPv6 Router Advertisements and DHCPv6. .It Ic noipv6rs Don't solicit or accept IPv6 Router Advertisements. .It Ic nolink Don't receive link messages about carrier status. You should only set this for buggy interface drivers. .It Ic noup Don't bring the interface up when in master mode. If .Nm cannot determine the carrier state, .Nm will enter a tight polling loop until the interface is marked up and running or a valid carrier state is reported. .It Ic option Ar option Requests the .Ar option from the server. It can be a variable to be used in .Xr dhcpcd-run-hooks 8 or the numerical value. You can specify more .Ar option Ns s separated by commas, spaces or more .Ic option lines. 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 and moving from DISCOVER to IPv4LL if no reply. The default is 5 seconds. A setting of 0 seconds causes .Nm dhcpcd to skip the REBOOT phase and go straight into DISCOVER. This is desirable for mobile users because if you change from network A to network B and they use the same subnet and the address from network A isn't in use on network B, then the DHCP server will remain silent even if authoritative which means .Nm dhcpcd will timeout before moving back to the DISCOVER phase. .It Ic release .Nm dhcpcd will release the lease prior to stopping the interface. .It Ic script Ar script Use .Ar script instead of the default .Pa @SCRIPT@ . .It Ic ssid Ar ssid Subsequent options are only parsed for this wireless .Ar ssid . .It Ic slaac Op Ar hwaddr | Ar private Selects the interface identifier used for SLAAC generated IPv6 addresses. If .Ar private is used, an RFC 7217 address is generated. .It Ic static Ar value Configures a static .Ar value . If you set .Ic ip_address then .Nm dhcpcd will not attempt to obtain a lease and will just use the value for the address with an infinite lease time. If you set .Ic ip6_address , .Nm dhcpcd will continue auto-configuation as normal. .Pp Here is an example which configures two static address, overriding the default IPv4 broadcast address, an IPv4 router, DNS and disables IPv6 auto-configuration. You could also use the .Ic inform6 command here if you wished to obtain more information via DHCPv6. For IPv4, you should use the .Ic inform Ar ipaddress option instead of setting a static address. .D1 interface eth0 .D1 noipv6rs .D1 static ip_address=192.168.0.10/24 .D1 static broadcast_address=192.168.0.63 .D1 static ip6_address=fd51:42f8:caae:d92e::ff/64 .D1 static routers=192.168.0.1 .D1 static domain_name_servers=192.168.0.1 fd51:42f8:caae:d92e::1 .Pp Here is an example for PPP which gives the destination a default route. It uses the special .Ar destination keyword to insert the destination address into the value. .D1 interface ppp0 .D1 static ip_address= .D1 destination routers .It Ic timeout Ar seconds Time out after .Ar seconds , instead of the default 30. A setting of 0 .Ar seconds causes .Nm dhcpcd to wait forever to get a lease. If .Nm dhcpcd is working on a single interface then .Nm dhcpcd will exit when a timeout occurs, otherwise .Nm dhcpcd will fork into the background. If using IPv4LL then .Nm dhcpcd start the IPv4LL process after the timeout and then wait a little longer before really timing out. .It Ic userclass Ar string Tag the DHCPv4 messages with the userclass. You can specify more than one. .It Ic vendor Ar code , Ns Ar value Add an encapsulated vendor option. .Ar code should be between 1 and 254 inclusive. To add a raw vendor string, omit .Ar code but keep the comma. Examples. .Pp Set the vendor option 01 with an IP address. .D1 vendor 01,192.168.0.2 Set the vendor option 02 with a hex code. .D1 vendor 02,01:02:03:04:05 Set the vendor option 03 with an IP address as a string. .D1 vendor 03,\e"192.168.0.2\e" Set un-encapsulated vendor option to hello world. .D1 vendor ,"hello world" .It Ic vendorclassid Ar string Set the DHCP Vendor Class. DHCPv6 has 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 .Xr RFC 3925 is undefined if the Enterprise Number differs. .It Ic waitip Op 4 | 6 Wait for an address to be assigned before forking to the background. 4 means wait for an IPv4 address to be assigned. 6 means wait for an IPv6 address to be assigned. If no argument is given, .Nm will wait for any address protocol to be assigned. It is possible to wait for more than one address protocol and .Nm will only fork to the background when all waiting conditions are satisfied. .It Ic xidhwaddr Use the last four bytes of the hardware address as the DHCP xid instead of a randomly generated number. .El .Ss Defining new options DHCP, ND and DHCPv6 allow for the use of custom options, and RFC 3925 vendor options for DHCP can also be supplied. Each option needs to be started with the .Ic define , .Ic definend, .Ic define6 or .Ic vendopt directive. This can optionally be followed by both .Ic embed or .Ic encap options. Both can be specified more than once and .Ic embed must come before .Ic encap . .Bl -tag -width indent .It Ic define Ar code Ar type Ar variable Defines the DHCP option .Ar code of .Ar type with a name of .Ar variable exported to .Xr dhcpcd-run-hooks 8 . .It Ic definend Ar code Ar type Ar variable Defines the ND option .Ar code of .Ar type with a name of .Ar variable exported to .Xr dhcpcd-run-hooks 8 , with a prefix of .Va _nd . .It Ic define6 Ar code Ar type Ar variable Defines the DHCPv6 option .Ar code of .Ar type with a name of .Ar variable exported to .Xr dhcpcd-run-hooks 8 , with a prefix of .Va _dhcp6 . .It Ic vendopt Ar code Ar type Ar variable Defines the Vendor-Identifying Vendor Options. The .Ar code is the IANA Enterprise Number which will uniquely describe the encapsulated options. .Ar type is normally .Ar encap . .Ar variable names the Vendor option to be exported. .It Ic embed Ar type Ar variable Defines an embedded variable within the defined option. The length is determined by the .Ar type . If the .Ar variable is not the same as defined in the parent option, it is prefixed with the parent .Ar variable first with an underscore. If the .Ar variable has the name of .Ar reserved then it is not processed. .It Ic encap Ar code Ar type Ar variable Defines an encapsulated variable within the defined option. The length is determined by the .Ar type . If the .Ar variable is not the same as defined in the parent option, it is prefixed with the parent .Ar variable first with an underscore. .El .Ss Type prefix These keywords come before the type itself, to describe it more fully. You can use more than one, but they must appear in the order listed below. .Bl -tag -width -indent .It Ic request Requests the option by default without having to be specified in user configuration. .It Ic norequest This option cannot be requested, regardless of user configuration. .It Ic optional This option is optional. Only makes sense for embedded options like the client FQDN option, where the FQDN string itself is optional. .It Ic index The option can appear more than once and will be indexed. .It Ic array The option data is split into a space separated array, each element being the same type. .El .Ss Types to define The type directly affects the length of data consumed inside the option. Any remaining data is normally discarded. Lengths can be specified for string and binhex types, but this is generally with other data embedded afterwards in the same option. .Bl -tag -width indent .It Ic ipaddress An IPv4 address, 4 bytes. .It Ic ip6address An IPv6 address, 16 bytes. .It Ic string Op : Ic length A NVT ASCII string of printable characters. .It Ic byte A byte. .It Ic bitflags : Ic flags A byte represented as a string of flags, most significant bit first. For example, using ABCDEFGH then A would equal 10000000, B 01000000, C 00100000, etc. If the bit is not set, the flag is not printed. A flag of 0 is not printed even if the bit position is set. This is to allow reservation of the first bits while assigning the last bits. .It Ic int16 A signed 16bit integer, 2 bytes. .It Ic uint16 An unsigned 16bit integer, 2 bytes. .It Ic int32 A signed 32bit integer, 4 bytes. .It Ic uint32 An unsigned 32bit integer, 4 bytes. .It Ic flag A fixed value (1) to indicate that the option is present, 0 bytes. .It Ic domain An RFC 3397 encoded string. .It Ic dname An RFC 1035 validated string. .It Ic binhex Op : Ic length Binary data expressed as hexadecimal. .It Ic embed Contains embedded options (implies encap as well). .It Ic encap Contains encapsulated options (implies embed as well). .It Ic option References an option from the global definition. .El .Ss Example definition .D1 # DHCP option 81, Fully Qualified Domain Name, RFC 4702 .D1 define 81 embed fqdn .D1 embed byte flags .D1 embed byte rcode1 .D1 embed byte rcode2 .D1 embed domain fqdn .Pp .D1 # DHCP option 125, Vendor Specific Information Option, RFC 3925 .D1 define 125 encap vsio .D1 embed uint32 enterprise_number .D1 # Options defined for the enterprise number .D1 encap 1 ipaddress ipaddress .Ss Supported Authentication Protocols .Bl -tag -width -indent .It Ic token Sends a plain text token the server expects and matches a token sent by the server. The tokens do not have to be the same. If unspecified, the token with a .Ar secretid of 0 will be used in sending messages and validating received messages. .It Ic delayedrealm Delayed Authentication. .Nm dhcpcd will send an authentication option with no key or MAC. The server will see this option, and select a key for .Nm , writing the .Ar realm and .Ar secretid in it. .Nm dhcpcd will then look for an unexpired token with a matching .Ar realm and .Ar secretid . This token is used to authenticate all other messages. .It Ic delayed Same as above, but without a realm. .El .Ss Supported Authentication Algorithms If none specified, .Ic hmac-md5 is the default. .Bl -tag -width -indent .It Ic hmac-md5 .El .Ss Supported Replay Detection Mechanisms If none specified, .Ic monotonic is the default. If this is changed from what was previously used, or the means of calculating or storing it is broken, then the DHCP server will probably have to have its notion of the client's Replay Detection Value reset. .Bl -tag -width -indent .It Ic monocounter Read the number in the file .Pa @DBDIR@/dhcpcd-rdm.monotonic and add one to it. .It Ic monotime Create an NTP timestamp from the system time. .It Ic monotonic Same as .Ic monotime . .El .Sh SEE ALSO .Xr fnmatch 3 , .Xr if_nametoindex 3 , .Xr dhcpcd 8 , .Xr dhcpcd-run-hooks 8 .Sh AUTHORS .An Roy Marples Aq Mt roy@marples.name .Sh BUGS Please report them to .Lk http://roy.marples.name/projects/dhcpcd dhcpcd5-7.1.0/src/dhcpcd.h000066400000000000000000000143111342162717100152220ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "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 /* If the interface does not support carrier status (ie PPP), * dhcpcd can poll it for the relevant flags periodically */ #define IF_POLL_UP 100 /* milliseconds */ #ifdef __QNX__ /* QNX carries defines for, but does not actually support PF_LINK */ #undef IFLR_ACTIVE #endif struct interface { struct dhcpcd_ctx *ctx; TAILQ_ENTRY(interface) next; char name[IF_NAMESIZE]; unsigned int index; unsigned int active; unsigned int flags; sa_family_t family; unsigned char hwaddr[HWADDR_LEN]; uint8_t hwlen; unsigned short vlanid; unsigned int metric; int carrier; int wireless; uint8_t ssid[IF_SSIDLEN + 1]; /* NULL terminated */ unsigned int ssid_len; char profile[PROFILE_LEN]; struct if_options *options; void *if_data[IF_DATA_MAX]; }; TAILQ_HEAD(if_head, interface); #ifdef INET6 /* 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 #define IP6BUFLEN (CMSG_SPACE(sizeof(struct in6_pktinfo)) + \ CMSG_SPACE(sizeof(int))) #endif struct dhcpcd_ctx { char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1]; 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 */ unsigned char *duid; size_t duid_len; struct if_head *ifaces; struct rt_head routes; /* our routes */ struct rt_head kroutes; /* all kernel routes */ struct rt_head froutes; /* free routes for re-use */ int pf_inet_fd; #ifdef IFLR_ACTIVE int pf_link_fd; #endif void *priv; int link_fd; int seq; /* route message sequence no */ int sseq; /* successful seq no sent */ struct iovec iov[1]; /* generic iovec buffer */ #ifdef USE_SIGNALS sigset_t sigset; #endif struct eloop *eloop; int control_fd; int control_unpriv_fd; struct fd_list_head control_fds; char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE]; gid_t control_group; /* DHCP Enterprise options, RFC3925 */ struct dhcp_opt *vivso; size_t vivso_len; char *randomstate; /* original state */ #ifdef INET struct dhcp_opt *dhcp_opts; size_t dhcp_opts_len; int udp_fd; /* 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; unsigned char ctlbuf[IP6BUFLEN]; struct sockaddr_in6 from; struct msghdr sndhdr; struct iovec sndiov[1]; unsigned char sndbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; struct msghdr rcvhdr; char ntopbuf[INET6_ADDRSTRLEN]; const char *sfrom; int nd_fd; struct ra_head *ra_routers; int dhcp6_fd; struct dhcp_opt *nd_opts; size_t nd_opts_len; struct dhcp_opt *dhcp6_opts; size_t dhcp6_opts_len; #ifndef __linux__ int ra_global; #endif #endif /* INET6 */ #ifdef PLUGIN_DEV char *dev_load; int dev_fd; struct dev *dev; void *dev_handle; #endif }; #ifdef USE_SIGNALS extern const int dhcpcd_signals[]; extern const size_t dhcpcd_signals_len; #endif int dhcpcd_ifafwaiting(const struct interface *); int dhcpcd_afwaiting(const struct dhcpcd_ctx *); pid_t dhcpcd_daemonise(struct dhcpcd_ctx *); void dhcpcd_linkoverflow(struct dhcpcd_ctx *); int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **); void dhcpcd_handlecarrier(struct dhcpcd_ctx *, int, unsigned int, const char *); int dhcpcd_handleinterface(void *, int, const char *); void dhcpcd_handlehwaddr(struct dhcpcd_ctx *, const char *, const 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 dhcpcd5-7.1.0/src/duid.c000066400000000000000000000132131342162717100147150ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 #define DUID_LLT 1 #define DUID_LL 3 #define DUID_UUID 4 #include #include #include #ifdef BSD # include #endif #include #include #include #include #include #include #include #include #include #include #ifndef ARPHRD_NETROM # define ARPHRD_NETROM 0 #endif #include "common.h" #include "dhcpcd.h" #include "duid.h" #include "logerr.h" static size_t duid_machineuuid(char *uuid, size_t uuid_len) { int r; size_t len = uuid_len; #if defined(HW_UUID) /* OpenBSD */ int mib[] = { CTL_HW, HW_UUID }; r = sysctl(mib, sizeof(mib)/sizeof(mib[0]), uuid, &len, NULL, 0); #elif defined(KERN_HOSTUUID) /* FreeBSD */ int mib[] = { CTL_KERN, KERN_HOSTUUID }; r = sysctl(mib, sizeof(mib)/sizeof(mib[0]), uuid, &len, NULL, 0); #elif defined(__NetBSD__) r = sysctlbyname("machdep.dmi.system-uuid", uuid, &len, NULL, 0); #elif defined(__linux__) FILE *fp; fp = fopen("/sys/class/dmi/id/product_uuid", "r"); if (fp == NULL) return 0; if (fgets(uuid, (int)uuid_len, fp) == NULL) { fclose(fp); return 0; } len = strlen(uuid) + 1; fclose(fp); r = len == 1 ? -1 : 0; #else 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; } static size_t duid_make(uint8_t *d, const struct interface *ifp, uint16_t type) { uint8_t *p; uint16_t u16; time_t t; uint32_t u32; p = d; u16 = htons(type); memcpy(p, &u16, 2); p += 2; u16 = htons(ifp->family); memcpy(p, &u16, 2); p += 2; if (type == DUID_LLT) { /* time returns seconds from jan 1 1970, but DUID-LLT is * seconds from jan 1 2000 modulo 2^32 */ t = time(NULL) - DUID_TIME_EPOCH; u32 = htonl((uint32_t)t & 0xffffffff); memcpy(p, &u32, 4); p += 4; } /* Finally, add the MAC address of the interface */ memcpy(p, ifp->hwaddr, ifp->hwlen); p += ifp->hwlen; return (size_t)(p - d); } #define DUID_STRLEN DUID_LEN * 3 static size_t duid_get(uint8_t **d, const struct interface *ifp) { FILE *fp; uint8_t *data; size_t len; int x = 0; char line[DUID_STRLEN]; const struct interface *ifp2; /* If we already have a DUID then use it as it's never supposed * to change once we have one even if the interfaces do */ if ((len = read_hwaddr_aton(&data, DUID)) != 0) { if (len <= DUID_LEN) { *d = 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; } } /* Regardless of what happens we will create a DUID to use. */ *d = data; /* No file? OK, lets make one based the machines UUID */ len = duid_make_uuid(data); if (len > 0) return len; /* No UUID? OK, lets make one based on our interface */ if (ifp->family == ARPHRD_NETROM) { logwarnx("%s: is a NET/ROM pseudo interface", ifp->name); TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { if (ifp2->family != ARPHRD_NETROM) break; } if (ifp2) { ifp = ifp2; logwarnx("picked interface %s to generate a DUID", ifp->name); } else { logwarnx("no interfaces have a fixed hardware " "address"); return duid_make(data, ifp, DUID_LL); } } if (!(fp = fopen(DUID, "w"))) { logerr("%s", DUID); return duid_make(data, ifp, DUID_LL); } len = duid_make(data, ifp, DUID_LLT); x = fprintf(fp, "%s\n", hwaddr_ntoa(data, len, line, sizeof(line))); if (fclose(fp) == EOF) x = -1; /* Failed to write the duid? scrub it, we cannot use it */ if (x < 1) { logerr("%s", DUID); unlink(DUID); return duid_make(data, ifp, DUID_LL); } return len; } size_t duid_init(const struct interface *ifp) { if (ifp->ctx->duid == NULL) ifp->ctx->duid_len = duid_get(&ifp->ctx->duid, ifp); return ifp->ctx->duid_len; } dhcpcd5-7.1.0/src/duid.h000066400000000000000000000027521342162717100147300ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2015 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef DUID_H #define DUID_H #define DUID_LEN 128 + 2 size_t duid_init(const struct interface *); #endif dhcpcd5-7.1.0/src/eloop.c000066400000000000000000000556401342162717100151200ustar00rootroot00000000000000/* * eloop - portable event based main loop. * Copyright (c) 2006-2019 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. */ #if (defined(__unix__) || defined(unix)) && !defined(USG) #include #endif #include #include #include #include #include #include #include #include #include /* config.h should define HAVE_KQUEUE, HAVE_EPOLL, etc. */ #if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H) #include "config.h" #endif /* Attempt to autodetect kqueue or epoll. * Failing that, fall back to pselect. */ #if !defined(HAVE_KQUEUE) && !defined(HAVE_EPOLL) && !defined(HAVE_PSELECT) && \ !defined(HAVE_POLLTS) && !defined(HAVE_PPOLL) #if defined(BSD) /* Assume BSD has a working sys/queue.h and kqueue(2) interface. */ #define HAVE_SYS_QUEUE_H #define HAVE_KQUEUE #define WARN_SELECT #elif defined(__linux__) || defined(__sun) /* Assume Linux and Solaris have a working epoll(3) interface. */ #define HAVE_EPOLL #define WARN_SELECT #else /* pselect(2) is a POSIX standard. */ #define HAVE_PSELECT #define WARN_SELECT #endif #endif /* pollts and ppoll require poll. * pselect is wrapped in a pollts/ppoll style interface * and as such require poll as well. */ #if defined(HAVE_PSELECT) || defined(HAVE_POLLTS) || defined(HAVE_PPOLL) #ifndef HAVE_POLL #define HAVE_POLL #endif #if defined(HAVE_POLLTS) #define POLLTS pollts #elif defined(HAVE_PPOLL) #define POLLTS ppoll #else #define POLLTS eloop_pollts #define ELOOP_NEED_POLLTS #endif #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 #ifndef MSEC_PER_SEC #define MSEC_PER_SEC 1000L #define NSEC_PER_MSEC 1000000L #endif #if defined(HAVE_KQUEUE) #include #include #ifdef __NetBSD__ /* udata is void * except on NetBSD. * lengths are int except on NetBSD. */ #define UPTR(x) ((intptr_t)(x)) #define LENC(x) (x) #else #define UPTR(x) (x) #define LENC(x) ((int)(x)) #endif #elif defined(HAVE_EPOLL) #include #elif defined(HAVE_POLL) #if defined(HAVE_PSELECT) #include #endif #include #endif #ifdef WARN_SELECT #if defined(HAVE_KQUEUE) #pragma message("Compiling eloop with kqueue(2) support.") #elif defined(HAVE_EPOLL) #pragma message("Compiling eloop with epoll(7) support.") #elif defined(HAVE_PSELECT) #pragma message("Compiling eloop with pselect(2) support.") #elif defined(HAVE_PPOLL) #pragma message("Compiling eloop with ppoll(2) support.") #elif defined(HAVE_POLLTS) #pragma message("Compiling eloop with pollts(2) support.") #else #error Unknown select mechanism for eloop #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 struct eloop_event { TAILQ_ENTRY(eloop_event) next; int fd; void (*read_cb)(void *); void *read_cb_arg; void (*write_cb)(void *); void *write_cb_arg; }; struct eloop_timeout { TAILQ_ENTRY(eloop_timeout) next; struct timespec when; void (*callback)(void *); void *arg; int queue; }; struct eloop { size_t events_len; TAILQ_HEAD (event_head, eloop_event) events; struct event_head free_events; int events_maxfd; struct eloop_event **event_fds; TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; struct timeout_head free_timeouts; void (*timeout0)(void *); void *timeout0_arg; const int *signals; size_t signals_len; void (*signal_cb)(int, void *); void *signal_cb_ctx; #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) int poll_fd; #elif defined(HAVE_POLL) struct pollfd *fds; size_t fds_len; #endif int exitnow; int exitcode; }; #ifdef HAVE_REALLOCARRAY #define eloop_realloca reallocarray #else /* Handy routing to check for potential overflow. * reallocarray(3) and reallocarr(3) are not portable. */ #define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2)) static void * eloop_realloca(void *ptr, size_t n, size_t size) { if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) { errno = EOVERFLOW; return NULL; } return realloc(ptr, n * size); } #endif #ifdef HAVE_POLL static void eloop_event_setup_fds(struct eloop *eloop) { struct eloop_event *e; size_t i; i = 0; TAILQ_FOREACH(e, &eloop->events, next) { eloop->fds[i].fd = e->fd; eloop->fds[i].events = 0; if (e->read_cb) eloop->fds[i].events |= POLLIN; if (e->write_cb) eloop->fds[i].events |= POLLOUT; eloop->fds[i].revents = 0; i++; } } #ifdef ELOOP_NEED_POLLTS /* Wrapper around pselect, to imitate the NetBSD pollts call. */ static int eloop_pollts(struct pollfd * fds, nfds_t nfds, const struct timespec *ts, const sigset_t *sigmask) { fd_set read_fds, write_fds; nfds_t n; int maxfd, r; FD_ZERO(&read_fds); FD_ZERO(&write_fds); maxfd = 0; for (n = 0; n < nfds; n++) { if (fds[n].events & POLLIN) { FD_SET(fds[n].fd, &read_fds); if (fds[n].fd > maxfd) maxfd = fds[n].fd; } if (fds[n].events & POLLOUT) { FD_SET(fds[n].fd, &write_fds); if (fds[n].fd > maxfd) maxfd = fds[n].fd; } } r = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask); if (r > 0) { for (n = 0; n < nfds; n++) { fds[n].revents = FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0; if (FD_ISSET(fds[n].fd, &write_fds)) fds[n].revents |= POLLOUT; } } return r; } #endif /* pollts */ #else /* !HAVE_POLL */ #define eloop_event_setup_fds(a) {} #endif /* HAVE_POLL */ int eloop_event_add_rw(struct eloop *eloop, int fd, void (*read_cb)(void *), void *read_cb_arg, void (*write_cb)(void *), void *write_cb_arg) { struct eloop_event *e; #if defined(HAVE_KQUEUE) struct kevent ke[2]; #elif defined(HAVE_EPOLL) struct epoll_event epe; #elif defined(HAVE_POLL) struct pollfd *nfds; #endif assert(eloop != NULL); assert(read_cb != NULL || write_cb != NULL); if (fd == -1) { errno = EINVAL; return -1; } #ifdef HAVE_EPOLL memset(&epe, 0, sizeof(epe)); epe.data.fd = fd; epe.events = EPOLLIN; if (write_cb) epe.events |= EPOLLOUT; #endif /* We should only have one callback monitoring the fd. */ if (fd <= eloop->events_maxfd) { if ((e = eloop->event_fds[fd]) != NULL) { int error; #if defined(HAVE_KQUEUE) EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, EV_ADD, 0, 0, UPTR(e)); if (write_cb) EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, EV_ADD, 0, 0, UPTR(e)); else if (e->write_cb) EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, EV_DELETE, 0, 0, UPTR(e)); error = kevent(eloop->poll_fd, ke, e->write_cb || write_cb ? 2 : 1, NULL, 0, NULL); #elif defined(HAVE_EPOLL) epe.data.ptr = e; error = epoll_ctl(eloop->poll_fd, EPOLL_CTL_MOD, fd, &epe); #else error = 0; #endif if (read_cb) { e->read_cb = read_cb; e->read_cb_arg = read_cb_arg; } if (write_cb) { e->write_cb = write_cb; e->write_cb_arg = write_cb_arg; } eloop_event_setup_fds(eloop); return error; } } else { struct eloop_event **new_fds; int maxfd, i; /* Reserve ourself and 4 more. */ maxfd = fd + 4; new_fds = eloop_realloca(eloop->event_fds, ((size_t)maxfd + 1), sizeof(*eloop->event_fds)); if (new_fds == NULL) return -1; /* set new entries NULL as the fd's may not be contiguous. */ for (i = maxfd; i > eloop->events_maxfd; i--) new_fds[i] = NULL; eloop->event_fds = new_fds; eloop->events_maxfd = maxfd; } /* Allocate a new event if no free ones already allocated. */ if ((e = TAILQ_FIRST(&eloop->free_events))) { TAILQ_REMOVE(&eloop->free_events, e, next); } else { e = malloc(sizeof(*e)); if (e == NULL) goto err; } /* Ensure we can actually listen to it. */ eloop->events_len++; #ifdef HAVE_POLL if (eloop->events_len > eloop->fds_len) { nfds = eloop_realloca(eloop->fds, (eloop->fds_len + 5), sizeof(*eloop->fds)); if (nfds == NULL) goto err; eloop->fds_len += 5; eloop->fds = nfds; } #endif /* Now populate the structure and add it to the list. */ e->fd = fd; e->read_cb = read_cb; e->read_cb_arg = read_cb_arg; e->write_cb = write_cb; e->write_cb_arg = write_cb_arg; #if defined(HAVE_KQUEUE) if (read_cb != NULL) EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, EV_ADD, 0, 0, UPTR(e)); if (write_cb != NULL) EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, EV_ADD, 0, 0, UPTR(e)); if (kevent(eloop->poll_fd, ke, write_cb ? 2 : 1, NULL, 0, NULL) == -1) goto err; #elif defined(HAVE_EPOLL) epe.data.ptr = e; if (epoll_ctl(eloop->poll_fd, EPOLL_CTL_ADD, fd, &epe) == -1) goto err; #endif TAILQ_INSERT_HEAD(&eloop->events, e, next); eloop->event_fds[e->fd] = e; eloop_event_setup_fds(eloop); return 0; err: if (e) { eloop->events_len--; TAILQ_INSERT_TAIL(&eloop->free_events, e, next); } return -1; } int eloop_event_add(struct eloop *eloop, int fd, void (*read_cb)(void *), void *read_cb_arg) { return eloop_event_add_rw(eloop, fd, read_cb, read_cb_arg, NULL, NULL); } int eloop_event_add_w(struct eloop *eloop, int fd, void (*write_cb)(void *), void *write_cb_arg) { return eloop_event_add_rw(eloop, fd, NULL,NULL, write_cb, write_cb_arg); } int eloop_event_delete_write(struct eloop *eloop, int fd, int write_only) { struct eloop_event *e; #if defined(HAVE_KQUEUE) struct kevent ke[2]; #elif defined(HAVE_EPOLL) struct epoll_event epe; #endif assert(eloop != NULL); if (fd > eloop->events_maxfd || (e = eloop->event_fds[fd]) == NULL) { errno = ENOENT; return -1; } if (write_only) { if (e->write_cb == NULL) return 0; if (e->read_cb == NULL) goto remove; e->write_cb = NULL; e->write_cb_arg = NULL; #if defined(HAVE_KQUEUE) EV_SET(&ke[0], (uintptr_t)e->fd, EVFILT_WRITE, EV_DELETE, 0, 0, UPTR(NULL)); kevent(eloop->poll_fd, ke, 1, NULL, 0, NULL); #elif defined(HAVE_EPOLL) memset(&epe, 0, sizeof(epe)); epe.data.fd = e->fd; epe.data.ptr = e; epe.events = EPOLLIN; epoll_ctl(eloop->poll_fd, EPOLL_CTL_MOD, fd, &epe); #endif eloop_event_setup_fds(eloop); return 1; } remove: TAILQ_REMOVE(&eloop->events, e, next); eloop->event_fds[e->fd] = NULL; TAILQ_INSERT_TAIL(&eloop->free_events, e, next); eloop->events_len--; #if defined(HAVE_KQUEUE) EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, EV_DELETE, 0, 0, UPTR(NULL)); if (e->write_cb) EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, EV_DELETE, 0, 0, UPTR(NULL)); kevent(eloop->poll_fd, ke, e->write_cb ? 2 : 1, NULL, 0, NULL); #elif defined(HAVE_EPOLL) /* NULL event is safe because we * rely on epoll_pwait which as added * after the delete without event was fixed. */ epoll_ctl(eloop->poll_fd, EPOLL_CTL_DEL, fd, NULL); #endif eloop_event_setup_fds(eloop); return 1; } int eloop_q_timeout_add_tv(struct eloop *eloop, int queue, const struct timespec *when, void (*callback)(void *), void *arg) { struct timespec now, w; struct eloop_timeout *t, *tt = NULL; assert(eloop != NULL); assert(when != NULL); assert(callback != NULL); clock_gettime(CLOCK_MONOTONIC, &now); timespecadd(&now, when, &w); /* Check for time_t overflow. */ if (timespeccmp(&w, &now, <)) { errno = ERANGE; return -1; } /* Remove existing timeout if present. */ TAILQ_FOREACH(t, &eloop->timeouts, next) { if (t->callback == callback && t->arg == arg) { TAILQ_REMOVE(&eloop->timeouts, t, next); break; } } if (t == NULL) { /* No existing, so allocate or grab one from the free pool. */ if ((t = TAILQ_FIRST(&eloop->free_timeouts))) { TAILQ_REMOVE(&eloop->free_timeouts, t, next); } else { if ((t = malloc(sizeof(*t))) == NULL) return -1; } } t->when = w; t->callback = callback; t->arg = arg; t->queue = queue; /* The timeout list should be in chronological order, * soonest first. */ TAILQ_FOREACH(tt, &eloop->timeouts, next) { if (timespeccmp(&t->when, &tt->when, <)) { TAILQ_INSERT_BEFORE(tt, t, next); return 0; } } TAILQ_INSERT_TAIL(&eloop->timeouts, t, next); return 0; } int eloop_q_timeout_add_sec(struct eloop *eloop, int queue, time_t when, void (*callback)(void *), void *arg) { struct timespec tv; tv.tv_sec = when; tv.tv_nsec = 0; return eloop_q_timeout_add_tv(eloop, queue, &tv, callback, arg); } int eloop_q_timeout_add_msec(struct eloop *eloop, int queue, long when, void (*callback)(void *), void *arg) { struct timespec tv; tv.tv_sec = when / MSEC_PER_SEC; tv.tv_nsec = (when % MSEC_PER_SEC) * NSEC_PER_MSEC; return eloop_q_timeout_add_tv(eloop, queue, &tv, callback, arg); } #if !defined(HAVE_KQUEUE) static int eloop_timeout_add_now(struct eloop *eloop, void (*callback)(void *), void *arg) { assert(eloop->timeout0 == NULL); eloop->timeout0 = callback; eloop->timeout0_arg = arg; return 0; } #endif int eloop_q_timeout_delete(struct eloop *eloop, int queue, void (*callback)(void *), void *arg) { struct eloop_timeout *t, *tt; int n; assert(eloop != NULL); n = 0; TAILQ_FOREACH_SAFE(t, &eloop->timeouts, next, tt) { if ((queue == 0 || t->queue == queue) && t->arg == arg && (!callback || t->callback == callback)) { TAILQ_REMOVE(&eloop->timeouts, t, next); TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); n++; } } return n; } void eloop_exit(struct eloop *eloop, int code) { assert(eloop != NULL); eloop->exitcode = code; eloop->exitnow = 1; } #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) static int eloop_open(struct eloop *eloop) { #if defined(HAVE_KQUEUE1) return (eloop->poll_fd = kqueue1(O_CLOEXEC)); #elif defined(HAVE_KQUEUE) int i; if ((eloop->poll_fd = kqueue()) == -1) return -1; if ((i = fcntl(eloop->poll_fd, F_GETFD, 0)) == -1 || fcntl(eloop->poll_fd, F_SETFD, i | FD_CLOEXEC) == -1) { close(eloop->poll_fd); eloop->poll_fd = -1; } return eloop->poll_fd; #elif defined (HAVE_EPOLL) return (eloop->poll_fd = epoll_create1(EPOLL_CLOEXEC)); #else return (eloop->poll_fd = -1); #endif } #endif int eloop_requeue(struct eloop *eloop) { #if defined(HAVE_POLL) UNUSED(eloop); return 0; #else /* !HAVE_POLL */ struct eloop_event *e; int error; #if defined(HAVE_KQUEUE) size_t i; struct kevent *ke; #elif defined(HAVE_EPOLL) struct epoll_event epe; #endif assert(eloop != NULL); if (eloop->poll_fd != -1) close(eloop->poll_fd); if (eloop_open(eloop) == -1) return -1; #if defined (HAVE_KQUEUE) i = eloop->signals_len; TAILQ_FOREACH(e, &eloop->events, next) { i++; if (e->write_cb) i++; } if ((ke = malloc(sizeof(*ke) * i)) == NULL) return -1; for (i = 0; i < eloop->signals_len; i++) EV_SET(&ke[i], (uintptr_t)eloop->signals[i], EVFILT_SIGNAL, EV_ADD, 0, 0, UPTR(NULL)); TAILQ_FOREACH(e, &eloop->events, next) { EV_SET(&ke[i], (uintptr_t)e->fd, EVFILT_READ, EV_ADD, 0, 0, UPTR(e)); i++; if (e->write_cb) { EV_SET(&ke[i], (uintptr_t)e->fd, EVFILT_WRITE, EV_ADD, 0, 0, UPTR(e)); i++; } } error = kevent(eloop->poll_fd, ke, LENC(i), NULL, 0, NULL); free(ke); #elif defined(HAVE_EPOLL) error = 0; TAILQ_FOREACH(e, &eloop->events, next) { memset(&epe, 0, sizeof(epe)); epe.data.fd = e->fd; epe.events = EPOLLIN; if (e->write_cb) epe.events |= EPOLLOUT; epe.data.ptr = e; if (epoll_ctl(eloop->poll_fd, EPOLL_CTL_ADD, e->fd, &epe) == -1) error = -1; } #endif return error; #endif /* HAVE_POLL */ } int eloop_signal_set_cb(struct eloop *eloop, const int *signals, size_t signals_len, void (*signal_cb)(int, void *), void *signal_cb_ctx) { assert(eloop != NULL); eloop->signals = signals; eloop->signals_len = signals_len; eloop->signal_cb = signal_cb; eloop->signal_cb_ctx = signal_cb_ctx; return eloop_requeue(eloop); } #ifndef HAVE_KQUEUE struct eloop_siginfo { int sig; struct eloop *eloop; }; static struct eloop_siginfo _eloop_siginfo; static struct eloop *_eloop; static void eloop_signal1(void *arg) { struct eloop_siginfo *si = arg; si->eloop->signal_cb(si->sig, si->eloop->signal_cb_ctx); } static void eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg) { /* So that we can operate safely under a signal we instruct * eloop to pass a copy of the siginfo structure to handle_signal1 * as the very first thing to do. */ _eloop_siginfo.eloop = _eloop; _eloop_siginfo.sig = sig; eloop_timeout_add_now(_eloop_siginfo.eloop, eloop_signal1, &_eloop_siginfo); } #endif int eloop_signal_mask(struct eloop *eloop, sigset_t *oldset) { sigset_t newset; size_t i; #ifndef HAVE_KQUEUE struct sigaction sa; #endif assert(eloop != NULL); sigemptyset(&newset); for (i = 0; i < eloop->signals_len; i++) sigaddset(&newset, eloop->signals[i]); if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) return -1; #ifndef HAVE_KQUEUE _eloop = eloop; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = eloop_signal3; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); for (i = 0; i < eloop->signals_len; i++) { if (sigaction(eloop->signals[i], &sa, NULL) == -1) return -1; } #endif return 0; } struct eloop * eloop_new(void) { struct eloop *eloop; struct timespec now; /* Check we have a working monotonic clock. */ if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) return NULL; eloop = calloc(1, sizeof(*eloop)); if (eloop) { TAILQ_INIT(&eloop->events); eloop->events_maxfd = -1; 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_free(struct eloop *eloop) { struct eloop_event *e; struct eloop_timeout *t; if (eloop == NULL) return; free(eloop->event_fds); while ((e = TAILQ_FIRST(&eloop->events))) { TAILQ_REMOVE(&eloop->events, e, next); free(e); } while ((e = TAILQ_FIRST(&eloop->free_events))) { TAILQ_REMOVE(&eloop->free_events, e, next); free(e); } while ((t = TAILQ_FIRST(&eloop->timeouts))) { TAILQ_REMOVE(&eloop->timeouts, t, next); free(t); } while ((t = TAILQ_FIRST(&eloop->free_timeouts))) { TAILQ_REMOVE(&eloop->free_timeouts, t, next); free(t); } #if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) close(eloop->poll_fd); #elif defined(HAVE_POLL) free(eloop->fds); #endif free(eloop); } int eloop_start(struct eloop *eloop, sigset_t *signals) { int n; struct eloop_event *e; struct eloop_timeout *t; struct timespec now, ts, *tsp; void (*t0)(void *); #if defined(HAVE_KQUEUE) struct kevent ke; UNUSED(signals); #elif defined(HAVE_EPOLL) struct epoll_event epe; #endif #ifndef HAVE_KQUEUE int timeout; #endif assert(eloop != NULL); eloop->exitnow = 0; for (;;) { if (eloop->exitnow) break; /* Run all timeouts first. */ if (eloop->timeout0) { t0 = eloop->timeout0; eloop->timeout0 = NULL; t0(eloop->timeout0_arg); continue; } if ((t = TAILQ_FIRST(&eloop->timeouts))) { clock_gettime(CLOCK_MONOTONIC, &now); if (timespeccmp(&now, &t->when, >)) { TAILQ_REMOVE(&eloop->timeouts, t, next); t->callback(t->arg); TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); continue; } timespecsub(&t->when, &now, &ts); tsp = &ts; } else /* No timeouts, so wait forever. */ tsp = NULL; if (tsp == NULL && eloop->events_len == 0) break; #ifndef HAVE_KQUEUE if (tsp == NULL) timeout = -1; else if (tsp->tv_sec > INT_MAX / 1000 || (tsp->tv_sec == INT_MAX / 1000 && (tsp->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000)) timeout = INT_MAX; else timeout = (int)(tsp->tv_sec * 1000 + (tsp->tv_nsec + 999999) / 1000000); #endif #if defined(HAVE_KQUEUE) n = kevent(eloop->poll_fd, NULL, 0, &ke, 1, tsp); #elif defined(HAVE_EPOLL) if (signals) n = epoll_pwait(eloop->poll_fd, &epe, 1, timeout, signals); else n = epoll_wait(eloop->poll_fd, &epe, 1, timeout); #elif defined(HAVE_POLL) if (signals) n = POLLTS(eloop->fds, (nfds_t)eloop->events_len, tsp, signals); else n = poll(eloop->fds, (nfds_t)eloop->events_len, timeout); #endif if (n == -1) { if (errno == EINTR) continue; return -errno; } /* Process any triggered events. * We go back to the start after calling each callback incase * the current event or next event is removed. */ #if defined(HAVE_KQUEUE) if (n) { if (ke.filter == EVFILT_SIGNAL) { eloop->signal_cb((int)ke.ident, eloop->signal_cb_ctx); continue; } e = (struct eloop_event *)ke.udata; if (ke.filter == EVFILT_WRITE) { e->write_cb(e->write_cb_arg); continue; } else if (ke.filter == EVFILT_READ) { e->read_cb(e->read_cb_arg); continue; } } #elif defined(HAVE_EPOLL) if (n) { e = (struct eloop_event *)epe.data.ptr; if (epe.events & EPOLLOUT && e->write_cb != NULL) { e->write_cb(e->write_cb_arg); continue; } if (epe.events & (EPOLLIN | EPOLLERR | EPOLLHUP) && e->read_cb != NULL) { e->read_cb(e->read_cb_arg); continue; } } #elif defined(HAVE_POLL) if (n > 0) { size_t i; for (i = 0; i < eloop->events_len; i++) { if (eloop->fds[i].revents & POLLOUT) { e = eloop->event_fds[eloop->fds[i].fd]; if (e->write_cb != NULL) { e->write_cb(e->write_cb_arg); break; } } if (eloop->fds[i].revents) { e = eloop->event_fds[eloop->fds[i].fd]; if (e->read_cb != NULL) { e->read_cb(e->read_cb_arg); break; } } } } #endif } return eloop->exitcode; } dhcpcd5-7.1.0/src/eloop.h000066400000000000000000000121141342162717100151120ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 /* Some systems don't define timespec macros */ #ifndef timespecclear #define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L) #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) #define timespeccmp(tsp, usp, cmp) \ (((tsp)->tv_sec == (usp)->tv_sec) ? \ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ ((tsp)->tv_sec cmp (usp)->tv_sec)) #define timespecadd(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ if ((vsp)->tv_nsec >= 1000000000L) { \ (vsp)->tv_sec++; \ (vsp)->tv_nsec -= 1000000000L; \ } \ } while (/* CONSTCOND */ 0) #define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (/* CONSTCOND */ 0) #endif /* eloop queues are really only for deleting timeouts registered * for a function or object. * The idea being that one interface has different timeouts for * say DHCP and DHCPv6. */ #ifndef ELOOP_QUEUE #define ELOOP_QUEUE 1 #endif /* Forward declare eloop - the content should be invisible to the outside */ struct eloop; int eloop_event_add_rw(struct eloop *, int, void (*)(void *), void *, void (*)(void *), void *); int eloop_event_add(struct eloop *, int, void (*)(void *), void *); int eloop_event_add_w(struct eloop *, int, void (*)(void *), void *); #define eloop_event_delete(eloop, fd) \ eloop_event_delete_write((eloop), (fd), 0) #define eloop_event_remove_writecb(eloop, fd) \ eloop_event_delete_write((eloop), (fd), 1) int eloop_event_delete_write(struct eloop *, int, int); #define eloop_timeout_add_tv(eloop, tv, cb, ctx) \ eloop_q_timeout_add_tv((eloop), ELOOP_QUEUE, (tv), (cb), (ctx)) #define eloop_timeout_add_sec(eloop, tv, cb, ctx) \ eloop_q_timeout_add_sec((eloop), ELOOP_QUEUE, (tv), (cb), (ctx)) #define eloop_timeout_add_msec(eloop, ms, cb, ctx) \ eloop_q_timeout_add_msec((eloop), ELOOP_QUEUE, (ms), (cb), (ctx)) #define eloop_timeout_delete(eloop, cb, ctx) \ eloop_q_timeout_delete((eloop), ELOOP_QUEUE, (cb), (ctx)) int eloop_q_timeout_add_tv(struct eloop *, int, const struct timespec *, void (*)(void *), void *); int eloop_q_timeout_add_sec(struct eloop *, int, time_t, void (*)(void *), void *); int eloop_q_timeout_add_msec(struct eloop *, int, 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); int eloop_requeue(struct eloop *); void eloop_free(struct eloop *); void eloop_exit(struct eloop *, int); int eloop_start(struct eloop *, sigset_t *); #endif dhcpcd5-7.1.0/src/genembedc000077500000000000000000000004401342162717100154610ustar00rootroot00000000000000#!/bin/sh set -e : ${TOOL_CAT:=cat} : ${TOOL_SED:=sed} CONF=${1:-dhcpcd-definitions.conf} C=${2:-dhcpcd-embedded.c.in} $TOOL_CAT $C $TOOL_SED \ -e 's/#.*$//' \ -e '/^$/d' \ -e 's/^/"/g' \ -e 's/$/\",/g' \ -e 's/ [ ]*/ /g' \ -e 's/ [ ]*/ /g' \ $CONF printf "%s\n%s\n" "NULL" "};" dhcpcd5-7.1.0/src/genembedh000077500000000000000000000007051342162717100154720ustar00rootroot00000000000000#!/bin/sh set -e : ${TOOL_SED:=sed} : ${TOOL_GREP:=grep} : ${TOOL_WC:=wc} CONF=${1:-dhcpcd-definitions.conf} H=${2:-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) $TOOL_SED \ -e "s/@INITDEFINES@/$INITDEFINES/" \ -e "s/@INITDEFINENDS@/$INITDEFINENDS/" \ -e "s/@INITDEFINE6S@/$INITDEFINE6S/" \ $H dhcpcd5-7.1.0/src/if-bsd.c000066400000000000000000001117451342162717100151450ustar00rootroot00000000000000/* * BSD interface driver for dhcpcd * Copyright (c) 2006-2019 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 */ #else #include #endif #ifdef __DragonFly__ # include #elif __APPLE__ /* FIXME: Add apple includes so we can work out SSID */ #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 "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 #ifdef INET6 static void ifa_scope(struct sockaddr_in6 *, unsigned int); #endif struct priv { int pf_inet6_fd; }; 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, #ifdef RTM_CHGADDR RTM_CHGADDR, #endif RTM_NEWADDR, RTM_DELADDR }; #ifdef ROUTE_MSGFILTER unsigned int i, msgfilter_mask; #endif #endif if ((priv = malloc(sizeof(*priv))) == NULL) return -1; ctx->priv = priv; #ifdef INET6 priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); /* Don't return an error so we at least work on kernels witout INET6 * even though we expect INET6 support. * We will fail noisily elsewhere anyway. */ #else priv->pf_inet6_fd = -1; #endif #define SOCK_FLAGS (SOCK_CLOEXEC | SOCK_NONBLOCK) ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_FLAGS, AF_UNSPEC); #undef SOCK_FLAGS if (ctx->link_fd == -1) 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__); #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__); #endif return 0; } void if_closesockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; priv = (struct priv *)ctx->priv; if (priv->pf_inet6_fd != -1) close(priv->pf_inet6_fd); } #if defined(INET) || defined(INET6) static void if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp) { memset(sdl, 0, sizeof(*sdl)); sdl->sdl_family = AF_LINK; sdl->sdl_len = sizeof(*sdl); sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0; sdl->sdl_index = (unsigned short)ifp->index; } #endif #if defined(SIOCG80211NWID) || defined(SIOCGETVLAN) static int if_direct_ioctl(int s, const char *ifname, unsigned long cmd, void *data) { strlcpy(data, ifname, IFNAMSIZ); return ioctl(s, cmd, data); } static int if_indirect_ioctl(int s, const char *ifname, unsigned long cmd, void *data) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_data = data; return if_direct_ioctl(s, ifname, cmd, &ifr); } #endif static int if_getssid1(int s, 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(s, ifname, SIOCG80211NWID, &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(s, 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->pf_inet_fd, 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(const struct dhcpcd_ctx *ctx, const char *ifname) { int r; struct ifmediareq ifmr; memset(&ifmr, 0, sizeof(ifmr)); strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); r = ioctl(ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr); if (r == -1) return -1; if (ifmr.ifm_status & IFM_AVALID && IFM_TYPE(ifmr.ifm_active) == IFM_IEEE80211) { if (if_getssid1(ctx->pf_inet_fd, ifname, NULL) == -1) return 1; } return 0; } unsigned short if_vlanid(const struct interface *ifp) { #ifdef SIOCGETVLAN struct vlanreq vlr; memset(&vlr, 0, sizeof(vlr)); if (if_indirect_ioctl(ifp->ctx->pf_inet_fd, ifp->name, SIOCGETVLAN, &vlr) != 0) return 0; /* 0 means no VLANID */ return vlr.vlr_tag; #elif defined(SIOCGVNETID) struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); 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 void get_addrs(int type, const void *data, const struct sockaddr **sa) { const char *cp; int i; cp = data; for (i = 0; i < RTAX_MAX; i++) { if (type & (1 << i)) { sa[i] = (const struct sockaddr *)cp; RT_ADVANCE(cp, sa[i]); } else sa[i] = NULL; } } #if defined(INET) || defined(INET6) static struct interface * if_findsdl(struct dhcpcd_ctx *ctx, 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; 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_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 { struct rt_msghdr hdr; char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; } rtmsg; struct rt_msghdr *rtm = &rtmsg.hdr; char *bp = rtmsg.buffer; struct sockaddr_dl sdl; bool gateway_unspec; assert(rt != 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; #ifdef __OpenBSD__ 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; /* Emulate the kernel by marking address generated * network routes non-static. */ if (!(rt->rt_dflags & RTDF_IFA_ROUTE)) 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) ifa_scope(&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); if (write(ctx->link_fd, rtm, rtm->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 | RTA_GATEWAY)) return -1; #ifdef RTF_CLONED if (rtm->rtm_flags & RTF_CLONED) return -1; #endif #ifdef RTF_LOCAL if (rtm->rtm_flags & RTF_LOCAL) return -1; #endif #ifdef RTF_BROADCAST if (rtm->rtm_flags & RTF_BROADCAST) return -1; #endif get_addrs(rtm->rtm_addrs, rtm + 1, rti_info); 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. */ if (rt->rt_flags & RTF_GATEWAY && rti_info[RTAX_GATEWAY]->sa_family != AF_LINK) 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) { errno = ESRCH; return -1; } return 0; } int if_initrt(struct dhcpcd_ctx *ctx, int af) { struct rt_msghdr *rtm; int mib[6]; size_t needed; char *buf, *p, *end; struct rt rt; rt_headclear(&ctx->kroutes, af); mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = af; mib[4] = NET_RT_DUMP; mib[5] = 0; if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1) return -1; if (needed == 0) return 0; if ((buf = malloc(needed)) == NULL) return -1; if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1) { free(buf); return -1; } end = buf + needed; for (p = buf; p < end; p += rtm->rtm_msglen) { rtm = (void *)p; if (if_copyrt(ctx, &rt, rtm) == 0) { rt.rt_dflags |= RTDF_INIT; rt_recvrt(RTM_ADD, &rt); } } free(buf); return 0; } #endif #ifdef INET int if_address(unsigned char cmd, const struct ipv4_addr *ia) { int r; struct in_aliasreq ifra; 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 = ioctl(ia->iface->ctx->pf_inet_fd, cmd == RTM_DELADDR ? SIOCDIFADDR : SIOCAIFADDR, &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 void ifa_scope(struct sockaddr_in6 *sin, unsigned int ifindex) { #ifdef __KAME__ /* KAME based systems want to store the scope inside the sin6_addr * for link local 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 } int if_address6(unsigned char cmd, const struct ipv6_addr *ia) { struct in6_aliasreq ifa; struct in6_addr mask; struct priv *priv; priv = (struct priv *)ia->iface->ctx->priv; memset(&ifa, 0, sizeof(ifa)); strlcpy(ifa.ifra_name, ia->iface->name, sizeof(ifa.ifra_name)); /* * We should not set IN6_IFF_TENTATIVE as the kernel should be * able to work out if it's a new address or not. * * We should set IN6_IFF_AUTOCONF, but the kernel won't let us. * This is probably a safety measure, but still it's not entirely right * either. */ #if 0 if (ia->autoconf) ifa.ifra_flags |= IN6_IFF_AUTOCONF; #endif #if defined(__FreeBSD__) || defined(__DragonFly__) if (ia->addr_flags & IN6_IFF_TENTATIVE) ifa.ifra_flags |= IN6_IFF_TENTATIVE; #endif #ifdef IPV6_MANGETEMPADDR if (ia->flags & IPV6_AF_TEMPORARY) ifa.ifra_flags |= IN6_IFF_TEMPORARY; #endif #define ADDADDR(v, addr) { \ (v)->sin6_family = AF_INET6; \ (v)->sin6_len = sizeof(*v); \ (v)->sin6_addr = *(addr); \ } ADDADDR(&ifa.ifra_addr, &ia->addr); ifa_scope(&ifa.ifra_addr, ia->iface->index); ipv6_mask(&mask, ia->prefix_len); ADDADDR(&ifa.ifra_prefixmask, &mask); #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)ioctl(priv->pf_inet6_fd, SIOCAIFADDR_IN6, &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 ioctl(priv->pf_inet6_fd, cmd == RTM_DELADDR ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &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; ifa_scope(&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; ifa_scope(&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; t = time(NULL); lifetime = &ifr6.ifr_ifru.ifru_lifetime; if (lifetime->ia6t_preferred) ia->prefix_pltime = (uint32_t)(lifetime->ia6t_preferred - MIN(t, lifetime->ia6t_preferred)); else ia->prefix_pltime = ND6_INFINITE_LIFETIME; if (lifetime->ia6t_expire) { ia->prefix_vltime = (uint32_t)(lifetime->ia6t_expire - MIN(t, lifetime->ia6t_expire)); /* Calculate the created time */ clock_gettime(CLOCK_MONOTONIC, &ia->created); ia->created.tv_sec -= lifetime->ia6t_vltime - ia->prefix_vltime; } else ia->prefix_vltime = ND6_INFINITE_LIFETIME; return 0; } #endif static void if_announce(struct dhcpcd_ctx *ctx, const struct if_announcemsghdr *ifan) { switch(ifan->ifan_what) { case IFAN_ARRIVAL: dhcpcd_handleinterface(ctx, 1, ifan->ifan_name); break; case IFAN_DEPARTURE: dhcpcd_handleinterface(ctx, -1, ifan->ifan_name); break; } } static void if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm) { struct interface *ifp; int link_state; if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL) return; /* If we get LINK_STATE_UNKNOWN here, it means the interface * doesn't support reporting carrier state. * As such, we need to rely on IFF_UP. * Even if LINK_STATE_UP is reported, we also need IFF_UP as well * so for dhcpcd they are equivalent and we only need to check * LINK_STATE_DOWN. */ if (ifm->ifm_data.ifi_link_state == LINK_STATE_DOWN) link_state = LINK_DOWN; else link_state = ifm->ifm_flags & IFF_UP ? LINK_UP : LINK_DOWN; dhcpcd_handlecarrier(ctx, link_state, (unsigned int)ifm->ifm_flags, ifp->name); } static void if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) { struct rt rt; /* Ignore errors. */ if (rtm->rtm_errno != 0) return; if (if_copyrt(ctx, &rt, rtm) == -1) return; #ifdef INET6 /* * BSD announces host routes. * As such, we should be notified of reachability by its * existance with a hardware address. */ if (rt.rt_dest.sa_family == AF_INET6 && rt.rt_flags & RTF_HOST) { struct sockaddr_in6 dest; struct sockaddr_dl sdl; memcpy(&dest, &rt.rt_dest, rt.rt_dest.sa_len); if (rt.rt_gateway.sa_family == AF_LINK) memcpy(&sdl, &rt.rt_gateway, rt.rt_gateway.sa_len); else sdl.sdl_alen = 0; ipv6nd_neighbour(ctx, &dest.sin6_addr, rtm->rtm_type != RTM_DELETE && sdl.sdl_alen ? IPV6ND_REACHABLE : 0); } #endif rt_recvrt(rtm->rtm_type, &rt); } static void if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam) { struct interface *ifp; const struct sockaddr *rti_info[RTAX_MAX]; int addrflags; pid_t pid; if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL) return; get_addrs(ifam->ifam_addrs, ifam + 1, rti_info); if (rti_info[RTAX_IFA] == NULL) return; #ifdef HAVE_IFAM_PID pid = ifam->ifam_pid; #else pid = 0; #endif #ifdef HAVE_IFAM_ADDRFLAGS addrflags = ifam->ifam_addrflags; #endif 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(ctx, ifp->name, 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; #if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000 /* * NetBSD-7 and older send an invalid broadcast address. * So we need to query the actual address to get * the right one. */ { #else /* * 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. */ if (ifam->ifam_type == RTM_DELADDR) { #endif #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 != EADDRNOTAVAIL) logerr("%s: SIOCGIFALIAS", __func__); if (ifam->ifam_type != RTM_DELADDR) break; } #if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000 else 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]; getifaddrs(&ifaddrs); 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; } freeifaddrs(ifaddrs); if (ifa != NULL) return; #endif } #ifndef HAVE_IFAM_ADDRFLAGS if (ifam->ifam_type == RTM_DELADDR) addrflags = 0 ; else if ((addrflags = if_addrflags(ifp, &addr, NULL)) == -1) { if (errno != EADDRNOTAVAIL) logerr("%s: if_addrflags", __func__); break; } #endif ipv4_handleifa(ctx, ifam->ifam_type, NULL, ifp->name, &addr, &mask, &bcast, addrflags, pid); break; } #endif #ifdef INET6 case AF_INET6: { struct in6_addr addr6, mask6; const struct sockaddr_in6 *sin6; int flags; 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. */ if (ifam->ifam_type == RTM_DELADDR) { flags = if_addrflags6(ifp, &addr6, NULL); if (flags != -1) break; addrflags = 0; } #ifndef HAVE_IFAM_ADDRFLAGS else if ((addrflags = if_addrflags6(ifp, &addr6, NULL)) == -1) { if (errno != EADDRNOTAVAIL) logerr("%s: if_addrflags6", __func__); break; } #endif #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), addrflags, pid); break; } #endif } } static void if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) { if (rtm->rtm_version != RTM_VERSION) return; switch(rtm->rtm_type) { #ifdef RTM_IFANNOUNCE case RTM_IFANNOUNCE: if_announce(ctx, (const void *)rtm); break; #endif case RTM_IFINFO: if_ifinfo(ctx, (const void *)rtm); break; case RTM_ADD: /* FALLTHROUGH */ case RTM_CHANGE: /* FALLTHROUGH */ case RTM_DELETE: if_rtm(ctx, (const void *)rtm); break; #ifdef RTM_CHGADDR case RTM_CHGADDR: /* FALLTHROUGH */ #endif case RTM_DELADDR: /* FALLTHROUGH */ case RTM_NEWADDR: if_ifa(ctx, (const void *)rtm); break; #ifdef RTM_DESYNC case RTM_DESYNC: dhcpcd_linkoverflow(ctx); break; #endif } } int if_handlelink(struct dhcpcd_ctx *ctx) { struct msghdr msg; ssize_t len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = ctx->iov; msg.msg_iovlen = 1; len = recvmsg_realloc(ctx->link_fd, &msg, 0); if (len == -1) return -1; if (len != 0) if_dispatch(ctx, ctx->iov[0].iov_base); return 0; } #ifndef SYS_NMLN /* OSX */ # define SYS_NMLN 256 #endif #ifndef HW_MACHINE_ARCH # ifdef HW_MODEL /* OpenBSD */ # define HW_MACHINE_ARCH HW_MODEL # endif #endif int if_machinearch(char *str, size_t len) { int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; char march[SYS_NMLN]; size_t marchlen = sizeof(march); if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), march, &marchlen, NULL, 0) != 0) return -1; return snprintf(str, len, ":%s", march); } #ifdef INET6 #if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \ defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME) || \ defined(IPV6CTL_FORWARDING) #define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0) #define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1) static int inet6_sysctl(int code, int val, int action) { int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 }; size_t size; mib[3] = code; size = sizeof(val); if (action) { if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), NULL, 0, &val, size) == -1) return -1; return 0; } if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &val, &size, NULL, 0) == -1) return -1; return val; } #endif #ifdef IPV6_MANAGETEMPADDR #ifndef IPV6CTL_TEMPVLTIME #define get_inet6_sysctlbyname(code) inet6_sysctlbyname(code, 0, 0) #define set_inet6_sysctlbyname(code, val) inet6_sysctlbyname(code, val, 1) static int inet6_sysctlbyname(const char *name, int val, int action) { size_t size; size = sizeof(val); if (action) { if (sysctlbyname(name, NULL, 0, &val, size) == -1) return -1; return 0; } if (sysctlbyname(name, &val, &size, NULL, 0) == -1) return -1; return val; } #endif int ip6_use_tempaddr(__unused const char *ifname) { int val; #ifdef IPV6CTL_USETEMPADDR val = get_inet6_sysctl(IPV6CTL_USETEMPADDR); #else val = get_inet6_sysctlbyname("net.inet6.ip6.use_tempaddr"); #endif return val == -1 ? 0 : val; } int ip6_temp_preferred_lifetime(__unused const char *ifname) { int val; #ifdef IPV6CTL_TEMPPLTIME val = get_inet6_sysctl(IPV6CTL_TEMPPLTIME); #else val = get_inet6_sysctlbyname("net.inet6.ip6.temppltime"); #endif return val < 0 ? TEMP_PREFERRED_LIFETIME : val; } int ip6_temp_valid_lifetime(__unused const char *ifname) { int val; #ifdef IPV6CTL_TEMPVLTIME val = get_inet6_sysctl(IPV6CTL_TEMPVLTIME); #else val = get_inet6_sysctlbyname("net.inet6.ip6.tempvltime"); #endif return val < 0 ? TEMP_VALID_LIFETIME : val; } #endif int ip6_forwarding(__unused const char *ifname) { int val; #ifdef IPV6CTL_FORWARDING val = get_inet6_sysctl(IPV6CTL_FORWARDING); #else val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding"); #endif return val < 0 ? 0 : val; } #ifdef SIOCIFAFATTACH static int af_attach(int s, const struct interface *ifp, int af) { struct if_afreq ifar; strlcpy(ifar.ifar_name, ifp->name, sizeof(ifar.ifar_name)); ifar.ifar_af = af; return ioctl(s, SIOCIFAFATTACH, (void *)&ifar); } #endif #ifdef SIOCGIFXFLAGS static int set_ifxflags(int s, const struct interface *ifp) { struct ifreq ifr; int flags; strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFXFLAGS, (void *)&ifr) == -1) return -1; flags = ifr.ifr_flags; #ifdef IFXF_NOINET6 flags &= ~IFXF_NOINET6; #endif /* * If 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 ioctl(s, SIOCSIFXFLAGS, (void *)&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_setup_inet6(const struct interface *ifp) { struct priv *priv; int s; #ifdef ND6_NDI_FLAGS struct in6_ndireq nd; int flags; #endif priv = (struct priv *)ifp->ctx->priv; s = priv->pf_inet6_fd; #ifdef ND6_NDI_FLAGS memset(&nd, 0, sizeof(nd)); strlcpy(nd.ifname, ifp->name, sizeof(nd.ifname)); if (ioctl(s, SIOCGIFINFO_IN6, &nd) == -1) logerr("%s: SIOCGIFINFO_FLAGS", ifp->name); flags = (int)nd.ndi.flags; #endif #ifdef ND6_IFF_AUTO_LINKLOCAL /* Unlike the kernel, * dhcpcd make make a stable private address. */ flags &= ~ND6_IFF_AUTO_LINKLOCAL; #endif #ifdef ND6_IFF_PERFORMNUD /* NUD is kind of essential. */ flags |= ND6_IFF_PERFORMNUD; #endif #ifdef ND6_IFF_IFDISABLED /* Ensure the interface is not disabled. */ flags &= ~ND6_IFF_IFDISABLED; #endif /* * If not doing autoconf, don't disable the kernel from doing it. * If we need to, we should have another option actively disable it. */ #ifdef ND6_IFF_ACCEPT_RTADV if (ifp->options->options & DHCPCD_IPV6RS) flags &= ~ND6_IFF_ACCEPT_RTADV; #ifdef ND6_IFF_OVERRIDE_RTADV if (ifp->options->options & DHCPCD_IPV6RS) flags |= ND6_IFF_OVERRIDE_RTADV; #endif #endif #ifdef ND6_NDI_FLAGS if (nd.ndi.flags != (uint32_t)flags) { nd.ndi.flags = (uint32_t)flags; if (ioctl(s, SIOCSIFINFO_FLAGS, &nd) == -1) logerr("%s: SIOCSIFINFO_FLAGS", ifp->name); } #endif /* Enabling IPv6 by whatever means must be the * last action undertaken to ensure kernel RS and * LLADDR auto configuration are disabled where applicable. */ #ifdef SIOCIFAFATTACH if (af_attach(s, ifp, AF_INET6) == -1) logerr("%s: af_attach", ifp->name); #endif #ifdef SIOCGIFXFLAGS if (set_ifxflags(s, ifp) == -1) logerr("%s: set_ifxflags", ifp->name); #endif #if defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV) /* If we cannot control ra per interface, disable it globally. */ if (ifp->options->options & DHCPCD_IPV6RS) { 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 #if defined(IPV6CTL_ACCEPT_RTADV) || defined(ND6_IFF_ACCEPT_RTADV) /* 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 (ioctl(s, SIOCSRTRFLUSH_IN6, &ifr) == -1 && errno != ENOTSUP) logwarn("SIOCSRTRFLUSH_IN6"); if (ioctl(s, SIOCSPFXFLUSH_IN6, &ifr) == -1 && errno != ENOTSUP) logwarn("SIOCSPFXFLUSH_IN6"); } #endif } #endif dhcpcd5-7.1.0/src/if-linux-wext.c000066400000000000000000000056361342162717100165220ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2009-2019 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * THIS IS A NASTY HACK THAT SHOULD NEVER HAVE HAPPENED * Basically we cannot include linux/if.h and net/if.h because * they have conflicting structures. * Sadly, linux/wireless.h includes linux/if.h all the time. * Some kernel-header installs fix this and some do not. * This file solely exists for those who do not. * * We *could* include wireless.h as that is designed for userspace, * but that then depends on the correct version of wireless-tools being * installed which isn't always the case. */ #include #include #include #include /* Support older kernels */ #ifdef IFLA_WIRELESS # include # include #else # define IFLA_WIRELESS (IFLA_MASTER + 1) #endif #include #include #include "config.h" /* We can't include if.h or dhcpcd.h because * they would pull in net/if.h, which defeats the purpose of this hack. */ #define IF_SSIDLEN 32 int if_getssid_wext(const char *ifname, uint8_t *ssid); int if_getssid_wext(const char *ifname, uint8_t *ssid) { #ifdef SIOCGIWESSID int s, retval; struct iwreq iwr; if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) return -1; memset(&iwr, 0, sizeof(iwr)); strlcpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name)); iwr.u.essid.pointer = ssid; iwr.u.essid.length = IF_SSIDLEN; if (ioctl(s, SIOCGIWESSID, &iwr) == 0) retval = iwr.u.essid.length; else retval = -1; close(s); return retval; #else /* Stop gcc warning about unused parameters */ ifname = ssid; return -1; #endif } dhcpcd5-7.1.0/src/if-linux.c000066400000000000000000001212321342162717100155240ustar00rootroot00000000000000/* * Linux interface driver for dhcpcd * Copyright (c) 2006-2019 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 #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 "route.h" #include "sa.h" #ifdef HAVE_NL80211_H #include #include #else int if_getssid_wext(const char *ifname, uint8_t *ssid); #endif /* Support older kernels */ #ifndef IFLA_WIRELESS #define IFLA_WIRELESS (IFLA_MASTER + 1) #endif /* For some reason, glibc doesn't include newer flags from linux/if.h * However, we cannot include linux/if.h directly as it conflicts * with the glibc version. D'oh! */ #ifndef IFF_LOWER_UP #define IFF_LOWER_UP 0x10000 /* driver signals L1 up */ #endif #define bpf_insn sock_filter #define BPF_SKIPTYPE #define BPF_ETHCOOK -ETH_HLEN #define BPF_WHOLEPACKET 0x0fffffff /* work around buggy LPF filters */ struct priv { int route_fd; uint32_t route_pid; struct iovec sndrcv_iov[1]; }; /* 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 #define PROC_INET6 "/proc/net/if_inet6" #define PROC_PROMOTE "/proc/sys/net/ipv4/conf/%s/promote_secondaries" #define SYS_LAYER2 "/sys/class/net/%s/device/layer2" static const char *mproc = #if defined(__alpha__) "system type" #elif defined(__arm__) "Hardware" #elif defined(__avr32__) "cpu family" #elif defined(__bfin__) "BOARD Name" #elif defined(__cris__) "cpu model" #elif defined(__frv__) "System" #elif defined(__i386__) || defined(__x86_64__) "vendor_id" #elif defined(__ia64__) "vendor" #elif defined(__hppa__) "model" #elif defined(__m68k__) "MMU" #elif defined(__mips__) "system type" #elif defined(__powerpc__) || defined(__powerpc64__) "machine" #elif defined(__s390__) || defined(__s390x__) "Manufacturer" #elif defined(__sh__) "machine" #elif defined(sparc) || defined(__sparc__) "cpu" #elif defined(__vax__) "cpu" #else NULL #endif ; int if_machinearch(char *str, size_t len) { FILE *fp; char buf[256]; if (mproc == NULL) { errno = EINVAL; return -1; } fp = fopen("/proc/cpuinfo", "r"); if (fp == NULL) return -1; while (fscanf(fp, "%255s : ", buf) != EOF) { if (strncmp(buf, mproc, strlen(mproc)) == 0 && fscanf(fp, "%255s", buf) == 1) { fclose(fp); return snprintf(str, len, ":%s", buf); } } fclose(fp); errno = ESRCH; return -1; } static int check_proc_int(const char *path) { FILE *fp; int i; fp = fopen(path, "r"); if (fp == NULL) return -1; if (fscanf(fp, "%d", &i) != 1) i = -1; fclose(fp); return i; } static ssize_t write_path(const char *path, const char *val) { FILE *fp; ssize_t r; fp = fopen(path, "w"); if (fp == NULL) return -1; r = fprintf(fp, "%s\n", val); fclose(fp); return r; } 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(path); if (n == -1) return errno == ENOENT ? 0 : -1; if (n == 1) return 0; return write_path(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(path); if (n == -1) return errno == ENOENT ? 0 : -1; if (n == 0) ifp->options->options |= DHCPCD_BROADCAST; return 0; } /* XXX work out Virtal Interface Masters */ int if_vimaster(__unused const 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, SIOCSIFVLAN, &v) != 0) return 0; /* 0 means no VLANID */ return (unsigned short)v.u.VID; } static int _open_link_socket(struct sockaddr_nl *nl, int protocol) { int fd; if ((fd = xsocket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol)) == -1) return -1; nl->nl_family = AF_NETLINK; if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) { close(fd); return -1; } return fd; } int if_opensockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; struct sockaddr_nl snl; socklen_t len; #ifdef NETLINK_BROADCAST_ERROR int on = 1; #endif /* Open the link socket first so it gets pid() for the socket. * Then open our persistent route socket so we get a unique * pid that doesn't clash with a process id for after we fork. */ memset(&snl, 0, sizeof(snl)); snl.nl_groups = RTMGRP_LINK; #ifdef INET snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR; #endif #ifdef INET6 snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH; #endif ctx->link_fd = _open_link_socket(&snl, NETLINK_ROUTE); if (ctx->link_fd == -1) return -1; #ifdef NETLINK_BROADCAST_ERROR setsockopt(ctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR, &on, sizeof(on)); #endif if ((priv = calloc(1, sizeof(*priv))) == NULL) return -1; ctx->priv = priv; memset(&snl, 0, sizeof(snl)); priv->route_fd = _open_link_socket(&snl, NETLINK_ROUTE); 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; return 0; } void if_closesockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; if (ctx->priv != NULL) { priv = (struct priv *)ctx->priv; free(priv->sndrcv_iov[0].iov_base); close(priv->route_fd); } } static int get_netlink(struct dhcpcd_ctx *ctx, struct iovec *iov, struct interface *ifp, int fd, int flags, int (*callback)(struct dhcpcd_ctx *, struct interface *, struct nlmsghdr *)) { struct msghdr msg; struct sockaddr_nl nladdr; ssize_t len; struct nlmsghdr *nlm; int r; unsigned int again; memset(&msg, 0, sizeof(msg)); msg.msg_name = &nladdr; msg.msg_namelen = sizeof(nladdr); memset(&nladdr, 0, sizeof(nladdr)); msg.msg_iov = iov; msg.msg_iovlen = 1; recv_again: if ((len = recvmsg_realloc(fd, &msg, flags)) == -1) return -1; if (len == 0) return 0; /* 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; r = 0; again = 0; /* Appease static analysis */ for (nlm = iov->iov_base; nlm && NLMSG_OK(nlm, (size_t)len); nlm = NLMSG_NEXT(nlm, len)) { again = (nlm->nlmsg_flags & NLM_F_MULTI); if (nlm->nlmsg_type == NLMSG_NOOP) continue; if (nlm->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err; if (nlm->nlmsg_len - sizeof(*nlm) < sizeof(*err)) { errno = EBADMSG; return -1; } err = (struct nlmsgerr *)NLMSG_DATA(nlm); if (err->error != 0) { errno = -err->error; return -1; } break; } if (nlm->nlmsg_type == NLMSG_DONE) { again = 0; break; } if (callback && (r = callback(ctx, ifp, nlm)) != 0) break; } if (r == 0 && again) 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 = (struct rtattr *)RTM_RTA(rtm); len = RTM_PAYLOAD(nlm); while (RTA_OK(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); while (RTA_OK(r2, l2)) { switch (r2->rta_type) { case RTAX_MTU: rt->rt_mtu = *(unsigned int *)RTA_DATA(r2); break; } r2 = RTA_NEXT(r2, l2); } break; } } if (sa != NULL) { socklen_t salen; sa->sa_family = rtm->rtm_family; salen = sa_addrlen(sa); memcpy((char *)sa + sa_addroffset(sa), RTA_DATA(rta), MIN(salen, RTA_PAYLOAD(rta))); } rta = RTA_NEXT(rta, len); } /* 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. */ 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); 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; #endif #ifdef INET6 struct in6_addr addr6; #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 messages we sent. */ 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 = (struct rtattr *)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); while (RTA_OK(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; } rta = RTA_NEXT(rta, len); } ipv4_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, &addr, &net, &brd, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid); break; #endif #ifdef INET6 case AF_INET6: memset(&addr6, 0, sizeof(addr6)); while (RTA_OK(rta, len)) { switch (rta->rta_type) { case IFA_ADDRESS: memcpy(&addr6.s6_addr, RTA_DATA(rta), sizeof(addr6.s6_addr)); break; } rta = RTA_NEXT(rta, len); } ipv6_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, &addr6, ifa->ifa_prefixlen, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid); break; #endif } return 0; } static uint8_t l2addr_len(unsigned short if_type) { switch (if_type) { case ARPHRD_ETHER: /* FALLTHROUGH */ case ARPHRD_IEEE802: /*FALLTHROUGH */ case ARPHRD_IEEE80211: return 6; case ARPHRD_IEEE1394: return 8; case ARPHRD_INFINIBAND: return 20; } /* Impossible */ return 0; } #ifdef INET6 static int link_neigh(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, struct nlmsghdr *nlm) { struct ndmsg *r; struct rtattr *rta; size_t len; struct in6_addr addr6; int flags; 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 = (struct rtattr *)RTM_RTA(r); len = RTM_PAYLOAD(nlm); if (r->ndm_family == AF_INET6) { flags = 0; if (r->ndm_flags & NTF_ROUTER) flags |= IPV6ND_ROUTER; if (nlm->nlmsg_type == RTM_NEWNEIGH && r->ndm_state & (NUD_REACHABLE | NUD_STALE | NUD_DELAY | NUD_PROBE | NUD_PERMANENT)) flags |= IPV6ND_REACHABLE; memset(&addr6, 0, sizeof(addr6)); while (RTA_OK(rta, len)) { switch (rta->rta_type) { case NDA_DST: memcpy(&addr6.s6_addr, RTA_DATA(rta), sizeof(addr6.s6_addr)); break; } rta = RTA_NEXT(rta, len); } ipv6nd_neighbour(ctx, &addr6, flags); } return 0; } #endif static int link_netlink(struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm) { 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; while (RTA_OK(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; } rta = RTA_NEXT(rta, len); } if (nlm->nlmsg_type == RTM_DELLINK) { /* If are listening to a dev manager, let that remove * the interface rather than the kernel. */ if (dev_listening(ctx) < 1) 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) { /* If are listening to a dev manager, let that announce * the interface rather than the kernel. */ if (dev_listening(ctx) < 1) 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) && hwaddr) { uint8_t l; l = l2addr_len(ifi->ifi_type); if (hwaddr->rta_len == RTA_LENGTH(l)) dhcpcd_handlehwaddr(ctx, ifn, RTA_DATA(hwaddr), l); } dhcpcd_handlecarrier(ctx, ifi->ifi_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN, ifi->ifi_flags, ifn); return 0; } int if_handlelink(struct dhcpcd_ctx *ctx) { return get_netlink(ctx, ctx->iov, NULL, ctx->link_fd, MSG_DONTWAIT, &link_netlink); } static int send_netlink(struct dhcpcd_ctx *ctx, struct interface *ifp, int protocol, struct nlmsghdr *hdr, int (*callback)(struct dhcpcd_ctx *, struct interface *, struct nlmsghdr *)) { int s, r; struct sockaddr_nl snl; struct iovec iov[1]; struct msghdr msg; memset(&snl, 0, sizeof(snl)); snl.nl_family = AF_NETLINK; if (protocol == NETLINK_ROUTE) { struct priv *priv; priv = (struct priv *)ctx->priv; s = priv->route_fd; } else { if ((s = _open_link_socket(&snl, protocol)) == -1) return -1; } memset(&msg, 0, sizeof(msg)); msg.msg_name = &snl; msg.msg_namelen = sizeof(snl); memset(&iov, 0, sizeof(iov)); iov[0].iov_base = hdr; iov[0].iov_len = hdr->nlmsg_len; msg.msg_iov = iov; msg.msg_iovlen = 1; /* Request a reply */ hdr->nlmsg_flags |= NLM_F_ACK; hdr->nlmsg_seq = (uint32_t)++ctx->seq; if (sendmsg(s, &msg, 0) != -1) { struct priv *priv; priv = (struct priv *)ctx->priv; r = get_netlink(ctx, priv->sndrcv_iov, ifp, s, 0, callback); } else r = -1; if (protocol != NETLINK_ROUTE) close(s); return r; } #define NLMSG_TAIL(nmsg) \ ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len))) static int add_attr_l(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, const void *data, unsigned short alen) { unsigned short len = (unsigned short)RTA_LENGTH(alen); struct rtattr *rta; if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { errno = ENOBUFS; return -1; } rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; if (alen) memcpy(RTA_DATA(rta), data, alen); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); return 0; } static int add_attr_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 struct interface *ifp, 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 send_netlink(ctx, NULL, NETLINK_GENERIC, &nlm.hdr, &_gnl_getfamily); } static int _if_getssid_nl80211(__unused struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm) { 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 (send_netlink(ifp->ctx, ifp, NETLINK_GENERIC, &nlm.hdr, 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 send_netlink(ifp->ctx, ifp, NETLINK_GENERIC, &nlm.hdr, &_if_getssid_nl80211); } #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]; }; 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); ADDSA(RTA_DST, &rt->rt_dest); if (cmd == RTM_ADD || cmd == RTM_CHANGE) { if (!gateway_unspec) 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)); } } 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 send_netlink(rt->rt_ifp->ctx, NULL, NETLINK_ROUTE, &nlm.hdr, NULL); } static int _if_initrt(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, struct nlmsghdr *nlm) { struct rt rt; if (if_copyrt(ctx, &rt, nlm) == 0) { rt.rt_dflags |= RTDF_INIT; rt_recvrt(RTM_ADD, &rt); } return 0; } int if_initrt(struct dhcpcd_ctx *ctx, int af) { struct nlmr nlm; rt_headclear(&ctx->kroutes, af); memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); nlm.hdr.nlmsg_type = RTM_GETROUTE; nlm.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH; nlm.rt.rtm_table = RT_TABLE_MAIN; nlm.rt.rtm_family = (unsigned char)af; return send_netlink(ctx, NULL, NETLINK_ROUTE, &nlm.hdr, &_if_initrt); } #ifdef INET /* Linux is a special snowflake when it comes to BPF. */ const char *bpf_name = "Packet Socket"; #define BPF_BUFFER_LEN 1500 int bpf_open(struct interface *ifp, int (*filter)(struct interface *, int)) { struct ipv4_state *state; /* Linux is a special snowflake for opening BPF. */ int s; union sockunion { struct sockaddr sa; struct sockaddr_ll sll; struct sockaddr_storage ss; } su; #ifdef PACKET_AUXDATA int n; #endif #define SF SOCK_CLOEXEC | SOCK_NONBLOCK if ((s = xsocket(PF_PACKET, SOCK_RAW | SF, htons(ETH_P_ALL))) == -1) return -1; #undef SF /* Allocate a suitably large buffer for a single packet. */ state = ipv4_getstate(ifp); if (state == NULL) goto eexit; if (state->buffer_size < ETH_DATA_LEN) { void *nb; if ((nb = realloc(state->buffer, ETH_DATA_LEN)) == NULL) goto eexit; state->buffer = nb; state->buffer_size = ETH_DATA_LEN; state->buffer_len = state->buffer_pos = 0; } if (filter(ifp, s) != 0) goto eexit; #ifdef PACKET_AUXDATA n = 1; if (setsockopt(s, SOL_PACKET, PACKET_AUXDATA, &n, sizeof(n)) != 0) { if (errno != ENOPROTOOPT) goto eexit; } #endif memset(&su, 0, sizeof(su)); su.sll.sll_family = PF_PACKET; su.sll.sll_protocol = htons(ETH_P_ALL); su.sll.sll_ifindex = (int)ifp->index; if (bind(s, &su.sa, sizeof(su.sll)) == -1) goto eexit; return s; eexit: if (state != NULL) { free(state->buffer); state->buffer = NULL; } close(s); return -1; } /* 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 interface *ifp, int s, void *data, size_t len, unsigned int *flags) { ssize_t bytes; struct ipv4_state *state = IPV4_STATE(ifp); struct iovec iov = { .iov_base = state->buffer, .iov_len = state->buffer_size }; struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; #ifdef PACKET_AUXDATA unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; struct cmsghdr *cmsg; struct tpacket_auxdata *aux; #endif #ifdef PACKET_AUXDATA msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); #endif bytes = recvmsg(s, &msg, 0); if (bytes == -1) return -1; *flags |= BPF_EOF; /* We only ever read one packet. */ *flags &= ~BPF_PARTIALCSUM; if (bytes) { ssize_t fl = (ssize_t)bpf_frame_header_len(ifp); bytes -= fl; if ((size_t)bytes > len) bytes = (ssize_t)len; memcpy(data, state->buffer + fl, (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) *flags |= BPF_PARTIALCSUM; } } #endif } return bytes; } int bpf_attach(int s, void *filter, unsigned int filter_len) { struct sock_fprog pf; /* Install the filter. */ memset(&pf, 0, sizeof(pf)); pf.filter = filter; pf.len = (unsigned short)filter_len; return setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)); } int if_address(unsigned char cmd, const struct ipv4_addr *addr) { struct nlma nlm; int retval = 0; #if 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 = addr->iface->index; nlm.ifa.ifa_family = AF_INET; nlm.ifa.ifa_prefixlen = inet_ntocidr(addr->mask); #if 0 /* This creates the aliased interface */ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, addr->iface->alias, (unsigned short)(strlen(addr->iface->alias) + 1)); #endif add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, &addr->addr.s_addr, sizeof(addr->addr.s_addr)); if (cmd == RTM_NEWADDR) add_attr_l(&nlm.hdr, sizeof(nlm), IFA_BROADCAST, &addr->brd.s_addr, sizeof(addr->brd.s_addr)); #ifdef IFA_F_NOPREFIXROUTE if (nlm.ifa.ifa_prefixlen < 32) flags |= IFA_F_NOPREFIXROUTE; if (flags) add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); #endif if (send_netlink(addr->iface->ctx, NULL, NETLINK_ROUTE, &nlm.hdr, 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; /* IFA_FLAGS is not a define, but is was added at the same time * IFA_F_NOPREFIXROUTE was do use that. */ #if defined(IFA_F_NOPREFIXROUTE) || defined(IFA_F_MANAGETEMPADDR) 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; #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) flags |= IFA_F_MANAGETEMPADDR; #endif /* 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) { 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)); } #ifdef IFA_F_NOPREFIXROUTE if (!IN6_IS_ADDR_LINKLOCAL(&ia->addr)) flags |= IFA_F_NOPREFIXROUTE; #endif #if defined(IFA_F_NOPREFIXROUTE) || defined(IFA_F_MANAGETEMPADDR) if (flags) add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); #endif return send_netlink(ia->iface->ctx, NULL, NETLINK_ROUTE, &nlm.hdr, NULL); } int if_addrflags6(const struct interface *ifp, const struct in6_addr *addr, __unused const char *alias) { FILE *fp; char *p, ifaddress[33], address[33], name[IF_NAMESIZE + 1]; unsigned int ifindex; int prefix, scope, flags, i; fp = fopen(PROC_INET6, "r"); if (fp == NULL) return -1; p = ifaddress; for (i = 0; i < (int)sizeof(addr->s6_addr); i++) { p += snprintf(p, 3, "%.2x", addr->s6_addr[i]); } *p = '\0'; while (fscanf(fp, "%32[a-f0-9] %x %x %x %x %"TOSTRING(IF_NAMESIZE)"s\n", address, &ifindex, &prefix, &scope, &flags, name) == 6) { if (strlen(address) != 32) { fclose(fp); errno = EINVAL; return -1; } if (strcmp(name, ifp->name) == 0 && strcmp(ifaddress, address) == 0) { fclose(fp); return flags; } } fclose(fp); errno = ESRCH; return -1; } int if_getlifetime6(__unused struct ipv6_addr *ia) { /* God knows how to work out address lifetimes on Linux */ errno = ENOTSUP; return -1; } struct nlml { struct nlmsghdr hdr; struct ifinfomsg i; char buffer[32]; }; 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 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); } 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 send_netlink(ctx, NULL, NETLINK_ROUTE, &nlm.hdr, NULL); #else UNUSED(ctx); UNUSED(ifindex); errno = ENOTSUP; return -1; #endif } static const char *prefix = "/proc/sys/net/ipv6/conf"; void if_setup_inet6(const struct interface *ifp) { int ra; char path[256]; /* The kernel cannot make stable private addresses. */ if (if_disable_autolinklocal(ifp->ctx, ifp->index) == -1) 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", prefix, ifp->name); ra = check_proc_int(path); if (ra != 1 && ra != -1) { if (write_path(path, "0") == -1) logerr("%s: %s", __func__, path); } snprintf(path, sizeof(path), "%s/%s/accept_ra", prefix, ifp->name); ra = check_proc_int(path); if (ra == -1) { logfunc_t *logfunc = errno == ENOENT? logdebug : logwarn; /* The sysctl probably doesn't exist, but this isn't an * error as such so just log it and continue */ logfunc("%s", path); } else if (ra != 0) { if (write_path(path, "0") == -1) logerr("%s: %s", __func__, path); } } #ifdef IPV6_MANAGETEMPADDR int ip6_use_tempaddr(const char *ifname) { char path[256]; int val; if (ifname == NULL) ifname = "all"; snprintf(path, sizeof(path), "%s/%s/use_tempaddr", prefix, ifname); val = check_proc_int(path); return val == -1 ? 0 : val; } int ip6_temp_preferred_lifetime(const char *ifname) { char path[256]; int val; if (ifname == NULL) ifname = "all"; snprintf(path, sizeof(path), "%s/%s/temp_prefered_lft", prefix, ifname); val = check_proc_int(path); return val < 0 ? TEMP_PREFERRED_LIFETIME : val; } int ip6_temp_valid_lifetime(const char *ifname) { char path[256]; int val; if (ifname == NULL) ifname = "all"; snprintf(path, sizeof(path), "%s/%s/temp_valid_lft", prefix, ifname); val = check_proc_int(path); return val < 0 ? TEMP_VALID_LIFETIME : val; } #endif /* IPV6_MANAGETEMPADDR */ int ip6_forwarding(const char *ifname) { char path[256]; int val; if (ifname == NULL) ifname = "all"; snprintf(path, sizeof(path), "%s/%s/forwarding", prefix, ifname); val = check_proc_int(path); return val == -1 ? 0 : val; } #endif /* INET6 */ dhcpcd5-7.1.0/src/if-options.c000066400000000000000000001743661342162717100161000ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "config.h" #include "common.h" #include "dhcp.h" #include "dhcp6.h" #include "dhcpcd-embedded.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "logerr.h" #include "sa.h" /* These options only make sense in the config file, so don't use any valid short options for them */ #define O_BASE MAX('z', 'Z') + 1 #define O_ARPING O_BASE + 1 #define O_FALLBACK O_BASE + 2 #define O_DESTINATION O_BASE + 3 #define O_IPV6RS O_BASE + 4 #define O_NOIPV6RS O_BASE + 5 #define O_IPV6RA_FORK O_BASE + 6 // unused O_BASE + 7 // unused 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 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'}, {"vendor", required_argument, NULL, 'v'}, {"waitip", optional_argument, NULL, 'w'}, {"exit", no_argument, NULL, 'x'}, {"allowinterfaces", required_argument, NULL, 'z'}, {"reboot", required_argument, NULL, 'y'}, {"noarp", no_argument, NULL, 'A'}, {"nobackground", no_argument, NULL, 'B'}, {"nohook", required_argument, NULL, 'C'}, {"duid", no_argument, NULL, 'D'}, {"lastlease", no_argument, NULL, 'E'}, {"fqdn", optional_argument, NULL, 'F'}, {"nogateway", no_argument, NULL, 'G'}, {"xidhwaddr", no_argument, NULL, 'H'}, {"clientid", optional_argument, NULL, 'I'}, {"broadcast", no_argument, NULL, 'J'}, {"nolink", no_argument, NULL, 'K'}, {"noipv4ll", no_argument, NULL, 'L'}, {"master", no_argument, NULL, 'M'}, {"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'}, {"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", no_argument, NULL, O_IA_NA}, {"ia_ta", no_argument, NULL, O_IA_TA}, {"ia_pd", no_argument, NULL, O_IA_PD}, {"hostname_short", no_argument, NULL, O_HOSTNAME_SHORT}, {"dev", required_argument, NULL, O_DEV}, {"nodev", no_argument, NULL, O_NODEV}, {"define", required_argument, NULL, O_DEFINE}, {"definend", required_argument, NULL, O_DEFINEND}, {"define6", required_argument, NULL, O_DEFINE6}, {"embed", required_argument, NULL, O_EMBED}, {"encap", required_argument, NULL, O_ENCAP}, {"vendopt", required_argument, NULL, O_VENDOPT}, {"vendclass", required_argument, NULL, O_VENDCLASS}, {"authprotocol", required_argument, NULL, O_AUTHPROTOCOL}, {"authtoken", required_argument, NULL, O_AUTHTOKEN}, {"noauthrequired", no_argument, NULL, O_AUTHNOTREQUIRED}, {"dhcp", no_argument, NULL, O_DHCP}, {"nodhcp", no_argument, NULL, O_NODHCP}, {"dhcp6", no_argument, NULL, O_DHCP6}, {"nodhcp6", no_argument, NULL, O_NODHCP6}, {"controlgroup", required_argument, NULL, O_CONTROLGRP}, {"slaac", required_argument, NULL, O_SLAAC}, {"gateway", no_argument, NULL, O_GATEWAY}, {"reject", required_argument, NULL, O_REJECT}, {"bootp", no_argument, NULL, O_BOOTP}, {"nodelay", no_argument, NULL, O_NODELAY}, {"noup", no_argument, NULL, O_NOUP}, {"lastleaseextend", no_argument, NULL, O_LASTLEASE_EXTEND}, {"inactive", no_argument, NULL, O_INACTIVE}, {"mudurl", required_argument, NULL, O_MUDURL}, {NULL, 0, NULL, '\0'} }; static char * add_environ(struct if_options *ifo, const char *value, int uniq) { char **newlist; char **lst = ifo->environ; size_t i = 0, l, lv; char *match = NULL, *p, *n; match = strdup(value); if (match == NULL) { 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 (lst && lst[i]) { if (match && strncmp(lst[i], match, l) == 0) { if (uniq) { n = strdup(value); if (n == NULL) { logerr(__func__); free(match); return NULL; } free(lst[i]); lst[i] = n; } else { /* Append a space and the value to it */ l = strlen(lst[i]); lv = strlen(p); n = realloc(lst[i], l + lv + 2); if (n == NULL) { logerr(__func__); free(match); return NULL; } lst[i] = n; lst[i][l] = ' '; memcpy(lst[i] + l + 1, p, lv); lst[i][l + lv + 1] = '\0'; } free(match); return lst[i]; } i++; } free(match); n = strdup(value); if (n == NULL) { logerr(__func__); return NULL; } newlist = reallocarray(lst, i + 2, sizeof(char *)); if (newlist == NULL) { logerr(__func__); free(n); return NULL; } newlist[i] = n; newlist[i + 1] = NULL; ifo->environ = newlist; return newlist[i]; } #define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) static ssize_t parse_string_hwaddr(char *sbuf, size_t slen, const char *str, int clid) { size_t l; const char *p; int i, punt_last = 0; char c[4], cmd; /* If surrounded by quotes then it's a string */ if (*str == '"') { str++; l = strlen(str); p = str + l - 1; if (*p == '"') punt_last = 1; } else { l = (size_t)hwaddr_aton(NULL, str); if ((ssize_t) l != -1 && l > 1) { if (l > slen) { errno = ENOBUFS; return -1; } hwaddr_aton((uint8_t *)sbuf, str); return (ssize_t)l; } } /* Process escapes */ l = 0; /* If processing a string on the clientid, first byte should be * 0 to indicate a non hardware type */ if (clid && *str) { if (sbuf) *sbuf++ = 0; l++; } c[3] = '\0'; while (*str) { if (++l > slen && sbuf) { errno = ENOBUFS; return -1; } if (*str == '\\') { str++; switch((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' && sbuf) { c[2] = '\0'; *sbuf++ = (char)strtol(c, NULL, 16); } else l--; break; case '0': /* Grab an octal code */ c[2] = '\0'; for (i = 0; i < 3; i++) { if (*str < '0' || *str > '7') break; c[i] = *str++; } if (c[2] != '\0' && sbuf) { i = (int)strtol(c, NULL, 8); if (i > 255) i = 255; *sbuf ++= (char)i; } else l--; break; default: if (sbuf) *sbuf++ = cmd; break; } } else { if (sbuf) *sbuf++ = *str; str++; } } if (punt_last) { if (sbuf) *--sbuf = '\0'; l--; } return (ssize_t)l; } static int parse_iaid1(uint8_t *iaid, const char *arg, size_t len, int n) { int e; uint32_t narg; ssize_t s; narg = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e == 0) { if (n) narg = htonl(narg); memcpy(iaid, &narg, sizeof(narg)); return 0; } if ((s = parse_string((char *)iaid, len, arg)) < 1) return -1; if (s < 4) iaid[3] = '\0'; if (s < 3) iaid[2] = '\0'; if (s < 2) iaid[1] = '\0'; return 0; } static int parse_iaid(uint8_t *iaid, const char *arg, size_t len) { return parse_iaid1(iaid, arg, len, 1); } #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("`%s' is not a valid CIDR", p); return -1; } } if (addr != NULL && inet_aton(arg, addr) == 0) { logerrx("`%s' is not a valid IP address", arg); return -1; } if (p != NULL) *--p = '/'; else if (net != NULL && addr != NULL) net->s_addr = ipv4_getnetmask(addr->s_addr); return 0; } #else static int parse_addr(__unused struct in_addr *addr, __unused struct in_addr *net, __unused const char *arg) { logerrx("No IPv4 support"); return -1; } #endif static const char * set_option_space(struct dhcpcd_ctx *ctx, const char *arg, const struct dhcp_opt **d, size_t *dl, const struct dhcp_opt **od, size_t *odl, struct if_options *ifo, uint8_t *request[], uint8_t *require[], uint8_t *no[], uint8_t *reject[]) { #if !defined(INET) && !defined(INET6) UNUSED(ctx); #endif #ifdef INET6 if (strncmp(arg, "nd_", strlen("nd_")) == 0) { *d = ctx->nd_opts; *dl = ctx->nd_opts_len; *od = ifo->nd_override; *odl = ifo->nd_override_len; *request = ifo->requestmasknd; *require = ifo->requiremasknd; *no = ifo->nomasknd; *reject = ifo->rejectmasknd; return arg + strlen("nd_"); } if (strncmp(arg, "dhcp6_", strlen("dhcp6_")) == 0) { *d = ctx->dhcp6_opts; *dl = ctx->dhcp6_opts_len; *od = ifo->dhcp6_override; *odl = ifo->dhcp6_override_len; *request = ifo->requestmask6; *require = ifo->requiremask6; *no = ifo->nomask6; *reject = ifo->rejectmask6; return arg + strlen("dhcp6_"); } #endif #ifdef INET *d = ctx->dhcp_opts; *dl = ctx->dhcp_opts_len; *od = ifo->dhcp_override; *odl = ifo->dhcp_override_len; #else *d = NULL; *dl = 0; *od = NULL; *odl = 0; #endif *request = ifo->requestmask; *require = ifo->requiremask; *no = ifo->nomask; *reject = ifo->rejectmask; return arg; } void free_dhcp_opt_embenc(struct dhcp_opt *opt) { size_t i; struct dhcp_opt *o; free(opt->var); for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++) free_dhcp_opt_embenc(o); free(opt->embopts); opt->embopts_len = 0; opt->embopts = NULL; for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++) free_dhcp_opt_embenc(o); free(opt->encopts); opt->encopts_len = 0; opt->encopts = NULL; } static char * strwhite(const char *s) { if (s == NULL) return NULL; while (*s != ' ' && *s != '\t') { if (*s == '\0') return NULL; s++; } return UNCONST(s); } static char * strskipwhite(const char *s) { if (s == NULL || *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, **nconf; ssize_t s; struct in_addr addr, addr2; in_addr_t *naddr; struct rt *rt; const struct dhcp_opt *d, *od; uint8_t *request, *require, *no, *reject; struct dhcp_opt **dop, *ndop; size_t *dop_len, dl, odl; struct vivco *vivco; struct 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; free(ifo->script); ifo->script = strdup(arg); if (ifo->script == NULL) logerr(__func__); break; case 'd': ifo->options |= DHCPCD_DEBUG; break; case 'e': ARG_REQUIRED; add_environ(ifo, arg, 1); break; case 'h': if (!arg) { ifo->options |= DHCPCD_HOSTNAME; break; } s = parse_string(ifo->hostname, HOSTNAME_MAX_LEN, arg); if (s == -1) { logerr("%s: hostname", __func__); return -1; } if (s != 0 && ifo->hostname[0] == '.') { logerrx("hostname cannot begin with ."); return -1; } ifo->hostname[s] = '\0'; if (ifo->hostname[0] == '\0') ifo->options &= ~DHCPCD_HOSTNAME; else ifo->options |= DHCPCD_HOSTNAME; break; case 'i': if (arg) s = parse_string((char *)ifo->vendorclassid + 1, VENDORCLASSID_MAX_LEN, arg); else s = 0; if (s == -1) { 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 (ifname == NULL && ctx->logfile == NULL) { logclose(); ctx->logfile = strdup(arg); logopen(ctx->logfile); } break; case 'k': ifo->options |= DHCPCD_RELEASE; break; case 'l': ARG_REQUIRED; 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; arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) { logerrx("unknown option `%s'", arg); return -1; } break; case O_REJECT: ARG_REQUIRED; arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, reject, arg, 1) != 0 || make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || make_option_mask(d, dl, od, odl, require, arg, -1) != 0) { 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 = (time_t)strtoi(arg, NULL, 0, 0, INT32_MAX, &e); if (e) { logerrx("failed to convert timeout %s", arg); return -1; } break; case 'u': s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1; s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2, (size_t)s, arg); if (s == -1) { 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; case 'v': ARG_REQUIRED; p = strchr(arg, ','); if (!p || !p[1]) { logerrx("invalid vendor format: %s", arg); return -1; } /* If vendor starts with , then it is not encapsulated */ if (p == arg) { arg++; s = parse_string((char *)ifo->vendor + 1, VENDOR_MAX_LEN, arg); if (s == -1) { logerr("vendor"); return -1; } ifo->vendor[0] = (uint8_t)s; ifo->options |= DHCPCD_VENDORRAW; break; } /* Encapsulated vendor options */ if (ifo->options & DHCPCD_VENDORRAW) { ifo->options &= ~DHCPCD_VENDORRAW; ifo->vendor[0] = 0; } /* Strip and preserve the comma */ *p = '\0'; i = (int)strtoi(arg, NULL, 0, 1, 254, &e); *p = ','; if (e) { logerrx("vendor option should be between" " 1 and 254 inclusive"); return -1; } arg = p + 1; s = VENDOR_MAX_LEN - ifo->vendor[0] - 2; if (inet_aton(arg, &addr) == 1) { if (s < 6) { s = -1; errno = ENOBUFS; } else { memcpy(ifo->vendor + ifo->vendor[0] + 3, &addr.s_addr, sizeof(addr.s_addr)); s = sizeof(addr.s_addr); } } else { s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3, (size_t)s, arg); } if (s == -1) { logerr("vendor"); return -1; } if (s != 0) { ifo->vendor[ifo->vendor[0] + 1] = (uint8_t)i; ifo->vendor[ifo->vendor[0] + 2] = (uint8_t)s; ifo->vendor[0] = (uint8_t)(ifo->vendor[0] + s + 2); } break; case 'w': ifo->options |= DHCPCD_WAITIP; if (arg != NULL && arg[0] != '\0') { if (arg[0] == '4' || arg[1] == '4') ifo->options |= DHCPCD_WAITIP4; if (arg[0] == '6' || arg[1] == '6') ifo->options |= DHCPCD_WAITIP6; } break; case 'y': ARG_REQUIRED; ifo->reboot = (time_t)strtoi(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerr("failed to convert reboot %s", arg); return -1; } break; case 'z': ARG_REQUIRED; if (ifname == NULL) 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, p, 0); free(p); break; case 'D': ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; break; case 'E': ifo->options |= DHCPCD_LASTLEASE; break; case 'F': if (!arg) { ifo->fqdn = FQDN_BOTH; break; } if (strcmp(arg, "none") == 0) ifo->fqdn = FQDN_NONE; else if (strcmp(arg, "ptr") == 0) ifo->fqdn = FQDN_PTR; else if (strcmp(arg, "both") == 0) ifo->fqdn = FQDN_BOTH; else if (strcmp(arg, "disable") == 0) ifo->fqdn = FQDN_DISABLE; else { logerrx("invalid value `%s' for FQDN", arg); return -1; } break; case 'G': ifo->options &= ~DHCPCD_GATEWAY; break; case 'H': ifo->options |= DHCPCD_XID_HWADDR; break; case 'I': /* Strings have a type of 0 */; ifo->clientid[1] = 0; if (arg) s = parse_string_hwaddr((char *)ifo->clientid + 1, CLIENTID_MAX_LEN, arg, 1); else s = 0; if (s == -1) { logerr("clientid"); return -1; } ifo->options |= DHCPCD_CLIENTID; ifo->clientid[0] = (uint8_t)s; break; case 'J': ifo->options |= DHCPCD_BROADCAST; break; case 'K': ifo->options &= ~DHCPCD_LINK; break; case 'L': ifo->options &= ~DHCPCD_IPV4LL; break; case 'M': ifo->options |= DHCPCD_MASTER; break; case 'O': ARG_REQUIRED; arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || make_option_mask(d, dl, od, odl, require, arg, -1) != 0 || make_option_mask(d, dl, od, odl, no, arg, 1) != 0) { logerrx("unknown option `%s'", arg); return -1; } break; case 'Q': ARG_REQUIRED; arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, require, arg, 1) != 0 || make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) { logerrx("unknown option `%s'", arg); return -1; } break; case 'S': ARG_REQUIRED; p = strchr(arg, '='); if (p == NULL) { logerrx("static assignment required"); return -1; } p++; if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { if (parse_addr(&ifo->req_addr, ifo->req_mask.s_addr == 0 ? &ifo->req_mask : NULL, p) != 0) return -1; ifo->options |= DHCPCD_STATIC; ifo->options &= ~DHCPCD_INFORM; } else if (strncmp(arg, "subnet_mask=", strlen("subnet_mask=")) == 0) { if (parse_addr(&ifo->req_mask, NULL, p) != 0) return -1; } else if (strncmp(arg, "broadcast_address=", strlen("broadcast_address=")) == 0) { if (parse_addr(&ifo->req_brd, NULL, p) != 0) return -1; } else if (strncmp(arg, "routes=", strlen("routes=")) == 0 || strncmp(arg, "static_routes=", strlen("static_routes=")) == 0 || strncmp(arg, "classless_static_routes=", strlen("classless_static_routes=")) == 0 || strncmp(arg, "ms_classless_static_routes=", strlen("ms_classless_static_routes=")) == 0) { struct in_addr addr3; fp = np = strwhite(p); if (np == NULL) { logerrx("all routes need a gateway"); return -1; } *np++ = '\0'; np = strskipwhite(np); if (parse_addr(&addr, &addr2, p) == -1 || parse_addr(&addr3, NULL, np) == -1) { *fp = ' '; return -1; } if ((rt = rt_new0(ctx)) == NULL) { *fp = ' '; return -1; } sa_in_init(&rt->rt_dest, &addr); sa_in_init(&rt->rt_netmask, &addr2); sa_in_init(&rt->rt_gateway, &addr3); TAILQ_INSERT_TAIL(&ifo->routes, rt, rt_next); *fp = ' '; } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) { if (parse_addr(&addr, NULL, p) == -1) return -1; if ((rt = rt_new0(ctx)) == NULL) return -1; addr2.s_addr = INADDR_ANY; sa_in_init(&rt->rt_dest, &addr2); sa_in_init(&rt->rt_netmask, &addr2); sa_in_init(&rt->rt_gateway, &addr); TAILQ_INSERT_TAIL(&ifo->routes, rt, rt_next); } else if (strncmp(arg, "interface_mtu=", strlen("interface_mtu=")) == 0 || strncmp(arg, "mtu=", strlen("mtu=")) == 0) { ifo->mtu = (unsigned int)strtou(p, NULL, 0, MTU_MIN, MTU_MAX, &e); if (e) { logerrx("invalid MTU %s", p); return -1; } } else if (strncmp(arg, "ip6_address=", strlen("ip6_address=")) == 0) { np = strchr(p, '/'); if (np) *np++ = '\0'; if (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; } } else { dl = 0; if (ifo->config != NULL) { while (ifo->config[dl] != NULL) { if (strncmp(ifo->config[dl], arg, (size_t)(p - arg)) == 0) { p = strdup(arg); if (p == NULL) { logerr(__func__); return -1; } free(ifo->config[dl]); ifo->config[dl] = p; return 1; } dl++; } } p = strdup(arg); if (p == NULL) { logerr(__func__); return -1; } nconf = reallocarray(ifo->config, dl+2, sizeof(char *)); if (nconf == NULL) { logerr(__func__); free(p); return -1; } ifo->config = nconf; ifo->config[dl] = p; ifo->config[dl + 1] = NULL; } 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 (ifname == NULL) ctx->ifdv = splitv(&ctx->ifdc, ctx->ifdv, arg); break; case '1': ifo->options |= DHCPCD_ONESHOT; break; case '4': ifo->options &= ~DHCPCD_IPV6; ifo->options |= DHCPCD_IPV4; break; case '6': ifo->options &= ~DHCPCD_IPV4; ifo->options |= DHCPCD_IPV6; break; case O_IPV4: ifo->options |= DHCPCD_IPV4; break; case O_NOIPV4: ifo->options &= ~DHCPCD_IPV4; break; case O_IPV6: ifo->options |= DHCPCD_IPV6; break; case O_NOIPV6: ifo->options &= ~DHCPCD_IPV6; break; #ifdef INET case O_ARPING: while (arg != 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; arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, ifo->dstmask, arg, 2) != 0) { if (errno == EINVAL) logerrx("option `%s' does not take" " an IPv4 address", 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 (ifname == NULL) { 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 (ifname == NULL) { logerr("IA PD must belong in an " "interface block"); return -1; } i = D6_OPTION_IA_PD; #endif } if (ifname == NULL && 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) { logerr("%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 } if (ia->ia_type != D6_OPTION_IA_PD) break; #ifndef SMALL 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 = 0; 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 = 1; 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 == 0 && strcmp(slap->ifname, sla->ifname) == 0) { logwarnx("%s: cannot specify the " "same interface twice with " "an automatic SLA", sla->ifname); goto err_sla; } if (slap->sla_set && sla->sla_set && slap->sla == sla->sla) { logerrx("%s: cannot" " assign the same SLA %u" " more than once", sla->ifname, sla->sla); goto err_sla; } } if (sla->sla_set && sla->sla > ia->sla_max) ia->sla_max = sla->sla; } break; err_sla: ia->sla_len--; return -1; #endif #endif case O_HOSTNAME_SHORT: ifo->options |= DHCPCD_HOSTNAME | DHCPCD_HOSTNAME_SHORT; break; case O_DEV: ARG_REQUIRED; #ifdef PLUGIN_DEV if (ctx->dev_load) free(ctx->dev_load); ctx->dev_load = strdup(arg); #endif break; case O_NODEV: ifo->options &= ~DHCPCD_DEV; break; case O_DEFINE: dop = &ifo->dhcp_override; dop_len = &ifo->dhcp_override_len; /* FALLTHROUGH */ case O_DEFINEND: if (dop == NULL) { dop = &ifo->nd_override; dop_len = &ifo->nd_override_len; } /* FALLTHROUGH */ case O_DEFINE6: if (dop == NULL) { dop = &ifo->dhcp6_override; dop_len = &ifo->dhcp6_override_len; } /* FALLTHROUGH */ case O_VENDOPT: if (dop == NULL) { dop = &ifo->vivso_override; dop_len = &ifo->vivso_override_len; } *edop = *ldop = NULL; /* FALLTHROUGH */ case O_EMBED: if (dop == NULL) { if (*edop) { dop = &(*edop)->embopts; dop_len = &(*edop)->embopts_len; } else if (ldop) { dop = &(*ldop)->embopts; dop_len = &(*ldop)->embopts_len; } else { logerrx("embed must be after a define " "or encap"); return -1; } } /* FALLTHROUGH */ case O_ENCAP: ARG_REQUIRED; if (dop == NULL) { if (*ldop == NULL) { logerrx("encap must be after a define"); return -1; } dop = &(*ldop)->encopts; dop_len = &(*ldop)->encopts_len; } /* Shared code for define, define6, embed and encap */ /* code */ if (opt == O_EMBED) /* Embedded options don't have codes */ u = 0; else { fp = strwhite(arg); if (fp == NULL) { logerrx("invalid syntax: %s", arg); return -1; } *fp++ = '\0'; u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerrx("invalid code: %s", arg); return -1; } arg = strskipwhite(fp); if (arg == NULL) { logerrx("invalid syntax"); return -1; } } /* type */ fp = strwhite(arg); if (fp) *fp++ = '\0'; np = strchr(arg, ':'); /* length */ if (np) { *np++ = '\0'; bp = NULL; /* No bitflag */ l = (long)strtou(np, NULL, 0, 0, LONG_MAX, &e); if (e) { logerrx("failed to convert length"); return -1; } } else { l = 0; bp = strchr(arg, '='); /* bitflag assignment */ if (bp) *bp++ = '\0'; } t = 0; if (strcasecmp(arg, "request") == 0) { t |= OT_REQUEST; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { logerrx("incomplete request type"); return -1; } *fp++ = '\0'; } else if (strcasecmp(arg, "norequest") == 0) { t |= OT_NOREQ; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { logerrx("incomplete request type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "optional") == 0) { t |= OT_OPTIONAL; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { logerrx("incomplete optional type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "index") == 0) { t |= OT_INDEX; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { logerrx("incomplete index type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "array") == 0) { t |= OT_ARRAY; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { logerrx("incomplete array type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "ipaddress") == 0) t |= OT_ADDRIPV4; else if (strcasecmp(arg, "ip6address") == 0) t |= OT_ADDRIPV6; else if (strcasecmp(arg, "string") == 0) t |= OT_STRING; else if (strcasecmp(arg, "byte") == 0) t |= OT_UINT8; else if (strcasecmp(arg, "bitflags") == 0) t |= OT_BITFLAG; else if (strcasecmp(arg, "uint8") == 0) t |= OT_UINT8; else if (strcasecmp(arg, "int8") == 0) t |= OT_INT8; else if (strcasecmp(arg, "uint16") == 0) t |= OT_UINT16; else if (strcasecmp(arg, "int16") == 0) t |= OT_INT16; else if (strcasecmp(arg, "uint32") == 0) t |= OT_UINT32; else if (strcasecmp(arg, "int32") == 0) t |= OT_INT32; else if (strcasecmp(arg, "flag") == 0) t |= OT_FLAG; else if (strcasecmp(arg, "raw") == 0) t |= OT_STRING | OT_RAW; else if (strcasecmp(arg, "ascii") == 0) t |= OT_STRING | OT_ASCII; else if (strcasecmp(arg, "domain") == 0) t |= OT_STRING | OT_DOMAIN | OT_RFC1035; else if (strcasecmp(arg, "dname") == 0) t |= OT_STRING | OT_DOMAIN; else if (strcasecmp(arg, "binhex") == 0) t |= OT_STRING | OT_BINHEX; else if (strcasecmp(arg, "embed") == 0) t |= OT_EMBED; else if (strcasecmp(arg, "encap") == 0) t |= OT_ENCAP; else if (strcasecmp(arg, "rfc3361") ==0) t |= OT_STRING | OT_RFC3361; else if (strcasecmp(arg, "rfc3442") ==0) t |= OT_STRING | OT_RFC3442; else if (strcasecmp(arg, "option") == 0) t |= OT_OPTION; else { logerrx("unknown type: %s", arg); return -1; } if (l && !(t & (OT_STRING | OT_BINHEX))) { logwarnx("ignoring length for type `%s'", arg); l = 0; } if (t & OT_ARRAY && t & (OT_STRING | OT_BINHEX) && !(t & (OT_RFC1035 | OT_DOMAIN))) { logwarnx("ignoring array for strings"); t &= ~OT_ARRAY; } if (t & OT_BITFLAG) { if (bp == NULL) logwarnx("missing bitflag assignment"); } /* variable */ if (!fp) { if (!(t & OT_OPTION)) { logerrx("type %s requires a variable name", arg); return -1; } np = NULL; } else { arg = strskipwhite(fp); fp = strwhite(arg); if (fp) *fp++ = '\0'; if (strcasecmp(arg, "reserved")) { np = strdup(arg); if (np == NULL) { logerr(__func__); return -1; } } else { np = NULL; t |= OT_RESERVED; } } if (opt != O_EMBED) { for (dl = 0, ndop = *dop; dl < *dop_len; dl++, ndop++) { /* type 0 seems freshly malloced struct * for us to use */ if (ndop->option == u || ndop->type == 0) break; } if (dl == *dop_len) ndop = NULL; } else ndop = NULL; if (ndop == NULL) { ndop = reallocarray(*dop, *dop_len + 1, sizeof(**dop)); if (ndop == NULL) { logerr(__func__); free(np); return -1; } *dop = ndop; ndop = &(*dop)[(*dop_len)++]; ndop->embopts = NULL; ndop->embopts_len = 0; ndop->encopts = NULL; ndop->encopts_len = 0; } else free_dhcp_opt_embenc(ndop); ndop->option = (uint32_t)u; /* could have been 0 */ ndop->type = t; ndop->len = (size_t)l; ndop->var = np; if (bp) { dl = strlen(bp); memcpy(ndop->bitflags, bp, dl); memset(ndop->bitflags + dl, 0, sizeof(ndop->bitflags) - dl); } else memset(ndop->bitflags, 0, sizeof(ndop->bitflags)); /* Save the define for embed and encap options */ switch (opt) { case O_DEFINE: case O_DEFINEND: case O_DEFINE6: case O_VENDOPT: *ldop = ndop; break; case O_ENCAP: *edop = ndop; break; } break; case O_VENDCLASS: ARG_REQUIRED; fp = strwhite(arg); if (fp) *fp++ = '\0'; u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerrx("invalid code: %s", arg); return -1; } fp = strskipwhite(fp); if (fp) { s = parse_string(NULL, 0, fp); if (s == -1) { logerr(__func__); return -1; } dl = (size_t)s; if (dl + (sizeof(uint16_t) * 2) > UINT16_MAX) { logerrx("vendor class is too big"); return -1; } np = malloc(dl); if (np == NULL) { logerr(__func__); return -1; } parse_string(np, dl, fp); } else { dl = 0; np = NULL; } vivco = reallocarray(ifo->vivco, ifo->vivco_len + 1, sizeof(*ifo->vivco)); if (vivco == NULL) { logerr( __func__); 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 = malloc(sizeof(*token)); if (token == NULL) { logerr(__func__); free(token); return -1; } if (parse_uint32(&token->secretid, arg) == -1) { logerrx("%s: not a number", arg); free(token); return -1; } arg = fp; fp = strend(arg); if (fp == NULL) { logerrx("authtoken requies an a key"); free(token); return -1; } *fp++ = '\0'; s = parse_string(NULL, 0, arg); if (s == -1) { logerr("realm_len"); free(token); return -1; } if (s) { token->realm_len = (size_t)s; token->realm = malloc(token->realm_len); if (token->realm == NULL) { logerr(__func__); free(token); return -1; } parse_string((char *)token->realm, token->realm_len, arg); } else { token->realm_len = 0; token->realm = NULL; } arg = fp; fp = strend(arg); if (fp == NULL) { logerrx("authtoken requies an expiry date"); free(token->realm); free(token); return -1; } *fp++ = '\0'; if (*arg == '"') { arg++; np = strchr(arg, '"'); if (np) *np = '\0'; } if (strcmp(arg, "0") == 0 || strcasecmp(arg, "forever") == 0) token->expire =0; else { struct tm tm; memset(&tm, 0, sizeof(tm)); if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) { logerrx("%s: invalid date time", arg); free(token->realm); free(token); return -1; } if ((token->expire = mktime(&tm)) == (time_t)-1) { logerr("%s: mktime", __func__); free(token->realm); free(token); return -1; } } arg = fp; s = parse_string(NULL, 0, arg); if (s == -1 || s == 0) { if (s == -1) logerr("token_len"); else logerrx("authtoken needs a key"); free(token->realm); free(token); return -1; } token->key_len = (size_t)s; token->key = malloc(token->key_len); parse_string((char *)token->key, token->key_len, arg); TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next); #else logerrx("no authentication support"); return -1; #endif break; case O_AUTHNOTREQUIRED: ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE; break; case O_DHCP: ifo->options |= DHCPCD_DHCP | DHCPCD_IPV4; break; case O_NODHCP: ifo->options &= ~DHCPCD_DHCP; break; case O_DHCP6: ifo->options |= DHCPCD_DHCP6 | DHCPCD_IPV6; break; case O_NODHCP6: ifo->options &= ~DHCPCD_DHCP6; break; case O_CONTROLGRP: ARG_REQUIRED; #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, (size_t)l, &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) { 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) { 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; if (strcmp(arg, "private") == 0 || strcmp(arg, "stableprivate") == 0 || strcmp(arg, "stable") == 0) ifo->options |= DHCPCD_SLAACPRIVATE; else ifo->options &= ~DHCPCD_SLAACPRIVATE; break; case O_BOOTP: ifo->options |= DHCPCD_BOOTP; break; case O_NODELAY: ifo->options &= ~DHCPCD_INITIAL_DELAY; break; 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; 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); } 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; } } /* Handy routine to read very long lines in text files. * This means we read the whole line and avoid any nasty buffer overflows. * We strip leading space and avoid comment lines, making the code that calls * us smaller. */ static char * get_line(char ** __restrict buf, size_t * __restrict buflen, FILE * __restrict fp) { char *p, *c; ssize_t bytes; int quoted; do { bytes = getline(buf, buflen, fp); if (bytes == -1) return NULL; for (p = *buf; *p == ' ' || *p == '\t'; p++) ; } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';'); if ((*buf)[--bytes] == '\n') (*buf)[bytes] = '\0'; /* Strip embedded comments unless in a quoted string or escaped */ quoted = 0; for (c = p; *c != '\0'; c++) { if (*c == '\\') { c++; /* escaped */ continue; } if (*c == '"') quoted = !quoted; else if (*c == '#' && !quoted) { *c = '\0'; break; } } return p; } struct if_options * default_config(struct dhcpcd_ctx *ctx) { struct if_options *ifo; /* Seed our default options */ if ((ifo = calloc(1, sizeof(*ifo))) == NULL) { logerr(__func__); return NULL; } ifo->options |= DHCPCD_IF_UP | DHCPCD_LINK | DHCPCD_INITIAL_DELAY; ifo->timeout = DEFAULT_TIMEOUT; ifo->reboot = DEFAULT_REBOOT; ifo->metric = -1; ifo->auth.options |= DHCPCD_AUTH_REQUIRE; TAILQ_INIT(&ifo->routes); #ifdef AUTH TAILQ_INIT(&ifo->auth.tokens); #endif /* Inherit some global defaults */ 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; FILE *fp; struct stat sb; char *line, *buf, *option, *p; size_t buflen; ssize_t vlen; int skip, have_profile, new_block, had_block; #ifndef EMBEDDED_CONFIG const char * const *e; size_t ol; #endif #if !defined(INET) || !defined(INET6) size_t i; struct dhcp_opt *opt; #endif struct dhcp_opt *ldop, *edop; /* Seed our default options */ if ((ifo = default_config(ctx)) == NULL) return NULL; ifo->options |= DHCPCD_DAEMONISE | DHCPCD_GATEWAY; #ifdef PLUGIN_DEV ifo->options |= DHCPCD_DEV; #endif #ifdef INET ifo->options |= DHCPCD_IPV4 | DHCPCD_ARP | DHCPCD_DHCP | DHCPCD_IPV4LL; #endif #ifdef INET6 ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS; ifo->options |= DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS; ifo->options |= DHCPCD_DHCP6; #endif vlen = dhcp_vendor((char *)ifo->vendorclassid + 1, sizeof(ifo->vendorclassid) - 1); ifo->vendorclassid[0] = (uint8_t)(vlen == -1 ? 0 : vlen); buf = NULL; buflen = 0; /* Parse our embedded options file */ if (ifname == NULL && !(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 fp = fopen(EMBEDDED_CONFIG, "r"); if (fp == NULL) logerr("%s: fopen `%s'", __func__, EMBEDDED_CONFIG); while (fp && (line = get_line(&buf, &buflen, fp))) { #else buflen = 80; buf = malloc(buflen); if (buf == NULL) { logerr(__func__); free_options(ctx, ifo); return NULL; } ldop = edop = NULL; for (e = dhcpcd_embedded_conf; *e; e++) { ol = strlen(*e) + 1; if (ol > buflen) { char *nbuf; buflen = ol; nbuf = realloc(buf, buflen); if (nbuf == NULL) { logerr(__func__); free(buf); free_options(ctx, ifo); return NULL; } buf = nbuf; } memcpy(buf, *e, ol); line = buf; #endif 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 EMBEDDED_CONFIG if (fp) fclose(fp); #endif #ifdef INET ctx->dhcp_opts = ifo->dhcp_override; ctx->dhcp_opts_len = ifo->dhcp_override_len; #else for (i = 0, opt = ifo->dhcp_override; i < ifo->dhcp_override_len; i++, opt++) free_dhcp_opt_embenc(opt); free(ifo->dhcp_override); #endif ifo->dhcp_override = NULL; ifo->dhcp_override_len = 0; #ifdef INET6 ctx->nd_opts = ifo->nd_override; ctx->nd_opts_len = ifo->nd_override_len; ctx->dhcp6_opts = ifo->dhcp6_override; ctx->dhcp6_opts_len = ifo->dhcp6_override_len; #else for (i = 0, opt = ifo->nd_override; i < ifo->nd_override_len; i++, opt++) free_dhcp_opt_embenc(opt); free(ifo->nd_override); for (i = 0, opt = ifo->dhcp6_override; i < ifo->dhcp6_override_len; i++, opt++) free_dhcp_opt_embenc(opt); free(ifo->dhcp6_override); #endif ifo->nd_override = NULL; ifo->nd_override_len = 0; ifo->dhcp6_override = NULL; ifo->dhcp6_override_len = 0; ctx->vivso = ifo->vivso_override; ctx->vivso_len = ifo->vivso_override_len; ifo->vivso_override = NULL; ifo->vivso_override_len = 0; } /* Parse our options file */ fp = fopen(ctx->cffile, "r"); if (fp == NULL) { /* dhcpcd can continue without it, but no DNS options * would be requested ... */ logwarn("%s: fopen `%s'", __func__, ctx->cffile); free(buf); return ifo; } if (stat(ctx->cffile, &sb) == 0) ifo->mtime = sb.st_mtime; ldop = edop = NULL; skip = have_profile = new_block = 0; had_block = ifname == NULL ? 1 : 0; while ((line = get_line(&buf, &buflen, fp))) { option = strsep(&line, " \t"); if (line) line = strskipwhite(line); /* Trim trailing whitespace */ if (line) { p = line + strlen(line) - 1; while (p != line && (*p == ' ' || *p == '\t') && *(p - 1) != '\\') *p-- = '\0'; } if (skip == 0 && new_block) { had_block = 1; new_block = 0; ifo->options &= ~DHCPCD_WAITOPTS; } /* Start of an interface block, skip if not ours */ if (strcmp(option, "interface") == 0) { char **n; new_block = 1; if (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); } fclose(fp); free(buf); if (profile && !have_profile) { free_options(ctx, ifo); errno = ENOENT; return NULL; } if (!had_block) ifo->options &= ~DHCPCD_WAITOPTS; finish_config(ifo); return ifo; } int add_options(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, int argc, char **argv) { int oi, opt, r; unsigned long long wait_opts; if (argc == 0) return 1; optind = 0; r = 1; /* Don't apply the command line wait options to each interface, * only use the dhcpcd.conf entry for that. */ if (ifname != NULL) wait_opts = ifo->options & DHCPCD_WAITOPTS; while ((opt = getopt_long(argc, argv, 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; struct dhcp_opt *opt; struct vivco *vo; #ifdef AUTH struct token *token; #endif if (ifo) { if (ifo->environ) { i = 0; while (ifo->environ[i]) free(ifo->environ[i++]); free(ifo->environ); } if (ifo->config) { i = 0; while (ifo->config[i]) free(ifo->config[i++]); free(ifo->config); } rt_headclear0(ctx, &ifo->routes, AF_UNSPEC); free(ifo->script); free(ifo->arping); free(ifo->blacklist); free(ifo->fallback); for (opt = ifo->dhcp_override; ifo->dhcp_override_len > 0; opt++, ifo->dhcp_override_len--) free_dhcp_opt_embenc(opt); free(ifo->dhcp_override); for (opt = ifo->nd_override; ifo->nd_override_len > 0; opt++, ifo->nd_override_len--) free_dhcp_opt_embenc(opt); free(ifo->nd_override); for (opt = ifo->dhcp6_override; ifo->dhcp6_override_len > 0; opt++, ifo->dhcp6_override_len--) free_dhcp_opt_embenc(opt); free(ifo->dhcp6_override); for (vo = ifo->vivco; ifo->vivco_len > 0; vo++, ifo->vivco_len--) free(vo->data); free(ifo->vivco); for (opt = ifo->vivso_override; ifo->vivso_override_len > 0; opt++, ifo->vivso_override_len--) free_dhcp_opt_embenc(opt); free(ifo->vivso_override); #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); } } dhcpcd5-7.1.0/src/if-options.h000066400000000000000000000157721342162717100161000ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef IF_OPTIONS_H #define IF_OPTIONS_H #include #include #include #include #include #include #include #include "auth.h" #include "route.h" /* Don't set any optional arguments here so we retain POSIX * compatibility with getopt */ #define IF_OPTS "146bc:de:f:gh:i:j:kl:m:no:pqr:s:t:u:v:wxy:z:" \ "ABC:DEF:GHI:JKLMNO:PQ:S:TUVW:X:Z:" #define NOERR_IF_OPTS ":" IF_OPTS #define DEFAULT_TIMEOUT 30 #define DEFAULT_REBOOT 5 #ifndef HOSTNAME_MAX_LEN #define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ #endif #define VENDORCLASSID_MAX_LEN 255 #define CLIENTID_MAX_LEN 48 #define USERCLASS_MAX_LEN 255 #define VENDOR_MAX_LEN 255 #define MUDURL_MAX_LEN 255 #define DHCPCD_ARP (1ULL << 0) #define DHCPCD_RELEASE (1ULL << 1) // unused (1ULL << 2) #define DHCPCD_GATEWAY (1ULL << 3) #define DHCPCD_STATIC (1ULL << 4) #define DHCPCD_DEBUG (1ULL << 5) #define DHCPCD_LASTLEASE (1ULL << 7) #define DHCPCD_INFORM (1ULL << 8) #define DHCPCD_REQUEST (1ULL << 9) #define DHCPCD_IPV4LL (1ULL << 10) #define DHCPCD_DUID (1ULL << 11) #define DHCPCD_PERSISTENT (1ULL << 12) #define DHCPCD_DAEMONISE (1ULL << 14) #define DHCPCD_DAEMONISED (1ULL << 15) #define DHCPCD_TEST (1ULL << 16) #define DHCPCD_MASTER (1ULL << 17) #define DHCPCD_HOSTNAME (1ULL << 18) #define DHCPCD_CLIENTID (1ULL << 19) #define DHCPCD_LINK (1ULL << 20) // unused (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) // unused (1ULL << 33) // unused (1ULL << 34) #define DHCPCD_IPV4 (1ULL << 35) #define DHCPCD_FORKED (1ULL << 36) #define DHCPCD_IPV6 (1ULL << 37) #define DHCPCD_STARTED (1ULL << 38) #define DHCPCD_NOALIAS (1ULL << 39) #define DHCPCD_IA_FORCED (1ULL << 40) #define DHCPCD_STOPPING (1ULL << 41) #define DHCPCD_DEPARTED (1ULL << 42) #define DHCPCD_HOSTNAME_SHORT (1ULL << 43) #define DHCPCD_EXITING (1ULL << 44) #define DHCPCD_WAITIP4 (1ULL << 45) #define DHCPCD_WAITIP6 (1ULL << 46) #define DHCPCD_DEV (1ULL << 47) #define DHCPCD_IAID (1ULL << 48) #define DHCPCD_DHCP (1ULL << 49) #define DHCPCD_DHCP6 (1ULL << 50) #define DHCPCD_IF_UP (1ULL << 51) #define DHCPCD_INFORM6 (1ULL << 52) // unused (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_NODROP (DHCPCD_EXITING | DHCPCD_PERSISTENT) #define DHCPCD_WAITOPTS (DHCPCD_WAITIP | DHCPCD_WAITIP4 | DHCPCD_WAITIP6) #define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \ DHCPCD_ROUTER_HOST_ROUTE_WARNED) extern const struct option cf_options[]; struct if_sla { char ifname[IF_NAMESIZE]; uint32_t sla; uint8_t prefix_len; uint64_t suffix; int8_t sla_set; }; struct if_ia { uint8_t iaid[4]; #ifdef INET6 uint16_t ia_type; uint8_t iaid_set; struct in6_addr addr; uint8_t prefix_len; #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; time_t timeout; time_t reboot; unsigned long long options; struct in_addr req_addr; struct in_addr req_mask; struct in_addr req_brd; struct rt_head routes; struct in6_addr req_addr6; uint8_t req_prefix_len; unsigned int mtu; char **config; char **environ; char *script; char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the length */ uint8_t fqdn; uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 2]; uint8_t clientid[CLIENTID_MAX_LEN + 2]; uint8_t userclass[USERCLASS_MAX_LEN + 2]; uint8_t vendor[VENDOR_MAX_LEN + 2]; uint8_t mudurl[MUDURL_MAX_LEN + 2]; size_t blacklist_len; in_addr_t *blacklist; size_t whitelist_len; in_addr_t *whitelist; ssize_t arping_len; in_addr_t *arping; char *fallback; struct if_ia *ia; size_t ia_len; struct dhcp_opt *dhcp_override; size_t dhcp_override_len; struct dhcp_opt *nd_override; size_t nd_override_len; struct dhcp_opt *dhcp6_override; size_t dhcp6_override_len; uint32_t vivco_en; struct vivco *vivco; size_t vivco_len; struct dhcp_opt *vivso_override; size_t vivso_override_len; struct auth auth; }; struct if_options *default_config(struct dhcpcd_ctx *); struct if_options *read_config(struct dhcpcd_ctx *, const char *, const char *, const char *); int add_options(struct dhcpcd_ctx *, const char *, struct if_options *, int, char **); void free_dhcp_opt_embenc(struct dhcp_opt *); void free_options(struct dhcpcd_ctx *, struct if_options *); #endif dhcpcd5-7.1.0/src/if-sun.c000066400000000000000000000756061342162717100152070ustar00rootroot00000000000000/* * Solaris interface driver for dhcpcd * Copyright (c) 2016-2019 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 /* 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 "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(long) - 1))) : sizeof(long)) #define RT_ADVANCE(x, n) (x += RT_ROUNDUP(salen(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), salen((src))) struct priv { #ifdef INET6 int pf_inet6_fd; #endif }; int if_init(__unused struct interface *ifp) { return 0; } int if_conf(__unused struct interface *ifp) { return 0; } int if_opensockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; int n; if ((priv = malloc(sizeof(*priv))) == NULL) return -1; ctx->priv = priv; #ifdef INET6 priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); /* Don't return an error so we at least work on kernels witout INET6 * even though we expect INET6 support. * We will fail noisily elsewhere anyway. */ #endif ctx->link_fd = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); if (ctx->link_fd == -1) { free(ctx->priv); return -1; } /* Ignore our own route(4) messages. * Sadly there is no way of doing this for route(4) messages * generated from addresses we add/delete. */ n = 0; if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK, &n, sizeof(n)) == -1) logerr("%s: SO_USELOOPBACK", __func__); return 0; } void if_closesockets_os(struct dhcpcd_ctx *ctx) { #ifdef INET6 struct priv *priv; priv = (struct priv *)ctx->priv; if (priv->pf_inet6_fd != -1) close(priv->pf_inet6_fd); #endif /* each interface should have closed itself */ free(ctx->priv); } int if_getssid(struct interface *ifp) { UNUSED(ifp); errno = ENOTSUP; return -1; } unsigned short if_vlanid(__unused const struct interface *ifp) { return 0; } int if_vimaster(__unused const 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; 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; if (dlpi_open(ifname, &dh, 0) != 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 int salen(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_LINK: return sizeof(struct sockaddr_dl); case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); default: return sizeof(struct sockaddr); } } 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, const struct sockaddr **sa) { const char *cp; int i; cp = data; for (i = 0; i < RTAX_MAX; i++) { if (type & (1 << i)) { 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; 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_finishrt(struct rt *rt) { /* 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_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_iffindaddr(rt->rt_ifp, in, NULL)) 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_iffindaddr(rt->rt_ifp, in6, 0)) *in6 = in6addr_any; break; } #endif } } /* 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. */ if (rt->rt_mtu == (unsigned int)if_getmtu(rt->rt_ifp)) rt->rt_mtu = 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 | RTA_GATEWAY)) return -1; get_addrs(rtm->rtm_addrs, rtm + 1, rti_info); 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. */ if (rt->rt_flags & RTF_GATEWAY && rti_info[RTAX_GATEWAY]->sa_family != AF_LINK) COPYSA(&rt->rt_gateway, rti_info[RTAX_GATEWAY]); 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) { errno = ESRCH; return -1; } return 0; } static int if_addrflags0(int fd, const char *ifname) { struct lifreq lifr; memset(&lifr, 0, sizeof(lifr)); strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) return -1; return lifr.lifr_flags; } static void if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) { const struct sockaddr *sa; struct rt rt; sa = (const void *)(rtm + 1); switch (sa->sa_family) { #ifdef INET6 case AF_INET6: if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) break; /* * BSD announces host routes. * But does this work on Solaris? * As such, we should be notified of reachability by its * existance with a hardware address. */ if (rtm->rtm_flags & (RTF_HOST)) { const struct sockaddr *rti_info[RTAX_MAX]; struct in6_addr dst6; struct sockaddr_dl sdl; get_addrs(rtm->rtm_addrs, sa, rti_info); COPYOUT6(dst6, rti_info[RTAX_DST]); if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) memcpy(&sdl, rti_info[RTAX_GATEWAY], sizeof(sdl)); else sdl.sdl_alen = 0; ipv6nd_neighbour(ctx, &dst6, rtm->rtm_type != RTM_DELETE && sdl.sdl_alen ? IPV6ND_REACHABLE : 0); } break; } #endif if (if_copyrt(ctx, &rt, rtm) == 0) { if_finishrt(&rt); rt_recvrt(rtm->rtm_type, &rt); } } static void if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam) { struct interface *ifp; const struct sockaddr *sa, *rti_info[RTAX_MAX]; int flags; const char *ifalias; /* 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; sa = (const void *)(ifam + 1); get_addrs(ifam->ifam_addrs, sa, rti_info); if ((sa = rti_info[RTAX_IFA]) == NULL) return; /* * 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 */ ifalias = ifp->name; if (ifam->ifam_type != RTM_DELADDR && sa->sa_family != AF_LINK) { struct ifaddrs *ifaddrs, *ifa; ifaddrs = NULL; if (getallifaddrs(sa->sa_family, &ifaddrs, 0) == -1) return; for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr != NULL) { if (sa_cmp(sa, ifa->ifa_addr) == 0) { /* Check it's for the right interace. */ struct interface *ifpx; ifpx = if_find(ctx->ifaces, ifa->ifa_name); if (ifp == ifpx) { ifalias = ifa->ifa_name; break; } } } } freeifaddrs(ifaddrs); } 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(ctx, ifp->name, 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) { flags = if_addrflags0(ctx->pf_inet_fd, ifalias); if (flags == -1) break; } else flags = 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) { const struct priv *priv; priv = (struct priv *)ctx->priv; flags = if_addrflags0(priv->pf_inet6_fd, ifalias); if (flags == -1) break; } else flags = 0; ipv6_handleifa(ctx, ifam->ifam_type == RTM_CHGADDR ? RTM_NEWADDR : ifam->ifam_type, NULL, ifalias, &addr6, ipv6_prefixlen(&mask6), flags, 0); break; } #endif } } static void if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm) { struct interface *ifp; int state; if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL) return; if (ifm->ifm_flags & IFF_OFFLINE || !(ifm->ifm_flags & IFF_UP)) state = LINK_DOWN; else state = LINK_UP; dhcpcd_handlecarrier(ctx, state, (unsigned int)ifm->ifm_flags, ifp->name); } static void if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) { if (rtm->rtm_version != RTM_VERSION) return; switch(rtm->rtm_type) { case RTM_IFINFO: if_ifinfo(ctx, (const void *)rtm); break; case RTM_ADD: /* FALLTHROUGH */ case RTM_CHANGE: /* FALLTHROUGH */ case RTM_DELETE: if_rtm(ctx, (const void *)rtm); break; case RTM_CHGADDR: /* FALLTHROUGH */ case RTM_DELADDR: /* FALLTHROUGH */ case RTM_NEWADDR: if_ifa(ctx, (const void *)rtm); break; } } int if_handlelink(struct dhcpcd_ctx *ctx) { struct msghdr msg; ssize_t len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = ctx->iov; msg.msg_iovlen = 1; if ((len = recvmsg_realloc(ctx->link_fd, &msg, 0)) == -1) return -1; if (len != 0) if_dispatch(ctx, ctx->iov[0].iov_base); return 0; } 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_addaddr(int fd, const char *ifname, struct sockaddr_storage *addr, struct sockaddr_storage *mask) { struct lifreq lifr; memset(&lifr, 0, sizeof(lifr)); strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); /* First assign the netmask. */ lifr.lifr_addr = *mask; if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) return -1; /* Then assign the address. */ lifr.lifr_addr = *addr; if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1) return -1; /* Now bring it up. */ if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) return -1; if (!(lifr.lifr_flags & IFF_UP)) { lifr.lifr_flags |= IFF_UP; if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) return -1; } 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; if (af == AF_INET) s = ctx->pf_inet_fd; else { struct priv *priv; priv = (struct priv *)ctx->priv; s = priv->pf_inet6_fd; } 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; int fd, af_fd, mux_fd, retval; struct lifreq lifr; const char *udp_dev; memset(&lifr, 0, sizeof(lifr)); switch (af) { case AF_INET: lifr.lifr_flags = IFF_IPV4; af_fd = ctx->pf_inet_fd; udp_dev = UDP_DEV_NAME; break; case AF_INET6: { struct priv *priv; /* We will take care of setting the link local address. */ lifr.lifr_flags = IFF_IPV6 | IFF_NOLINKLOCAL; priv = (struct priv *)ctx->priv; af_fd = priv->pf_inet6_fd; 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; strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1) goto out; /* Get full flags. */ if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1) goto out; /* 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 (lifr.lifr_flags & IFF_IPV4 && !(lifr.lifr_flags & IFF_NOARP)) { if (ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1) goto out; } /* PLINK the interface stream so it persists. */ if (ioctl(mux_fd, I_PLINK, fd) == -1) goto out; retval = 0; out: dlpi_close(dh); 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, mask; int fd; /* For the time being, don't unplumb the interface, just * set the address to zero. */ memset(&addr, 0, sizeof(addr)); addr.ss_family = af; memset(&mask, 0, sizeof(mask)); mask.ss_family = af; switch (af) { #ifdef INET case AF_INET: fd = ctx->pf_inet_fd; break; #endif #ifdef INET6 case AF_INET6: { struct priv *priv; priv = (struct priv *)ctx->priv; fd = priv->pf_inet6_fd; break; } #endif default: errno = EAFNOSUPPORT; return -1; } return if_addaddr(fd, ifname, &addr, &mask); } 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); } int if_route(unsigned char cmd, const struct rt *rt) { struct dhcpcd_ctx *ctx; struct rtm { struct rt_msghdr hdr; char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; } rtmsg; struct rt_msghdr *rtm; char *bp = rtmsg.buffer; size_t l; /* WARNING: Solaris will not allow you to delete RTF_KERNEL routes. * This includes subnet/prefix routes. */ ctx = rt->rt_ifp->ctx; #define ADDSA(sa) do { \ l = RT_ROUNDUP(salen((sa))); \ memcpy(bp, (sa), l); \ bp += l; \ } 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; 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; if (!sa_is_unspecified(&rt->rt_ifa)) rtm->rtm_addrs |= RTA_IFA; } if (netmask_bcast) rtm->rtm_flags |= RTF_HOST; else rtm->rtm_flags |= RTF_GATEWAY; /* Emulate the kernel by marking address generated * network routes non-static. */ if (!(rt->rt_dflags & RTDF_IFA_ROUTE)) rtm->rtm_flags |= RTF_STATIC; if (rt->rt_mtu != 0) { rtm->rtm_inits |= RTV_MTU; rtm->rtm_rmx.rmx_mtu = rt->rt_mtu; } } ADDSA(&rt->rt_dest); if (sa_is_unspecified(&rt->rt_gateway)) 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); } /* This no workie :/ */ #if 1 /* route(1M) says RTA_IFA is accepted but ignored * it's unclear how RTA_SRC is different. */ if (rtm->rtm_addrs & RTA_IFA) { rtm->rtm_addrs &= ~RTA_IFA; rtm->rtm_addrs |= RTA_SRC; } if (rtm->rtm_addrs & RTA_SRC) ADDSA(&rt->rt_ifa); #endif #undef ADDSA rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm); if (write(ctx->link_fd, rtm, rtm->rtm_msglen) == -1) return -1; return 0; } #ifdef INET static int if_walkrt(struct dhcpcd_ctx *ctx, char *data, size_t len) { mib2_ipRouteEntry_t *re, *e; struct rt rt; 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)); rt.rt_dflags |= RTDF_INIT; 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 (rt.rt_ifp != NULL) { if_finishrt(&rt); rt_recvrt(RTM_ADD, &rt); } } while (++re < e); return 0; } #endif #ifdef INET6 static int if_walkrt6(struct dhcpcd_ctx *ctx, char *data, size_t len) { mib2_ipv6RouteEntry_t *re, *e; struct rt rt; 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)); rt.rt_dflags |= RTDF_INIT; 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); rt.rt_mtu = re->ipv6RouteInfo.re_max_frag; if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname)); rt.rt_ifp = if_find(ctx->ifaces, ifname); if (rt.rt_ifp != NULL) { if_finishrt(&rt); rt_recvrt(RTM_ADD, &rt); } } while (++re < e); return 0; } #endif static int if_parsert(struct dhcpcd_ctx *ctx, unsigned int level, unsigned int name, int (*walkrt)(struct dhcpcd_ctx *, 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, databuf.buf, req->len) == -1) break; } } free(databuf.buf); out: close(s); return retval; } int if_initrt(struct dhcpcd_ctx *ctx, int af) { rt_headclear(&ctx->kroutes, af); #ifdef INET if ((af == AF_UNSPEC || af == AF_INET) && if_parsert(ctx, MIB2_IP,MIB2_IP_ROUTE, if_walkrt) == -1) return -1; #endif #ifdef INET6 if ((af == AF_UNSPEC || af == AF_INET6) && if_parsert(ctx, 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 interface *ifp, __unused int fd, uint16_t protocol, const void *data, size_t len) { 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) { struct sockaddr_storage ss_addr, ss_mask; struct sockaddr_in *sin_addr, *sin_mask; /* Either remove the alias or ensure it exists. */ if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1) return -1; if (cmd == RTM_DELADDR) return 0; if (cmd != RTM_NEWADDR) { errno = EINVAL; return -1; } sin_addr = (struct sockaddr_in *)&ss_addr; sin_addr->sin_family = AF_INET; sin_addr->sin_addr = ia->addr; sin_mask = (struct sockaddr_in *)&ss_mask; sin_mask->sin_family = AF_INET; sin_mask->sin_addr = ia->mask; return if_addaddr(ia->iface->ctx->pf_inet_fd, ia->alias, &ss_addr, &ss_mask); } int if_addrflags(const struct interface *ifp, __unused const struct in_addr *addr, const char *alias) { int flags, aflags; aflags = if_addrflags0(ifp->ctx->pf_inet_fd, alias); if (aflags == -1) return -1; flags = 0; if (aflags & IFF_DUPLICATE) flags |= IN_IFF_DUPLICATED; return flags; } #endif #ifdef INET6 int if_address6(unsigned char cmd, const struct ipv6_addr *ia) { struct sockaddr_storage ss_addr, ss_mask; struct sockaddr_in6 *sin6_addr, *sin6_mask; struct priv *priv; int r; /* Either remove the alias or ensure it exists. */ if (if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias) == -1) return -1; if (cmd == RTM_DELADDR) return 0; if (cmd != RTM_NEWADDR) { errno = EINVAL; return -1; } priv = (struct priv *)ia->iface->ctx->priv; sin6_addr = (struct sockaddr_in6 *)&ss_addr; sin6_addr->sin6_family = AF_INET6; sin6_addr->sin6_addr = ia->addr; sin6_mask = (struct sockaddr_in6 *)&ss_mask; sin6_mask->sin6_family = AF_INET6; ipv6_mask(&sin6_mask->sin6_addr, ia->prefix_len); r = if_addaddr(priv->pf_inet6_fd, ia->alias, &ss_addr, &ss_mask); if (r == -1 && errno == EEXIST) return 0; return r; } int if_addrflags6(const struct interface *ifp, __unused const struct in6_addr *addr, const char *alias) { struct priv *priv; int aflags, flags; priv = (struct priv *)ifp->ctx->priv; aflags = if_addrflags0(priv->pf_inet6_fd, alias); flags = 0; if (aflags & IFF_DUPLICATE) flags |= IN6_IFF_DUPLICATED; return flags; } int if_getlifetime6(struct ipv6_addr *addr) { UNUSED(addr); errno = ENOTSUP; return -1; } void if_setup_inet6(__unused const struct interface *ifp) { } #endif dhcpcd5-7.1.0/src/if.c000066400000000000000000000454571342162717100144050ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 #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 #include "common.h" #include "dev.h" #include "dhcp.h" #include "dhcp6.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv4ll.h" #include "ipv6nd.h" #include "logerr.h" void if_free(struct interface *ifp) { if (ifp == NULL) return; ipv4ll_free(ifp); dhcp_free(ifp); ipv4_free(ifp); dhcp6_free(ifp); ipv6nd_free(ifp); ipv6_free(ifp); 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; /* 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; #ifdef IFLR_ACTIVE ctx->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (ctx->pf_link_fd == -1) return -1; #endif return 0; } void if_closesockets(struct dhcpcd_ctx *ctx) { if (ctx->pf_inet_fd != -1) close(ctx->pf_inet_fd); #ifdef IFLR_ACTIVE if (ctx->pf_link_fd != -1) close(ctx->pf_link_fd); #endif if (ctx->priv) { if_closesockets_os(ctx); free(ctx->priv); } } int if_carrier(struct interface *ifp) { int r; struct ifreq ifr; #ifdef SIOCGIFMEDIA struct ifmediareq ifmr; #endif memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1) return LINK_UNKNOWN; ifp->flags = (unsigned int)ifr.ifr_flags; #ifdef SIOCGIFMEDIA memset(&ifmr, 0, sizeof(ifmr)); strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name)); if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr) != -1 && ifmr.ifm_status & IFM_AVALID) r = (ifmr.ifm_status & IFM_ACTIVE) ? LINK_UP : LINK_DOWN; else r = ifr.ifr_flags & IFF_RUNNING ? LINK_UP : LINK_UNKNOWN; #else r = ifr.ifr_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN; #endif return r; } int if_setflag(struct interface *ifp, short flag) { struct ifreq ifr; int r; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); r = -1; if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == 0) { if (flag == 0 || (ifr.ifr_flags & flag) == flag) r = 0; else { ifr.ifr_flags |= flag; if (ioctl(ifp->ctx->pf_inet_fd, SIOCSIFFLAGS, &ifr) ==0) r = 0; } ifp->flags = (unsigned int)ifr.ifr_flags; } return r; } static int if_hasconf(struct dhcpcd_ctx *ctx, const char *ifname) { int i; for (i = 0; i < ctx->ifcc; i++) { if (strcmp(ctx->ifcv[i], ifname) == 0) return 1; } return 0; } 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) logerr("%s: if_addrflags", __func__); 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) logerr("%s: if_addrflags6", __func__); continue; } #endif ipv6_handleifa(ctx, RTM_NEWADDR, ifs, ifa->ifa_name, &sin6->sin6_addr, ipv6_prefixlen(&net6->sin6_addr), addrflags, 0); break; #endif } } freeifaddrs(*ifaddrs); *ifaddrs = NULL; } void if_deletestaleaddrs(struct if_head *ifs) { struct interface *ifp; TAILQ_FOREACH(ifp, ifs, next) { #ifdef INET ipv4_deletestaleaddrs(ifp); #endif #ifdef INET6 ipv6_deletestaleaddrs(ifp); #endif } } bool if_valid_hwaddr(const uint8_t *hwaddr, size_t hwlen) { size_t i; bool all_zeros, all_ones; all_zeros = all_ones = true; for (i = 0; i < hwlen; i++) { if (hwaddr[i] != 0x00) all_zeros = false; if (hwaddr[i] != 0xff) all_ones = false; if (!all_zeros && !all_ones) return true; } return false; } 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; #ifdef AF_LINK const struct sockaddr_dl *sdl; #ifdef SIOCGIFPRIORITY struct ifreq ifr; #endif #ifdef IFLR_ACTIVE struct if_laddrreq iflr; #endif #ifdef IFLR_ACTIVE memset(&iflr, 0, sizeof(iflr)); #endif #elif AF_PACKET const struct sockaddr_ll *sll; #endif if ((ifs = malloc(sizeof(*ifs))) == NULL) { logerr(__func__); return NULL; } TAILQ_INIT(ifs); if (getifaddrs(ifaddrs) == -1) { logerr(__func__); goto out; } for (ifa = *ifaddrs; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr != NULL) { #ifdef AF_LINK if (ifa->ifa_addr->sa_family != AF_LINK) continue; #elif AF_PACKET if (ifa->ifa_addr->sa_family != AF_PACKET) continue; #endif } 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_initialized(ctx, spec.devname)) continue; #endif /* Don't allow loopback or pointopoint unless explicit */ if (ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) { if ((argc == 0 || argc == -1) && ctx->ifac == 0 && !if_hasconf(ctx, spec.devname)) active = IF_INACTIVE; } if (if_vimaster(ctx, spec.devname) == 1) { logfunc_t *logfunc = argc != 0 ? logerrx : logdebugx; logfunc("%s: is a Virtual Interface Master, skipping", spec.devname); continue; } 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_PPP case IFT_PPP: /* FALLTHROUGH */ #endif #ifdef IFT_PROPVIRTUAL case IFT_PROPVIRTUAL: /* FALLTHROUGH */ #endif #if defined(IFT_BRIDGE) || defined(IFT_PPP) || defined(IFT_PROPVIRTUAL) /* Don't allow unless explicit */ if ((argc == 0 || argc == -1) && ctx->ifac == 0 && active && !if_hasconf(ctx, ifp->name)) { logdebugx("%s: ignoring due to" " interface type and" " no config", ifp->name); active = IF_INACTIVE; } /* FALLTHROUGH */ #endif #ifdef IFT_L2VLAN case IFT_L2VLAN: /* FALLTHROUGH */ #endif #ifdef IFT_L3IPVLAN case IFT_L3IPVLAN: /* FALLTHROUGH */ #endif case IFT_ETHER: ifp->family = ARPHRD_ETHER; break; #ifdef IFT_IEEE1394 case IFT_IEEE1394: ifp->family = ARPHRD_IEEE1394; break; #endif #ifdef IFT_INFINIBAND case IFT_INFINIBAND: ifp->family = ARPHRD_INFINIBAND; break; #endif default: /* Don't allow unless explicit */ if ((argc == 0 || argc == -1) && ctx->ifac == 0 && !if_hasconf(ctx, ifp->name)) active = IF_INACTIVE; if (active) logwarnx("%s: unsupported" " interface type %.2x", ifp->name, sdl->sdl_type); /* Pretend it's ethernet */ ifp->family = ARPHRD_ETHER; break; } ifp->hwlen = sdl->sdl_alen; memcpy(ifp->hwaddr, CLLADDR(sdl), ifp->hwlen); #elif AF_PACKET sll = (const void *)ifa->ifa_addr; ifp->index = (unsigned int)sll->sll_ifindex; ifp->family = sll->sll_hatype; ifp->hwlen = sll->sll_halen; if (ifp->hwlen != 0) memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen); #endif } #ifdef __linux__ /* PPP addresses on Linux don't have hardware addresses */ else ifp->index = if_nametoindex(ifp->name); #endif /* Ensure hardware address is valid. */ if (!if_valid_hwaddr(ifp->hwaddr, ifp->hwlen)) ifp->hwlen = 0; /* We only work on ethernet by default */ if (ifp->family != ARPHRD_ETHER) { if ((argc == 0 || argc == -1) && ctx->ifac == 0 && !if_hasconf(ctx, ifp->name)) active = IF_INACTIVE; switch (ifp->family) { case ARPHRD_IEEE1394: case ARPHRD_INFINIBAND: #ifdef ARPHRD_LOOPBACK case ARPHRD_LOOPBACK: #endif #ifdef ARPHRD_PPP case ARPHRD_PPP: #endif /* We don't warn for supported families */ break; /* IFT already checked */ #ifndef AF_LINK default: if (active) logwarnx("%s: unsupported" " interface family %.2x", ifp->name, ifp->family); break; #endif } } if (!(ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) { /* Handle any platform init for the interface */ if (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 (ioctl(ctx->pf_inet_fd, SIOCGIFPRIORITY, &ifr) == 0) ifp->metric = (unsigned int)ifr.ifr_metric; if_getssid(ifp); #else /* We reserve the 100 range for virtual interfaces, if and when * we can work them out. */ ifp->metric = 200 + ifp->index; if (if_getssid(ifp) != -1) { ifp->wireless = 1; ifp->metric += 100; } #endif ifp->active = active; if (ifp->active) ifp->carrier = if_carrier(ifp); else ifp->carrier = LINK_UNKNOWN; TAILQ_INSERT_TAIL(ifs, ifp, next); } out: return ifs; } /* Decode bge0:1 as dev = bge, ppa = 0 and lun = 1 */ int if_nametospec(const char *ifname, struct if_spec *spec) { char *ep; 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; } 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'; } else { spec->lun = -1; ep = spec->drvname + strlen(spec->drvname) - 1; } strlcpy(spec->devname, spec->drvname, sizeof(spec->devname)); while (ep > spec->drvname && isdigit((int)*ep)) ep--; if (*ep++ == ':') { errno = EINVAL; return -1; } spec->ppa = (int)strtoi(ep, NULL, 10, 0, INT_MAX, &e); if (e != 0) spec->ppa = -1; *ep = '\0'; return 0; } static struct interface * if_findindexname(struct if_head *ifaces, unsigned int idx, const char *name) { if (ifaces != NULL) { struct if_spec spec; struct interface *ifp; if (name && if_nametospec(name, &spec) == -1) return NULL; TAILQ_FOREACH(ifp, ifaces, next) { if ((name && strcmp(ifp->name, spec.devname) == 0) || (!name && ifp->index == idx)) return ifp; } } errno = ENXIO; return NULL; } struct interface * if_find(struct if_head *ifaces, const char *name) { return if_findindexname(ifaces, 0, name); } struct interface * if_findindex(struct if_head *ifaces, unsigned int idx) { return if_findindexname(ifaces, idx, NULL); } struct interface * if_loopback(struct dhcpcd_ctx *ctx) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (ifp->flags & IFF_LOOPBACK) return ifp; } return NULL; } int if_domtu(const struct interface *ifp, short int mtu) { int r; struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); ifr.ifr_mtu = mtu; r = ioctl(ifp->ctx->pf_inet_fd, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); if (r == -1) return -1; return ifr.ifr_mtu; } /* Interface comparer for working out ordering. */ static int if_cmp(const struct interface *si, const struct interface *ti) { #ifdef INET int r; #endif /* Check active first */ if (si->active > ti->active) return -1; if (si->active < ti->active) return 1; /* Check carrier status next */ if (si->carrier > ti->carrier) return -1; if (si->carrier < ti->carrier) return 1; if (D_STATE_RUNNING(si) && !D_STATE_RUNNING(ti)) return -1; if (!D_STATE_RUNNING(si) && D_STATE_RUNNING(ti)) return 1; if (RS_STATE_RUNNING(si) && !RS_STATE_RUNNING(ti)) return -1; if (!RS_STATE_RUNNING(si) && RS_STATE_RUNNING(ti)) return 1; if (D6_STATE_RUNNING(si) && !D6_STATE_RUNNING(ti)) return -1; if (!D6_STATE_RUNNING(si) && D6_STATE_RUNNING(ti)) return 1; #ifdef INET /* Special attention needed here due to states and IPv4LL. */ if ((r = ipv4_ifcmp(si, ti)) != 0) return r; #endif /* Finally, metric */ if (si->metric < ti->metric) return -1; if (si->metric > ti->metric) return 1; return 0; } /* Sort the interfaces into a preferred order - best first, worst last. */ void if_sortinterfaces(struct dhcpcd_ctx *ctx) { struct if_head sorted; struct interface *ifp, *ift; if (ctx->ifaces == NULL || (ifp = TAILQ_FIRST(ctx->ifaces)) == NULL || TAILQ_NEXT(ifp, next) == NULL) return; TAILQ_INIT(&sorted); TAILQ_REMOVE(ctx->ifaces, ifp, next); TAILQ_INSERT_HEAD(&sorted, ifp, next); while ((ifp = TAILQ_FIRST(ctx->ifaces))) { TAILQ_REMOVE(ctx->ifaces, ifp, next); TAILQ_FOREACH(ift, &sorted, next) { if (if_cmp(ifp, ift) == -1) { TAILQ_INSERT_BEFORE(ift, ifp, next); break; } } if (ift == NULL) TAILQ_INSERT_TAIL(&sorted, ifp, next); } TAILQ_CONCAT(ctx->ifaces, &sorted, next); } int xsocket(int domain, int type, int protocol) { int s; #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) int xflags, xtype = type; #endif #ifdef SO_RERROR int on; #endif #ifndef HAVE_SOCK_CLOEXEC if (xtype & SOCK_CLOEXEC) type &= ~SOCK_CLOEXEC; #endif #ifndef HAVE_SOCK_NONBLOCK if (xtype & SOCK_NONBLOCK) type &= ~SOCK_NONBLOCK; #endif if ((s = socket(domain, type, protocol)) == -1) return -1; #ifndef HAVE_SOCK_CLOEXEC if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(s, F_GETFD)) == -1 || fcntl(s, F_SETFD, xflags | FD_CLOEXEC) == -1)) goto out; #endif #ifndef HAVE_SOCK_NONBLOCK if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(s, F_GETFL)) == -1 || fcntl(s, F_SETFL, xflags | O_NONBLOCK) == -1)) goto out; #endif #ifdef SO_RERROR /* Tell recvmsg(2) to return ENOBUFS if the receiving socket overflows. */ on = 1; if (setsockopt(s, SOL_SOCKET, SO_RERROR, &on, sizeof(on)) == -1) logerr("%s: SO_RERROR", __func__); #endif return s; #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) out: close(s); return -1; #endif } dhcpcd5-7.1.0/src/if.h000066400000000000000000000147521342162717100144040ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 /* Some systems have in-built IPv4 DAD. * However, we need them to do DAD at carrier up as well. */ #ifdef IN_IFF_TENTATIVE # ifdef __NetBSD__ # define NOCARRIER_PRESERVE_IP # endif #endif /* * 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" #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 /* Work out if we have a private address or not * 10/8 * 172.16/12 * 192.168/16 */ #ifndef IN_PRIVATE # define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \ ((addr & 0xfff00000) == 0xac100000) || \ ((addr & IN_CLASSB_NET) == 0xc0a80000)) #endif #define RAW_EOF 1 << 0 #define RAW_PARTIALCSUM 2 << 0 #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 /* 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 #endif int if_setflag(struct interface *ifp, short flag); #define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING)) bool if_valid_hwaddr(const uint8_t *, size_t); struct if_head *if_discover(struct dhcpcd_ctx *, struct ifaddrs **, int, char * const *); void if_markaddrsstale(struct if_head *); void if_learnaddrs(struct dhcpcd_ctx *, struct if_head *, struct ifaddrs **); void if_deletestaleaddrs(struct if_head *); struct interface *if_find(struct if_head *, const char *); struct interface *if_findindex(struct if_head *, unsigned int); struct interface *if_loopback(struct dhcpcd_ctx *); void if_sortinterfaces(struct dhcpcd_ctx *); void if_free(struct interface *); int if_domtu(const struct interface *, short int); #define if_getmtu(ifp) if_domtu((ifp), 0) #define if_setmtu(ifp, mtu) if_domtu((ifp), (mtu)) int if_carrier(struct interface *); /* * 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 lun; }; int if_nametospec(const char *, struct if_spec *); /* The below functions are provided by if-KERNEL.c */ int if_conf(struct interface *); int if_init(struct interface *); int if_getssid(struct interface *); int if_vimaster(const struct dhcpcd_ctx *ctx, const char *); unsigned short if_vlanid(const struct interface *); 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 *); /* 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 int if_route(unsigned char, const struct rt *rt); int if_initrt(struct dhcpcd_ctx *, int); #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_setup_inet6(const struct interface *); #ifdef IPV6_MANAGETEMPADDR int ip6_use_tempaddr(const char *ifname); int ip6_temp_preferred_lifetime(const char *ifname); int ip6_temp_valid_lifetime(const char *ifname); #else #define ip6_use_tempaddr(a) (0) #endif int ip6_forwarding(const char *ifname); 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); int xsocket(int, int, int); #endif dhcpcd5-7.1.0/src/ipv4.c000066400000000000000000000516641342162717100146660ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "config.h" #include "arp.h" #include "common.h" #include "dhcpcd.h" #include "dhcp.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv4ll.h" #include "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(htonl(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; } 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; } int ipv4_hasaddr(const struct interface *ifp) { const struct dhcp_state *dstate; if (IPV4LL_STATE_RUNNING(ifp)) return 1; 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(struct rt_head *routes, struct interface *ifp) { const struct dhcp_state *state; struct rt_head 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); TAILQ_INIT(&nroutes); /* First, add a subnet route. */ if (!(ifp->flags & IFF_POINTOPOINT) && #ifndef BSD /* BSD adds a route in this instance */ state->addr->mask.s_addr != INADDR_BROADCAST && #endif state->addr->mask.s_addr != INADDR_ANY) { 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; TAILQ_INSERT_HEAD(&nroutes, rt, rt_next); } /* If any set routes, grab them, otherwise DHCP routes. */ if (TAILQ_FIRST(&ifp->options->routes)) { TAILQ_FOREACH(r, &ifp->options->routes, rt_next) { 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; TAILQ_INSERT_TAIL(&nroutes, rt, rt_next); } } 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); TAILQ_INSERT_HEAD(routes, rt, rt_next); } /* Copy our address as the source address and set mtu */ mtu = dhcp_get_mtu(ifp); n = 0; TAILQ_FOREACH(rt, &nroutes, rt_next) { rt->rt_mtu = mtu; if (!(rt->rt_dflags & RTDF_STATIC)) rt->rt_dflags |= RTDF_DHCP; sa_in_init(&rt->rt_ifa, &state->addr->addr); n++; } TAILQ_CONCAT(routes, &nroutes, rt_next); 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(struct rt_head *routes, struct interface *ifp) { struct rt *rt, *rth; 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; /* Don't add a host route for these interfaces. */ if (ifp->flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) return 0; TAILQ_FOREACH(rt, routes, rt_next) { 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 */ TAILQ_FOREACH(rth, routes, rt_next) { if (rth == rt) break; /* match host */ if (sa_cmp(&rth->rt_dest, &rt->rt_gateway) == 0) break; /* match subnet */ 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); sa_in_init(&rth->rt_ifa, &state->addr->addr); TAILQ_INSERT_BEFORE(rt, rth, rt_next); } return 0; } bool inet_getroutes(struct dhcpcd_ctx *ctx, struct rt_head *routes) { struct interface *ifp; struct rt def; bool have_default; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; if (inet_dhcproutes(routes, ifp) == -1) return false; if (ipv4ll_subnetroute(routes, ifp) == -1) return false; if (inet_routerhostroute(routes, ifp) == -1) return false; } /* If there is no default route, see if we can use an IPv4LL one. */ memset(&def, 0, sizeof(def)); def.rt_dest.sa_family = AF_INET; have_default = (rt_find(routes, &def) != NULL); if (!have_default) { TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (ifp->active && ipv4ll_defaultroute(routes, ifp) == 1) break; } } return true; } int ipv4_deladdr(struct ipv4_addr *addr, int keeparp) { int r; struct ipv4_state *state; struct ipv4_addr *ap; #ifdef ARP struct arp_state *astate; #else UNUSED(keeparp); #endif 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 && (astate = arp_find(addr->iface, &addr->addr)) != NULL) arp_free(astate); #endif state = IPV4_STATE(addr->iface); TAILQ_FOREACH(ap, &state->addrs, next) { if (IPV4_MASK_EQ(ap, addr)) { struct dhcp_state *dstate; dstate = D_STATE(ap->iface); TAILQ_REMOVE(&state->addrs, ap, next); free(ap); if (dstate && dstate->addr == ap) { dstate->added = 0; dstate->addr = NULL; } break; } } return r; } static int delete_address(struct interface *ifp) { int r; struct if_options *ifo; struct dhcp_state *state; state = D_STATE(ifp); ifo = ifp->options; /* The lease could have been added, but the address deleted * by a 3rd party. */ if (state->addr == NULL || ifo->options & DHCPCD_INFORM || (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0)) return 0; r = ipv4_deladdr(state->addr, 0); return r; } struct ipv4_state * ipv4_getstate(struct interface *ifp) { struct ipv4_state *state; state = IPV4_STATE(ifp); if (state == NULL) { ifp->if_data[IF_DATA_IPV4] = malloc(sizeof(*state)); state = IPV4_STATE(ifp); if (state == NULL) { logerr(__func__); return NULL; } TAILQ_INIT(&state->addrs); state->buffer_size = state->buffer_len = state->buffer_pos = 0; state->buffer = NULL; } 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 (lun == 0) strlcpy(alias, ia->iface->name, sizeof(alias)); else snprintf(alias, sizeof(alias), "%s:%u", ia->iface->name, lun); 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) { struct ipv4_state *state; struct ipv4_addr *ia; bool is_new = false; #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 is_new = true; } ia->mask = *mask; ia->brd = *bcast; 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 broadcast %s", ifp->name, ia->saddr, inet_ntoa(*bcast)); if (if_address(RTM_NEWADDR, ia) == -1) { if (errno != EEXIST) logerr("%s: if_addaddress", __func__); free(ia); return NULL; } #ifdef ALIAS_ADDR if (replaced) { TAILQ_REMOVE(&state->addrs, replaced_ia, next); free(replaced_ia); } #endif if (is_new) TAILQ_INSERT_TAIL(&state->addrs, ia, next); return ia; } static int ipv4_daddaddr(struct interface *ifp, const struct dhcp_lease *lease) { struct dhcp_state *state; struct ipv4_addr *ia; ia = ipv4_addaddr(ifp, &lease->addr, &lease->mask, &lease->brd); if (ia == NULL) return -1; state = D_STATE(ifp); state->added = STATE_ADDED; state->addr = ia; return 0; } void 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; int r; if (state == NULL) return; lease = &state->lease; if (state->new == NULL) { if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != (DHCPCD_EXITING | DHCPCD_PERSISTENT)) { if (state->added) { struct in_addr addr; addr = lease->addr; delete_address(ifp); rt_build(ifp->ctx, AF_INET); #ifdef ARP /* Announce the preferred address to * kick ARP caches. */ arp_announceaddr(ifp->ctx, &addr); #endif } script_runreason(ifp, state->reason); } else rt_build(ifp->ctx, AF_INET); return; } /* If the netmask or broadcast is different, re-add the addresss */ ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); if (ia && ia->mask.s_addr == lease->mask.s_addr && ia->brd.s_addr == lease->brd.s_addr) logdebugx("%s: IP address %s already exists", ifp->name, ia->saddr); else { #if __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 r = ipv4_daddaddr(ifp, lease); if (r == -1 && errno != EEXIST) return; } ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); if (ia == NULL) { logerrx("%s: added address vanished", ifp->name); return; } #if defined(ARP) && defined(IN_IFF_NOTUSEABLE) if (ia->addr_flags & IN_IFF_NOTUSEABLE) return; #endif /* Delete the old address if different */ if (state->addr && state->addr->addr.s_addr != lease->addr.s_addr && ipv4_iffindaddr(ifp, &lease->addr, NULL)) delete_address(ifp); state->addr = ia; state->added = STATE_ADDED; /* Find any freshly added routes, such as the subnet route. * We do this because we cannot rely on recieving the kernel * notification right now via our link socket. */ if_initrt(ifp->ctx, AF_INET); rt_build(ifp->ctx, AF_INET); #ifdef ARP arp_announceaddr(ifp->ctx, &state->addr->addr); #endif if (state->state == DHS_BOUND) { script_runreason(ifp, state->reason); dhcpcd_daemonise(ifp->ctx); } } void ipv4_markaddrsstale(struct interface *ifp) { struct ipv4_state *state; struct ipv4_addr *ia; state = IPV4_STATE(ifp); if (state == NULL) return; TAILQ_FOREACH(ia, &state->addrs, next) { ia->flags |= IPV4_AF_STALE; } } void ipv4_deletestaleaddrs(struct interface *ifp) { struct ipv4_state *state; struct ipv4_addr *ia, *ia1; state = IPV4_STATE(ifp); if (state == NULL) return; TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) { if (!(ia->flags & IPV4_AF_STALE)) continue; ipv4_handleifa(ifp->ctx, RTM_DELADDR, ifp->ctx->ifaces, ifp->name, &ia->addr, &ia->mask, &ia->brd, 0, getpid()); } } void ipv4_handleifa(struct dhcpcd_ctx *ctx, int cmd, struct if_head *ifs, const char *ifname, const struct in_addr *addr, const struct in_addr *mask, const struct in_addr *brd, int addrflags, pid_t pid) { struct interface *ifp; struct ipv4_state *state; struct ipv4_addr *ia; bool ia_is_new; #if 0 char sbrdbuf[INET_ADDRSTRLEN]; const char *sbrd; if (brd) sbrd = inet_ntop(AF_INET, brd, sbrdbuf, sizeof(sbrdbuf)); else sbrd = NULL; logdebugx("%s: %s %s/%d %s %d", ifname, cmd == RTM_NEWADDR ? "RTM_NEWADDR" : cmd == RTM_DELADDR ? "RTM_DELADDR" : "???", inet_ntoa(*addr), inet_ntocidr(*mask), sbrd, addrflags); #endif if (ifs == NULL) ifs = ctx->ifaces; if (ifs == NULL) { errno = ESRCH; return; } if ((ifp = if_find(ifs, ifname)) == NULL) return; if ((state = ipv4_getstate(ifp)) == NULL) { errno = ENOENT; return; } ia = ipv4_iffindaddr(ifp, addr, NULL); switch (cmd) { case RTM_NEWADDR: if (ia == NULL) { if ((ia = malloc(sizeof(*ia))) == NULL) { logerr(__func__); return; } ia->iface = ifp; ia->addr = *addr; ia->mask = *mask; ia->flags = 0; ia_is_new = true; #ifdef ALIAS_ADDR strlcpy(ia->alias, ifname, sizeof(ia->alias)); #endif TAILQ_INSERT_TAIL(&state->addrs, ia, next); } else ia_is_new = false; /* Mask could have changed */ if (ia_is_new || (mask->s_addr != INADDR_ANY && mask->s_addr != ia->mask.s_addr)) { ia->mask = *mask; snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", inet_ntoa(*addr), inet_ntocidr(*mask)); } if (brd != NULL) ia->brd = *brd; else ia->brd.s_addr = INADDR_ANY; ia->addr_flags = addrflags; ia->flags &= ~IPV4_AF_STALE; break; case RTM_DELADDR: if (ia == NULL) return; if (mask->s_addr != INADDR_ANY && mask->s_addr != ia->mask.s_addr) return; TAILQ_REMOVE(&state->addrs, ia, next); break; default: return; } if (addr->s_addr != INADDR_ANY && addr->s_addr != INADDR_BROADCAST) { #ifdef ARP arp_handleifa(cmd, ia); #endif dhcp_handleifa(cmd, ia, pid); } if (cmd == RTM_DELADDR) free(ia); } void ipv4_free(struct interface *ifp) { struct ipv4_state *state; struct ipv4_addr *ia; if (ifp) { state = IPV4_STATE(ifp); if (state) { while ((ia = TAILQ_FIRST(&state->addrs))) { TAILQ_REMOVE(&state->addrs, ia, next); free(ia); } free(state->buffer); free(state); } } } dhcpcd5-7.1.0/src/ipv4.h000066400000000000000000000115341342162717100146630ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef IPV4_H #define IPV4_H #include "dhcpcd.h" /* Prefer our macro */ #ifdef HTONL #undef HTONL #endif #ifndef BYTE_ORDER #define BIG_ENDIAN 1234 #define LITTLE_ENDIAN 4321 #if defined(_BIG_ENDIAN) #define BYTE_ORDER BIG_ENDIAN #elif defined(_LITTLE_ENDIAN) #define BYTE_ORDER LITTLE_ENDIAN #else #error Endian unknown #endif #endif #if BYTE_ORDER == BIG_ENDIAN #define HTONL(A) (A) #elif BYTE_ORDER == LITTLE_ENDIAN #define HTONL(A) \ ((((uint32_t)(A) & 0xff000000) >> 24) | \ (((uint32_t)(A) & 0x00ff0000) >> 8) | \ (((uint32_t)(A) & 0x0000ff00) << 8) | \ (((uint32_t)(A) & 0x000000ff) << 24)) #endif /* BYTE_ORDER */ #ifdef __sun /* Solaris lacks these defines. * While it supports DaD, to seems to only expose IFF_DUPLICATE * so we have no way of knowing if it's tentative or not. * I don't even know if Solaris has any special treatment for tentative. */ # define IN_IFF_TENTATIVE 0 # define IN_IFF_DUPLICATED 0x02 # define IN_IFF_DETACHED 0 #endif #ifdef IN_IFF_TENTATIVE #define IN_IFF_NOTUSEABLE \ (IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED) #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; 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_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; /* Buffer for BPF */ size_t buffer_size, buffer_len, buffer_pos; char *buffer; }; #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 *, struct rt_head *); #define STATE_ADDED 0x01 #define STATE_FAKE 0x02 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 *); void 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 *); 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 *); #else #define ipv4_sortinterfaces(a) {} #define ipv4_applyaddr(a) {} #define ipv4_free(a) {} #define ipv4_hasaddr(a) (0) #endif #endif dhcpcd5-7.1.0/src/ipv4ll.c000066400000000000000000000327421342162717100152120ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 #define ELOOP_QUEUE 6 #include "config.h" #include "arp.h" #include "common.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv4ll.h" #include "logerr.h" #include "sa.h" #include "script.h" #ifdef IPV4LL 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 in_addr_t ipv4ll_pickaddr(struct arp_state *astate) { struct in_addr addr; struct ipv4ll_state *istate; istate = IPV4LL_STATE(astate->iface); setstate(istate->randomstate); do { long r; /* 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 (addr.s_addr == astate->failed.s_addr) continue; /* Ensure we don't have the address on another interface */ } while (ipv4_findaddr(astate->iface->ctx, &addr) != NULL); /* Restore the original random state */ setstate(istate->arp->iface->ctx->randomstate); return addr.s_addr; } int ipv4ll_subnetroute(struct rt_head *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); TAILQ_INSERT_TAIL(routes, rt, rt_next); return 1; } int ipv4ll_defaultroute(struct rt_head *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); TAILQ_INSERT_TAIL(routes, rt, rt_next); return 1; } ssize_t ipv4ll_env(char **env, const char *prefix, const struct interface *ifp) { const struct ipv4ll_state *state; const char *pf = prefix == NULL ? "" : "_"; struct in_addr netnum; assert(ifp != NULL); if ((state = IPV4LL_CSTATE(ifp)) == NULL || state->addr == NULL) return 0; if (env == NULL) return 5; /* Emulate a DHCP environment */ if (asprintf(&env[0], "%s%sip_address=%s", prefix, pf, inet_ntoa(state->addr->addr)) == -1) return -1; if (asprintf(&env[1], "%s%ssubnet_mask=%s", prefix, pf, inet_ntoa(state->addr->mask)) == -1) return -1; if (asprintf(&env[2], "%s%ssubnet_cidr=%d", prefix, pf, inet_ntocidr(state->addr->mask)) == -1) return -1; if (asprintf(&env[3], "%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 (asprintf(&env[4], "%s%snetwork_number=%s", prefix, pf, inet_ntoa(netnum)) == -1) return -1; return 5; } static void ipv4ll_probed(struct arp_state *astate) { struct interface *ifp; struct ipv4ll_state *state; struct ipv4_addr *ia; assert(astate != NULL); assert(astate->iface != NULL); ifp = astate->iface; state = IPV4LL_STATE(ifp); assert(state != NULL); ia = ipv4_iffindaddr(ifp, &astate->addr, &inaddr_llmask); #ifdef IN_IFF_NOTREADY if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY) #endif loginfox("%s: using IPv4LL address %s", ifp->name, inet_ntoa(astate->addr)); if (ia == NULL) { if (ifp->ctx->options & DHCPCD_TEST) goto test; ia = ipv4_addaddr(ifp, &astate->addr, &inaddr_llmask, &inaddr_llbcast); } if (ia == NULL) return; #ifdef IN_IFF_NOTREADY if (ia->addr_flags & IN_IFF_NOTREADY) return; logdebugx("%s: DAD completed for %s", ifp->name, inet_ntoa(astate->addr)); #endif test: state->addr = ia; if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } timespecclear(&state->defend); if_initrt(ifp->ctx, AF_INET); rt_build(ifp->ctx, AF_INET); arp_announce(astate); script_runreason(ifp, "IPV4LL"); dhcpcd_daemonise(ifp->ctx); } static void ipv4ll_announced(struct arp_state *astate) { struct ipv4ll_state *state = IPV4LL_STATE(astate->iface); state->conflicts = 0; /* Need to keep the arp state so we can defend our IP. */ } static void ipv4ll_probe(void *arg) { #ifdef IN_IFF_TENTATIVE ipv4ll_probed(arg); #else arp_probe(arg); #endif } static void ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg) { struct interface *ifp; struct ipv4ll_state *state; #ifdef IN_IFF_DUPLICATED struct ipv4_addr *ia; #endif assert(astate != NULL); assert(astate->iface != NULL); ifp = astate->iface; state = IPV4LL_STATE(ifp); assert(state != NULL); /* * NULL amsg means kernel detected DAD. * We always fail on matching sip. * We only fail on matching tip and we haven't added that address yet. */ if (amsg == NULL || amsg->sip.s_addr == astate->addr.s_addr || (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr && ipv4_iffindaddr(ifp, &amsg->tip, NULL) == NULL)) astate->failed = astate->addr; else return; arp_report_conflicted(astate, amsg); if (state->addr != NULL && astate->failed.s_addr == state->addr->addr.s_addr) { #ifdef KERNEL_RFC5227 logwarnx("%s: IPv4LL defence failed for %s", ifp->name, state->addr->saddr); #else struct timespec now, defend; /* 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. */ defend.tv_sec = state->defend.tv_sec + DEFEND_INTERVAL; defend.tv_nsec = state->defend.tv_nsec; clock_gettime(CLOCK_MONOTONIC, &now); if (timespeccmp(&defend, &now, >)) logwarnx("%s: IPv4LL %d second defence failed for %s", ifp->name, DEFEND_INTERVAL, state->addr->saddr); else if (arp_request(ifp, state->addr->addr.s_addr, state->addr->addr.s_addr) == -1) logerr(__func__); else { logdebugx("%s: defended IPv4LL address %s", ifp->name, state->addr->saddr); state->defend = now; return; } #endif ipv4_deladdr(state->addr, 1); state->down = 1; state->addr = NULL; script_runreason(ifp, "IPV4LL"); } #ifdef IN_IFF_DUPLICATED ia = ipv4_iffindaddr(ifp, &astate->addr, NULL); if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) ipv4_deladdr(ia, 1); #endif arp_cancel(astate); if (++state->conflicts == MAX_CONFLICTS) logerr("%s: failed to acquire an IPv4LL address", ifp->name); state->pickedaddr.s_addr = ipv4ll_pickaddr(astate); astate->addr = state->pickedaddr; eloop_timeout_add_sec(ifp->ctx->eloop, state->conflicts >= MAX_CONFLICTS ? RATE_LIMIT_INTERVAL : PROBE_WAIT, ipv4ll_probe, astate); } static void ipv4ll_arpfree(struct arp_state *astate) { struct ipv4ll_state *state; state = IPV4LL_STATE(astate->iface); if (state != NULL && state->arp == astate) state->arp = NULL; } void ipv4ll_start(void *arg) { struct interface *ifp; struct ipv4ll_state *state; struct arp_state *astate; struct ipv4_addr *ia; assert(arg != NULL); ifp = arg; if ((state = IPV4LL_STATE(ifp)) == NULL) { ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state)); if ((state = IPV4LL_STATE(ifp)) == NULL) { logerr(__func__); return; } } /* RFC 3927 Section 2.1 states that the random number generator * SHOULD be seeded with a value derived from persistent information * such as the IEEE 802 MAC address so that it usually picks * the same address without persistent storage. */ if (!state->seeded) { unsigned int seed; char *orig; if (sizeof(seed) > ifp->hwlen) { seed = 0; memcpy(&seed, ifp->hwaddr, ifp->hwlen); } else memcpy(&seed, ifp->hwaddr + ifp->hwlen - sizeof(seed), sizeof(seed)); /* coverity[dont_call] */ orig = initstate(seed, state->randomstate, sizeof(state->randomstate)); /* Save the original state. */ if (ifp->ctx->randomstate == NULL) ifp->ctx->randomstate = orig; /* Set back the original state until we need the seeded one. */ setstate(ifp->ctx->randomstate); state->seeded = true; } if (state->arp != NULL) return; if ((astate = arp_new(ifp, NULL)) == NULL) return; state->arp = astate; astate->probed_cb = ipv4ll_probed; astate->announced_cb = ipv4ll_announced; astate->conflicted_cb = ipv4ll_conflicted; astate->free_cb = ipv4ll_arpfree; /* Find 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); #ifdef IN_IFF_TENTATIVE if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) { ipv4_deladdr(ia, 0); ia = NULL; } #endif if (ia != NULL) { state->pickedaddr = astate->addr = 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; } loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr); #endif ipv4ll_probed(astate); return; } loginfox("%s: probing for an IPv4LL address", ifp->name); if (state->pickedaddr.s_addr == INADDR_ANY) state->pickedaddr.s_addr = ipv4ll_pickaddr(astate); astate->addr = state->pickedaddr; #ifdef IN_IFF_TENTATIVE ipv4ll_probed(astate); #else arp_probe(astate); #endif } 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; } void ipv4ll_drop(struct interface *ifp) { struct ipv4ll_state *state; bool dropped = false; struct ipv4_state *istate; assert(ifp != NULL); ipv4ll_freearp(ifp); #ifndef IN_IFF_TENATIVE if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP) #endif return; state = IPV4LL_STATE(ifp); if (state && state->addr != NULL) { 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))) { 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; 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; /* Ignore route init. */ if (rt->rt_dflags & RTDF_INIT) return 0; /* 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)) { if_initrt(ctx, AF_INET); rt_build(ctx, AF_INET); break; } } return 0; } #endif #endif dhcpcd5-7.1.0/src/ipv4ll.h000066400000000000000000000056121342162717100152130ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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; struct arp_state *arp; unsigned int conflicts; struct timespec defend; char randomstate[128]; bool seeded; uint8_t down; }; #define IPV4LL_STATE(ifp) \ ((struct ipv4ll_state *)(ifp)->if_data[IF_DATA_IPV4LL]) #define IPV4LL_CSTATE(ifp) \ ((const struct ipv4ll_state *)(ifp)->if_data[IF_DATA_IPV4LL]) #define IPV4LL_STATE_RUNNING(ifp) \ (IPV4LL_CSTATE((ifp)) && !IPV4LL_CSTATE((ifp))->down && \ (IPV4LL_CSTATE((ifp))->addr != NULL)) int ipv4ll_subnetroute(struct rt_head *, struct interface *); int ipv4ll_defaultroute(struct rt_head *,struct interface *); ssize_t ipv4ll_env(char **, const char *, const struct interface *); void ipv4ll_start(void *); void ipv4ll_claimed(void *); void ipv4ll_handle_failure(void *); #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 *); #else #define IPV4LL_STATE_RUNNING(ifp) (0) #define ipv4ll_subnetroute(route, ifp) (0) #define ipv4ll_defaultroute(route, ifp) (0) #define ipv4ll_handlert(a, b, c) (0) #define ipv4ll_free(a) {} #endif #endif dhcpcd5-7.1.0/src/ipv6.c000066400000000000000000001552341342162717100146660ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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" #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 #define ELOOP_QUEUE 7 #include "common.h" #include "if.h" #include "dhcpcd.h" #include "dhcp6.h" #include "eloop.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.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) || (ifp)->carrier != LINK_DOWN) #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_regentempifid(void *); static void ipv6_regentempaddr(void *); #else #define ipv6_regentempifid(a) {} #endif int ipv6_init(struct dhcpcd_ctx *ctx) { if (ctx->sndhdr.msg_iovlen == 1) return 0; if (ctx->ra_routers == NULL) { ctx->ra_routers = malloc(sizeof(*ctx->ra_routers)); if (ctx->ra_routers == NULL) return -1; } TAILQ_INIT(ctx->ra_routers); ctx->sndhdr.msg_namelen = sizeof(struct sockaddr_in6); ctx->sndhdr.msg_iov = ctx->sndiov; ctx->sndhdr.msg_iovlen = 1; ctx->sndhdr.msg_control = ctx->sndbuf; ctx->sndhdr.msg_controllen = sizeof(ctx->sndbuf); ctx->rcvhdr.msg_name = &ctx->from; ctx->rcvhdr.msg_namelen = sizeof(ctx->from); ctx->rcvhdr.msg_iov = ctx->iov; ctx->rcvhdr.msg_iovlen = 1; ctx->rcvhdr.msg_control = ctx->ctlbuf; // controllen is set at recieve //ctx->rcvhdr.msg_controllen = sizeof(ctx->rcvbuf); ctx->nd_fd = -1; ctx->dhcp6_fd = -1; return 0; } static ssize_t ipv6_readsecret(struct dhcpcd_ctx *ctx) { FILE *fp; char line[1024]; unsigned char *p; size_t len; uint32_t r; int x; if ((ctx->secret_len = read_hwaddr_aton(&ctx->secret, SECRET)) != 0) return (ssize_t)ctx->secret_len; if (errno != ENOENT) logerr("%s: %s", __func__, SECRET); /* Chaining arc4random should be good enough. * RFC7217 section 5.1 states the key SHOULD be at least 128 bits. * To attempt and future proof ourselves, we'll generate a key of * 512 bits (64 bytes). */ 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); } /* Ensure that only the dhcpcd user can read the secret. * Write permission is also denied as chaning it would remove * it's stability. */ if ((fp = fopen(SECRET, "w")) == NULL || chmod(SECRET, S_IRUSR) == -1) goto eexit; x = fprintf(fp, "%s\n", hwaddr_ntoa(ctx->secret, ctx->secret_len, line, sizeof(line))); if (fclose(fp) == EOF) x = -1; fp = NULL; if (x > 0) return (ssize_t)ctx->secret_len; eexit: logerr("%s: %s", __func__, SECRET); if (fp != NULL) fclose(fp); unlink(SECRET); ctx->secret_len = 0; return -1; } /* http://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml * RFC5453 */ static const struct reslowhigh { const uint8_t high[8]; const uint8_t low[8]; } reslowhigh[] = { /* RFC4291 + RFC6543 */ { { 0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x00, 0x00 }, { 0x02, 0x00, 0x5e, 0xff, 0xfe, 0xff, 0xff, 0xff } }, /* RFC2526 */ { { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 }, { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } } }; static int ipv6_reserved(const struct in6_addr *addr) { uint64_t id, low, high; size_t i; const struct reslowhigh *r; id = be64dec(addr->s6_addr + sizeof(id)); if (id == 0) /* RFC4291 */ return 1; for (i = 0; i < sizeof(reslowhigh) / sizeof(reslowhigh[0]); i++) { r = &reslowhigh[i]; low = be64dec(r->low); high = be64dec(r->high); if (id >= low && id <= high) return 1; } return 0; } /* RFC7217 */ static int ipv6_makestableprivate1(struct in6_addr *addr, const struct in6_addr *prefix, int prefix_len, const unsigned char *netiface, size_t netiface_len, const unsigned char *netid, size_t netid_len, unsigned short vlanid, uint32_t *dad_counter, const unsigned char *secret, size_t secret_len) { unsigned char buf[2048], *p, digest[SHA256_DIGEST_LENGTH]; size_t len, l; SHA256_CTX ctx; if (prefix_len < 0 || prefix_len > 120) { errno = EINVAL; return -1; } l = (size_t)(ROUNDUP8(prefix_len) / NBBY); len = l + netiface_len + netid_len + sizeof(*dad_counter) + secret_len; if (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, secret, secret_len); /* Make an address using the digest of the above. * RFC7217 Section 5.1 states that we shouldn't use MD5. * Pity as we use that for HMAC-MD5 which is still deemed OK. * SHA-256 is recommended */ SHA256_Init(&ctx); SHA256_Update(&ctx, buf, len); SHA256_Final(digest, &ctx); p = addr->s6_addr; memcpy(p, prefix, l); /* RFC7217 section 5.2 says we need to start taking the id from * the least significant bit */ len = sizeof(addr->s6_addr) - l; memcpy(p + l, digest + (sizeof(digest) - len), len); /* Ensure that the Interface ID does not match a reserved one, * if it does then treat it as a DAD failure. * RFC7217 section 5.2 */ if (prefix_len != 64) break; if (!ipv6_reserved(addr)) break; } return 0; } int ipv6_makestableprivate(struct in6_addr *addr, const struct in6_addr *prefix, int prefix_len, const struct interface *ifp, int *dad_counter) { uint32_t dad; int r; if (ifp->ctx->secret_len == 0) { if (ipv6_readsecret(ifp->ctx) == -1) return -1; } dad = (uint32_t)*dad_counter; /* For our implementation, we shall set the hardware address * as the interface identifier */ r = ipv6_makestableprivate1(addr, prefix, prefix_len, ifp->hwaddr, ifp->hwlen, ifp->ssid, ifp->ssid_len, ifp->vlanid, &dad, ifp->ctx->secret, ifp->ctx->secret_len); if (r == 0) *dad_counter = (int)dad; return r; } int ipv6_makeaddr(struct in6_addr *addr, struct interface *ifp, const struct in6_addr *prefix, int prefix_len) { const struct ipv6_addr *ap; int dad; if (prefix_len < 0 || prefix_len > 120) { errno = EINVAL; return -1; } if (ifp->options->options & DHCPCD_SLAACPRIVATE) { dad = 0; if (ipv6_makestableprivate(addr, prefix, prefix_len, ifp, &dad) == -1) return -1; return dad; } if (prefix_len > 64) { errno = EINVAL; return -1; } if ((ap = ipv6_linklocal(ifp)) == NULL) { /* We delay a few functions until we get a local-link address * so this should never be hit. */ errno = ENOENT; return -1; } /* Make the address from the first local-link address */ memcpy(addr, prefix, sizeof(*prefix)); addr->s6_addr32[2] = ap->addr.s6_addr32[2]; addr->s6_addr32[3] = ap->addr.s6_addr32[3]; return 0; } static int ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len) { int bytes, bits; if (len < 0 || len > 128) { errno = EINVAL; return -1; } bytes = len / NBBY; bits = len % NBBY; memcpy(&prefix->s6_addr, &addr->s6_addr, (size_t)bytes); if (bits != 0) { /* Coverify false positive. * bytelen cannot be 16 if bitlen is non zero */ /* coverity[overrun-local] */ prefix->s6_addr[bytes] = (uint8_t)(prefix->s6_addr[bytes] >> (NBBY - bits)); } memset((char *)prefix->s6_addr + bytes, 0, sizeof(prefix->s6_addr) - (size_t)bytes); return 0; } int ipv6_mask(struct in6_addr *mask, int len) { static const unsigned char masks[NBBY] = { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; int bytes, bits, i; if (len < 0 || len > 128) { errno = EINVAL; return -1; } memset(mask, 0, sizeof(*mask)); bytes = len / NBBY; bits = len % NBBY; for (i = 0; i < bytes; i++) mask->s6_addr[i] = 0xff; if (bits != 0) { /* Coverify false positive. * bytelen cannot be 16 if bitlen is non zero */ /* coverity[overrun-local] */ mask->s6_addr[bytes] = masks[bits - 1]; } return 0; } uint8_t ipv6_prefixlen(const struct in6_addr *mask) { int x = 0, y; const unsigned char *lim, *p; lim = (const unsigned char *)mask + sizeof(*mask); for (p = (const unsigned char *)mask; p < lim; x++, p++) { if (*p != 0xff) break; } y = 0; if (p < lim) { for (y = 0; y < NBBY; y++) { if ((*p & (0x80 >> y)) == 0) break; } } /* * when the limit pointer is given, do a stricter check on the * remaining bits. */ if (p < lim) { if (y != 0 && (*p & (0x00ff >> y)) != 0) return 0; for (p = p + 1; p < lim; p++) if (*p != 0) return 0; } return (uint8_t)(x * NBBY + y); } static void in6_to_h64(uint64_t *vhigh, uint64_t *vlow, const struct in6_addr *addr) { *vhigh = be64dec(addr->s6_addr); *vlow = be64dec(addr->s6_addr + 8); } static void h64_to_in6(struct in6_addr *addr, uint64_t vhigh, uint64_t vlow) { be64enc(addr->s6_addr, vhigh); be64enc(addr->s6_addr + 8, vlow); } int ipv6_userprefix( const struct in6_addr *prefix, // prefix from router short prefix_len, // length of prefix received uint64_t user_number, // "random" number from user struct in6_addr *result, // resultant prefix short result_len) // desired prefix length { uint64_t vh, vl, user_low, user_high; if (prefix_len < 1 || prefix_len > 128 || result_len < 1 || result_len > 128) { errno = EINVAL; return -1; } /* Check that the user_number fits inside result_len less prefix_len */ if (result_len < prefix_len || fls64(user_number) > result_len - prefix_len) { errno = ERANGE; return -1; } /* If user_number is zero, just copy the prefix into the result. */ if (user_number == 0) { *result = *prefix; return 0; } /* Shift user_number so it fit's just inside result_len. * Shifting by 0 or sizeof(user_number) is undefined, * so we cater for that. */ if (result_len == 128) { user_high = 0; user_low = user_number; } else if (result_len > 64) { if (prefix_len >= 64) user_high = 0; else user_high = user_number >> (result_len - prefix_len); user_low = user_number << (128 - result_len); } else if (result_len == 64) { user_high = user_number; user_low = 0; } else { user_high = user_number << (64 - result_len); user_low = 0; } /* convert to two 64bit host order values */ in6_to_h64(&vh, &vl, prefix); vh |= user_high; vl |= user_low; /* copy back result */ h64_to_in6(result, vh, vl); return 0; } #ifdef IPV6_POLLADDRFLAG void ipv6_checkaddrflags(void *arg) { struct ipv6_addr *ia; int flags; const char *alias; ia = arg; #ifdef ALIAS_ADDR alias = ia->alias; #else alias = NULL; #endif if ((flags = if_addrflags6(ia->iface, &ia->addr, alias)) == -1) { if (errno != EEXIST && errno != EADDRNOTAVAIL) logerr("%s: if_addrflags6", __func__); return; } if (!(flags & IN6_IFF_TENTATIVE)) { /* Simulate the kernel announcing the new address. */ ipv6_handleifa(ia->iface->ctx, RTM_NEWADDR, ia->iface->ctx->ifaces, ia->iface->name, &ia->addr, ia->prefix_len, flags, 0); } else { /* Still tentative? Check again in a bit. */ struct timespec tv; ms_to_ts(&tv, RETRANS_TIMER / 2); eloop_timeout_add_tv(ia->iface->ctx->eloop, &tv, ipv6_checkaddrflags, ia); } } #endif static void ipv6_deletedaddr(struct ipv6_addr *ia) { #ifdef SMALL UNUSED(ia); #else /* NOREJECT is set if we delegated exactly the prefix to another * address. * This can only be one address, so just clear the flag. * This should ensure the reject route will be restored. */ if (ia->delegating_prefix != NULL) ia->delegating_prefix->flags &= ~IPV6_AF_NOREJECT; #endif } 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; } } /* Advertise the address if it exists on another interface. */ ipv6nd_advertise(ia); } static int ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now) { struct interface *ifp; uint32_t pltime, vltime; bool vltime_was_zero; __printflike(1, 2) void (*logfunc)(const char *, ...); /* 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 (timespecisset(&ia->acquired) && (ia->prefix_pltime != ND6_INFINITE_LIFETIME || ia->prefix_vltime != ND6_INFINITE_LIFETIME)) { struct timespec n; if (now == NULL) { clock_gettime(CLOCK_MONOTONIC, &n); now = &n; } timespecsub(now, &ia->acquired, &n); if (ia->prefix_pltime != ND6_INFINITE_LIFETIME) { ia->prefix_pltime -= (uint32_t)n.tv_sec; /* This can happen when confirming a * deprecated but still valid lease. */ if (ia->prefix_pltime > pltime) ia->prefix_pltime = 0; } if (ia->prefix_vltime != ND6_INFINITE_LIFETIME) { ia->prefix_vltime -= (uint32_t)n.tv_sec; /* This should never happen. */ if (ia->prefix_vltime > vltime) { logerrx("%s: %s: lifetime overflow", ifp->name, ia->saddr); ia->prefix_vltime = ia->prefix_pltime = 0; } } } logfunc = ia->flags & IPV6_AF_NEW ? loginfox : logdebugx; logfunc("%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); vltime_was_zero = ia->prefix_vltime == 0; 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 && ip6_use_tempaddr(ifp->name)) eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)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)) { struct timespec tv; ms_to_ts(&tv, RETRANS_TIMER / 2); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6_checkaddrflags, ia); } #endif #ifdef __sun /* Solaris does not announce new addresses which need DaD * so we need to take a copy and add it to our list. * Otherwise aliasing gets confused if we add another * address during DaD. */ state = IPV6_STATE(ifp); TAILQ_FOREACH(ia2, &state->addrs, next) { if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr)) break; } if (ia2 == NULL) { if ((ia2 = malloc(sizeof(*ia2))) == NULL) { logerr(__func__); return 0; /* Well, we did add the address */ } memcpy(ia2, ia, sizeof(*ia2)); TAILQ_INSERT_TAIL(&state->addrs, ia2, next); } #endif /* Re-advertise the preferred address to be safe. */ if (!vltime_was_zero) ipv6nd_advertise(ia); return 0; } #ifdef ALIAS_ADDR /* Find the next logical aliase 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 unit; 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; } } unit = 0; find_unit: if (unit == 0) strlcpy(alias, ia->iface->name, sizeof(alias)); else snprintf(alias, sizeof(alias), "%s:%u", ia->iface->name, unit); 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 (unit == UINT_MAX) { errno = ERANGE; return -1; } unit++; 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 *dap, *nap; dap = dhcp6_findaddr(ctx, addr, flags); nap = ipv6nd_findaddr(ctx, addr, flags); if (!dap && !nap) return NULL; if (dap && !nap) return dap; if (nap && !dap) return nap; if (nap->iface->metric < dap->iface->metric) return nap; return dap; } ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs) { struct ipv6_addr *ap, *apn; ssize_t i; struct timespec now; i = 0; timespecclear(&now); TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { /* A delegated prefix is not an address. */ if (ap->flags & IPV6_AF_DELEGATEDPFX) continue; if (ap->prefix_vltime == 0) { if (ap->flags & IPV6_AF_ADDED) { ipv6_deleteaddr(ap); i++; } eloop_q_timeout_delete(ap->iface->ctx->eloop, 0, NULL, ap); if (ap->flags & IPV6_AF_REQUEST) { ap->flags &= ~IPV6_AF_ADDED; } else { TAILQ_REMOVE(addrs, ap, next); ipv6_freeaddr(ap); } } else if (!(ap->flags & IPV6_AF_STALE) && !IN6_IS_ADDR_UNSPECIFIED(&ap->addr)) { if (ap->flags & IPV6_AF_NEW) i++; if (!timespecisset(&now)) clock_gettime(CLOCK_MONOTONIC, &now); ipv6_addaddr(ap, &now); } } return i; } void ipv6_freeaddr(struct ipv6_addr *ia) { #ifndef SMALL struct ipv6_addr *iad; /* Forget the reference */ if (ia->flags & IPV6_AF_DELEGATEDPFX) { TAILQ_FOREACH(iad, &ia->pd_pfxs, pd_next) { iad->delegating_prefix = NULL; } } else if (ia->delegating_prefix != NULL) { TAILQ_REMOVE(&ia->delegating_prefix->pd_pfxs, ia, pd_next); } #endif if (ia->dhcp6_fd != -1) { close(ia->dhcp6_fd); eloop_event_delete(ia->iface->ctx->eloop, ia->dhcp6_fd); } eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, NULL, ia); free(ia->na); free(ia); } void ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop, const struct interface *ifd) { struct ipv6_addr *ap, *apn, *apf; struct timespec now; #ifdef SMALL UNUSED(ifd); #endif timespecclear(&now); TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { #ifndef SMALL if (ifd != NULL && (ap->delegating_prefix == NULL || ap->delegating_prefix->iface != ifd)) continue; #endif if (drop != 2) TAILQ_REMOVE(addrs, ap, next); if (drop && ap->flags & IPV6_AF_ADDED && (ap->iface->options->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != (DHCPCD_EXITING | DHCPCD_PERSISTENT)) { /* Don't drop link-local addresses. */ if (!IN6_IS_ADDR_LINKLOCAL(&ap->addr) || CAN_DROP_LLADDR(ap->iface)) { if (drop == 2) TAILQ_REMOVE(addrs, ap, next); /* Find the same address somewhere else */ apf = ipv6_findaddr(ap->iface->ctx, &ap->addr, 0); if ((apf == NULL || (apf->iface != ap->iface))) ipv6_deleteaddr(ap); if (!(ap->iface->options->options & DHCPCD_EXITING) && apf) { if (!timespecisset(&now)) clock_gettime(CLOCK_MONOTONIC, &now); ipv6_addaddr(apf, &now); } if (drop == 2) ipv6_freeaddr(ap); } } if (drop != 2) ipv6_freeaddr(ap); } } static struct ipv6_state * ipv6_getstate(struct interface *ifp) { struct ipv6_state *state; state = IPV6_STATE(ifp); if (state == NULL) { ifp->if_data[IF_DATA_IPV6] = calloc(1, sizeof(*state)); state = IPV6_STATE(ifp); if (state == NULL) { logerr(__func__); return NULL; } TAILQ_INIT(&state->addrs); TAILQ_INIT(&state->ll_callbacks); /* Regenerate new ids */ if (ifp->options && ip6_use_tempaddr(ifp->name)) ipv6_regentempifid(ifp); } return state; } void ipv6_handleifa(struct dhcpcd_ctx *ctx, int cmd, struct if_head *ifs, const char *ifname, const struct in6_addr *addr, uint8_t prefix_len, int addrflags, pid_t pid) { struct interface *ifp; struct ipv6_state *state; struct ipv6_addr *ia; struct ll_callback *cb; #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", ifname, cmd, dbp); #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; TAILQ_FOREACH(ia, &state->addrs, next) { if (IN6_ARE_ADDR_EQUAL(&ia->addr, addr)) break; } switch (cmd) { case RTM_DELADDR: if (ia != NULL) { TAILQ_REMOVE(&state->addrs, ia, next); /* Advertise the address if it exists on * another interface. */ ipv6nd_advertise(ia); /* We'll free it at the end of the function. */ } break; case RTM_NEWADDR: if (ia == NULL) { ia = ipv6_newaddr(ifp, addr, prefix_len, 0); #ifdef ALIAS_ADDR strlcpy(ia->alias, ifname, sizeof(ia->alias)); #endif if (if_getlifetime6(ia) == -1) { /* No support or address vanished. * Either way, just set a deprecated * infinite time lifetime and continue. * This is fine because we only want * to know this when trying to extend * temporary addresses. * As we can't extend infinite, we'll * create a new temporary address. */ ia->prefix_pltime = 0; ia->prefix_vltime = ND6_INFINITE_LIFETIME; } /* This is a minor regression against RFC 4941 * because the kernel only knows when the * lifetimes were last updated, not when the * address was initially created. * Provided dhcpcd is not restarted, this * won't be a problem. * If we don't like it, we can always * pretend lifetimes are infinite and always * generate a new temporary address on * restart. */ ia->acquired = ia->created; TAILQ_INSERT_TAIL(&state->addrs, ia, next); } ia->addr_flags = addrflags; ia->flags &= ~IPV6_AF_STALE; #ifdef IPV6_MANAGETEMPADDR if (ia->addr_flags & IN6_IFF_TEMPORARY) ia->flags |= IPV6_AF_TEMPORARY; #endif if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) { #ifdef IPV6_POLLADDRFLAG if (ia->addr_flags & IN6_IFF_TENTATIVE) { struct timespec tv; ms_to_ts(&tv, RETRANS_TIMER / 2); eloop_timeout_add_tv( ia->iface->ctx->eloop, &tv, ipv6_checkaddrflags, ia); break; } #endif if (ia->dadcallback) { ia->dadcallback(ia); if (ctx->options & DHCPCD_FORKED) goto out; } 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); if (ctx->options & DHCPCD_FORKED) goto out; } } } break; } if (ia == NULL) return; ipv6nd_handleifa(cmd, ia, pid); #ifdef DHCP6 dhcp6_handleifa(cmd, ia, pid); #endif out: /* Done with the ia now, so free it. */ if (cmd == RTM_DELADDR) ipv6_freeaddr(ia); } int ipv6_hasaddr(const struct interface *ifp) { if (ipv6nd_iffindaddr(ifp, NULL, 0) != NULL) return 1; if (dhcp6_iffindaddr(ifp, NULL, 0) != NULL) return 1; return 0; } struct ipv6_addr * ipv6_iffindaddr(struct interface *ifp, const struct in6_addr *addr, int revflags) { struct ipv6_state *state; struct ipv6_addr *ap; state = IPV6_STATE(ifp); if (state) { TAILQ_FOREACH(ap, &state->addrs, next) { if (addr == NULL) { if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) && (!revflags || !(ap->addr_flags & revflags))) return ap; } else { if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr) && (!revflags || !(ap->addr_flags & revflags))) return ap; } } } return NULL; } static struct ipv6_addr * ipv6_iffindmaskaddr(const struct interface *ifp, const struct in6_addr *addr) { struct ipv6_state *state; struct ipv6_addr *ap; struct in6_addr mask; state = IPV6_STATE(ifp); if (state) { TAILQ_FOREACH(ap, &state->addrs, next) { if (ipv6_mask(&mask, ap->prefix_len) == -1) continue; if (IN6_ARE_MASKED_ADDR_EQUAL(&ap->addr, addr, &mask)) return ap; } } return NULL; } struct ipv6_addr * ipv6_findmaskaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr) { struct interface *ifp; struct ipv6_addr *ap; TAILQ_FOREACH(ifp, ctx->ifaces, next) { ap = ipv6_iffindmaskaddr(ifp, addr); if (ap != NULL) return ap; } return NULL; } int ipv6_addlinklocalcallback(struct interface *ifp, void (*callback)(void *), void *arg) { struct ipv6_state *state; struct ll_callback *cb; state = ipv6_getstate(ifp); TAILQ_FOREACH(cb, &state->ll_callbacks, next) { if (cb->callback == callback && cb->arg == arg) break; } if (cb == NULL) { cb = malloc(sizeof(*cb)); if (cb == NULL) { logerr(__func__); return -1; } cb->callback = callback; cb->arg = arg; TAILQ_INSERT_TAIL(&state->ll_callbacks, cb, next); } return 0; } static struct ipv6_addr * ipv6_newlinklocal(struct interface *ifp) { struct ipv6_addr *ia; struct in6_addr in6; memset(&in6, 0, sizeof(in6)); in6.s6_addr32[0] = htonl(0xfe800000); ia = ipv6_newaddr(ifp, &in6, 64, 0); if (ia != NULL) { ia->prefix_pltime = ND6_INFINITE_LIFETIME; ia->prefix_vltime = ND6_INFINITE_LIFETIME; } return ia; } static const uint8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; static const uint8_t allone[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static int ipv6_addlinklocal(struct interface *ifp) { struct ipv6_state *state; struct ipv6_addr *ap, *ap2; int dadcounter; /* Check sanity before malloc */ if (!(ifp->options->options & DHCPCD_SLAACPRIVATE)) { switch (ifp->family) { case ARPHRD_ETHER: /* Check for a valid hardware address */ if (ifp->hwlen != 6 && ifp->hwlen != 8) { errno = ENOTSUP; return -1; } if (memcmp(ifp->hwaddr, allzero, ifp->hwlen) == 0 || memcmp(ifp->hwaddr, allone, ifp->hwlen) == 0) { errno = EINVAL; return -1; } break; default: errno = ENOTSUP; return -1; } } state = ipv6_getstate(ifp); if (state == NULL) return -1; ap = ipv6_newlinklocal(ifp); if (ap == NULL) return -1; 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->family) { case ARPHRD_ETHER: if (ifp->hwlen == 6) { ap->addr.s6_addr[ 8] = ifp->hwaddr[0]; ap->addr.s6_addr[ 9] = ifp->hwaddr[1]; ap->addr.s6_addr[10] = ifp->hwaddr[2]; ap->addr.s6_addr[11] = 0xff; ap->addr.s6_addr[12] = 0xfe; ap->addr.s6_addr[13] = ifp->hwaddr[3]; ap->addr.s6_addr[14] = ifp->hwaddr[4]; ap->addr.s6_addr[15] = ifp->hwaddr[5]; } else if (ifp->hwlen == 8) memcpy(&ap->addr.s6_addr[8], ifp->hwaddr, 8); else { free(ap); errno = ENOTSUP; return -1; } break; } /* Sanity check: g bit must not indciate "group" */ if (EUI64_GROUP(&ap->addr)) { free(ap); errno = EINVAL; return -1; } EUI64_TO_IFID(&ap->addr); } /* Do we already have this address? */ TAILQ_FOREACH(ap2, &state->addrs, next) { if (IN6_ARE_ADDR_EQUAL(&ap->addr, &ap2->addr)) { if (ap2->addr_flags & IN6_IFF_DUPLICATED) { if (ifp->options->options & DHCPCD_SLAACPRIVATE) { dadcounter++; goto nextslaacprivate; } free(ap); errno = EADDRNOTAVAIL; return -1; } 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) { struct timespec tv; ms_to_ts(&tv, RETRANS_TIMER / 2); eloop_timeout_add_tv( ia->iface->ctx->eloop, &tv, ipv6_checkaddrflags, ia); } #endif return 0; } if (!CAN_ADD_LLADDR(ifp)) return 0; return ipv6_addlinklocal(ifp); } struct ipv6_addr * ipv6_newaddr(struct interface *ifp, const struct in6_addr *addr, uint8_t prefix_len, unsigned int flags) { struct ipv6_addr *ia; char buf[INET6_ADDRSTRLEN]; const char *cbp; bool tempaddr; int addr_flags; /* If adding a new DHCP / RA derived address, check current flags * from an existing address. */ ia = ipv6_iffindaddr(ifp, addr, 0); if (ia != NULL) addr_flags = ia->addr_flags; else addr_flags = IN6_IFF_TENTATIVE; ia = calloc(1, sizeof(*ia)); if (ia == NULL) goto err; ia->iface = ifp; ia->flags = IPV6_AF_NEW | flags; ia->addr_flags = addr_flags; ia->prefix_len = prefix_len; ia->dhcp6_fd = -1; #ifdef IPV6_AF_TEMPORARY tempaddr = ia->flags & IPV6_AF_TEMPORARY; #else tempaddr = false; #endif if (ia->flags & IPV6_AF_AUTOCONF && !tempaddr) { ia->prefix = *addr; ia->dadcounter = ipv6_makeaddr(&ia->addr, ifp, &ia->prefix, ia->prefix_len); if (ia->dadcounter == -1) goto err; } else if (ia->flags & IPV6_AF_RAPFX) { ia->prefix = *addr; return ia; } else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX) && prefix_len != 128) { ia->prefix = *addr; cbp = inet_ntop(AF_INET6, &ia->prefix, buf, sizeof(buf)); goto paddr; } else { 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->flags & IPV6_AF_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(char **env, const char *prefix, const struct interface *ifp) { char **ep; ssize_t n; struct ipv6_addr *ia; ep = env; n = 0; ia = ipv6_iffindaddr(UNCONST(ifp), &ifp->options->req_addr6, IN6_IFF_NOTUSEABLE); if (ia) { if (env) addvar(&ep, prefix, "ip6_address", ia->saddr); n++; } return n; } 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); if_initrt(ifp->ctx, AF_INET6); 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; if (IPV6_CSTATE(ifp)) { /* Regenerate new ids */ if (ip6_use_tempaddr(ifp->name)) ipv6_regentempifid(ifp); } /* Load existing routes */ if_initrt(ifp->ctx, AF_INET6); return 0; } void ipv6_freedrop(struct interface *ifp, int drop) { struct ipv6_state *state; struct ll_callback *cb; if (ifp == NULL) return; if ((state = IPV6_STATE(ifp)) == NULL) return; /* If we got here, we can get rid of any LL callbacks. */ while ((cb = TAILQ_FIRST(&state->ll_callbacks))) { TAILQ_REMOVE(&state->ll_callbacks, cb, next); free(cb); } ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL); if (drop) { if (ifp->ctx->ra_routers != NULL) { if_initrt(ifp->ctx, AF_INET6); rt_build(ifp->ctx, AF_INET6); } } else { /* Because we need to cache the addresses we don't control, * we only free the state on when NOT dropping addresses. */ free(state); ifp->if_data[IF_DATA_IPV6] = NULL; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); } } void ipv6_ctxfree(struct dhcpcd_ctx *ctx) { free(ctx->ra_routers); free(ctx->secret); } int ipv6_handleifa_addrs(int cmd, struct ipv6_addrhead *addrs, const struct ipv6_addr *addr, pid_t pid) { struct ipv6_addr *ia, *ian; uint8_t found, alldadcompleted; alldadcompleted = 1; found = 0; TAILQ_FOREACH_SAFE(ia, addrs, next, ian) { if (!IN6_ARE_ADDR_EQUAL(&addr->addr, &ia->addr)) { if (ia->flags & IPV6_AF_ADDED && !(ia->flags & IPV6_AF_DADCOMPLETED)) alldadcompleted = 0; continue; } switch (cmd) { case RTM_DELADDR: if (ia->flags & IPV6_AF_ADDED) { logwarnx("%s: pid %d deleted address %s", ia->iface->name, pid, ia->saddr); ia->flags &= ~IPV6_AF_ADDED; } if (ia->flags & IPV6_AF_DELEGATED) { TAILQ_REMOVE(addrs, ia, next); ipv6_deletedaddr(ia); ipv6_freeaddr(ia); } break; case RTM_NEWADDR: /* Safety - ignore tentative announcements */ if (addr->addr_flags & (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) break; if ((ia->flags & IPV6_AF_DADCOMPLETED) == 0) { found++; if (addr->addr_flags & IN6_IFF_DUPLICATED) ia->flags |= IPV6_AF_DUPLICATED; else ia->flags &= ~IPV6_AF_DUPLICATED; if (ia->dadcallback) ia->dadcallback(ia); /* We need to set this here in-case the * dadcallback function checks it */ ia->flags |= IPV6_AF_DADCOMPLETED; } break; } } return alldadcompleted ? found : 0; } #ifdef IPV6_MANAGETEMPADDR static const struct ipv6_addr * ipv6_findaddrid(struct dhcpcd_ctx *ctx, uint8_t *addr) { const struct interface *ifp; const struct ipv6_state *state; const struct ipv6_addr *ia; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if ((state = IPV6_CSTATE(ifp))) { TAILQ_FOREACH(ia, &state->addrs, next) { if (memcmp(&ia->addr.s6_addr[8], addr, 8) == 0) return ia; } } } return NULL; } static const uint8_t nullid[8]; static const uint8_t anycastid[8] = { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 }; static const uint8_t isatapid[4] = { 0x00, 0x00, 0x5e, 0xfe }; static void ipv6_regen_desync(struct interface *ifp, int force) { struct ipv6_state *state; time_t max; state = IPV6_STATE(ifp); /* RFC4941 Section 5 states that DESYNC_FACTOR must never be * greater than TEMP_VALID_LIFETIME - REGEN_ADVANCE. * I believe this is an error and it should be never be greateter than * TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE. */ max = ip6_temp_preferred_lifetime(ifp->name) - REGEN_ADVANCE; if (state->desync_factor && !force && state->desync_factor < max) return; if (state->desync_factor == 0) state->desync_factor = (time_t)arc4random_uniform(MIN(MAX_DESYNC_FACTOR, (uint32_t)max)); max = ip6_temp_preferred_lifetime(ifp->name) - state->desync_factor - REGEN_ADVANCE; eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempifid, ifp); } void ipv6_gentempifid(struct interface *ifp) { struct ipv6_state *state; MD5_CTX md5; uint8_t seed[16], digest[16]; int retry; if ((state = IPV6_STATE(ifp)) == NULL) return; retry = 0; if (memcmp(nullid, state->randomseed0, sizeof(nullid)) == 0) { uint32_t r; r = arc4random(); memcpy(seed, &r, sizeof(r)); r = arc4random(); memcpy(seed + sizeof(r), &r, sizeof(r)); } else memcpy(seed, state->randomseed0, sizeof(state->randomseed0)); memcpy(seed + sizeof(state->randomseed0), state->randomseed1, sizeof(state->randomseed1)); again: MD5Init(&md5); MD5Update(&md5, seed, sizeof(seed)); MD5Final(digest, &md5); /* RFC4941 Section 3.2.1.1 * Take the left-most 64bits and set bit 6 to zero */ memcpy(state->randomid, digest, sizeof(state->randomid)); state->randomid[0] = (uint8_t)(state->randomid[0] & ~EUI64_UBIT); /* RFC4941 Section 3.2.1.4 * Reject reserved or existing id's */ if (memcmp(nullid, state->randomid, sizeof(nullid)) == 0 || (memcmp(anycastid, state->randomid, 7) == 0 && (anycastid[7] & state->randomid[7]) == anycastid[7]) || memcmp(isatapid, state->randomid, sizeof(isatapid)) == 0 || ipv6_findaddrid(ifp->ctx, state->randomid)) { if (++retry < GEN_TEMPID_RETRY_MAX) { memcpy(seed, digest + 8, 8); goto again; } memset(state->randomid, 0, sizeof(state->randomid)); } /* RFC4941 Section 3.2.1.6 * Save the right-most 64bits of the digest */ memcpy(state->randomseed0, digest + 8, sizeof(state->randomseed0)); } /* RFC4941 Section 3.3.7 */ static void ipv6_tempdadcallback(void *arg) { struct ipv6_addr *ia = arg; if (ia->flags & IPV6_AF_DUPLICATED) { struct ipv6_addr *ia1; struct timespec tv; if (++ia->dadcounter == TEMP_IDGEN_RETRIES) { 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; const struct ipv6_state *cstate; int genid; struct in6_addr addr, mask; uint32_t randid[2]; const struct interface *ifp; const struct ipv6_addr *ap; struct ipv6_addr *ia; uint32_t i, trylimit; trylimit = TEMP_IDGEN_RETRIES; state = IPV6_STATE(ia0->iface); genid = 0; addr = ia0->addr; ipv6_mask(&mask, ia0->prefix_len); /* clear the old ifid */ for (i = 0; i < 4; i++) addr.s6_addr32[i] &= mask.s6_addr32[i]; again: if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) genid = 1; if (genid) { memcpy(state->randomseed1, &ia0->addr.s6_addr[8], sizeof(state->randomseed1)); ipv6_gentempifid(ia0->iface); if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) { errno = EFAULT; return NULL; } } memcpy(&randid[0], state->randomid, sizeof(randid[0])); memcpy(&randid[1], state->randomid + sizeof(randid[1]), sizeof(randid[2])); addr.s6_addr32[2] |= randid[0] & ~mask.s6_addr32[2]; addr.s6_addr32[3] |= randid[1] & ~mask.s6_addr32[3]; /* Ensure we don't already have it */ TAILQ_FOREACH(ifp, ia0->iface->ctx->ifaces, next) { cstate = IPV6_CSTATE(ifp); if (cstate) { TAILQ_FOREACH(ap, &cstate->addrs, next) { if (IN6_ARE_ADDR_EQUAL(&ap->addr, &addr)) { if (--trylimit == 0) { errno = EEXIST; return NULL; } genid = 1; goto again; } } } } ia = ipv6_newaddr(ia0->iface, &addr, ia0->prefix_len, IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY); /* Must be made tentative, for our DaD to work */ ia->addr_flags = IN6_IFF_TENTATIVE; ia->dadcallback = ipv6_tempdadcallback; ia->created = ia->acquired = now ? *now : ia0->acquired; /* Ensure desync is still valid */ ipv6_regen_desync(ia->iface, 0); /* RFC4941 Section 3.3.4 */ i = (uint32_t)(ip6_temp_preferred_lifetime(ia0->iface->name) - state->desync_factor); ia->prefix_pltime = MIN(ia0->prefix_pltime, i); i = (uint32_t)ip6_temp_valid_lifetime(ia0->iface->name); ia->prefix_vltime = MIN(ia0->prefix_vltime, i); if (ia->prefix_pltime <= REGEN_ADVANCE || ia->prefix_pltime > ia0->prefix_vltime) { errno = EINVAL; free(ia); return NULL; } 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)) { time_t max, ext; if (flags == 0) { if (ap->prefix_pltime - (uint32_t)(ia->acquired.tv_sec - ap->acquired.tv_sec) < REGEN_ADVANCE) continue; return ap; } if (!(ap->flags & IPV6_AF_ADDED)) ap->flags |= IPV6_AF_NEW | IPV6_AF_AUTOCONF; ap->flags &= ~IPV6_AF_STALE; /* RFC4941 Section 3.4 * Deprecated prefix, deprecate the temporary address */ if (ia->prefix_pltime == 0) { ap->prefix_pltime = 0; goto valid; } /* Ensure desync is still valid */ ipv6_regen_desync(ap->iface, 0); /* RFC4941 Section 3.3.2 * Extend temporary times, but ensure that they * never last beyond the system limit. */ ext = ia->acquired.tv_sec + (time_t)ia->prefix_pltime; max = ap->created.tv_sec + ip6_temp_preferred_lifetime(ap->iface->name) - state->desync_factor; if (ext < max) ap->prefix_pltime = ia->prefix_pltime; else ap->prefix_pltime = (uint32_t)(max - ia->acquired.tv_sec); valid: ext = ia->acquired.tv_sec + (time_t)ia->prefix_vltime; max = ap->created.tv_sec + ip6_temp_valid_lifetime(ap->iface->name); if (ext < max) ap->prefix_vltime = ia->prefix_vltime; else ap->prefix_vltime = (uint32_t)(max - ia->acquired.tv_sec); /* Just extend the latest matching prefix */ ap->acquired = ia->acquired; /* If extending return the last match as * it's the most current. * If deprecating, deprecate any other addresses we * may have, although this should not be needed */ if (ia->prefix_pltime) return ap; if (first == NULL) first = ap; } } return first; } void ipv6_addtempaddrs(struct interface *ifp, const struct timespec *now) { struct ipv6_state *state; struct ipv6_addr *ia; state = IPV6_STATE(ifp); TAILQ_FOREACH(ia, &state->addrs, next) { if (ia->flags & IPV6_AF_TEMPORARY && !(ia->flags & IPV6_AF_STALE)) ipv6_addaddr(ia, now); } } static void ipv6_regentempaddr(void *arg) { struct ipv6_addr *ia = arg, *ia1; struct timespec tv; logdebugx("%s: regen temp addr %s", ia->iface->name, ia->saddr); clock_gettime(CLOCK_MONOTONIC, &tv); ia1 = ipv6_createtempaddr(ia, &tv); if (ia1) ipv6_addaddr(ia1, &tv); else logerr(__func__); } static void ipv6_regentempifid(void *arg) { struct interface *ifp = arg; struct ipv6_state *state; state = IPV6_STATE(ifp); if (memcmp(state->randomid, nullid, sizeof(state->randomid))) ipv6_gentempifid(ifp); ipv6_regen_desync(ifp, 1); } #endif /* IPV6_MANAGETEMPADDR */ void ipv6_markaddrsstale(struct interface *ifp, unsigned int flags) { struct ipv6_state *state; struct ipv6_addr *ia; state = IPV6_STATE(ifp); if (state == NULL) return; TAILQ_FOREACH(ia, &state->addrs, next) { if (flags == 0 || ia->flags & flags) ia->flags |= IPV6_AF_STALE; } } void ipv6_deletestaleaddrs(struct interface *ifp) { struct ipv6_state *state; struct ipv6_addr *ia, *ia1; state = IPV6_STATE(ifp); if (state == NULL) return; TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) { if (ia->flags & IPV6_AF_STALE) ipv6_handleifa(ifp->ctx, RTM_DELADDR, ifp->ctx->ifaces, ifp->name, &ia->addr, ia->prefix_len, 0, getpid()); } } static struct rt * inet6_makeroute(struct interface *ifp, const struct ra *rap) { struct rt *rt; if ((rt = rt_new(ifp)) == NULL) return NULL; #ifdef HAVE_ROUTE_METRIC rt->rt_metric = ifp->metric; #endif if (rap != NULL) rt->rt_mtu = rap->mtu; return rt; } static struct rt * inet6_makeprefix(struct interface *ifp, const struct ra *rap, const struct ipv6_addr *addr) { struct rt *rt; struct in6_addr netmask; if (addr == NULL || addr->prefix_len > 128) { errno = EINVAL; return NULL; } /* There is no point in trying to manage a /128 prefix, * ones without a lifetime. */ if (addr->prefix_len == 128 || addr->prefix_vltime == 0) return NULL; /* Don't install a reject route when not creating bigger prefixes. */ if (addr->flags & IPV6_AF_NOREJECT) return NULL; /* This address is the delegated prefix, so add a reject route for * it via the loopback interface. */ if (addr->flags & IPV6_AF_DELEGATEDPFX) { struct interface *lo0; TAILQ_FOREACH(lo0, ifp->ctx->ifaces, next) { if (lo0->flags & IFF_LOOPBACK) break; } if (lo0 == NULL) logwarnx("cannot find a loopback interface " "to reject via"); else ifp = lo0; } if ((rt = inet6_makeroute(ifp, rap)) == NULL) return NULL; sa_in6_init(&rt->rt_dest, &addr->prefix); ipv6_mask(&netmask, addr->prefix_len); sa_in6_init(&rt->rt_netmask, &netmask); if (addr->flags & IPV6_AF_DELEGATEDPFX) { rt->rt_flags |= RTF_REJECT; /* Linux does not like a gateway for a reject route. */ #ifndef __linux__ sa_in6_init(&rt->rt_gateway, &in6addr_loopback); #endif } else if (!(addr->flags & IPV6_AF_ONLINK)) sa_in6_init(&rt->rt_gateway, &rap->from); else rt->rt_gateway.sa_family = AF_UNSPEC; sa_in6_init(&rt->rt_ifa, &addr->addr); return rt; } static struct rt * inet6_makerouter(struct ra *rap) { struct rt *rt; if ((rt = inet6_makeroute(rap->iface, rap)) == NULL) return NULL; sa_in6_init(&rt->rt_dest, &in6addr_any); sa_in6_init(&rt->rt_netmask, &in6addr_any); sa_in6_init(&rt->rt_gateway, &rap->from); return rt; } #define RT_IS_DEFAULT(rtp) \ (IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) && \ IN6_ARE_ADDR_EQUAL(&((rtp)->mask), &in6addr_any)) static int inet6_staticroutes(struct rt_head *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) TAILQ_INSERT_TAIL(routes, rt, rt_next); } } } return 0; } static int inet6_raroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx, int expired, bool *have_default) { struct rt *rt; struct ra *rap; const struct ipv6_addr *addr; TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (rap->expired != expired) continue; TAILQ_FOREACH(addr, &rap->addrs, next) { if (addr->prefix_vltime == 0) continue; rt = inet6_makeprefix(rap->iface, rap, addr); if (rt) { rt->rt_dflags |= RTDF_RA; TAILQ_INSERT_TAIL(routes, rt, rt_next); } } if (rap->lifetime) { rt = inet6_makerouter(rap); if (rt) { rt->rt_dflags |= RTDF_RA; TAILQ_INSERT_TAIL(routes, rt, rt_next); if (have_default) *have_default = true; } } } return 0; } static int inet6_dhcproutes(struct rt_head *routes, struct dhcpcd_ctx *ctx, enum DH6S dstate) { struct interface *ifp; const struct dhcp6_state *d6_state; const struct ipv6_addr *addr; struct rt *rt; TAILQ_FOREACH(ifp, ctx->ifaces, next) { d6_state = D6_CSTATE(ifp); if (d6_state && d6_state->state == dstate) { TAILQ_FOREACH(addr, &d6_state->addrs, next) { rt = inet6_makeprefix(ifp, NULL, addr); if (rt) { rt->rt_dflags |= RTDF_DHCP; TAILQ_INSERT_TAIL(routes, rt, rt_next); } } } } return 0; } bool inet6_getroutes(struct dhcpcd_ctx *ctx, struct rt_head *routes) { bool have_default; /* Should static take priority? */ if (inet6_staticroutes(routes, ctx) == -1) return false; /* First add reachable routers and their prefixes */ have_default = false; if (inet6_raroutes(routes, ctx, 0, &have_default) == -1) return false; /* 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; #ifdef HAVE_ROUTE_METRIC /* If we have an unreachable router, we really do need to remove the * route to it beause it could be a lower metric than a reachable * router. Of course, we should at least have some routers if all * are unreachable. */ if (!have_default) { #endif /* Add our non-reachable routers and prefixes * Unsure if this is needed, but it's a close match to kernel * behaviour */ if (inet6_raroutes(routes, ctx, 1, NULL) == -1) return false; #ifdef HAVE_ROUTE_METRIC } #endif return true; } dhcpcd5-7.1.0/src/ipv6.h000066400000000000000000000227111342162717100146640ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 ALLNODES "ff02::1" #define ALLROUTERS "ff02::2" #define EUI64_GBIT 0x01 #define EUI64_UBIT 0x02 #define EUI64_TO_IFID(in6) do {(in6)->s6_addr[8] ^= EUI64_UBIT; } while (0) #define EUI64_GROUP(in6) ((in6)->s6_addr[8] & EUI64_GBIT) #ifndef ND6_INFINITE_LIFETIME # define ND6_INFINITE_LIFETIME ((uint32_t)~0) #endif /* RFC4941 constants */ #define TEMP_VALID_LIFETIME 604800 /* 1 week */ #define TEMP_PREFERRED_LIFETIME 86400 /* 1 day */ #define REGEN_ADVANCE 5 /* seconds */ #define MAX_DESYNC_FACTOR 600 /* 10 minutes */ #define TEMP_IDGEN_RETRIES 3 #define GEN_TEMPID_RETRY_MAX 5 /* RFC7217 constants */ #define IDGEN_RETRIES 3 #define IDGEN_DELAY 1 /* second */ #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 /* * 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(__NetBSD_Version__) && __NetBSD_Version__ >= 699002000 # undef IPV6_POLLADDRFLAG #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 0 # define IN6_IFF_DUPLICATED 0x04 # define IN6_IFF_DETACHED 0 #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 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_DUPLICATED (1U << 5) #define IPV6_AF_DADCOMPLETED (1U << 6) #define IPV6_AF_DELEGATED (1U << 7) #define IPV6_AF_DELEGATEDPFX (1U << 8) #define IPV6_AF_NOREJECT (1U << 9) #define IPV6_AF_REQUEST (1U << 10) #define IPV6_AF_STATIC (1U << 11) #define IPV6_AF_DELEGATEDLOG (1U << 12) #define IPV6_AF_RAPFX (1U << 13) #define IPV6_AF_EXTENDED (1U << 14) #ifdef IPV6_MANAGETEMPADDR #define IPV6_AF_TEMPORARY (1U << 15) #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 time_t desync_factor; uint8_t randomseed0[8]; /* upper 64 bits of MD5 digest */ uint8_t randomseed1[8]; /* lower 64 bits */ uint8_t randomid[8]; #endif }; #define IPV6_STATE(ifp) \ ((struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6]) #define IPV6_CSTATE(ifp) \ ((const struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6]) #define IPV6_STATE_RUNNING(ifp) ipv6_staticdadcompleted((ifp)) #ifdef INET6 int ipv6_init(struct dhcpcd_ctx *); int ipv6_makestableprivate(struct in6_addr *addr, const struct in6_addr *prefix, int prefix_len, const struct interface *ifp, int *dad_counter); int ipv6_makeaddr(struct in6_addr *, struct interface *, const struct in6_addr *, int); int ipv6_mask(struct in6_addr *, int); uint8_t ipv6_prefixlen(const struct in6_addr *); int ipv6_userprefix( const struct in6_addr *, short prefix_len, uint64_t user_number, struct in6_addr *result, short result_len); void ipv6_checkaddrflags(void *); void ipv6_markaddrsstale(struct interface *, unsigned int); void ipv6_deletestaleaddrs(struct interface *); int ipv6_addaddr(struct ipv6_addr *, const struct timespec *); ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs); void ipv6_deleteaddr(struct ipv6_addr *); void ipv6_freedrop_addrs(struct ipv6_addrhead *, int, const struct interface *); void ipv6_handleifa(struct dhcpcd_ctx *ctx, int, struct if_head *, const char *, const struct in6_addr *, uint8_t, int, pid_t); int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct ipv6_addr *, pid_t); struct ipv6_addr *ipv6_iffindaddr(struct interface *, const struct in6_addr *, int); int ipv6_hasaddr(const struct interface *); 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 *); 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 void ipv6_gentempifid(struct interface *); struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *, const struct timespec *); struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int); void ipv6_addtempaddrs(struct interface *, const struct timespec *); #else #define ipv6_gentempifid(a) {} #define ipv6_settempstale(a) {} #endif int ipv6_start(struct interface *); int ipv6_staticdadcompleted(const struct interface *); int ipv6_startstatic(struct interface *); ssize_t ipv6_env(char **, const char *, const struct interface *); void ipv6_ctxfree(struct dhcpcd_ctx *); bool inet6_getroutes(struct dhcpcd_ctx *, struct rt_head *); #else #define ipv6_start(a) (-1) #define ipv6_startstatic(a) #define ipv6_staticdadcompleted(a) (0) #define ipv6_hasaddr(a) (0) #define ipv6_free_ll_callbacks(a) {} #define ipv6_free(a) {} #define ipv6_ctxfree(a) {} #define ipv6_gentempifid(a) {} #endif #endif dhcpcd5-7.1.0/src/ipv6nd.c000066400000000000000000001261171342162717100152060ustar00rootroot00000000000000/* * dhcpcd - IPv6 ND handling * Copyright (c) 2006-2019 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 #define ELOOP_QUEUE 3 #include "common.h" #include "dhcpcd.h" #include "dhcp6.h" #include "eloop.h" #include "if.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" #include "route.h" #include "script.h" /* Debugging Router Solicitations is a lot of spam, so disable it */ //#define DEBUG_RS #ifndef ND_OPT_RDNSS #define ND_OPT_RDNSS 25 struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ uint8_t nd_opt_rdnss_type; uint8_t nd_opt_rdnss_len; uint16_t nd_opt_rdnss_reserved; uint32_t nd_opt_rdnss_lifetime; /* followed by list of IP prefixes */ }; __CTASSERT(sizeof(struct nd_opt_rdnss) == 8); #endif #ifndef ND_OPT_DNSSL #define ND_OPT_DNSSL 31 struct nd_opt_dnssl { /* DNSSL option RFC 6106 */ uint8_t nd_opt_dnssl_type; uint8_t nd_opt_dnssl_len; uint16_t nd_opt_dnssl_reserved; uint32_t nd_opt_dnssl_lifetime; /* followed by list of DNS servers */ }; __CTASSERT(sizeof(struct nd_opt_rdnss) == 8); #endif /* Impossible options, so we can easily add extras */ #define _ND_OPT_PREFIX_ADDR 255 + 1 /* Minimal IPv6 MTU */ #ifndef IPV6_MMTU #define IPV6_MMTU 1280 #endif #ifndef ND_RA_FLAG_RTPREF_HIGH #define ND_RA_FLAG_RTPREF_MASK 0x18 #define ND_RA_FLAG_RTPREF_HIGH 0x08 #define ND_RA_FLAG_RTPREF_MEDIUM 0x00 #define ND_RA_FLAG_RTPREF_LOW 0x18 #define ND_RA_FLAG_RTPREF_RSV 0x10 #endif /* RTPREF_MEDIUM has to be 0! */ #define RTPREF_HIGH 1 #define RTPREF_MEDIUM 0 #define RTPREF_LOW (-1) #define RTPREF_RESERVED (-2) #define RTPREF_INVALID (-3) /* internal */ #define MIN_RANDOM_FACTOR 500 /* millisecs */ #define MAX_RANDOM_FACTOR 1500 /* millisecs */ #define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */ #define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */ #if BYTE_ORDER == BIG_ENDIAN #define IPV6_ADDR_INT32_ONE 1 #define IPV6_ADDR_INT16_MLL 0xff02 #elif BYTE_ORDER == LITTLE_ENDIAN #define IPV6_ADDR_INT32_ONE 0x01000000 #define IPV6_ADDR_INT16_MLL 0x02ff #endif /* Debugging Neighbor Solicitations is a lot of spam, so disable it */ //#define DEBUG_NS // static void ipv6nd_handledata(void *); /* * Android ships buggy ICMP6 filter headers. * Supply our own until they fix their shit. * References: * https://android-review.googlesource.com/#/c/58438/ * http://code.google.com/p/android/issues/original?id=32621&seq=24 */ #ifdef __ANDROID__ #undef ICMP6_FILTER_WILLPASS #undef ICMP6_FILTER_WILLBLOCK #undef ICMP6_FILTER_SETPASS #undef ICMP6_FILTER_SETBLOCK #undef ICMP6_FILTER_SETPASSALL #undef ICMP6_FILTER_SETBLOCKALL #define ICMP6_FILTER_WILLPASS(type, filterp) \ ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) #define ICMP6_FILTER_WILLBLOCK(type, filterp) \ ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) #define ICMP6_FILTER_SETPASS(type, filterp) \ ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31)))) #define ICMP6_FILTER_SETBLOCK(type, filterp) \ ((((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31)))) #define ICMP6_FILTER_SETPASSALL(filterp) \ memset(filterp, 0, sizeof(struct icmp6_filter)); #define ICMP6_FILTER_SETBLOCKALL(filterp) \ memset(filterp, 0xff, sizeof(struct icmp6_filter)); #endif /* Support older systems with different defines */ #if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT) #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT #endif #if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) #define IPV6_RECVPKTINFO IPV6_PKTINFO #endif /* 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); } } static int ipv6nd_open(struct dhcpcd_ctx *ctx) { int on; struct icmp6_filter filt; if (ctx->nd_fd != -1) return ctx->nd_fd; #define SOCK_FLAGS SOCK_CLOEXEC | SOCK_NONBLOCK ctx->nd_fd = xsocket(PF_INET6, SOCK_RAW | SOCK_FLAGS, IPPROTO_ICMPV6); #undef SOCK_FLAGS if (ctx->nd_fd == -1) return -1; /* RFC4861 4.1 */ on = 255; if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &on, sizeof(on)) == -1) goto eexit; on = 1; if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == -1) goto eexit; on = 1; if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) == -1) goto eexit; ICMP6_FILTER_SETBLOCKALL(&filt); ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); if (setsockopt(ctx->nd_fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) == -1) goto eexit; eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx); return ctx->nd_fd; eexit: if (ctx->nd_fd != -1) { eloop_event_delete(ctx->eloop, ctx->nd_fd); close(ctx->nd_fd); ctx->nd_fd = -1; } return -1; } static int ipv6nd_makersprobe(struct interface *ifp) { struct rs_state *state; struct nd_router_solicit *rs; 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 dhcpcd_ctx *ctx; struct rs_state *state; struct sockaddr_in6 dst; struct cmsghdr *cm; struct in6_pktinfo pi; if (ipv6_linklocal(ifp) == NULL) { logdebugx("%s: delaying Router Solicitation for LL address", ifp->name); ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp); return; } memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; #ifdef HAVE_SA_LEN dst.sin6_len = sizeof(dst); #endif dst.sin6_scope_id = ifp->index; if (inet_pton(AF_INET6, ALLROUTERS, &dst.sin6_addr) != 1) { logerr(__func__); return; } state = RS_STATE(ifp); ctx = ifp->ctx; ctx->sndhdr.msg_name = (void *)&dst; ctx->sndhdr.msg_iov[0].iov_base = state->rs; ctx->sndhdr.msg_iov[0].iov_len = state->rslen; /* Set the outbound interface */ cm = CMSG_FIRSTHDR(&ctx->sndhdr); if (cm == NULL) /* unlikely */ return; cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(pi)); memset(&pi, 0, sizeof(pi)); pi.ipi6_ifindex = ifp->index; memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); logdebugx("%s: sending Router Solicitation", ifp->name); if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 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. */ } 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); ipv6nd_drop(ifp); dhcp6_dropnondelegates(ifp); } } static void ipv6nd_sendadvertisement(void *arg) { struct ipv6_addr *ia = arg; struct interface *ifp = ia->iface; struct dhcpcd_ctx *ctx = ifp->ctx; struct sockaddr_in6 dst; struct cmsghdr *cm; struct in6_pktinfo pi; const struct rs_state *state = RS_CSTATE(ifp); if (state == NULL || ifp->carrier == LINK_DOWN) goto freeit; memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; #ifdef SIN6_LEN dst.sin6_len = sizeof(dst); #endif dst.sin6_scope_id = ifp->index; if (inet_pton(AF_INET6, ALLNODES, &dst.sin6_addr) != 1) { logerr(__func__); return; } ctx->sndhdr.msg_name = (void *)&dst; ctx->sndhdr.msg_iov[0].iov_base = ia->na; ctx->sndhdr.msg_iov[0].iov_len = ia->na_len; /* Set the outbound interface. */ cm = CMSG_FIRSTHDR(&ctx->sndhdr); assert(cm != NULL); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(pi)); memset(&pi, 0, sizeof(pi)); pi.ipi6_ifindex = ifp->index; memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); logdebugx("%s: sending NA for %s", ifp->name, ia->saddr); if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) logerr(__func__); if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) { eloop_timeout_add_sec(ctx->eloop, state->retrans / 1000, ipv6nd_sendadvertisement, ia); return; } freeit: free(ia->na); ia->na = NULL; ia->na_count = 0; } void ipv6nd_advertise(struct ipv6_addr *ia) { struct dhcpcd_ctx *ctx; struct interface *ifp; struct ipv6_state *state; struct ipv6_addr *iap, *iaf; struct nd_neighbor_advert *na; if (IN6_IS_ADDR_MULTICAST(&ia->addr)) return; ctx = ia->iface->ctx; if_sortinterfaces(ctx); /* Find the most preferred address to advertise. */ iaf = NULL; TAILQ_FOREACH(ifp, ctx->ifaces, next) { state = IPV6_STATE(ifp); if (state == NULL || ifp->carrier == LINK_DOWN) continue; TAILQ_FOREACH(iap, &state->addrs, next) { if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) continue; /* Cancel any current advertisement. */ eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iap); /* Don't advertise what we can't use. */ if (iap->prefix_vltime == 0 || iap->addr_flags & IN6_IFF_NOTUSEABLE) continue; if (iaf == NULL) iaf = iap; } } if (iaf == NULL) return; /* Make the packet. */ ifp = iaf->iface; iaf->na_len = sizeof(*na); if (ifp->hwlen != 0) iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2); na = calloc(1, iaf->na_len); if (na == NULL) { logerr(__func__); return; } na->nd_na_type = ND_NEIGHBOR_ADVERT; na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; if (ip6_forwarding(ifp->name) == 1) na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; na->nd_na_target = ia->addr; if (ifp->hwlen != 0) { struct nd_opt_hdr *opt; opt = (struct nd_opt_hdr *)(na + 1); opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); memcpy(opt + 1, ifp->hwaddr, ifp->hwlen); } iaf->na_count = 0; free(iaf->na); iaf->na = na; eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf); ipv6nd_sendadvertisement(iaf); } void ipv6nd_expire(struct interface *ifp, uint32_t seconds) { struct ra *rap; struct timespec now; uint32_t vltime = seconds; uint32_t pltime = seconds / 2; if (ifp->ctx->ra_routers == NULL) return; clock_gettime(CLOCK_MONOTONIC, &now); TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp) { rap->acquired = now; rap->expired = seconds ? 0 : 1; if (seconds) { struct ipv6_addr *ia; rap->lifetime = seconds; TAILQ_FOREACH(ia, &rap->addrs, next) { if (ia->prefix_pltime > pltime || ia->prefix_vltime > vltime) { ia->acquired = now; if (ia->prefix_pltime != 0) ia->prefix_pltime = pltime; ia->prefix_vltime = vltime; } } ipv6_addaddrs(&rap->addrs); } } } if (seconds) ipv6nd_expirera(ifp); else rt_build(ifp->ctx, AF_INET6); } static void ipv6nd_reachable(struct ra *rap, int flags) { if (rap->lifetime == 0) return; if (flags & IPV6ND_REACHABLE) { if (rap->expired == 0) return; loginfox("%s: %s is reachable again", rap->iface->name, rap->sfrom); rap->expired = 0; } else { if (rap->expired != 0) return; logwarnx("%s: %s is unreachable, expiring it", rap->iface->name, rap->sfrom); rap->expired = 1; } rt_build(rap->iface->ctx, AF_INET6); /* XXX Not really an RA */ script_runreason(rap->iface, "ROUTERADVERT"); } void ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags) { struct ra *rap; if (ctx->ra_routers) { TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) { ipv6nd_reachable(rap, flags); break; } } } } const struct ipv6_addr * ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr, 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 void ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra) { eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); if (remove_ra) TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next); ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL); free(rap->data); free(rap); } static void ipv6nd_freedrop_ra(struct ra *rap, int drop) { ipv6nd_removefreedrop_ra(rap, 1, drop); } ssize_t ipv6nd_free(struct interface *ifp) { struct rs_state *state; struct ra *rap, *ran; struct dhcpcd_ctx *ctx; ssize_t n; state = RS_STATE(ifp); if (state == NULL) return 0; 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++; } } /* If we don't have any more IPv6 enabled interfaces, * close the global socket and release resources */ ctx = ifp->ctx; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (RS_STATE(ifp)) break; } if (ifp == NULL) { if (ctx->nd_fd != -1) { eloop_event_delete(ctx->eloop, ctx->nd_fd); close(ctx->nd_fd); ctx->nd_fd = -1; } } return n; } static int rtpref(struct ra *rap) { switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) { case ND_RA_FLAG_RTPREF_HIGH: return (RTPREF_HIGH); case ND_RA_FLAG_RTPREF_MEDIUM: case ND_RA_FLAG_RTPREF_RSV: return (RTPREF_MEDIUM); case ND_RA_FLAG_RTPREF_LOW: return (RTPREF_LOW); default: logerrx("rtpref: impossible RA flag %x", rap->flags); return (RTPREF_INVALID); } /* NOTREACHED */ } static void add_router(struct dhcpcd_ctx *ctx, struct ra *router) { struct ra *rap; TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (router->iface->metric < rap->iface->metric || (router->iface->metric == rap->iface->metric && rtpref(router) > rtpref(rap))) { TAILQ_INSERT_BEFORE(rap, router, next); return; } } TAILQ_INSERT_TAIL(ctx->ra_routers, router, next); } static int ipv6nd_scriptrun(struct ra *rap) { int hasdns, hasaddress, pid; struct ipv6_addr *ap; hasaddress = 0; /* If all addresses have completed DAD run the script */ TAILQ_FOREACH(ap, &rap->addrs, next) { if ((ap->flags & (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) == (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) { hasaddress = 1; if (!(ap->flags & IPV6_AF_DADCOMPLETED) && ipv6_iffindaddr(ap->iface, &ap->addr, 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 0; } } } /* If we don't require RDNSS then set hasdns = 1 so we fork */ if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS)) hasdns = 1; else { hasdns = rap->hasdns; } script_runreason(rap->iface, "ROUTERADVERT"); pid = 0; if (hasdns && (hasaddress || !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) pid = dhcpcd_daemonise(rap->iface->ctx); #if 0 else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED) && new_data) logwarnx("%s: did not fork due to an absent" " RDNSS option in the RA", ifp->name); } #endif return pid; } static void ipv6nd_addaddr(void *arg) { struct ipv6_addr *ap = arg; ipv6_addaddr(ap, NULL); } int ipv6nd_dadcompleted(const struct interface *ifp) { const struct ra *rap; const struct ipv6_addr *ap; TAILQ_FOREACH(rap, ifp->ctx->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; struct timespec tv; 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->flags & IPV6_AF_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) { 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'; tv.tv_sec = 0; tv.tv_nsec = (suseconds_t) arc4random_uniform(IDGEN_DELAY * NSEC_PER_SEC); timespecnorm(&tv); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_addaddr, 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); if (ipv6nd_scriptrun(rap)) return; } } ipv6nd_advertise(ia); } } #ifndef DHCP6 /* If DHCPv6 is compiled out, supply a shim to provide an error message * if IPv6RA requests DHCPv6. */ #undef dhcp6_start 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, struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit) { size_t i, olen; struct nd_router_advert *nd_ra; struct nd_opt_hdr ndo; struct nd_opt_prefix_info pi; struct nd_opt_mtu mtu; struct nd_opt_rdnss rdnss; uint8_t *p; struct ra *rap; struct in6_addr pi_prefix; struct ipv6_addr *ap; struct dhcp_opt *dho; bool new_rap, new_data; uint32_t old_lifetime; __printflike(1, 2) void (*logfunc)(const char *, ...); #ifdef IPV6_MANAGETEMPADDR uint8_t new_ap; #endif if (ifp == NULL) { #ifdef DEBUG_RS logdebugx("RA for unexpected interface from %s", ctx->sfrom); #endif return; } if (len < sizeof(struct nd_router_advert)) { logerrx("IPv6 RA packet too short from %s", ctx->sfrom); return; } /* RFC 4861 7.1.2 */ if (hoplimit != 255) { logerrx("invalid hoplimit(%d) in RA from %s", hoplimit, ctx->sfrom); return; } if (!IN6_IS_ADDR_LINKLOCAL(&ctx->from.sin6_addr)) { logerrx("RA from non local address %s", ctx->sfrom); return; } if (!(ifp->options->options & DHCPCD_IPV6RS)) { #ifdef DEBUG_RS logerrx("%s: unexpected RA from %s", ifp->name, ctx->sfrom); #endif return; } /* We could receive a RA before we sent a RS*/ if (ipv6_linklocal(ifp) == NULL) { #ifdef DEBUG_RS logdebugx("%s: received RA from %s (no link-local)", ifp->name, ctx->sfrom); #endif return; } if (ipv6_iffindaddr(ifp, &ctx->from.sin6_addr, IN6_IFF_TENTATIVE)) { logdebugx("%s: ignoring RA from ourself %s", ifp->name, ctx->sfrom); return; } TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (ifp == rap->iface && IN6_ARE_ADDR_EQUAL(&rap->from, &ctx->from.sin6_addr)) break; } nd_ra = (struct nd_router_advert *)icp; /* We don't want to spam the log with the fact we got an RA every * 30 seconds or so, so only spam the log if it's different. */ if (rap == NULL || (rap->data_len != len || memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) { if (rap) { free(rap->data); rap->data_len = 0; } 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 = ctx->from.sin6_addr; strlcpy(rap->sfrom, ctx->sfrom, sizeof(rap->sfrom)); TAILQ_INIT(&rap->addrs); new_rap = 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. */ logfunc = new_rap ? loginfox : logdebugx, logfunc("%s: Router Advertisement from %s", ifp->name, ctx->sfrom); clock_gettime(CLOCK_MONOTONIC, &rap->acquired); rap->flags = nd_ra->nd_ra_flags_reserved; old_lifetime = rap->lifetime; rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); if (!new_rap && rap->lifetime == 0 && old_lifetime != 0) logwarnx("%s: %s: no longer a default router", ifp->name, rap->sfrom); if (nd_ra->nd_ra_reachable) { rap->reachable = ntohl(nd_ra->nd_ra_reachable); if (rap->reachable > MAX_REACHABLE_TIME) rap->reachable = 0; } if (nd_ra->nd_ra_retransmit) { struct rs_state *state = RS_STATE(ifp); state->retrans = rap->retrans = ntohl(nd_ra->nd_ra_retransmit); } if (rap->lifetime) rap->expired = 0; rap->hasdns = 0; #ifdef IPV6_AF_TEMPORARY ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY); #endif TAILQ_FOREACH(ap, &rap->addrs, next) { ap->flags |= IPV6_AF_STALE; } len -= sizeof(struct nd_router_advert); p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); for (; len > 0; p += olen, len -= olen) { if (len < sizeof(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, ctx->sfrom); else logwarnx("%s: reject RA (option %d) from %s", ifp->name, ndo.nd_opt_type, ctx->sfrom); if (new_rap) ipv6nd_removefreedrop_ra(rap, 0, 0); else ipv6nd_free_ra(rap); return; } if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type)) continue; switch (ndo.nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: logfunc = new_data ? logerrx : logdebugx; if (ndo.nd_opt_len != 4) { logfunc("%s: invalid option len for prefix", ifp->name); continue; } memcpy(&pi, p, sizeof(pi)); if (pi.nd_opt_pi_prefix_len > 128) { logfunc("%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)) { logfunc("%s: invalid prefix in RA", ifp->name); continue; } if (ntohl(pi.nd_opt_pi_preferred_time) > ntohl(pi.nd_opt_pi_valid_time)) { logfunc("%s: pltime > vltime", ifp->name); continue; } TAILQ_FOREACH(ap, &rap->addrs, next) if (ap->prefix_len ==pi.nd_opt_pi_prefix_len && IN6_ARE_ADDR_EQUAL(&ap->prefix, &pi_prefix)) break; if (ap == NULL) { unsigned int flags; flags = IPV6_AF_RAPFX; if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO && rap->iface->options->options & DHCPCD_IPV6RA_AUTOCONF) flags |= IPV6_AF_AUTOCONF; ap = ipv6_newaddr(rap->iface, &pi_prefix, pi.nd_opt_pi_prefix_len, flags); if (ap == NULL) break; ap->prefix = pi_prefix; if (flags & IPV6_AF_AUTOCONF) ap->dadcallback = ipv6nd_dadcallback; ap->created = ap->acquired = rap->acquired; TAILQ_INSERT_TAIL(&rap->addrs, ap, next); #ifdef IPV6_MANAGETEMPADDR /* New address to dhcpcd RA handling. * If the address already exists and a valid * temporary address also exists then * extend the existing one rather than * create a new one */ if (flags & IPV6_AF_AUTOCONF && ipv6_iffindaddr(ifp, &ap->addr, IN6_IFF_NOTUSEABLE) && ipv6_settemptime(ap, 0)) new_ap = 0; else new_ap = 1; #endif } else { #ifdef IPV6_MANAGETEMPADDR new_ap = 0; #endif ap->flags &= ~IPV6_AF_STALE; ap->acquired = rap->acquired; } if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) ap->flags |= IPV6_AF_ONLINK; ap->prefix_vltime = ntohl(pi.nd_opt_pi_valid_time); ap->prefix_pltime = ntohl(pi.nd_opt_pi_preferred_time); #ifdef IPV6_MANAGETEMPADDR /* RFC4941 Section 3.3.3 */ if (ap->flags & IPV6_AF_AUTOCONF && ip6_use_tempaddr(ap->iface->name)) { if (!new_ap) { if (ipv6_settemptime(ap, 1) == NULL) new_ap = 1; } if (new_ap && ap->prefix_pltime) { if (ipv6_createtempaddr(ap, &ap->acquired) == NULL) logerr("ipv6_createtempaddr"); } } #endif break; case ND_OPT_MTU: memcpy(&mtu, p, sizeof(mtu)); mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu); if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) { logerrx("%s: invalid MTU %d", ifp->name, mtu.nd_opt_mtu_mtu); break; } rap->mtu = mtu.nd_opt_mtu_mtu; break; case ND_OPT_RDNSS: memcpy(&rdnss, p, sizeof(rdnss)); if (rdnss.nd_opt_rdnss_lifetime && rdnss.nd_opt_rdnss_len > 1) rap->hasdns = 1; break; default: continue; } } for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; i++, dho++) { if (has_option_mask(ifp->options->requiremasknd, dho->option)) { logwarnx("%s: reject RA (no option %s) from %s", ifp->name, dho->var, ctx->sfrom); if (new_rap) ipv6nd_removefreedrop_ra(rap, 0, 0); else ipv6nd_free_ra(rap); return; } } if (new_rap) add_router(ifp->ctx, rap); if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); goto handle_flag; } ipv6_addaddrs(&rap->addrs); #ifdef IPV6_MANAGETEMPADDR ipv6_addtempaddrs(ifp, &rap->acquired); #endif /* Find any freshly added routes, such as the subnet route. * We do this because we cannot rely on recieving the kernel * notification right now via our link socket. */ if_initrt(ifp->ctx, AF_INET6); rt_build(ifp->ctx, AF_INET6); if (ipv6nd_scriptrun(rap)) return; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */ handle_flag: if (!(ifp->options->options & DHCPCD_DHCP6)) goto nodhcp6; /* 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 { if (new_data) logdebugx("%s: No DHCPv6 instruction in RA", ifp->name); nodhcp6: if (ifp->ctx->options & DHCPCD_TEST) { eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } } /* Expire should be called last as the rap object could be destroyed */ ipv6nd_expirera(ifp); } int ipv6nd_hasra(const struct interface *ifp) { const struct ra *rap; if (ifp->ctx->ra_routers) { TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) if (rap->iface == ifp && !rap->expired) return 1; } return 0; } int ipv6nd_hasradhcp(const struct interface *ifp) { const struct ra *rap; if (ifp->ctx->ra_routers) { TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp && !rap->expired && (rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) return 1; } } return 0; } static const uint8_t * ipv6nd_getoption(struct dhcpcd_ctx *ctx, size_t *os, unsigned int *code, size_t *len, const uint8_t *od, size_t ol, struct dhcp_opt **oopt) { 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(char **env, const char *prefix, const struct interface *ifp) { size_t i, j, n, len, olen; struct ra *rap; char ndprefix[32], abuf[24]; struct dhcp_opt *opt; uint8_t *p; struct nd_opt_hdr ndo; struct ipv6_addr *ia; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); i = n = 0; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface != ifp) continue; i++; if (prefix != NULL) snprintf(ndprefix, sizeof(ndprefix), "%s_nd%zu", prefix, i); else snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i); if (env) setvar(&env[n], ndprefix, "from", rap->sfrom); n++; if (env) setvard(&env[n], ndprefix, "acquired", (size_t)rap->acquired.tv_sec); n++; if (env) setvard(&env[n], ndprefix, "now", (size_t)now.tv_sec); n++; /* Zero our indexes */ if (env) { for (j = 0, opt = rap->iface->ctx->nd_opts; j < rap->iface->ctx->nd_opts_len; j++, opt++) dhcp_zero_index(opt); for (j = 0, opt = rap->iface->options->nd_override; j < rap->iface->options->nd_override_len; j++, opt++) dhcp_zero_index(opt); } /* Unlike DHCP, ND6 options *may* occur more than once. * There is also no provision for option concatenation * unlike DHCP. */ len = rap->data_len - 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) { n += dhcp_envoption(rap->iface->ctx, env == NULL ? NULL : &env[n], 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 ) continue; j++; if (env) { snprintf(abuf, sizeof(abuf), "addr%zu", j); setvar(&env[n], ndprefix, abuf, ia->saddr); } n++; } } return (ssize_t)n; } 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, lt, expire, next; uint8_t expired, anyvalid, valid, validone; struct ipv6_addr *ia; ifp = arg; clock_gettime(CLOCK_MONOTONIC, &now); expired = 0; timespecclear(&next); anyvalid = 0; TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { if (rap->iface != ifp) continue; valid = validone = 0; if (rap->lifetime) { lt.tv_sec = (time_t)rap->lifetime; lt.tv_nsec = 0; timespecadd(&rap->acquired, <, &expire); if (rap->lifetime == 0 || timespeccmp(&now, &expire, >)) { if (!rap->expired) { logwarnx("%s: %s: router expired", ifp->name, rap->sfrom); rap->expired = expired = 1; rap->lifetime = 0; } } else { valid = 1; timespecsub(&expire, &now, <); if (!timespecisset(&next) || timespeccmp(&next, <, >)) next = lt; } } /* 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) { validone = 1; continue; } lt.tv_sec = (time_t)ia->prefix_vltime; lt.tv_nsec = 0; timespecadd(&ia->acquired, <, &expire); if (timespeccmp(&now, &expire, >)) { if (ia->flags & IPV6_AF_ADDED) { logwarnx("%s: expired address %s", ia->iface->name, 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 = 1; } else { timespecsub(&expire, &now, <); if (!timespecisset(&next) || timespeccmp(&next, <, >)) next = lt; validone = 1; } } /* XXX FixMe! * We need to extract the lifetime from each option and check * if that has expired or not. * If it has, zero the option out in the returned data. */ /* No valid lifetimes are left on the RA, so we might * as well punt it. */ if (!valid && !validone) ipv6nd_free_ra(rap); else anyvalid = 1; } if (timespecisset(&next)) eloop_timeout_add_tv(ifp->ctx->eloop, &next, ipv6nd_expirera, ifp); if (expired) { rt_build(ifp->ctx, AF_INET6); script_runreason(ifp, "ROUTERADVERT"); } /* No valid routers? Kill any DHCPv6. */ if (!anyvalid) dhcp6_dropnondelegates(ifp); } void ipv6nd_drop(struct interface *ifp) { struct ra *rap, *ran; uint8_t expired = 0; 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 = 1; ipv6nd_drop_ra(rap); } } if (expired) { rt_build(ifp->ctx, AF_INET6); if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP) script_runreason(ifp, "ROUTERADVERT"); } } static void ipv6nd_handlena(struct dhcpcd_ctx *ctx, struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit) { struct nd_neighbor_advert *nd_na; struct in6_addr nd_na_target; struct ra *rap; uint32_t is_router, is_solicited; char buf[INET6_ADDRSTRLEN]; const char *taddr; if (ifp == NULL) { #ifdef DEBUG_NS logdebugx("NA for unexpected interface from %s", ctx->sfrom); #endif return; } if ((size_t)len < sizeof(struct nd_neighbor_advert)) { logerrx("%s: IPv6 NA too short from %s", ifp->name, ctx->sfrom); return; } /* RFC 4861 7.1.2 */ if (hoplimit != 255) { logerrx("invalid hoplimit(%d) in NA from %s", hoplimit, ctx->sfrom); return; } nd_na = (struct nd_neighbor_advert *)icp; is_router = nd_na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER; is_solicited = nd_na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED; taddr = inet_ntop(AF_INET6, &nd_na->nd_na_target, buf, INET6_ADDRSTRLEN); /* nd_na->nd_na_target is not aligned. */ memcpy(&nd_na_target, &nd_na->nd_na_target, sizeof(nd_na_target)); if (IN6_IS_ADDR_MULTICAST(&nd_na_target)) { logerrx("%s: NA multicast address %s (%s)", ifp->name, taddr, ctx->sfrom); return; } TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (rap->iface == ifp && IN6_ARE_ADDR_EQUAL(&rap->from, &nd_na_target)) break; } if (rap == NULL) { #ifdef DEBUG_NS logdebugx("%s: unexpected NA from %s for %s", ifp->name, ctx->sfrom, taddr); #endif return; } #ifdef DEBUG_NS logdebugx("%s: %sNA for %s from %s", ifp->name, is_solicited ? "solicited " : "", taddr, ctx->sfrom); #endif /* Node is no longer a router, so remove it from consideration */ if (!is_router && !rap->expired) { loginfox("%s: %s not a router (%s)", ifp->name, taddr, ctx->sfrom); rap->expired = 1; rt_build(ifp->ctx, AF_INET6); script_runreason(ifp, "ROUTERADVERT"); return; } if (is_solicited && is_router && rap->lifetime) ipv6nd_reachable(rap, IPV6ND_REACHABLE); } static void ipv6nd_handledata(void *arg) { struct dhcpcd_ctx *ctx; ssize_t len; struct cmsghdr *cm; int hoplimit; struct in6_pktinfo pkt; struct icmp6_hdr *icp; struct interface *ifp; ctx = arg; ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); len = recvmsg_realloc(ctx->nd_fd, &ctx->rcvhdr, 0); if (len == -1) { logerr(__func__); return; } ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr, ctx->ntopbuf, INET6_ADDRSTRLEN); if ((size_t)len < sizeof(struct icmp6_hdr)) { logerrx("IPv6 ICMP packet too short from %s", ctx->sfrom); return; } pkt.ipi6_ifindex = 0; hoplimit = 0; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&ctx->rcvhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(&ctx->rcvhdr, cm)) { if (cm->cmsg_level != IPPROTO_IPV6) continue; switch(cm->cmsg_type) { case IPV6_PKTINFO: if (cm->cmsg_len == CMSG_LEN(sizeof(pkt))) memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt)); break; case IPV6_HOPLIMIT: if (cm->cmsg_len == CMSG_LEN(sizeof(int))) memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int)); break; } } if (pkt.ipi6_ifindex == 0) { logerrx("IPv6 RA/NA did not contain index from %s", ctx->sfrom); return; } /* Find the receiving interface */ TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (ifp->index == (unsigned int)pkt.ipi6_ifindex) break; } /* Don't do anything if the user hasn't configured it. */ if (ifp != NULL && (ifp->active != IF_ACTIVE_USER || !(ifp->options->options & DHCPCD_IPV6))) return; icp = (struct icmp6_hdr *)ctx->rcvhdr.msg_iov[0].iov_base; if (icp->icmp6_code == 0) { switch(icp->icmp6_type) { case ND_NEIGHBOR_ADVERT: ipv6nd_handlena(ctx, ifp, icp, (size_t)len, hoplimit); return; case ND_ROUTER_ADVERT: ipv6nd_handlera(ctx, ifp, icp, (size_t)len, hoplimit); return; } } logerrx("invalid IPv6 type %d or code %d from %s", icp->icmp6_type, icp->icmp6_code, ctx->sfrom); } static void ipv6nd_startrs1(void *arg) { struct interface *ifp = arg; struct rs_state *state; loginfox("%s: soliciting an IPv6 router", ifp->name); if (ipv6nd_open(ifp->ctx) == -1) { logerr(__func__); return; } state = RS_STATE(ifp); if (state == NULL) { ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state)); state = RS_STATE(ifp); if (state == NULL) { logerr(__func__); return; } } /* Always make a new probe as the underlying hardware * address could have changed. */ ipv6nd_makersprobe(ifp); if (state->rs == NULL) { logerr(__func__); return; } state->retrans = RETRANS_TIMER; state->rsprobes = 0; ipv6nd_sendrsprobe(ifp); } void ipv6nd_startrs(struct interface *ifp) { struct timespec tv; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) { ipv6nd_startrs1(ifp); return; } tv.tv_sec = 0; tv.tv_nsec = (suseconds_t)arc4random_uniform( MAX_RTR_SOLICITATION_DELAY * NSEC_PER_SEC); timespecnorm(&tv); logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds", ifp->name, timespec_to_double(&tv)); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_startrs1, ifp); return; } dhcpcd5-7.1.0/src/ipv6nd.h000066400000000000000000000077511342162717100152150ustar00rootroot00000000000000/* * dhcpcd - IPv6 ND handling * Copyright (c) 2006-2019 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 #include #include "config.h" #include "dhcpcd.h" #include "ipv6.h" struct ra { TAILQ_ENTRY(ra) next; struct interface *iface; struct in6_addr from; char sfrom[INET6_ADDRSTRLEN]; uint8_t *data; size_t data_len; struct timespec acquired; unsigned char flags; uint32_t lifetime; uint32_t reachable; uint32_t retrans; uint32_t mtu; struct ipv6_addrhead addrs; uint8_t hasdns; uint8_t expired; }; TAILQ_HEAD(ra_head, ra); struct rs_state { struct nd_router_solicit *rs; size_t rslen; int rsprobes; uint32_t retrans; }; #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 */ #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 IPV6ND_REACHABLE (1 << 0) #define IPV6ND_ROUTER (1 << 1) #ifdef INET6 void ipv6nd_printoptions(const struct dhcpcd_ctx *, const struct dhcp_opt *, size_t); void ipv6nd_startrs(struct interface *); ssize_t ipv6nd_env(char **, const char *, const struct interface *); const struct ipv6_addr *ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr, unsigned int flags); struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *, const struct in6_addr *, unsigned int); ssize_t ipv6nd_free(struct interface *); void ipv6nd_expirera(void *arg); int ipv6nd_hasra(const struct interface *); int ipv6nd_hasradhcp(const struct interface *); void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t); int ipv6nd_dadcompleted(const struct interface *); void ipv6nd_advertise(struct ipv6_addr *); void ipv6nd_expire(struct interface *, uint32_t); void ipv6nd_drop(struct interface *); void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int); #else #define ipv6nd_startrs(a) {} #define ipv6nd_free(a) {} #define ipv6nd_hasra(a) (0) #define ipv6nd_dadcompleted(a) (0) #define ipv6nd_expire(a, b) {} #endif #endif dhcpcd5-7.1.0/src/logerr.c000066400000000000000000000173311342162717100152670ustar00rootroot00000000000000/* * logerr: errx with logging * Copyright (c) 2006-2019 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 #define UNUSED(a) (void)(a) struct logctx { unsigned int log_opts; #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, }; #if defined(LOGERR_TAG) && 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) 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 void logprintdate(FILE *stream) { struct timeval tv; time_t now; struct tm tmnow; char buf[32]; if (gettimeofday(&tv, NULL) == -1) return; now = tv.tv_sec; tzset(); localtime_r(&now, &tmnow); strftime(buf, sizeof(buf), "%b %d %T ", &tmnow); fprintf(stream, "%s", buf); } #endif __printflike(3, 0) static void vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args) { 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)) logprintdate(stream); #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(); fprintf(stream, "%s", ctx->log_tag); } #endif log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) || (stream != stderr && ctx->log_opts & LOGERR_LOG_PID)); if (log_pid) fprintf(stream, "[%d]", getpid()); #ifdef LOGERR_TAG if (log_tag || log_pid) #else if (log_pid) #endif fprintf(stream, ": "); #else UNUSED(ctx); #endif va_copy(a, args); vfprintf(stream, fmt, a); fputc('\n', stream); va_end(a); } /* * 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 void vlogmessage(int pri, const char *fmt, va_list args) { struct logctx *ctx = &_logctx; 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))) vlogprintf_r(ctx, stderr, fmt, args); if (!(ctx->log_opts & LOGERR_LOG)) return; #ifdef SMALL vsyslog(pri, fmt, args); #else if (ctx->log_file == NULL) { vsyslog(pri, fmt, args); return; } if (pri == LOG_DEBUG && !(ctx->log_opts & LOGERR_DEBUG)) return; vlogprintf_r(ctx, ctx->log_file, fmt, args); #endif } #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)) #pragma GCC diagnostic pop #endif __printflike(2, 3) static 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)); } void logdebug(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogerrmessage(LOG_DEBUG, fmt, args); va_end(args); } void logdebugx(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogmessage(LOG_DEBUG, fmt, args); va_end(args); } void loginfo(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogerrmessage(LOG_INFO, fmt, args); va_end(args); } void loginfox(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogmessage(LOG_INFO, fmt, args); va_end(args); } void logwarn(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogerrmessage(LOG_WARNING, fmt, args); va_end(args); } void logwarnx(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogmessage(LOG_WARNING, fmt, args); va_end(args); } void logerr(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogerrmessage(LOG_ERR, fmt, args); va_end(args); } void logerrx(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogmessage(LOG_ERR, fmt, args); va_end(args); } 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; if (path == NULL) { int opts = 0; if (ctx->log_opts & LOGERR_LOG_PID) opts |= LOG_PID; openlog(NULL, opts, LOGERR_SYSLOG_FACILITY); return 1; } #ifndef SMALL if ((ctx->log_file = fopen(path, "a")) == 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(); #ifndef SMALL if (ctx->log_file == NULL) return; fclose(ctx->log_file); ctx->log_file = NULL; #endif #if defined(LOGERR_TAG) && defined(__linux__) free(_logprog); #endif } dhcpcd5-7.1.0/src/logerr.h000066400000000000000000000054111342162717100152700ustar00rootroot00000000000000/* * logerr: errx with logging * Copyright (c) 2006-2019 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 */ __printflike(1, 2) typedef void logfunc_t(const char *, ...); __printflike(1, 2) void logdebug(const char *, ...); __printflike(1, 2) void logdebugx(const char *, ...); __printflike(1, 2) void loginfo(const char *, ...); __printflike(1, 2) void loginfox(const char *, ...); __printflike(1, 2) void logwarn(const char *, ...); __printflike(1, 2) void logwarnx(const char *, ...); __printflike(1, 2) void logerr(const char *, ...); #define LOGERROR logerr("%s: %d", __FILE__, __LINE__) __printflike(1, 2) void logerrx(const char *, ...); 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 int logopen(const char *); void logclose(void); int logreopen(void); #endif dhcpcd5-7.1.0/src/route.c000066400000000000000000000315751342162717100151410ustar00rootroot00000000000000/* * dhcpcd - route management * Copyright (c) 2006-2019 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 "config.h" #include "common.h" #include "dhcpcd.h" #include "if.h" #include "ipv4.h" #include "ipv4ll.h" #include "ipv6.h" #include "logerr.h" #include "route.h" #include "sa.h" /* * 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); } void rt_init(struct dhcpcd_ctx *ctx) { TAILQ_INIT(&ctx->routes); TAILQ_INIT(&ctx->kroutes); TAILQ_INIT(&ctx->froutes); } 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 (sa_is_unspecified(&rt->rt_dest) && sa_is_unspecified(&rt->rt_netmask)) { 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, struct rt_head *rts, int af) { struct rt *rt, *rtn; if (rts == NULL) return; assert(ctx != NULL); assert(&ctx->froutes != rts); TAILQ_FOREACH_SAFE(rt, rts, rt_next, rtn) { if (af != AF_UNSPEC && rt->rt_dest.sa_family != af && rt->rt_gateway.sa_family != af) continue; TAILQ_REMOVE(rts, rt, rt_next); TAILQ_INSERT_TAIL(&ctx->froutes, rt, rt_next); } } void rt_headclear(struct rt_head *rts, int af) { struct rt *rt; if (rts == NULL || (rt = TAILQ_FIRST(rts)) == NULL) return; rt_headclear0(rt->rt_ifp->ctx, rts, af); } static void rt_headfree(struct rt_head *rts) { struct rt *rt; while ((rt = TAILQ_FIRST(rts))) { TAILQ_REMOVE(rts, rt, rt_next); free(rt); } } void rt_dispose(struct dhcpcd_ctx *ctx) { assert(ctx != NULL); rt_headfree(&ctx->routes); rt_headfree(&ctx->kroutes); rt_headfree(&ctx->froutes); } struct rt * rt_new0(struct dhcpcd_ctx *ctx) { struct rt *rt; assert(ctx != NULL); if ((rt = TAILQ_FIRST(&ctx->froutes)) != NULL) TAILQ_REMOVE(&ctx->froutes, rt, rt_next); else 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; #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; } void rt_free(struct rt *rt) { assert(rt != NULL); assert(rt->rt_ifp->ctx != NULL); TAILQ_INSERT_TAIL(&rt->rt_ifp->ctx->froutes, rt, rt_next); } void rt_freeif(struct interface *ifp) { struct dhcpcd_ctx *ctx; struct rt *rt, *rtn; if (ifp == NULL) return; ctx = ifp->ctx; TAILQ_FOREACH_SAFE(rt, &ctx->routes, rt_next, rtn) { if (rt->rt_ifp == ifp) { TAILQ_REMOVE(&ctx->routes, rt, rt_next); rt_free(rt); } } TAILQ_FOREACH_SAFE(rt, &ctx->kroutes, rt_next, rtn) { if (rt->rt_ifp == ifp) { TAILQ_REMOVE(&ctx->kroutes, rt, rt_next); rt_free(rt); } } } struct rt * rt_find(struct rt_head *rts, const struct rt *f) { struct rt *rt; assert(rts != NULL); assert(f != NULL); TAILQ_FOREACH(rt, rts, rt_next) { if (sa_cmp(&rt->rt_dest, &f->rt_dest) == 0 && #ifdef HAVE_ROUTE_METRIC (f->rt_ifp == NULL || rt->rt_ifp->metric == f->rt_ifp->metric) && #endif rt_cmp_netmask(f, rt) == 0) return rt; } return NULL; } static void rt_kfree(struct rt *rt) { struct dhcpcd_ctx *ctx; struct rt *f; assert(rt != NULL); ctx = rt->rt_ifp->ctx; if ((f = rt_find(&ctx->kroutes, rt)) != NULL) { TAILQ_REMOVE(&ctx->kroutes, f, rt_next); rt_free(f); } } /* 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) { struct dhcpcd_ctx *ctx; struct rt *f; assert(rt != NULL); ctx = rt->rt_ifp->ctx; f = rt_find(&ctx->kroutes, rt); switch(cmd) { case RTM_DELETE: if (f != NULL) { TAILQ_REMOVE(&ctx->kroutes, f, rt_next); rt_free(f); } if ((f = rt_find(&ctx->routes, rt)) != NULL) { TAILQ_REMOVE(&ctx->routes, f, rt_next); rt_desc("deleted", f); rt_free(f); } break; case RTM_ADD: if (f != NULL) break; if ((f = rt_new(rt->rt_ifp)) == NULL) break; memcpy(f, rt, sizeof(*f)); TAILQ_INSERT_TAIL(&ctx->kroutes, f, rt_next); break; } #if defined(INET) && defined(HAVE_ROUTE_METRIC) if (rt->rt_dest.sa_family == AF_INET) ipv4ll_recvrt(cmd, rt); #endif } static bool rt_add(struct rt *nrt, struct rt *ort) { struct dhcpcd_ctx *ctx; bool change; 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 = false; if (ort == NULL) { ort = rt_find(&ctx->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; } } 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) return true; 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)"); rt_kfree(ort); } return true; } /* 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 rt_kfree(ort); } #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 if (if_route(RTM_ADD, nrt) != -1) return true; #ifdef HAVE_ROUTE_METRIC logerr: #endif logerr("if_route (ADD)"); return false; } 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__); /* Remove the route from our kernel table so we can add a * IPv4LL default route if possible. */ else rt_kfree(rt); 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(struct rt *rt) { struct dhcpcd_ctx *ctx; struct rt *or; ctx = rt->rt_ifp->ctx; /* Do we already manage it? */ if ((or = rt_find(&ctx->routes, rt))) { 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(rt, or)) return false; } TAILQ_REMOVE(&ctx->routes, or, rt_next); rt_free(or); } else { if (rt->rt_dflags & RTDF_FAKE) { if ((or = rt_find(&ctx->kroutes, rt)) == NULL) return false; if (!rt_cmp(rt, or)) return false; } else { if (!rt_add(rt, NULL)) return false; } } return true; } void rt_build(struct dhcpcd_ctx *ctx, int af) { struct rt_head routes, added; struct rt *rt, *rtn; unsigned long long o; /* We need to have the interfaces in the correct order to ensure * our routes are managed correctly. */ if_sortinterfaces(ctx); TAILQ_INIT(&routes); TAILQ_INIT(&added); switch (af) { #ifdef INET case AF_INET: if (!inet_getroutes(ctx, &routes)) goto getfail; break; #endif #ifdef INET6 case AF_INET6: if (!inet6_getroutes(ctx, &routes)) goto getfail; break; #endif } TAILQ_FOREACH_SAFE(rt, &routes, rt_next, rtn) { if (rt->rt_dest.sa_family != af && rt->rt_gateway.sa_family != af) continue; /* Is this route already in our table? */ if ((rt_find(&added, rt)) != NULL) continue; if (rt_doroute(rt)) { TAILQ_REMOVE(&routes, rt, rt_next); TAILQ_INSERT_TAIL(&added, rt, rt_next); } } /* Remove old routes we used to manage. */ TAILQ_FOREACH_SAFE(rt, &ctx->routes, rt_next, rtn) { if (rt->rt_dest.sa_family != af && rt->rt_gateway.sa_family != af) continue; TAILQ_REMOVE(&ctx->routes, rt, rt_next); if (rt_find(&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); } TAILQ_INSERT_TAIL(&ctx->froutes, rt, rt_next); } rt_headclear(&ctx->routes, af); TAILQ_CONCAT(&ctx->routes, &added, rt_next); getfail: rt_headclear(&routes, AF_UNSPEC); } dhcpcd5-7.1.0/src/route.h000066400000000000000000000064531342162717100151430ustar00rootroot00000000000000/* * dhcpcd - route management * Copyright (c) 2006-2019 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 #include #include #include #include "dhcpcd.h" #include "sa.h" /* 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 #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 { TAILQ_ENTRY(rt) rt_next; 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 unsigned int rt_dflags; #define RTDF_INIT 0x01 /* Generated by if_initrt() */ #define RTDF_IFA_ROUTE 0x02 /* Address generated route */ #define RTDF_FAKE 0x04 /* Maybe us on lease reboot */ #define RTDF_RA 0x08 /* Router Advertisement */ #define RTDF_DHCP 0x10 /* DHCP route */ #define RTDF_STATIC 0x20 /* Configured in dhcpcd */ }; TAILQ_HEAD(rt_head, rt); void rt_init(struct dhcpcd_ctx *); void rt_dispose(struct dhcpcd_ctx *); struct rt * rt_find(struct rt_head *, const struct rt *); void rt_free(struct rt *); void rt_freeif(struct interface *); void rt_headclear0(struct dhcpcd_ctx *, struct rt_head *, int); void rt_headclear(struct rt_head *, int); void rt_headfreeif(struct rt_head *); struct rt * rt_new0(struct dhcpcd_ctx *); void rt_setif(struct rt *, struct interface *); struct rt * rt_new(struct interface *); void rt_recvrt(int, const struct rt *); void rt_build(struct dhcpcd_ctx *, int); #endif dhcpcd5-7.1.0/src/sa.c000066400000000000000000000224511342162717100143770ustar00rootroot00000000000000/* * Socket Address handling for dhcpcd * Copyright (c) 2015-2019 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 AF_PACKET #include #endif #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; } } 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_inprefix = true; memset(&ss, 0, sizeof(ss)); ss.sa.sa_family = sa->sa_family; sa_fromprefix(&ss.sa, prefix); assert(sa_cmp(sa, &ss.sa) == 0); sa_inprefix = false; } #endif return prefix; } int sa_fromprefix(struct sockaddr *sa, int prefix) { uint8_t *ap; int max_prefix, bytes, bits, i; switch (sa->sa_family) { #ifdef INET case AF_INET: max_prefix = 32; #ifdef HAVE_SA_LEN sa->sa_len = sizeof(struct sockaddr_in); #endif break; #endif #ifdef INET6 case AF_INET6: max_prefix = 128; #ifdef HAVE_SA_LEN sa->sa_len = sizeof(struct sockaddr_in6); #endif break; #endif default: errno = EAFNOSUPPORT; return -1; } bytes = prefix / NBBY; bits = prefix % NBBY; ap = (uint8_t *)sa + sa_addroffset(sa); for (i = 0; i < bytes; i++) *ap++ = 0xff; if (bits) { uint8_t a; a = 0xff; a = (uint8_t)(a << (8 - bits)); *ap++ = a; } bytes = (max_prefix - prefix) / NBBY; for (i = 0; i < bytes; i++) *ap++ = 0x00; #ifndef NDEBUG /* Ensure the calculation is correct */ if (!sa_inprefix) { sa_inprefix = true; assert(sa_toprefix(sa) == prefix); sa_inprefix = false; } #endif return 0; } /* inet_ntop, but for sockaddr. */ const char * sa_addrtop(const struct sockaddr *sa, char *buf, socklen_t len) { const void *addr; assert(buf != NULL); #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 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 dhcpcd5-7.1.0/src/sa.h000066400000000000000000000050521342162717100144020ustar00rootroot00000000000000/* * Socket Address handling for dhcpcd * Copyright (c) 2015-2019 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 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); bool sa_is_unspecified(const struct sockaddr *); bool sa_is_allones(const struct sockaddr *); bool sa_is_loopback(const struct sockaddr *); void *sa_toaddr(struct sockaddr *); int sa_toprefix(const struct sockaddr *); int sa_fromprefix(struct sockaddr *, int); const char *sa_addrtop(const struct sockaddr *, char *, socklen_t); int sa_cmp(const struct sockaddr *, const struct sockaddr *); void sa_in_init(struct sockaddr *, const struct in_addr *); void sa_in6_init(struct sockaddr *, const struct in6_addr *); #endif dhcpcd5-7.1.0/src/script.c000066400000000000000000000435711342162717100153060ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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 "config.h" #include "common.h" #include "dhcp.h" #include "dhcp6.h" #include "if.h" #include "if-options.h" #include "ipv4ll.h" #include "ipv6nd.h" #include "logerr.h" #include "script.h" /* Allow the OS to define another script env var name */ #ifndef RC_SVCNAME #define RC_SVCNAME "RC_SVCNAME" #endif #define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" static const char * const if_params[] = { "interface", "protocol", "reason", "pid", "ifcarrier", "ifmetric", "ifwireless", "ifflags", "ssid", "profile", "interface_order", NULL }; void if_printoptions(void) { const char * const *p; for (p = if_params; *p; p++) printf(" - %s\n", *p); } static int exec_script(const struct dhcpcd_ctx *ctx, char *const *argv, char *const *env) { pid_t pid; posix_spawnattr_t attr; int r; #ifdef USE_SIGNALS size_t i; short flags; sigset_t defsigs; #else UNUSED(ctx); #endif /* posix_spawn is a safe way of executing another image * and changing signals back to how they should be. */ if (posix_spawnattr_init(&attr) == -1) return -1; #ifdef USE_SIGNALS flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; posix_spawnattr_setflags(&attr, flags); sigemptyset(&defsigs); for (i = 0; i < dhcpcd_signals_len; i++) sigaddset(&defsigs, dhcpcd_signals[i]); posix_spawnattr_setsigdefault(&attr, &defsigs); posix_spawnattr_setsigmask(&attr, &ctx->sigset); #endif errno = 0; r = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); posix_spawnattr_destroy(&attr); if (r) { errno = r; return -1; } return pid; } #ifdef INET static char * make_var(const char *prefix, const char *var) { size_t len; char *v; len = strlen(prefix) + strlen(var) + 2; if ((v = malloc(len)) == NULL) { logerr(__func__); return NULL; } snprintf(v, len, "%s_%s", prefix, var); return v; } static int append_config(char ***env, size_t *len, const char *prefix, const char *const *config) { size_t i, j, e1; char **ne, *eq, **nep, *p; int ret; if (config == NULL) return 0; ne = *env; ret = 0; for (i = 0; config[i] != NULL; i++) { eq = strchr(config[i], '='); e1 = (size_t)(eq - config[i] + 1); for (j = 0; j < *len; j++) { if (strncmp(ne[j], prefix, strlen(prefix)) == 0 && ne[j][strlen(prefix)] == '_' && strncmp(ne[j] + strlen(prefix) + 1, config[i], e1) == 0) { p = make_var(prefix, config[i]); if (p == NULL) { ret = -1; break; } free(ne[j]); ne[j] = p; break; } } if (j == *len) { j++; p = make_var(prefix, config[i]); if (p == NULL) { ret = -1; break; } nep = realloc(ne, sizeof(char *) * (j + 1)); if (nep == NULL) { logerr(__func__); free(p); ret = -1; break; } ne = nep; ne[j - 1] = p; *len = j; } } *env = ne; return ret; } #endif static ssize_t arraytostr(const char *const *argv, char **s) { const char *const *ap; char *p; size_t len, l; if (*argv == NULL) return 0; len = 0; ap = argv; while (*ap) len += strlen(*ap++) + 1; *s = p = malloc(len); if (p == NULL) return -1; ap = argv; while (*ap) { l = strlen(*ap) + 1; memcpy(p, *ap, l); p += l; ap++; } return (ssize_t)len; } #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" }; static ssize_t make_env(const struct interface *ifp, const char *reason, char ***argv) { int protocol, r; char **env, **nenv, *p; size_t e, elen, l; #if defined(INET) || defined(INET6) ssize_t n; #endif const struct if_options *ifo = ifp->options; const struct interface *ifp2; int af; #ifdef INET const struct dhcp_state *state; #ifdef IPV4LL const struct ipv4ll_state *istate; #endif #endif #ifdef INET6 const struct dhcp6_state *d6_state; #endif #ifdef INET state = D_STATE(ifp); #ifdef IPV4LL istate = IPV4LL_CSTATE(ifp); #endif #endif #ifdef INET6 d6_state = D6_CSTATE(ifp); #endif if (strcmp(reason, "TEST") == 0) { if (1 == 2) {} #ifdef INET6 else if (d6_state && d6_state->new) protocol = PROTO_DHCP6; 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; else if (reason[strlen(reason) - 1] == '6') protocol = PROTO_DHCP6; 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, "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 /* When dumping the lease, we only want to report interface and reason - the other interface variables are meaningless */ if (ifp->ctx->options & DHCPCD_DUMPLEASE) elen = 2; else elen = 11; #define EMALLOC(i, l) if ((env[(i)] = malloc((l))) == NULL) goto eexit; /* Make our env + space for profile, wireless and debug */ env = calloc(1, sizeof(char *) * (elen + 5 + 1)); if (env == NULL) goto eexit; e = strlen("interface") + strlen(ifp->name) + 2; EMALLOC(0, e); snprintf(env[0], e, "interface=%s", ifp->name); e = strlen("reason") + strlen(reason) + 2; EMALLOC(1, e); snprintf(env[1], e, "reason=%s", reason); if (ifp->ctx->options & DHCPCD_DUMPLEASE) goto dumplease; e = 20; EMALLOC(2, e); snprintf(env[2], e, "pid=%d", getpid()); EMALLOC(3, e); snprintf(env[3], e, "ifcarrier=%s", ifp->carrier == LINK_UNKNOWN ? "unknown" : ifp->carrier == LINK_UP ? "up" : "down"); EMALLOC(4, e); snprintf(env[4], e, "ifmetric=%d", ifp->metric); EMALLOC(5, e); snprintf(env[5], e, "ifwireless=%d", ifp->wireless); EMALLOC(6, e); snprintf(env[6], e, "ifflags=%u", ifp->flags); EMALLOC(7, e); snprintf(env[7], e, "ifmtu=%d", if_getmtu(ifp)); l = e = strlen("interface_order="); TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { e += strlen(ifp2->name) + 1; } EMALLOC(8, e); p = env[8]; strlcpy(p, "interface_order=", e); e -= l; p += l; TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { l = strlcpy(p, ifp2->name, e); p += l; e -= l; *p++ = ' '; e--; } *--p = '\0'; if (strcmp(reason, "STOPPED") == 0) { env[9] = strdup("if_up=false"); if (ifo->options & DHCPCD_RELEASE) env[10] = strdup("if_down=true"); else env[10] = strdup("if_down=false"); } else if (strcmp(reason, "TEST") == 0 || strcmp(reason, "PREINIT") == 0 || strcmp(reason, "CARRIER") == 0 || strcmp(reason, "UNKNOWN") == 0) { env[9] = strdup("if_up=false"); env[10] = strdup("if_down=false"); } else if (1 == 2 /* appease ifdefs */ #ifdef INET || (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)) || (protocol == PROTO_DHCP6 && d6_state && d6_state->new) || (protocol == PROTO_RA && ipv6nd_hasra(ifp)) #endif ) { env[9] = strdup("if_up=true"); env[10] = strdup("if_down=false"); } else { env[9] = strdup("if_up=false"); env[10] = strdup("if_down=true"); } if (env[9] == NULL || env[10] == NULL) goto eexit; if (protocols[protocol] != NULL) { r = asprintf(&env[elen], "protocol=%s", protocols[protocol]); if (r == -1) goto eexit; elen++; } if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { e = 20; EMALLOC(elen, e); snprintf(env[elen++], e, "if_afwaiting=%d", af); } if ((af = dhcpcd_afwaiting(ifp->ctx)) != AF_MAX) { TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { if ((af = dhcpcd_ifafwaiting(ifp2)) != AF_MAX) break; } } if (af != AF_MAX) { e = 20; EMALLOC(elen, e); snprintf(env[elen++], e, "af_waiting=%d", af); } if (ifo->options & DHCPCD_DEBUG) { e = strlen("syslog_debug=true") + 1; EMALLOC(elen, e); snprintf(env[elen++], e, "syslog_debug=true"); } if (*ifp->profile) { e = strlen("profile=") + strlen(ifp->profile) + 1; EMALLOC(elen, e); snprintf(env[elen++], e, "profile=%s", ifp->profile); } if (ifp->wireless) { static const char *pfx = "ifssid="; size_t pfx_len; ssize_t psl; pfx_len = strlen(pfx); psl = print_string(NULL, 0, OT_ESCSTRING, (const uint8_t *)ifp->ssid, ifp->ssid_len); if (psl != -1) { EMALLOC(elen, pfx_len + (size_t)psl + 1); memcpy(env[elen], pfx, pfx_len); print_string(env[elen] + pfx_len, (size_t)psl + 1, OT_ESCSTRING, (const uint8_t *)ifp->ssid, ifp->ssid_len); elen++; } } #ifdef INET if (protocol == PROTO_DHCP && state && state->old) { n = dhcp_env(NULL, NULL, state->old, state->old_len, ifp); if (n == -1) goto eexit; if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; n = dhcp_env(env + elen, "old", state->old, state->old_len, ifp); if (n == -1) goto eexit; elen += (size_t)n; } if (append_config(&env, &elen, "old", (const char *const *)ifo->config) == -1) goto eexit; } #endif #ifdef INET6 if (protocol == PROTO_DHCP6 && d6_state && d6_state->old) { n = dhcp6_env(NULL, NULL, ifp, d6_state->old, d6_state->old_len); if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; n = dhcp6_env(env + elen, "old", ifp, d6_state->old, d6_state->old_len); if (n == -1) goto eexit; elen += (size_t)n; } } #endif dumplease: #ifdef INET #ifdef IPV4LL if (protocol == PROTO_IPV4LL) { n = ipv4ll_env(NULL, NULL, ifp); if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; if ((n = ipv4ll_env(env + elen, istate->down ? "old" : "new", ifp)) == -1) goto eexit; elen += (size_t)n; } } #endif if (protocol == PROTO_DHCP && state && state->new) { n = dhcp_env(NULL, NULL, state->new, state->new_len, ifp); if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; n = dhcp_env(env + elen, "new", state->new, state->new_len, ifp); if (n == -1) goto eexit; elen += (size_t)n; } if (append_config(&env, &elen, "new", (const char *const *)ifo->config) == -1) goto eexit; } #endif #ifdef INET6 if (protocol == PROTO_STATIC6) { n = ipv6_env(NULL, NULL, ifp); if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; n = ipv6_env(env + elen, "new", ifp); if (n == -1) goto eexit; elen += (size_t)n; } } if (protocol == PROTO_DHCP6 && D6_STATE_RUNNING(ifp)) { n = dhcp6_env(NULL, NULL, ifp, d6_state->new, d6_state->new_len); if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; n = dhcp6_env(env + elen, "new", ifp, d6_state->new, d6_state->new_len); if (n == -1) goto eexit; elen += (size_t)n; } } if (protocol == PROTO_RA) { n = ipv6nd_env(NULL, NULL, ifp); if (n > 0) { nenv = realloc(env, sizeof(char *) * (elen + (size_t)n + 1)); if (nenv == NULL) goto eexit; env = nenv; n = ipv6nd_env(env + elen, NULL, ifp); if (n == -1) goto eexit; elen += (size_t)n; } } #endif /* Add our base environment */ if (ifo->environ) { e = 0; while (ifo->environ[e++]) ; nenv = realloc(env, sizeof(char *) * (elen + e + 1)); if (nenv == NULL) goto eexit; env = nenv; e = 0; while (ifo->environ[e]) { env[elen + e] = strdup(ifo->environ[e]); if (env[elen + e] == NULL) goto eexit; e++; } elen += e; } env[elen] = NULL; *argv = env; return (ssize_t)elen; eexit: logerr(__func__); if (env) { nenv = env; while (*nenv) free(*nenv++); free(env); } return -1; } static int send_interface1(struct fd_list *fd, const struct interface *iface, const char *reason) { char **env, **ep, *s; size_t elen; int retval; if (make_env(iface, reason, &env) == -1) return -1; s = NULL; elen = (size_t)arraytostr((const char *const *)env, &s); if ((ssize_t)elen == -1) { free(s); retval = -1; } else retval = control_queue(fd, s, elen, 1); ep = env; while (*ep) free(*ep++); free(env); return retval; } int send_interface(struct fd_list *fd, const struct interface *ifp) { const char *reason; int retval = 0; #ifdef INET const struct dhcp_state *d; #endif #ifdef INET6 const struct dhcp6_state *d6; #endif switch (ifp->carrier) { case LINK_UP: reason = "CARRIER"; break; case LINK_DOWN: reason = "NOCARRIER"; break; default: reason = "UNKNOWN"; break; } if (send_interface1(fd, ifp, reason) == -1) retval = -1; #ifdef INET if (D_STATE_RUNNING(ifp)) { d = D_CSTATE(ifp); if (send_interface1(fd, ifp, d->reason) == -1) retval = -1; } #ifdef IPV4LL if (IPV4LL_STATE_RUNNING(ifp)) { if (send_interface1(fd, ifp, "IPV4LL") == -1) retval = -1; } #endif #endif #ifdef INET6 if (IPV6_STATE_RUNNING(ifp)) { if (send_interface1(fd, ifp, "STATIC6") == -1) retval = -1; } if (RS_STATE_RUNNING(ifp)) { if (send_interface1(fd, ifp, "ROUTERADVERT") == -1) retval = -1; } if (D6_STATE_RUNNING(ifp)) { d6 = D6_CSTATE(ifp); if (send_interface1(fd, ifp, d6->reason) == -1) retval = -1; } #endif return retval; } int script_runreason(const struct interface *ifp, const char *reason) { char *argv[2]; char **env = NULL, **ep; char *svcname, *path, *bigenv; size_t e, elen = 0; pid_t pid; int status = 0; struct fd_list *fd; if (ifp->options->script && (ifp->options->script[0] == '\0' || strcmp(ifp->options->script, "/dev/null") == 0) && TAILQ_FIRST(&ifp->ctx->control_fds) == NULL) return 0; /* Make our env */ elen = (size_t)make_env(ifp, reason, &env); if (elen == (size_t)-1) { logerr(__func__); return -1; } if (ifp->options->script && (ifp->options->script[0] == '\0' || strcmp(ifp->options->script, "/dev/null") == 0)) goto send_listeners; argv[0] = ifp->options->script ? ifp->options->script : UNCONST(SCRIPT); argv[1] = NULL; logdebugx("%s: executing `%s' %s", ifp->name, argv[0], reason); /* Resize for PATH and RC_SVCNAME */ svcname = getenv(RC_SVCNAME); ep = reallocarray(env, elen + 2 + (svcname ? 1 : 0), sizeof(char *)); if (ep == NULL) { elen = 0; goto out; } env = ep; /* Add path to it */ path = getenv("PATH"); if (path) { e = strlen("PATH") + strlen(path) + 2; env[elen] = malloc(e); if (env[elen] == NULL) { elen = 0; goto out; } snprintf(env[elen], e, "PATH=%s", path); } else { env[elen] = strdup(DEFAULT_PATH); if (env[elen] == NULL) { elen = 0; goto out; } } if (svcname) { e = strlen(RC_SVCNAME) + strlen(svcname) + 2; env[++elen] = malloc(e); if (env[elen] == NULL) { elen = 0; goto out; } snprintf(env[elen], e, "%s=%s", RC_SVCNAME, svcname); } env[++elen] = NULL; pid = exec_script(ifp->ctx, argv, env); if (pid == -1) logerr("%s: %s", __func__, argv[0]); else if (pid != 0) { /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { logerr("%s: waitpid", __func__); status = 0; break; } } if (WIFEXITED(status)) { if (WEXITSTATUS(status)) logerrx("%s: %s: WEXITSTATUS %d", __func__, argv[0], WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) logerrx("%s: %s: %s", __func__, argv[0], strsignal(WTERMSIG(status))); } send_listeners: /* Send to our listeners */ bigenv = NULL; status = 0; TAILQ_FOREACH(fd, &ifp->ctx->control_fds, next) { if (!(fd->flags & FD_LISTEN)) continue; if (bigenv == NULL) { elen = (size_t)arraytostr((const char *const *)env, &bigenv); if ((ssize_t)elen == -1) { logerr("%s: arraytostr", ifp->name); break; } } if (control_queue(fd, bigenv, elen, 1) == -1) logerr("%s: control_queue", __func__); else status = 1; } if (!status) free(bigenv); out: /* Cleanup */ ep = env; while (*ep) free(*ep++); free(env); if (elen == 0) { logerr(__func__); return -1; } return WEXITSTATUS(status); } dhcpcd5-7.1.0/src/script.h000066400000000000000000000031301342162717100152760ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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" void if_printoptions(void); int send_interface(struct fd_list *, const struct interface *); int script_runreason(const struct interface *, const char *); #endif dhcpcd5-7.1.0/tests/000077500000000000000000000000001342162717100141775ustar00rootroot00000000000000dhcpcd5-7.1.0/tests/Makefile000066400000000000000000000004011342162717100156320ustar00rootroot00000000000000SUBDIRS= crypt eloop-bench all: for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done install: proginstall: clean: for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done test: for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done tests: test dhcpcd5-7.1.0/tests/crypt/000077500000000000000000000000001342162717100153405ustar00rootroot00000000000000dhcpcd5-7.1.0/tests/crypt/.gitignore000066400000000000000000000000111342162717100173200ustar00rootroot00000000000000run-test dhcpcd5-7.1.0/tests/crypt/GNUmakefile000066400000000000000000000003021342162717100174050ustar00rootroot00000000000000# 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 dhcpcd5-7.1.0/tests/crypt/Makefile000066400000000000000000000011321342162717100167750ustar00rootroot00000000000000TOP?= ../.. include ${TOP}/iconfig.mk PROG= run-test SRCS= run-test.c SRCS+= test_hmac_md5.c CFLAGS?= -O2 CSTD?= c99 CFLAGS+= -std=${CSTD} CPPFLAGS+= -I${TOP} -I${TOP}/src PCRYPT_SRCS= ${CRYPT_SRCS:compat/%=${TOP}/compat/%} OBJS+= ${SRCS:.c=.o} ${PCRYPT_SRCS:.c=.o} .c.o: ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ all: ${PROG} clean: rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} distclean: clean rm -f .depend .depend: ${SRCS} ${PCRYPT_SRCS} ${CC} ${CPPFLAGS} -MM ${SRCS} ${PCRYPT_SRCS} ${PROG}: ${DEPEND} ${OBJS} ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} test: ${PROG} ./${PROG} dhcpcd5-7.1.0/tests/crypt/README.md000066400000000000000000000004521342162717100166200ustar00rootroot00000000000000# 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. dhcpcd5-7.1.0/tests/crypt/run-test.c000066400000000000000000000027341342162717100172730ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2018 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "test.h" int main(void) { int r = 0; if (test_hmac_md5()) r = -1; return r; } dhcpcd5-7.1.0/tests/crypt/test.h000066400000000000000000000026561342162717100165010ustar00rootroot00000000000000/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2018 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef TEST_H int test_hmac_md5(void); #endif dhcpcd5-7.1.0/tests/crypt/test_hmac_md5.c000066400000000000000000000124051342162717100202220ustar00rootroot00000000000000/* * 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; } dhcpcd5-7.1.0/tests/eloop-bench/000077500000000000000000000000001342162717100163725ustar00rootroot00000000000000dhcpcd5-7.1.0/tests/eloop-bench/.gitignore000066400000000000000000000000141342162717100203550ustar00rootroot00000000000000eloop-bench dhcpcd5-7.1.0/tests/eloop-bench/Makefile000066400000000000000000000014501342162717100200320ustar00rootroot00000000000000TOP?= ../.. 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 depend: ${PROG}: ${DEPEND} ${OBJS} ${CC} ${LDFLAGS} -o $@ ${OBJS} test: ${PROG} ./${PROG} dhcpcd5-7.1.0/tests/eloop-bench/README.md000066400000000000000000000041201342162717100176460ustar00rootroot00000000000000# 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. dhcpcd5-7.1.0/tests/eloop-bench/eloop-bench.c000066400000000000000000000116741342162717100207420ustar00rootroot00000000000000/* * eloop benchmark * Copyright (c) 2006-2018 Roy Marples * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "eloop.h" #ifndef timespecsub #define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (/* CONSTCOND */ 0) #endif struct pipe { int fd[2]; }; static size_t good, bad, writes, fired; static size_t npipes = 100, nwrites = 100, nactive = 1; static struct pipe *pipes; static struct eloop *e; static void read_cb(void *arg) { struct pipe *p = arg; unsigned char buf[1]; if (read(p->fd[0], buf, 1) != 1) { warn("%s: read", __func__); bad++; } else good++; if (writes != 0) { writes--; if (write(p->fd[1], "e", 1) != 1) { warn("%s: write", __func__); bad++; } else fired++; } if (writes == 0 && fired == good) { //printf("fired %zu, good %zu, bad %zu\n", fired, good, bad); eloop_exit(e, good == fired && bad == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } } static int runone(struct timespec *t) { size_t i; struct pipe *p; struct timespec ts, te; int result; writes = nwrites; fired = good = 0; for (i = 0, p = pipes; i < nactive; i++, p++) { if (write(p->fd[1], "e", 1) != 1) err(EXIT_FAILURE, "send"); writes--; fired++; } if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) err(EXIT_FAILURE, "clock_gettime"); result = eloop_start(e, NULL); if (clock_gettime(CLOCK_MONOTONIC, &te) == -1) err(EXIT_FAILURE, "clock_gettime"); timespecsub(&te, &ts, t); return result; } int main(int argc, char **argv) { int c, result, exit_code; size_t i, nruns = 25; struct pipe *p; struct timespec ts, te, t; while ((c = getopt(argc, argv, "a:n:r:w:")) != -1) { switch (c) { case 'a': nactive = (size_t)atoi(optarg); break; case 'n': npipes = (size_t)atoi(optarg); break; case 'r': nruns = (size_t)atoi(optarg); break; case 'w': nwrites = (size_t)atoi(optarg); break; default: errx(EXIT_FAILURE, "illegal argument `%c'", c); } } if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) err(EXIT_FAILURE, "clock_gettime"); if ((e = eloop_new()) == NULL) err(EXIT_FAILURE, "eloop_init"); if (nactive > npipes) nactive = npipes; pipes = calloc(npipes, sizeof(*p)); if (pipes == NULL) err(EXIT_FAILURE, "malloc"); for (i = 0, p = pipes; i < npipes; i++, p++) { if (pipe2(p->fd, O_CLOEXEC | O_NONBLOCK) == -1) err(EXIT_FAILURE, "pipe"); if (eloop_event_add(e, p->fd[0], read_cb, p) == -1) err(EXIT_FAILURE, "eloop_event_add"); } printf("active = %zu, pipes = %zu, runs = %zu, writes = %zu\n", nactive, npipes, nruns, nwrites); exit_code = EXIT_SUCCESS; for (i = 0; i < nruns; i++) { result = runone(&t); if (result != EXIT_SUCCESS) exit_code = result; printf("run %zu took %lld.%.9ld seconds, result %d\n", i + 1, (long long)t.tv_sec, t.tv_nsec, result); } eloop_free(e); free(pipes); if (clock_gettime(CLOCK_MONOTONIC, &te) == -1) err(EXIT_FAILURE, "clock_gettime"); timespecsub(&te, &ts, &t); printf("total %lld.%.9ld seconds, result %d\n", (long long)t.tv_sec, t.tv_nsec, exit_code); exit(exit_code); }