diff options
author | Samuel Tan <samueltan@google.com> | 2015-08-13 16:11:35 -0700 |
---|---|---|
committer | Samuel Tan <samueltan@google.com> | 2015-08-18 11:18:17 -0700 |
commit | d7ed851d6fc26c5e7db96971dbe6b44342b97727 (patch) | |
tree | 3e1ae319b16457fefca59bc4a391d167ac8e6450 | |
parent | da5a2d5783b96e2b26cb58a6e4559b9a1fabbdc2 (diff) | |
download | dhcpcd-6.8.2-d7ed851d6fc26c5e7db96971dbe6b44342b97727.tar.gz |
Import dhcpcd 6.8.2
This is a simple import of dhcpcd 6.8.2.
SRC URI: http://roy.marples.name/downloads/dhcpcd/dhcpcd-6.8.2.tar.bz2
SHA1 Hash: 134a7c06bffc20789fedd5c0d259145d2dc4f058
(on the private dhcpcd repo 'trunk')
BUG: 22956197
Change-Id: I701af1d1532725b1058640fcb7ddeab1c24a797e
-rw-r--r-- | GNUmakefile | 12 | ||||
-rw-r--r-- | Makefile | 182 | ||||
-rw-r--r-- | Makefile.inc | 12 | ||||
-rw-r--r-- | README | 149 | ||||
-rw-r--r-- | arp.c | 422 | ||||
-rw-r--r-- | arp.h | 82 | ||||
-rw-r--r-- | auth.c | 671 | ||||
-rw-r--r-- | auth.h | 86 | ||||
-rw-r--r-- | bpf-filter.h | 99 | ||||
-rw-r--r-- | common.c | 370 | ||||
-rw-r--r-- | common.h | 190 | ||||
-rw-r--r-- | compat/arc4random.c | 151 | ||||
-rw-r--r-- | compat/arc4random.h | 34 | ||||
-rw-r--r-- | compat/arc4random_uniform.c | 56 | ||||
-rw-r--r-- | compat/arc4random_uniform.h | 34 | ||||
-rw-r--r-- | compat/closefrom.c | 49 | ||||
-rw-r--r-- | compat/closefrom.h | 31 | ||||
-rw-r--r-- | compat/dprintf.c | 65 | ||||
-rw-r--r-- | compat/dprintf.h | 43 | ||||
-rw-r--r-- | compat/endian.h | 71 | ||||
-rw-r--r-- | compat/getline.c | 75 | ||||
-rw-r--r-- | compat/getline.h | 36 | ||||
-rw-r--r-- | compat/pollts.c | 63 | ||||
-rw-r--r-- | compat/pollts.h | 39 | ||||
-rw-r--r-- | compat/posix_spawn.c | 157 | ||||
-rw-r--r-- | compat/posix_spawn.h | 53 | ||||
-rw-r--r-- | compat/pselect.c | 65 | ||||
-rw-r--r-- | compat/queue.h | 175 | ||||
-rw-r--r-- | compat/strlcpy.c | 52 | ||||
-rw-r--r-- | compat/strlcpy.h | 34 | ||||
-rw-r--r-- | compat/strtoi.c | 112 | ||||
-rw-r--r-- | compat/strtoi.h | 37 | ||||
-rw-r--r-- | config-null.mk | 3 | ||||
-rwxr-xr-x | configure | 1243 | ||||
-rw-r--r-- | control.c | 443 | ||||
-rw-r--r-- | control.h | 64 | ||||
-rw-r--r-- | crypt/crypt.h | 33 | ||||
-rw-r--r-- | crypt/hmac_md5.c | 89 | ||||
-rw-r--r-- | crypt/md5.c | 242 | ||||
-rw-r--r-- | crypt/md5.h | 33 | ||||
-rw-r--r-- | crypt/sha256.c | 294 | ||||
-rw-r--r-- | crypt/sha256.h | 50 | ||||
-rw-r--r-- | defs.h | 76 | ||||
-rw-r--r-- | dev.c | 188 | ||||
-rw-r--r-- | dev.h | 60 | ||||
-rw-r--r-- | dev/Makefile | 42 | ||||
-rw-r--r-- | dev/udev.c | 179 | ||||
-rw-r--r-- | dhcp-common.c | 927 | ||||
-rw-r--r-- | dhcp-common.h | 119 | ||||
-rw-r--r-- | dhcp.c | 3409 | ||||
-rw-r--r-- | dhcp.h | 300 | ||||
-rw-r--r-- | dhcp6.c | 3600 | ||||
-rw-r--r-- | dhcp6.h | 261 | ||||
-rw-r--r-- | dhcpcd-definitions.conf | 530 | ||||
-rw-r--r-- | dhcpcd-embedded.c.in | 36 | ||||
-rw-r--r-- | dhcpcd-embedded.h.in | 31 | ||||
-rw-r--r-- | dhcpcd-hooks/01-test | 8 | ||||
-rw-r--r-- | dhcpcd-hooks/02-dump | 8 | ||||
-rw-r--r-- | dhcpcd-hooks/10-mtu | 38 | ||||
-rw-r--r-- | dhcpcd-hooks/10-wpa_supplicant | 119 | ||||
-rw-r--r-- | dhcpcd-hooks/15-timezone | 48 | ||||
-rw-r--r-- | dhcpcd-hooks/20-resolv.conf | 164 | ||||
-rw-r--r-- | dhcpcd-hooks/29-lookup-hostname | 40 | ||||
-rw-r--r-- | dhcpcd-hooks/30-hostname | 154 | ||||
-rw-r--r-- | dhcpcd-hooks/50-dhcpcd-compat | 41 | ||||
-rw-r--r-- | dhcpcd-hooks/50-ntp.conf | 118 | ||||
-rw-r--r-- | dhcpcd-hooks/50-yp.conf | 56 | ||||
-rw-r--r-- | dhcpcd-hooks/50-ypbind.in | 86 | ||||
-rw-r--r-- | dhcpcd-hooks/GNUmakefile | 2 | ||||
-rw-r--r-- | dhcpcd-hooks/Makefile | 30 | ||||
-rw-r--r-- | dhcpcd-run-hooks.8.in | 222 | ||||
-rw-r--r-- | dhcpcd-run-hooks.in | 381 | ||||
-rw-r--r-- | dhcpcd.8.in | 737 | ||||
-rw-r--r-- | dhcpcd.c | 1889 | ||||
-rw-r--r-- | dhcpcd.conf | 43 | ||||
-rw-r--r-- | dhcpcd.conf.5.in | 821 | ||||
-rw-r--r-- | dhcpcd.h | 178 | ||||
-rw-r--r-- | duid.c | 165 | ||||
-rw-r--r-- | duid.h | 35 | ||||
-rw-r--r-- | eloop.c | 655 | ||||
-rw-r--r-- | eloop.h | 116 | ||||
-rwxr-xr-x | genembedc | 16 | ||||
-rwxr-xr-x | genembedh | 15 | ||||
-rw-r--r-- | iconfig.mk | 7 | ||||
-rw-r--r-- | if-bsd.c | 1686 | ||||
-rw-r--r-- | if-linux-wext.c | 90 | ||||
-rw-r--r-- | if-linux.c | 1814 | ||||
-rw-r--r-- | if-options.c | 2368 | ||||
-rw-r--r-- | if-options.h | 210 | ||||
-rw-r--r-- | if-sun.c | 183 | ||||
-rw-r--r-- | if.c | 674 | ||||
-rw-r--r-- | if.h | 172 | ||||
-rw-r--r-- | ipv4.c | 1093 | ||||
-rw-r--r-- | ipv4.h | 113 | ||||
-rw-r--r-- | ipv4ll.c | 281 | ||||
-rw-r--r-- | ipv4ll.h | 36 | ||||
-rw-r--r-- | ipv6.c | 2122 | ||||
-rw-r--r-- | ipv6.h | 276 | ||||
-rw-r--r-- | ipv6nd.c | 1762 | ||||
-rw-r--r-- | ipv6nd.h | 121 | ||||
-rw-r--r-- | script.c | 735 | ||||
-rw-r--r-- | script.h | 37 | ||||
-rw-r--r-- | test/GNUmakefile | 7 | ||||
-rw-r--r-- | test/Makefile | 35 | ||||
-rw-r--r-- | test/test.c | 38 | ||||
-rw-r--r-- | test/test.h | 32 | ||||
-rw-r--r-- | test/test_hmac_md5.c | 173 |
107 files changed, 36209 insertions, 0 deletions
diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..5565294 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,12 @@ +# GNU Make does not automagically include .depend +# Luckily it does read GNUmakefile over Makefile so we can work around it + +# Nasty hack so that make clean works without configure being run +TOP?= . +CONFIG_MK?= $(shell test -e ${TOP}/config.mk && \ + echo config.mk || echo config-null.mk) + +include Makefile +ifneq ($(wildcard .depend), ) +include .depend +endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36ff297 --- /dev/null +++ b/Makefile @@ -0,0 +1,182 @@ +# dhcpcd Makefile + +PROG= dhcpcd +SRCS= common.c control.c dhcpcd.c duid.c eloop.c +SRCS+= if.c if-options.c script.c +SRCS+= dhcp-common.c + +CFLAGS?= -O2 +MKDIRS= + +TOP?= . +include ${TOP}/iconfig.mk + +CSTD?= c99 +CFLAGS+= -std=${CSTD} + +SRCS+= ${DHCPCD_SRCS} + +SRCS+= auth.c +CPPFLAGS+= -I./crypt +CRYPT_SRCS= crypt/hmac_md5.c ${MD5_SRC} ${SHA256_SRC} + +OBJS+= ${SRCS:.c=.o} ${COMPAT_SRCS:.c=.o} ${CRYPT_SRCS:.c=.o} + +SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks +HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks + +MAN5= dhcpcd.conf.5 +MAN8= dhcpcd.8 dhcpcd-run-hooks.8 +CLEANFILES= dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8 + +SCRIPTS= dhcpcd-run-hooks +SCRIPTSDIR= ${LIBEXECDIR} +CLEANFILES+= dhcpcd-run-hooks + +FILES= dhcpcd.conf +FILESDIR= ${SYSCONFDIR} + +SUBDIRS= dhcpcd-hooks ${MKDIRS} + +SED_RUNDIR= -e 's:@RUNDIR@:${RUNDIR}:g' +SED_DBDIR= -e 's:@DBDIR@:${DBDIR}:g' +SED_LIBDIR= -e 's:@LIBDIR@:${LIBDIR}: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_SCRIPT= -e 's:@SCRIPT@:${SCRIPT}:g' +SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' + +DEPEND!= test -e .depend && echo ".depend" || echo "" +VERSION!= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' defs.h + +FOSSILID?= current + +DISTPREFIX?= ${PROG}-${VERSION} +DISTFILEGZ?= ${DISTPREFIX}.tar.gz +DISTFILE?= ${DISTPREFIX}.tar.bz2 + +HOST_SH?= /bin/sh + +CLEANFILES+= *.tar.bz2 + +.PHONY: import import-bsd dev test + +.SUFFIXES: .in + +.in: + ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \ + ${SED_SYS} ${SED_SCRIPT} \ + ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \ + $< > $@ + +all: config.h ${PROG} ${SCRIPTS} ${MAN5} ${MAN8} + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done + +dev: + cd dev && ${MAKE} + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +CLEANFILES+= dhcpcd-embedded.h dhcpcd-embedded.c + +dhcpcd-embedded.h: genembedh dhcpcd-definitions.conf dhcpcd-embedded.h.in + ${HOST_SH} ${.ALLSRC} $^ > $@ + +dhcpcd-embedded.c: genembedc dhcpcd-definitions.conf + ${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} + +test: + cd $@; ${MAKE} $@; ./$@ + +_embeddedinstall: dhcpcd-definitions.conf + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${CONFMODE} dhcpcd-definitions.conf ${DESTDIR}${SCRIPTSDIR} + +_proginstall: ${PROG} + ${INSTALL} -d ${DESTDIR}${SBINDIR} + ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${SBINDIR} + ${INSTALL} -d ${DESTDIR}${DBDIR} + +_scriptsinstall: ${SCRIPTS} + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${BINMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR} + +proginstall: _proginstall _scriptsinstall ${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} + test -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.conf || \ + ${INSTALL} -m ${CONFMODE} dhcpcd.conf ${DESTDIR}${SYSCONFDIR} + +install: proginstall _maninstall _confinstall + +clean: + rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} + for x in ${SUBDIRS} test; do cd $$x; ${MAKE} $@; cd ..; done + +distclean: clean + rm -f .depend config.h config.mk config.log + +dist: + fossil tarball --name ${DISTPREFIX} ${FOSSILID} ${DISTFILEGZ} + gunzip -c ${DISTFILEGZ} | bzip2 >${DISTFILE} + rm ${DISTFILEGZ} + +snapshot: + rm -rf /tmp/${DISTPREFIX} + ${INSTALL} -d /tmp/${DISTPREFIX} + cp -RPp * /tmp/${DISTPREFIX} + cd /tmp/${DISTPREFIX} && ${MAKE} distclean + cd /tmp && tar -cvjpf ${DISTFILE} ${DISTPREFIX} + mv /tmp/${DISTFILE} . + ls -l ${DISTFILE} + +import: ${SRCS} + rm -rf /tmp/${DISTPREFIX} + ${INSTALL} -d /tmp/${DISTPREFIX} + cp ${SRCS} dhcpcd.conf dhcpcd-definitions.conf *.in /tmp/${DISTPREFIX} + cp $$(${CC} ${CPPFLAGS} -DDEPGEN -MM ${SRCS} | \ + sed -e 's/^.*\.c //g' -e 's/.*\.c$$//g' -e 's/\\//g' | \ + tr ' ' '\n' | \ + sed -e '/^compat\//d' | \ + sed -e '/^crypt\//d' | \ + sort -u) /tmp/${DISTPREFIX}; \ + if test -n "${CRYPT_SRCS}"; then \ + ${INSTALL} -d /tmp/${DISTPREFIX}/crypt; \ + cp ${CRYPT_SRCS} /tmp/${DISTPREFIX}/crypt; \ + cp $$(${CC} ${CPPFLAGS} -DDEPGEN -MM ${CRYPT_SRCS} | \ + sed -e 's/^.*c //g' -e 's/.*\.c$$//g' -e 's/\\//g' | \ + tr ' ' '\n' | sed -e '/\/\.\.\//d' | \ + sort -u) /tmp/${DISTPREFIX}/crypt; \ + fi; + if test -n "${COMPAT_SRCS}"; then \ + ${INSTALL} -d /tmp/${DISTPREFIX}/compat; \ + cp ${COMPAT_SRCS} /tmp/${DISTPREFIX}/compat; \ + cp $$(${CC} ${CPPFLAGS} -DDEPGEN -MM ${COMPAT_SRCS} | \ + sed -e 's/^.*c //g' -e 's/.*\.c$$//g' -e 's/\\//g' | \ + tr ' ' '\n' | \ + sort -u) /tmp/${DISTPREFIX}/compat; \ + fi; + cd dhcpcd-hooks; ${MAKE} DISTPREFIX=${DISTPREFIX} $@ + +include Makefile.inc diff --git a/Makefile.inc b/Makefile.inc new file mode 100644 index 0000000..22151ab --- /dev/null +++ b/Makefile.inc @@ -0,0 +1,12 @@ +# System definitions + +PICFLAG?= -fPIC + +BINMODE?= 0555 +NONBINMODE?= 0444 +MANMODE?= ${NONBINMODE} +CONFMODE?= 0644 + +CC?= cc +INSTALL?= install +SED?= sed @@ -0,0 +1,149 @@ +dhcpcd - DHCP client daemon +Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + + +Installation +------------ +./configure; make; make install +man dhcpcd for command line options +man dhcpcd.conf for configuration options +man dhcpcd-run-hooks to learn how to hook scripts into dhcpcd events + + +Notes +----- +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. + +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 + +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. + +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 + +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. + +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 suppors 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. + +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. + +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 + +To shrink dhcpcd you can disable IPv4 or IPv6: + --disable-inet + --disable-inet6 + +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. + +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 + +Building for distribution (ie making a dhcpcd source tarball) now requires +gmake-4 or any BSD make. + + +Hooks +----- +Not all the hooks in dhcpcd-hooks are installed by default. +By default we install 01-test, 02-dump, 10-mtu, 10-wpa_supplicant, +15-timezone, 20-resolv.conf, 29-lookup-hostname and 30-hostname. +The default dhcpcd.conf disables the lookup-hostname hook by default. +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_oneup && $if_ipwaited + 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. + + +Compatibility +------------- +dhcpcd-5.0 is only fully command line compatible with dhcpcd-4.0 +For compatibility with older versions, use dhcpcd-4.0 + + +ChangeLog +--------- +We no longer supply a ChangeLog. +However, you're more than welcome to read the commit log at +http://roy.marples.name/projects/dhcpcd/timeline/ @@ -0,0 +1,422 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/socket.h> +#include <sys/types.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define ELOOP_QUEUE 5 +#include "config.h" +#include "arp.h" +#include "if.h" +#include "ipv4.h" +#include "common.h" +#include "dhcp.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4ll.h" + +#define ARP_LEN \ + (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) + +static 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; + + 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)); + return if_sendrawpacket(ifp, ETHERTYPE_ARP, arp_buffer, len); + +eexit: + errno = ENOBUFS; + return -1; +} + +void +arp_report_conflicted(const struct arp_state *astate, const struct arp_msg *amsg) +{ + + if (amsg) { + char buf[HWADDR_LEN * 3]; + + logger(astate->iface->ctx, LOG_ERR, + "%s: hardware address %s claims %s", + astate->iface->name, + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, + buf, sizeof(buf)), + inet_ntoa(astate->failed)); + } else + logger(astate->iface->ctx, LOG_ERR, + "%s: DAD detected %s", + astate->iface->name, inet_ntoa(astate->failed)); +} + +static void +arp_packet(void *arg) +{ + struct interface *ifp = arg; + const struct interface *ifn; + uint8_t arp_buffer[ARP_LEN]; + struct arphdr ar; + struct arp_msg arm; + ssize_t bytes; + struct dhcp_state *state; + struct arp_state *astate, *astaten; + unsigned char *hw_s, *hw_t; + int flags; + + state = D_STATE(ifp); + flags = 0; + while (!(flags & RAW_EOF)) { + bytes = if_readrawpacket(ifp, ETHERTYPE_ARP, + arp_buffer, sizeof(arp_buffer), &flags); + if (bytes == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: arp if_readrawpacket: %m", ifp->name); + dhcp_close(ifp); + return; + } + /* We must have a full ARP header */ + if ((size_t)bytes < sizeof(ar)) + continue; + memcpy(&ar, arp_buffer, sizeof(ar)); + /* Families must match */ + if (ar.ar_hrd != htons(ifp->family)) + continue; + /* Protocol must be IP. */ + if (ar.ar_pro != htons(ETHERTYPE_IP)) + continue; + if (ar.ar_pln != sizeof(arm.sip.s_addr)) + continue; + /* Only these types are recognised */ + if (ar.ar_op != htons(ARPOP_REPLY) && + ar.ar_op != htons(ARPOP_REQUEST)) + continue; + + /* Get pointers to the hardware addreses */ + hw_s = arp_buffer + sizeof(ar); + hw_t = hw_s + ar.ar_hln + ar.ar_pln; + /* Ensure we got all the data */ + if ((hw_t + ar.ar_hln + ar.ar_pln) - arp_buffer > bytes) + continue; + /* 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) + continue; + /* 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 */ + TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) { + if (astate->conflicted_cb) + astate->conflicted_cb(astate, &arm); + } + } +} + +static void +arp_open(struct interface *ifp) +{ + struct dhcp_state *state; + + state = D_STATE(ifp); + if (state->arp_fd == -1) { + state->arp_fd = if_openrawsocket(ifp, ETHERTYPE_ARP); + if (state->arp_fd == -1) { + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", + __func__, ifp->name); + return; + } + eloop_event_add(ifp->ctx->eloop, state->arp_fd, + arp_packet, ifp, NULL, NULL); + } +} + +static void +arp_announced(void *arg) +{ + struct arp_state *astate = arg; + + if (astate->announced_cb) { + astate->announced_cb(astate); + return; + } + + /* Nothing more to do, so free us */ + arp_free(astate); +} + +static void +arp_announce1(void *arg) +{ + struct arp_state *astate = arg; + struct interface *ifp = astate->iface; + + if (++astate->claims < ANNOUNCE_NUM) + logger(ifp->ctx, LOG_DEBUG, + "%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 + logger(ifp->ctx, LOG_DEBUG, + "%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) + logger(ifp->ctx, LOG_ERR, "send_arp: %m"); + eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT, + astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced, + astate); +} + +void +arp_announce(struct arp_state *astate) +{ + + arp_open(astate->iface); + astate->claims = 0; + arp_announce1(astate); +} + +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); + } + logger(ifp->ctx, LOG_DEBUG, + "%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) + logger(ifp->ctx, LOG_ERR, "send_arp: %m"); +} + +void +arp_probe(struct arp_state *astate) +{ + + arp_open(astate->iface); + astate->probes = 0; + logger(astate->iface->ctx, LOG_DEBUG, "%s: probing for %s", + astate->iface->name, inet_ntoa(astate->addr)); + arp_probe1(astate); +} + +static struct arp_state * +arp_find(struct interface *ifp, const struct in_addr *addr) +{ + struct arp_state *astate; + struct dhcp_state *state; + + state = D_STATE(ifp); + TAILQ_FOREACH(astate, &state->arp_states, next) { + if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp) + return astate; + } + errno = ESRCH; + return NULL; +} + +struct arp_state * +arp_new(struct interface *ifp, const struct in_addr *addr) +{ + struct arp_state *astate; + struct dhcp_state *state; + + if (addr && (astate = arp_find(ifp, addr))) + return astate; + + if ((astate = calloc(1, sizeof(*astate))) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__); + return NULL; + } + state = D_STATE(ifp); + astate->iface = ifp; + if (addr) + astate->addr = *addr; + TAILQ_INSERT_TAIL(&state->arp_states, astate, next); + 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 dhcp_state *state; + + if (astate) { + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate); + state = D_STATE(astate->iface); + TAILQ_REMOVE(&state->arp_states, astate, next); + if (state->arp_ipv4ll == astate) { + ipv4ll_stop(astate->iface); + state->arp_ipv4ll = NULL; + } + free(astate); + } +} + +void +arp_free_but(struct arp_state *astate) +{ + struct arp_state *p, *n; + struct dhcp_state *state; + + state = D_STATE(astate->iface); + TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) { + if (p != astate) + arp_free(p); + } +} + +void +arp_close(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + struct arp_state *astate; + + if (state == NULL) + return; + + if (state->arp_fd != -1) { + eloop_event_delete(ifp->ctx->eloop, state->arp_fd, 0); + close(state->arp_fd); + state->arp_fd = -1; + } + + while ((astate = TAILQ_FIRST(&state->arp_states))) { +#ifndef __clang_analyzer__ + /* clang guard needed for a more compex variant on this bug: + * http://llvm.org/bugs/show_bug.cgi?id=18222 */ + arp_free(astate); +#endif + } +} + +void +arp_handleifa(int cmd, struct interface *ifp, const struct in_addr *addr, + int flags) +{ +#ifdef IN_IFF_DUPLICATED + struct dhcp_state *state = D_STATE(ifp); + struct arp_state *astate, *asn; + + if (cmd != RTM_NEWADDR || (state = D_STATE(ifp)) == NULL) + return; + + TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, asn) { + if (astate->addr.s_addr == addr->s_addr) { + if (flags & IN_IFF_DUPLICATED) { + if (astate->conflicted_cb) + astate->conflicted_cb(astate, NULL); + } else if (!(flags & IN_IFF_NOTUSEABLE)) { + if (astate->probed_cb) + astate->probed_cb(astate); + } + } + } +#else + UNUSED(cmd); + UNUSED(ifp); + UNUSED(addr); + UNUSED(flags); +#endif +} @@ -0,0 +1,82 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARP_H +#define ARP_H + +/* ARP timings from RFC5227 */ +#define PROBE_WAIT 1 +#define PROBE_NUM 3 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +#define ANNOUNCE_NUM 2 +#define ANNOUNCE_INTERVAL 2 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + +#include "dhcpcd.h" + +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 *); + + struct in_addr addr; + int probes; + int claims; + struct in_addr failed; +}; +TAILQ_HEAD(arp_statehead, arp_state); + +#ifdef INET +void arp_report_conflicted(const struct arp_state *, const struct arp_msg *); +void arp_announce(struct arp_state *); +void arp_probe(struct arp_state *); +struct arp_state *arp_new(struct interface *, const struct in_addr *); +void arp_cancel(struct arp_state *); +void arp_free(struct arp_state *); +void arp_free_but(struct arp_state *); +void arp_close(struct interface *); + +void arp_handleifa(int, struct interface *, const struct in_addr *, int); +#else +#define arp_close(a) {} +#endif +#endif @@ -0,0 +1,671 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/file.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "auth.h" +#include "crypt/crypt.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "dhcpcd.h" + +#ifdef __sun +#define htonll +#define ntohll +#endif + +#ifndef htonll +#if (BYTE_ORDER == LITTLE_ENDIAN) +static inline uint64_t +htonll(uint64_t x) +{ + + return (uint64_t)htonl((uint32_t)(x >> 32)) | + (uint64_t)htonl((uint32_t)(x & 0xffffffff)) << 32; +} +#else /* (BYTE_ORDER == LITTLE_ENDIAN) */ +#define htonll(x) (x) +#endif +#endif /* htonll */ + +#ifndef ntohll +#if (BYTE_ORDER == LITTLE_ENDIAN) +static inline uint64_t +ntohll(uint64_t x) +{ + + return (uint64_t)ntohl((uint32_t)(x >> 32)) | + (uint64_t)ntohl((uint32_t)(x & 0xffffffff)) << 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 uint8_t *m, size_t mlen, int mp, int mt, + const uint8_t *data, size_t dlen) +{ + 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[HMAC_LENGTH]; + + if (dlen < 3 + sizeof(replay)) { + errno = EINVAL; + return NULL; + } + + /* 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); + if (state->token) { + 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 = 0; + break; + case AUTH_PROTO_DELAYED: + if (dlen < sizeof(secretid) + sizeof(hmac)) { + errno = EINVAL; + return NULL; + } + memcpy(&secretid, d, sizeof(secretid)); + d += sizeof(secretid); + dlen -= sizeof(secretid); + break; + case AUTH_PROTO_DELAYEDREALM: + if (dlen < sizeof(secretid) + sizeof(hmac)) { + errno = EINVAL; + return NULL; + } + realm_len = dlen - (sizeof(secretid) + sizeof(hmac)); + if (realm_len) { + realm = d; + d += realm_len; + dlen -= realm_len; + } + memcpy(&secretid, d, sizeof(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 */ + secretid = ntohl(secretid); + 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) { + *(mm + offsetof(struct dhcp_message, hwopcount)) = '\0'; + memset(mm + offsetof(struct dhcp_message, giaddr), 0, 4); + } + + memset(hmac, 0, sizeof(hmac)); + switch (algorithm) { + case AUTH_ALG_HMAC_MD5: + hmac_md5(mm, mlen, t->key, t->key_len, hmac); + break; + default: + errno = ENOSYS; + free(mm); + return NULL; + } + + free(mm); + if (memcmp(d, &hmac, 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) + { + if (!auth->last_replay_set) { + auth->last_replay = rdm; + auth->last_replay_set = 1; + } else + rdm = ++auth->last_replay; + /* report error? */ + } + fflush(fp); +#ifdef LOCK_EX + if (flocked == 0) + flock(fileno(fp), LOCK_UN); +#endif + fclose(fp); + return rdm; +} + +#define JAN_1970 2208988800U /* 1970 - 1900 in seconds */ +static uint64_t +get_next_rdm_monotonic_clock(struct auth *auth) +{ + struct timespec ts; + uint32_t pack[2]; + double frac; + uint64_t rdm; + + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) + return ++auth->last_replay; /* report error? */ + pack[0] = htonl((uint32_t)ts.tv_sec + JAN_1970); + frac = ((double)ts.tv_nsec / 1e9 * 0x100000000ULL); + pack[1] = htonl((uint32_t)frac); + + memcpy(&rdm, &pack, sizeof(rdm)); + 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, + uint8_t *m, size_t mlen, int mp, int mt, + uint8_t *data, size_t dlen) +{ + uint64_t rdm; + uint8_t hmac[HMAC_LENGTH]; + time_t now; + uint8_t hops, *p, info; + uint32_t giaddr, secretid; + + if (auth->protocol == 0 && t == NULL) { + TAILQ_FOREACH(t, &auth->tokens, next) { + if (t->secretid == 0 && + t->realm_len == 0) + 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_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))) + info = 0; + else + info = 1; + + /* Work out the auth area size. + * We only need to do this for DISCOVER messages */ + if (data == NULL) { + dlen = 1 + 1 + 1 + 8; + switch(auth->protocol) { + case AUTH_PROTO_TOKEN: + dlen += t->key_len; + break; + case AUTH_PROTO_DELAYEDREALM: + if (info && t) + dlen += t->realm_len; + /* FALLTHROUGH */ + case AUTH_PROTO_DELAYED: + if (info && t) + dlen += sizeof(t->secretid) + sizeof(hmac); + 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 */ + if (data < m || data > m + mlen || data + dlen > m + mlen) { + errno = ERANGE; + return -1; + } + + /* Write out our option */ + *data++ = auth->protocol; + *data++ = auth->algorithm; + *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); + 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 (!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 dhcp_message, hwopcount); + hops = *p; + *p = '\0'; + p = m + offsetof(struct dhcp_message, 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(m, mlen, t->key, t->key_len, hmac); + memcpy(data, hmac, sizeof(hmac)); + break; + } + + /* RFC3318, section 5.2 - restore giaddr and hops */ + if (mp == 4) { + p = m + offsetof(struct dhcp_message, hwopcount); + *p = hops; + p = m + offsetof(struct dhcp_message, giaddr); + memcpy(p, &giaddr, sizeof(giaddr)); + } + + /* Done! */ + return (int)(dlen - sizeof(hmac)); /* should be zero */ +} @@ -0,0 +1,86 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef AUTH_H +#define AUTH_H + +#include "config.h" + +#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_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; + uint8_t protocol; + uint8_t algorithm; + uint8_t rdm; + uint64_t last_replay; + uint8_t last_replay_set; + struct token_head tokens; +}; + +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 uint8_t *, size_t, int, int, + const uint8_t *, size_t); + +ssize_t dhcp_auth_encode(struct auth *, const struct token *, + uint8_t *, size_t, int, int, + uint8_t *, size_t); +#endif diff --git a/bpf-filter.h b/bpf-filter.h new file mode 100644 index 0000000..08df683 --- /dev/null +++ b/bpf-filter.h @@ -0,0 +1,99 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef BPF_ETHCOOK +# define BPF_ETHCOOK 0 +#endif +#ifndef BPF_WHOLEPACKET +# define BPF_WHOLEPACKET ~0U +#endif +static const struct bpf_insn arp_bpf_filter [] = { +#ifndef BPF_SKIPTYPE + /* Make sure this is an ARP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3), +#endif + /* Make sure this is an ARP REQUEST... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), + /* or ARP REPLY... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define arp_bpf_filter_len sizeof(arp_bpf_filter) / sizeof(arp_bpf_filter[0]) + + +/* dhcp_bpf_filter taken from bpf.c in dhcp-3.1.0 + * + * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * 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 ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * http://www.isc.org/ + */ + +static const struct bpf_insn dhcp_bpf_filter [] = { +#ifndef BPF_SKIPTYPE + /* Make sure this is an IP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), +#endif + /* Make sure it's a UDP packet... */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + /* Get the IP header length... */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 + BPF_ETHCOOK), + /* Make sure it's to the right port... */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define dhcp_bpf_filter_len sizeof(dhcp_bpf_filter) / sizeof(dhcp_bpf_filter[0]) diff --git a/common.c b/common.c new file mode 100644 index 0000000..f930ee0 --- /dev/null +++ b/common.c @@ -0,0 +1,370 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef __APPLE__ +# include <mach/mach_time.h> +# include <mach/kern_return.h> +#endif + +#include <sys/param.h> +#include <sys/time.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#ifdef BSD +# include <paths.h> +#endif +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "common.h" +#include "dhcpcd.h" +#include "if-options.h" + +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/null" +#endif + +const char * +get_hostname(char *buf, size_t buflen, int short_hostname) +{ + char *p; + + if (gethostname(buf, buflen) != 0) + return NULL; + buf[buflen - 1] = '\0'; + if (strcmp(buf, "(none)") == 0 || + strcmp(buf, "localhost") == 0 || + strncmp(buf, "localhost.", strlen("localhost.")) == 0 || + buf[0] == '.') + return NULL; + + if (short_hostname) { + p = strchr(buf, '.'); + if (p) + *p = '\0'; + } + + return buf; +} + +/* Handy function to get the time. + * We only care about time advancements, not the actual time itself + * Which is why we use CLOCK_MONOTONIC, but it is not available on all + * platforms. + */ +#define NO_MONOTONIC "host does not support a monotonic clock - timing can skew" +int +get_monotonic(struct timespec *ts) +{ + +#if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC) + return clock_gettime(CLOCK_MONOTONIC, ts); +#elif defined(__APPLE__) + /* We can use mach kernel functions here. + * This is crap though - why can't they implement clock_gettime?*/ + static struct mach_timebase_info info = { 0, 0 }; + static double factor = 0.0; + uint64_t nano; + long rem; + + if (!posix_clock_set) { + if (mach_timebase_info(&info) == KERN_SUCCESS) { + factor = (double)info.numer / (double)info.denom; + clock_monotonic = posix_clock_set = 1; + } + } + if (clock_monotonic) { + nano = mach_absolute_time(); + if ((info.denom != 1 || info.numer != 1) && factor != 0.0) + nano *= factor; + ts->tv_sec = nano / NSEC_PER_SEC; + ts->tv_nsec = nano % NSEC_PER_SEC; + if (ts->tv_nsec < 0) { + ts->tv_sec--; + ts->tv_nsec += NSEC_PER_SEC; + } + return 0; + } +#endif + +#if 0 + /* Something above failed, so fall back to gettimeofday */ + if (!posix_clock_set) { + logger(NULL, LOG_WARNING, NO_MONOTONIC); + posix_clock_set = 1; + } +#endif + { + struct timeval tv; + if (gettimeofday(&tv, NULL) == 0) { + TIMEVAL_TO_TIMESPEC(&tv, ts); + return 0; + } + } + + return -1; +} + +#if USE_LOGFILE +void +logger_open(struct dhcpcd_ctx *ctx) +{ + + if (ctx->logfile) { + int f = O_CREAT | O_APPEND | O_TRUNC; + +#ifdef O_CLOEXEC + f |= O_CLOEXEC; +#endif + ctx->log_fd = open(ctx->logfile, O_WRONLY | f, 0644); + if (ctx->log_fd == -1) + warn("open: %s", ctx->logfile); +#ifndef O_CLOEXEC + else { + if (fcntl(ctx->log_fd, F_GETFD, &f) == -1 || + fcntl(ctx->log_fd, F_SETFD, f | FD_CLOEXEC) == -1) + warn("fcntl: %s", ctx->logfile); + } +#endif + } else + openlog(PACKAGE, LOG_PID, LOG_DAEMON); +} + +void +logger_close(struct dhcpcd_ctx *ctx) +{ + + if (ctx->log_fd != -1) { + close(ctx->log_fd); + ctx->log_fd = -1; + } + closelog(); +} + +void +logger(struct dhcpcd_ctx *ctx, int pri, const char *fmt, ...) +{ + va_list va; + int serrno; +#ifndef HAVE_PRINTF_M + char fmt_cpy[1024]; +#endif + + if (pri >= LOG_DEBUG && ctx && !(ctx->options & DHCPCD_DEBUG)) + return; + + serrno = errno; + va_start(va, fmt); + +#ifndef HAVE_PRINTF_M + /* Print strerrno(errno) in place of %m */ + if (ctx == NULL || !(ctx->options & DHCPCD_QUIET) || ctx->log_fd != -1) + { + const char *p; + char *fp = fmt_cpy, *serr = NULL; + size_t fmt_left = sizeof(fmt_cpy) - 1, fmt_wrote; + + for (p = fmt; *p != '\0'; p++) { + if (p[0] == '%' && p[1] == '%') { + if (fmt_left < 2) + break; + *fp++ = '%'; + *fp++ = '%'; + fmt_left -= 2; + p++; + } else if (p[0] == '%' && p[1] == 'm') { + if (serr == NULL) + serr = strerror(serrno); + fmt_wrote = strlcpy(fp, serr, fmt_left); + if (fmt_wrote > fmt_left) + break; + fp += fmt_wrote; + fmt_left -= fmt_wrote; + p++; + } else { + *fp++ = *p; + --fmt_left; + } + if (fmt_left == 0) + break; + } + *fp++ = '\0'; + fmt = fmt_cpy; + } + +#endif + + if (ctx == NULL || !(ctx->options & DHCPCD_QUIET)) { + va_list vac; + + va_copy(vac, va); + vfprintf(pri <= LOG_ERR ? stderr : stdout, fmt, vac); + fputc('\n', pri <= LOG_ERR ? stderr : stdout); + va_end(vac); + } + +#ifdef HAVE_PRINTF_M + errno = serrno; +#endif + if (ctx && ctx->log_fd != -1) { + struct timeval tv; + char buf[32]; + + /* Write the time, syslog style. month day time - */ + if (gettimeofday(&tv, NULL) != -1) { + time_t now; + struct tm tmnow; + + tzset(); + now = tv.tv_sec; + localtime_r(&now, &tmnow); + strftime(buf, sizeof(buf), "%b %d %T ", &tmnow); + dprintf(ctx->log_fd, "%s", buf); + } + + vdprintf(ctx->log_fd, fmt, va); + dprintf(ctx->log_fd, "\n"); + } else + vsyslog(pri, fmt, va); + va_end(va); +} +#endif + +ssize_t +setvar(struct dhcpcd_ctx *ctx, + 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; + **e = malloc(len); + if (**e == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + if (prefix) + snprintf(**e, len, "%s_%s=%s", prefix, var, value); + else + snprintf(**e, len, "%s=%s", var, value); + (*e)++; + return (ssize_t)len; +} + +ssize_t +setvard(struct dhcpcd_ctx *ctx, + char ***e, const char *prefix, const char *var, size_t value) +{ + char buffer[32]; + + snprintf(buffer, sizeof(buffer), "%zu", value); + return setvar(ctx, e, prefix, var, buffer); +} + + +time_t +uptime(void) +{ + struct timespec tv; + + if (get_monotonic(&tv) == -1) + return -1; + return tv.tv_sec; +} + +char * +hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen, char *buf, size_t buflen) +{ + char *p; + size_t i; + + if (buf == NULL) { + return NULL; + } + + if (hwlen * 3 > buflen) { + errno = ENOBUFS; + return 0; + } + + p = buf; + for (i = 0; i < hwlen; i++) { + if (i > 0) + *p ++= ':'; + p += snprintf(p, 3, "%.2x", hwaddr[i]); + } + *p ++= '\0'; + return buf; +} + +size_t +hwaddr_aton(unsigned char *buffer, const char *addr) +{ + char c[3]; + const char *p = addr; + unsigned char *bp = buffer; + size_t len = 0; + + c[2] = '\0'; + while (*p) { + c[0] = *p++; + 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; + } + /* Ensure that next data is EOL or a seperator with data */ + if (!(*p == '\0' || (*p == ':' && *(p + 1) != '\0'))) { + errno = EINVAL; + return 0; + } + if (*p) + p++; + if (bp) + *bp++ = (unsigned char)strtol(c, NULL, 16); + len++; + } + return len; +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..76215bf --- /dev/null +++ b/common.h @@ -0,0 +1,190 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include <sys/param.h> +#include <sys/time.h> +#include <stdio.h> +#include <syslog.h> + +#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 USEC_PER_SEC 1000000L +#define USEC_PER_NSEC 1000L +#define NSEC_PER_SEC 1000000000L +#define MSEC_PER_SEC 1000L +#define MSEC_PER_NSEC 1000000L + +/* 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 / MSEC_PER_NSEC; \ +} 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)) \ + * MSEC_PER_NSEC; \ +} 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 __dead +# define __dead __attribute__((__noreturn__)) +# endif +# ifndef __packed +# define __packed __attribute__((__packed__)) +# endif +# ifndef __printflike +# define __printflike(a, b) __attribute__((format(printf, a, b))) +# endif +# ifndef __unused +# define __unused __attribute__((__unused__)) +# endif +#else +# ifndef __dead +# define __dead +# endif +# ifndef __packed +# define __packed +# endif +# ifndef __printflike +# define __printflike +# endif +# ifndef __unused +# define __unused +# endif +#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); +const char *get_hostname(char *, size_t, int); +extern int clock_monotonic; +int get_monotonic(struct timespec *); + +/* We could shave a few k off the binary size by just using the + * syslog(3) interface. + * However, this results in a ugly output on the command line + * and relies on syslogd(8) starting before dhcpcd which is not + * always the case. */ +#ifndef USE_LOGFILE +# define USE_LOGFILE 1 +#endif +#if USE_LOGFILE +void logger_open(struct dhcpcd_ctx *); +#define logger_mask(ctx, lvl) setlogmask((lvl)) +__printflike(3, 4) void logger(struct dhcpcd_ctx *, int, const char *, ...); +void logger_close(struct dhcpcd_ctx *); +#else +#define logger_open(ctx) openlog(PACKAGE, LOG_PERROR | LOG_PID, LOG_DAEMON) +#define logger_mask(ctx, lvl) setlogmask((lvl)) +#define logger(ctx, pri, fmt, ...) \ + do { \ + UNUSED((ctx)); \ + syslog((pri), (fmt), ##__VA_ARGS__); \ + } while (0 /*CONSTCOND */) +#define logger_close(ctx) closelog() +#endif + +ssize_t setvar(struct dhcpcd_ctx *, + char ***, const char *, const char *, const char *); +ssize_t setvard(struct dhcpcd_ctx *, + char ***, const char *, const char *, size_t); +time_t uptime(void); + +char *hwaddr_ntoa(const unsigned char *, size_t, char *, size_t); +size_t hwaddr_aton(unsigned char *, const char *); +#endif diff --git a/compat/arc4random.c b/compat/arc4random.c new file mode 100644 index 0000000..1321aed --- /dev/null +++ b/compat/arc4random.c @@ -0,0 +1,151 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres <dm@lcs.mit.edu>. + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project by leaving this copyright notice intact. + */ + +/* + * This code is derived from section 17.1 of Applied Cryptography, + * second edition, which describes a stream cipher allegedly + * compatible with RSA Labs "RC4" cipher (the actual description of + * which is a trade secret). The same algorithm is used as a stream + * cipher called "arcfour" in Tatu Ylonen's ssh package. + * + * Here the stream cipher has been modified always to include the time + * when initializing the state. That makes it impossible to + * regenerate the same random sequence twice, so this can't be used + * for encryption, but will generate good random numbers. + * + * RC4 is a registered trademark of RSA Laboratories. + */ + +#include <sys/time.h> + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include "arc4random.h" + +struct arc4_stream { + uint8_t i; + uint8_t j; + uint8_t s[256]; + size_t count; + pid_t stir_pid; +}; + +#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; +} + +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) { + (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... */ + arc4_addrandom(as, (void *) &rdat, sizeof(rdat)); + + /* + * Throw away the first N words of output, as suggested in the + * paper "Weaknesses in the Key Scheduling Algorithm of RC4" + * by Fluher, Mantin, and Shamir. (N = 256 in our case.) + */ + for (n = 0; n < 256 * sizeof(uint32_t); n++) + arc4_getbyte(as); + as->count = 1600000; +} + +static void +arc4_stir_if_needed(struct arc4_stream *as) +{ + pid_t pid; + + pid = getpid(); + if (as->count <= sizeof(uint32_t) || !as->stir_pid != pid) { + as->stir_pid = pid; + arc4_stir(as); + } else + as->count -= sizeof(uint32_t); +} + +uint32_t +arc4random() +{ + + arc4_stir_if_needed(&rs); + return arc4_getword(&rs); +} diff --git a/compat/arc4random.h b/compat/arc4random.h new file mode 100644 index 0000000..d8ae3c7 --- /dev/null +++ b/compat/arc4random.h @@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARC4RANDOM_H +#define ARC4RANDOM_H + +#include <stdint.h> + +uint32_t arc4random(void); +#endif diff --git a/compat/arc4random_uniform.c b/compat/arc4random_uniform.c new file mode 100644 index 0000000..b597551 --- /dev/null +++ b/compat/arc4random_uniform.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdint.h> +#include <stdlib.h> + +/* We need to include config.h so we pickup either the system arc4random + * or our compat one. */ +#include "../config.h" + +/* + * Calculate a uniformly distributed random number less than upper_bound + * avoiding "modulo bias". + * + * Uniformity is achieved by generating new random numbers until the one + * returned is outside the range [0, 2**32 % upper_bound). This + * guarantees the selected random number will be inside + * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound) + * after reduction modulo upper_bound. + */ +uint32_t +arc4random_uniform(uint32_t upper_bound) +{ + uint32_t r, min; + + if (upper_bound < 2) + return 0; + + /* 2**32 % x == (2**32 - x) % x */ + min = -upper_bound % upper_bound; + + /* + * This could theoretically loop forever but each retry has + * p > 0.5 (worst case, usually far better) of selecting a + * number inside the range we need, so it should rarely need + * to re-roll. + */ + do + r = arc4random(); + while (r < min); + + return r % upper_bound; +} diff --git a/compat/arc4random_uniform.h b/compat/arc4random_uniform.h new file mode 100644 index 0000000..e6a9b74 --- /dev/null +++ b/compat/arc4random_uniform.h @@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARC4RANDOM_UNIFORM_H +#define ARC4RANDOM_UNIFORM_H + +#include <stdint.h> + +uint32_t arc4random_uniform(uint32_t); +#endif diff --git a/compat/closefrom.c b/compat/closefrom.c new file mode 100644 index 0000000..ab57c77 --- /dev/null +++ b/compat/closefrom.c @@ -0,0 +1,49 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <unistd.h> + +#include "closefrom.h" + +int +closefrom(int fd) +{ + long max; + int i, r; + +#ifdef _SC_OPEN_MAX + max = sysconf(_SC_OPEN_MAX); +#else + max = getdtablesize(); +#endif + r = 0; + for (i = fd; i < max; i++) { + if (close(i) == -1) + r = -1; + } + return r; +} diff --git a/compat/closefrom.h b/compat/closefrom.h new file mode 100644 index 0000000..ed21068 --- /dev/null +++ b/compat/closefrom.h @@ -0,0 +1,31 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CLOSEFROM_H +#define CLOSEFROM_H +int closefrom(int); +#endif diff --git a/compat/dprintf.c b/compat/dprintf.c new file mode 100644 index 0000000..33748d0 --- /dev/null +++ b/compat/dprintf.c @@ -0,0 +1,65 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> + +#include "dprintf.h" + +int +vdprintf(int fd, const char * __restrict fmt, va_list va) +{ + int e; + FILE *fp; + + if ((e = dup(fd)) == -1) + return -1; + + if ((fp = fdopen(e, "r+")) == NULL) { + close(e); + return -1; + } + + e = vfprintf(fp, fmt, va); + fclose(fp); + return e; +} + +int +dprintf(int fd, const char * __restrict fmt, ...) +{ + int e; + va_list va; + + va_start(va, fmt); + e = vdprintf(fd, fmt, va); + va_end(va); + return e; +} + diff --git a/compat/dprintf.h b/compat/dprintf.h new file mode 100644 index 0000000..b8cd497 --- /dev/null +++ b/compat/dprintf.h @@ -0,0 +1,43 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DPRINTF_H +#define DPRINTF_H + +#include <stdarg.h> + +#ifndef __printflike +# if __GNUC__ > 2 || defined(__INTEL_COMPILER) +# define __printflike(a, b) __attribute__((format(printf, a, b))) +# else +# define __printflike(a, b) +# endif +#endif + +__printflike(2, 0) int vdprintf(int, const char * __restrict, va_list); +__printflike(2, 3) int dprintf(int, const char * __restrict, ...); +#endif diff --git a/compat/endian.h b/compat/endian.h new file mode 100644 index 0000000..8d01738 --- /dev/null +++ b/compat/endian.h @@ -0,0 +1,71 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ENDIAN_H +#define ENDIAN_H + +#include <stdint.h> + +inline static void +be32enc(uint8_t *buf, uint32_t u) +{ + + buf[0] = (uint8_t)((u >> 24) & 0xff); + buf[1] = (uint8_t)((u >> 16) & 0xff); + buf[2] = (uint8_t)((u >> 8) & 0xff); + buf[3] = (uint8_t)(u & 0xff); +} + +inline static void +be64enc(uint8_t *buf, uint64_t u) +{ + + be32enc(buf, (uint32_t)(u >> 32)); + be32enc(buf + sizeof(uint32_t), (uint32_t)(u & 0xffffffffULL)); +} + +inline static uint16_t +be16dec(const uint8_t *buf) +{ + + return (uint16_t)(buf[0] << 8 | buf[1]); +} + +inline static uint32_t +be32dec(const uint8_t *buf) +{ + + return (uint32_t)((uint32_t)be16dec(buf) << 16 | be16dec(buf + 2)); +} + +inline static uint64_t +be64dec(const uint8_t *buf) +{ + + return (uint64_t)((uint64_t)be32dec(buf) << 32 | be32dec(buf + 4)); +} +#endif diff --git a/compat/getline.c b/compat/getline.c new file mode 100644 index 0000000..8c0cbdf --- /dev/null +++ b/compat/getline.c @@ -0,0 +1,75 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "getline.h" + +/* Redefine a small buffer for our simple text config files */ +#undef BUFSIZ +#define BUFSIZ 128 + +ssize_t +getline(char ** __restrict buf, size_t * __restrict buflen, + FILE * __restrict fp) +{ + size_t bytes, newlen; + char *newbuf, *p; + + if (buf == NULL || buflen == NULL) { + errno = EINVAL; + return -1; + } + if (*buf == NULL) + *buflen = 0; + + bytes = 0; + do { + if (feof(fp)) + break; + if (*buf == NULL || bytes != 0 || *buflen < BUFSIZ) { + newlen = *buflen + BUFSIZ; + newbuf = realloc(*buf, newlen); + if (newbuf == NULL) + return -1; + *buf = newbuf; + *buflen = newlen; + } + p = *buf + bytes; + memset(p, 0, BUFSIZ); + if (fgets(p, BUFSIZ, fp) == NULL) + break; + bytes += strlen(p); + } while (bytes == 0 || *(*buf + (bytes - 1)) != '\n'); + if (bytes == 0) + return -1; + return bytes; +} diff --git a/compat/getline.h b/compat/getline.h new file mode 100644 index 0000000..3db807a --- /dev/null +++ b/compat/getline.h @@ -0,0 +1,36 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef GETLINE_H +#define GETLINE_H + +#include <sys/types.h> +#include <stdio.h> + +ssize_t getline(char ** __restrict buf, size_t * __restrict buflen, + FILE * __restrict fp); +#endif diff --git a/compat/pollts.c b/compat/pollts.c new file mode 100644 index 0000000..905dad6 --- /dev/null +++ b/compat/pollts.c @@ -0,0 +1,63 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2013 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/time.h> +#include <sys/types.h> + +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <unistd.h> + +#include "pollts.h" + +#warning "This pollts(2) implementation is not entirely race condition safe." +#warning "Only operating system support for pollts(2) can correct this." + +int +pollts(struct pollfd *__restrict fds, nfds_t nfds, + const struct timespec *__restrict ts, const sigset_t *__restrict sigmask) +{ + int r, timeout; + sigset_t oldset; + + if (ts == NULL) + timeout = -1; + else if (ts->tv_sec > INT_MAX / 1000 || + (ts->tv_sec == INT_MAX / 1000 && + (ts->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000)) + timeout = INT_MAX; + else + timeout = ts->tv_sec * 1000 + (ts->tv_nsec + 999999) / 1000000; + if (sigmask && sigprocmask(SIG_SETMASK, sigmask, &oldset) == -1) + return -1; + r = poll(fds, nfds, timeout); + if (sigmask && sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) + return -1; + + return r; +} diff --git a/compat/pollts.h b/compat/pollts.h new file mode 100644 index 0000000..8fc63d0 --- /dev/null +++ b/compat/pollts.h @@ -0,0 +1,39 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2013 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef PPOLL_H +#define PPOLL_H + +#include <poll.h> +#include <signal.h> +#include <time.h> + +int +pollts(struct pollfd *__restrict, nfds_t, const struct timespec *__restrict, + const sigset_t *__restrict); + +#endif diff --git a/compat/posix_spawn.c b/compat/posix_spawn.c new file mode 100644 index 0000000..0cb142e --- /dev/null +++ b/compat/posix_spawn.c @@ -0,0 +1,157 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* This implementation of posix_spawn is only suitable for the needs of dhcpcd + * but it could easily be extended to other applications. */ + +#include <sys/types.h> +#include <sys/wait.h> + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common.h" +#include "posix_spawn.h" + +#ifndef _NSIG +#ifdef _SIG_MAXSIG +#define _NSIG _SIG_MAXSIG + 1 +#else +/* Guess */ +#define _NSIG SIGPWR + 1 +#endif +#endif + +extern char **environ; + +static int +posix_spawnattr_handle(const posix_spawnattr_t *attrp) +{ + struct sigaction sa; + int i; + + if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGMASK) + sigprocmask(SIG_SETMASK, &attrp->posix_attr_sigmask, NULL); + + if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGDEF) { + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + for (i = 1; i < _NSIG; i++) { + if (sigismember(&attrp->posix_attr_sigdefault, i)) { + if (sigaction(i, &sa, NULL) == -1) + return -1; + } + } + } + + return 0; +} + +inline static int +is_vfork_safe(short int flags) +{ + return !(flags & (POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK)); +} + +int +posix_spawn(pid_t *pid, const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], char *const envp[]) +{ + short int flags; + pid_t p; + volatile int error; + + error = 0; + flags = attrp ? attrp->posix_attr_flags : 0; + if (file_actions == NULL && is_vfork_safe(flags)) + p = vfork(); + else +#ifdef THERE_IS_NO_FORK + return ENOSYS; +#else + p = fork(); +#endif + switch (p) { + case -1: + return errno; + case 0: + if (attrp) { + error = posix_spawnattr_handle(attrp); + if (error) + _exit(127); + } + execve(path, argv, envp); + error = errno; + _exit(127); + default: + if (error != 0) + waitpid(p, NULL, WNOHANG); + else if (pid != NULL) + *pid = p; + return error; + } +} + +int +posix_spawnattr_init(posix_spawnattr_t *attr) +{ + + memset(attr, 0, sizeof(*attr)); + attr->posix_attr_flags = 0; + sigprocmask(0, NULL, &attr->posix_attr_sigmask); + sigemptyset(&attr->posix_attr_sigdefault); + return 0; +} + +int +posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) +{ + + attr->posix_attr_flags = flags; + return 0; +} + +int +posix_spawnattr_setsigmask(posix_spawnattr_t *attr, const sigset_t *sigmask) +{ + + attr->posix_attr_sigmask = *sigmask; + return 0; +} + +int +posix_spawnattr_setsigdefault(posix_spawnattr_t *attr, const sigset_t *sigmask) +{ + + attr->posix_attr_sigdefault = *sigmask; + return 0; +} diff --git a/compat/posix_spawn.h b/compat/posix_spawn.h new file mode 100644 index 0000000..ccfb0f0 --- /dev/null +++ b/compat/posix_spawn.h @@ -0,0 +1,53 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef POSIX_SPAWN_H +#define POSIX_SPAWN_H + +#include <signal.h> + +typedef struct { + short posix_attr_flags; +#define POSIX_SPAWN_SETSIGDEF 0x10 +#define POSIX_SPAWN_SETSIGMASK 0x20 + sigset_t posix_attr_sigmask; + sigset_t posix_attr_sigdefault; +} posix_spawnattr_t; + +typedef struct { +// int unused; +} posix_spawn_file_actions_t; + +int posix_spawn(pid_t *, const char *, + const posix_spawn_file_actions_t *, const posix_spawnattr_t *, + char *const [], char *const []); +int posix_spawnattr_init(posix_spawnattr_t *); +int posix_spawnattr_setflags(posix_spawnattr_t *, short); +int posix_spawnattr_setsigmask(posix_spawnattr_t *, const sigset_t *); +int posix_spawnattr_setsigdefault(posix_spawnattr_t *, const sigset_t *); + +#endif diff --git a/compat/pselect.c b/compat/pselect.c new file mode 100644 index 0000000..78911fa --- /dev/null +++ b/compat/pselect.c @@ -0,0 +1,65 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2013 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/time.h> +#include <sys/types.h> + +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <unistd.h> + +#include "pollts.h" + +int +pollts(struct pollfd *__restrict fds, nfds_t nfds, + const struct timespec *__restrict ts, const sigset_t *__restrict sigmask) +{ + fd_set read_fds; + nfds_t n; + int maxfd, r; + + FD_ZERO(&read_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; + } + } + + r = pselect(maxfd + 1, &read_fds, NULL, NULL, ts, sigmask); + if (r > 0) { + for (n = 0; n < nfds; n++) { + fds[n].revents = + FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0; + } + } + + return r; +} diff --git a/compat/queue.h b/compat/queue.h new file mode 100644 index 0000000..99ac6b9 --- /dev/null +++ b/compat/queue.h @@ -0,0 +1,175 @@ +/* $NetBSD: queue.h,v 1.65 2013/12/25 17:19:34 christos Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef COMPAT_QUEUE_H +#define COMPAT_QUEUE_H + +/* + * Tail queue definitions. + */ +#ifndef TAILQ_END +#define TAILQ_END(head) (NULL) +#endif + +#ifndef TAILQ_HEAD +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { TAILQ_END(head), &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) +#endif /* !TAILQ_HEAD */ + +/* + * Tail queue access methods. + */ +#ifndef TAILQ_FIRST +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) +#endif /* !TAILQ_FIRST */ + +#ifndef TAILQ_FOREACH +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var) != TAILQ_END(head); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\ + (var) != TAILQ_END(head); \ + (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) +#endif /* !TAILQ_FOREACH */ + +#ifndef TAILQ_INIT +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = TAILQ_END(head); \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = TAILQ_END(head); \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) +#endif /* !TAILQ_INIT */ + +#ifndef TAILQ_REPLACE +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ +} while (/*CONSTCOND*/0) +#endif /* !TAILQ_REPLACE */ + +#ifndef TAILQ_FOREACH_SAFE +#define TAILQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((next) = TAILQ_NEXT(var, field), 1); (var) = (next)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) != TAILQ_END(head) && \ + ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev)) +#endif /* !TAILQ_FOREACH_SAFE */ + +#ifndef TAILQ_CONCAT +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) +#endif /* !TAILQ_CONCAT */ + +#endif /* !COMAPT_QUEUE_H */ diff --git a/compat/strlcpy.c b/compat/strlcpy.c new file mode 100644 index 0000000..47776a0 --- /dev/null +++ b/compat/strlcpy.c @@ -0,0 +1,52 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> + +#include "strlcpy.h" + +size_t +strlcpy(char *dst, const char *src, size_t size) +{ + const char *s = src; + size_t n = size; + + if (n && --n) + do { + if (!(*dst++ = *src++)) + break; + } while (--n); + + if (!n) { + if (size) + *dst = '\0'; + while (*src++); + } + + + return (size_t)(src - s - 1); +} diff --git a/compat/strlcpy.h b/compat/strlcpy.h new file mode 100644 index 0000000..951390a --- /dev/null +++ b/compat/strlcpy.h @@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef STRLCPY_H +#define STRLCPY_H + +#include <sys/types.h> + +size_t strlcpy(char *, const char *, size_t); +#endif diff --git a/compat/strtoi.c b/compat/strtoi.c new file mode 100644 index 0000000..701d57b --- /dev/null +++ b/compat/strtoi.c @@ -0,0 +1,112 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> + +#include "strtoi.h" + +intmax_t +strtoi(const char * __restrict nptr, char ** __restrict endptr, int base, + intmax_t lo, intmax_t hi, int *rstatus) +{ + int serrno; + intmax_t r; + char *ep; + int rep; + + if (endptr == NULL) + endptr = &ep; + if (rstatus == NULL) + rstatus = &rep; + + serrno = errno; + errno = 0; + r = strtoimax(nptr, endptr, base); + *rstatus = errno; + errno = serrno; + + if (*rstatus == 0) { + if (nptr == *endptr) + *rstatus = ECANCELED; + else if (**endptr != '\0') + *rstatus = ENOTSUP; + } + + if (r < lo) { + if (*rstatus == 0) + *rstatus = ERANGE; + return lo; + } + if (r > hi) { + if (*rstatus == 0) + *rstatus = ERANGE; + return hi; + } + return r; +} + +uintmax_t +strtou(const char * __restrict nptr, char ** __restrict endptr, int base, + uintmax_t lo, uintmax_t hi, int *rstatus) +{ + int serrno; + uintmax_t r; + char *ep; + int rep; + + if (endptr == NULL) + endptr = &ep; + if (rstatus == NULL) + rstatus = &rep; + + serrno = errno; + errno = 0; + r = strtoumax(nptr, endptr, base); + *rstatus = errno; + errno = serrno; + + if (*rstatus == 0) { + if (nptr == *endptr) + *rstatus = ECANCELED; + else if (**endptr != '\0') + *rstatus = ENOTSUP; + } + + if (r < lo) { + if (*rstatus == 0) + *rstatus = ERANGE; + return lo; + } + if (r > hi) { + if (*rstatus == 0) + *rstatus = ERANGE; + return hi; + } + return r; +} diff --git a/compat/strtoi.h b/compat/strtoi.h new file mode 100644 index 0000000..4b0c878 --- /dev/null +++ b/compat/strtoi.h @@ -0,0 +1,37 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef STRTOI_H +#define STRTOI_H + +#include <inttypes.h> + +intmax_t strtoi(const char * __restrict nptr, char ** __restrict endptr, + int base, intmax_t lo, intmax_t hi, int *rstatus); +uintmax_t strtou(const char * __restrict nptr, char ** __restrict endptr, + int base, uintmax_t lo, uintmax_t hi, int *rstatus); +#endif diff --git a/config-null.mk b/config-null.mk new file mode 100644 index 0000000..c7a8de3 --- /dev/null +++ b/config-null.mk @@ -0,0 +1,3 @@ +# This space left intentionally blank + +DHCPCD_SRCS+= dhcpcd-embedded.c diff --git a/configure b/configure new file mode 100755 index 0000000..b13c232 --- /dev/null +++ b/configure @@ -0,0 +1,1243 @@ +#!/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= +INET6= +ARC4RANDOM= +CLOSEFROM= +GETLINE= +STRLCPY= +UDEV= +OS= +BUILD= +HOST= +HOSTCC= +TARGET= +DEBUG= +FORK= +STATIC= +INCLUDEDIR= +DEVS= +EMBEDDED= +POLL= + +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) INET=no;; + --enable-ipv4) INET=yes;; + --disable-ipv6) INET6=no;; + --enable-ipv6) INET6=yes;; + --disable-embedded) EMBEDDED=no;; + --enable-embedded) EMBEDDED=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;; + --with-ccopts|CFLAGS) CFLAGS=$var;; + CC) CC=$var;; + CPPFLAGS) CPPFLAGS=$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-closefrom) CLOSEFROM=no;; + --without-getline) GETLINE=no;; + --without-strlcpy) STRLCPY=no;; + --without-posix_spawn) POSIX_SPAWN=no;; + --without-md5) MD5=no;; + --without-sha2) SHA2=no;; + --without-sha256) SHA2=no;; + --without-dev) DEV=no;; + --without-udev) UDEV=no;; + --with-poll) POLL="$var";; + --serviceexists) SERVICEEXISTS=$var;; + --servicecmd) SERVICECMD=$var;; + --servicestatus) SERVICESTATUS=$var;; + --includedir) eval INCLUDEDIR="$INCLUDEDIR${INCLUDEDIR:+ }$var";; + --datadir|--infodir) ;; # ignore autotools + --disable-maintainer-mode|--disable-dependency-tracking) ;; + --disable-silent-rules) ;; + -V|--version) + v=$(sed -ne 's/.*VERSION[[:space:]]*"\([^"]*\).*/\1/p' defs.h); + c=$(sed -ne 's/^.*copyright\[\] = "\([^"]*\).*/\1/p' dhcpcd.c); + echo "dhcpcd-$v $c"; + exit 0;; + -h|--help) cat <<EOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: configure [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + -V, --version display version information and exit + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX [/] + +By default, \`make install' will install all the files in \'/sbin', +\`/libexec', etc. You can specify +an installation prefix other than \`/' using \`--prefix', +for instance \`--prefix=$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [PREFIX/bin] + --sbindir=DIR system admin executables [PREFIX/sbin] + --libexecdir=DIR program executables [PREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [PREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --mandir=DIR man documentation [PREFIX/man] + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST build programs to run on HOST [BUILD] + --target=TARGET configure for building compilers for TARGET [HOST] + +Optional Features: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a + nonstandard directory <lib dir> + CPPFLAGS C/C++ preprocessor flags, e.g. -I<include dir> if you have + headers in a nonstandard directory <include dir> + CPP C preprocessor + +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} +: ${WC:=wc} + +: ${FORK:=yes} +: ${SYSCONFDIR:=$PREFIX/etc} +: ${SBINDIR:=$PREFIX/sbin} +: ${LIBDIR:=$PREFIX/lib} +: ${LIBEXECDIR:=$PREFIX/libexec} +: ${STATEDIR:=/var} +: ${DBDIR:=$STATEDIR/db} +: ${RUNDIR:=$STATEDIR/run} +: ${MANDIR:=${PREFIX:-/usr}/share/man} + +eval SYSCONFDIR="$SYSCONFDIR" +eval LIBDIR="$LIBDIR" +eval LIBEXECDIR="$LIBEXECDIR" +eval STATEDIR="$STATEDIR" +eval DBDIR="$DBDIR" +eval RUNDIR="$RUNDIR" +eval MANDIR="$MANDIR" + +_which() +{ + x="$(which "$1" 2>/dev/null)" + if [ -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 + # 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 + +# 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 "$LDFLAGS" ]; then + echo "LDFLAGS=" >>$CONFIG_MK + echo "LDFLAGS+= $LDFLAGS" >>$CONFIG_MK +fi + +# 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*) ;; +*) + if ! [ -x "$LDELF" -o -x /libexec/ld-elf.so.[0-9]* ] && \ + [ -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 +for x in $INCLUDEDIR; do + echo "CPPFLAGS+= -I$x" >>$CONFIG_MK +done + +if [ -z "$DEBUG" -a -f .fslckout ]; then + printf "Found fossil checkout ... " + DEBUG=yes +fi +if [ -n "$DEBUG" -a "$DEBUG" != no -a "$DEBUG" != false ]; then + echo "Adding debugging CFLAGS" + cat <<EOF >>$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+= -Wconversion +CFLAGS+= -Wformat=2 +CFLAGS+= -Wpointer-sign -Wmissing-noreturn +EOF + case "$OS" in + mirbsd*|openbsd*);; # OpenBSD has many redundant decs in system headers + *) echo "CFLAGS+= -Wredundant-decls" >>$CONFIG_MK;; + esac + + case "$OS" in + sunos*);; + *) echo "CFLAGS+= -Wstrict-overflow" >>$CONFIG_MK;; + esac +fi + +if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then + echo "dhcpcd-definitions.conf will be embedded in dhcpcd itself" + echo "DHCPCD_SRCS+= dhcpcd-embedded.c" >>$CONFIG_MK +else + echo "dhcpcd-definitions.conf will be installed to $LIBEXECDIR" + echo "CFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK + echo "EMBEDDEDINSTALL= _embeddedinstall" >>$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 + +case "$OS" in +freebsd*|kfreebsd*) + echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK + echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK + ;; +linux*) + echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK + # Large File Support, should be fine for 32-bit systems. + # But if this is the case, why is it not set by default? + echo "CPPFLAGS+= -D_FILE_OFFSET_BITS=64" >>$CONFIG_MK + echo "CPPFLAGS+= -D_LARGEFILE_SOURCE" >>$CONFIG_MK + echo "CPPFLAGS+= -D_LARGEFILE64_SOURCE" >>$CONFIG_MK + echo "DHCPCD_SRCS+= if-linux.c" >>$CONFIG_MK + # for RTM_NEWADDR and friends + echo "#include <asm/types.h> /* fix broken headers */" >>$CONFIG_H + echo "#include <linux/rtnetlink.h>" >>$CONFIG_H + ;; +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 + + # IPv6 won't work, but it will at least compile. + echo "CPPFLAGS+= -DIN6_IFF_DETACHED=0" >>$CONFIG_MK + echo "CPPFLAGS+= -DIN6_IFF_TENTATIVE=0" >>$CONFIG_MK + echo "CPPFLAGS+= -DIN6_IFF_DUPLICATED=0" >>$CONFIG_MK + ;; +*) + echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK + ;; +esac + +if [ -z "$INET" -o "$INET" = yes ]; then + echo "CPPFLAGS+= -DINET" >>$CONFIG_MK + echo "DHCPCD_SRCS+= arp.c dhcp.c ipv4.c ipv4ll.c" >>$CONFIG_MK +fi +if [ -z "$INET6" -o "$INET6" = yes ]; then + echo "CPPFLAGS+= -DINET6" >>$CONFIG_MK + echo "DHCPCD_SRCS+= ipv6.c ipv6nd.c dhcp6.c" >>$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`" + +# Now test we can use the compiler with our CFLAGS +cat <<EOF >_test.c +int main(void) { + return 0; +} +EOF +_CC=false +if $XCC _test.c -o _test >/dev/null 2>&3; then + [ -x _test ] && _CC=true +fi +rm -f _test.c _test +if ! $_CC; then + echo "$CC does not create executables" + exit 1 +fi +[ "$CC" != cc ] && echo "CC= $CC" >>$CONFIG_MK +$CC --version | $SED -e '1!d' + +if [ "$OS" = linux ]; then + printf "Testing for nl80211 ... " + cat <<EOF >_nl80211.c +#include <linux/nl80211.h> +int main(void) { + return 0; +} +EOF + if $XCC _nl80211.c -o _nl80211 2>&3; then + echo "yes" + echo "#define HAVE_NL80211_H" >>$CONFIG_H + else + echo "no" + fi + rm -f _nl80211.c _nl80211 + + # Even though we have nl80211, we only use it as a fallback + # because it's currently possible to configure a kernel + # where the SSID associated to won't be reported by nl80211 + # but will be via WEXT. + echo "DHCPCD_SRCS+= if-linux-wext.c" >>$CONFIG_MK +fi + +abort=false +# We require the libc to support non standard functions, like getifaddrs +printf "Testing for getifaddrs ... " +cat <<EOF >_getifaddrs.c +#include <sys/types.h> +#include <ifaddrs.h> +int main(void) { + struct ifaddrs *ifap; + return getifaddrs(&ifap); +} +EOF +if $XCC _getifaddrs.c -o _getifaddrs 2>&3; then + echo "yes" +elif $XCC _getifaddrs.c -o _getifaddrs -lsocket 2>&3; then + 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 clock_gettime ... " +cat <<EOF >_clock_gettime.c +#include <time.h> +int main(void) { + struct timespec ts; + return clock_gettime(CLOCK_MONOTONIC, &ts); +} +EOF +if $XCC _clock_gettime.c -o _clock_gettime 2>&3; then + echo "yes" +elif $XCC _clock_gettime.c -lrt -o _clock_gettime 2>&3; then + echo "yes (-lrt)" + echo "LDADD+= -lrt" >>$CONFIG_MK +else + echo "no" + echo "libc support for clock_getttime is required - aborting" >&2 + abort=true +fi +rm -f _clock_gettime.c _clock_gettime +$abort && exit 1 + +printf "Testing for inet_ntoa ... " +cat <<EOF >_inet_ntoa.c +#include <netinet/in.h> +#include <arpa/inet.h> +int main(void) { + struct in_addr in; + inet_ntoa(in); + return 0; +} +EOF +if $XCC _inet_ntoa.c -o _inet_ntoa 2>&3; then + echo "yes" +elif $XCC _inet_ntoa.c -lnsl -o _inet_ntoa 2>&3; then + echo "yes (-lnsl)" + echo "LDADD+= -lnsl" >>$CONFIG_MK +elif $XCC _inet_ntoa.c -lsocket -o _inet_ntoa 2>&3; then + echo "yes (-lsocket)" + echo "LDADD+= -lsocket" >>$CONFIG_MK +else + echo "no" + echo "libc support for inet_ntoa is required - aborting" >&2 + abort=true +fi +rm -f _inet_ntoa.c _inet_ntoa +$abort && exit 1 + +if [ -z "$ARC4RANDOM" ]; then + printf "Testing for arc4random ... " + cat <<EOF >_arc4random.c +#include <stdlib.h> +int main(void) { + arc4random(); + return 0; +} +EOF + if $XCC _arc4random.c -o _arc4random 2>&3; then + ARC4RANDOM=yes + else + ARC4RANDOM=no + fi + echo "$ARC4RANDOM" + rm -f _arc4random.c _arc4random +fi +if [ "$ARC4RANDOM" = no ]; then + echo "COMPAT_SRCS+= compat/arc4random.c" >>$CONFIG_MK + echo "#include \"compat/arc4random.h\"" >>$CONFIG_H +fi + +if [ -z "$ARC4RANDOM_UNIFORM" ]; then + printf "Testing for arc4random_uniform ... " + cat <<EOF >_arc4random_uniform.c +#include <stdlib.h> +int main(void) { + arc4random_uniform(100); + return 0; +} +EOF + if $XCC _arc4random_uniform.c -o _arc4random_uniform 2>&3; then + ARC4RANDOM_UNIFORM=yes + else + ARC4RANDOM_UNIFORM=no + fi + echo "$ARC4RANDOM" + 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 "$CLOSEFROM" ]; then + printf "Testing for closefrom ... " + cat <<EOF >_closefrom.c +#include <unistd.h> +int main(void) { + closefrom(3); + return 0; +} +EOF + if $XCC _closefrom.c -o _closefrom 2>&3; then + CLOSEFROM=yes + else + CLOSEFROM=no + fi + echo "$CLOSEFROM" + rm -f _closefrom.c _closefrom +fi +if [ "$CLOSEFROM" = no ]; then + echo "COMPAT_SRCS+= compat/closefrom.c" >>$CONFIG_MK + echo "#include \"compat/closefrom.h\"" >>$CONFIG_H +fi + +if [ -z "$GETLINE" ]; then + printf "Testing for getline ... " + cat <<EOF >_getline.c +#include <stdio.h> +int main(void) { + char *buf = NULL; + size_t n = 0; + getline(&buf, &n, stdin); + return 0; +} +EOF + if $XCC _getline.c -o _getline 2>&3; then + GETLINE=yes + else + GETLINE=no + fi + echo "$GETLINE" + rm -f _getline.c _getline +fi +if [ "$GETLINE" = no ]; then + echo "COMPAT_SRCS+= compat/getline.c" >>$CONFIG_MK + echo "#include \"compat/getline.h\"" >>$CONFIG_H +fi + +if [ -z "$STRLCPY" ]; then + printf "Testing for strlcpy ... " + cat <<EOF >_strlcpy.c +#include <string.h> +int main(void) { + const char s1[] = "foo"; + char s2[10]; + strlcpy(s2, s1, sizeof(s2)); + return 0; +} +EOF + if $XCC _strlcpy.c -o _strlcpy 2>&3; then + STRLCPY=yes + else + STRLCPY=no + fi + echo "$STRLCPY" + rm -f _strlcpy.c _strlcpy +fi +if [ "$STRLCPY" = no ]; then + echo "COMPAT_SRCS+= compat/strlcpy.c" >>$CONFIG_MK + echo "#include \"compat/strlcpy.h\"" >>$CONFIG_H +fi + +if [ -z "$STRTOI" ]; then + printf "Testing for strtoi ... " + cat <<EOF >_strtoi.c +#include <stdlib.h> +#include <limits.h> +#include <inttypes.h> +int main(void) { + int e; + return strtoi("1234", NULL, 0, 0, INT32_MAX, &e); +} +EOF + if $XCC _strtoi.c -o _strtoi 2>&3; then + STRTOI=yes + else + STRTOI=no + fi + echo "$STRTOI" + rm -f _strtoi.c _strtoi +fi +if [ "$STRTOI" = no ]; then + echo "COMPAT_SRCS+= compat/strtoi.c" >>$CONFIG_MK + echo "#include \"compat/strtoi.h\"" >>$CONFIG_H +fi + +if [ -z "$DPRINTF" ]; then + printf "Testing for dprintf ... " + cat <<EOF >_dprintf.c +#include <stdio.h> +int main(void) { + return dprintf(0, "%d", 0); +} +EOF + if $XCC _dprintf.c -o _dprintf 2>&3; then + DPRINTF=yes + else + DPRINTF=no + fi + echo "$DPRINTF" + rm -f _dprintf.c _dprintf +fi +if [ "$DPRINTF" = no ]; then + echo "COMPAT_SRCS+= compat/dprintf.c" >>$CONFIG_MK + echo "#include \"compat/dprintf.h\"" >>$CONFIG_H +fi + +if [ -z "$TAILQ_FOREACH_SAFE" ]; then + printf "Testing for TAILQ_FOREACH_SAFE ... " + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_FOREACH_SAFE +#error TAILQ_FOREACH_SAFE +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_FOREACH_SAFE=yes + TAILQ_FOREACH=yes + else + TAILQ_FOREACH_SAFE=no + fi + echo "$TAILQ_FOREACH_SAFE" + rm -f _queue.c _queue +fi + +if [ -z "$TAILQ_CONCAT" ]; then + printf "Testing for TAILQ_CONCAT ..." + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_CONCAT +#error TAILQ_CONCAT +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_CONCAT=yes + TAILQ_FOREACH=yes + else + TAILQ_CONCAT=no + fi + echo "$TAILQ_CONCAT" + rm -f _queue.c _queue +fi + +if [ -z "$TAILQ_FOREACH" ]; then + printf "Testing for TAILQ_FOREACH ... " + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_FOREACH +#error TAILQ_FOREACH +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_FOREACH=yes + else + TAILQ_FOREACH=no + fi + echo "$TAILQ_FOREACH" + rm -f _queue.c _queue +fi + +if [ "$TAILQ_FOREACH_SAFE" = no -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 <sys/queue.h>">>$CONFIG_H + fi + echo "#include \"compat/queue.h\"">>$CONFIG_H +else + echo "#include <sys/queue.h>" >>$CONFIG_H +fi + +if [ -z "$POSIX_SPAWN" ]; then + printf "Testing for posix_spawn ... " + cat <<EOF >_posix_spawn.c +#include <spawn.h> +#include <stdlib.h> + +#ifdef __OpenBSD__ +# include <sys/param.h> +# if OpenBSD<201505 +# error posix_spawn was fixed in OpenBSD-5.7 +# endif +#endif + +int main(void) { + posix_spawn(NULL, NULL, NULL, NULL, NULL, NULL); + return 0; +} +EOF + if $XCC _posix_spawn.c -o _posix_spawn 2>&3; then + POSIX_SPAWN=yes + else + POSIX_SPAWN=no + fi + echo "$POSIX_SPAWN" + rm -f _posix_spawn.c _posix_spawn +fi +if [ "$POSIX_SPAWN" = no ]; then + echo "COMPAT_SRCS+= compat/posix_spawn.c" >>$CONFIG_MK +else + echo "#define HAVE_SPAWN_H" >>$CONFIG_H +fi + +if [ -z "$POLL" ]; then + printf "Testing for kqueue1 ... " + cat <<EOF >_kqueue.c +#include <sys/types.h> +#include <sys/event.h> +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 <<EOF >_kqueue.c +#include <sys/types.h> +#include <sys/event.h> +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 <<EOF >_epoll.c +#ifdef __linux__ +#include <linux/version.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) +#error kernel has buggy epoll_wait timeout +#endif +#endif + +#include <sys/epoll.h> +#include <unistd.h> +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 ppoll ... " + cat <<EOF >_ppoll.c +#include <poll.h> +#include <stdlib.h> +int main(void) { + ppoll(NULL, 0, NULL, NULL); + return 0; +} +EOF + if $XCC _ppoll.c -o _ppoll 2>&3; then + POLL=ppoll + echo "yes" + else + echo "no" + fi + rm -f _ppoll.c _ppoll +fi +if [ -z "$POLL" ]; then + printf "Testing for pselect ... " + cat <<EOF >_pselect.c +#include <sys/select.h> +#include <stdlib.h> +int main(void) { + pselect(0, NULL, NULL, NULL, NULL, NULL); + return 0; +} +EOF + if $XCC _pselect.c -o _pselect 2>&3; then + POLL=pselect + echo "yes" + else + echo "no" + fi + rm -f _pselect.c _pselect +fi +case "$POLL" in +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 + ;; +ppoll) + echo "#define pollts ppoll" >>$CONFIG_H + ;; +pselect) + echo "COMPAT_SRCS+= compat/pselect.c" >>$CONFIG_MK + echo "#include \"compat/pollts.h\"" >>$CONFIG_H + ;; +*) + echo "COMPAT_SRCS+= compat/pollts.c" >>$CONFIG_MK + echo "#include \"compat/pollts.h\"" >>$CONFIG_H + ;; +esac + +if [ -z "$BE64ENC" ]; then + printf "Testing for be64enc ... " + cat <<EOF >_be64enc.c +#include <sys/endian.h> +#include <stdlib.h> +int main(void) { + be64enc(NULL, 0); +} +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 "$MD5" ]; then + MD5_LIB= + printf "Testing for MD5Init ... " + cat <<EOF >_md5.c +#include <sys/types.h> +#include <md5.h> +#include <stdlib.h> +int main(void) { + MD5_CTX context; + MD5Init(&context); + return 0; +} +EOF + # We only want to link to libmd if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libmd.so.* 2>/dev/null) + fi + if $XCC _md5.c -o _md5 2>&3; then + MD5=yes + elif [ -e "$1" ] && $XCC _md5.c -lmd -o _md5 2>&3; then + MD5="yes (-lmd)" + MD5_LIB=-lmd + else + MD5=no + fi + echo "$MD5" + rm -f _md5.c _md5 +fi +if [ "$MD5" = no ]; then + echo "MD5_SRC= 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 <<EOF >_sha256.c +#include <sys/types.h> +EOF + [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c + cat <<EOF >>_sha256.c +#include <stdlib.h> +int main(void) { + SHA256_CTX context; + SHA256_Init(&context); + return 0; +} +EOF + # We only want to link to libmd if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libmd.so.* 2>/dev/null) + fi + if $XCC _sha256.c -o _sha256 2>&3; then + SHA2=yes + elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3; then + SHA2="yes (-lmd)" + SHA2_LIB=-lmd + else + SHA2=no + fi + echo "$SHA2" + rm -f _sha256.c _sha256 + if [ "$SHA2" = no ]; then + # Did OpenBSD really change this? grrrr + printf "Testing for SHA256Init ... " + cat <<EOF >_sha256.c +#include <sys/types.h> +EOF + [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c + cat <<EOF >>_sha256.c +#include <stdlib.h> +int main(void) { + SHA2_CTX context; + SHA256Init(&context); + return 0; +} +EOF + # We only want to link to libmd if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libmd.so.* 2>/dev/null) + fi + if $XCC _sha256.c -o _sha256 2>&3; then + SHA2=yes + SHA2_RENAMED=yes + elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3 + then + SHA2="yes (-lmd)" + SHA2_LIB=-lmd + SHA2_RENAMED=yes + else + SHA2=no + fi + echo "$SHA2" + rm -f _sha256.c _sha256 + fi +fi +if [ "$SHA2" = no ]; then + echo "SHA256_SRC= 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 [ "$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 <<EOF >_udev.c +#include <libudev.h> +#include <stdlib.h> +int main(void) { + udev_monitor_filter_add_match_subsystem_devtype(NULL, NULL, NULL); + return 0; +} +EOF + if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3 + then + echo "yes" + else + echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOFILTER" >>$CONFIG_MK + echo "no" + fi + rm -f _udev.c _udev + + printf "Checking udev_device_get_is_initialized ... " + cat <<EOF >_udev.c +#include <libudev.h> +#include <stdlib.h> +int main(void) { + udev_device_get_is_initialized(NULL); + return 0; +} +EOF + if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3 + then + echo "yes" + else + echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOINIT" >>$CONFIG_MK + echo "no" + fi + rm -f _udev.c _udev +elif [ "$DEV" != no -a "$UDEV" != no ]; then + echo "no" +fi + +if [ "$DEV" = yes ]; then + echo "DHCPCD_SRCS+= dev.c" >>$CONFIG_MK + echo "CPPFLAGS+= -DPLUGIN_DEV" >>$CONFIG_MK + echo "MKDIRS+= dev" >>$CONFIG_MK + + printf "Testing for dlopen ... " + cat <<EOF >_dlopen.c +#include <dlfcn.h> +#include <stdlib.h> +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 + +HOOKS= +if ! $HOOKSET; then + printf "Checking for ntpd ... " + NTPD=$(_which ntpd) + if [ -n "$NTPD" ]; then + echo "$NTPD (50-ntp.conf)" + HOOKS="$HOOKS${HOOKS:+ }50-ntp.conf" + else + echo "not found" + fi + + printf "Checking for ypbind ... " + YPBIND=$(_which ypbind) + if [ -n "$YPBIND" ]; then + YPHOOK="50-ypbind" + if strings "$YPBIND" | $GREP -q yp\\.conf; then + YPHOOK="50-yp.conf" + YPOS="Linux" + elif strings "$YPBIND" | $GREP -q \\.ypservers; then + YPOS="NetBSD" + echo "YPDOMAIN_DIR= /var/yp" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=.ypservers" >>$CONFIG_MK + elif strings "$YPBIND" | $GREP -q /etc/yp; then + YPOS="OpenBSD" + echo "YPDOMAIN_DIR= /etc/yp" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK + else + YPOS="FreeBSD" + echo "YPDOMAIN_DIR=" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK + fi + echo "$YPBIND ($YPHOOK${YPOS:+ }$YPOS)" + HOOKS="$HOOKS${HOOKS:+ }$YPHOOK" + else + echo "not found" + fi +fi + +if cd dhcpcd-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 + cd .. +fi +echo "HOOKSCRIPTS= $HOOKS" >>$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 " HOOKSCRIPTS = $HOOKS" +echo + +rm -f dhcpcd tests/test diff --git a/control.c b/control.c new file mode 100644 index 0000000..1e53d0e --- /dev/null +++ b/control.c @@ -0,0 +1,443 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/un.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcpcd.h" +#include "control.h" +#include "eloop.h" + +#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, 0); + 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) { + logger(fd->ctx, LOG_ERR, + "%s: dhcpcd_handleargs: %m", __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, NULL, NULL); + } 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; + +#ifdef SOCK_CLOEXEC + if ((fd = socket(AF_UNIX, + SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1) + return -1; +#else + int flags; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + return -1; + if ((flags = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(fd); + return -1; + } + if ((flags = fcntl(fd, F_GETFL, 0)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + { + close(fd); + return -1; + } +#endif + 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, NULL, NULL); + + 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, NULL, NULL); + } + 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, 0); + 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, 0); + 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, 0); + close(l->fd); + control_queue_free(l); + free(l); + } + + return retval; +} + +int +control_open(struct dhcpcd_ctx *ctx, const char *ifname) +{ + struct sockaddr_un sa; + socklen_t len; + + if ((ctx->control_fd = make_sock(&sa, ifname, 0)) == -1) + return -1; + len = (socklen_t)SUN_LEN(&sa); + if (connect(ctx->control_fd, (struct sockaddr *)&sa, len) == -1) { + close(ctx->control_fd); + ctx->control_fd = -1; + return -1; + } + return 0; +} + +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) { + logger(fd->ctx, LOG_ERR, + "%s: writev fd %d: %m", __func__, fd->fd); + 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_delete(fd->ctx->eloop, fd->fd, 1); +} + +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(fd->ctx->eloop, fd->fd, + NULL, NULL, 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; + } +} diff --git a/control.h b/control.h new file mode 100644 index 0000000..1b5a619 --- /dev/null +++ b/control.h @@ -0,0 +1,64 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include "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(struct dhcpcd_ctx *, 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 diff --git a/crypt/crypt.h b/crypt/crypt.h new file mode 100644 index 0000000..c919dc4 --- /dev/null +++ b/crypt/crypt.h @@ -0,0 +1,33 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CRYPT_H +#define CRYPT_H + +void hmac_md5(const uint8_t *, size_t, const uint8_t *, size_t, uint8_t *); + +#endif diff --git a/crypt/hmac_md5.c b/crypt/hmac_md5.c new file mode 100644 index 0000000..453ea09 --- /dev/null +++ b/crypt/hmac_md5.c @@ -0,0 +1,89 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <inttypes.h> +#include <string.h> + +#include "crypt.h" + +#include "../config.h" +#ifdef HAVE_MD5_H +# ifndef DEPGEN +# include <md5.h> +# endif +#else +# include "md5.h" +#endif + +#define HMAC_PAD_LEN 64 +#define IPAD 0x36 +#define OPAD 0x5C + +/* hmac_md5 as per RFC3118 */ +void +hmac_md5(const uint8_t *text, size_t text_len, + const uint8_t *key, size_t key_len, + uint8_t *digest) +{ + uint8_t k_ipad[HMAC_PAD_LEN], k_opad[HMAC_PAD_LEN]; + uint8_t tk[MD5_DIGEST_LENGTH]; + int i; + MD5_CTX context; + + /* Ensure key is no bigger than HMAC_PAD_LEN */ + if (key_len > HMAC_PAD_LEN) { + MD5Init(&context); + MD5Update(&context, key, (unsigned int)key_len); + MD5Final(tk, &context); + key = tk; + key_len = MD5_DIGEST_LENGTH; + } + + /* store key in pads */ + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + memset(k_ipad + key_len, 0, sizeof(k_ipad) - key_len); + memset(k_opad + key_len, 0, sizeof(k_opad) - key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < HMAC_PAD_LEN; i++) { + k_ipad[i] ^= IPAD; + k_opad[i] ^= OPAD; + } + + /* inner MD5 */ + MD5Init(&context); + MD5Update(&context, k_ipad, HMAC_PAD_LEN); + MD5Update(&context, text, (unsigned int)text_len); + MD5Final(digest, &context); + + /* outer MD5 */ + MD5Init(&context); + MD5Update(&context, k_opad, HMAC_PAD_LEN); + MD5Update(&context, digest, MD5_DIGEST_LENGTH); + MD5Final(digest, &context); +} diff --git a/crypt/md5.c b/crypt/md5.c new file mode 100644 index 0000000..dea2356 --- /dev/null +++ b/crypt/md5.c @@ -0,0 +1,242 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include <sys/param.h> +#include <inttypes.h> + +#include <string.h> + +#include "md5.h" + +#define PUT_64BIT_LE(cp, value) do { \ + (cp)[7] = (uint8_t)((value) >> 56); \ + (cp)[6] = (uint8_t)((value) >> 48); \ + (cp)[5] = (uint8_t)((value) >> 40); \ + (cp)[4] = (uint8_t)((value) >> 32); \ + (cp)[3] = (uint8_t)((value) >> 24); \ + (cp)[2] = (uint8_t)((value) >> 16); \ + (cp)[1] = (uint8_t)((value) >> 8); \ + (cp)[0] = (uint8_t)(value); } while (0) + +#define PUT_32BIT_LE(cp, value) do { \ + (cp)[3] = (uint8_t)((value) >> 24); \ + (cp)[2] = (uint8_t)((value) >> 16); \ + (cp)[1] = (uint8_t)((value) >> 8); \ + (cp)[0] = (uint8_t)(value); } while (0) + +static uint8_t PADDING[MD5_BLOCK_LENGTH] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5_CTX *ctx) +{ + ctx->count = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +MD5Transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH]) +{ + uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; + +#if BYTE_ORDER == LITTLE_ENDIAN + memcpy(in, block, sizeof(in)); +#else + for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { + in[a] = (uint32_t)( + (uint32_t)(block[a * 4 + 0]) | + (uint32_t)(block[a * 4 + 1]) << 8 | + (uint32_t)(block[a * 4 + 2]) << 16 | + (uint32_t)(block[a * 4 + 3]) << 24); + } +#endif + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len) +{ + size_t have, need; + + /* Check how many bytes we already have and how many more we need. */ + have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + need = MD5_BLOCK_LENGTH - have; + + /* Update bitcount */ + ctx->count += (uint64_t)len << 3; + + if (len >= need) { + if (have != 0) { + memcpy(ctx->buffer + have, input, need); + MD5Transform(ctx->state, ctx->buffer); + input += need; + len -= need; + have = 0; + } + + /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ + while (len >= MD5_BLOCK_LENGTH) { + MD5Transform(ctx->state, input); + input += MD5_BLOCK_LENGTH; + len -= MD5_BLOCK_LENGTH; + } + } + + /* Handle any remaining bytes of data. */ + if (len != 0) + memcpy(ctx->buffer + have, input, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx) +{ + uint8_t count[8]; + size_t padlen; + int i; + + /* Convert count to 8 bytes in little endian order. */ + PUT_64BIT_LE(count, ctx->count); + + /* Pad out to 56 mod 64. */ + padlen = MD5_BLOCK_LENGTH - + ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + if (padlen < 1 + 8) + padlen += MD5_BLOCK_LENGTH; + MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ + MD5Update(ctx, count, 8); + + if (digest != NULL) { + for (i = 0; i < 4; i++) + PUT_32BIT_LE(digest + i * 4, ctx->state[i]); + } + memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */ +} + + diff --git a/crypt/md5.h b/crypt/md5.h new file mode 100644 index 0000000..402309c --- /dev/null +++ b/crypt/md5.h @@ -0,0 +1,33 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#ifndef MD5_H_ +#define MD5_H_ + +#define MD5_DIGEST_LENGTH 16 +#define MD5_BLOCK_LENGTH 64 + +typedef struct MD5Context { + uint32_t state[4]; /* state (ABCD) */ + uint64_t count; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[MD5_BLOCK_LENGTH]; /* input buffer */ +} MD5_CTX; + +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, const unsigned char *, size_t); +void MD5Final(unsigned char[MD5_DIGEST_LENGTH], MD5_CTX *); +#endif diff --git a/crypt/sha256.c b/crypt/sha256.c new file mode 100644 index 0000000..7e3ab0f --- /dev/null +++ b/crypt/sha256.c @@ -0,0 +1,294 @@ +/*- + * Copyright 2005 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <inttypes.h> + +#include <string.h> + +#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)); +} diff --git a/crypt/sha256.h b/crypt/sha256.h new file mode 100644 index 0000000..34025e3 --- /dev/null +++ b/crypt/sha256.h @@ -0,0 +1,50 @@ +/*- + * Copyright 2005 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef SHA256_H_ +#define SHA256_H_ + +#include <sys/types.h> + +#define SHA256_DIGEST_LENGTH 32 + +typedef struct SHA256Context { + uint32_t state[8]; + uint64_t count; + unsigned char buf[64]; +} SHA256_CTX; + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX *, const void *, size_t); +void SHA256_Final(unsigned char [32], SHA256_CTX *); +char *SHA256_End(SHA256_CTX *, char *); +char *SHA256_File(const char *, char *); +char *SHA256_FileChunk(const char *, char *, off_t, off_t); +char *SHA256_Data(const void *, unsigned int, char *); + +#endif @@ -0,0 +1,76 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PACKAGE "dhcpcd" +#define VERSION "6.8.2" + +#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 SYSCONFDIR "/" PACKAGE ".duid" +#endif +#ifndef SECRET +# define SECRET SYSCONFDIR "/" PACKAGE ".secret" +#endif +#ifndef LEASEFILE +# define LEASEFILE DBDIR "/" PACKAGE "-%s%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 "/" PACKAGE "-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 @@ -0,0 +1,188 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dirent.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define _INDEV +#include "common.h" +#include "dev.h" +#include "eloop.h" +#include "dhcpcd.h" + +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) + logger(ctx, LOG_DEBUG, + "dev: unloaded %s", ctx->dev->name); + eloop_event_delete(ctx->eloop, ctx->dev_fd, 0); + 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) { + logger(ctx, LOG_ERR, "dlopen: %s", dlerror()); + return -1; + } + fptr = (void (*)(struct dev *, const struct dev_dhcpcd *)) + dlsym(h, "dev_init"); + if (fptr == NULL) { + logger(ctx, LOG_ERR, "dlsym: %s", dlerror()); + dlclose(h); + return -1; + } + ctx->dev = calloc(1, sizeof(*ctx->dev)); + 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; + } + logger(ctx, LOG_INFO, "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) { + logger(ctx, LOG_ERR, "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) { + logger(ctx, LOG_DEBUG, "dev: %s: %m", 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) { + logger(ctx, LOG_ERR, "%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, NULL, NULL) == -1) + { + logger(ctx, LOG_ERR, + "%s: eloop_event_add: %m", __func__); + dev_stop1(ctx, 1); + return -1; + } + } + + return ctx->dev_fd; +} @@ -0,0 +1,60 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DEV_H +#define DEV_H + +// dev plugin setup +struct dev { + const char *name; + int (*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 diff --git a/dev/Makefile b/dev/Makefile new file mode 100644 index 0000000..e6b3a2c --- /dev/null +++ b/dev/Makefile @@ -0,0 +1,42 @@ +TOP?= ../ +include ${TOP}/Makefile.inc +include ${TOP}/config.mk + +CFLAGS?= -O2 +CSTD?= c99 +CFLAGS+= -std=${CSTD} + +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} + +install: proginstall + +clean: + rm -f ${CLEANFILES} diff --git a/dev/udev.c b/dev/udev.c new file mode 100644 index 0000000..4f7030e --- /dev/null +++ b/dev/udev.c @@ -0,0 +1,179 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef LIBUDEV_NOINIT +# define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE +# warning This version of udev is too old does not support +# warning per device initialization checks. +# warning As such, dhcpcd will need to depend on the +# warning udev-settle service or similar if starting +# warning in master mode. +#endif + +#include <libudev.h> +#include <string.h> +#include <syslog.h> + +#include "../common.h" +#include "../dev.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) { + syslog(LOG_ERR, "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) { + syslog(LOG_DEBUG, "%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) { + syslog(LOG_ERR, "udev: already started"); + return -1; + } + + syslog(LOG_DEBUG, "udev: starting"); + udev = udev_new(); + if (udev == NULL) { + syslog(LOG_ERR, "udev_new: %m"); + return -1; + } + monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (monitor == NULL) { + syslog(LOG_ERR, "udev_monitor_new_from_netlink: %m"); + goto bad; + } +#ifndef LIBUDEV_NOFILTER + if (udev_monitor_filter_add_match_subsystem_devtype(monitor, + "net", NULL) != 0) + { + syslog(LOG_ERR, + "udev_monitor_filter_add_match_subsystem_devtype: %m"); + goto bad; + } +#endif + if (udev_monitor_enable_receiving(monitor) != 0) { + syslog(LOG_ERR, "udev_monitor_enable_receiving: %m"); + goto bad; + } + fd = udev_monitor_get_fd(monitor); + if (fd == -1) { + syslog(LOG_ERR, "udev_monitor_get_fd: %m"); + 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; +} diff --git a/dhcp-common.c b/dhcp-common.c new file mode 100644 index 0000000..2936b55 --- /dev/null +++ b/dhcp-common.c @@ -0,0 +1,927 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/utsname.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" + +#include "common.h" +#include "dhcp-common.h" +#include "dhcp.h" +#include "if.h" +#include "ipv6.h" + +void +dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols) +{ + + while (cols < 40) { + putchar(' '); + cols++; + } + putchar('\t'); + if (opt->type & EMBED) + printf(" embed"); + if (opt->type & ENCAP) + printf(" encap"); + if (opt->type & INDEX) + printf(" index"); + if (opt->type & ARRAY) + printf(" array"); + if (opt->type & UINT8) + printf(" byte"); + else if (opt->type & UINT16) + printf(" uint16"); + else if (opt->type & SINT16) + printf(" sint16"); + else if (opt->type & UINT32) + printf(" uint32"); + else if (opt->type & SINT32) + printf(" sint32"); + else if (opt->type & ADDRIPV4) + printf(" ipaddress"); + else if (opt->type & ADDRIPV6) + printf(" ip6address"); + else if (opt->type & FLAG) + printf(" flag"); + else if (opt->type & RFC3397) + printf(" domain"); + else if (opt->type & DOMAIN) + printf(" dname"); + else if (opt->type & ASCII) + printf(" ascii"); + else if (opt->type & RAW) + printf(" raw"); + else if (opt->type & BINHEX) + printf(" binhex"); + else if (opt->type & STRING) + printf(" string"); + if (opt->type & RFC3361) + printf(" rfc3361"); + if (opt->type & RFC3442) + printf(" rfc3442"); + if (opt->type & RFC5969) + printf(" rfc5969"); + if (opt->type & REQUEST) + printf(" request"); + if (opt->type & 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) != 0) + 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 (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 & 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 RFC3397 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_rfc3397(char *out, size_t len, const uint8_t *p, size_t pl) +{ + const char *start; + size_t start_len, l, count; + const uint8_t *r, *q = p, *e; + int hops; + uint8_t ltype; + + count = 0; + start = out; + start_len = len; + q = p; + e = p + pl; + while (q < e) { + r = NULL; + 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) + 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; + } + count += l + 1; + if (out) { + if (l + 1 > len) { + errno = ENOBUFS; + return -1; + } + memcpy(out, q, l); + out += l; + *out++ = '.'; + len -= l; + len--; + } + q += l; + } + } + /* 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'; + } + + if (count) + /* Don't count the trailing NUL */ + count--; + return (ssize_t)count; +} + +/* Check for a valid domain name as per RFC1123 with the exception of + * allowing - and _ (but not at start or end) as they seem to be widely used. */ +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 & 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 > 63) { + 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 & BINHEX) { + if (dst) { + if (len == 0 || len == 1) { + errno = ENOSPC; + return -1; + } + *dst++ = hexchrs[(c & 0xF0) >> 4]; + *dst++ = hexchrs[(c & 0x0F)]; + len -= 2; + } + bytes += 2; + continue; + } + if (type & ASCII && (!isascii(c))) { + errno = EINVAL; + break; + } + if (!(type & (ASCII | RAW | ESCSTRING | ESCFILE)) /* plain */ && + (!isascii(c) && !isprint(c))) + { + errno = EINVAL; + break; + } + if ((type & (ESCSTRING | ESCFILE) && + (c == '\\' || !isascii(c) || !isprint(c))) || + (type & ESCFILE && (c == '/' || c == ' '))) + { + errno = EINVAL; + if (c == '\\') { + if (dst) { + if (len == 0 || len == 1) { + errno = ENOSPC; + return -1; + } + *dst++ = '\\'; *dst++ = '\\'; + len -= 2; + } + bytes += 2; + continue; + } + if (dst) { + if (len < 5) { + errno = ENOSPC; + 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 = ENOSPC; + return -1; + } + *dst++ = (char)c; + len--; + } + bytes++; + } + } + + /* NULL */ + if (dst) { + if (len == 0) { + errno = ENOSPC; + return -1; + } + *dst = '\0'; + + /* Now we've printed it, validate the domain */ + if (type & DOMAIN && !valid_domainname(odst, type)) { + *odst = '\0'; + return 1; + } + + } + + return (ssize_t)bytes; +} + +#define ADDRSZ 4 +#define ADDR6SZ 16 +static size_t +dhcp_optlen(const struct dhcp_opt *opt, size_t dl) +{ + size_t sz; + + if (dl == 0) + return 0; + + if (opt->type == 0 || + opt->type & (STRING | BINHEX | RFC3442 | RFC5969)) + { + if (opt->len) { + if ((size_t)opt->len > dl) + return 0; + return (size_t)opt->len; + } + return dl; + } + + if ((opt->type & (ADDRIPV4 | ARRAY)) == (ADDRIPV4 | ARRAY)) { + if (dl < ADDRSZ) + return 0; + return dl - (dl % ADDRSZ); + } + + if ((opt->type & (ADDRIPV6 | ARRAY)) == (ADDRIPV6 | ARRAY)) { + if (dl < ADDR6SZ) + return 0; + return dl - (dl % ADDR6SZ); + } + + if (opt->type & (UINT32 | ADDRIPV4)) + sz = sizeof(uint32_t); + else if (opt->type & UINT16) + sz = sizeof(uint16_t); + else if (opt->type & UINT8) + sz = sizeof(uint8_t); + else if (opt->type & ADDRIPV6) + sz = ADDR6SZ; + else + /* If we don't know the size, assume it's valid */ + return dl; + return (dl < sz ? 0 : sz); +} + +#ifdef INET6 +#define PO_IFNAME +#else +#define PO_IFNAME __unused +#endif + +ssize_t +print_option(char *s, size_t len, int type, const uint8_t *data, size_t dl, + PO_IFNAME 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; + char *tmp; + + if (type & RFC3397) { + sl = decode_rfc3397(NULL, 0, data, dl); + if (sl == 0 || sl == -1) + return sl; + l = (size_t)sl + 1; + tmp = malloc(l); + if (tmp == NULL) + return -1; + decode_rfc3397(tmp, l, data, dl); + sl = print_string(s, len, type, (uint8_t *)tmp, l - 1); + free(tmp); + return sl; + } + +#ifdef INET + if (type & RFC3361) { + if ((tmp = decode_rfc3361(data, dl)) == NULL) + return -1; + l = strlen(tmp); + sl = print_string(s, len, type, (uint8_t *)tmp, l); + free(tmp); + return sl; + } + + if (type & RFC3442) + return decode_rfc3442(s, len, data, dl); + + if (type & RFC5969) + return decode_rfc5969(s, len, data, dl); +#endif + + if (type & STRING) + return print_string(s, len, type, data, dl); + + if (type & FLAG) { + if (s) { + *s++ = '1'; + *s = '\0'; + } + return 1; + } + + if (!s) { + if (type & UINT8) + l = 3; + else if (type & UINT16) { + l = 5; + dl /= 2; + } else if (type & SINT16) { + l = 6; + dl /= 2; + } else if (type & UINT32) { + l = 10; + dl /= 4; + } else if (type & SINT32) { + l = 11; + dl /= 4; + } else if (type & ADDRIPV4) { + l = 16; + dl /= 4; + } +#ifdef INET6 + else if (type & ADDRIPV6) { + e = data + dl; + l = 0; + while (data < e) { + if (l) + l++; /* space */ + sl = ipv6_printaddr(NULL, 0, data, ifname); + if (sl != -1) + l += (size_t)sl; + data += 16; + } + return (ssize_t)l; + } +#endif + 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 (type & UINT8) { + sl = snprintf(s, len, "%u", *data); + data++; + } else if (type & UINT16) { + memcpy(&u16, data, sizeof(u16)); + u16 = ntohs(u16); + sl = snprintf(s, len, "%u", u16); + data += sizeof(u16); + } else if (type & SINT16) { + memcpy(&u16, data, sizeof(u16)); + s16 = (int16_t)ntohs(u16); + sl = snprintf(s, len, "%d", s16); + data += sizeof(u16); + } else if (type & UINT32) { + memcpy(&u32, data, sizeof(u32)); + u32 = ntohl(u32); + sl = snprintf(s, len, "%u", u32); + data += sizeof(u32); + } else if (type & SINT32) { + memcpy(&u32, data, sizeof(u32)); + s32 = (int32_t)ntohl(u32); + sl = snprintf(s, len, "%d", s32); + data += sizeof(u32); + } else if (type & ADDRIPV4) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + sl = snprintf(s, len, "%s", inet_ntoa(addr)); + data += sizeof(addr.s_addr); + } +#ifdef INET6 + else if (type & ADDRIPV6) { + ssize_t r; + + r = ipv6_printaddr(s, len, data, ifname); + if (r != -1) + sl = r; + else + sl = 0; + data += 16; + } +#endif + else + sl = 0; + 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, const char *extra) +{ + char ssid[len]; + + 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, + 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, extra); +} + +static size_t +dhcp_envoption1(struct dhcpcd_ctx *ctx, 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; + + if (opt->len && opt->len < ol) + ol = opt->len; + len = print_option(NULL, 0, opt->type, 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) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return 0; + } + if (vname) + v += snprintf(val, e, "%s_%s=", prefix, opt->var); + else + v += snprintf(val, e, "%s=", prefix); + if (len != 0) + print_option(v, (size_t)len + 1, opt->type, od, ol, ifname); + 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; + 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 (dhcp_envoption1(ctx, env == NULL ? NULL : &env[0], + prefix, opt, 1, od, ol, ifname)) + return 1; + return 0; + } + + /* Create a new prefix based on the option */ + if (env) { + if (opt->type & INDEX) { + if (opt->index > 999) { + errno = ENOBUFS; + logger(ctx, LOG_ERR, "%s: %m", __func__); + return 0; + } + } + e = strlen(prefix) + strlen(opt->var) + 2 + + (opt->type & INDEX ? 3 : 0); + pfx = malloc(e); + if (pfx == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return 0; + } + if (opt->type & 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++) { + e = dhcp_optlen(eopt, ol); + if (e == 0) + /* Report error? */ + return 0; + /* Use the option prefix if the embedded option + * name is different. + * This avoids new_fqdn_fqdn which would be silly. */ + ov = strcmp(opt->var, eopt->var); + if (dhcp_envoption1(ctx, env == NULL ? NULL : &env[n], + pfx, eopt, ov, od, e, ifname)) + n++; + od += e; + ol -= e; + } + + /* 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 & 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 & OPTION) { + if (oopt == NULL) + /* Report error? */ + continue; + } + n += dhcp_envoption(ctx, + env == NULL ? NULL : &env[n], pfx, + ifname, + eopt->type & OPTION ? oopt : eopt, + dgetopt, eod, eol); + break; + } + } + od += eos + eol; + ol -= eos + eol; + } + } + + 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); +} diff --git a/dhcp-common.h b/dhcp-common.h new file mode 100644 index 0000000..a3d62fc --- /dev/null +++ b/dhcp-common.h @@ -0,0 +1,119 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCOMMON_H +#define DHCPCOMMON_H + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <stdint.h> + +#include "common.h" +#include "dhcpcd.h" + +/* Max MTU - defines dhcp option length */ +#define MTU_MAX 1500 +#define MTU_MIN 576 + +#define REQUEST (1 << 0) +#define UINT8 (1 << 1) +#define UINT16 (1 << 2) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define ADDRIPV4 (1 << 6) +#define STRING (1 << 7) +#define ARRAY (1 << 8) +#define RFC3361 (1 << 9) +#define RFC3397 (1 << 10) +#define RFC3442 (1 << 11) +#define RFC5969 (1 << 12) +#define ADDRIPV6 (1 << 13) +#define BINHEX (1 << 14) +#define FLAG (1 << 15) +#define NOREQ (1 << 16) +#define EMBED (1 << 17) +#define ENCAP (1 << 18) +#define INDEX (1 << 19) +#define OPTION (1 << 20) +#define DOMAIN (1 << 21) +#define ASCII (1 << 22) +#define RAW (1 << 23) +#define ESCSTRING (1 << 24) +#define ESCFILE (1 << 25) + +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 */ + + /* 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; +}; + +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_rfc3397(char *, size_t, const uint8_t *, size_t); +ssize_t print_string(char *, size_t, int, const uint8_t *, size_t); +ssize_t print_option(char *, size_t, int, const uint8_t *, size_t, + const char *); +int dhcp_set_leasefile(char *, size_t, int, + const struct interface *, const char *); + +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 *); + +#endif @@ -0,0 +1,3409 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <net/route.h> +#include <netinet/if_ether.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include <netinet/udp.h> +#undef __FAVOR_BSD + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define ELOOP_QUEUE 2 +#include "config.h" +#include "arp.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 "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 + +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, "DHCP_FORCERENEW"}, + { 0, NULL } +}; + +static const char * const dhcp_params[] = { + "ip_address", + "subnet_cidr", + "network_number", + "filename", + "server_name", + NULL +}; + +struct udp_dhcp_packet +{ + struct ip ip; + struct udphdr udp; + struct dhcp_message dhcp; +}; + +static const size_t udp_dhcp_len = sizeof(struct udp_dhcp_packet); + +static int dhcp_open(struct interface *ifp); + +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, dhcp, opt) get_option(ctx, dhcp, opt, NULL) +static const uint8_t * +get_option(struct dhcpcd_ctx *ctx, + const struct dhcp_message *dhcp, unsigned int opt, size_t *len) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + size_t bl = 0; + + /* Check we have the magic cookie */ + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + errno = ENOTSUP; + return NULL; + } + + while (p < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!ctx->opt_buffer) { + ctx->opt_buffer = + malloc(DHCP_OPTION_LEN + + BOOTFILE_LEN + SERVERNAME_LEN); + if (ctx->opt_buffer == NULL) + return NULL; + } + if (!bp) + bp = ctx->opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = *p; + if (p + ol > e) { + errno = EINVAL; + return NULL; + } + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl = (uint8_t)(overl & ~1); + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl = (uint8_t)(overl & ~2); + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* 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 (!overl) + overl = 0x80 | p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)ctx->opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_addr(struct dhcpcd_ctx *ctx, + struct in_addr *a, const struct dhcp_message *dhcp, + uint8_t option) +{ + const uint8_t *p; + size_t len; + + p = get_option(ctx, dhcp, 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 dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p; + size_t len; + uint32_t d; + + p = get_option(ctx, dhcp, 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_uint8(struct dhcpcd_ctx *ctx, + uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p; + size_t len; + + p = get_option(ctx, dhcp, 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 struct rt_head * +decode_rfc3442_rt(struct dhcpcd_ctx *ctx, const uint8_t *data, size_t dl) +{ + const uint8_t *p = data; + const uint8_t *e; + uint8_t cidr; + size_t ocets; + struct rt_head *routes; + struct rt *rt = NULL; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (dl < 5) + return NULL; + + routes = malloc(sizeof(*routes)); + TAILQ_INIT(routes); + e = p + dl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + ipv4_freeroutes(routes); + errno = EINVAL; + return NULL; + } + + ocets = (size_t)(cidr + 7) / NBBY; + if (p + 4 + ocets > e) { + ipv4_freeroutes(routes); + errno = ERANGE; + return NULL; + } + + rt = calloc(1, sizeof(*rt)); + if (rt == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(routes); + return NULL; + } + TAILQ_INSERT_TAIL(routes, rt, next); + + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + memcpy(&rt->dest.s_addr, p, ocets); + p += ocets; + rt->net.s_addr = htonl(~0U << (32 - cidr)); + } + + /* Finally, snag the router */ + memcpy(&rt->gate.s_addr, p, 4); + p += 4; + } + return routes; +} + +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_rfc3397(NULL, 0, data, dl)) > 0) { + l = (size_t)r; + sip = malloc(l); + if (sip == NULL) + return 0; + decode_rfc3397(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; +} + +/* Decode an RFC5969 6rd order option into a space + * separated string. Returns length of string (including + * terminating zero) or zero on error. */ +ssize_t +decode_rfc5969(char *out, size_t len, const uint8_t *p, size_t pl) +{ + uint8_t ipv4masklen, ipv6prefixlen; + uint8_t ipv6prefix[16]; + uint8_t br[4]; + int i; + ssize_t b, bytes = 0; + + if (pl < 22) { + errno = EINVAL; + return 0; + } + + ipv4masklen = *p++; + pl--; + ipv6prefixlen = *p++; + pl--; + + for (i = 0; i < 16; i++) { + ipv6prefix[i] = *p++; + pl--; + } + if (out) { + b= snprintf(out, len, + "%d %d " + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x", + ipv4masklen, ipv6prefixlen, + ipv6prefix[0], ipv6prefix[1], ipv6prefix[2], ipv6prefix[3], + ipv6prefix[4], ipv6prefix[5], ipv6prefix[6], ipv6prefix[7], + ipv6prefix[8], ipv6prefix[9], ipv6prefix[10],ipv6prefix[11], + ipv6prefix[12],ipv6prefix[13],ipv6prefix[14], ipv6prefix[15] + ); + + len -= (size_t)b; + out += b; + bytes += b; + } else { + bytes += 16 * 2 + 8 + 2 + 1 + 2; + } + + while (pl >= 4) { + br[0] = *p++; + br[1] = *p++; + br[2] = *p++; + br[3] = *p++; + pl -= 4; + + if (out) { + b= snprintf(out, len, " %d.%d.%d.%d", + br[0], br[1], br[2], br[3]); + len -= (size_t)b; + out += b; + bytes += b; + } else { + bytes += (4 * 4); + } + } + + return bytes; +} + +static char * +get_option_string(struct dhcpcd_ctx *ctx, + const struct dhcp_message *dhcp, uint8_t option) +{ + size_t len; + const uint8_t *p; + char *s; + + p = get_option(ctx, dhcp, 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. */ +struct rt_head * +get_option_routes(struct interface *ifp, const struct dhcp_message *dhcp) +{ + struct if_options *ifo = ifp->options; + const uint8_t *p; + const uint8_t *e; + struct rt_head *routes = NULL; + struct rt *route = NULL; + size_t len; + const char *csr = ""; + + /* If we have CSR's then we MUST use these only */ + if (!has_option_mask(ifo->nomask, DHO_CSR)) + p = get_option(ifp->ctx, dhcp, 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, dhcp, DHO_MSCSR, &len); + if (p) + csr = "MS "; + } + if (p) { + routes = decode_rfc3442_rt(ifp->ctx, p, len); + if (routes) { + const struct dhcp_state *state; + + state = D_CSTATE(ifp); + if (!(ifo->options & DHCPCD_CSR_WARNED) && + !(state->added & STATE_FAKE)) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: using %sClassless Static Routes", + ifp->name, csr); + ifo->options |= DHCPCD_CSR_WARNED; + } + return routes; + } + } + + /* OK, get our static routes first. */ + routes = malloc(sizeof(*routes)); + if (routes == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + TAILQ_INIT(routes); + if (!has_option_mask(ifo->nomask, DHO_STATICROUTE)) + p = get_option(ifp->ctx, dhcp, 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) { + if ((route = calloc(1, sizeof(*route))) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(routes); + return NULL; + } + memcpy(&route->dest.s_addr, p, 4); + p += 4; + memcpy(&route->gate.s_addr, p, 4); + p += 4; + /* RFC 2131 Section 5.8 states default route is + * illegal */ + if (route->dest.s_addr == htonl(INADDR_ANY)) { + errno = EINVAL; + free(route); + continue; + } + route->net.s_addr = route_netmask(route->dest.s_addr); + TAILQ_INSERT_TAIL(routes, route, next); + } + } + + /* Now grab our routers */ + if (!has_option_mask(ifo->nomask, DHO_ROUTER)) + p = get_option(ifp->ctx, dhcp, DHO_ROUTER, &len); + else + p = NULL; + if (p) { + e = p + len; + while (p < e) { + if ((route = calloc(1, sizeof(*route))) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(routes); + return NULL; + } + memcpy(&route->gate.s_addr, p, 4); + p += 4; + TAILQ_INSERT_TAIL(routes, route, next); + } + } + + return routes; +} + +#define PUTADDR(_type, _val) \ + { \ + *p++ = _type; \ + *p++ = 4; \ + memcpy(p, &_val.s_addr, 4); \ + p += 4; \ + } + +int +dhcp_message_add_addr(struct dhcp_message *dhcp, + uint8_t type, struct in_addr addr) +{ + uint8_t *p; + size_t len; + + p = dhcp->options; + while (*p != DHO_END) { + p++; + p += *p + 1; + } + + len = (size_t)(p - (uint8_t *)dhcp); + if (len + 6 > sizeof(*dhcp)) { + errno = ENOMEM; + return -1; + } + + PUTADDR(type, addr); + *p = DHO_END; + return 0; +} + +ssize_t +make_message(struct dhcp_message **message, + const struct interface *ifp, + uint8_t type) +{ + struct dhcp_message *dhcp; + uint8_t *m, *lp, *p, *auth; + uint8_t *n_params = NULL, auth_len; + 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; + time_t up = uptime() - state->start_uptime; + char hbuf[HOSTNAME_MAX_LEN + 1]; + const char *hostname; + const struct vivco *vivco; + + dhcp = calloc(1, sizeof (*dhcp)); + if (dhcp == NULL) + return -1; + m = (uint8_t *)dhcp; + p = dhcp->options; + + if ((type == DHCP_INFORM || type == DHCP_RELEASE || + (type == DHCP_REQUEST && + state->net.s_addr == lease->net.s_addr && + (state->new == NULL || + state->new->cookie == htonl(MAGIC_COOKIE))))) + { + dhcp->ciaddr = state->addr.s_addr; + /* In-case we haven't actually configured the address yet */ + if (type == DHCP_INFORM && state->addr.s_addr == 0) + dhcp->ciaddr = lease->addr.s_addr; + } + + dhcp->op = DHCP_BOOTREQUEST; + dhcp->hwtype = (uint8_t)ifp->family; + switch (ifp->family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + dhcp->hwlen = (uint8_t)ifp->hwlen; + memcpy(&dhcp->chaddr, &ifp->hwaddr, ifp->hwlen); + break; + } + + if (ifo->options & DHCPCD_BROADCAST && + dhcp->ciaddr == 0 && + type != DHCP_DECLINE && + type != DHCP_RELEASE) + dhcp->flags = htons(BROADCAST_FLAG); + + if (type != DHCP_DECLINE && type != DHCP_RELEASE) { + if (up < 0 || up > (time_t)UINT16_MAX) + dhcp->secs = htons((uint16_t)UINT16_MAX); + else + dhcp->secs = htons((uint16_t)up); + } + dhcp->xid = htonl(state->xid); + dhcp->cookie = htonl(MAGIC_COOKIE); + + if (!(ifo->options & DHCPCD_BOOTP)) { + *p++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; + } + + if (state->clientid) { + *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 && + lease->addr.s_addr != state->addr.s_addr)) + { + PUTADDR(DHO_IPADDRESS, lease->addr); + if (lease->server.s_addr) + PUTADDR(DHO_SERVERID, lease->server); + } + + if (type == DHCP_RELEASE) { + if (lease->server.s_addr) + PUTADDR(DHO_SERVERID, lease->server); + } + } + + if (type == DHCP_DECLINE) { + *p++ = DHO_MESSAGE; + len = strlen(DAD); + *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 */ + *p++ = DHO_RAPIDCOMMIT; + *p++ = 0; + } + + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) + PUTADDR(DHO_IPADDRESS, ifo->req_addr); + + /* RFC 2563 Auto Configure */ + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL) { + *p++ = DHO_AUTOCONFIGURE; + *p++ = 1; + *p++ = 1; + } + + if (type == DHCP_DISCOVER || + type == DHCP_INFORM || + type == DHCP_REQUEST) + { + if (!(ifo->options & DHCPCD_BOOTP)) { + int mtu; + + *p++ = DHO_MAXMESSAGESIZE; + *p++ = 2; + mtu = if_getmtu(ifp->name); + if (mtu < MTU_MIN) { + if (if_setmtu(ifp->name, MTU_MIN) == 0) + sz = MTU_MIN; + } else if (mtu > MTU_MAX) { + /* Even though our MTU could be greater than + * MTU_MAX (1500) dhcpcd does not presently + * handle DHCP packets any bigger. */ + mtu = MTU_MAX; + } + sz = htons((uint16_t)mtu); + memcpy(p, &sz, 2); + p += 2; + } + + if (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]) { + *p++ = DHO_VENDORCLASSID; + memcpy(p, ifo->vendorclassid, + (size_t)ifo->vendorclassid[0] + 1); + p += ifo->vendorclassid[0] + 1; + } + + if (type != DHCP_INFORM) { + if (ifo->leasetime != 0) { + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(ifo->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + if (ifo->hostname[0] == '\0') + hostname = get_hostname(hbuf, sizeof(hbuf), + ifo->options & DHCPCD_HOSTNAME_SHORT ? 1 : 0); + else + hostname = ifo->hostname; + + /* + * 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 */ + *p++ = DHO_FQDN; + lp = p; + *p++ = 3; + /* + * 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); + *lp = (uint8_t)(*lp + i); + p += i; + } + } else if (ifo->options & DHCPCD_HOSTNAME && hostname) { + *p++ = DHO_HOSTNAME; + len = strlen(hostname); + *p++ = (uint8_t)len; + memcpy(p, hostname, len); + p += len; + } + + /* vendor is already encoded correctly, so just add it */ + if (ifo->vendor[0]) { + *p++ = DHO_VENDOR; + memcpy(p, ifo->vendor, (size_t)ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; + } + + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE) + { + /* We support HMAC-MD5 */ + *p++ = DHO_FORCERENEW_NONCE; + *p++ = 1; + *p++ = AUTH_ALG_HMAC_MD5; + } + + if (ifo->vivco_len) { + *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++) + { + len = (size_t)(p - m) + vivco->len + 1; + if (len > sizeof(*dhcp)) + goto toobig; + if (vivco->len + 2 + *lp > 255) { + logger(ifp->ctx, LOG_ERR, + "%s: VIVCO option too big", + ifp->name); + free(dhcp); + return -1; + } + *p++ = (uint8_t)vivco->len; + memcpy(p, vivco->data, vivco->len); + p += vivco->len; + *lp = (uint8_t)(*lp + vivco->len + 1); + } + } + + len = (size_t)((p - m) + 3); + if (len > sizeof(*dhcp)) + goto toobig; + *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 & REQUEST || + has_option_mask(ifo->requestmask, opt->option))) + continue; + if (opt->type & NOREQ) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + len = (size_t)((p - m) + 2); + if (len > sizeof(*dhcp)) + goto toobig; + *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 & REQUEST || + has_option_mask(ifo->requestmask, opt->option))) + continue; + if (opt->type & NOREQ) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + len = (size_t)((p - m) + 2); + if (len > sizeof(*dhcp)) + goto toobig; + *p++ = (uint8_t)opt->option; + } + *n_params = (uint8_t)(p - n_params - 1); + } + + /* silence GCC */ + auth_len = 0; + auth = NULL; + + 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) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp_auth_encode: %m", ifp->name); + else if (alen != 0) { + auth_len = (uint8_t)alen; + len = (size_t)((p + alen) - m); + if (len > sizeof(*dhcp)) + goto toobig; + *p++ = DHO_AUTHENTICATION; + *p++ = auth_len; + auth = p; + p += auth_len; + } + } + + *p++ = DHO_END; + + /* Pad out to the BOOTP minimum message length. + * Some DHCP servers incorrectly require this. */ + while (p - m < BOOTP_MESSAGE_LENTH_MIN) + *p++ = DHO_PAD; + + len = (size_t)(p - m); + if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) + dhcp_auth_encode(&ifo->auth, state->auth.token, + m, len, 4, type, auth, auth_len); + + *message = dhcp; + return (ssize_t)len; + +toobig: + logger(ifp->ctx, LOG_ERR, "%s: DHCP messge too big", ifp->name); + free(dhcp); + return -1; +} + +static ssize_t +write_lease(const struct interface *ifp, const struct dhcp_message *dhcp) +{ + int fd; + size_t len; + ssize_t bytes; + const uint8_t *e, *p; + uint8_t l; + uint8_t o = 0; + const struct dhcp_state *state = D_CSTATE(ifp); + + /* We don't write BOOTP leases */ + if (IS_BOOTP(ifp, dhcp)) { + unlink(state->leasefile); + return 0; + } + + logger(ifp->ctx, LOG_DEBUG, "%s: writing lease `%s'", + ifp->name, state->leasefile); + + fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) + return -1; + + /* Only write as much as we need */ + p = dhcp->options; + e = p + sizeof(dhcp->options); + len = sizeof(*dhcp); + while (p < e) { + o = *p; + if (o == DHO_END) { + len = (size_t)(p - (const uint8_t *)dhcp); + break; + } + p++; + if (o != DHO_PAD) { + l = *p++; + p += l; + } + } + bytes = write(fd, dhcp, len); + close(fd); + return bytes; +} + +static struct dhcp_message * +read_lease(struct interface *ifp) +{ + int fd; + struct dhcp_message *dhcp; + struct dhcp_state *state = D_STATE(ifp); + ssize_t bytes; + const uint8_t *auth; + uint8_t type; + size_t auth_len; + + fd = open(state->leasefile, O_RDONLY); + if (fd == -1) { + if (errno != ENOENT) + logger(ifp->ctx, LOG_ERR, "%s: open `%s': %m", + ifp->name, state->leasefile); + return NULL; + } + logger(ifp->ctx, LOG_DEBUG, "%s: reading lease `%s'", + ifp->name, state->leasefile); + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) { + close(fd); + return NULL; + } + bytes = read(fd, dhcp, sizeof(*dhcp)); + close(fd); + if (bytes < 0) { + free(dhcp); + return NULL; + } + + /* We may have found a BOOTP server */ + if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + + /* Authenticate the message */ + auth = get_option(ifp->ctx, dhcp, DHO_AUTHENTICATION, &auth_len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifp->options->auth, + (uint8_t *)dhcp, sizeof(*dhcp), 4, type, + auth, auth_len) == NULL) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: dhcp_auth_validate: %m", ifp->name); + free(dhcp); + return NULL; + } + if (state->auth.token) + logger(ifp->ctx, LOG_DEBUG, + "%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + logger(ifp->ctx, LOG_DEBUG, + "%s: accepted reconfigure key", ifp->name); + } + + return dhcp; +} + +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) { + errno = EINVAL; + return 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 dhcp_message *dhcp, + 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[(BOOTFILE_LEN * 4) + 1]; + uint8_t overl = 0; + uint32_t en; + + e = 0; + ifo = ifp->options; + get_option_uint8(ifp->ctx, &overl, dhcp, DHO_OPTIONSOVERLOADED); + + if (env == NULL) { + if (dhcp->yiaddr || dhcp->ciaddr) + e += 5; + if (*dhcp->bootfile && !(overl & 1)) + e++; + if (*dhcp->servername && !(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, dhcp, 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, dhcp, 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 (dhcp->yiaddr || dhcp->ciaddr) { + /* Set some useful variables that we derive from the DHCP + * message but are not necessarily in the options */ + addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr; + setvar(ifp->ctx, &ep, prefix, "ip_address", inet_ntoa(addr)); + if (get_option_addr(ifp->ctx, &net, + dhcp, DHO_SUBNETMASK) == -1) { + net.s_addr = ipv4_getnetmask(addr.s_addr); + setvar(ifp->ctx, &ep, prefix, + "subnet_mask", inet_ntoa(net)); + } + snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); + setvar(ifp->ctx, &ep, prefix, "subnet_cidr", cidr); + if (get_option_addr(ifp->ctx, &brd, + dhcp, DHO_BROADCAST) == -1) { + brd.s_addr = addr.s_addr | ~net.s_addr; + setvar(ifp->ctx, &ep, prefix, + "broadcast_address", inet_ntoa(brd)); + } + addr.s_addr = dhcp->yiaddr & net.s_addr; + setvar(ifp->ctx, &ep, prefix, + "network_number", inet_ntoa(addr)); + } + + if (*dhcp->bootfile && !(overl & 1)) { + print_string(safe, sizeof(safe), STRING, + dhcp->bootfile, sizeof(dhcp->bootfile)); + setvar(ifp->ctx, &ep, prefix, "filename", safe); + } + if (*dhcp->servername && !(overl & 2)) { + print_string(safe, sizeof(safe), STRING | DOMAIN, + dhcp->servername, sizeof(dhcp->servername)); + setvar(ifp->ctx, &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; + if ((p = get_option(ifp->ctx, dhcp, opt->option, &pl))) { + ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name, + opt, dhcp_getoption, p, pl); + if (opt->option == DHO_VIVSO && + pl > (int)sizeof(uint32_t)) + { + memcpy(&en, p, sizeof(en)); + en = ntohl(en); + vo = vivso_find(en, ifp); + if (vo) { + /* 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; + if ((p = get_option(ifp->ctx, dhcp, opt->option, &pl))) + ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name, + opt, dhcp_getoption, p, pl); + } + + return ep - env; +} + +static void +get_lease(struct dhcpcd_ctx *ctx, + struct dhcp_lease *lease, const struct dhcp_message *dhcp) +{ + + lease->cookie = dhcp->cookie; + /* BOOTP does not set yiaddr for replies when ciaddr is set. */ + if (dhcp->yiaddr) + lease->addr.s_addr = dhcp->yiaddr; + else + lease->addr.s_addr = dhcp->ciaddr; + if (get_option_addr(ctx, &lease->net, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = ipv4_getnetmask(lease->addr.s_addr); + if (get_option_addr(ctx, &lease->brd, dhcp, DHO_BROADCAST) == -1) + lease->brd.s_addr = lease->addr.s_addr | ~lease->net.s_addr; + if (get_option_uint32(ctx, &lease->leasetime, dhcp, DHO_LEASETIME) != 0) + lease->leasetime = ~0U; /* Default to infinite lease */ + if (get_option_uint32(ctx, &lease->renewaltime, + dhcp, DHO_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(ctx, &lease->rebindtime, + dhcp, DHO_REBINDTIME) != 0) + lease->rebindtime = 0; + if (get_option_addr(ctx, &lease->server, dhcp, 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); +} + +uint32_t +dhcp_xid(const struct interface *ifp) +{ + 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 + xid = arc4random(); + + return xid; +} + +void +dhcp_close(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL) + return; + + if (state->raw_fd != -1) { + eloop_event_delete(ifp->ctx->eloop, state->raw_fd, 0); + close(state->raw_fd); + state->raw_fd = -1; + } + + state->interval = 0; +} + +static int +dhcp_openudp(struct interface *ifp) +{ + int s; + struct sockaddr_in sin; + int n; + struct dhcp_state *state; +#ifdef SO_BINDTODEVICE + struct ifreq ifr; + char *p; +#endif + +#ifdef SOCK_CLOEXEC + if ((s = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP)) == -1) + return -1; +#else + if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + if ((n = fcntl(s, F_GETFD, 0)) == -1 || + fcntl(s, F_SETFD, n | FD_CLOEXEC) == -1) + { + close(s); + return -1; + } +#endif + + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto eexit; +#ifdef SO_BINDTODEVICE + if (ifp) { + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + /* We can only bind to the real device */ + p = strchr(ifr.ifr_name, ':'); + if (p) + *p = '\0'; + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, + sizeof(ifr)) == -1) + goto eexit; + } +#endif + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(DHCP_CLIENT_PORT); + if (ifp) { + state = D_STATE(ifp); + sin.sin_addr.s_addr = state->addr.s_addr; + } else + state = NULL; /* appease gcc */ + 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, unsigned int 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 udp_dhcp_packet * +dhcp_makeudppacket(size_t *sz, const uint8_t *data, size_t length, + struct in_addr source, struct in_addr dest) +{ + struct udp_dhcp_packet *udpp; + struct ip *ip; + struct udphdr *udp; + + udpp = calloc(1, sizeof(*udpp)); + if (udpp == 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->dhcp, 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(DHCP_CLIENT_PORT); + udp->uh_dport = htons(DHCP_SERVER_PORT); + udp->uh_ulen = htons((uint16_t)(sizeof(*udp) + length)); + ip->ip_len = udp->uh_ulen; + udp->uh_sum = checksum(udpp, sizeof(*udpp)); + + 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 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 dhcp_message *dhcp; + struct udp_dhcp_packet *udp; + size_t len; + ssize_t r; + struct in_addr from, to; + in_addr_t a = INADDR_ANY; + struct timespec tv; + int s; +#ifdef IN_IFF_NOTUSEABLE + struct ipv4_addr *ia; +#endif + + if (!callback) + logger(ifp->ctx, LOG_DEBUG, "%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); + logger(ifp->ctx, LOG_DEBUG, + "%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)); + } + + if (dhcp_open(ifp) == -1) + return; + + if (state->added && !(state->added & STATE_FAKE) && + state->addr.s_addr != INADDR_ANY && + state->new != NULL && +#ifdef IN_IFF_NOTUSEABLE + ((ia = ipv4_iffindaddr(ifp, &state->addr, NULL)) && + !(ia->addr_flags & IN_IFF_NOTUSEABLE)) && +#endif + (state->new->cookie == htonl(MAGIC_COOKIE) || + ifp->options->options & DHCPCD_INFORM)) + { + s = dhcp_openudp(ifp); + if (s == -1 && errno != EADDRINUSE) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp_openudp: %m", ifp->name); + } else + s = -1; + + /* If we couldn't open a UDP port for our IP address + * then we cannot renew. + * This could happen if our IP was pulled out from underneath us. + * Also, we should not unicast from a BOOTP lease. */ + if (s == -1 || + (!(ifo->options & DHCPCD_INFORM) && + IS_BOOTP(ifp, state->new))) + { + a = state->addr.s_addr; + state->addr.s_addr = INADDR_ANY; + } + r = make_message(&dhcp, ifp, type); + if (r == -1) + goto fail; + len = (size_t)r; + if (a) + state->addr.s_addr = a; + from.s_addr = dhcp->ciaddr; + if (from.s_addr) + to.s_addr = state->lease.server.s_addr; + else + to.s_addr = INADDR_ANY; + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = to.s_addr; + sin.sin_port = htons(DHCP_SERVER_PORT); + r = sendto(s, (uint8_t *)dhcp, len, 0, + (struct sockaddr *)&sin, sizeof(sin)); + if (r == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp_sendpacket: %m", ifp->name); + } else { + size_t ulen; + + r = 0; + udp = dhcp_makeudppacket(&ulen, (uint8_t *)dhcp, len, from, to); + if (udp == NULL) { + logger(ifp->ctx, LOG_ERR, "dhcp_makeudppacket: %m"); + } else { + r = if_sendrawpacket(ifp, 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) { + logger(ifp->ctx, LOG_ERR, + "%s: if_sendrawpacket: %m", ifp->name); + switch(errno) { + case ENETDOWN: + case ENETRESET: + case ENETUNREACH: + break; + default: + if (!(ifp->ctx->options & DHCPCD_TEST)) + dhcp_drop(ifp, "FAIL"); + dhcp_free(ifp); + eloop_timeout_delete(ifp->ctx->eloop, + NULL, ifp); + callback = NULL; + } + } + } + free(dhcp); + +fail: + if (s != -1) + close(s); + + /* 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; + state->xid = dhcp_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); + else if (ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(state->addr.s_addr))) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, ipv4ll_start, ifp); + if (ifo->options & DHCPCD_REQUEST) + logger(ifp->ctx, LOG_INFO, + "%s: soliciting a DHCP lease (requesting %s)", + ifp->name, inet_ntoa(ifo->req_addr)); + else + logger(ifp->ctx, LOG_INFO, + "%s: soliciting a %s lease", + ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : "DHCP"); + send_discover(ifp); +} + +static void +dhcp_request(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + state->state = DHS_REQUEST; + send_request(ifp); +} + +static void +dhcp_expire(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + logger(ifp->ctx, LOG_ERR, "%s: DHCP lease expired", ifp->name); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + dhcp_drop(ifp, "EXPIRE"); + unlink(state->leasefile); + state->interval = 0; + dhcp_discover(ifp); +} + +static void +dhcp_decline(struct interface *ifp) +{ + + send_message(ifp, DHCP_DECLINE, NULL); +} + +static void +dhcp_renew(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease = &state->lease; + + logger(ifp->ctx, LOG_DEBUG, "%s: renewing lease of %s", + ifp->name, inet_ntoa(lease->addr)); + logger(ifp->ctx, LOG_DEBUG, "%s: rebind in %"PRIu32" seconds," + " expire in %"PRIu32" seconds", + ifp->name, lease->rebindtime - lease->renewaltime, + lease->leasetime - lease->renewaltime); + state->state = DHS_RENEW; + state->xid = dhcp_xid(ifp); + send_renew(ifp); +} + +#ifndef IN_IFF_TENTATIVE +static void +dhcp_arp_announced(struct arp_state *astate) +{ + + arp_close(astate->iface); +} +#endif + +static void +dhcp_rebind(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease = &state->lease; + + logger(ifp->ctx, LOG_WARNING, + "%s: failed to renew DHCP, rebinding", ifp->name); + logger(ifp->ctx, LOG_DEBUG, "%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 = 0; + ifp->options->options &= ~(DHCPCD_CSR_WARNED | + DHCPCD_ROUTER_HOST_ROUTE_WARNED); + send_rebind(ifp); +} + +void +dhcp_bind(struct interface *ifp, struct arp_state *astate) +{ + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + struct dhcp_lease *lease = &state->lease; + uint8_t ipv4ll = 0; + + if (state->state == DHS_BOUND) + goto applyaddr; + state->reason = NULL; + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + get_lease(ifp->ctx, lease, state->new); + if (ifo->options & DHCPCD_STATIC) { + logger(ifp->ctx, LOG_INFO, "%s: using static address %s/%d", + ifp->name, inet_ntoa(lease->addr), + inet_ntocidr(lease->net)); + lease->leasetime = ~0U; + state->reason = "STATIC"; + } else if (state->new->cookie != htonl(MAGIC_COOKIE)) { + logger(ifp->ctx, LOG_INFO, "%s: using IPv4LL address %s", + ifp->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "IPV4LL"; + ipv4ll = 1; + } else if (ifo->options & DHCPCD_INFORM) { + if (ifo->req_addr.s_addr != 0) + lease->addr.s_addr = ifo->req_addr.s_addr; + else + lease->addr.s_addr = state->addr.s_addr; + logger(ifp->ctx, LOG_INFO, "%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; + logger(ifp->ctx, LOG_INFO, "%s: leased %s for infinity", + ifp->name, inet_ntoa(lease->addr)); + } else { + if (lease->leasetime < DHCP_MIN_LEASE) { + logger(ifp->ctx, LOG_WARNING, + "%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); + logger(ifp->ctx, LOG_WARNING, + "%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); + logger(ifp->ctx, LOG_WARNING, + "%s: renewal time greater than rebind " + "time, forcing to %"PRIu32" seconds", + ifp->name, lease->renewaltime); + } + logger(ifp->ctx, + lease->addr.s_addr == state->addr.s_addr && + !(state->added & STATE_FAKE) ? + LOG_DEBUG : LOG_INFO, + "%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->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_renew, 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); + logger(ifp->ctx, LOG_DEBUG, + "%s: renew in %"PRIu32" seconds, rebind in %"PRIu32 + " seconds", + ifp->name, lease->renewaltime, lease->rebindtime); + } + if (!(ifo->options & DHCPCD_STATIC) && + state->new->cookie != htonl(MAGIC_COOKIE)) + state->state = DHS_IPV4LL_BOUND; + else + state->state = DHS_BOUND; + if (!state->lease.frominfo && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) + if (write_lease(ifp, state->new) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: write_lease: %m", __func__); + +applyaddr: + ipv4_applyaddr(ifp); + if (ifo->options & DHCPCD_ARP && + !(ifp->ctx->options & DHCPCD_FORKED)) + { +#ifdef IN_IFF_TENTATIVE + if (astate) + arp_free_but(astate); + else if (!ipv4ll) + arp_close(ifp); +#else + if (state->added) { + if (astate == NULL) { + astate = arp_new(ifp, &state->addr); + astate->announced_cb = + dhcp_arp_announced; + } + if (astate) { + arp_announce(astate); + if (!ipv4ll) + arp_free_but(astate); + } + } else if (!ipv4ll) + arp_close(ifp); +#endif + } +} + +static void +dhcp_timeout(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + dhcp_bind(ifp, NULL); + state->interval = 0; + dhcp_discover(ifp); +} + +struct dhcp_message * +dhcp_message_new(const struct in_addr *addr, const struct in_addr *mask) +{ + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) + return NULL; + dhcp->yiaddr = addr->s_addr; + p = dhcp->options; + if (mask && 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 dhcp; +} + +static void +dhcp_static(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp_state *state; + + state = D_STATE(ifp); + ifo = ifp->options; + if (ifo->req_addr.s_addr == INADDR_ANY) { + logger(ifp->ctx, LOG_INFO, + "%s: waiting for 3rd party to " + "configure IP address", + ifp->name); + state->reason = "3RDPARTY"; + script_runreason(ifp, state->reason); + return; + } + state->offer = dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + if (state->offer) { + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + dhcp_bind(ifp, NULL); + } +} + +void +dhcp_inform(struct interface *ifp) +{ + struct dhcp_state *state; + struct if_options *ifo; + struct ipv4_addr *ap; + + state = D_STATE(ifp); + ifo = ifp->options; + if (ifp->ctx->options & DHCPCD_TEST) { + state->addr.s_addr = ifo->req_addr.s_addr; + state->net.s_addr = ifo->req_mask.s_addr; + } else { + if (ifo->req_addr.s_addr == INADDR_ANY) { + state = D_STATE(ifp); + ap = ipv4_iffindaddr(ifp, NULL, NULL); + if (ap == NULL) { + logger(ifp->ctx, LOG_INFO, + "%s: waiting for 3rd party to " + "configure IP address", + ifp->name); + state->reason = "3RDPARTY"; + script_runreason(ifp, state->reason); + return; + } + state->offer = + dhcp_message_new(&ap->addr, &ap->net); + } else + state->offer = + dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + if (state->offer) { + ifo->options |= DHCPCD_STATIC; + dhcp_bind(ifp, NULL); + ifo->options &= ~DHCPCD_STATIC; + } + } + + state->state = DHS_INFORM; + state->xid = dhcp_xid(ifp); + 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) + return; + ifo = ifp->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) && + state->addr.s_addr != ifo->req_addr.s_addr) || + (oldopts & (DHCPCD_INFORM | DHCPCD_STATIC) && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) + { + dhcp_drop(ifp, "EXPIRE"); + } +} + +static void +dhcp_reboot(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL) + return; + ifo = ifp->options; + state->state = DHS_REBOOT; + state->interval = 0; + + if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) { + logger(ifp->ctx, LOG_INFO, + "%s: waiting for carrier", ifp->name); + return; + } + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + if (ifo->options & DHCPCD_INFORM) { + logger(ifp->ctx, LOG_INFO, "%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 (state->offer->cookie == 0) + return; + + logger(ifp->ctx, LOG_INFO, "%s: rebinding lease of %s", + ifp->name, inet_ntoa(state->lease.addr)); + state->xid = dhcp_xid(ifp); + state->lease.server.s_addr = 0; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + + /* Need to add this before dhcp_expire and friends. */ + if (!ifo->fallback && ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(state->addr.s_addr))) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, ipv4ll_start, ifp); + + if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, dhcp_timeout, 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) { + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + return; + } + /* Don't reset DHCP state if we have an IPv4LL address and link is up, + * unless the interface is departing. */ + if (state->state != DHS_IPV4LL_BOUND || + ifp->carrier != LINK_UP || + ifp->options->options & DHCPCD_DEPARTED) + { + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + dhcp_auth_reset(&state->auth); + dhcp_close(ifp); + } + + if (ifp->options->options & DHCPCD_RELEASE) { + unlink(state->leasefile); + if (ifp->carrier != LINK_DOWN && + state->new != NULL && + state->new->cookie == htonl(MAGIC_COOKIE)) + { + logger(ifp->ctx, LOG_INFO, "%s: releasing lease of %s", + ifp->name, inet_ntoa(state->lease.addr)); + state->xid = dhcp_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 + } + } + + free(state->old); + state->old = state->new; + state->new = NULL; + state->reason = reason; + ipv4_applyaddr(ifp); + free(state->old); + state->old = NULL; + state->lease.addr.s_addr = 0; + ifp->options->options &= ~(DHCPCD_CSR_WARNED | + DHCPCD_ROUTER_HOST_ROUTE_WARNED); +} + +static void +log_dhcp1(int lvl, const char *msg, + const struct interface *ifp, const struct dhcp_message *dhcp, + const struct in_addr *from, int ad) +{ + const char *tfrom; + char *a, sname[sizeof(dhcp->servername) * 4]; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) { + a = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE); + if (a) { + char *tmp; + size_t al, tmpl; + + al = strlen(a); + tmpl = (al * 4) + 1; + tmp = malloc(tmpl); + if (tmp == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + free(a); + return; + } + print_string(tmp, tmpl, STRING, (uint8_t *)a, al); + free(a); + a = tmp; + } + } else if (ad && dhcp->yiaddr != 0) { + addr.s_addr = dhcp->yiaddr; + a = strdup(inet_ntoa(addr)); + if (a == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return; + } + } else + a = NULL; + + tfrom = "from"; + r = get_option_addr(ifp->ctx, &addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) { + print_string(sname, sizeof(sname), STRING, + dhcp->servername, strlen((const char *)dhcp->servername)); + if (a == NULL) + logger(ifp->ctx, lvl, "%s: %s %s %s `%s'", + ifp->name, msg, tfrom, inet_ntoa(addr), sname); + else + logger(ifp->ctx, lvl, "%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) + logger(ifp->ctx, lvl, "%s: %s %s %s", + ifp->name, msg, tfrom, inet_ntoa(addr)); + else + logger(ifp->ctx, lvl, "%s: %s %s %s %s", + ifp->name, msg, a, tfrom, inet_ntoa(addr)); + } + free(a); +} + +static void +log_dhcp(int lvl, const char *msg, + const struct interface *ifp, const struct dhcp_message *dhcp, + const struct in_addr *from) +{ + + log_dhcp1(lvl, msg, ifp, dhcp, from, 1); +} + +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; +} + +static int +whitelisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + if (ifo->whitelist_len == 0) + return -1; + for (i = 0; i < ifo->whitelist_len; i += 2) + if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) + return 1; + return 0; +} + +static void +dhcp_arp_probed(struct arp_state *astate) +{ + struct dhcp_state *state; + struct if_options *ifo; + + /* We didn't find a profile for this + * address or hwaddr, so move to the next + * arping profile */ + state = D_STATE(astate->iface); + ifo = astate->iface->options; + if (state->arping_index < ifo->arping_len) { + if (++state->arping_index < ifo->arping_len) { + astate->addr.s_addr = + ifo->arping[state->arping_index - 1]; + arp_probe(astate); + } + dhcpcd_startinterface(astate->iface); + return; + } + dhcp_close(astate->iface); + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); +#ifdef IN_IFF_TENTATIVE + ipv4_finaliseaddr(astate->iface); + arp_close(astate->iface); +#else + dhcp_bind(astate->iface, astate); +#endif +} + +static void +dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg) +{ + struct dhcp_state *state; + struct if_options *ifo; + + state = D_STATE(astate->iface); + ifo = astate->iface->options; + if (state->arping_index && + state->arping_index <= ifo->arping_len && + amsg && + (amsg->sip.s_addr == ifo->arping[state->arping_index - 1] || + (amsg->sip.s_addr == 0 && + amsg->tip.s_addr == ifo->arping[state->arping_index - 1]))) + { + char buf[HWADDR_LEN * 3]; + + astate->failed.s_addr = ifo->arping[state->arping_index - 1]; + arp_report_conflicted(astate, amsg); + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)); + if (dhcpcd_selectprofile(astate->iface, buf) == -1 && + dhcpcd_selectprofile(astate->iface, + 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; + } + dhcp_close(astate->iface); + arp_close(astate->iface); + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, + astate->iface); + dhcpcd_startinterface(astate->iface); + } + + /* 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); + if (!state->lease.frominfo) + dhcp_decline(astate->iface); +#ifdef IN_IFF_DUPLICATED + ia = ipv4_iffindaddr(astate->iface, &astate->addr, NULL); + if (ia) + ipv4_deladdr(astate->iface, &ia->addr, &ia->net); +#endif + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, + astate->iface); + eloop_timeout_add_sec(astate->iface->ctx->eloop, + DHCP_RAND_MAX, dhcp_discover, astate->iface); + } +} + +static void +dhcp_handledhcp(struct interface *ifp, struct dhcp_message **dhcpp, + const struct in_addr *from) +{ + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + struct dhcp_message *dhcp = *dhcpp; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; + const uint8_t *auth; + struct in_addr addr; + unsigned int i; + size_t auth_len; + char *msg; + struct arp_state *astate; + struct ipv4_addr *ia; + + /* We may have found a BOOTP server */ + if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + else if (ifo->options & DHCPCD_BOOTP) { + logger(ifp->ctx, LOG_DEBUG, + "%s: ignoring DHCP reply (excpecting BOOTP)", + ifp->name); + return; + } + + /* Authenticate the message */ + auth = get_option(ifp->ctx, dhcp, DHO_AUTHENTICATION, &auth_len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifo->auth, + (uint8_t *)*dhcpp, sizeof(**dhcpp), 4, type, + auth, auth_len) == NULL) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: dhcp_auth_validate: %m", ifp->name); + log_dhcp1(LOG_ERR, "authentication failed", + ifp, dhcp, from, 0); + return; + } + if (state->auth.token) + logger(ifp->ctx, LOG_DEBUG, + "%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + logger(ifp->ctx, LOG_DEBUG, + "%s: accepted reconfigure key", ifp->name); + } else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { + log_dhcp1(LOG_ERR, "no authentication", ifp, dhcp, from, 0); + return; + } else if (ifo->auth.options & DHCPCD_AUTH_SEND) + log_dhcp1(LOG_WARNING, "no authentication", + ifp, dhcp, from, 0); + + /* RFC 3203 */ + if (type == DHCP_FORCERENEW) { + if (from->s_addr == INADDR_ANY || + from->s_addr == INADDR_BROADCAST) + { + log_dhcp(LOG_ERR, "discarding Force Renew", + ifp, dhcp, from); + return; + } + if (auth == NULL) { + log_dhcp(LOG_ERR, "unauthenticated Force Renew", + ifp, dhcp, from); + return; + } + if (state->state != DHS_BOUND && state->state != DHS_INFORM) { + log_dhcp(LOG_DEBUG, "not bound, ignoring Force Renew", + ifp, dhcp, from); + return; + } + log_dhcp(LOG_ERR, "Force Renew from", ifp, dhcp, from); + /* The rebind and expire timings are still the same, we just + * enter the renew state early */ + if (state->state == DHS_BOUND) { + eloop_timeout_delete(ifp->ctx->eloop, + dhcp_renew, ifp); + dhcp_renew(ifp); + } else { + eloop_timeout_delete(ifp->ctx->eloop, + send_inform, ifp); + dhcp_inform(ifp); + } + 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. */ + //log_dhcp(LOG_DEBUG, "bound, ignoring", iface, dhcp, from); + return; + } + + /* Ensure it's the right transaction */ + if (state->xid != ntohl(dhcp->xid)) { + logger(ifp->ctx, LOG_DEBUG, + "%s: wrong xid 0x%x (expecting 0x%x) from %s", + ifp->name, ntohl(dhcp->xid), state->xid, + inet_ntoa(*from)); + 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, dhcp, (uint8_t)i) == 0) + { + log_dhcp(LOG_WARNING, "reject DHCP", ifp, dhcp, from); + 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, dhcp, DHO_SERVERID) == -1) + { + log_dhcp(LOG_WARNING, "reject NAK", ifp, dhcp, from); + return; + } + + /* We should restart on a NAK */ + log_dhcp(LOG_WARNING, "NAK:", ifp, dhcp, from); + if ((msg = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE))) { + logger(ifp->ctx, LOG_WARNING, "%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, dhcp, (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; + log_dhcp(LOG_WARNING, "reject DHCP", ifp, dhcp, from); + return; + } + } + + /* DHCP Auto-Configure, RFC 2563 */ + if (type == DHCP_OFFER && dhcp->yiaddr == 0) { + log_dhcp(LOG_WARNING, "no address given", ifp, dhcp, from); + if ((msg = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE))) { + logger(ifp->ctx, LOG_WARNING, + "%s: message: %s", ifp->name, msg); + free(msg); + } + if ((state->state == DHS_DISCOVER || + state->state == DHS_IPV4LL_BOUND) && + get_option_uint8(ifp->ctx, &tmp, dhcp, + DHO_AUTOCONFIGURE) == 0) + { + switch (tmp) { + case 0: + log_dhcp(LOG_WARNING, "IPv4LL disabled from", + ifp, dhcp, from); + dhcp_drop(ifp, "EXPIRE"); + arp_close(ifp); + eloop_timeout_delete(ifp->ctx->eloop, + NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + DHCP_MAX, dhcp_discover, + ifp); + break; + case 1: + log_dhcp(LOG_WARNING, "IPv4LL enabled from", + ifp, dhcp, from); + eloop_timeout_delete(ifp->ctx->eloop, + NULL, ifp); + if (IN_LINKLOCAL(htonl(state->addr.s_addr))) + eloop_timeout_add_sec(ifp->ctx->eloop, + DHCP_MAX, dhcp_discover, ifp); + else + ipv4ll_start(ifp); + break; + default: + logger(ifp->ctx, LOG_ERR, + "%s: unknown auto configuration option %d", + ifp->name, tmp); + break; + } + } + return; + } + + /* Ensure that the address offered is valid */ + if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && + (dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) && + (dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST)) + { + log_dhcp(LOG_WARNING, "reject invalid address", + ifp, dhcp, from); + return; + } + +#ifdef IN_IFF_DUPLICATED + ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); + if (ia && ia->addr_flags & IN_IFF_DUPLICATED) { + log_dhcp(LOG_WARNING, "declined duplicate address", + ifp, dhcp, from); + if (type) + dhcp_decline(ifp); + ipv4_deladdr(ifp, &ia->addr, &ia->net); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + DHCP_RAND_MAX, dhcp_discover, ifp); + return; + } +#endif + + if ((type == 0 || type == DHCP_OFFER) && + (state->state == DHS_DISCOVER || state->state == DHS_IPV4LL_BOUND)) + { + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + lease->cookie = dhcp->cookie; + if (type == 0 || + get_option_addr(ifp->ctx, + &lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; + log_dhcp(LOG_INFO, "offered", ifp, dhcp, from); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + if (ifp->ctx->options & DHCPCD_TEST) { + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + 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) { + log_dhcp(LOG_WARNING, "ignoring offer of", + ifp, dhcp, from); + return; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", + ifp, dhcp, from); + return; + } + + if (!(ifo->options & DHCPCD_INFORM)) + log_dhcp(LOG_DEBUG, "acknowledged", ifp, dhcp, from); + 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, so check we still + * have a pointer. */ + if (*dhcpp) { + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + } + + lease->frominfo = 0; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + astate = NULL; + +#ifndef IN_IFF_TENTATIVE + if (ifo->options & DHCPCD_ARP + && state->addr.s_addr != state->offer->yiaddr) +#endif + { + addr.s_addr = state->offer->yiaddr; +#ifndef IN_IFF_TENTATIVE + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + ia = ipv4_findaddr(ifp->ctx, &addr); + if (ia) { +#endif + astate = arp_new(ifp, &addr); + if (astate) { + astate->probed_cb = dhcp_arp_probed; + astate->conflicted_cb = dhcp_arp_conflicted; +#ifndef IN_IFF_TENTATIVE + arp_probe(astate); +#endif + } +#ifndef IN_IFF_TENTATIVE + return; + } +#endif + } + + dhcp_bind(ifp, astate); +} + +static size_t +get_udp_data(const uint8_t **data, const uint8_t *udp) +{ + struct udp_dhcp_packet p; + + memcpy(&p, udp, sizeof(p)); + *data = udp + offsetof(struct udp_dhcp_packet, dhcp); + return ntohs(p.ip.ip_len) - sizeof(p.ip) - sizeof(p.udp); +} + +static int +valid_udp_packet(const uint8_t *data, size_t data_len, struct in_addr *from, + int noudpcsum) +{ + struct udp_dhcp_packet p; + uint16_t bytes, udpsum; + + if (data_len < sizeof(p.ip)) { + if (from) + from->s_addr = INADDR_ANY; + errno = EINVAL; + return -1; + } + memcpy(&p, data, MIN(data_len, sizeof(p))); + if (from) + from->s_addr = p.ip.ip_src.s_addr; + if (data_len > sizeof(p)) { + errno = EINVAL; + return -1; + } + if (checksum(&p.ip, sizeof(p.ip)) != 0) { + errno = EINVAL; + return -1; + } + + bytes = ntohs(p.ip.ip_len); + if (data_len < bytes) { + errno = EINVAL; + return -1; + } + + if (noudpcsum == 0) { + 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; + } + } + + return 0; +} + +static void +dhcp_handlepacket(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_message *dhcp = NULL; + const uint8_t *pp; + size_t bytes; + struct in_addr from; + int i, flags; + const struct dhcp_state *state = D_CSTATE(ifp); + + /* Need this API due to BPF */ + flags = 0; + while (!(flags & RAW_EOF)) { + bytes = (size_t)if_readrawpacket(ifp, ETHERTYPE_IP, + ifp->ctx->packet, udp_dhcp_len, &flags); + if ((ssize_t)bytes == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: dhcp if_readrawpacket: %m", ifp->name); + dhcp_close(ifp); + arp_close(ifp); + break; + } + if (valid_udp_packet(ifp->ctx->packet, bytes, + &from, flags & RAW_PARTIALCSUM) == -1) + { + logger(ifp->ctx, LOG_ERR, + "%s: invalid UDP packet from %s", + ifp->name, inet_ntoa(from)); + continue; + } + i = whitelisted_ip(ifp->options, from.s_addr); + if (i == 0) { + logger(ifp->ctx, LOG_WARNING, + "%s: non whitelisted DHCP packet from %s", + ifp->name, inet_ntoa(from)); + continue; + } else if (i != 1 && + blacklisted_ip(ifp->options, from.s_addr) == 1) + { + logger(ifp->ctx, LOG_WARNING, + "%s: blacklisted DHCP packet from %s", + ifp->name, inet_ntoa(from)); + continue; + } + if (ifp->flags & IFF_POINTOPOINT && + state->dst.s_addr != from.s_addr) + { + logger(ifp->ctx, LOG_WARNING, + "%s: server %s is not destination", + ifp->name, inet_ntoa(from)); + } + bytes = get_udp_data(&pp, ifp->ctx->packet); + if (bytes > sizeof(*dhcp)) { + logger(ifp->ctx, LOG_ERR, + "%s: packet greater than DHCP size from %s", + ifp->name, inet_ntoa(from)); + continue; + } + if (dhcp == NULL) { + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) { + logger(ifp->ctx, LOG_ERR, + "%s: calloc: %m", __func__); + break; + } + } + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + logger(ifp->ctx, LOG_DEBUG, "%s: bogus cookie from %s", + ifp->name, inet_ntoa(from)); + continue; + } + /* Ensure packet is for us */ + if (ifp->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, ifp->hwaddr, ifp->hwlen)) + { + char buf[sizeof(dhcp->chaddr) * 3]; + + logger(ifp->ctx, LOG_DEBUG, + "%s: xid 0x%x is for hwaddr %s", + ifp->name, ntohl(dhcp->xid), + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr), + buf, sizeof(buf))); + continue; + } + dhcp_handledhcp(ifp, &dhcp, &from); + if (state->raw_fd == -1) + break; + } + free(dhcp); +} + +static void +dhcp_handleudp(void *arg) +{ + struct dhcpcd_ctx *ctx; + uint8_t buffer[sizeof(struct dhcp_message)]; + + 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) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + eloop_event_delete(ctx->eloop, ctx->udp_fd, 0); + close(ctx->udp_fd); + ctx->udp_fd = -1; + } +} + +static int +dhcp_open(struct interface *ifp) +{ + struct dhcp_state *state; + + if (ifp->ctx->packet == NULL) { + ifp->ctx->packet = malloc(udp_dhcp_len); + if (ifp->ctx->packet == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + } + + state = D_STATE(ifp); + if (state->raw_fd == -1) { + state->raw_fd = if_openrawsocket(ifp, ETHERTYPE_IP); + if (state->raw_fd == -1) { + if (errno == ENOENT) { + logger(ifp->ctx, LOG_ERR, + "%s not found", if_pfname); + /* May as well disable IPv4 entirely at + * this point as we really need it. */ + ifp->options->options &= ~DHCPCD_IPV4; + } else + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", + __func__, ifp->name); + return -1; + } + eloop_event_add(ifp->ctx->eloop, + state->raw_fd, dhcp_handlepacket, ifp, NULL, NULL); + } + 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->raw_fd = state->arp_fd = -1; + TAILQ_INIT(&state->arp_states); + dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), + AF_INET, ifp, ""); + state->new = read_lease(ifp); + if (state->new == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", + *ifp->name ? ifp->name : state->leasefile, __func__); + return -1; + } + state->reason = "DUMP"; + return script_runreason(ifp, state->reason); + +eexit: + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; +} + +void +dhcp_free(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + struct dhcpcd_ctx *ctx; + + dhcp_close(ifp); + arp_close(ifp); + if (state) { + free(state->old); + free(state->new); + free(state->offer); + free(state->buffer); + free(state->clientid); + free(state); + ifp->if_data[IF_DATA_DHCP] = NULL; + } + + 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) { + if (D_STATE(ifp)) + break; + } + } + if (ifp == NULL) { + if (ctx->udp_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->udp_fd, 0); + close(ctx->udp_fd); + ctx->udp_fd = -1; + } + + free(ctx->packet); + free(ctx->opt_buffer); + ctx->packet = NULL; + ctx->opt_buffer = NULL; + } +} + +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]; + + state = D_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_DHCP] = calloc(1, sizeof(*state)); + state = D_STATE(ifp); + if (state == NULL) + return -1; + /* 0 is a valid fd, so init to -1 */ + state->raw_fd = state->arp_fd = -1; + TAILQ_INIT(&state->arp_states); + + /* Now is a good time to find IPv4 routes */ + if_initrt(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) + logger(ifp->ctx, LOG_DEBUG, "%s: using ClientID %s", ifp->name, + hwaddr_ntoa(state->clientid + 1, state->clientid[0], + buf, sizeof(buf))); + else if (ifp->hwlen) + logger(ifp->ctx, LOG_DEBUG, "%s: using hwaddr %s", ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf))); + return 0; + +eexit: + logger(ifp->ctx, LOG_ERR, "%s: error making ClientID: %m", __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) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp_openudp: %m", __func__); + } else + eloop_event_add(ifp->ctx->eloop, + ifp->ctx->udp_fd, dhcp_handleudp, + ifp->ctx, NULL, NULL); + } + + if (dhcp_init(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: dhcp_init: %m", ifp->name); + return; + } + + state = D_STATE(ifp); + state->start_uptime = uptime(); + free(state->offer); + state->offer = NULL; + + if (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; + } + + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + + if (ifo->options & DHCPCD_DHCP && dhcp_open(ifp) == -1) + return; + + if (ifo->options & DHCPCD_INFORM) { + dhcp_inform(ifp); + return; + } + if (ifp->hwlen == 0 && ifo->clientid[0] == '\0') { + logger(ifp->ctx, LOG_WARNING, + "%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) { + state->offer = read_lease(ifp); + /* 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_BOOTP(ifp, state->offer) && + !(ifo->options & DHCPCD_BOOTP)) || +#ifdef IN_IFF_DUPLICATED + (ia && ia->addr_flags & IN_IFF_DUPLICATED) || +#endif + (!IS_BOOTP(ifp, state->offer) && + ifo->options & DHCPCD_BOOTP)) + { + free(state->offer); + state->offer = NULL; + } + } + } + if (state->offer) { + get_lease(ifp->ctx, &state->lease, state->offer); + state->lease.frominfo = 1; + if (state->new == NULL && + ipv4_iffindaddr(ifp, &state->lease.addr, &state->lease.net)) + { + /* 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(sizeof(*state->new)); + if (state->new) { + memcpy(state->new, state->offer, + sizeof(*state->new)); + state->addr = state->lease.addr; + state->net = state->lease.net; + state->added |= STATE_ADDED | STATE_FAKE; + ipv4_buildroutes(ifp->ctx); + } else + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + } + if (state->offer->cookie == 0) { + if (state->offer->yiaddr == state->addr.s_addr) { + free(state->offer); + state->offer = NULL; + } + } else if (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) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: discarding expired lease", ifp->name); + free(state->offer); + state->offer = NULL; + 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; + } + } + } + + if (!(ifo->options & DHCPCD_DHCP)) { + if (ifo->options & DHCPCD_IPV4LL) { + if (state->offer && state->offer->cookie != 0) { + free(state->offer); + state->offer = NULL; + } + ipv4ll_start(ifp); + } + return; + } + + if (state->offer == NULL || state->offer->cookie == 0) + dhcp_discover(ifp); + else + dhcp_reboot(ifp); +} + +void +dhcp_start(struct interface *ifp) +{ + struct timespec tv; + + if (!(ifp->options->options & DHCPCD_IPV4)) + return; + + /* No point in delaying a static configuration */ + tv.tv_sec = DHCP_MIN_DELAY; + tv.tv_nsec = (suseconds_t)arc4random_uniform( + (DHCP_MAX_DELAY - DHCP_MIN_DELAY) * NSEC_PER_SEC); + timespecnorm(&tv); + logger(ifp->ctx, LOG_DEBUG, + "%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_handleifa(int cmd, struct interface *ifp, + const struct in_addr *addr, + const struct in_addr *net, + const struct in_addr *dst, + __unused int flags) +{ + struct dhcp_state *state; + struct if_options *ifo; + uint8_t i; + + state = D_STATE(ifp); + if (state == NULL) + return; + + if (cmd == RTM_DELADDR) { + if (state->addr.s_addr == addr->s_addr && + state->net.s_addr == net->s_addr) + { + logger(ifp->ctx, LOG_INFO, + "%s: removing IP address %s/%d", + ifp->name, inet_ntoa(state->addr), + inet_ntocidr(state->net)); + dhcp_drop(ifp, "EXPIRE"); + } + return; + } + + if (cmd != RTM_NEWADDR) + return; + + 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 = dhcp_message_new(addr, net); + if (state->new == NULL) + return; + state->dst.s_addr = dst ? dst->s_addr : INADDR_ANY; + if (dst) { + for (i = 1; i < 255; i++) + if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i)) + dhcp_message_add_addr(state->new, i, *dst); + } + state->reason = "STATIC"; + ipv4_buildroutes(ifp->ctx); + script_runreason(ifp, state->reason); + if (ifo->options & DHCPCD_INFORM) { + state->state = DHS_INFORM; + state->xid = dhcp_xid(ifp); + state->lease.server.s_addr = dst ? dst->s_addr : INADDR_ANY; + state->addr = *addr; + state->net = *net; + dhcp_inform(ifp); + } +} @@ -0,0 +1,300 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <limits.h> +#include <stdint.h> + +#include "arp.h" +#include "auth.h" +#include "dhcp-common.h" + +/* UDP port numbers for DHCP */ +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 + +#define MAGIC_COOKIE 0x63825363 +#define BROADCAST_FLAG 0x8000 + +/* DHCP message OP code */ +#define DHCP_BOOTREQUEST 1 +#define DHCP_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_OPTIONSOVERLOADED = 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_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 DHCP options */ +#define DHCP_CHADDR_LEN 16 +#define SERVERNAME_LEN 64 +#define BOOTFILE_LEN 128 +#define DHCP_UDP_LEN (14 + 20 + 8) +#define DHCP_FIXED_LEN (DHCP_UDP_LEN + 226) +#define DHCP_OPTION_LEN (MTU_MAX - DHCP_FIXED_LEN) + +/* Some crappy DHCP servers require the BOOTP minimum length */ +#define BOOTP_MESSAGE_LENTH_MIN 300 + +/* Don't import common.h as that defines __unused which causes problems + * on some Linux systems which define it as part of a structure */ +#if __GNUC__ > 2 || defined(__INTEL_COMPILER) +# ifndef __packed +# define __packed __attribute__((__packed__)) +# endif +#else +# ifndef __packed +# define __packed +# endif +#endif + +struct dhcp_message { + uint8_t op; /* message type */ + uint8_t hwtype; /* hardware address type */ + uint8_t hwlen; /* hardware address length */ + uint8_t hwopcount; /* should be zero in client message */ + uint32_t xid; /* transaction id */ + uint16_t secs; /* elapsed time in sec. from boot */ + uint16_t flags; + 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[DHCP_CHADDR_LEN]; /* client's hardware address */ + uint8_t servername[SERVERNAME_LEN]; /* server host name */ + uint8_t bootfile[BOOTFILE_LEN]; /* boot file name */ + uint32_t cookie; + uint8_t options[DHCP_OPTION_LEN]; /* message options - cookie */ +} __packed; + +struct dhcp_lease { + struct in_addr addr; + struct in_addr net; + 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_INIT, + DHS_DISCOVER, + DHS_REQUEST, + DHS_BOUND, + DHS_RENEW, + DHS_REBIND, + DHS_REBOOT, + DHS_INFORM, + DHS_RENEW_REQUESTED, + DHS_IPV4LL_BOUND, + DHS_PROBE +}; + +struct dhcp_state { + enum DHS state; + struct dhcp_message *sent; + struct dhcp_message *offer; + struct dhcp_message *new; + struct dhcp_message *old; + struct dhcp_lease lease; + const char *reason; + time_t interval; + time_t nakoff; + uint32_t xid; + int socket; + + int raw_fd; + int arp_fd; + size_t buffer_size, buffer_len, buffer_pos; + unsigned char *buffer; + + struct in_addr addr; + struct in_addr net; + struct in_addr dst; + uint8_t added; + + char leasefile[sizeof(LEASEFILE) + IF_NAMESIZE + (IF_SSIDSIZE * 4)]; + time_t start_uptime; + + unsigned char *clientid; + + struct authstate auth; + struct arp_statehead arp_states; + + size_t arping_index; + + struct arp_state *arp_ipv4ll; + unsigned int conflicts; + time_t defend; + char randomstate[128]; +}; + +#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) + +#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); +ssize_t decode_rfc5969(char *, size_t, const uint8_t *p, size_t); + +void dhcp_printoptions(const struct dhcpcd_ctx *, + const struct dhcp_opt *, size_t); +int get_option_addr(struct dhcpcd_ctx *,struct in_addr *, + const struct dhcp_message *, uint8_t); +#define IS_BOOTP(i, m) ((m) && \ + !IN_LINKLOCAL(htonl((m)->yiaddr)) && \ + get_option_uint8((i)->ctx, NULL, (m), DHO_MESSAGETYPE) == -1) +struct rt_head *get_option_routes(struct interface *, + const struct dhcp_message *); +ssize_t dhcp_env(char **, const char *, const struct dhcp_message *, + const struct interface *); + +uint32_t dhcp_xid(const struct interface *); +struct dhcp_message *dhcp_message_new(const struct in_addr *addr, + const struct in_addr *mask); +int dhcp_message_add_addr(struct dhcp_message *, uint8_t, struct in_addr); +ssize_t make_message(struct dhcp_message **, const struct interface *, + uint8_t); +int valid_dhcp_packet(unsigned char *); + +void dhcp_handleifa(int, struct interface *, + const struct in_addr *, const struct in_addr *, const struct in_addr *, + int); + +void dhcp_drop(struct interface *, const char *); +void dhcp_start(struct interface *); +void dhcp_stop(struct interface *); +void dhcp_discover(void *); +void dhcp_inform(struct interface *); +void dhcp_bind(struct interface *, struct arp_state *); +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_drop(a, b) {} +#define dhcp_start(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 @@ -0,0 +1,3600 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* TODO: We should decline dupliate addresses detected */ + +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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 "script.h" + +#ifndef __UNCONST +#define __UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#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 + +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, "RECONFIURE6" }, + { 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" +}; + +struct dhcp6_ia_addr { + struct in6_addr addr; + uint32_t pltime; + uint32_t vltime; +} __packed; + +struct dhcp6_pd_addr { + uint32_t pltime; + uint32_t vltime; + uint8_t prefix_len; + struct in6_addr prefix; +} __packed; + +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_makevendor(struct dhcp6_option *o, const struct interface *ifp) +{ + const struct if_options *ifo; + size_t len, i; + uint8_t *p; + uint16_t u16; + uint32_t u32; + ssize_t vlen; + const struct vivco *vivco; + char vendor[VENDORCLASSID_MAX_LEN]; + + 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) { + logger(ifp->ctx, LOG_ERR, + "%s: DHCPv6 Vendor Class too big", ifp->name); + return 0; + } + + if (o) { + o->code = htons(D6_OPTION_VENDOR_CLASS); + o->len = htons((uint16_t)len); + p = D6_OPTION_DATA(o); + u32 = htonl(ifo->vivco_en ? ifo->vivco_en : DHCPCD_IANA_PEN); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + if (ifo->vivco_en) { + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + { + u16 = htons((uint16_t)vivco->len); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + memcpy(p, vivco->data, vivco->len); + p += vivco->len; + } + } else if (vlen) { + u16 = htons((uint16_t)vlen); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + memcpy(p, vendor, (size_t)vlen); + } + } + + return len; +} + +static const struct dhcp6_option * +dhcp6_findoption(uint16_t code, const uint8_t *d, size_t len) +{ + const struct dhcp6_option *o; + size_t ol; + + code = htons(code); + for (o = (const struct dhcp6_option *)d; + len >= sizeof(*o); + o = D6_CNEXT_OPTION(o)) + { + ol = sizeof(*o) + ntohs(o->len); + if (ol > len) { + errno = EINVAL; + return NULL; + } + if (o->code == code) + return o; + len -= ol; + } + + errno = ESRCH; + return NULL; +} + +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) +{ + const struct dhcp6_option *o; + size_t i; + struct dhcp_opt *opt; + + if (od) { + *os = sizeof(*o); + if (ol < *os) { + errno = EINVAL; + return NULL; + } + o = (const struct dhcp6_option *)od; + *len = ntohs(o->len); + if (*len > ol) { + errno = EINVAL; + return NULL; + } + *code = ntohs(o->code); + } else + o = NULL; + + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; i++, opt++) + { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + if (o) + return D6_COPTION_DATA(o); + return NULL; +} + +static const struct dhcp6_option * +dhcp6_getmoption(uint16_t code, const struct dhcp6_message *m, size_t len) +{ + + if (len < sizeof(*m)) { + errno = EINVAL; + return NULL; + } + len -= sizeof(*m); + return dhcp6_findoption(code, + (const uint8_t *)D6_CFIRST_OPTION(m), len); +} + +static int +dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, size_t len) +{ + struct dhcp6_state *state; + const struct dhcp6_option *co; + struct dhcp6_option *o; + time_t up; + uint16_t u16; + + co = dhcp6_getmoption(D6_OPTION_ELAPSED, m, len); + if (co == NULL) + return -1; + + o = __UNCONST(co); + state = D6_STATE(ifp); + up = uptime() - state->start_uptime; + if (up < 0 || up > (time_t)UINT16_MAX) + up = (time_t)UINT16_MAX; + u16 = htons((uint16_t)up); + memcpy(D6_OPTION_DATA(o), &u16, sizeof(u16)); + return 0; +} + +static void +dhcp6_newxid(const struct interface *ifp, struct dhcp6_message *m) +{ + 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 + xid = arc4random(); + + m->xid[0] = (xid >> 16) & 0xff; + m->xid[1] = (xid >> 8) & 0xff; + m->xid[2] = xid & 0xff; +} + +static const struct if_sla * +dhcp6_findselfsla(struct interface *ifp, const uint8_t *iaid) +{ + size_t i, j; + + for (i = 0; i < ifp->options->ia_len; i++) { + if (iaid == NULL || + memcmp(&ifp->options->ia[i].iaid, iaid, + sizeof(ifp->options->ia[i].iaid)) == 0) + { + for (j = 0; j < ifp->options->ia[i].sla_len; j++) { + if (strcmp(ifp->options->ia[i].sla[j].ifname, + ifp->name) == 0) + return &ifp->options->ia[i].sla[j]; + } + } + } + return NULL; +} + + +#ifndef ffs32 +static int +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) + v += 1; + + return v; +} +#endif + +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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + + TAILQ_INIT(&state->addrs); + state->state = DH6S_DELEGATED; + state->reason = "DELEGATED6"; + } + + if (sla == NULL || sla->sla_set == 0) { + asla.sla = ifp->index; + asla.prefix_len = 0; + sla = &asla; + } else if (sla->prefix_len == 0) { + asla.sla = sla->sla; + if (asla.sla == 0) + asla.prefix_len = prefix->prefix_len; + else + asla.prefix_len = 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 != ifp && ifi->index > sla_max) + sla_max = ifi->index; + } + } else + sla_max = ia->sla_max; + + bits = ffs32(sla_max); + + if (prefix->prefix_len + bits > 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 maks SLAAC + * possible. Otherwise round up to the nearest octet. */ + if (asla.prefix_len <= 64) + asla.prefix_len = 64; + else + asla.prefix_len = (uint8_t)ROUNDUP8(asla.prefix_len); + + } + +#define BIT(n) (1l << (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)); + logger(ifp->ctx, LOG_ERR, + "%s: invalid prefix %s/%d + %d/%d: %m", + 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)); + logger(ifp->ctx, LOG_ERR, + "%s: cannot delegate excluded prefix %s/%d", + ifp->name, sa, prefix->prefix_exclude_len); + return -1; + } + + return sla->prefix_len; +} + +int +dhcp6_has_public_addr(const struct interface *ifp) +{ + const struct dhcp6_state *state = D6_CSTATE(ifp); + const struct ipv6_addr *ia; + + if (state == NULL) + return 0; + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ipv6_publicaddr(ia)) + return 1; + } + return 0; +} + +static int +dhcp6_makemessage(struct interface *ifp) +{ + struct dhcp6_state *state; + struct dhcp6_message *m; + struct dhcp6_option *o, *so, *eo; + const struct dhcp6_option *si, *unicast; + size_t l, n, len, ml; + uint8_t u8, type; + uint16_t u16, n_options, auth_len; + struct if_options *ifo; + const struct dhcp_opt *opt, *opt2; + uint8_t IA, *p; + const uint8_t *pp; + uint32_t u32; + const struct ipv6_addr *ap; + char hbuf[HOSTNAME_MAX_LEN + 1]; + const char *hostname; + int fqdn; + struct dhcp6_ia_addr *iap; + struct dhcp6_pd_addr *pdp; + + 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) { + if (ifo->hostname[0] == '\0') + hostname = get_hostname(hbuf, sizeof(hbuf), + ifo->options & DHCPCD_HOSTNAME_SHORT ? 1 : 0); + else + hostname = ifo->hostname; + } else + hostname = NULL; /* appearse gcc */ + + /* Work out option size first */ + n_options = 0; + len = 0; + si = NULL; + 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 & NOREQ) && + (opt->type & REQUEST || + has_option_mask(ifo->requestmask6, opt->option))) + { + n_options++; + len += sizeof(u16); + } + } + for (l = 0, opt = ifo->dhcp6_override; + l < ifo->dhcp6_override_len; + l++, opt++) + { + if (!(opt->type & NOREQ) && + (opt->type & REQUEST || + has_option_mask(ifo->requestmask6, opt->option))) + { + n_options++; + len += sizeof(u16); + } + } + if (dhcp6_findselfsla(ifp, NULL)) { + n_options++; + len += sizeof(u16); + } + if (len) + len += sizeof(*o); + + if (fqdn != FQDN_DISABLE) + len += sizeof(*o) + 1 + encode_rfc1035(hostname, NULL); + + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE) + len += sizeof(*o); /* Reconfigure Accept */ + } + + len += sizeof(*state->send); + len += sizeof(*o) + ifp->ctx->duid_len; + len += sizeof(*o) + sizeof(uint16_t); /* elapsed */ + len += sizeof(*o) + 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_getmoption(D6_OPTION_SERVERID, m, ml); + if (si == NULL) { + errno = ESRCH; + return -1; + } + len += sizeof(*si) + ntohs(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->prefix_vltime == 0 && + !(ap->flags & IPV6_AF_REQUEST)) + continue; + if (ap->ia_type == D6_OPTION_IA_PD) { + if (!(ifo->options & DHCPCD_NOPFXDLG)) { + len += sizeof(*o) + sizeof(u8) + + sizeof(u32) + sizeof(u32) + + sizeof(ap->prefix); + if (ap->prefix_exclude_len) + len += sizeof(*o) + 1 + + (uint8_t)((ap->prefix_exclude_len - + ap->prefix_len - 1) / NBBY) + + 1; + + } + } else if (!(ifo->options & DHCPCD_PFXDLGONLY)) + len += sizeof(*o) + sizeof(ap->addr) + + sizeof(u32) + sizeof(u32); + } + /* FALLTHROUGH */ + case DH6S_INIT: + for (l = 0; l < ifo->ia_len; l++) { + if (ifo->ia[l].ia_type == D6_OPTION_IA_PD) { + if (ifo->options & DHCPCD_NOPFXDLG) + continue; + } else if (ifo->options & DHCPCD_PFXDLGONLY) + continue; + len += sizeof(*o) + (sizeof(u32) * 3); + } + 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) { + break; + case DH6S_INIT: /* FALLTHROUGH */ + case DH6S_DISCOVER: + type = DHCP6_SOLICIT; + break; + case DH6S_REQUEST: + type = DHCP6_REQUEST; + unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml); + break; + case DH6S_CONFIRM: + type = DHCP6_CONFIRM; + break; + case DH6S_REBIND: + type = DHCP6_REBIND; + break; + case DH6S_RENEW: + type = DHCP6_RENEW; + unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml); + break; + case DH6S_INFORM: + type = DHCP6_INFORMATION_REQ; + break; + case DH6S_RELEASE: + type = DHCP6_RELEASE; + unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml); + break; + default: + errno = EINVAL; + return -1; + } + + 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) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp_auth_encode: %m", ifp->name); + else if (alen != 0) { + auth_len = (uint16_t)alen; + len += sizeof(*o) + auth_len; + } + } + + 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 && ntohs(unicast->len) == sizeof(state->unicast)) + memcpy(&state->unicast, D6_COPTION_DATA(unicast), + sizeof(state->unicast)); + else + state->unicast = in6addr_any; + + dhcp6_newxid(ifp, state->send); + + o = D6_FIRST_OPTION(state->send); + o->code = htons(D6_OPTION_CLIENTID); + o->len = htons((uint16_t)ifp->ctx->duid_len); + memcpy(D6_OPTION_DATA(o), ifp->ctx->duid, ifp->ctx->duid_len); + + if (si) { + o = D6_NEXT_OPTION(o); + memcpy(o, si, sizeof(*si) + ntohs(si->len)); + } + + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_ELAPSED); + o->len = htons(sizeof(uint16_t)); + p = D6_OPTION_DATA(o); + memset(p, 0, sizeof(uint16_t)); + + o = D6_NEXT_OPTION(o); + dhcp6_makevendor(o, ifp); + + if (state->state == DH6S_DISCOVER && + !(ifp->ctx->options & DHCPCD_TEST) && + has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT)) + { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_RAPID_COMMIT); + o->len = 0; + } + + for (l = 0; IA && l < ifo->ia_len; l++) { + if (ifo->ia[l].ia_type == D6_OPTION_IA_PD) { + if (ifo->options & DHCPCD_NOPFXDLG) + continue; + } else if (ifo->options & DHCPCD_PFXDLGONLY) + continue; + o = D6_NEXT_OPTION(o); + o->code = htons(ifo->ia[l].ia_type); + o->len = htons(sizeof(u32) + sizeof(u32) + sizeof(u32)); + p = D6_OPTION_DATA(o); + memcpy(p, ifo->ia[l].iaid, sizeof(u32)); + p += sizeof(u32); + memset(p, 0, sizeof(u32) + sizeof(u32)); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->prefix_vltime == 0 && + !(ap->flags & IPV6_AF_REQUEST)) + continue; + if (memcmp(ifo->ia[l].iaid, ap->iaid, sizeof(u32))) + continue; + so = D6_NEXT_OPTION(o); + if (ap->ia_type == D6_OPTION_IA_PD) { + so->code = htons(D6_OPTION_IAPREFIX); + so->len = htons(sizeof(ap->prefix) + + sizeof(u32) + sizeof(u32) + sizeof(u8)); + pdp = (struct dhcp6_pd_addr *) + D6_OPTION_DATA(so); + pdp->pltime = htonl(ap->prefix_pltime); + pdp->vltime = htonl(ap->prefix_vltime); + pdp->prefix_len = ap->prefix_len; + pdp->prefix = ap->prefix; + + /* RFC6603 Section 4.2 */ + if (ap->prefix_exclude_len) { + n = (size_t)((ap->prefix_exclude_len - + ap->prefix_len - 1) / NBBY) + 1; + eo = D6_NEXT_OPTION(so); + eo->code = htons(D6_OPTION_PD_EXCLUDE); + eo->len = (uint16_t)(n + 1); + p = D6_OPTION_DATA(eo); + *p++ = (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) + *p++ = *pp--; + if (u8) + *p = (uint8_t)(*pp << u8); + u16 = (uint16_t)(ntohs(so->len) + + sizeof(*eo) + eo->len); + so->len = htons(u16); + eo->len = htons(eo->len); + } + + u16 = (uint16_t)(ntohs(o->len) + sizeof(*so) + + ntohs(so->len)); + o->len = htons(u16); + } else { + so->code = htons(D6_OPTION_IA_ADDR); + so->len = sizeof(ap->addr) + + sizeof(u32) + sizeof(u32); + iap = (struct dhcp6_ia_addr *) + D6_OPTION_DATA(so); + iap->addr = ap->addr; + iap->pltime = htonl(ap->prefix_pltime); + iap->vltime = htonl(ap->prefix_vltime); + u16 = (uint16_t)(ntohs(o->len) + sizeof(*so) + + so->len); + so->len = htons(so->len); + o->len = htons(u16); + } + } + } + + if (state->send->type != DHCP6_RELEASE) { + if (fqdn != FQDN_DISABLE) { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_FQDN); + p = D6_OPTION_DATA(o); + switch (fqdn) { + case FQDN_BOTH: + *p = D6_FQDN_BOTH; + break; + case FQDN_PTR: + *p = D6_FQDN_PTR; + break; + default: + *p = D6_FQDN_NONE; + break; + } + l = encode_rfc1035(hostname, p + 1); + if (l == 0) + *p = D6_FQDN_NONE; + o->len = htons((uint16_t)(l + 1)); + } + + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE) + { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_RECONF_ACCEPT); + o->len = 0; + } + + if (n_options) { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_ORO); + o->len = 0; + p = D6_OPTION_DATA(o); + 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 & NOREQ) && + (opt->type & REQUEST || + has_option_mask(ifo->requestmask6, + opt->option))) + { + u16 = htons((uint16_t)opt->option); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + o->len = (uint16_t)(o->len + sizeof(u16)); + } + } + for (l = 0, opt = ifo->dhcp6_override; + l < ifo->dhcp6_override_len; + l++, opt++) + { + if (!(opt->type & NOREQ) && + (opt->type & REQUEST || + has_option_mask(ifo->requestmask6, + opt->option))) + { + u16 = htons((uint16_t)opt->option); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + o->len = (uint16_t)(o->len + sizeof(u16)); + } + } + if (dhcp6_findselfsla(ifp, NULL)) { + u16 = htons(D6_OPTION_PD_EXCLUDE); + memcpy(p, &u16, sizeof(u16)); + o->len = (uint16_t)(o->len + sizeof(u16)); + } + o->len = htons(o->len); + } + } + + /* This has to be the last option */ + if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_AUTH); + o->len = htons((uint16_t)auth_len); + /* data will be filled at send message time */ + } + + 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) + ipv6_buildroutes(ifp->ctx); + } +} + +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); + } + } +} + +static ssize_t +dhcp6_update_auth(struct interface *ifp, struct dhcp6_message *m, size_t len) +{ + struct dhcp6_state *state; + const struct dhcp6_option *co; + struct dhcp6_option *o; + + co = dhcp6_getmoption(D6_OPTION_AUTH, m, len); + if (co == NULL) + return -1; + + o = __UNCONST(co); + 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, + D6_OPTION_DATA(o), ntohs(o->len)); +} + +static int +dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) +{ + struct dhcp6_state *state; + struct ipv6_ctx *ctx; + struct sockaddr_in6 dst; + struct cmsghdr *cm; + struct in6_pktinfo pi; + struct timespec RTprev; + double rnd; + time_t ms; + uint8_t neg; + const char *broad_uni; + const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT; + + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; + dst.sin6_port = htons(DHCP6_SERVER_PORT); +#ifdef SIN6_LEN + dst.sin6_len = sizeof(dst); +#endif + + state = D6_STATE(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) || !ipv6_linklocal(ifp)))) + { + dst.sin6_addr = alldhcp; + broad_uni = "broadcasting"; + } else { + dst.sin6_addr = state->unicast; + broad_uni = "unicasting"; + } + + if (!callback) + logger(ifp->ctx, LOG_DEBUG, + "%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) { + /* 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->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: + logger(ifp->ctx, LOG_DEBUG, + "%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)); + + /* Wait the initial delay */ + if (state->IMD) { + state->IMD = 0; + eloop_timeout_add_tv(ifp->ctx->eloop, + &state->RT, callback, ifp); + return 0; + } + } + + /* Update the elapsed time */ + dhcp6_updateelapsed(ifp, state->send, state->send_len); + if (ifp->options->auth.options & DHCPCD_AUTH_SEND && + dhcp6_update_auth(ifp, state->send, state->send_len) == -1) + { + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_updateauth: %m", ifp->name); + if (errno != ESRCH) + return -1; + } + + ctx = ifp->ctx->ipv6; + dst.sin6_scope_id = ifp->index; + 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 */ + 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)); + + if (sendmsg(ctx->dhcp_fd, &ctx->sndhdr, 0) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: %s: sendmsg: %m", ifp->name, __func__); + ifp->options->options &= ~DHCPCD_IPV6; + dhcp6_drop(ifp, "EXPIRE6"); + return -1; + } + + 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 + logger(ifp->ctx, LOG_WARNING, + "%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; + state = D6_STATE(ifp); + state->state = DH6S_RENEW; + state->start_uptime = uptime(); + state->RTC = 0; + state->IRT = REN_TIMEOUT; + state->MRT = REN_MAX_RT; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_sendrenew(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 *ap = arg; + struct interface *ifp; + struct dhcp6_state *state; + int wascompleted, valid; + + wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED); + ap->flags |= IPV6_AF_DADCOMPLETED; + if (ap->flags & IPV6_AF_DUPLICATED) + /* XXX FIXME + * We should decline the address */ + logger(ap->iface->ctx, LOG_WARNING, "%s: DAD detected %s", + ap->iface->name, ap->saddr); + + if (!wascompleted) { + ifp = ap->iface; + state = D6_STATE(ifp); + if (state->state == DH6S_BOUND || + state->state == DH6S_DELEGATED) + { + struct ipv6_addr *ap2; + + valid = (ap->delegating_iface == NULL); + TAILQ_FOREACH(ap2, &state->addrs, next) { + if (ap2->flags & IPV6_AF_ADDED && + !(ap2->flags & IPV6_AF_DADCOMPLETED)) + { + wascompleted = 1; + break; + } + } + if (!wascompleted) { + logger(ap->iface->ctx, LOG_DEBUG, + "%s: DHCPv6 DAD completed", ifp->name); + script_runreason(ifp, + ap->delegating_iface ? + "DELEGATED6" : state->reason); + if (valid) + dhcpcd_daemonise(ifp->ctx); + } + } + } +} + +static void +dhcp6_addrequestedaddrs(struct interface *ifp) +{ + struct dhcp6_state *state; + size_t i; + struct if_ia *ia; + struct ipv6_addr *a; + char iabuf[INET6_ADDRSTRLEN]; + const char *iap; + + 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 = calloc(1, sizeof(*a)); + if (a == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return; + } + a->flags = IPV6_AF_REQUEST; + a->iface = ifp; + a->dadcallback = dhcp6_dadcallback; + memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid)); + a->ia_type = ia->ia_type; + //a->prefix_pltime = 0; + //a->prefix_vltime = 0; + + if (ia->ia_type == D6_OPTION_IA_PD) { + memcpy(&a->prefix, &ia->addr, sizeof(a->addr)); + a->prefix_len = ia->prefix_len; + iap = inet_ntop(AF_INET6, &a->prefix, + iabuf, sizeof(iabuf)); + } else { + memcpy(&a->addr, &ia->addr, sizeof(a->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. + */ + a->prefix_len = 128; + ipv6_makeprefix(&a->prefix, &a->addr, a->prefix_len); + iap = inet_ntop(AF_INET6, &a->addr, + iabuf, sizeof(iabuf)); + } + snprintf(a->saddr, sizeof(a->saddr), + "%s/%d", iap, a->prefix_len); + TAILQ_INSERT_TAIL(&state->addrs, a, next); + } +} + +static void +dhcp6_startdiscover(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + + ifp = arg; + dhcp6_delete_delegates(ifp); + logger(ifp->ctx, LOG_INFO, "%s: soliciting a DHCPv6 lease", ifp->name); + state = D6_STATE(ifp); + state->state = DH6S_DISCOVER; + state->start_uptime = uptime(); + state->RTC = 0; + state->IMD = SOL_MAX_DELAY; + state->IRT = SOL_TIMEOUT; + state->MRT = state->sol_max_rt; + state->MRC = 0; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + free(state->new); + state->new = NULL; + state->new_len = 0; + + dhcp6_freedrop_addrs(ifp, 0, NULL); + unlink(state->leasefile); + + dhcp6_addrequestedaddrs(ifp); + + if (dhcp6_makemessage(ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_senddiscover(ifp); +} + +static void +dhcp6_failconfirm(void *arg) +{ + struct interface *ifp; + + ifp = arg; + logger(ifp->ctx, LOG_ERR, + "%s: failed to confirm prior address", ifp->name); + /* Section 18.1.2 says that we SHOULD use the last known + * IP address(s) and lifetimes if we didn't get a reply. + * I disagree with this. */ + dhcp6_startdiscover(ifp); +} + +static void +dhcp6_failrequest(void *arg) +{ + struct interface *ifp; + + ifp = arg; + logger(ifp->ctx, LOG_ERR, "%s: failed to request address", ifp->name); + /* Section 18.1.1 says that client local policy dictates + * what happens if a REQUEST fails. + * Of the possible scenarios listed, moving back to the + * DISCOVER phase makes more sense for us. */ + dhcp6_startdiscover(ifp); +} + +static void +dhcp6_failrebind(void *arg) +{ + struct interface *ifp; + + ifp = arg; + logger(ifp->ctx, LOG_ERR, + "%s: failed to rebind prior delegation", ifp->name); + dhcp6_delete_delegates(ifp); + /* Section 18.1.2 says that we SHOULD use the last known + * IP address(s) and lifetimes if we didn't get a reply. + * I disagree with this. */ + dhcp6_startdiscover(ifp); +} + + +static int +dhcp6_hasprefixdelegation(struct interface *ifp) +{ + size_t i; + uint16_t t; + + if (ifp->options->options & DHCPCD_NOPFXDLG) + return 0; + + 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; +} + +static void +dhcp6_startrebind(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + int pd; + + ifp = arg; + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp); + state = D6_STATE(ifp); + if (state->state == DH6S_RENEW) + logger(ifp->ctx, LOG_WARNING, + "%s: failed to renew DHCPv6, rebinding", ifp->name); + else + logger(ifp->ctx, LOG_INFO, + "%s: rebinding prior DHCPv6 lease", ifp->name); + state->state = DH6S_REBIND; + state->RTC = 0; + state->MRC = 0; + + /* 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 { + state->IRT = REB_TIMEOUT; + state->MRT = REB_MAX_RT; + } + + if (dhcp6_makemessage(ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_sendrebind(ifp); + + /* RFC 3633 section 12.1 */ + if (pd) + eloop_timeout_add_sec(ifp->ctx->eloop, + CNF_MAX_RD, dhcp6_failrebind, ifp); +} + + +static void +dhcp6_startrequest(struct interface *ifp) +{ + struct dhcp6_state *state; + + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp); + state = D6_STATE(ifp); + state->state = DH6S_REQUEST; + state->RTC = 0; + state->IRT = REQ_TIMEOUT; + state->MRT = REQ_MAX_RT; + state->MRC = REQ_MAX_RC; + state->MRCcallback = dhcp6_failrequest; + + if (dhcp6_makemessage(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", 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->start_uptime = uptime(); + state->RTC = 0; + state->IMD = CNF_MAX_DELAY; + state->IRT = CNF_TIMEOUT; + state->MRT = CNF_MAX_RT; + state->MRC = 0; + + logger(ifp->ctx, LOG_INFO, + "%s: confirming prior DHCPv6 lease", ifp->name); + if (dhcp6_makemessage(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", ifp->name); + return; + } + dhcp6_sendconfirm(ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + CNF_MAX_RD, dhcp6_failconfirm, 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) + logger(ifp->ctx, LOG_INFO, + "%s: requesting DHCPv6 information", ifp->name); + state->state = DH6S_INFORM; + state->start_uptime = uptime(); + state->RTC = 0; + state->IMD = INF_MAX_DELAY; + state->IRT = INF_TIMEOUT; + state->MRT = state->inf_max_rt; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_sendinform(ifp); +} + +static void +dhcp6_startexpire(void *arg) +{ + struct interface *ifp; + + ifp = arg; + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp); + + logger(ifp->ctx, LOG_ERR, "%s: DHCPv6 lease expired", ifp->name); + dhcp6_freedrop_addrs(ifp, 1, NULL); + dhcp6_delete_delegates(ifp); + script_runreason(ifp, "EXPIRE6"); + if (ipv6nd_hasradhcp(ifp) || dhcp6_hasprefixdelegation(ifp)) + dhcp6_startdiscover(ifp); + else + logger(ifp->ctx, LOG_WARNING, + "%s: no advertising IPv6 router wants DHCP", ifp->name); +} + +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->start_uptime = uptime(); + state->RTC = 0; + state->IRT = REL_TIMEOUT; + state->MRT = 0; + state->MRC = REL_MAX_RC; + //state->MRCcallback = dhcp6_failrelease; + state->MRCcallback = NULL; + + if (dhcp6_makemessage(ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_makemessage: %m", ifp->name); + else + /* XXX: We should loop a few times + * Luckily RFC3315 section 18.1.6 says this is optional */ + //dhcp6_sendrelease(ifp); + dhcp6_sendmessage(ifp, NULL); +} + +static int +dhcp6_checkstatusok(const struct interface *ifp, + const struct dhcp6_message *m, const uint8_t *p, size_t len) +{ + const struct dhcp6_option *o; + uint16_t code; + char *status; + + if (p) + o = dhcp6_findoption(D6_OPTION_STATUS_CODE, p, len); + else + o = dhcp6_getmoption(D6_OPTION_STATUS_CODE, m, len); + if (o == NULL) { + //logger(ifp->ctx, LOG_DEBUG, "%s: no status", ifp->name); + return 0; + } + + len = ntohs(o->len); + if (len < sizeof(code)) { + logger(ifp->ctx, LOG_ERR, "%s: status truncated", ifp->name); + return -1; + } + + p = D6_COPTION_DATA(o); + memcpy(&code, p, sizeof(code)); + code = ntohs(code); + if (code == D6_STATUS_OK) + return 1; + + len -= sizeof(code); + + if (len == 0) { + if (code < sizeof(dhcp6_statuses) / sizeof(char *)) { + p = (const uint8_t *)dhcp6_statuses[code]; + len = strlen((const char *)p); + } else + p = NULL; + } else + p += sizeof(code); + + status = malloc(len + 1); + if (status == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + if (p) + memcpy(status, p, len); + status[len] = '\0'; + logger(ifp->ctx, LOG_ERR, "%s: DHCPv6 REPLY: %s", ifp->name, status); + free(status); + return -1; +} + +static struct ipv6_addr * +dhcp6_iffindaddr(struct interface *ifp, const struct in6_addr *addr, + short flags) +{ + struct dhcp6_state *state; + struct ipv6_addr *ap; + + state = D6_STATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (addr == NULL) { + if ((ap->flags & + (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) == + (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) + return ap; + } else if (ap->prefix_vltime && + IN6_ARE_ADDR_EQUAL(&ap->addr, addr) && + (!flags || ap->flags & flags)) + return ap; + } + } + return NULL; +} + +struct ipv6_addr * +dhcp6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, + short flags) +{ + struct interface *ifp; + struct ipv6_addr *ap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ap = dhcp6_iffindaddr(ifp, addr, flags); + if (ap) + return ap; + } + return NULL; +} + +static int +dhcp6_findna(struct interface *ifp, uint16_t ot, const uint8_t *iaid, + const uint8_t *d, size_t l, const struct timespec *acquired) +{ + struct dhcp6_state *state; + const struct dhcp6_option *o; + struct ipv6_addr *a; + char iabuf[INET6_ADDRSTRLEN]; + const char *ia; + int i; + uint32_t u32; + size_t off; + const struct dhcp6_ia_addr *iap; + + i = 0; + state = D6_STATE(ifp); + while ((o = dhcp6_findoption(D6_OPTION_IA_ADDR, d, l))) { + off = (size_t)((const uint8_t *)o - d); + l -= off; + d += off; + u32 = ntohs(o->len); + l -= sizeof(*o) + u32; + d += sizeof(*o) + u32; + if (u32 < 24) { + errno = EINVAL; + logger(ifp->ctx, LOG_ERR, + "%s: IA Address option truncated", ifp->name); + continue; + } + iap = (const struct dhcp6_ia_addr *)D6_COPTION_DATA(o); + a = dhcp6_iffindaddr(ifp, &iap->addr, 0); + if (a == NULL) { + a = calloc(1, sizeof(*a)); + if (a == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + break; + } + a->iface = ifp; + a->flags = IPV6_AF_NEW | IPV6_AF_ONLINK; + a->dadcallback = dhcp6_dadcallback; + a->ia_type = ot; + memcpy(a->iaid, iaid, sizeof(a->iaid)); + a->addr = iap->addr; + a->created = *acquired; + + /* + * 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->prefix_len = 128; + ipv6_makeprefix(&a->prefix, &a->addr, a->prefix_len); + ia = inet_ntop(AF_INET6, &a->addr, + iabuf, sizeof(iabuf)); + snprintf(a->saddr, sizeof(a->saddr), + "%s/%d", ia, a->prefix_len); + + 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; + } + a->acquired = *acquired; + a->prefix_pltime = ntohl(iap->pltime); + u32 = ntohl(iap->vltime); + if (a->prefix_vltime != u32) { + a->flags |= IPV6_AF_NEW; + a->prefix_vltime = u32; + } + 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; +} + +static int +dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, + const uint8_t *d, size_t l, const struct timespec *acquired) +{ + struct dhcp6_state *state; + const struct dhcp6_option *o, *ex; + const uint8_t *p, *op; + struct ipv6_addr *a; + char iabuf[INET6_ADDRSTRLEN]; + const char *ia; + int i; + uint8_t u8, *pw; + size_t off; + uint16_t ol; + const struct dhcp6_pd_addr *pdp; + + i = 0; + state = D6_STATE(ifp); + while ((o = dhcp6_findoption(D6_OPTION_IAPREFIX, d, l))) { + off = (size_t)((const uint8_t *)o - d); + l -= off; + d += off; + ol = ntohs(o->len); + l -= sizeof(*o) + ol; + d += sizeof(*o) + ol; + if (ol < sizeof(*pdp)) { + errno = EINVAL; + logger(ifp->ctx, LOG_ERR, + "%s: IA Prefix option truncated", ifp->name); + continue; + } + + pdp = (const struct dhcp6_pd_addr *)D6_COPTION_DATA(o); + TAILQ_FOREACH(a, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&a->prefix, &pdp->prefix)) + break; + } + if (a == NULL) { + a = calloc(1, sizeof(*a)); + if (a == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + break; + } + a->iface = ifp; + a->flags = IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; + a->created = *acquired; + a->dadcallback = dhcp6_dadcallback; + a->ia_type = D6_OPTION_IA_PD; + memcpy(a->iaid, iaid, sizeof(a->iaid)); + a->prefix = pdp->prefix; + a->prefix_len = pdp->prefix_len; + ia = inet_ntop(AF_INET6, &a->prefix, + iabuf, sizeof(iabuf)); + snprintf(a->saddr, sizeof(a->saddr), + "%s/%d", ia, a->prefix_len); + TAILQ_INSERT_TAIL(&state->addrs, a, next); + } else { + if (!(a->flags & IPV6_AF_DELEGATEDPFX)) + a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; + a->flags &= ~(IPV6_AF_STALE | IPV6_AF_REQUEST); + if (a->prefix_vltime != ntohl(pdp->vltime)) + a->flags |= IPV6_AF_NEW; + } + + a->acquired = *acquired; + a->prefix_pltime = ntohl(pdp->pltime); + a->prefix_vltime = ntohl(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++; + + p = D6_COPTION_DATA(o) + sizeof(pdp); + ol = (uint16_t)(ol - sizeof(pdp)); + ex = dhcp6_findoption(D6_OPTION_PD_EXCLUDE, p, 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 (ex == NULL) + continue; + ol = ntohs(ex->len); + if (ol < 2) { + logger(ifp->ctx, LOG_ERR, + "%s: truncated PD Exclude", ifp->name); + continue; + } + op = D6_COPTION_DATA(ex); + a->prefix_exclude_len = *op++; + ol--; + if (((a->prefix_exclude_len - a->prefix_len - 1) / NBBY) + 1 + != ol) + { + logger(ifp->ctx, LOG_ERR, + "%s: PD Exclude length mismatch", ifp->name); + a->prefix_exclude_len = 0; + continue; + } + u8 = a->prefix_len % NBBY; + memcpy(&a->prefix_exclude, &a->prefix, + sizeof(a->prefix_exclude)); + if (u8) + ol--; + pw = a->prefix_exclude.s6_addr + + (a->prefix_exclude_len / NBBY) - 1; + while (ol-- > 0) + *pw-- = *op++; + if (u8) + *pw = (uint8_t)(*pw | (*op >> u8)); + } + return i; +} + +static int +dhcp6_findia(struct interface *ifp, const struct dhcp6_message *m, size_t l, + const char *sfrom, const struct timespec *acquired) +{ + struct dhcp6_state *state; + const struct if_options *ifo; + const struct dhcp6_option *o; + const uint8_t *p; + int i, e; + size_t j; + uint32_t u32, renew, rebind; + uint16_t code, ol; + uint8_t iaid[4]; + char buf[sizeof(iaid) * 3]; + struct ipv6_addr *ap, *nap; + + 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) { + ap->flags |= IPV6_AF_STALE; + } + l -= sizeof(*m); + for (o = D6_CFIRST_OPTION(m); l > sizeof(*o); o = D6_CNEXT_OPTION(o)) { + ol = ntohs(o->len); + if (sizeof(*o) + ol > l) { + errno = EINVAL; + logger(ifp->ctx, LOG_ERR, + "%s: option overflow", ifp->name); + break; + } + l -= sizeof(*o) + ol; + + code = ntohs(o->code); + switch(code) { + case D6_OPTION_IA_TA: + u32 = 4; + break; + case D6_OPTION_IA_NA: + case D6_OPTION_IA_PD: + u32 = 12; + break; + default: + continue; + } + if (ol < u32) { + errno = EINVAL; + logger(ifp->ctx, LOG_ERR, + "%s: IA option truncated", ifp->name); + continue; + } + + p = D6_COPTION_DATA(o); + memcpy(iaid, p, sizeof(iaid)); + p += sizeof(iaid); + ol = (uint16_t)(ol - sizeof(iaid)); + + for (j = 0; j < ifo->ia_len; j++) { + if (memcmp(&ifo->ia[j].iaid, iaid, sizeof(iaid)) == 0) + break; + } + if (j == ifo->ia_len && + !(ifo->ia_len == 0 && ifp->ctx->options & DHCPCD_DUMPLEASE)) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: ignoring unrequested IAID %s", + ifp->name, + hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf))); + continue; + } + if ( j < ifo->ia_len && ifo->ia[j].ia_type != code) { + logger(ifp->ctx, LOG_ERR, + "%s: IAID %s: option type mismatch", + ifp->name, + hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf))); + continue; + } + + if (code != D6_OPTION_IA_TA) { + memcpy(&u32, p, sizeof(u32)); + renew = ntohl(u32); + p += sizeof(u32); + ol = (uint16_t)(ol - sizeof(u32)); + memcpy(&u32, p, sizeof(u32)); + rebind = ntohl(u32); + p += sizeof(u32); + ol = (uint16_t)(ol - sizeof(u32)); + } else + renew = rebind = 0; /* appease gcc */ + if (dhcp6_checkstatusok(ifp, NULL, p, ol) == -1) { + e = 1; + continue; + } + if (code == D6_OPTION_IA_PD) { + if (!(ifo->options & DHCPCD_NOPFXDLG) && + dhcp6_findpd(ifp, iaid, p, ol, acquired) == 0) + { + logger(ifp->ctx, LOG_WARNING, + "%s: %s: DHCPv6 REPLY missing Prefix", + ifp->name, sfrom); + continue; + } + } else if (!(ifo->options & DHCPCD_PFXDLGONLY)) { + if (dhcp6_findna(ifp, code, iaid, p, ol, acquired) == 0) + { + logger(ifp->ctx, LOG_WARNING, + "%s: %s: DHCPv6 REPLY missing IA Address", + ifp->name, sfrom); + continue; + } + } + if (code != D6_OPTION_IA_TA) { + if (renew > rebind && rebind > 0) { + if (sfrom) + logger(ifp->ctx, LOG_WARNING, + "%s: T1 (%d) > T2 (%d) from %s", + ifp->name, renew, rebind, sfrom); + renew = 0; + rebind = 0; + } + if (renew != 0 && + (renew < state->renew || state->renew == 0)) + state->renew = renew; + if (rebind != 0 && + (rebind < state->rebind || state->rebind == 0)) + state->rebind = rebind; + } + i++; + } + TAILQ_FOREACH_SAFE(ap, &state->addrs, next, nap) { + if (ap->flags & IPV6_AF_STALE) { + eloop_q_timeout_delete(ifp->ctx->eloop, 0, NULL, ap); + if (ap->flags & IPV6_AF_REQUEST) { + ap->prefix_vltime = ap->prefix_pltime = 0; + } else { + TAILQ_REMOVE(&state->addrs, ap, next); + free(ap); + } + } + } + if (i == 0 && e) + return -1; + return i; +} + +static int +dhcp6_validatelease(struct interface *ifp, + const struct dhcp6_message *m, size_t len, + const char *sfrom, const struct timespec *acquired) +{ + struct dhcp6_state *state; + int nia; + struct timespec aq; + + if (len <= sizeof(*m)) { + logger(ifp->ctx, LOG_ERR, + "%s: DHCPv6 lease truncated", ifp->name); + return -1; + } + + state = D6_STATE(ifp); + if (dhcp6_checkstatusok(ifp, m, NULL, len) == -1) + return -1; + + state->renew = state->rebind = state->expire = 0; + state->lowpl = ND6_INFINITE_LIFETIME; + if (!acquired) { + get_monotonic(&aq); + acquired = &aq; + } + nia = dhcp6_findia(ifp, m, len, sfrom, acquired); + if (nia == 0) { + logger(ifp->ctx, LOG_ERR, + "%s: no useable IA found in lease", ifp->name); + return -1; + } + 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); + logger(ifp->ctx, LOG_DEBUG, + "%s: writing lease `%s'", ifp->name, state->leasefile); + + fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + logger(ifp->ctx, LOG_ERR, "%s: dhcp6_writelease: %m", ifp->name); + 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; + ssize_t bytes; + const struct dhcp6_option *o; + struct timespec acquired; + time_t now; + int retval; + + state = D6_STATE(ifp); + if (stat(state->leasefile, &st) == -1) + return -1; + logger(ifp->ctx, LOG_DEBUG, "%s: reading lease `%s'", + ifp->name, state->leasefile); + if (st.st_size > UINT32_MAX) { + errno = E2BIG; + return -1; + } + if ((fd = open(state->leasefile, O_RDONLY)) == -1) + return -1; + if ((state->new = malloc((size_t)st.st_size)) == NULL) + return -1; + retval = -1; + state->new_len = (size_t)st.st_size; + bytes = read(fd, state->new, state->new_len); + close(fd); + if (bytes != (ssize_t)state->new_len) + goto ex; + + /* If not validating IA's and if they have expired, + * skip to the auth check. */ + if (!validate) { + fd = 0; + goto auth; + } + + if ((now = time(NULL)) == -1) + goto ex; + + get_monotonic(&acquired); + 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, + &acquired); + if (fd == -1) + goto ex; + + if (!(ifp->ctx->options & DHCPCD_DUMPLEASE) && + state->expire != ND6_INFINITE_LIFETIME) + { + if ((time_t)state->expire < now - st.st_mtime) { + logger(ifp->ctx, + LOG_DEBUG,"%s: discarding expired lease", + ifp->name); + retval = 0; + goto ex; + } + } + +auth: + + retval = 0; + /* Authenticate the message */ + o = dhcp6_getmoption(D6_OPTION_AUTH, state->new, state->new_len); + if (o) { + if (dhcp_auth_validate(&state->auth, &ifp->options->auth, + (uint8_t *)state->new, state->new_len, 6, state->new->type, + D6_COPTION_DATA(o), ntohs(o->len)) == NULL) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: dhcp_auth_validate: %m", ifp->name); + logger(ifp->ctx, LOG_ERR, + "%s: authentication failed", ifp->name); + goto ex; + } + if (state->auth.token) + logger(ifp->ctx, LOG_DEBUG, + "%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + logger(ifp->ctx, LOG_DEBUG, + "%s: accepted reconfigure key", ifp->name); + } else if (ifp->options->auth.options & DHCPCD_AUTH_REQUIRE) { + logger(ifp->ctx, LOG_ERR, + "%s: authentication now required", ifp->name); + goto ex; + } + + 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)) + 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) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_readlease: %s: %m", + ifp->name, state->leasefile); + } else if (r != 0) { + /* RFC 3633 section 12.1 */ + if (dhcp6_hasprefixdelegation(ifp)) + dhcp6_startrebind(ifp); + else + dhcp6_startconfirm(ifp); + return; + } + } + dhcp6_startdiscover(ifp); +} + +static struct ipv6_addr * +dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix, + const struct if_sla *sla, struct if_ia *ia, struct interface *ifs) +{ + struct dhcp6_state *state; + struct in6_addr addr; + struct ipv6_addr *a, *ap, *apn; + char sabuf[INET6_ADDRSTRLEN]; + const char *sa; + int pfxlen; + + /* RFC6603 Section 4.2 */ + if (strcmp(ifp->name, ifs->name) == 0) { + if (prefix->prefix_exclude_len == 0) { + /* Don't spam the log automatically */ + if (sla) + logger(ifp->ctx, LOG_WARNING, + "%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, ia)) == -1) + return NULL; + + + a = calloc(1, sizeof(*a)); + if (a == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + a->iface = ifp; + a->flags = IPV6_AF_NEW | IPV6_AF_ONLINK; + a->dadcallback = dhcp6_dadcallback; + a->delegating_iface = ifs; + memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid)); + a->created = a->acquired = prefix->acquired; + a->prefix_pltime = prefix->prefix_pltime; + a->prefix_vltime = prefix->prefix_vltime; + a->prefix = addr; + a->prefix_len = (uint8_t)pfxlen; + + /* Wang a 1 at the end as the prefix could be >64 + * making SLAAC impossible. */ + a->addr = a->prefix; + a->addr.s6_addr[sizeof(a->addr.s6_addr) - 1] = + (uint8_t)(a->addr.s6_addr[sizeof(a->addr.s6_addr) - 1] + 1); + + state = D6_STATE(ifp); + /* Remove any exiting address */ + TAILQ_FOREACH_SAFE(ap, &state->addrs, next, apn) { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, &a->addr)) { + TAILQ_REMOVE(&state->addrs, ap, next); + /* Keep our flags */ + a->flags |= ap->flags; + a->flags &= ~IPV6_AF_NEW; + a->created = ap->created; + ipv6_freeaddr(ap); + } + } + + sa = inet_ntop(AF_INET6, &a->addr, sabuf, sizeof(sabuf)); + snprintf(a->saddr, sizeof(a->saddr), "%s/%d", sa, a->prefix_len); + TAILQ_INSERT_TAIL(&state->addrs, a, next); + return a; +} + +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)) + ap->flags |= IPV6_AF_DADCOMPLETED; + if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0 && + ((delegated && ap->delegating_iface) || + (!delegated && !ap->delegating_iface))) + { + completed = 0; + break; + } + } + } + if (completed) { + script_runreason(ifp, delegated ? "DELEGATED6" : state->reason); + if (!delegated) + dhcpcd_daemonise(ifp->ctx); + } else + logger(ifp->ctx, LOG_DEBUG, + "%s: waiting for DHCPv6 DAD to complete", ifp->name); +} + +static void +dhcp6_delegate_prefix(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp6_state *state, *ifd_state; + struct ipv6_addr *ap; + size_t i, j, k; + struct if_ia *ia; + struct if_sla *sla; + struct interface *ifd; + uint8_t carrier_warned, abrt; + + ifo = ifp->options; + state = D6_STATE(ifp); + + /* Try to load configured interfaces for delegation that do not exist */ + for (i = 0; i < ifo->ia_len; i++) { + ia = &ifo->ia[i]; + for (j = 0; j < ia->sla_len; j++) { + sla = &ia->sla[j]; + for (k = 0; k < i; j++) + if (strcmp(sla->ifname, ia->sla[j].ifname) == 0) + break; + if (j >= i && + if_find(ifp->ctx->ifaces, sla->ifname) == NULL) + { + logger(ifp->ctx, LOG_INFO, + "%s: loading for delegation", sla->ifname); + if (dhcpcd_handleinterface(ifp->ctx, 2, + sla->ifname) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: interface does not exist" + " for delegation", + sla->ifname); + } + } + } + + TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) { + if (ifd->options->options & DHCPCD_NOPFXDLG) + continue; + k = 0; + carrier_warned = abrt = 0; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (!(ap->flags & IPV6_AF_DELEGATEDPFX)) + continue; + if (ap->flags & IPV6_AF_NEW) { + ap->flags &= ~IPV6_AF_NEW; + logger(ifp->ctx, LOG_DEBUG, + "%s: delegated prefix %s", + ifp->name, ap->saddr); + } + for (i = 0; i < ifo->ia_len; i++) { + ia = &ifo->ia[i]; + 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) { + logger(ifp->ctx, LOG_DEBUG, + "%s: has no carrier, cannot" + " delegate addresses", + ifd->name); + carrier_warned = 1; + break; + } + if (dhcp6_ifdelegateaddr(ifd, ap, + NULL, ia, ifp)) + k++; + } + for (j = 0; j < ia->sla_len; j++) { + sla = &ia->sla[j]; + if (sla->sla_set && sla->sla == 0) + ap->flags |= + IPV6_AF_DELEGATEDZERO; + if (strcmp(ifd->name, sla->ifname)) + continue; + if (ifd->carrier != LINK_UP) { + logger(ifp->ctx, LOG_DEBUG, + "%s: has no carrier, cannot" + " delegate addresses", + ifd->name); + carrier_warned = 1; + break; + } + if (dhcp6_ifdelegateaddr(ifd, ap, + sla, ia, ifp)) + k++; + } + if (carrier_warned ||abrt) + break; + } + if (carrier_warned || abrt) + break; + } + if (k && !carrier_warned) { + ifd_state = D6_STATE(ifd); + ipv6_addaddrs(&ifd_state->addrs); + if_initrt6(ifd); + dhcp6_script_try_run(ifd, 1); + } + } +} + +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 (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) { + logger(ifp->ctx, LOG_DEBUG, + "%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, ifd)) + k++; + } + } + } + } + + if (k) { + logger(ifp->ctx, LOG_INFO, + "%s: adding delegated prefixes", ifp->name); + state = D6_STATE(ifp); + state->state = DH6S_DELEGATED; + ipv6_addaddrs(&state->addrs); + if_initrt6(ifp); + ipv6_buildroutes(ifp->ctx); + dhcp6_script_try_run(ifp, 1); + } + return k; +} + +static struct interface * +dhcp6_findpfxdlgif(struct interface *ifp) +{ + struct interface *ifn; + + if (ifp->options && ifp->options->options & DHCPCD_PFXDLGONLY) + return NULL; + + if (ifp->ctx && ifp->ctx->ifaces) { + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (strcmp(ifn->name, ifp->name) == 0 && + ifn->options->options & DHCPCD_PFXDLGONLY) + return ifn; + } + } + return NULL; +} + +/* ARGSUSED */ +static void +dhcp6_handledata(void *arg) +{ + struct dhcpcd_ctx *dctx; + struct ipv6_ctx *ctx; + size_t i, len; + ssize_t bytes; + struct cmsghdr *cm; + struct in6_pktinfo pkt; + struct interface *ifp, *ifpx; + const char *op; + struct dhcp6_message *r; + struct dhcp6_state *state; + const struct dhcp6_option *o, *auth; + const struct dhcp_opt *opt; + const struct if_options *ifo; + struct ipv6_addr *ap; + uint8_t has_new; + int error; + uint32_t u32; + + dctx = arg; + ctx = dctx->ipv6; + ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); + bytes = recvmsg(ctx->dhcp_fd, &ctx->rcvhdr, 0); + if (bytes == -1) { + logger(dctx, LOG_ERR, "%s: recvmsg: %m", __func__); + close(ctx->dhcp_fd); + eloop_event_delete(dctx->eloop, ctx->dhcp_fd, 0); + ctx->dhcp_fd = -1; + 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)) { + logger(dctx, LOG_ERR, + "DHCPv6 packet too short from %s", ctx->sfrom); + return; + } + + pkt.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(pkt))) + memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt)); + break; + } + } + if (pkt.ipi6_ifindex == 0) { + logger(dctx, LOG_ERR, + "DHCPv6 reply did not contain index from %s", ctx->sfrom); + return; + } + + TAILQ_FOREACH(ifp, dctx->ifaces, next) { + /* Ensure we work on the master interface */ + if (ifp->index == (unsigned int)pkt.ipi6_ifindex && + !(ifp->options->options & DHCPCD_PFXDLGONLY)) + break; + } + if (ifp == NULL) { + logger(dctx, LOG_DEBUG, + "DHCPv6 reply for unexpected interface from %s", + ctx->sfrom); + return; + } + + r = (struct dhcp6_message *)ctx->rcvhdr.msg_iov[0].iov_base; + + /* Which interface state is the IAID for? */ + ifpx = dhcp6_findpfxdlgif(ifp); + if (ifpx && D6_STATE(ifpx)) { + state = D6_STATE(ifpx); + if (r->xid[0] == state->send->xid[0] && + r->xid[1] == state->send->xid[1] && + r->xid[2] == state->send->xid[2]) + ifp = ifpx; + } + + state = D6_STATE(ifp); + if (state == NULL || state->send == NULL) { + logger(ifp->ctx, LOG_DEBUG, + "%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)) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: DHCPv6 reply received but already bound", ifp->name); + return; + } + + if (r->type != DHCP6_RECONFIGURE && + (r->xid[0] != state->send->xid[0] || + r->xid[1] != state->send->xid[1] || + r->xid[2] != state->send->xid[2])) + { + logger(dctx, LOG_DEBUG, + "%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; + } + + if (dhcp6_getmoption(D6_OPTION_SERVERID, r, len) == NULL) { + logger(ifp->ctx, LOG_DEBUG, "%s: no DHCPv6 server ID from %s", + ifp->name, ctx->sfrom); + return; + } + + o = dhcp6_getmoption(D6_OPTION_CLIENTID, r, len); + if (o == NULL || ntohs(o->len) != dctx->duid_len || + memcmp(D6_COPTION_DATA(o), dctx->duid, dctx->duid_len) != 0) + { + logger(ifp->ctx, LOG_DEBUG, "%s: incorrect client ID from %s", + ifp->name, ctx->sfrom); + return; + } + + ifo = ifp->options; + for (i = 0, opt = dctx->dhcp6_opts; + i < dctx->dhcp6_opts_len; + i++, opt++) + { + if (has_option_mask(ifo->requiremask6, opt->option) && + dhcp6_getmoption((uint16_t)opt->option, r, len) == NULL) + { + logger(ifp->ctx, LOG_WARNING, + "%s: reject DHCPv6 (no option %s) from %s", + ifp->name, opt->var, ctx->sfrom); + return; + } + if (has_option_mask(ifo->rejectmask6, opt->option) && + dhcp6_getmoption((uint16_t)opt->option, r, len)) + { + logger(ifp->ctx, LOG_WARNING, + "%s: reject DHCPv6 (option %s) from %s", + ifp->name, opt->var, ctx->sfrom); + return; + } + } + + /* Authenticate the message */ + auth = dhcp6_getmoption(D6_OPTION_AUTH, r, len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifo->auth, + (uint8_t *)r, len, 6, r->type, + D6_COPTION_DATA(auth), ntohs(auth->len)) == NULL) + { + logger(ifp->ctx, LOG_DEBUG, "dhcp_auth_validate: %m"); + logger(ifp->ctx, LOG_ERR, + "%s: authentication failed from %s", + ifp->name, ctx->sfrom); + return; + } + if (state->auth.token) + logger(ifp->ctx, LOG_DEBUG, + "%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + logger(ifp->ctx, LOG_DEBUG, + "%s: accepted reconfigure key", ifp->name); + } else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { + logger(ifp->ctx, LOG_ERR, + "%s: no authentication from %s", ifp->name, ctx->sfrom); + return; + } else if (ifo->auth.options & DHCPCD_AUTH_SEND) + logger(ifp->ctx, LOG_WARNING, + "%s: no authentication from %s", ifp->name, ctx->sfrom); + + op = dhcp6_get_op(r->type); + switch(r->type) { + case DHCP6_REPLY: + switch(state->state) { + case DH6S_INFORM: + if (dhcp6_checkstatusok(ifp, r, NULL, len) == -1) + return; + /* RFC4242 */ + o = dhcp6_getmoption(D6_OPTION_INFO_REFRESH_TIME, + r, len); + if (o == NULL || ntohs(o->len) != sizeof(u32)) + state->renew = IRT_DEFAULT; + else { + memcpy(&u32, D6_COPTION_DATA(o), sizeof(u32)); + state->renew = ntohl(u32); + if (state->renew < IRT_MINIMUM) + state->renew = IRT_MINIMUM; + } + break; + case DH6S_CONFIRM: + error = dhcp6_checkstatusok(ifp, r, NULL, len); + /* If we got an OK status the chances are that we + * didn't get the IA's returned, so preserve them + * from our saved response */ + if (error == 1) + goto recv; + if (error == -1 || + dhcp6_validatelease(ifp, r, len, + ctx->sfrom, NULL) == -1) + { + dhcp6_startdiscover(ifp); + return; + } + break; + case DH6S_DISCOVER: + if (has_option_mask(ifo->requestmask6, + D6_OPTION_RAPID_COMMIT) && + dhcp6_getmoption(D6_OPTION_RAPID_COMMIT, r, len)) + state->state = DH6S_REQUEST; + else + op = NULL; + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_RENEW: /* FALLTHROUGH */ + case DH6S_REBIND: + if (dhcp6_validatelease(ifp, r, len, + ctx->sfrom, NULL) == -1) + { + /* PD doesn't use CONFIRM, so REBIND could + * throw up an invalid prefix if we + * changed link */ + if (dhcp6_hasprefixdelegation(ifp)) + dhcp6_startdiscover(ifp); + return; + } + break; + default: + op = NULL; + } + break; + case DHCP6_ADVERTISE: + if (state->state != DH6S_DISCOVER) { + op = NULL; + break; + } + /* RFC7083 */ + o = dhcp6_getmoption(D6_OPTION_SOL_MAX_RT, r, len); + if (o && ntohs(o->len) >= sizeof(u32)) { + memcpy(&u32, D6_COPTION_DATA(o), sizeof(u32)); + u32 = ntohl(u32); + if (u32 >= 60 && u32 <= 86400) { + logger(ifp->ctx, LOG_DEBUG, + "%s: SOL_MAX_RT %llu -> %d", ifp->name, + (unsigned long long)state->sol_max_rt, u32); + state->sol_max_rt = (time_t)u32; + } else + logger(ifp->ctx, LOG_ERR, + "%s: invalid SOL_MAX_RT %d", + ifp->name, u32); + } + o = dhcp6_getmoption(D6_OPTION_INF_MAX_RT, r, len); + if (o && ntohs(o->len) >= sizeof(u32)) { + memcpy(&u32, D6_COPTION_DATA(o), sizeof(u32)); + u32 = ntohl(u32); + if (u32 >= 60 && u32 <= 86400) { + logger(ifp->ctx, LOG_DEBUG, + "%s: INF_MAX_RT %llu -> %d", + ifp->name, + (unsigned long long)state->inf_max_rt, u32); + state->inf_max_rt = (time_t)u32; + } else + logger(ifp->ctx, LOG_ERR, + "%s: invalid INF_MAX_RT %d", + ifp->name, u32); + } + if (dhcp6_validatelease(ifp, r, len, ctx->sfrom, NULL) == -1) + return; + break; + case DHCP6_RECONFIGURE: + if (auth == NULL) { + logger(ifp->ctx, LOG_ERR, + "%s: unauthenticated %s from %s", + ifp->name, op, ctx->sfrom); + return; + } + logger(ifp->ctx, LOG_INFO, "%s: %s from %s", + ifp->name, op, ctx->sfrom); + o = dhcp6_getmoption(D6_OPTION_RECONF_MSG, r, len); + if (o == NULL) { + logger(ifp->ctx, LOG_ERR, + "%s: missing Reconfigure Message option", + ifp->name); + return; + } + if (ntohs(o->len) != 1) { + logger(ifp->ctx, LOG_ERR, + "%s: missing Reconfigure Message type", ifp->name); + return; + } + switch(*D6_COPTION_DATA(o)) { + case DHCP6_RENEW: + if (state->state != DH6S_BOUND) { + logger(ifp->ctx, LOG_ERR, + "%s: not bound, ignoring %s", + ifp->name, op); + return; + } + eloop_timeout_delete(ifp->ctx->eloop, + dhcp6_startrenew, ifp); + dhcp6_startrenew(ifp); + break; + case DHCP6_INFORMATION_REQ: + if (state->state != DH6S_INFORMED) { + logger(ifp->ctx, LOG_ERR, + "%s: not informed, ignoring %s", + ifp->name, op); + return; + } + eloop_timeout_delete(ifp->ctx->eloop, + dhcp6_sendinform, ifp); + dhcp6_startinform(ifp); + break; + default: + logger(ifp->ctx, LOG_ERR, + "%s: unsupported %s type %d", + ifp->name, op, *D6_COPTION_DATA(o)); + break; + } + return; + default: + logger(ifp->ctx, LOG_ERR, "%s: invalid DHCP6 type %s (%d)", + ifp->name, op, r->type); + return; + } + if (op == NULL) { + logger(ifp->ctx, LOG_WARNING, + "%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) { + logger(ifp->ctx, LOG_ERR, + "%s: malloc recv: %m", ifp->name); + return; + } + } + memcpy(state->recv, r, len); + state->recv_len = len; + + switch(r->type) { + case DHCP6_ADVERTISE: + if (state->state == DH6S_REQUEST) /* rapid commit */ + break; + ap = TAILQ_FIRST(&state->addrs); + logger(ifp->ctx, LOG_INFO, "%s: ADV %s from %s", + ifp->name, ap->saddr, ctx->sfrom); + if (ifp->ctx->options & DHCPCD_TEST) + break; + dhcp6_startrequest(ifp); + return; + } + +recv: + has_new = 0; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->flags & IPV6_AF_NEW) { + has_new = 1; + break; + } + } + logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG, + "%s: %s received from %s", ifp->name, op, ctx->sfrom); + + state->reason = NULL; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + switch(state->state) { + case DH6S_INFORM: + state->rebind = 0; + state->expire = ND6_INFINITE_LIFETIME; + state->lowpl = ND6_INFINITE_LIFETIME; + state->reason = "INFORM6"; + break; + case DH6S_REQUEST: + if (state->reason == NULL) + state->reason = "BOUND6"; + /* FALLTHROUGH */ + case DH6S_RENEW: + if (state->reason == NULL) + state->reason = "RENEW6"; + /* FALLTHROUGH */ + case DH6S_REBIND: + if (state->reason == NULL) + state->reason = "REBIND6"; + /* FALLTHROUGH */ + case DH6S_CONFIRM: + if (state->reason == NULL) + state->reason = "REBOOT6"; + if (state->renew == 0) { + if (state->expire == ND6_INFINITE_LIFETIME) + state->renew = ND6_INFINITE_LIFETIME; + else if (state->lowpl != ND6_INFINITE_LIFETIME) + state->renew = (uint32_t)(state->lowpl * 0.5); + } + if (state->rebind == 0) { + if (state->expire == ND6_INFINITE_LIFETIME) + state->rebind = ND6_INFINITE_LIFETIME; + else if (state->lowpl != ND6_INFINITE_LIFETIME) + state->rebind = (uint32_t)(state->lowpl * 0.8); + } + break; + default: + state->reason = "UNKNOWN6"; + break; + } + + if (state->state != DH6S_CONFIRM) { + 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; + } + + if (ifp->ctx->options & DHCPCD_TEST) + script_runreason(ifp, "TEST"); + else { + if (state->state == DH6S_INFORM) + state->state = DH6S_INFORMED; + else + state->state = DH6S_BOUND; + 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); + + ipv6nd_runignoredra(ifp); + ipv6_addaddrs(&state->addrs); + dhcp6_delegate_prefix(ifp); + + if (state->state == DH6S_INFORMED) + logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG, + "%s: refresh in %"PRIu32" seconds", + ifp->name, state->renew); + else if (state->renew || state->rebind) + logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG, + "%s: renew in %"PRIu32" seconds," + " rebind in %"PRIu32" seconds", + ifp->name, state->renew, state->rebind); + else if (state->expire == 0) + logger(ifp->ctx, has_new ? LOG_INFO : LOG_DEBUG, + "%s: will expire", ifp->name); + if_initrt6(ifp); + ipv6_buildroutes(ifp->ctx); + dhcp6_writelease(ifp); + 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 int +dhcp6_open(struct dhcpcd_ctx *dctx) +{ + struct ipv6_ctx *ctx; + struct sockaddr_in6 sa; + int n; + + 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 + + ctx = dctx->ipv6; +#ifdef SOCK_CLOEXEC + ctx->dhcp_fd = socket(PF_INET6, + SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + IPPROTO_UDP); + if (ctx->dhcp_fd == -1) + return -1; +#else + if ((ctx->dhcp_fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + if ((n = fcntl(ctx->dhcp_fd, F_GETFD, 0)) == -1 || + fcntl(ctx->dhcp_fd, F_SETFD, n | FD_CLOEXEC) == -1) + { + close(ctx->dhcp_fd); + ctx->dhcp_fd = -1; + return -1; + } + if ((n = fcntl(ctx->dhcp_fd, F_GETFL, 0)) == -1 || + fcntl(ctx->dhcp_fd, F_SETFL, n | O_NONBLOCK) == -1) + { + close(ctx->dhcp_fd); + ctx->dhcp_fd = -1; + return -1; + } +#endif + + n = 1; + if (setsockopt(ctx->dhcp_fd, SOL_SOCKET, SO_REUSEADDR, + &n, sizeof(n)) == -1) + goto errexit; + + n = 1; + if (setsockopt(ctx->dhcp_fd, SOL_SOCKET, SO_BROADCAST, + &n, sizeof(n)) == -1) + goto errexit; + +#ifdef SO_REUSEPORT + n = 1; + if (setsockopt(ctx->dhcp_fd, SOL_SOCKET, SO_REUSEPORT, + &n, sizeof(n)) == -1) + logger(dctx, LOG_WARNING, "setsockopt: SO_REUSEPORT: %m"); +#endif + + if (bind(ctx->dhcp_fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) + goto errexit; + + n = 1; + if (setsockopt(ctx->dhcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &n, sizeof(n)) == -1) + goto errexit; + + eloop_event_add(dctx->eloop, ctx->dhcp_fd, + dhcp6_handledata, dctx, NULL, NULL); + return 0; + +errexit: + close(ctx->dhcp_fd); + ctx->dhcp_fd = -1; + return -1; +} + +static void +dhcp6_start1(void *arg) +{ + struct interface *ifp = arg; + struct if_options *ifo = ifp->options; + struct dhcp6_state *state; + size_t i; + const struct dhcp_compat *dhc; + + state = D6_STATE(ifp); + /* Match any DHCPv4 opton to DHCPv6 options if given for easy + * configuration */ + 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); + } + + /* Rapid commit won't wor with Prefix Delegation Exclusion */ + if (dhcp6_findselfsla(ifp, NULL)) + del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT); + + /* Create a 2nd interface to handle the PD state */ + if (!(ifo->options & (DHCPCD_PFXDLGONLY | DHCPCD_PFXDLGMIX)) && + dhcp6_hasprefixdelegation(ifp) > 1) + { + const char * const argv[] = { ifp->name }; + struct if_head *ifs; + struct interface *ifn; + + ifn = dhcp6_findpfxdlgif(ifp); + if (ifn == NULL) { + ifs = if_discover(ifp->ctx, -1, UNCONST(argv)); + if (ifs) { + ifn = TAILQ_FIRST(ifs); + if (ifn) { + logger(ifp->ctx, LOG_INFO, + "%s: creating pseudo interface" + " to handle Prefix Delegation", + ifp->name); + ifp->options->options |= + DHCPCD_NOPFXDLG; + TAILQ_REMOVE(ifs, ifn, next); + TAILQ_INSERT_AFTER(ifp->ctx->ifaces, + ifp, ifn, next); + dhcpcd_initstate(ifn, + DHCPCD_PFXDLGONLY); + eloop_timeout_add_sec(ifp->ctx->eloop, + 0, dhcpcd_startinterface, ifn); + } + while ((ifn = TAILQ_FIRST(ifs))) { + TAILQ_REMOVE(ifs, ifn, next); + if_free(ifn); + } + free(ifs); + } + } + } + + 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); + } +} + +int +dhcp6_start(struct interface *ifp, enum DH6S init_state) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + if (state) { + if (state->state == DH6S_INFORMED && + init_state == DH6S_INFORM) + { + dhcp6_startinform(ifp); + return 0; + } + if (init_state == DH6S_INIT && + ifp->options->options & DHCPCD_DHCP6 && + (state->state == DH6S_INFORM || + state->state == DH6S_INFORMED || + state->state == DH6S_DELEGATED)) + { + /* Change from stateless to stateful */ + goto gogogo; + } + /* We're already running DHCP6 */ + /* XXX: What if the managed flag vanishes from all RA? */ + return 0; + } + + if (!(ifp->options->options & DHCPCD_DHCP6)) + return 0; + + if (ifp->ctx->ipv6->dhcp_fd == -1 && dhcp6_open(ifp->ctx) == -1) + return -1; + + 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, + ifp->options->options & DHCPCD_PFXDLGONLY ? ".pd" : ""); + if (ipv6_linklocal(ifp) == NULL) { + logger(ifp->ctx, LOG_DEBUG, + "%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 interface *ifpx; + struct dhcp6_state *state; + struct dhcpcd_ctx *ctx; + unsigned long long options; + int dropdele; + + /* + * As the interface is going away from dhcpcd we need to + * remove the delegated addresses, otherwise we lose track + * of which interface is delegating as we remeber it by pointer. + * So if we need to change this behaviour, we need to change + * how we remember which interface delegated. + * + * XXX The below is no longer true due to the change of the + * default IAID, but do PPP links have stable ethernet + * addresses? + * + * To make it more interesting, on some OS's with PPP links + * there is no guarantee the delegating interface will have + * the same name or index so think very hard before changing + * this. + */ + if (ifp->options) + options = ifp->options->options; + else + options = 0; + dropdele = (options & (DHCPCD_STOPPING | DHCPCD_RELEASE) && + (options & + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)); + + ifpx = dhcp6_findpfxdlgif(ifp); + if (ifpx) { + if (options & DHCPCD_EXITING) + ifpx->options->options |= DHCPCD_EXITING; + dhcp6_freedrop(ifpx, dropdele ? 1 : drop, reason); + TAILQ_REMOVE(ifp->ctx->ifaces, ifpx, next); + if_free(ifpx); + } + + if (ifp->ctx->eloop) + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + + if (dropdele) + dhcp6_delete_delegates(ifp); + + state = D6_STATE(ifp); + if (state) { + dhcp_auth_reset(&state->auth); + if (drop && options & DHCPCD_RELEASE) { + if (ifp->carrier == LINK_UP) + dhcp6_startrelease(ifp); + 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_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + { + 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->ipv6) { + if (ctx->ipv6->dhcp_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->ipv6->dhcp_fd, 0); + close(ctx->ipv6->dhcp_fd); + ctx->ipv6->dhcp_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_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname, + const struct in6_addr *addr, int flags) +{ + struct interface *ifp; + struct dhcp6_state *state; + + if (ctx->ifaces == NULL) + return; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = D6_STATE(ifp); + if (state == NULL || strcmp(ifp->name, ifname)) + continue; + ipv6_handleifa_addrs(cmd, &state->addrs, addr, flags); + } + +} + +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 struct dhcp6_option *o; + size_t i, n; + uint16_t ol, oc; + char *pfx; + uint32_t en; + const struct dhcpcd_ctx *ctx; + const struct dhcp6_state *state; + const struct ipv6_addr *ap; + char *v, *val; + + 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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __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. */ + for (o = D6_CFIRST_OPTION(m); + len > (ssize_t)sizeof(*o); + o = D6_CNEXT_OPTION(o)) + { + ol = ntohs(o->len); + if (sizeof(*o) + ol > len) { + errno = EINVAL; + break; + } + len -= sizeof(*o) + ol; + oc = ntohs(o->code); + if (has_option_mask(ifo->nomask6, oc)) + continue; + for (i = 0, opt = ifo->dhcp6_override; + i < ifo->dhcp6_override_len; + i++, opt++) + if (opt->option == oc) + break; + if (i == ifo->dhcp6_override_len && + oc == D6_OPTION_VENDOR_OPTS && + ol > sizeof(en)) + { + memcpy(&en, D6_COPTION_DATA(o), 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 == oc) + 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, D6_COPTION_DATA(o), ol); + } + if (vo) { + n += dhcp_envoption(ifp->ctx, + env == NULL ? NULL : &env[n], + pfx, ifp->name, + vo, dhcp6_getoption, + D6_COPTION_DATA(o) + sizeof(en), + ol - sizeof(en)); + } + } + free(pfx); + +delegated: + /* Needed for Delegated Prefixes */ + state = D6_CSTATE(ifp); + i = 0; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->delegating_iface) { + i += strlen(ap->saddr) + 1; + } + } + if (env && i) { + i += strlen(prefix) + strlen("_delegated_dhcp6_prefix="); + v = val = env[n] = malloc(i); + if (v == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + v += snprintf(val, i, "%s_delegated_dhcp6_prefix=", prefix); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->delegating_iface) { + /* 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++; + + 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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + TAILQ_INIT(&state->addrs); + dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), + AF_INET6, ifp, + ifp->options->options & DHCPCD_PFXDLGONLY ? ".pd" : ""); + if (dhcp6_readlease(ifp, 0) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", + *ifp->name ? ifp->name : state->leasefile, __func__); + return -1; + } + state->reason = "DUMP6"; + return script_runreason(ifp, state->reason); +} @@ -0,0 +1,261 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCP6_H +#define DHCP6_H + +#include "dhcpcd.h" + +#define IN6ADDR_LINKLOCAL_ALLDHCP_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 }}} + +/* UDP port numbers for DHCP */ +#define DHCP6_CLIENT_PORT 546 +#define DHCP6_SERVER_PORT 547 + +/* DHCP message type */ +#define DHCP6_SOLICIT 1 +#define DHCP6_ADVERTISE 2 +#define DHCP6_REQUEST 3 +#define DHCP6_CONFIRM 4 +#define DHCP6_RENEW 5 +#define DHCP6_REBIND 6 +#define DHCP6_REPLY 7 +#define DHCP6_RELEASE 8 +#define DHCP6_DECLINE 9 +#define DHCP6_RECONFIGURE 10 +#define DHCP6_INFORMATION_REQ 11 +#define DHCP6_RELAY_FLOW 12 +#define DHCP6_RELAY_REPL 13 +#define DHCP6_RECONFIGURE_REQ 18 +#define DHCP6_RECONFIGURE_REPLY 19 + +#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_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_FQDN_PTR 0x00 +#define D6_FQDN_BOTH 0x01 +#define D6_FQDN_NONE 0x04 + +#include "dhcp.h" +#include "ipv6.h" + +struct dhcp6_message { + uint8_t type; + uint8_t xid[3]; + /* followed by options */ +} __packed; + +struct dhcp6_option { + uint16_t code; + uint16_t len; + /* followed by data */ +} __packed; + +#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 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_RD 10 +#define REN_TIMEOUT 10 +#define REN_MAX_RT 600 +#define REB_TIMEOUT 10 +#define REB_MAX_RT 600 +#define INF_MAX_DELAY 1 +#define INF_TIMEOUT 1 +#define INF_MAX_RT 3600 /* RFC7083 */ +#define REL_TIMEOUT 1 +#define REL_MAX_RC 5 +#define DEC_TIMEOUT 1 +#define DEC_MAX_RC 5 +#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_RELEASE +}; + +struct dhcp6_state { + enum DH6S state; + time_t start_uptime; + + /* 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; + + 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_SSIDSIZE * 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))) + +#define D6_FIRST_OPTION(m) \ + ((struct dhcp6_option *) \ + ((uint8_t *)(m) + sizeof(struct dhcp6_message))) +#define D6_NEXT_OPTION(o) \ + ((struct dhcp6_option *) \ + (((uint8_t *)o) + sizeof(struct dhcp6_option) + ntohs((o)->len))) +#define D6_OPTION_DATA(o) \ + ((uint8_t *)(o) + sizeof(struct dhcp6_option)) +#define D6_CFIRST_OPTION(m) \ + ((const struct dhcp6_option *) \ + ((const uint8_t *)(m) + sizeof(struct dhcp6_message))) +#define D6_CNEXT_OPTION(o) \ + ((const struct dhcp6_option *) \ + (((const uint8_t *)o) + sizeof(struct dhcp6_option) + ntohs((o)->len))) +#define D6_COPTION_DATA(o) \ + ((const uint8_t *)(o) + sizeof(struct dhcp6_option)) + +#ifdef INET6 +void dhcp6_printoptions(const struct dhcpcd_ctx *, + const struct dhcp_opt *, size_t); +struct ipv6_addr *dhcp6_findaddr(struct dhcpcd_ctx *, const struct in6_addr *, + short); +size_t dhcp6_find_delegates(struct interface *); +int dhcp6_has_public_addr(const struct interface *); +int dhcp6_start(struct interface *, enum DH6S); +void dhcp6_reboot(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(struct dhcpcd_ctx *, int, const char *, + const struct in6_addr *addr, int); +int dhcp6_dadcompleted(const struct interface *); +void dhcp6_drop(struct interface *, const char *); +int dhcp6_dump(struct interface *); +#else +#define dhcp6_findaddr(a, b, c) (0) +#define dhcp6_find_delegates(a) {} +#define dhcp6_start(a, b) (0) +#define dhcp6_reboot(a) {} +#define dhcp6_env(a, b, c, d, e) {} +#define dhcp6_free(a) {} +#define dhcp6_dadcompleted(a) (0) +#define dhcp6_drop(a, b) {} +#define dhcp6_dump(a) (-1) +#endif + +#endif diff --git a/dhcpcd-definitions.conf b/dhcpcd-definitions.conf new file mode 100644 index 0000000..58eba07 --- /dev/null +++ b/dhcpcd-definitions.conf @@ -0,0 +1,530 @@ +# Copyright (c) 2006-2015 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 int16 max_dgram_reassembly +define 23 uint16 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 binhex 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 byte flags +embed byte rcode1 +embed byte rcode2 +embed domain fqdn + +# Option 82 is for Relay Agents and DHCP servers + +# Options 83 ad 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 + +# 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 rfc5969 sixrd + +# DHCP Access Network Domain Name, RFC5986 +define 213 domain access_domain + +# Options 214-219 are unused, RFC3942 + +# DHCP Subnet Allocation, RFC6656 +# Option 220 looks specific to Cisco hardware. + +# DHCP Virtual Subnet Selection, RFC6607 +define 221 encap vss +encap 0 string nvt +encap 1 binhex vpn_id +encap 255 flag global + +# Options 222 and 223 are unused, RFC3942 +# Options 224-254 are reserved for Private Use +# Option 255 End + +############################################################################## +# 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 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 byte flags +embed 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 Address Selection Policy +# Currently not supported + +# Options 86-65535 are unasssinged + +############################################################################## +# Vendor-Identifying Vendor Options +# An example: +#vendopt 12345 encap frobozzco +#encap 1 string maze_location +#encap 2 byte grue_probability diff --git a/dhcpcd-embedded.c.in b/dhcpcd-embedded.c.in new file mode 100644 index 0000000..fcaa629 --- /dev/null +++ b/dhcpcd-embedded.c.in @@ -0,0 +1,36 @@ +/* + * DO NOT EDIT! + * Automatically generated from dhcpcd-embedded.conf + * Ths allows us to simply generate DHCP structure without any C programming. + */ + +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <unistd.h> + +const char * const dhcpcd_embedded_conf[] = { diff --git a/dhcpcd-embedded.h.in b/dhcpcd-embedded.h.in new file mode 100644 index 0000000..3d15316 --- /dev/null +++ b/dhcpcd-embedded.h.in @@ -0,0 +1,31 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define INITDEFINES @INITDEFINES@ +#define INITDEFINE6S @INITDEFINE6S@ + +extern const char * const dhcpcd_embedded_conf[]; diff --git a/dhcpcd-hooks/01-test b/dhcpcd-hooks/01-test new file mode 100644 index 0000000..261f7ca --- /dev/null +++ b/dhcpcd-hooks/01-test @@ -0,0 +1,8 @@ +# Just echo our DHCP options we have + +if [ "$reason" = "TEST" ]; then + set | grep "^\(interface\|pid\|reason\|profile\|skip_hooks\)=" | sort + set | grep "^if\(carrier\|flags\|mtu\|wireless\|ssid\)=" | sort + set | grep "^\(new_\|old_\|ra_count=\|ra[0-9]*_\)" | sort + exit 0 +fi diff --git a/dhcpcd-hooks/02-dump b/dhcpcd-hooks/02-dump new file mode 100644 index 0000000..0d515f7 --- /dev/null +++ b/dhcpcd-hooks/02-dump @@ -0,0 +1,8 @@ +# Just echo our DHCP options we have + +case "$reason" in +DUMP|DUMP6) + set | sed -ne 's/^new_//p' | sort + exit 0 + ;; +esac diff --git a/dhcpcd-hooks/10-mtu b/dhcpcd-hooks/10-mtu new file mode 100644 index 0000000..f8657cb --- /dev/null +++ b/dhcpcd-hooks/10-mtu @@ -0,0 +1,38 @@ +# Configure the MTU for the interface + +mtu_dir="$state_dir/mtu" + +set_mtu() +{ + local mtu=$1 + + if [ -w /sys/class/net/$interface/mtu ]; then + echo "$mtu" >/sys/class/net/$interface/mtu + else + ifconfig "$interface" mtu "$mtu" + fi +} + +if [ "$reason" = PREINIT -a -e "$mtu_dir/$interface" ]; then + rm "$mtu_dir/$interface" +elif [ -n "$new_interface_mtu" ] && $if_up; then + # The smalled MTU dhcpcd can work with is 576 + if [ "$new_interface_mtu" -ge 576 ]; then + if set_mtu "$new_interface_mtu"; then + syslog info "MTU set to $new_interface_mtu" + # Save the MTU so we can restore it later + if [ ! -e "$mtu_dir/$interface" ]; then + mkdir -p "$mtu_dir" + echo "$ifmtu" > "$mtu_dir/$interface" + fi + fi + fi +elif [ -e "$mtu_dir/$interface" ]; then + if $if_up || $if_down; then + # No MTU in this state, so restore the prior MTU + mtu=$(cat "$mtu_dir/$interface") + syslog info "MTU restored to $mtu" + set_mtu "$mtu" + rm "$mtu_dir/$interface" + fi +fi diff --git a/dhcpcd-hooks/10-wpa_supplicant b/dhcpcd-hooks/10-wpa_supplicant new file mode 100644 index 0000000..9eec0c1 --- /dev/null +++ b/dhcpcd-hooks/10-wpa_supplicant @@ -0,0 +1,119 @@ +# 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() +{ + local dir + + 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() +{ + local dir err errn + + # 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() +{ + local dir err errn + + 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() +{ + local dir err errn + + 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 diff --git a/dhcpcd-hooks/15-timezone b/dhcpcd-hooks/15-timezone new file mode 100644 index 0000000..0ccdc45 --- /dev/null +++ b/dhcpcd-hooks/15-timezone @@ -0,0 +1,48 @@ +# Configure timezone + +: ${localtime:=/etc/localtime} + +set_zoneinfo() +{ + local zoneinfo_dir= zone_file= + + [ -z "$new_tzdb_timezone" ] && return 0 + + 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 diff --git a/dhcpcd-hooks/20-resolv.conf b/dhcpcd-hooks/20-resolv.conf new file mode 100644 index 0000000..c9f7bed --- /dev/null +++ b/dhcpcd-hooks/20-resolv.conf @@ -0,0 +1,164 @@ +# 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=" +" + +build_resolv_conf() +{ + local cf="$state_dir/resolv.conf.$ifname" + local interfaces= header= search= srvs= servers= x= + + # 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" +} + +add_resolv_conf() +{ + local x= conf="$signature$NL" i=${ra_count:-0} ra= warn=true + + while [ $i -ne 0 ]; do + eval ra=\$ra${i}_rdnss + new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$ra" + eval ra=\$ra${i}_dnssl + new_domain_search="$new_domain_search${new_domain_search:+ }$ra" + i=$(($i - 1)) + done + + # If we don't have any configuration, remove it + if [ -z "$new_domain_name_servers" -a \ + -z "$new_domain_name" -a \ + -z "$new_domain_search" ]; then + remove_resolv_conf + return $? + fi + + # 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 [ -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 diff --git a/dhcpcd-hooks/29-lookup-hostname b/dhcpcd-hooks/29-lookup-hostname new file mode 100644 index 0000000..04ad275 --- /dev/null +++ b/dhcpcd-hooks/29-lookup-hostname @@ -0,0 +1,40 @@ +# Lookup the hostname in DNS if not set + +lookup_hostname() +{ + [ -z "$new_ip_address" ] && return 1 + local h= + # 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" -a -z "$new_fqdn_name" ]; then + export new_host_name="$(lookup_hostname)" + fi +} + +if $if_up; then + set_hostname +fi diff --git a/dhcpcd-hooks/30-hostname b/dhcpcd-hooks/30-hostname new file mode 100644 index 0000000..616fb82 --- /dev/null +++ b/dhcpcd-hooks/30-hostname @@ -0,0 +1,154 @@ +# 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} + +# Some systems don't have hostname(1) +_hostname() +{ + local name= + + if [ -z "$1" ]; then + if type hostname >/dev/null 2>&1; then + hostname + elif [ -r /proc/sys/kernel/hostname ]; then + read name </proc/sys/kernel/hostname && echo "$name" + elif sysctl kern.hostname >/dev/null 2>&1; then + sysctl -n kern.hostname + elif sysctl kernel.hostname >/dev/null 2>&1; then + sysctl -n kernel.hostname + else + return 1 + fi + return $? + fi + + # 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 +} + +need_hostname() +{ + local hostname hfqdn=false hshort=false + + case "$force_hostname" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;; + esac + + hostname="$(_hostname)" + case "$hostname" in + ""|"(none)"|localhost|localhost.localdomain) return 0;; + esac + + case "$hostname_fqdn" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; + [Ss][Ee][Rr][Vv][Ee][Rr]) ;; + *) hshort=true;; + esac + + 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" -a \ + "$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() +{ + + if valid_domainname "$1"; then + _hostname "$1" + else + syslog err "Invalid hostname: $1" + fi +} + +set_hostname() +{ + local hfqdn=false hshort=false + + need_hostname || return + + case "$hostname_fqdn" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; + "") ;; + *) hshort=true;; + esac + + 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" -a \ + "$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 + 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" + ;; +esac + +if $if_up; then + set_hostname +fi diff --git a/dhcpcd-hooks/50-dhcpcd-compat b/dhcpcd-hooks/50-dhcpcd-compat new file mode 100644 index 0000000..0d6256e --- /dev/null +++ b/dhcpcd-hooks/50-dhcpcd-compat @@ -0,0 +1,41 @@ +# Compat enter hook shim for older dhcpcd versions + +IPADDR=$new_ip_address +INTERFACE=$interface +NETMASK=$new_subnet_mask +BROADCAST=$new_broadcast_address +NETWORK=$new_network_number +DHCPSID=$new_dhcp_server_identifier +GATEWAYS=$new_routers +DNSSERVERS=$new_domain_name_servers +DNSDOMAIN=$new_domain_name +DNSSEARCH=$new_domain_search +NISDOMAIN=$new_nis_domain +NISSERVERS=$new_nis_servers +NTPSERVERS=$new_ntp_servers + +GATEWAY= +for x in $new_routers; do + GATEWAY="$GATEWAY${GATEWAY:+,}$x" +done +DNS= +for x in $new_domain_name_servers; do + DNS="$DNS${DNS:+,}$x" +done + +r="down" +case "$reason" in +RENEW) r="up";; +BOUND|INFORM|REBIND|REBOOT|TEST|TIMEOUT|IPV4LL) r="new";; +esac + +if [ "$r" != "down" ]; then + rm -f /var/lib/dhcpcd-"$INTERFACE".info + for x in IPADDR INTERFACE NETMASK BROADCAST NETWORK DHCPSID GATEWAYS \ + DNSSERVERS DNSDOMAIN DNSSEARCH NISDOMAIN NISSERVERS \ + NTPSERVERS GATEWAY DNS; do + eval echo "$x=\'\$$x\'" >> /var/lib/dhcpcd-"$INTERFACE".info + done +fi + +set -- /var/lib/dhcpcd-"$INTERFACE".info "$r" diff --git a/dhcpcd-hooks/50-ntp.conf b/dhcpcd-hooks/50-ntp.conf new file mode 100644 index 0000000..551c5be --- /dev/null +++ b/dhcpcd-hooks/50-ntp.conf @@ -0,0 +1,118 @@ +# Sample dhcpcd hook script for ntp +# 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 another file like this +# dhcpcd -e 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 OpenNTPD instead of the default NTP. + +if type invoke-rc.d >/dev/null 2>&1; then + # Debian has a seperate file for DHCP config to avoid stamping on + # the master. + [ -e /var/lib/ntp ] || mkdir /var/lib/ntp + : ${ntp_service:=ntp} + : ${NTP_DHCP_CONF:=/var/lib/ntp/ntp.conf.dhcp} +fi + +: ${ntp_service:=ntpd} +: ${ntp_restart_cmd:=service_condcommand $ntp_service restart} +ntp_conf_dir="$state_dir/ntp.conf" + +# If we have installed OpenNTPD but not NTP then prefer it +# XXX If both exist then update both? +if [ -z "$NTP_CONF" -a -e /etc/ntpd.conf -a ! -e /etc/ntp.conf ]; then + : ${NTP_CONF:=/etc/ntpd.conf} +else + : ${NTP_CONF:=/etc/ntp.conf} +fi + +ntp_conf=${NTP_CONF} +NL=" +" + +build_ntp_conf() +{ + local cf="$state_dir/ntp.conf.$ifname" + local interfaces= header= srvs= servers= x= + + # Build a list of interfaces + interfaces=$(list_interfaces "$ntp_conf_dir") + + 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" -a -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() +{ + local cf="$ntp_conf_dir/$ifname" x= + + [ -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 diff --git a/dhcpcd-hooks/50-yp.conf b/dhcpcd-hooks/50-yp.conf new file mode 100644 index 0000000..2da68eb --- /dev/null +++ b/dhcpcd-hooks/50-yp.conf @@ -0,0 +1,56 @@ +# 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" -a -z "$new_nis_servers" ] && return 0 + local cf=/etc/yp.conf."$ifname" prefix= x= pid= + rm -f "$cf" + echo "$signature" > "$cf" + 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 + local 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 diff --git a/dhcpcd-hooks/50-ypbind.in b/dhcpcd-hooks/50-ypbind.in new file mode 100644 index 0000000..a9ebbfa --- /dev/null +++ b/dhcpcd-hooks/50-ypbind.in @@ -0,0 +1,86 @@ +# 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() +{ + local i= + + 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 + local cf="$ypdomain_dir/$new_nis_domain$ypdomain_suffix" + if [ -n "$new_nis_servers" ]; then + local ncf="$cf.$ifname" x= + 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 + + local nd="$(best_domain)" + if [ $? = 0 -a "$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" + local 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 diff --git a/dhcpcd-hooks/GNUmakefile b/dhcpcd-hooks/GNUmakefile new file mode 100644 index 0000000..1f97d8c --- /dev/null +++ b/dhcpcd-hooks/GNUmakefile @@ -0,0 +1,2 @@ +TOP?= .. +include ${TOP}/GNUmakefile diff --git a/dhcpcd-hooks/Makefile b/dhcpcd-hooks/Makefile new file mode 100644 index 0000000..6328509 --- /dev/null +++ b/dhcpcd-hooks/Makefile @@ -0,0 +1,30 @@ +TOP?= ../ +include ${TOP}/Makefile.inc +include ${TOP}/iconfig.mk + +SCRIPTSDIR= ${LIBEXECDIR}/dhcpcd-hooks +SCRIPTS= 01-test 02-dump +SCRIPTS+= 10-mtu 10-wpa_supplicant 15-timezone 20-resolv.conf +SCRIPTS+= 29-lookup-hostname 30-hostname +SCRIPTS+= ${HOOKSCRIPTS} + +50-ypbind: 50-ypbind.in + ${SED} \ + -e 's:@YPDOMAIN_DIR@:${YPDOMAIN_DIR}:g' \ + -e 's:@YPDOMAIN_SUFFIX@:${YPDOMAIN_SUFFIX}:g' \ + 50-ypbind.in > $@ + +all: ${HOOKSCRIPTS} + +clean: + rm -f 50-ypbind + +proginstall: ${HOOKSCRIPTS} + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR} + +install: proginstall + +import: ${HOOKSCRIPTS} + ${INSTALL} -d /tmp/${DISTPREFIX}/dhcpcd-hooks + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} /tmp/${DISTPREFIX}/dhcpcd-hooks diff --git a/dhcpcd-run-hooks.8.in b/dhcpcd-run-hooks.8.in new file mode 100644 index 0000000..87d85f4 --- /dev/null +++ b/dhcpcd-run-hooks.8.in @@ -0,0 +1,222 @@ +.\" 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. +.\" +.Dd October 3, 2014 +.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 +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 failed to contact any DHCP servers but did obtain an IPV4LL address. +.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 Advertisment, 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 $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 $if_oneup +.Dv true +if any interface is up, otherwise false. +.It Ev $if_ipwaited +.Dv true +if any interface has been assigned an IP address which matches any wait +requirements specified in +.Xr dhcpcd.conf 5 . +.It Ev $profile +the name of the profile selected from +.Xr dhcpcd.conf 5 . +.It Ev $new_delegated_dhcp6_prefix +space separated list of delegated prefixes. +.El +.Sh FILES +When +.Nm +runs, it loads +.Pa @SYSCONFDIR@/dhcpcd.enter-hook +and any scripts found in +.Pa @HOOKDIR@ +in a lexical order and then finally +.Pa @SYSCONFDIR@/dhcpcd.exit-hook +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples Aq Mt roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd +.Sh SECURITY CONSIDERATIONS +.Nm dhcpcd +will validate the content of each option against its encoding. +For string, ascii, raw or binhex encoding it's up to the user to validate it +for the intended purpose. +.Pp +When used in a shell script, each variable must be quoted correctly. diff --git a/dhcpcd-run-hooks.in b/dhcpcd-run-hooks.in new file mode 100644 index 0000000..870e8ce --- /dev/null +++ b/dhcpcd-run-hooks.in @@ -0,0 +1,381 @@ +#!/bin/sh +# dhcpcd client configuration script + +# Handy variables and functions for our hooks to use +case "$reason" in + ROUTERADVERT) + ifsuffix=".ra";; + INFORM6|BOUND6|RENEW6|REBIND6|REBOOT6|EXPIRE6|RELEASE6|STOP6) + ifsuffix=".dhcp6";; + *) + ifsuffix=".dhcp";; +esac +ifname="$interface$ifsuffix${ifclass+.}$ifclass" + +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() +{ + local result= i= + for i do + case " $result " in + *" $i "*);; + *) 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() +{ + local i= x= 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() +{ + local 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() +{ + local key="$1" value= x= line= + + 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() +{ + local m1="$1" m2="$2" x= line= 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" ] || return 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() +{ + local 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 domain name as per RFC1123 with the exception of +# allowing - and _ as they seem to be widely used. +valid_domainname() +{ + local name="$1" label + + [ -z "$name" -o ${#name} -gt 255 ] && return 1 + + while [ -n "$name" ]; do + label="${name%%.*}" + [ -z "$label" -o ${#label} -gt 63 ] && return 1 + case "$label" in + -*|_*|*-|*_) return 1;; + # some sh require - as the first or last character in the class + # when matching it + *[![:alnum:]_-]*) return 1;; + esac + [ "$name" = "${name#*.}" ] && break + name="${name#*.}" + done + return 0 +} + +valid_domainname_list() +{ + local name + + for name do + valid_domainname "$name" || return $? + done + return 0 +} + +# Check for a valid path +valid_path() +{ + + case "$@" in + *[![:alnum:]#%+-_:\.,@~\\/\[\]=\ ]*) return 1;; + esac + 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. + _service_status= + if [ -x /bin/systemctl -a -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 -a -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 -a \ + -s /libexec/rc/init.d/softlevel -o -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 /bin/sv ]; then + _service_exists="/bin/sv status \1 >/dev/null 2>&1" + _service_cmd="/bin/sv \$1 \$2" + elif [ -x /usr/bin/sv ]; then + _service_exists="/usr/bin/sv status \1 >/dev/null 2>&1" + _service_cmd="/usr/bin/sv \$1 \$2" + elif [ -e /etc/slackware-version -a -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 1>/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" + break + fi + done + if [ -e /etc/arch-release ]; then + _service_status="[ -e /var/run/daemons/\$1 ]" + elif [ "$x" = "/etc/rc.d" -a -e /etc/rc.d/rc.subr ]; then + _service_status="$x/\$1 check 1>/dev/null 2>&1" + fi + fi + + _detected_init=true + if [ -z "$_service_cmd" ]; then + syslog err "could not detect a useable init system" + return 1 + fi + return 0 +} + +# Check a system service exists +service_exists() +{ + + if [ -z "$_service_exists" ]; then + detect_init || return 1 + fi + eval $_service_exists +} + +# Send a command to a system service +service_cmd() +{ + + if [ -z "$_service_cmd" ]; then + detect_init || return 1 + fi + eval $_service_cmd +} + +# Send a command to a system service if it is running +service_status() +{ + + if [ -z "$_service_cmd" ]; then + detect_init || return 1 + fi + if [ -n "$_service_status" ]; then + eval $_service_status + else + service_command $1 status >/dev/null 2>&1 + fi +} + +# Handy macros for our hooks +service_command() +{ + + service_exists $1 && service_cmd $1 $2 +} +service_condcommand() +{ + + service_exists $1 && service_status $1 && service_cmd $1 $2 +} + +# We source each script into this one so that scripts run earlier can +# remove variables from the environment so later scripts don't see them. +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + @SYSCONFDIR@/dhcpcd.enter-hook \ + @HOOKDIR@/* \ + @SYSCONFDIR@/dhcpcd.exit-hook +do + for skip in $skip_hooks; do + case "$hook" in + */*~) continue 2;; + */"$skip") continue 2;; + */[0-9][0-9]"-$skip") continue 2;; + */[0-9][0-9]"-$skip.sh") continue 2;; + esac + done + if [ -f "$hook" ]; then + . "$hook" + fi +done diff --git a/dhcpcd.8.in b/dhcpcd.8.in new file mode 100644 index 0000000..a67626a --- /dev/null +++ b/dhcpcd.8.in @@ -0,0 +1,737 @@ +.\" 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. +.\" +.Dd March 17, 2015 +.Dt DHCPCD 8 +.Os +.Sh NAME +.Nm dhcpcd +.Nd a DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl 46ABbDdEGgHJKLMpqTV +.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 +.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 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 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 beings to expire or the DHCP server sends 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 implemenation of the DHCPv6 client as specified in +.Li RFC 3315 . +By default, +.Nm +only starts DHCPv6 when instructed to do so by an IPV6 Router Advertisement. +If no Identity Association is configured, +then a Non-temporary Address is requested. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it probes for a valid IPv4LL address +.Po +aka ZeroConf, aka APIPA +.Pc . +Once obtained it restarts the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the +.Fl L , Fl Fl noipv4ll +option. +.Ss Multiple interfaces +If a list of interfaces are given on the command line, then +.Nm +only works with those interfaces, otherwise +.Nm +discovers available Ethernet interfaces that can be configured. +When +.Nm +is operating on more than one interface, +it is called Master mode. and this behaviour can be forced with the +.Fl M , Fl Fl master +option so that an individual interface can start +.Nm +but only one instance is running. +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. +The +.Fl w , Fl Fl waitip +option is enabled in this instance to maintain compatibility with older +versions. +.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 10-mtu , +.Pa 10-wpa_supplicant , +.Pa 15-timezone , +.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. +.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 +Generate an +.Li RFC 4361 +compliant clientid. +This requires persistent storage and not all DHCP servers work with it so it +is not enabled by default. +.Nm +generates the DUID and stores it in +.Pa @SYSCONFDIR@/dhcpcd.duid . +This file should not be copied to other hosts. +.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. +If the +.Fl p, Fl Fl persistent +option is not given then the lease is used if it hasn't 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 RFC1035 . +.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 (ie, contains a .) then it will be encoded as such. +.It Fl I , Fl Fl clientid Ar clientid +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Fl i , Fl Fl vendorclassid Ar vendorclassid +Override the DHCPv4 +.Ar vendorclassid +field sent. +The default is +dhcpcd-<version>:<os>:<machine>:<platform>. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 +If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. +.It Fl j , Fl Fl logfile Ar logfile +Writes to the specified +.Ar logfile +rather than +.Xr syslog 3 . +The +.Ar logfile +is truncated when opened and is reopened when +.Nm +receives the +.Dv SIGUSR2 +signal. +.It Fl k , Fl Fl release Op Ar interface +This causes an existing +.Nm +process running on the +.Ar interface +to release its lease and de-configure the +.Ar interface . +If no +.Ar interface +is specified then this applies to all interfaces. +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 interface is specified then this applies to all interfaces. +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. +.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 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 Fl s , Fl Fl inform Op Ar address Ns Op Ar /cidr +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 S, Fl Fl static Ar value +Configures a static +.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 +.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. +.Fl fl waitip +option. +.It Fl Fl 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 Fl x , Fl Fl exit Op Ar interface +This will signal an existing +.Nm +process running on the +.Ar interface +to de-configure the +.Ar interface +and exit. +If no interface is specified, then the above is applied to all interfaces. +.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. +.Bl -tag -width indent +.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 or MTU settings you would do:- +.D1 dhcpcd -C resolv.conf -C mtu 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 +Don't request the specified option. +If no option given, then don't request any options other than those to +configure the interface and routing. +.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. +.Ar interface +could also be a path to a DHCP wire formatted file. +Use the +.Fl 4 +or +.Fl 6 +flags to specify an address family. +Pass a 2nd +.Fl U, Fl Fl dumplease option to dump a secondary lease, such as +DHCPv6 Prefix Delegation when not being mixed with another IA type. +.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 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 futher calls to +.Nm +to rebind, reconfigure or exit need to include the same restrictive flags +so that +.Nm +knows which process to signal. +.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 @SYSCONFDIR@/dhcpcd.duid +Text file that holds the DUID used to identify the host. +.It Pa @SYSCONFDIR@/dhcpcd.secret +Text file that holds a secret key known only to the host. +.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@/dhcpcd\- 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 files mtime as when it was issued. +.It Pa @DBDIR@/dhcpcd\- 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 files mtime as when it was issued. +.It Pa @DBDIR@/dhcpcd-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 +Unpriviledged socket to the master daemon, only allows state retrieval. +Control socket to the master daemon. +.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\ 6603, +RFC\ 6704, RFC\ 7217. +.Sh AUTHORS +.An Roy Marples Aq Mt roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd.c b/dhcpcd.c new file mode 100644 index 0000000..2ffdac1 --- /dev/null +++ b/dhcpcd.c @@ -0,0 +1,1889 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char dhcpcd_copyright[] = "Copyright (c) 2006-2015 Roy Marples"; + +#define _WITH_DPRINTF /* Stop FreeBSD bitching */ + +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +#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 "ipv6.h" +#include "ipv6nd.h" +#include "script.h" + +#ifdef USE_SIGNALS +const int dhcpcd_handlesigs[] = { + SIGTERM, + SIGINT, + SIGALRM, + SIGHUP, + SIGUSR1, + SIGUSR2, + SIGPIPE, + 0 +}; + +/* Handling signals needs *some* context */ +static struct dhcpcd_ctx *dhcpcd_ctx; +#endif + +#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) +static pid_t +read_pid(const char *pidfile) +{ + FILE *fp; + pid_t pid; + + if ((fp = fopen(pidfile, "r")) == NULL) { + errno = ENOENT; + return 0; + } + if (fscanf(fp, "%d", &pid) != 1) + pid = 0; + fclose(fp); + return pid; +} + +static int +write_pid(int fd, pid_t pid) +{ + + if (ftruncate(fd, (off_t)0) == -1) + return -1; + lseek(fd, (off_t)0, SEEK_SET); + return dprintf(fd, "%d\n", (int)pid); +} +#endif + +static void +usage(void) +{ + +printf("usage: "PACKAGE"\t[-46ABbDdEGgHJKkLnpqTVw]\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] [-l, --leasetime seconds]\n" + "\t\t[-m, --metric metric] [-O, --nooption option]\n" + "\t\t[-o, --option option] [-Q, --require option]\n" + "\t\t[-r, --request address] [-S, --static value]\n" + "\t\t[-s, --inform address[/cidr]] [-t, --timeout seconds]\n" + "\t\t[-u, --userclass class] [-v, --vendor code, value]\n" + "\t\t[-W, --whitelist address[/cidr]] [-y, --reboot seconds]\n" + "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n" + "\t\t[-z, --allowinterfaces pattern] [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->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; + logger(ctx, LOG_ERR, "timed out"); + if (!(ctx->options & DHCPCD_MASTER)) { + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + ctx->options |= DHCPCD_NOWAITIP; + dhcpcd_daemonise(ctx); +} + +int +dhcpcd_oneup(struct dhcpcd_ctx *ctx) +{ + const struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (D_STATE_RUNNING(ifp) || + RS_STATE_RUNNING(ifp) || + D6_STATE_RUNNING(ifp)) + return 1; + } + return 0; +} + +int +dhcpcd_ipwaited(struct dhcpcd_ctx *ctx) +{ + + if (ctx->options & DHCPCD_WAITIP4 && + !ipv4_addrexists(ctx, NULL)) + return 0; + if (ctx->options & DHCPCD_WAITIP6 && + !ipv6nd_findaddr(ctx, NULL, 0) && + !dhcp6_findaddr(ctx, NULL, 0)) + return 0; + if (ctx->options & DHCPCD_WAITIP && + !(ctx->options & (DHCPCD_WAITIP4 | DHCPCD_WAITIP6)) && + !ipv4_addrexists(ctx, NULL) && + !ipv6nd_findaddr(ctx, NULL, 0) && + !dhcp6_findaddr(ctx, NULL, 0)) + 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; + char buf = '\0'; + int sidpipe[2], fd; + + if (ctx->options & DHCPCD_DAEMONISE && + !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) + { + if (!dhcpcd_ipwaited(ctx)) + return 0; + } + + eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); + if (ctx->options & DHCPCD_DAEMONISED || + !(ctx->options & DHCPCD_DAEMONISE)) + return 0; + /* Setup a signal pipe so parent knows when to exit. */ + if (pipe(sidpipe) == -1) { + logger(ctx, LOG_ERR, "pipe: %m"); + return 0; + } + logger(ctx, LOG_DEBUG, "forking to background"); + switch (pid = fork()) { + case -1: + logger(ctx, LOG_ERR, "fork: %m"); + return 0; + case 0: + setsid(); + /* Some polling methods don't survive after forking, + * so ensure we can requeue all our events. */ + if (eloop_requeue(ctx->eloop) == -1) { + logger(ctx, LOG_ERR, "eloop_requeue: %m"); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + /* Notify parent it's safe to exit as we've detached. */ + close(sidpipe[0]); + if (write(sidpipe[1], &buf, 1) == -1) + logger(ctx, LOG_ERR, "failed to notify parent: %m"); + close(sidpipe[1]); + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + break; + default: + /* Wait for child to detach */ + close(sidpipe[1]); + if (read(sidpipe[0], &buf, 1) == -1) + logger(ctx, LOG_ERR, "failed to read child: %m"); + close(sidpipe[0]); + break; + } + /* Done with the fd now */ + if (pid != 0) { + logger(ctx, LOG_INFO, "forked to background, child pid %d", pid); + write_pid(ctx->pid_fd, pid); + close(ctx->pid_fd); + ctx->pid_fd = -1; + ctx->options |= DHCPCD_FORKED; + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return pid; + } + ctx->options |= DHCPCD_DAEMONISED; + return pid; +#endif +} + +static void +dhcpcd_drop(struct interface *ifp, int stop) +{ + + dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); + ipv6nd_drop(ifp); + ipv6_drop(ifp); + dhcp_drop(ifp, stop ? "STOP" : "EXPIRE"); + arp_close(ifp); +} + +static void +stop_interface(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx; + + ctx = ifp->ctx; + logger(ctx, LOG_INFO, "%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); + + /* Remove the interface from our list */ + TAILQ_REMOVE(ifp->ctx->ifaces, ifp, next); + if_free(ifp); + + 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; + int ra_global, ra_iface; +#ifdef INET6 + size_t i; +#endif + + /* 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 && !(ifo->options & DHCPCD_INFORM)) + ifo->options |= DHCPCD_STATIC; + if (ifp->flags & IFF_NOARP || + ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) + ifo->options &= ~(DHCPCD_ARP | DHCPCD_IPV4LL); + if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK) || + !(ifp->flags & IFF_MULTICAST)) + ifo->options &= ~DHCPCD_IPV6RS; + + if (ifo->metric != -1) + ifp->metric = (unsigned int)ifo->metric; + + if (!(ifo->options & DHCPCD_IPV4)) + ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL); + + if (!(ifo->options & DHCPCD_IPV6)) + ifo->options &= ~(DHCPCD_IPV6RS | DHCPCD_DHCP6); + + if (ifo->options & DHCPCD_SLAACPRIVATE && + !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) + ifo->options |= DHCPCD_IPV6RA_OWN; + + /* If we're a psuedo interface, ensure we disable as much as we can */ + if (ifp->options->options & DHCPCD_PFXDLGONLY) + ifp->options->options &= ~(DHCPCD_IPV4 | DHCPCD_IPV6RS); + + /* We want to disable kernel interface RA as early as possible. */ + if (ifo->options & DHCPCD_IPV6RS && + !(ifp->ctx->options & DHCPCD_DUMPLEASE)) + { + /* If not doing any DHCP, disable the RDNSS requirement. */ + if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6))) + ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; + ra_global = if_checkipv6(ifp->ctx, NULL, + ifp->ctx->options & DHCPCD_IPV6RA_OWN ? 1 : 0); + ra_iface = if_checkipv6(ifp->ctx, ifp, + ifp->options->options & DHCPCD_IPV6RA_OWN ? 1 : 0); + if (ra_global == -1 || ra_iface == -1) + ifo->options &= ~DHCPCD_IPV6RS; + else if (ra_iface == 0 && + !(ifp->ctx->options & DHCPCD_TEST)) + ifo->options |= DHCPCD_IPV6RA_OWN; + } + + /* If we haven't specified a ClientID and our hardware address + * length is greater than DHCP_CHADDR_LEN 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 > DHCP_CHADDR_LEN) + ifo->options |= DHCPCD_CLIENTID; + else if (ifp->hwlen == 0 && !(ifo->options & DHCPCD_CLIENTID)) + ifo->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: + ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST; + break; + } + + 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. + * + * 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->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(len)); + } + } + 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) + logger(ifp->ctx, LOG_ERR, "%s: %m", __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)); + ifo->ia->sla = NULL; + ifo->ia->sla_len = 0; + } + } else { + for (i = 0; i < ifo->ia_len; i++) { + if (!ifo->ia[i].iaid_set) { + memcpy(&ifo->ia[i].iaid, ifo->iaid, + sizeof(ifo->ia[i].iaid)); + ifo->ia[i].iaid_set = 1; + } + } + } +#endif + + /* If we are not sending an authentication option, don't require it */ + if (!(ifo->auth.options & DHCPCD_AUTH_SEND)) + ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE; +} + +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), ESCSTRING, + ifp->ssid, ifp->ssid_len); + if (r == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: %s: %m", ifp->name, __func__); + pssid[0] = '\0'; + } + } else + pssid[0] = '\0'; + ifo = read_config(ifp->ctx, ifp->name, pssid, profile); + if (ifo == NULL) { + logger(ifp->ctx, LOG_DEBUG, "%s: no profile %s", + ifp->name, profile); + return -1; + } + if (profile != NULL) { + strlcpy(ifp->profile, profile, sizeof(ifp->profile)); + logger(ifp->ctx, LOG_INFO, "%s: selected profile %s", + ifp->name, profile); + } else + *ifp->profile = '\0'; + + free_options(ifp->options); + ifp->options = ifo; + if (profile) + 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); + 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 (ifp->options && old != 0 && ifp->options->mtime != old) { + logger(ifp->ctx, LOG_WARNING, + "%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 * MSEC_PER_NSEC; + eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcpcd_pollup, ifp); + return; + } + + dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); +} + +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->options & DHCPCD_LINK)) + 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) /* For example a PPP link on BSD */ + logger(ctx, LOG_ERR, "%s: carrier_status: %m", ifname); + } else if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) { + if (ifp->carrier != LINK_DOWN) { + if (ifp->carrier == LINK_UP) + logger(ctx, LOG_INFO, "%s: carrier lost", + ifp->name); + ifp->carrier = LINK_DOWN; + script_runreason(ifp, "NOCARRIER"); +#ifdef NOCARRIER_PRESERVE_IP + arp_close(ifp); + ipv4_buildroutes(ifp->ctx); + ipv6nd_expire(ifp, 0); +#else + dhcpcd_drop(ifp, 0); +#endif + } + } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { + if (ifp->carrier != LINK_UP) { + logger(ctx, LOG_INFO, "%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_SSIDSIZE]; +#ifdef NOCARRIER_PRESERVE_IP + size_t olen; + + olen = ifp->ssid_len; +#endif + memcpy(ossid, ifp->ssid, ifp->ssid_len); + if_getssid(ifp); +#ifdef NOCARRIER_PRESERVE_IP + /* If we changed SSID network, drop leases */ + if (ifp->ssid_len != olen || + memcmp(ifp->ssid, ossid, ifp->ssid_len)) + dhcpcd_drop(ifp, 0); +#endif + } + 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 */ + if (ifp->options->options & DHCPCD_IPV6RA_OWN) + ipv6_gentempifid(ifp); + dhcpcd_startinterface(ifp); + } + } +} + +static void +warn_iaid_conflict(struct interface *ifp, uint8_t *iaid) +{ + struct interface *ifn; + size_t i; + + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp) + continue; + if (ifn->options->options & DHCPCD_PFXDLGONLY) + continue; + if (memcmp(ifn->options->iaid, iaid, + sizeof(ifn->options->iaid)) == 0) + break; + for (i = 0; i < ifn->options->ia_len; i++) { + if (memcmp(&ifn->options->ia[i].iaid, iaid, + sizeof(ifn->options->ia[i].iaid)) == 0) + break; + } + } + + /* This is only a problem if the interfaces are on the same network. */ + if (ifn && strcmp(ifp->name, ifn->name)) + logger(ifp->ctx, LOG_ERR, + "%s: IAID conflicts with one assigned to %s", + ifp->name, ifn->name); +} + +static void +pre_start(struct interface *ifp) +{ + + /* Add our link-local address before upping the interface + * so our RFC7217 address beats the hwaddr based one. + * This is also a safety check incase it was ripped out + * from under us. */ + if (ifp->options->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name); + ifp->options->options &= ~DHCPCD_IPV6; + } +} + +void +dhcpcd_startinterface(void *arg) +{ + struct interface *ifp = arg; + struct if_options *ifo = ifp->options; + size_t i; + 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: + logger(ifp->ctx, LOG_INFO, "%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 * MSEC_PER_NSEC; + 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; + if (!(ifo->options & DHCPCD_PFXDLGONLY)) + logger(ifp->ctx, LOG_INFO, "DUID %s", + hwaddr_ntoa(ifp->ctx->duid, + ifp->ctx->duid_len, + buf, sizeof(buf))); + } + } + + if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) && + !(ifo->options & DHCPCD_PFXDLGONLY)) + { + /* Report IAIDs */ + logger(ifp->ctx, LOG_INFO, "%s: IAID %s", ifp->name, + hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid), + buf, sizeof(buf))); + warn_iaid_conflict(ifp, ifo->iaid); + for (i = 0; i < ifo->ia_len; i++) { + if (memcmp(ifo->iaid, ifo->ia[i].iaid, + sizeof(ifo->iaid))) + { + logger(ifp->ctx, LOG_INFO, "%s: IAID %s", + ifp->name, hwaddr_ntoa(ifo->ia[i].iaid, + sizeof(ifo->ia[i].iaid), + buf, sizeof(buf))); + warn_iaid_conflict(ifp, ifo->ia[i].iaid); + } + } + } + + if (ifo->options & DHCPCD_IPV6) { + if (ifo->options & DHCPCD_IPV6RS && + !(ifo->options & DHCPCD_INFORM)) + ipv6nd_startrs(ifp); + + if (ifo->options & DHCPCD_DHCP6) + dhcp6_find_delegates(ifp); + + if (!(ifo->options & DHCPCD_IPV6RS) || + ifo->options & DHCPCD_IA_FORCED) + { + ssize_t nolease; + + if (ifo->options & DHCPCD_IA_FORCED) + nolease = dhcp6_start(ifp, DH6S_INIT); + else { + nolease = 0; + /* Enabling the below doesn't really make + * sense as there is currently no standard + * to push routes via DHCPv6. + * (There is an expired working draft, + * maybe abandoned?) + * You can also get it to work by forcing + * an IA as shown above. */ +#if 0 + /* With no RS or delegates we might + * as well try and solicit a DHCPv6 address */ + if (nolease == 0) + nolease = dhcp6_start(ifp, DH6S_INIT); +#endif + } + if (nolease == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_start: %m", ifp->name); + } + } + + if (ifo->options & DHCPCD_IPV4) + dhcp_start(ifp); +} + +static void +dhcpcd_prestartinterface(void *arg) +{ + struct interface *ifp = arg; + + pre_start(ifp); + if (if_up(ifp) == -1) + logger(ifp->ctx, LOG_ERR, "%s: if_up: %m", 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; + } + logger(ifp->ctx, LOG_INFO, + "%s: unknown carrier, waiting for interface flags", + ifp->name); + } + + dhcpcd_startinterface(ifp); +} + +static void +handle_link(void *arg) +{ + struct dhcpcd_ctx *ctx; + + ctx = arg; + if (if_managelink(ctx) == -1) { + logger(ctx, LOG_ERR, "if_managelink: %m"); + eloop_event_delete(ctx->eloop, ctx->link_fd, 0); + close(ctx->link_fd); + ctx->link_fd = -1; + } +} + +static void +dhcpcd_initstate1(struct interface *ifp, int argc, char **argv, + unsigned long long options) +{ + struct if_options *ifo; + + configure_interface(ifp, argc, argv, options); + ifo = ifp->options; + + if (ifo->options & DHCPCD_IPV4 && ipv4_init(ifp->ctx) == -1) { + logger(ifp->ctx, LOG_ERR, "ipv4_init: %m"); + ifo->options &= ~DHCPCD_IPV4; + } + if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == NULL) { + logger(ifp->ctx, LOG_ERR, "ipv6_init: %m"); + ifo->options &= ~DHCPCD_IPV6RS; + } + + /* Add our link-local address before upping the interface + * so our RFC7217 address beats the hwaddr based one. + * This needs to happen before PREINIT incase a hook script + * inadvertently ups the interface. */ + if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name); + ifo->options &= ~DHCPCD_IPV6; + } +} + +void +dhcpcd_initstate(struct interface *ifp, unsigned long long options) +{ + + dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); +} + +static void +run_preinit(struct interface *ifp) +{ + + pre_start(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"); +} + +int +dhcpcd_handleinterface(void *arg, int action, const char *ifname) +{ + struct dhcpcd_ctx *ctx; + struct if_head *ifs; + struct interface *ifp, *iff, *ifn; + const char * const argv[] = { ifname }; + int i; + + ctx = arg; + if (action == -1) { + ifp = if_find(ctx->ifaces, ifname); + if (ifp == NULL) { + errno = ESRCH; + return -1; + } + logger(ctx, LOG_DEBUG, "%s: interface departed", ifp->name); + ifp->options->options |= DHCPCD_DEPARTED; + stop_interface(ifp); + return 0; + } + + /* If running off an interface list, check it's in it. */ + if (ctx->ifc && action != 2) { + for (i = 0; i < ctx->ifc; i++) + if (strcmp(ctx->ifv[i], ifname) == 0) + break; + if (i >= ctx->ifc) + return 0; + } + + i = -1; + ifs = if_discover(ctx, -1, UNCONST(argv)); + if (ifs == NULL) { + logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__); + return -1; + } + TAILQ_FOREACH_SAFE(ifp, ifs, next, ifn) { + if (strcmp(ifp->name, ifname) != 0) + continue; + i = 0; + /* Check if we already have the interface */ + iff = if_find(ctx->ifaces, ifp->name); + if (iff) { + logger(ctx, LOG_DEBUG, "%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 { + logger(ctx, LOG_DEBUG, "%s: interface added", ifp->name); + TAILQ_REMOVE(ifs, ifp, next); + TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); + dhcpcd_initstate(ifp, 0); + run_preinit(ifp); + iff = ifp; + } + if (action > 0) + dhcpcd_prestartinterface(iff); + } + + /* Free our discovered list */ + while ((ifp = TAILQ_FIRST(ifs))) { + TAILQ_REMOVE(ifs, ifp, next); + if_free(ifp); + } + free(ifs); + + if (i == -1) + errno = ENOENT; + return i; +} + +void +dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname, + const uint8_t *hwaddr, uint8_t hwlen) +{ + struct interface *ifp; + char buf[sizeof(ifp->hwaddr) * 3]; + + ifp = if_find(ctx->ifaces, ifname); + if (ifp == NULL) + return; + + if (hwlen > sizeof(ifp->hwaddr)) { + errno = ENOBUFS; + logger(ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__); + return; + } + + if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0) + return; + + logger(ctx, LOG_INFO, "%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); + ifo = read_config(ctx, NULL, NULL, NULL); + 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(ifo); +} + +static void +reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi) +{ + struct if_head *ifs; + struct interface *ifn, *ifp; + + ifs = if_discover(ctx, argc - oi, argv + oi); + if (ifs == NULL) { + logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__); + return; + } + + while ((ifp = TAILQ_FIRST(ifs))) { + TAILQ_REMOVE(ifs, ifp, next); + ifn = if_find(ctx->ifaces, ifp->name); + if (ifn) { + if (action) + if_reboot(ifn, argc, argv); + else + ipv4_applyaddr(ifn); + if_free(ifp); + } else { + TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); + dhcpcd_initstate1(ifp, argc, argv, 0); + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); + } + } + free(ifs); +} + +static void +stop_all_interfaces(struct dhcpcd_ctx *ctx, int do_release) +{ + struct interface *ifp; + + /* drop_dhcp could change the order, so we do it like this. */ + for (;;) { + /* Be sane and drop the last config first, + * skipping any pseudo interfaces */ + TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { + if (!(ifp->options->options & DHCPCD_PFXDLGONLY)) + break; + } + if (ifp == NULL) + break; + if (do_release) { + ifp->options->options |= DHCPCD_RELEASE; + ifp->options->options &= ~DHCPCD_PERSISTENT; + } + ifp->options->options |= DHCPCD_EXITING; + stop_interface(ifp); + } +} + +#ifdef USE_SIGNALS +struct dhcpcd_siginfo dhcpcd_siginfo; +#define sigmsg "received %s, %s" +void +dhcpcd_handle_signal(void *arg) +{ + struct dhcpcd_ctx *ctx; + struct dhcpcd_siginfo *si; + struct interface *ifp; + int do_release, exit_code;; + + ctx = dhcpcd_ctx; + si = arg; + do_release = 0; + exit_code = EXIT_FAILURE; + switch (si->signo) { + case SIGINT: + logger(ctx, LOG_INFO, sigmsg, "SIGINT", "stopping"); + break; + case SIGTERM: + logger(ctx, LOG_INFO, sigmsg, "SIGTERM", "stopping"); + exit_code = EXIT_SUCCESS; + break; + case SIGALRM: + logger(ctx, LOG_INFO, sigmsg, "SIGALRM", "releasing"); + do_release = 1; + exit_code = EXIT_SUCCESS; + break; + case SIGHUP: + logger(ctx, LOG_INFO, 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: + logger(ctx, LOG_INFO, sigmsg, "SIGUSR1", "reconfiguring"); + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ipv4_applyaddr(ifp); + } + return; + case SIGUSR2: + logger_close(ctx); + logger_open(ctx); + logger(ctx, LOG_INFO, sigmsg, "SIGUSR2", "reopened logfile"); + return; + case SIGPIPE: + logger(ctx, LOG_WARNING, "received SIGPIPE"); + return; + default: + logger(ctx, LOG_ERR, + "received signal %d, " + "but don't know what to do with it", + si->signo); + return; + } + + if (!(ctx->options & DHCPCD_TEST)) + stop_all_interfaces(ctx, do_release); + eloop_exit(ctx->eloop, exit_code); +} + +#ifndef HAVE_KQUEUE +static void +handle_signal(int sig, __unused siginfo_t *siginfo, __unused void *context) +{ + + /* 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. */ + dhcpcd_siginfo.signo = sig; + eloop_timeout_add_now(dhcpcd_ctx->eloop, + dhcpcd_handle_signal, &dhcpcd_siginfo); +} +#endif + +static int +signal_init(sigset_t *oldset) +{ + sigset_t newset; +#ifndef HAVE_KQUEUE + int i; + struct sigaction sa; +#endif + + sigfillset(&newset); + if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) + return -1; + +#ifndef HAVE_KQUEUE + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handle_signal; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + + for (i = 0; dhcpcd_handlesigs[i]; i++) { + if (sigaction(dhcpcd_handlesigs[i], &sa, NULL) == -1) + return -1; + } +#endif + return 0; +} +#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) { + len++; + if (D_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_delete(fd->ctx->eloop, fd->fd, 1); + TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { + if (send_interface(fd, ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "send_interface %d: %m", fd->fd); + } +} + +int +dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, + int argc, char **argv) +{ + struct interface *ifp; + int do_exit = 0, do_release = 0, do_reboot = 0; + int opt, oi = 0; + 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(fd->ctx->eloop, fd->fd, NULL, NULL, + 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'; + logger(ctx, LOG_INFO, "control command: %s", tmp); + free(tmp); + + optind = 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': + do_release = 1; + break; + case 'n': + do_reboot = 1; + break; + case 'x': + do_exit = 1; + break; + } + } + + if (do_release || do_exit) { + if (optind == argc) { + stop_all_interfaces(ctx, do_release); + 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 (do_release) { + ifp->options->options |= DHCPCD_RELEASE; + ifp->options->options &= ~DHCPCD_PERSISTENT; + } + ifp->options->options |= DHCPCD_EXITING; + stop_interface(ifp); + } + return 0; + } + + reload_config(ctx); + /* XXX: Respect initial commandline options? */ + reconf_reboot(ctx, do_reboot, argc, argv, optind); + return 0; +} + +int +main(int argc, char **argv) +{ + struct dhcpcd_ctx ctx; + struct if_options *ifo; + struct interface *ifp; + uint16_t family = 0; + int opt, oi = 0, i; + time_t t; + ssize_t len; +#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) + pid_t pid; +#endif +#ifdef USE_SIGNALS + int sig; + const char *siga; +#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); + return EXIT_SUCCESS; + } + } + + memset(&ctx, 0, sizeof(ctx)); +#ifdef USE_SIGNALS + dhcpcd_ctx = &ctx; + sig = 0; + siga = NULL; +#endif + closefrom(3); + + ctx.log_fd = -1; + logger_open(&ctx); + logger_mask(&ctx, LOG_UPTO(LOG_INFO)); + + ifo = NULL; + ctx.cffile = CONFIG; + ctx.pid_fd = ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; + TAILQ_INIT(&ctx.control_fds); +#ifdef PLUGIN_DEV + ctx.dev_fd = -1; +#endif +#ifdef INET + ctx.udp_fd = -1; +#endif + i = 0; + while ((opt = getopt_long(argc, argv, 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; +#ifdef USE_SIGNALS + case 'g': + sig = SIGUSR1; + siga = "USR1"; + break; + case 'j': + ctx.logfile = strdup(optarg); + logger_close(&ctx); + logger_open(&ctx); + break; + case 'k': + sig = SIGALRM; + siga = "ARLM"; + break; + case 'n': + sig = SIGHUP; + siga = "HUP"; + break; + case 'x': + sig = SIGTERM; + siga = "TERM";; + break; +#endif + case 'T': + i = 1; + break; + case 'U': + if (i == 3) + i = 4; + else if (i != 4) + i = 3; + break; + case 'V': + i = 2; + break; + case '?': + usage(); + goto exit_failure; + } + } + + ctx.argv = argv; + ctx.argc = argc; + ctx.ifc = argc - optind; + ctx.ifv = argv + optind; + + ifo = read_config(&ctx, NULL, NULL, NULL); + if (ifo == NULL) + goto exit_failure; + opt = add_options(&ctx, NULL, ifo, argc, argv); + if (opt != 1) { + if (opt == 0) + usage(); + goto exit_failure; + } + if (i == 2) { + printf("Interface options:\n"); + if (optind == argc - 1) { + free_options(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("\nDHCPv6 options:\n"); + dhcp6_printoptions(&ctx, + ifo->dhcp6_override, ifo->dhcp6_override_len); + } +#endif + goto exit_success; + } + ctx.options = ifo->options; + if (i != 0) { + if (i == 1) + ctx.options |= DHCPCD_TEST; + else + ctx.options |= DHCPCD_DUMPLEASE; + if (i == 4) + ctx.options |= DHCPCD_PFXDLGONLY; + ctx.options |= DHCPCD_PERSISTENT; + ctx.options &= ~DHCPCD_DAEMONISE; + } + +#ifdef THERE_IS_NO_FORK + ctx.options &= ~DHCPCD_DAEMONISE; +#endif + + if (ctx.options & DHCPCD_DEBUG) + logger_mask(&ctx, LOG_UPTO(LOG_DEBUG)); + if (ctx.options & DHCPCD_QUIET) { + i = open(_PATH_DEVNULL, O_RDWR); + if (i == -1) + logger(&ctx, LOG_ERR, "%s: open: %m", __func__); + else { + dup2(i, STDERR_FILENO); + close(i); + } + } + + if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { + /* 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; + + if (strlen(argv[optind]) > IF_NAMESIZE) { + logger(&ctx, LOG_ERR, + "%s: interface name too long", + argv[optind]); + 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, "-", argv[optind], per); + } else { + snprintf(ctx.pidfile, sizeof(ctx.pidfile), + PIDFILE, "", "", ""); + ctx.options |= DHCPCD_MASTER; + } + } + + if (chdir("/") == -1) + logger(&ctx, LOG_ERR, "chdir `/': %m"); + + /* Freeing allocated addresses from dumping leases can trigger + * eloop removals as well, so init here. */ + ctx.eloop = eloop_init(&ctx); + if (ctx.eloop == NULL) { + logger(&ctx, LOG_ERR, "%s: eloop_init: %m", __func__); + goto exit_failure; + } + + if (ctx.options & DHCPCD_DUMPLEASE) { + if (optind != argc - 1) { + logger(&ctx, LOG_ERR, + "dumplease requires an interface"); + goto exit_failure; + } + i = 0; + /* We need to try and find the interface so we can + * load the hardware address to compare automated IAID */ + ctx.ifaces = if_discover(&ctx, 1, argv + optind); + if (ctx.ifaces == NULL) { + logger(&ctx, LOG_ERR, "if_discover: %m"); + goto exit_failure; + } + ifp = TAILQ_FIRST(ctx.ifaces); + if (ifp == NULL) { + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) { + logger(&ctx, LOG_ERR, "%s: %m", __func__); + goto exit_failure; + } + strlcpy(ctx.pidfile, argv[optind], sizeof(ctx.pidfile)); + ifp->ctx = &ctx; + TAILQ_INSERT_HEAD(ctx.ifaces, ifp, next); + if (family == 0) { + if (ctx.pidfile[strlen(ctx.pidfile) - 1] == '6') + family = AF_INET6; + else + family = AF_INET; + } + } + configure_interface(ifp, ctx.argc, ctx.argv, 0); + if (ctx.options & DHCPCD_PFXDLGONLY) + ifp->options->options |= DHCPCD_PFXDLGONLY; + 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 + if (!(ctx.options & DHCPCD_TEST) && + (sig == 0 || ctx.ifc != 0)) + { +#endif + if (ctx.options & DHCPCD_MASTER) + i = -1; + else + i = control_open(&ctx, argv[optind]); + if (i == -1) + i = control_open(&ctx, NULL); + if (i != -1) { + logger(&ctx, LOG_INFO, + "sending commands to master dhcpcd process"); + len = control_send(&ctx, argc, argv); + control_close(&ctx); + if (len > 0) { + logger(&ctx, LOG_DEBUG, "send OK"); + goto exit_success; + } else { + logger(&ctx, LOG_ERR, + "failed to send commands"); + goto exit_failure; + } + } else { + if (errno != ENOENT) + logger(&ctx, LOG_ERR, "control_open: %m"); + } +#ifdef USE_SIGNALS + } +#endif + + if (geteuid()) + logger(&ctx, LOG_WARNING, + PACKAGE " will not work correctly unless run as root"); + +#ifdef USE_SIGNALS + if (sig != 0) { + pid = read_pid(ctx.pidfile); + if (pid != 0) + logger(&ctx, LOG_INFO, "sending signal %s to pid %d", + siga, pid); + if (pid == 0 || kill(pid, sig) != 0) { + if (sig != SIGHUP && errno != EPERM) + logger(&ctx, LOG_ERR, ""PACKAGE" not running"); + if (pid != 0 && errno != ESRCH) { + logger(&ctx, LOG_ERR, "kill: %m"); + goto exit_failure; + } + unlink(ctx.pidfile); + if (sig != SIGHUP) + goto exit_failure; + } else { + struct timespec ts; + + if (sig == SIGHUP || sig == SIGUSR1) + goto exit_success; + /* Spin until it exits */ + logger(&ctx, LOG_INFO, + "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 (read_pid(ctx.pidfile) == 0) + goto exit_success; + } + logger(&ctx, LOG_ERR, "pid %d failed to exit", pid); + goto exit_failure; + } + } + + if (!(ctx.options & DHCPCD_TEST)) { + if ((pid = read_pid(ctx.pidfile)) > 0 && + kill(pid, 0) == 0) + { + logger(&ctx, LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, ctx.pidfile); + goto exit_failure; + } + + /* Ensure we have the needed directories */ + if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) + logger(&ctx, LOG_ERR, "mkdir `%s': %m", RUNDIR); + if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) + logger(&ctx, LOG_ERR, "mkdir `%s': %m", DBDIR); + + opt = O_WRONLY | O_CREAT | O_NONBLOCK; +#ifdef O_CLOEXEC + opt |= O_CLOEXEC; +#endif + ctx.pid_fd = open(ctx.pidfile, opt, 0664); + if (ctx.pid_fd == -1) + logger(&ctx, LOG_ERR, "open `%s': %m", ctx.pidfile); + else { +#ifdef LOCK_EX + /* Lock the file so that only one instance of dhcpcd + * runs on an interface */ + if (flock(ctx.pid_fd, LOCK_EX | LOCK_NB) == -1) { + logger(&ctx, LOG_ERR, "flock `%s': %m", ctx.pidfile); + close(ctx.pid_fd); + ctx.pid_fd = -1; + goto exit_failure; + } +#endif +#ifndef O_CLOEXEC + if (fcntl(ctx.pid_fd, F_GETFD, &opt) == -1 || + fcntl(ctx.pid_fd, F_SETFD, opt | FD_CLOEXEC) == -1) + { + logger(&ctx, LOG_ERR, "fcntl: %m"); + close(ctx.pid_fd); + ctx.pid_fd = -1; + goto exit_failure; + } +#endif + write_pid(ctx.pid_fd, getpid()); + } + } + + if (ctx.options & DHCPCD_MASTER) { + if (control_start(&ctx, NULL) == -1) + logger(&ctx, LOG_ERR, "control_start: %m"); + } +#else + if (control_start(&ctx, + ctx.options & DHCPCD_MASTER ? NULL : argv[optind]) == -1) + { + logger(&ctx, LOG_ERR, "control_start: %m"); + goto exit_failure; + } +#endif + + logger(&ctx, LOG_DEBUG, PACKAGE "-" VERSION " starting"); + ctx.options |= DHCPCD_STARTED; +#ifdef USE_SIGNALS + /* Save signal mask, block and redirect signals to our handler */ + if (signal_init(&ctx.sigset) == -1) { + logger(&ctx, LOG_ERR, "signal_setup: %m"); + goto exit_failure; + } +#endif + + /* When running dhcpcd against a single interface, we need to retain + * the old behaviour of waiting for an IP address */ + if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND)) + ctx.options |= DHCPCD_WAITIP; + + /* RTM_NEWADDR goes through the link socket as well which we + * need for IPv6 DAD, so we check for DHCPCD_LINK in + * dhcpcd_handlecarrier instead. + * We also need to open this before checking for interfaces below + * so that we pickup any new addresses during the discover phase. */ + ctx.link_fd = if_openlinksocket(); + if (ctx.link_fd == -1) + logger(&ctx, LOG_ERR, "open_link_socket: %m"); + else + eloop_event_add(ctx.eloop, ctx.link_fd, + handle_link, &ctx, NULL, NULL); + + /* 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, ctx.ifc, ctx.ifv); + if (ctx.ifaces == NULL) { + logger(&ctx, LOG_ERR, "if_discover: %m"); + goto exit_failure; + } + for (i = 0; i < ctx.ifc; i++) { + if (if_find(ctx.ifaces, ctx.ifv[i]) == NULL) + logger(&ctx, LOG_ERR, + "%s: interface not found or invalid", + ctx.ifv[i]); + } + if (TAILQ_FIRST(ctx.ifaces) == NULL) { + if (ctx.ifc == 0) + logger(&ctx, LOG_ERR, "no valid interfaces found"); + else + goto exit_failure; + if (!(ctx.options & DHCPCD_LINK)) { + logger(&ctx, LOG_ERR, + "aborting as link detection is disabled"); + goto exit_failure; + } + } + + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + dhcpcd_initstate1(ifp, argc, argv, 0); + } + + if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx)) + goto exit_success; + + opt = 0; + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + run_preinit(ifp); + if (ifp->carrier != LINK_DOWN) + opt = 1; + } + + if (!(ctx.options & DHCPCD_BACKGROUND)) { + if (ctx.options & DHCPCD_MASTER) + t = ifo->timeout; + else if ((ifp = TAILQ_FIRST(ctx.ifaces))) + t = ifp->options->timeout; + else + t = 0; + if (opt == 0 && + ctx.options & DHCPCD_LINK && + !(ctx.options & DHCPCD_WAITIP)) + { + logger(&ctx, LOG_WARNING, + "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(ifo); + ifo = NULL; + + if_sortinterfaces(&ctx); + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + eloop_timeout_add_sec(ctx.eloop, 0, + dhcpcd_prestartinterface, ifp); + } + + i = eloop_start(ctx.eloop); + goto exit1; + +exit_success: + i = EXIT_SUCCESS; + goto exit1; + +exit_failure: + i = EXIT_FAILURE; + +exit1: + /* 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(ctx.duid); + if (ctx.link_fd != -1) { + eloop_event_delete(ctx.eloop, ctx.link_fd, 0); + close(ctx.link_fd); + } + + free_options(ifo); + free_globals(&ctx); + ipv4_ctxfree(&ctx); + ipv6_ctxfree(&ctx); + dev_stop(&ctx); + if (control_stop(&ctx) == -1) + logger(&ctx, LOG_ERR, "control_stop: %m:"); + if (ctx.pid_fd != -1) { + close(ctx.pid_fd); + unlink(ctx.pidfile); + } + eloop_free(ctx.eloop); + + if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) + logger(&ctx, LOG_INFO, PACKAGE " exited"); + logger_close(&ctx); + free(ctx.logfile); + return i; +} diff --git a/dhcpcd.conf b/dhcpcd.conf new file mode 100644 index 0000000..e5a19cd --- /dev/null +++ b/dhcpcd.conf @@ -0,0 +1,43 @@ +# 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 +# Most distributions have NTP support. +option ntp_servers +# Respect the network MTU. +# Some interface drivers reset when changing the MTU so disabled by default. +#option interface_mtu + +# A ServerID is required by RFC2131. +require dhcp_server_identifier + +# Generate Stable Private IPv6 Addresses instead of hardware based ones +slaac private + +# A hook script is provided to lookup the hostname if not set by the DHCP +# server, but it should not be run by default. +nohook lookup-hostname diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in new file mode 100644 index 0000000..905a924 --- /dev/null +++ b/dhcpcd.conf.5.in @@ -0,0 +1,821 @@ +.\" 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. +.\" +.Dd April 6, 2015 +.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. +.Pp +Blank lines and lines starting with # are ignored. +.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 profile 192.168.0.1 +.D1 static ip_address=192.168.0.10/24 +.It Ic authprotocol Ar protocol Ar algorithm Ar rdm +Authenticate DHCP messages. +See the Supported Authentication Protocols section. +.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 +prptocol. +.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 +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, 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 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 +Generate an +.Rs +.%T "RFC 4361" +.Re +compliant DHCP Unique Identifier. +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-LLT generated will be held in +.Pa @SYSCONFDIR@/dhcpcd.duid +and should not be copied to other hosts. +.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 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 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 +Fallback 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 +.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 (ie, 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 +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, 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 suffix of 1. +You cannot assign a prefix to the requesting interface unless the +DHCPv6 server supports +.Li RFC6603 +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 and is added to the prefix which must fit inside +.Ar prefix_len +less the length of the delegated prefix. +.Ar sla_id can be 0 only if the Delegated Prefix is assigned to one interface. +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. +.Xr rtadvd 8 +can be used with an empty configuration file on eth1 to provide automatic +IPv6 address configuration for the internal network. +.Bd -literal -indent +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 # get a /64 and assign it to eth1 +.Ed +.It Ic ia_pd_mix +To be RFC compliant, +.Nm dhcpcd +cannot mix Prefix Delegation with other DHCPv6 address types in the same +session. +This has a number of issues: additional DHCP traffic and potential collisions +between options. +.Ic ia_pd_mix +enables +.Li draft-ietf-dhc-dhcpv6-stateful-issues-06 +support so that Prefix Delegation can be mixed with other address types in +the same session. +.It Ic ipv4only +Only configure IPv4. +.It Ic ipv6only +Only confgiure IPv6. +.It Ic fqdn Op disable | ptr | both +ptr just asks the DHCP server to update the PTR +record of the host in DNS whereas both also updates the A record. +disable will disable the FQDN option. +The default is both. +.Nm dhcpcd +itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . +.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 a +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 RA, +.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 ipv6ra_own +Disables kernel IPv6 Router Advertisment processing so dhcpcd can manage +addresses and routes. +.It Ic ipv6ra_own_default +Each time dhcpcd receives an IPv6 Router Adveristment, dhcpcd will manage +the default route only. +This allows dhcpcd to prefer an interface for outbound traffic based on metric +and/or user selection rather than the kernel. +.It Ic ipv6ra_accept_nopublic +Some IPv6 routers advertise themselves as a default router without any +public prefixes or managed addresses. +Generally, this is incorrect behaviour and +.Nm dhcpcd +will ignore the advertisement unless this option is turned on. +.It Ic ipv6rs +Enables IPv6 Router Advertisment 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 truncated when opened and 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 noalias +Any pre-existing IPv4 addresses existing address 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. +.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 a RA 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 or MTU settings you would do:- +.D1 nohook resolv.conf, mtu +.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 attmept to configure an IPv6 address. +.It Ic noipv6rs +Disable solicitation and receipt of 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 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. +DHCPv4 options are mapped to DHCPv6 where applicable. +.It Ic nooption Ar option +Remove the option from the DHCP message. +This should only be used when a DHCP server sends a non requested option +that should not be processed. +.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 authorative +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 require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options separated by commas, spaces or more require lines. +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 DHCP message that contains the +.Ar option . +This is useful when you cannot use +.Ic require +to select / de-select BOOTP messages. +.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, a RFC7217 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 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 interface eth0 +.D1 static ip_address=192.168.0.10/24 +.D1 static routers=192.168.0.1 +.D1 static domain_name_servers=192.168.0.1 +.Pp +Here is an example for PPP which gives the destination a default route. +It uses the special 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 +Timeout 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 it's own option as shown below. +The default is +dhcpcd-<version>:<os>:<machine>:<platform>. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 +If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. +.It Ic vendclass Ar en Ar data +Add the DHCPv6 Vendor Indetifying Vendor Class with the IANA assigned Enterprise +Number +.Ar en +with the +.Ar data . +This option can be set more than once to add more data, but the behaviour, +as per +.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 allows for the use of custom options. +Each option needs to be started with the +.Ic define +or +.Ic define6 +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 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 unqiuely 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. +.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 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 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 +A RFC 3397 encoded string +.It Ic dname +A 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, RFC4702 +.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, RFC3925 +.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 and expects the token with the secretid 0 and realm of "" in each message. +.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 a non-expired token with a matching realm and secretid. +This token is used to authenicate 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 clients 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 a NTP timestamp from the system time. +.It Ic monotonic +Same as +.Ic monotime . +.El +.Sh SEE ALSO +.Xr fnmatch 3 , +.Xr if_nametoindex 3 , +.Xr dhcpcd 8 , +.Xr dhcpcd-run-hooks 8 +.Sh AUTHORS +.An Roy Marples Aq Mt roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd.h b/dhcpcd.h new file mode 100644 index 0000000..8b34997 --- /dev/null +++ b/dhcpcd.h @@ -0,0 +1,178 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCD_H +#define DHCPCD_H + +#include <sys/socket.h> +#include <net/if.h> + +#include "config.h" +#include "defs.h" +#include "control.h" +#include "if-options.h" + +#define HWADDR_LEN 20 +#define IF_SSIDSIZE 33 +#define PROFILE_LEN 64 +#define SECRET_LEN 64 + +#define LINK_UP 1 +#define LINK_UNKNOWN 0 +#define LINK_DOWN -1 + +#define IF_DATA_IPV4 0 +#define IF_DATA_DHCP 1 +#define IF_DATA_IPV6 2 +#define IF_DATA_IPV6ND 3 +#define IF_DATA_DHCP6 4 +#define IF_DATA_MAX 5 + +/* 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 */ + +struct interface { + struct dhcpcd_ctx *ctx; + TAILQ_ENTRY(interface) next; + char name[IF_NAMESIZE]; +#ifdef __linux__ + char alias[IF_NAMESIZE]; +#endif + unsigned int index; + unsigned int flags; + sa_family_t family; + unsigned char hwaddr[HWADDR_LEN]; + uint8_t hwlen; + unsigned int metric; + int carrier; + int wireless; + uint8_t ssid[IF_SSIDSIZE]; + unsigned int ssid_len; + + char profile[PROFILE_LEN]; + struct if_options *options; + void *if_data[IF_DATA_MAX]; +}; +TAILQ_HEAD(if_head, interface); + +struct dhcpcd_ctx { + int pid_fd; + char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1]; + const char *cffile; + unsigned long long options; + char *logfile; + int log_fd; + 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; + int link_fd; + struct if_head *ifaces; + +#ifdef USE_SIGNALS + sigset_t sigset; +#endif + struct eloop_ctx *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; + +#ifdef INET + struct dhcp_opt *dhcp_opts; + size_t dhcp_opts_len; + struct rt_head *ipv4_routes; + struct rt_head *ipv4_kroutes; + + int udp_fd; + uint8_t *packet; + + /* 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; +#endif +#ifdef INET6 + unsigned char secret[SECRET_LEN]; + size_t secret_len; + + struct dhcp_opt *dhcp6_opts; + size_t dhcp6_opts_len; + struct ipv6_ctx *ipv6; +#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 +struct dhcpcd_siginfo { + int signo; +}; + +extern const int dhcpcd_handlesigs[]; +void dhcpcd_handle_signal(void *); +#endif + +int dhcpcd_oneup(struct dhcpcd_ctx *); +int dhcpcd_ipwaited(struct dhcpcd_ctx *); +pid_t dhcpcd_daemonise(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 unsigned char *, uint8_t); +void dhcpcd_dropinterface(struct interface *, const char *); +int dhcpcd_selectprofile(struct interface *, const char *); + +void dhcpcd_startinterface(void *); +void dhcpcd_initstate(struct interface *, unsigned long long); + +#endif @@ -0,0 +1,165 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define DUID_TIME_EPOCH 946684800 +#define DUID_LLT 1 +#define DUID_LL 3 + +#include <sys/socket.h> +#include <sys/types.h> + +#include <net/if.h> +#include <net/if_arp.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#ifndef ARPHRD_NETROM +# define ARPHRD_NETROM 0 +#endif + +#include "common.h" +#include "dhcpcd.h" +#include "duid.h" + +static size_t +duid_make(unsigned char *d, const struct interface *ifp, uint16_t type) +{ + unsigned char *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(unsigned char *d, const struct interface *ifp) +{ + FILE *fp; + int x = 0; + size_t len = 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 ((fp = fopen(DUID, "r"))) { + while (fgets(line, DUID_STRLEN, fp)) { + len = strlen(line); + if (len) { + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + } + len = hwaddr_aton(NULL, line); + if (len && len <= DUID_LEN) { + hwaddr_aton(d, line); + break; + } + len = 0; + } + fclose(fp); + if (len) + return len; + } else { + if (errno != ENOENT) + logger(ifp->ctx, LOG_ERR, + "error reading DUID: %s: %m", DUID); + } + + /* No file? OK, lets make one based on our interface */ + if (ifp->family == ARPHRD_NETROM) { + logger(ifp->ctx, LOG_WARNING, + "%s: is a NET/ROM psuedo interface", ifp->name); + TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { + if (ifp2->family != ARPHRD_NETROM) + break; + } + if (ifp2) { + ifp = ifp2; + logger(ifp->ctx, LOG_WARNING, + "picked interface %s to generate a DUID", + ifp->name); + } else { + logger(ifp->ctx, LOG_WARNING, + "no interfaces have a fixed hardware address"); + return duid_make(d, ifp, DUID_LL); + } + } + + if (!(fp = fopen(DUID, "w"))) { + logger(ifp->ctx, LOG_ERR, "error writing DUID: %s: %m", DUID); + return duid_make(d, ifp, DUID_LL); + } + len = duid_make(d, ifp, DUID_LLT); + x = fprintf(fp, "%s\n", hwaddr_ntoa(d, len, line, sizeof(line))); + fclose(fp); + /* Failed to write the duid? scrub it, we cannot use it */ + if (x < 1) { + logger(ifp->ctx, LOG_ERR, "error writing DUID: %s: %m", DUID); + unlink(DUID); + return duid_make(d, ifp, DUID_LL); + } + return len; +} + +size_t duid_init(const struct interface *ifp) +{ + + if (ifp->ctx->duid == NULL) { + ifp->ctx->duid = malloc(DUID_LEN); + if (ifp->ctx->duid == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return 0; + } + ifp->ctx->duid_len = duid_get(ifp->ctx->duid, ifp); + } + return ifp->ctx->duid_len; +} @@ -0,0 +1,35 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DUID_H +#define DUID_H + +#define DUID_LEN 128 + 2 + +size_t duid_init(const struct interface *); + +#endif @@ -0,0 +1,655 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/time.h> + +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcpcd.h" +#include "eloop.h" + +#if defined(HAVE_KQUEUE) +#include <sys/event.h> +#include <fcntl.h> +#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 +#define eloop_event_setup_fds(ctx) +#elif defined(HAVE_EPOLL) +#include <sys/epoll.h> +#define eloop_event_setup_fds(ctx) +#else +#include <poll.h> +static void +eloop_event_setup_fds(struct eloop_ctx *ctx) +{ + struct eloop_event *e; + size_t i; + + i = 0; + TAILQ_FOREACH(e, &ctx->events, next) { + ctx->fds[i].fd = e->fd; + ctx->fds[i].events = 0; + if (e->read_cb) + ctx->fds[i].events |= POLLIN; + if (e->write_cb) + ctx->fds[i].events |= POLLOUT; + ctx->fds[i].revents = 0; + e->pollfd = &ctx->fds[i]; + i++; + } +} +#endif + +int +eloop_event_add(struct eloop_ctx *ctx, 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; +#else + struct pollfd *nfds; +#endif + +#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 */ + TAILQ_FOREACH(e, &ctx->events, next) { + if (e->fd == fd) { + 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(ctx->poll_fd, ke, + e->write_cb || write_cb ? 2 : 1, NULL, 0, NULL); +#elif defined(HAVE_EPOLL) + epe.data.ptr = e; + error = epoll_ctl(ctx->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(ctx); + return error; + } + } + + /* Allocate a new event if no free ones already allocated */ + if ((e = TAILQ_FIRST(&ctx->free_events))) { + TAILQ_REMOVE(&ctx->free_events, e, next); + } else { + e = malloc(sizeof(*e)); + if (e == NULL) + goto err; + } + + /* Ensure we can actually listen to it */ + ctx->events_len++; +#if !defined(HAVE_KQUEUE) && !defined(HAVE_EPOLL) + if (ctx->events_len > ctx->fds_len) { + nfds = realloc(ctx->fds, sizeof(*ctx->fds) * (ctx->fds_len+5)); + if (nfds == NULL) + goto err; + ctx->fds_len += 5; + ctx->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) + 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)); + if (kevent(ctx->poll_fd, ke, write_cb ? 2 : 1, NULL, 0, NULL) == -1) + goto err; +#elif defined(HAVE_EPOLL) + epe.data.ptr = e; + if (epoll_ctl(ctx->poll_fd, EPOLL_CTL_ADD, fd, &epe) == -1) + goto err; +#endif + + /* The order of events should not matter. + * However, some PPP servers love to close the link right after + * sending their final message. So to ensure dhcpcd processes this + * message (which is likely to be that the DHCP addresses are wrong) + * we insert new events at the queue head as the link fd will be + * the first event added. */ + TAILQ_INSERT_HEAD(&ctx->events, e, next); + eloop_event_setup_fds(ctx); + return 0; + +err: + logger(ctx->ctx, LOG_ERR, "%s: %m", __func__); + if (e) { + ctx->events_len--; + TAILQ_INSERT_TAIL(&ctx->free_events, e, next); + } + return -1; +} + +void +eloop_event_delete(struct eloop_ctx *ctx, 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 + + TAILQ_FOREACH(e, &ctx->events, next) { + if (e->fd == fd) { + if (write_only) { + if (e->write_cb) { + e->write_cb = NULL; + e->write_cb_arg = NULL; +#if defined(HAVE_KQUEUE) + EV_SET(&ke[0], (uintptr_t)fd, + EVFILT_WRITE, EV_DELETE, + 0, 0, UPTR(NULL)); + kevent(ctx->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(ctx->poll_fd, EPOLL_CTL_MOD, + fd, &epe); +#endif + } + + } else { + TAILQ_REMOVE(&ctx->events, e, next); +#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(ctx->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(ctx->poll_fd, EPOLL_CTL_DEL, + fd, NULL); +#endif + TAILQ_INSERT_TAIL(&ctx->free_events, e, next); + ctx->events_len--; + } + eloop_event_setup_fds(ctx); + break; + } + } +} + +int +eloop_q_timeout_add_tv(struct eloop_ctx *ctx, int queue, + const struct timespec *when, void (*callback)(void *), void *arg) +{ + struct timespec now, w; + struct eloop_timeout *t, *tt = NULL; + + get_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, &ctx->timeouts, next) { + if (t->callback == callback && t->arg == arg) { + TAILQ_REMOVE(&ctx->timeouts, t, next); + break; + } + } + + if (t == NULL) { + /* No existing, so allocate or grab one from the free pool */ + if ((t = TAILQ_FIRST(&ctx->free_timeouts))) { + TAILQ_REMOVE(&ctx->free_timeouts, t, next); + } else { + t = malloc(sizeof(*t)); + if (t == NULL) { + logger(ctx->ctx, LOG_ERR, "%s: %m", __func__); + 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, &ctx->timeouts, next) { + if (timespeccmp(&t->when, &tt->when, <)) { + TAILQ_INSERT_BEFORE(tt, t, next); + return 0; + } + } + TAILQ_INSERT_TAIL(&ctx->timeouts, t, next); + return 0; +} + +int +eloop_q_timeout_add_sec(struct eloop_ctx *ctx, 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(ctx, queue, &tv, callback, arg); +} + +#if !defined(HAVE_KQUEUE) +int +eloop_timeout_add_now(struct eloop_ctx *ctx, + void (*callback)(void *), void *arg) +{ + + if (ctx->timeout0 != NULL) { + logger(ctx->ctx, LOG_WARNING, + "%s: timeout0 already set", __func__); + return eloop_q_timeout_add_sec(ctx, 0, 0, callback, arg); + } + + ctx->timeout0 = callback; + ctx->timeout0_arg = arg; + return 0; +} +#endif + +void +eloop_q_timeout_delete(struct eloop_ctx *ctx, int queue, + void (*callback)(void *), void *arg) +{ + struct eloop_timeout *t, *tt; + + TAILQ_FOREACH_SAFE(t, &ctx->timeouts, next, tt) { + if ((queue == 0 || t->queue == queue) && + t->arg == arg && + (!callback || t->callback == callback)) + { + TAILQ_REMOVE(&ctx->timeouts, t, next); + TAILQ_INSERT_TAIL(&ctx->free_timeouts, t, next); + } + } +} + +void +eloop_exit(struct eloop_ctx *ctx, int code) +{ + + ctx->exitcode = code; + ctx->exitnow = 1; +} + +#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) +static int +eloop_open(struct eloop_ctx *ctx) +{ +#if defined(HAVE_KQUEUE1) + return (ctx->poll_fd = kqueue1(O_CLOEXEC)); +#elif defined(HAVE_KQUEUE) + int i; + + if ((ctx->poll_fd = kqueue()) == -1) + return -1; + if ((i = fcntl(ctx->poll_fd, F_GETFD, 0)) == -1 || + fcntl(ctx->poll_fd, F_SETFD, i | FD_CLOEXEC) == -1) + { + close(ctx->poll_fd); + ctx->poll_fd = -1; + return -1; + } + + return ctx->poll_fd; +#elif defined (HAVE_EPOLL) + return (ctx->poll_fd = epoll_create1(EPOLL_CLOEXEC)); +#endif +} + +int +eloop_requeue(struct eloop_ctx *ctx) +{ + 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 + + if (ctx->poll_fd != -1) + close(ctx->poll_fd); + if (eloop_open(ctx) == -1) + return -1; +#if defined (HAVE_KQUEUE) + i = 0; + while (dhcpcd_handlesigs[i]) + i++; + TAILQ_FOREACH(e, &ctx->events, next) { + i++; + if (e->write_cb) + i++; + } + + if ((ke = malloc(sizeof(*ke) * i)) == NULL) + return -1; + + for (i = 0; dhcpcd_handlesigs[i]; i++) + EV_SET(&ke[i], (uintptr_t)dhcpcd_handlesigs[i], + EVFILT_SIGNAL, EV_ADD, 0, 0, UPTR(NULL)); + + TAILQ_FOREACH(e, &ctx->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(ctx->poll_fd, ke, LENC(i), NULL, 0, NULL); + free(ke); + +#elif defined(HAVE_EPOLL) + + error = 0; + TAILQ_FOREACH(e, &ctx->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(ctx->poll_fd, EPOLL_CTL_ADD, e->fd, &epe) == -1) + error = -1; + } +#endif + + return error; +} +#endif + +struct eloop_ctx * +eloop_init(struct dhcpcd_ctx *dctx) +{ + struct eloop_ctx *ctx; + struct timespec now; + + /* Check we have a working monotonic clock. */ + if (get_monotonic(&now) == -1) + return NULL; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->ctx = dctx; + TAILQ_INIT(&ctx->events); + TAILQ_INIT(&ctx->free_events); + TAILQ_INIT(&ctx->timeouts); + TAILQ_INIT(&ctx->free_timeouts); + ctx->exitcode = EXIT_FAILURE; +#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) + ctx->poll_fd = -1; +#endif + if (eloop_requeue(ctx) == -1) { + free(ctx); + return NULL; + } + } + + return ctx; +} + +void eloop_free(struct eloop_ctx *ctx) +{ + struct eloop_event *e; + struct eloop_timeout *t; + + if (ctx == NULL) + return; + + while ((e = TAILQ_FIRST(&ctx->events))) { + TAILQ_REMOVE(&ctx->events, e, next); + free(e); + } + while ((e = TAILQ_FIRST(&ctx->free_events))) { + TAILQ_REMOVE(&ctx->free_events, e, next); + free(e); + } + while ((t = TAILQ_FIRST(&ctx->timeouts))) { + TAILQ_REMOVE(&ctx->timeouts, t, next); + free(t); + } + while ((t = TAILQ_FIRST(&ctx->free_timeouts))) { + TAILQ_REMOVE(&ctx->free_timeouts, t, next); + free(t); + } +#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) + close(ctx->poll_fd); +#else + free(ctx->fds); +#endif + free(ctx); +} + +int +eloop_start(struct eloop_ctx *ctx) +{ + int n; + struct eloop_event *e; + struct eloop_timeout *t; + struct timespec now, ts, *tsp; + void (*t0)(void *); +#if defined(HAVE_EPOLL) || !defined(USE_SIGNALS) + int timeout; +#endif +#if defined(HAVE_KQUEUE) + struct kevent ke; +#elif defined(HAVE_EPOLL) + struct epoll_event epe; +#endif + + for (;;) { + if (ctx->exitnow) + break; + + /* Run all timeouts first */ + if (ctx->timeout0) { + t0 = ctx->timeout0; + ctx->timeout0 = NULL; + t0(ctx->timeout0_arg); + continue; + } + if ((t = TAILQ_FIRST(&ctx->timeouts))) { + get_monotonic(&now); + if (timespeccmp(&now, &t->when, >)) { + TAILQ_REMOVE(&ctx->timeouts, t, next); + t->callback(t->arg); + TAILQ_INSERT_TAIL(&ctx->free_timeouts, t, next); + continue; + } + timespecsub(&t->when, &now, &ts); + tsp = &ts; + } else + /* No timeouts, so wait forever */ + tsp = NULL; + + if (tsp == NULL && ctx->events_len == 0) { + logger(ctx->ctx, LOG_ERR, "nothing to do"); + break; + } + +#if defined(HAVE_EPOLL) || !defined(USE_SIGNALS) + 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(ctx->poll_fd, NULL, 0, &ke, 1, tsp); +#elif defined(HAVE_EPOLL) +#ifdef USE_SIGNALS + n = epoll_pwait(ctx->poll_fd, &epe, 1, timeout, + &ctx->ctx->sigset); +#else + n = epoll_wait(ctx->poll_fd, &epe, 1, timeout); +#endif +#else +#ifdef USE_SIGNALS + n = pollts(ctx->fds, (nfds_t)ctx->events_len, tsp, + &ctx->ctx->sigset); +#else + n = poll(ctx->fds, (nfds_t)ctx->events_len, timeout); +#endif +#endif + if (n == -1) { + if (errno == EINTR) + continue; + logger(ctx->ctx, LOG_ERR, "poll: %m"); + break; + } + + /* 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) { + struct dhcpcd_siginfo si; + + si.signo = (int)ke.ident; + dhcpcd_handle_signal(&si); + 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) { + e->write_cb(e->write_cb_arg); + continue; + } + if (epe.events & + (EPOLLIN | EPOLLERR | EPOLLHUP)) + { + e->read_cb(e->read_cb_arg); + continue; + } + } +#else + if (n > 0) { + TAILQ_FOREACH(e, &ctx->events, next) { + if (e->pollfd->revents & POLLOUT && + e->write_cb) + { + e->write_cb(e->write_cb_arg); + break; + } + if (e->pollfd->revents) { + e->read_cb(e->read_cb_arg); + break; + } + } + } +#endif + } + + return ctx->exitcode; +} @@ -0,0 +1,116 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ELOOP_H +#define ELOOP_H + +#include <time.h> + +#include "config.h" + +#ifndef ELOOP_QUEUE + #define ELOOP_QUEUE 1 +#endif + +/* EXIT_FAILURE is a non zero value and EXIT_SUCCESS is zero. + * To add a CONTINUE definition, simply do the opposite of EXIT_FAILURE. */ +#define ELOOP_CONTINUE -EXIT_FAILURE + +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; +#if !defined(HAVE_KQUEUE) && !defined(HAVE_EPOLL) + struct pollfd *pollfd; +#endif +}; + +struct eloop_timeout { + TAILQ_ENTRY(eloop_timeout) next; + struct timespec when; + void (*callback)(void *); + void *arg; + int queue; +}; + +struct eloop_ctx { + struct dhcpcd_ctx *ctx; + + size_t events_len; + TAILQ_HEAD (event_head, eloop_event) events; + struct event_head free_events; + + TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; + struct timeout_head free_timeouts; + + void (*timeout0)(void *); + void *timeout0_arg; + +#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) + int poll_fd; +#else + struct pollfd *fds; + size_t fds_len; +#endif + + int exitnow; + int exitcode; +}; + +#define eloop_timeout_add_tv(a, b, c, d) \ + eloop_q_timeout_add_tv(a, ELOOP_QUEUE, b, c, d) +#define eloop_timeout_add_sec(a, b, c, d) \ + eloop_q_timeout_add_sec(a, ELOOP_QUEUE, b, c, d) +#define eloop_timeout_delete(a, b, c) \ + eloop_q_timeout_delete(a, ELOOP_QUEUE, b, c) + +int eloop_event_add(struct eloop_ctx *, int, + void (*)(void *), void *, + void (*)(void *), void *); +void eloop_event_delete(struct eloop_ctx *, int, int); +int eloop_q_timeout_add_sec(struct eloop_ctx *, int queue, + time_t, void (*)(void *), void *); +int eloop_q_timeout_add_tv(struct eloop_ctx *, int queue, + const struct timespec *, void (*)(void *), void *); +#if !defined(HAVE_KQUEUE) +int eloop_timeout_add_now(struct eloop_ctx *, void (*)(void *), void *); +#endif +void eloop_q_timeout_delete(struct eloop_ctx *, int, void (*)(void *), void *); +struct eloop_ctx * eloop_init(struct dhcpcd_ctx *); +#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) +int eloop_requeue(struct eloop_ctx *); +#else +#define eloop_requeue(a) (0) +#endif +void eloop_free(struct eloop_ctx *); +void eloop_exit(struct eloop_ctx *, int); +int eloop_start(struct eloop_ctx *); + +#endif diff --git a/genembedc b/genembedc new file mode 100755 index 0000000..5bd7b6f --- /dev/null +++ b/genembedc @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +: ${TOOL_SED:=sed} +CONF=${1:-dhcpcd-definitions.conf} + +cat dhcpcd-embedded.c.in +$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" "};" diff --git a/genembedh b/genembedh new file mode 100755 index 0000000..a155cfa --- /dev/null +++ b/genembedh @@ -0,0 +1,15 @@ +#!/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) +INITDEFINE6S=$($TOOL_GREP "^define6 " $CONF | $TOOL_WC -l) +$TOOL_SED \ + -e "s/@INITDEFINES@/$INITDEFINES/" \ + -e "s/@INITDEFINE6S@/$INITDEFINE6S/" \ + $H diff --git a/iconfig.mk b/iconfig.mk new file mode 100644 index 0000000..465e02e --- /dev/null +++ b/iconfig.mk @@ -0,0 +1,7 @@ +# 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} diff --git a/if-bsd.c b/if-bsd.c new file mode 100644 index 0000000..14ea913 --- /dev/null +++ b/if-bsd.c @@ -0,0 +1,1686 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/utsname.h> + +#include <arpa/inet.h> +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_dl.h> +#ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */ +# include <net/if_var.h> +#endif +#include <net/if_media.h> +#include <net/route.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet6/in6_var.h> +#include <netinet6/nd6.h> +#ifdef __DragonFly__ +# include <netproto/802_11/ieee80211_ioctl.h> +#elif __APPLE__ + /* FIXME: Add apple includes so we can work out SSID */ +#else +# include <net80211/ieee80211.h> +# include <net80211/ieee80211_ioctl.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <paths.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if defined(OpenBSD) && OpenBSD >= 201411 +/* OpenBSD dropped the global setting from sysctl but left the #define + * which causes a EPERM error when trying to use it. + * I think both the error and keeping the define are wrong, so we #undef it. */ +#undef IPV6CTL_ACCEPT_RTADV +#endif + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv6.h" +#include "ipv6nd.h" + +#include "bpf-filter.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 + +#define COPYOUT(sin, sa) do { \ + if ((sa) && ((sa)->sa_family == AF_INET || (sa)->sa_family == 255)) \ + (sin) = ((struct sockaddr_in*)(void *)(sa))->sin_addr; \ + } while (0) + +#define COPYOUT6(sin, sa) do { \ + if ((sa) && ((sa)->sa_family == AF_INET6 || (sa)->sa_family == 255)) \ + (sin) = ((struct sockaddr_in6*)(void *)(sa))->sin6_addr; \ + } while (0) + +#ifndef CLLADDR +# define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen)) +#endif + +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_openlinksocket(void) +{ + +#ifdef SOCK_CLOEXEC + return socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); +#else + int s, flags; + + if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) + return -1; + if ((flags = fcntl(s, F_GETFD, 0)) == -1 || + fcntl(s, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(s); + return -1; + } + if ((flags = fcntl(s, F_GETFL, 0)) == -1 || + fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) + { + close(s); + return -1; + } + return s; +#endif +} + +#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 + +static int +if_getssid1(const char *ifname, uint8_t *ssid) +{ + int s, retval = -1; +#if defined(SIOCG80211NWID) + struct ifreq ifr; + struct ieee80211_nwid nwid; +#elif defined(IEEE80211_IOC_SSID) + struct ieee80211req ireq; + char nwid[IEEE80211_NWID_LEN + 1]; +#endif + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + +#if defined(SIOCG80211NWID) /* NetBSD */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + memset(&nwid, 0, sizeof(nwid)); + ifr.ifr_data = (void *)&nwid; + if (ioctl(s, SIOCG80211NWID, &ifr) == 0) { + if (ssid == NULL) + retval = nwid.i_len; + else if (nwid.i_len > IF_SSIDSIZE) { + errno = ENOBUFS; + retval = -1; + } else { + retval = nwid.i_len; + memcpy(ssid, nwid.i_nwid, nwid.i_len); + ssid[nwid.i_len] = '\0'; + } + } +#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_SSIDSIZE) { + errno = ENOBUFS; + retval = -1; + } else { + retval = ireq.i_len; + memcpy(ssid, nwid, ireq.i_len); + ssid[ireq.i_len] = '\0'; + } + } +#endif + + close(s); + return retval; +} + +int +if_getssid(struct interface *ifp) +{ + int r; + + r = if_getssid1(ifp->name, ifp->ssid); + if (r != -1) + ifp->ssid_len = (unsigned int)r; + 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 char *ifname) +{ + int s, r; + struct ifmediareq ifmr; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifmr, 0, sizeof(ifmr)); + strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); + r = ioctl(s, SIOCGIFMEDIA, &ifmr); + close(s); + if (r == -1) + return -1; + if (ifmr.ifm_status & IFM_AVALID && + IFM_TYPE(ifmr.ifm_active) == IFM_IEEE80211) + { + if (if_getssid1(ifname, NULL) == -1) + return 1; + } + return 0; +} + +static void +get_addrs(int type, char *cp, struct sockaddr **sa) +{ + int i; + + for (i = 0; i < RTAX_MAX; i++) { + if (type & (1 << i)) { + sa[i] = (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, struct sockaddr_dl *sdl) +{ + + 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); + } + return NULL; +} +#endif + +#ifdef INET +const char *if_pfname = "Berkley Packet Filter"; + +int +if_openrawsocket(struct interface *ifp, uint16_t protocol) +{ + struct dhcp_state *state; + int fd = -1; + struct ifreq ifr; + int ibuf_len = 0; + size_t buf_len; + struct bpf_version pv; + struct bpf_program pf; +#ifdef BIOCIMMEDIATE + int flags; +#endif +#ifdef _PATH_BPF + fd = open(_PATH_BPF, O_RDWR | O_CLOEXEC | O_NONBLOCK); +#else + char device[32]; + int n = 0; + + do { + snprintf(device, sizeof(device), "/dev/bpf%d", n++); + fd = open(device, O_RDWR | O_CLOEXEC | O_NONBLOCK); + } while (fd == -1 && errno == EBUSY); +#endif + + if (fd == -1) + return -1; + + state = D_STATE(ifp); + + 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) { + logger(ifp->ctx, LOG_ERR, "BPF version mismatch - recompile"); + 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; + if (state->buffer_size != buf_len) { + free(state->buffer); + state->buffer = malloc(buf_len); + if (state->buffer == NULL) + goto eexit; + state->buffer_size = buf_len; + state->buffer_len = state->buffer_pos = 0; + } + +#ifdef BIOCIMMEDIATE + flags = 1; + if (ioctl(fd, BIOCIMMEDIATE, &flags) == -1) + goto eexit; +#endif + + /* Install the DHCP filter */ + memset(&pf, 0, sizeof(pf)); + if (protocol == ETHERTYPE_ARP) { + pf.bf_insns = UNCONST(arp_bpf_filter); + pf.bf_len = arp_bpf_filter_len; + } else { + pf.bf_insns = UNCONST(dhcp_bpf_filter); + pf.bf_len = dhcp_bpf_filter_len; + } + if (ioctl(fd, BIOCSETF, &pf) == -1) + goto eexit; + + return fd; + +eexit: + free(state->buffer); + state->buffer = NULL; + close(fd); + return -1; +} + +ssize_t +if_sendrawpacket(const struct interface *ifp, uint16_t protocol, + const void *data, size_t len) +{ + struct iovec iov[2]; + struct ether_header hw; + int fd; + const struct dhcp_state *state; + + memset(&hw, 0, ETHER_HDR_LEN); + memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN); + hw.ether_type = htons(protocol); + iov[0].iov_base = &hw; + iov[0].iov_len = ETHER_HDR_LEN; + iov[1].iov_base = UNCONST(data); + iov[1].iov_len = len; + state = D_CSTATE(ifp); + if (protocol == ETHERTYPE_ARP) + fd = state->arp_fd; + else + fd = state->raw_fd; + return writev(fd, iov, 2); +} + +/* 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 +if_readrawpacket(struct interface *ifp, uint16_t protocol, + void *data, size_t len, int *flags) +{ + int fd; + struct bpf_hdr packet; + ssize_t bytes; + const unsigned char *payload; + struct dhcp_state *state; + + state = D_STATE(ifp); + if (protocol == ETHERTYPE_ARP) + fd = state->arp_fd; + else + fd = state->raw_fd; + + *flags = 0; + for (;;) { + if (state->buffer_len == 0) { + bytes = read(fd, state->buffer, state->buffer_size); + 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 (packet.bh_caplen != packet.bh_datalen) + goto next; /* Incomplete packet, drop. */ + 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 + ETHER_HDR_LEN; + bytes = (ssize_t)packet.bh_caplen - ETHER_HDR_LEN; + 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 |= RAW_EOF; + } + if (bytes != -1) + return bytes; + } +} + +int +if_address(const struct interface *ifp, const struct in_addr *address, + const struct in_addr *netmask, const struct in_addr *broadcast, + int action) +{ + int s, r; + struct in_aliasreq ifra; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + memset(&ifra, 0, sizeof(ifra)); + strlcpy(ifra.ifra_name, ifp->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, address); + ADDADDR(&ifra.ifra_mask, netmask); + if (action >= 0 && broadcast) + ADDADDR(&ifra.ifra_broadaddr, broadcast); +#undef ADDADDR + + r = ioctl(s, + action < 0 ? SIOCDIFADDR : SIOCAIFADDR, &ifra); + close(s); + return r; +} + +static int +if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct rt_msghdr *rtm) +{ + char *cp; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + + cp = (char *)(void *)(rtm + 1); + sa = (struct sockaddr *)(void *)cp; + if (sa->sa_family != AF_INET) + return -1; + 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, cp, rti_info); + memset(rt, 0, sizeof(*rt)); + COPYOUT(rt->dest, rti_info[RTAX_DST]); + if (rtm->rtm_addrs & RTA_NETMASK) + COPYOUT(rt->net, rti_info[RTAX_NETMASK]); + else + rt->net.s_addr = INADDR_BROADCAST; + COPYOUT(rt->gate, rti_info[RTAX_GATEWAY]); + + if (rtm->rtm_index) + rt->iface = if_findindex(ctx->ifaces, rtm->rtm_index); + else if (rtm->rtm_addrs & RTA_IFP) { + struct sockaddr_dl *sdl; + + sdl = (struct sockaddr_dl *)(void *)rti_info[RTAX_IFP]; + rt->iface = if_findsdl(ctx, sdl); + } + /* If we don't have an interface and it's a host route, it maybe + * to a local ip via the loopback interface. */ + if (rt->iface == NULL && + !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY))) + { + struct ipv4_addr *ia; + + if ((ia = ipv4_findaddr(ctx, &rt->dest))) + rt->iface = ia->iface; + } + + return 0; +} + +int +if_route(unsigned char cmd, const struct rt *rt) +{ + const struct dhcp_state *state; + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_dl sdl; + } su; + struct rtm + { + struct rt_msghdr hdr; + char buffer[sizeof(su) * RTAX_MAX]; + } rtm; + char *bp = rtm.buffer; + size_t l; + int s, retval; + + if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) + return -1; + +#define ADDSU { \ + l = RT_ROUNDUP(su.sa.sa_len); \ + memcpy(bp, &su, l); \ + bp += l; \ + } +#define ADDADDR(addr) { \ + memset(&su, 0, sizeof(su)); \ + su.sin.sin_family = AF_INET; \ + su.sin.sin_len = sizeof(su.sin); \ + (&su.sin)->sin_addr = *addr; \ + ADDSU; \ + } + + if (cmd != RTM_DELETE) + state = D_CSTATE(rt->iface); + else /* appease GCC */ + state = NULL; + memset(&rtm, 0, sizeof(rtm)); + rtm.hdr.rtm_version = RTM_VERSION; + rtm.hdr.rtm_seq = 1; + rtm.hdr.rtm_type = cmd; + rtm.hdr.rtm_addrs = RTA_DST; + if (cmd == RTM_ADD || cmd == RTM_CHANGE) + rtm.hdr.rtm_addrs |= RTA_GATEWAY; + rtm.hdr.rtm_flags = RTF_UP; +#ifdef RTF_PINNED + if (cmd != RTM_ADD) + rtm.hdr.rtm_flags |= RTF_PINNED; +#endif + + if (cmd != RTM_DELETE) { + rtm.hdr.rtm_addrs |= RTA_IFA | RTA_IFP; + /* None interface subnet routes are static. */ + if (rt->gate.s_addr != INADDR_ANY || + rt->net.s_addr != state->net.s_addr || + rt->dest.s_addr != (state->addr.s_addr & state->net.s_addr)) + rtm.hdr.rtm_flags |= RTF_STATIC; + else { +#ifdef RTF_CLONING + rtm.hdr.rtm_flags |= RTF_CLONING; +#endif +#ifdef RTP_CONNECTED + rtm.hdr.rtm_priority = RTP_CONNECTED; +#endif + } + } + if (rt->net.s_addr == htonl(INADDR_BROADCAST) && + rt->gate.s_addr == htonl(INADDR_ANY)) + { +#ifdef RTF_CLONING + /* 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.hdr.rtm_flags |= RTF_CLONING; + rtm.hdr.rtm_addrs |= RTA_NETMASK; +#else + rtm.hdr.rtm_flags |= RTF_HOST; +#endif + } else if (rt->gate.s_addr == htonl(INADDR_LOOPBACK) && + rt->net.s_addr == htonl(INADDR_BROADCAST)) + { + rtm.hdr.rtm_flags |= RTF_HOST | RTF_GATEWAY; + /* Going via lo0 so remove the interface flags */ + if (cmd == RTM_ADD) + rtm.hdr.rtm_addrs &= ~(RTA_IFA | RTA_IFP); + } else { + rtm.hdr.rtm_addrs |= RTA_NETMASK; + if (rtm.hdr.rtm_flags & RTF_STATIC) + rtm.hdr.rtm_flags |= RTF_GATEWAY; + } + if ((cmd == RTM_ADD || cmd == RTM_CHANGE) && + !(rtm.hdr.rtm_flags & RTF_GATEWAY)) + rtm.hdr.rtm_addrs |= RTA_IFA | RTA_IFP; + + ADDADDR(&rt->dest); + if (rtm.hdr.rtm_addrs & RTA_GATEWAY) { +#ifdef RTF_CLONING + if ((rtm.hdr.rtm_flags & (RTF_HOST | RTF_CLONING) && +#else + if ((rtm.hdr.rtm_flags & RTF_HOST && +#endif + rt->gate.s_addr != htonl(INADDR_LOOPBACK)) || + !(rtm.hdr.rtm_flags & RTF_STATIC)) + { + if_linkaddr(&su.sdl, rt->iface); + ADDSU; + } else + ADDADDR(&rt->gate); + } + + if (rtm.hdr.rtm_addrs & RTA_NETMASK) + ADDADDR(&rt->net); + + if ((cmd == RTM_ADD || cmd == RTM_CHANGE) && + (rtm.hdr.rtm_addrs & (RTA_IFP | RTA_IFA))) + { + rtm.hdr.rtm_index = (unsigned short)rt->iface->index; + if (rtm.hdr.rtm_addrs & RTA_IFP) { + if_linkaddr(&su.sdl, rt->iface); + ADDSU; + } + + if (rtm.hdr.rtm_addrs & RTA_IFA) + ADDADDR(&state->addr); + } + +#undef ADDADDR +#undef ADDSU + + rtm.hdr.rtm_msglen = (unsigned short)(bp - (char *)&rtm); + retval = write(s, &rtm, rtm.hdr.rtm_msglen) == -1 ? -1 : 0; + close(s); + return retval; +} + +int +if_initrt(struct interface *ifp) +{ + struct rt_msghdr *rtm; + int mib[6]; + size_t needed; + char *buf, *p, *end; + struct rt rt; + + ipv4_freerts(ifp->ctx->ipv4_kroutes); + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + 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) + return -1; + + end = buf + needed; + for (p = buf; p < end; p += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)p; + if (if_copyrt(ifp->ctx, &rt, rtm) == 0) + ipv4_handlert(ifp->ctx, RTM_ADD, &rt); + } + free(buf); + return 0; +} + +#ifdef SIOCGIFAFLAG_IN +int +if_addrflags(const struct in_addr *addr, const struct interface *ifp) +{ + int s, flags; + struct ifreq ifr; + struct sockaddr_in *sin; + + s = socket(PF_INET, SOCK_DGRAM, 0); + flags = -1; + if (s != -1) { + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + sin = (struct sockaddr_in *)(void *)&ifr.ifr_addr; + sin->sin_family = AF_INET; + sin->sin_addr = *addr; + if (ioctl(s, SIOCGIFAFLAG_IN, &ifr) != -1) + flags = ifr.ifr_addrflags; + close(s); + } + return flags; +} +#else +int +if_addrflags(__unused const struct in_addr *addr, + __unused const struct interface *ifp) +{ + + errno = ENOTSUP; + return 0; +} +#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 addreses */ + 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 +} + +#ifdef __KAME__ +#define DESCOPE(ia6) do { \ + if (IN6_IS_ADDR_LINKLOCAL((ia6))) \ + (ia6)->s6_addr[2] = (ia6)->s6_addr[3] = '\0'; \ + } while (/*CONSTCOND */0) +#else +#define DESCOPE(ia6) +#endif + +int +if_address6(const struct ipv6_addr *a, int action) +{ + int s, r; + struct in6_aliasreq ifa; + struct in6_addr mask; + + if ((s = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) + return -1; + + memset(&ifa, 0, sizeof(ifa)); + strlcpy(ifa.ifra_name, a->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 (a->autoconf) + ifa.ifra_flags |= IN6_IFF_AUTOCONF; +#endif +#ifdef IPV6_MANGETEMPADDR + if (a->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, &a->addr); + ifa_scope(&ifa.ifra_addr, a->iface->index); + ipv6_mask(&mask, a->prefix_len); + ADDADDR(&ifa.ifra_prefixmask, &mask); + ifa.ifra_lifetime.ia6t_vltime = a->prefix_vltime; + ifa.ifra_lifetime.ia6t_pltime = a->prefix_pltime; +#undef ADDADDR + + r = ioctl(s, action < 0 ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &ifa); + close(s); + return r; +} + + +static int +if_copyrt6(struct dhcpcd_ctx *ctx, struct rt6 *rt, struct rt_msghdr *rtm) +{ + char *cp; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + + cp = (char *)(void *)(rtm + 1); + sa = (struct sockaddr *)(void *)cp; + if (sa->sa_family != AF_INET6) + return -1; + if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) + return -1; +#ifdef RTF_CLONED + if (rtm->rtm_flags & (RTF_CLONED | RTF_HOST)) + return -1; +#else + if (rtm->rtm_flags & RTF_HOST) + return -1; +#endif +#ifdef RTF_LOCAL + if (rtm->rtm_flags & RTF_LOCAL) + return -1; +#endif + + get_addrs(rtm->rtm_addrs, cp, rti_info); + memset(rt, 0, sizeof(*rt)); + rt->flags = (unsigned int)rtm->rtm_flags; + COPYOUT6(rt->dest, rti_info[RTAX_DST]); + if (rtm->rtm_addrs & RTA_NETMASK) { + /* + * We need to zero out the struct beyond sin6_len and + * ensure it's valid. + * I have no idea what the invalid data is for, could be + * a kernel bug or actually used for something. + * Either way it needs to be zeroed out. + */ + struct sockaddr_in6 *sin6; + size_t e, i, len = 0, final = 0; + + sin6 = (struct sockaddr_in6 *)(void *)rti_info[RTAX_NETMASK]; + rt->net = sin6->sin6_addr; + e = sin6->sin6_len - offsetof(struct sockaddr_in6, sin6_addr); + if (e > sizeof(struct in6_addr)) + e = sizeof(struct in6_addr); + for (i = 0; i < e; i++) { + switch (rt->net.s6_addr[i] & 0xff) { + case 0xff: + /* We don't really want the length, + * just that it's valid */ + len++; + break; + case 0xfe: + case 0xfc: + case 0xf8: + case 0xf0: + case 0xe0: + case 0xc0: + case 0x80: + len++; + final = 1; + break; + default: + rt->net.s6_addr[i] = 0x00; + final = 1; + break; + } + if (final) + break; + } + if (len == 0) + i = 0; + while (i < sizeof(rt->net.s6_addr)) + rt->net.s6_addr[i++] = 0x00; + } else + ipv6_mask(&rt->net, 128); + COPYOUT6(rt->gate, rti_info[RTAX_GATEWAY]); + + if (rtm->rtm_index) + rt->iface = if_findindex(ctx->ifaces, rtm->rtm_index); + else if (rtm->rtm_addrs & RTA_IFP) { + struct sockaddr_dl *sdl; + + sdl = (struct sockaddr_dl *)(void *)rti_info[RTAX_IFP]; + rt->iface = if_findsdl(ctx, sdl); + } + /* If we don't have an interface and it's a host route, it maybe + * to a local ip via the loopback interface. */ + if (rt->iface == NULL && + !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY))) + { + struct ipv6_addr *ia; + + if ((ia = ipv6_findaddr(ctx, &rt->dest, 0))) + rt->iface = ia->iface; + } + + return 0; +} + +int +if_route6(unsigned char cmd, const struct rt6 *rt) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_in6 sin; + struct sockaddr_dl sdl; + } su; + struct rtm + { + struct rt_msghdr hdr; + char buffer[sizeof(su) * RTAX_MAX]; + } rtm; + char *bp = rtm.buffer; + size_t l; + int s, retval; + + if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) + return -1; + +#define ADDSU { \ + l = RT_ROUNDUP(su.sa.sa_len); \ + memcpy(bp, &su, l); \ + bp += l; \ + } +#define ADDADDRS(addr, scope) { \ + memset(&su, 0, sizeof(su)); \ + su.sin.sin6_family = AF_INET6; \ + su.sin.sin6_len = sizeof(su.sin); \ + (&su.sin)->sin6_addr = *addr; \ + if (scope) \ + ifa_scope(&su.sin, scope); \ + ADDSU; \ + } +#define ADDADDR(addr) ADDADDRS(addr, 0) + + memset(&rtm, 0, sizeof(rtm)); + rtm.hdr.rtm_version = RTM_VERSION; + rtm.hdr.rtm_seq = 1; + rtm.hdr.rtm_type = cmd; + rtm.hdr.rtm_flags = RTF_UP | (int)rt->flags; +#ifdef RTF_PINNED + if (rtm.hdr.rtm_type != RTM_ADD) + rtm.hdr.rtm_flags |= RTF_PINNED; +#endif + rtm.hdr.rtm_addrs = RTA_DST | RTA_NETMASK; + /* None interface subnet routes are static. */ + if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) { +#ifdef RTF_CLONING + rtm.hdr.rtm_flags |= RTF_CLONING; +#endif +#ifdef RTP_CONNECTED + rtm.hdr.rtm_priority = RTP_CONNECTED; +#endif + } else + rtm.hdr.rtm_flags |= RTF_GATEWAY | RTF_STATIC; + + if (cmd == RTM_ADD) + rtm.hdr.rtm_addrs |= RTA_GATEWAY; + if (cmd == RTM_ADD && !(rtm.hdr.rtm_flags & RTF_REJECT)) + rtm.hdr.rtm_addrs |= RTA_IFP | RTA_IFA; + + ADDADDR(&rt->dest); + if (rtm.hdr.rtm_addrs & RTA_GATEWAY) { + if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) { + if_linkaddr(&su.sdl, rt->iface); + ADDSU; + } else { + ADDADDRS(&rt->gate, rt->iface->index); + } + } + + if (rtm.hdr.rtm_addrs & RTA_NETMASK) + ADDADDR(&rt->net); + + if ((cmd == RTM_ADD || cmd == RTM_CHANGE) && + (rtm.hdr.rtm_addrs & (RTA_IFP | RTA_IFA))) + { + rtm.hdr.rtm_index = (unsigned short)rt->iface->index; + if (rtm.hdr.rtm_addrs & RTA_IFP) { + if_linkaddr(&su.sdl, rt->iface); + ADDSU; + } + + if (rtm.hdr.rtm_addrs & RTA_IFA) { + const struct ipv6_addr *lla; + + lla = ipv6_linklocal(rt->iface); + if (lla == NULL) /* unlikely */ + return -1; + ADDADDRS(&lla->addr, rt->iface->index); + } + + if (rt->mtu) { + rtm.hdr.rtm_inits |= RTV_MTU; + rtm.hdr.rtm_rmx.rmx_mtu = rt->mtu; + } + } + +#undef ADDADDR +#undef ADDSU + + rtm.hdr.rtm_msglen = (unsigned short)(bp - (char *)&rtm); + retval = write(s, &rtm, rtm.hdr.rtm_msglen) == -1 ? -1 : 0; + close(s); + return retval; +} + +int +if_initrt6(struct interface *ifp) +{ + struct rt_msghdr *rtm; + int mib[6]; + size_t needed; + char *buf, *p, *end; + struct rt6 rt; + + ipv6_freerts(&ifp->ctx->ipv6->kroutes); + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET6; + 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) + return -1; + + end = buf + needed; + for (p = buf; p < end; p += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)p; + if (if_copyrt6(ifp->ctx, &rt, rtm) == 0) + ipv6_handlert(ifp->ctx, RTM_ADD, &rt); + } + free(buf); + return 0; +} + +int +if_addrflags6(const struct in6_addr *addr, const struct interface *ifp) +{ + int s, flags; + struct in6_ifreq ifr6; + + s = socket(PF_INET6, SOCK_DGRAM, 0); + flags = -1; + if (s != -1) { + 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); + if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) != -1) + flags = ifr6.ifr_ifru.ifru_flags6; + close(s); + } + return flags; +} + +int +if_getlifetime6(struct ipv6_addr *ia) +{ + int s, r; + struct in6_ifreq ifr6; + + s = socket(PF_INET6, SOCK_DGRAM, 0); + r = -1; + if (s != -1) { + 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); + if (ioctl(s, SIOCGIFALIFETIME_IN6, &ifr6) != -1) { + time_t t; + struct in6_addrlifetime *lifetime; + + 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 */ + get_monotonic(&ia->created); + ia->created.tv_sec -= + lifetime->ia6t_vltime - ia->prefix_vltime; + } else + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + + r = 0; + } + close(s); + } + return r; +} +#endif + +int +if_managelink(struct dhcpcd_ctx *ctx) +{ + /* route and ifwatchd like a msg buf size of 2048 */ + char msg[2048], *p, *e, *cp; + ssize_t bytes; + struct rt_msghdr *rtm; + struct if_announcemsghdr *ifan; + struct if_msghdr *ifm; + struct ifa_msghdr *ifam; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + int len; + struct sockaddr_dl sdl; + struct interface *ifp; +#ifdef INET + struct rt rt; +#endif +#ifdef INET6 + struct rt6 rt6; + struct in6_addr ia6, net6; + struct sockaddr_in6 *sin6; +#endif +#if (defined(INET) && defined(IN_IFF_TENTATIVE)) || defined(INET6) + int ifa_flags; +#endif + + if ((bytes = read(ctx->link_fd, msg, sizeof(msg))) == -1) + return -1; + e = msg + bytes; + for (p = msg; p < e; p += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)p; + // Ignore messages generated by us + if (rtm->rtm_pid == getpid()) + break; + switch(rtm->rtm_type) { +#ifdef RTM_IFANNOUNCE + case RTM_IFANNOUNCE: + ifan = (struct if_announcemsghdr *)(void *)p; + 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; + } + break; +#endif + case RTM_IFINFO: + ifm = (struct if_msghdr *)(void *)p; + ifp = if_findindex(ctx->ifaces, ifm->ifm_index); + if (ifp == NULL) + break; + switch (ifm->ifm_data.ifi_link_state) { + case LINK_STATE_DOWN: + len = LINK_DOWN; + break; + case LINK_STATE_UP: + len = LINK_UP; + break; + default: + /* handle_carrier will re-load + * the interface flags and check for + * IFF_RUNNING as some drivers that + * don't handle link state also don't + * set IFF_RUNNING when this routing + * message is generated. + * As such, it is a race ...*/ + len = LINK_UNKNOWN; + break; + } + dhcpcd_handlecarrier(ctx, len, + (unsigned int)ifm->ifm_flags, ifp->name); + break; + case RTM_ADD: + case RTM_CHANGE: + case RTM_DELETE: + cp = (char *)(void *)(rtm + 1); + sa = (struct sockaddr *)(void *)cp; + switch (sa->sa_family) { +#ifdef INET + case AF_INET: + if (if_copyrt(ctx, &rt, rtm) == 0) + ipv4_handlert(ctx, rtm->rtm_type, &rt); + break; +#endif +#ifdef INET6 + case AF_INET6: + if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) + break; + /* + * BSD caches host routes in the + * routing table. + * As such, we should be notified of + * reachability by its existance + * with a hardware address + */ + if (rtm->rtm_flags & (RTF_HOST)) { + get_addrs(rtm->rtm_addrs, cp, rti_info); + COPYOUT6(ia6, rti_info[RTAX_DST]); + DESCOPE(&ia6); + 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, &ia6, + rtm->rtm_type != RTM_DELETE && + sdl.sdl_alen ? + IPV6ND_REACHABLE : 0); + break; + } + + if (if_copyrt6(ctx, &rt6, rtm) == 0) + ipv6_handlert(ctx, rtm->rtm_type, &rt6); + break; +#endif + } + break; +#ifdef RTM_CHGADDR + case RTM_CHGADDR: /* FALLTHROUGH */ +#endif + case RTM_DELADDR: /* FALLTHROUGH */ + case RTM_NEWADDR: + ifam = (struct ifa_msghdr *)(void *)p; + ifp = if_findindex(ctx->ifaces, ifam->ifam_index); + if (ifp == NULL) + break; + cp = (char *)(void *)(ifam + 1); + get_addrs(ifam->ifam_addrs, cp, rti_info); + if (rti_info[RTAX_IFA] == NULL) + break; + switch (rti_info[RTAX_IFA]->sa_family) { + case AF_LINK: +#ifdef RTM_CHGADDR + if (rtm->rtm_type != RTM_CHGADDR) + break; +#else + if (rtm->rtm_type != RTM_NEWADDR) + break; +#endif + memcpy(&sdl, rti_info[RTAX_IFA], + rti_info[RTAX_IFA]->sa_len); + dhcpcd_handlehwaddr(ctx, ifp->name, + (const unsigned char*)CLLADDR(&sdl), + sdl.sdl_alen); + break; +#ifdef INET + case AF_INET: + case 255: /* FIXME: Why 255? */ + COPYOUT(rt.dest, rti_info[RTAX_IFA]); + COPYOUT(rt.net, rti_info[RTAX_NETMASK]); + COPYOUT(rt.gate, rti_info[RTAX_BRD]); + if (rtm->rtm_type == RTM_NEWADDR) { + ifa_flags = if_addrflags(&rt.dest, ifp); + if (ifa_flags == -1) + break; + } else + ifa_flags = 0; + ipv4_handleifa(ctx, rtm->rtm_type, + NULL, ifp->name, + &rt.dest, &rt.net, &rt.gate, ifa_flags); + break; +#endif +#ifdef INET6 + case AF_INET6: + sin6 = (struct sockaddr_in6*)(void *) + rti_info[RTAX_IFA]; + ia6 = sin6->sin6_addr; + DESCOPE(&ia6); + sin6 = (struct sockaddr_in6*)(void *) + rti_info[RTAX_NETMASK]; + net6 = sin6->sin6_addr; + DESCOPE(&net6); + if (rtm->rtm_type == RTM_NEWADDR) { + ifa_flags = if_addrflags6(&ia6, ifp); + if (ifa_flags == -1) + break; + } else + ifa_flags = 0; + ipv6_handleifa(ctx, rtm->rtm_type, NULL, + ifp->name, &ia6, ipv6_prefixlen(&net6), + ifa_flags); + break; +#endif + } + break; + } + } + 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 +#ifdef IPV6CTL_ACCEPT_RTADV +#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 + +#define del_if_nd6_flag(s, ifname, flag) if_nd6_flag((s), (ifp), (flag), -1) +#define get_if_nd6_flag(s, ifname, flag) if_nd6_flag((s), (ifp), (flag), 0) +#define set_if_nd6_flag(s, ifname, flag) if_nd6_flag((s), (ifp), (flag), 1) +static int +if_nd6_flag(int s, const struct interface *ifp, unsigned int flag, int set) +{ + struct in6_ndireq nd; + unsigned int oflags; + + memset(&nd, 0, sizeof(nd)); + strlcpy(nd.ifname, ifp->name, sizeof(nd.ifname)); + if (ioctl(s, SIOCGIFINFO_IN6, &nd) == -1) + return -1; + if (set == 0) + return nd.ndi.flags & flag ? 1 : 0; + + oflags = nd.ndi.flags; + if (set == -1) + nd.ndi.flags &= ~flag; + else + nd.ndi.flags |= flag; + if (oflags == nd.ndi.flags) + return 0; + return ioctl(s, SIOCSIFINFO_FLAGS, &nd); +} + +static int +if_raflush(int s) +{ + char dummy[IFNAMSIZ + 8]; + + strlcpy(dummy, "lo0", sizeof(dummy)); + if (ioctl(s, SIOCSRTRFLUSH_IN6, (void *)&dummy) == -1 || + ioctl(s, SIOCSPFXFLUSH_IN6, (void *)&dummy) == -1) + return -1; + return 0; +} + +#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, int own) +{ + struct ifreq ifr; + int flags; + +#ifndef IFXF_NOINET6 + /* No point in removing the no inet6 flag if it doesn't + * exist and we're not owning inet6. */ + if (! own) + return 0; +#endif + + 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 (own) + flags &= ~IFXF_AUTOCONF6; + if (ifr.ifr_flags == flags) + return 0; + ifr.ifr_flags = flags; + return ioctl(s, SIOCSIFXFLAGS, (void *)&ifr); +} +#endif + +static int +_if_checkipv6(int s, struct dhcpcd_ctx *ctx, + const struct interface *ifp, int own) +{ + int ra; + + if (ifp) { +#ifdef ND6_IFF_OVERRIDE_RTADV + int override; +#endif + +#ifdef ND6_IFF_IFDISABLED + if (del_if_nd6_flag(s, ifp, ND6_IFF_IFDISABLED) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: del_if_nd6_flag: ND6_IFF_IFDISABLED: %m", + ifp->name); + return -1; + } +#endif + +#ifdef ND6_IFF_PERFORMNUD + if (set_if_nd6_flag(s, ifp, ND6_IFF_PERFORMNUD) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: set_if_nd6_flag: ND6_IFF_PERFORMNUD: %m", + ifp->name); + return -1; + } +#endif + +#ifdef ND6_IFF_AUTO_LINKLOCAL + if (own) { + int all; + + all = get_if_nd6_flag(s, ifp, ND6_IFF_AUTO_LINKLOCAL); + if (all == -1) + logger(ifp->ctx, LOG_ERR, + "%s: get_if_nd6_flag: " + "ND6_IFF_AUTO_LINKLOCAL: %m", + ifp->name); + else if (all != 0) { + logger(ifp->ctx, LOG_DEBUG, + "%s: disabling Kernel IPv6 " + "auto link-local support", + ifp->name); + if (del_if_nd6_flag(s, ifp, + ND6_IFF_AUTO_LINKLOCAL) == -1) + { + logger(ifp->ctx, LOG_ERR, + "%s: del_if_nd6_flag: " + "ND6_IFF_AUTO_LINKLOCAL: %m", + ifp->name); + return -1; + } + } + } +#endif + +#ifdef SIOCIFAFATTACH + if (af_attach(s, ifp, AF_INET6) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: af_attach: %m", ifp->name); + return 1; + } +#endif + +#ifdef SIOCGIFXFLAGS + if (set_ifxflags(s, ifp, own) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: set_ifxflags: %m", ifp->name); + return -1; + } +#endif + +#ifdef ND6_IFF_OVERRIDE_RTADV + override = get_if_nd6_flag(s, ifp, ND6_IFF_OVERRIDE_RTADV); + if (override == -1) + logger(ifp->ctx, LOG_ERR, + "%s: get_if_nd6_flag: ND6_IFF_OVERRIDE_RTADV: %m", + ifp->name); + else if (override == 0 && own) { + if (set_if_nd6_flag(s, ifp, ND6_IFF_OVERRIDE_RTADV) + == -1) + logger(ifp->ctx, LOG_ERR, + "%s: set_if_nd6_flag: " + "ND6_IFF_OVERRIDE_RTADV: %m", + ifp->name); + else + override = 1; + } +#endif + +#ifdef ND6_IFF_ACCEPT_RTADV + ra = get_if_nd6_flag(s, ifp, ND6_IFF_ACCEPT_RTADV); + if (ra == -1) + logger(ifp->ctx, LOG_ERR, + "%s: get_if_nd6_flag: ND6_IFF_ACCEPT_RTADV: %m", + ifp->name); + else if (ra != 0 && own) { + logger(ifp->ctx, LOG_DEBUG, + "%s: disabling Kernel IPv6 RA support", + ifp->name); + if (del_if_nd6_flag(s, ifp, ND6_IFF_ACCEPT_RTADV) + == -1) + logger(ifp->ctx, LOG_ERR, + "%s: del_if_nd6_flag: " + "ND6_IFF_ACCEPT_RTADV: %m", + ifp->name); + else + ra = 0; + } else if (ra == 0 && !own) + logger(ifp->ctx, LOG_WARNING, + "%s: IPv6 kernel autoconf disabled", ifp->name); +#ifdef ND6_IFF_OVERRIDE_RTADV + if (override == 0 && ra) + return ctx->ra_global; +#endif + return ra; +#else + return ctx->ra_global; +#endif + } + +#ifdef IPV6CTL_ACCEPT_RTADV + ra = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV); + if (ra == -1) + /* The sysctl probably doesn't exist, but this isn't an + * error as such so just log it and continue */ + logger(ifp->ctx, errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "IPV6CTL_ACCEPT_RTADV: %m"); + else if (ra != 0 && own) { + logger(ifp->ctx, LOG_DEBUG, "disabling Kernel IPv6 RA support"); + if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1) { + logger(ifp->ctx, LOG_ERR, "IPV6CTL_ACCEPT_RTADV: %m"); + return ra; + } + ra = 0; +#else + ra = 0; + if (own) { +#endif + /* 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 (if_raflush(s) == -1) + logger(ctx, LOG_WARNING, "if_raflush: %m"); + } + + ctx->ra_global = ra; + return ra; +} + +int +if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *ifp, int own) +{ + int s, r; + + if ((s = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) + return -1; + r = _if_checkipv6(s, ctx, ifp, own); + close(s); + return r; +} +#endif diff --git a/if-linux-wext.c b/if-linux-wext.c new file mode 100644 index 0000000..860cba9 --- /dev/null +++ b/if-linux-wext.c @@ -0,0 +1,90 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2009-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * THIS IS A NASTY HACK THAT SHOULD NEVER HAVE HAPPENED + * Basically we cannot include linux/if.h and net/if.h because + * they have conflicting structures. + * Sadly, linux/wireless.h includes linux/if.h all the time. + * Some kernel-header installs fix this and some do not. + * This file solely exists for those who do not. + * + * We *could* include wireless.h as that is designed for userspace, + * but that then depends on the correct version of wireless-tools being + * installed which isn't always the case. + */ + +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <linux/types.h> +#include <linux/rtnetlink.h> +/* Support older kernels */ +#ifdef IFLA_WIRELESS +# include <linux/if.h> +# include <linux/wireless.h> +#else +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +#include <string.h> +#include <unistd.h> + +#include "config.h" + +/* We can't include if.h or dhcpcd.h because + * they would pull in net/if.h, which defeats the purpose of this hack. */ +#define IF_SSIDSIZE 33 +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_SSIDSIZE - 1; + + if (ioctl(s, SIOCGIWESSID, &iwr) == 0) { + retval = iwr.u.essid.length; + ssid[retval] = '\0'; + } else + retval = -1; + close(s); + return retval; +#else + /* Stop gcc warning about unused parameters */ + ifname = ssid; + return -1; +#endif +} diff --git a/if-linux.c b/if-linux.c new file mode 100644 index 0000000..9871d6b --- /dev/null +++ b/if-linux.c @@ -0,0 +1,1814 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <asm/types.h> /* Needed for 2.4 kernels */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/param.h> + +#include <linux/if_addr.h> +#include <linux/if_link.h> +#include <linux/if_packet.h> +#include <linux/filter.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/if_ether.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <net/route.h> + +/* Support older kernels */ +#ifndef IFLA_WIRELESS +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +/* Linux has these in an enum and there is just no way to work + * out of they exist at compile time. Silly silly silly. */ +#define IFLA_AF_SPEC 26 +#define IFLA_INET6_ADDR_GEN_MODE 8 +#define IN6_ADDR_GEN_MODE_NONE 1 + +/* 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 + +#include <errno.h> +#include <fcntl.h> +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dev.h" +#include "dhcp.h" +#include "if.h" +#include "ipv4.h" +#include "ipv6.h" +#include "ipv6nd.h" + +#ifdef HAVE_NL80211_H +#include <linux/genetlink.h> +#include <linux/nl80211.h> +#endif +int if_getssid_wext(const char *ifname, uint8_t *ssid); + +#define bpf_insn sock_filter +#define BPF_SKIPTYPE +#define BPF_ETHCOOK -ETH_HLEN +#define BPF_WHOLEPACKET 0x0fffffff /* work around buggy LPF filters */ + +#include "bpf-filter.h" + +/* 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 +}; + +#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 char *ifname) +{ + + return 0; +} + +static int +_open_link_socket(struct sockaddr_nl *nl, int flags, int protocol) +{ + int fd; + +#ifdef SOCK_CLOEXEC + if (flags) + flags = SOCK_CLOEXEC; + fd = socket(AF_NETLINK, SOCK_RAW | flags, protocol); + if (fd == -1) + return -1; +#else + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd == -1) + return -1; + if (flags && + (flags = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(fd); + return -1; + } +#endif + nl->nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) { + close(fd); + return -1; + } + return fd; +} + +int +if_openlinksocket(void) +{ + struct sockaddr_nl snl; + + 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 + + return _open_link_socket(&snl, 1, NETLINK_ROUTE); +} + +static int +err_netlink(struct nlmsghdr *nlm) +{ + struct nlmsgerr *err; + size_t len; + + if (nlm->nlmsg_type != NLMSG_ERROR) + return 0; + len = nlm->nlmsg_len - sizeof(*nlm); + if (len < sizeof(*err)) { + errno = EBADMSG; + return -1; + } + err = (struct nlmsgerr *)NLMSG_DATA(nlm); + if (err->error == 0) + return (int)len; + errno = -err->error; + return -1; +} + +static int +get_netlink(struct dhcpcd_ctx *ctx, struct interface *ifp, int fd, int flags, + int (*callback)(struct dhcpcd_ctx *, struct interface *, struct nlmsghdr *)) +{ + char *buf = NULL, *nbuf; + ssize_t bytes; + size_t buflen; + struct nlmsghdr *nlm; + struct sockaddr_nl nladdr; + socklen_t nladdr_len; + int r; + + buflen = 0; + r = -1; + for (;;) { + bytes = recv(fd, NULL, 0, + flags | MSG_PEEK | MSG_DONTWAIT | MSG_TRUNC); + if (bytes == -1) + goto eexit; + if ((size_t)bytes == buflen) { + /* Support kernels older than 2.6.22 */ + if (bytes == 0) + bytes = 512; + else + bytes *= 2; + } + if (buflen < (size_t)bytes) { + /* Alloc 1 more so we work with older kernels */ + buflen = (size_t)bytes + 1; + nbuf = realloc(buf, buflen); + if (nbuf == NULL) + goto eexit; + buf = nbuf; + } + nladdr_len = sizeof(nladdr); + bytes = recvfrom(fd, buf, buflen, flags, + (struct sockaddr *)&nladdr, &nladdr_len); + if (bytes == -1 || bytes == 0) + goto eexit; + + /* Check sender */ + if (nladdr_len != sizeof(nladdr)) { + errno = EINVAL; + goto eexit; + } + /* Ignore message if it is not from kernel */ + if (nladdr.nl_pid != 0) { + r = 0; + continue; + } + + for (nlm = (struct nlmsghdr *)(void *)buf; + nlm && NLMSG_OK(nlm, (size_t)bytes); + nlm = NLMSG_NEXT(nlm, bytes)) + { + r = err_netlink(nlm); + if (r == -1) + goto eexit; + if (r) + continue; + if (callback) { + r = callback(ctx, ifp, nlm); + if (r != 0) + goto eexit; + } + } + } + +eexit: + free(buf); + return r; +} + +#ifdef INET +static int +if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct nlmsghdr *nlm) +{ + size_t len; + struct rtmsg *rtm; + struct rtattr *rta; + struct in_addr prefsrc; + + 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 || rtm->rtm_family != AF_INET) + return -1; + + memset(rt, 0, sizeof(*rt)); + if (rtm->rtm_type == RTN_UNREACHABLE) + rt->flags = RTF_REJECT; + if (rtm->rtm_scope == RT_SCOPE_HOST) + rt->flags |= RTF_HOST; + + prefsrc.s_addr = INADDR_ANY; + rta = (struct rtattr *)RTM_RTA(rtm); + len = RTM_PAYLOAD(nlm); + while (RTA_OK(rta, len)) { + switch (rta->rta_type) { + case RTA_DST: + memcpy(&rt->dest.s_addr, RTA_DATA(rta), + sizeof(rt->dest.s_addr)); + break; + case RTA_GATEWAY: + memcpy(&rt->gate.s_addr, RTA_DATA(rta), + sizeof(rt->gate.s_addr)); + break; + case RTA_PREFSRC: + memcpy(&prefsrc.s_addr, RTA_DATA(rta), + sizeof(prefsrc.s_addr)); + break; + case RTA_OIF: + rt->iface = if_findindex(ctx->ifaces, + *(unsigned int *)RTA_DATA(rta)); + break; + case RTA_PRIORITY: + rt->metric = *(unsigned int *)RTA_DATA(rta); + break; + } + rta = RTA_NEXT(rta, len); + } + + inet_cidrtoaddr(rtm->rtm_dst_len, &rt->net); + if (rt->iface == NULL && prefsrc.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, &prefsrc))) + rt->iface = ap->iface; + } + return 0; +} +#endif + +#ifdef INET6 +static int +if_copyrt6(struct dhcpcd_ctx *ctx, struct rt6 *rt, struct nlmsghdr *nlm) +{ + size_t len; + struct rtmsg *rtm; + struct rtattr *rta; + + 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 || rtm->rtm_family != AF_INET6) + return -1; + + memset(rt, 0, sizeof(*rt)); + if (rtm->rtm_type == RTN_UNREACHABLE) + rt->flags = RTF_REJECT; + if (rtm->rtm_scope == RT_SCOPE_HOST) + rt->flags |= RTF_HOST; + ipv6_mask(&rt->net, rtm->rtm_dst_len); + + rta = (struct rtattr *)RTM_RTA(rtm); + len = RTM_PAYLOAD(nlm); + while (RTA_OK(rta, len)) { + switch (rta->rta_type) { + case RTA_DST: + memcpy(&rt->dest.s6_addr, RTA_DATA(rta), + sizeof(rt->dest.s6_addr)); + break; + case RTA_GATEWAY: + memcpy(&rt->gate.s6_addr, RTA_DATA(rta), + sizeof(rt->gate.s6_addr)); + break; + case RTA_OIF: + rt->iface = if_findindex(ctx->ifaces, + *(unsigned int *)RTA_DATA(rta)); + break; + case RTA_PRIORITY: + rt->metric = *(unsigned int *)RTA_DATA(rta); + break; + } + rta = RTA_NEXT(rta, len); + } + + return 0; +} +#endif + +/* Work out the maximum pid size */ +static inline long long +get_max_pid_t() +{ + + if (sizeof(pid_t) == sizeof(short)) return SHRT_MAX; + if (sizeof(pid_t) == sizeof(int)) return INT_MAX; + if (sizeof(pid_t) == sizeof(long)) return LONG_MAX; + if (sizeof(pid_t) == sizeof(long long)) return LLONG_MAX; + abort(); +} + +static int +link_route(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, + struct nlmsghdr *nlm) +{ + size_t len; + struct rtmsg *rtm; + int cmd; +#ifdef INET + struct rt rt; +#endif +#ifdef INET6 + struct rt6 rt6; +#endif + 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(*rtm)) { + errno = EBADMSG; + return -1; + } + + /* Ignore messages generated by us. + * For some reason we get messages generated by us + * with a very large value in nlmsg_pid that seems to be + * sequentially changing. Is there a better test for this? */ + if (nlm->nlmsg_pid > get_max_pid_t()) + return 1; + + rtm = NLMSG_DATA(nlm); + switch (rtm->rtm_family) { +#ifdef INET + case AF_INET: + if (if_copyrt(ctx, &rt, nlm) == 0) + ipv4_handlert(ctx, cmd, &rt); + break; +#endif +#ifdef INET6 + case AF_INET6: + if (if_copyrt6(ctx, &rt6, nlm) == 0) + ipv6_handlert(ctx, cmd, &rt6); + break; +#endif + } + + 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; +#ifdef INET + struct in_addr addr, net, dest; +#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; + } + 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 = dest.s_addr = INADDR_ANY; + dest.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(&dest.s_addr, RTA_DATA(rta), + sizeof(addr.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, &dest, ifa->ifa_flags); + 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); + 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; +} + +static int +handle_rename(struct dhcpcd_ctx *ctx, unsigned int ifindex, const char *ifname) +{ + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->index == ifindex && strcmp(ifp->name, ifname)) { + dhcpcd_handleinterface(ctx, -1, ifp->name); + /* Let dev announce the interface for renaming */ + if (!dev_listening(ctx)) + dhcpcd_handleinterface(ctx, 1, ifname); + return 1; + } + } + 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 = (struct rtattr *)(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, RTA_DATA(rta), sizeof(ifn)); + break; + case IFLA_ADDRESS: + hwaddr = rta; + break; + } + rta = RTA_NEXT(rta, len); + } + + if (nlm->nlmsg_type == RTM_DELLINK) { + 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 interface name change */ + if (handle_rename(ctx, (unsigned int)ifi->ifi_index, ifn)) + return 0; + + /* Check for a new interface */ + if ((ifp = if_find(ctx->ifaces, ifn)) == 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; + } + + /* 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_managelink(struct dhcpcd_ctx *ctx) +{ + + return get_netlink(ctx, 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; + struct msghdr msg; + static unsigned int seq; + + memset(&snl, 0, sizeof(snl)); + if ((s = _open_link_socket(&snl, 0, protocol)) == -1) + return -1; + memset(&iov, 0, sizeof(iov)); + iov.iov_base = hdr; + iov.iov_len = hdr->nlmsg_len; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &snl; + msg.msg_namelen = sizeof(snl); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* Request a reply */ + hdr->nlmsg_flags |= NLM_F_ACK; + hdr->nlmsg_seq = ++seq; + + if (sendmsg(s, &msg, 0) != -1) + r = get_netlink(ctx, ifp, s, 0, callback); + else + r = -1; + 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; +} + +#ifdef HAVE_NL80211_H +static struct nlattr * +nla_next(struct nlattr *nla, size_t *rem) +{ + + *rem -= NLA_ALIGN(nla->nla_len); + return (struct nlattr *)(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) ((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 +gnl_parse(struct nlmsghdr *nlm, struct nlattr *tb[], int maxtype) +{ + struct genlmsghdr *ghdr; + struct nlattr *head, *nla; + size_t len, rem; + int type; + + memset(tb, 0, sizeof(*tb) * ((unsigned int)maxtype + 1)); + ghdr = NLMSG_DATA(nlm); + head = (struct nlattr *)(void *)((char *) ghdr + GENL_HDRLEN); + len = nlm->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN; + NLA_FOR_EACH_ATTR(nla, head, len, rem) { + type = NLA_TYPE(nla); + if (type > maxtype) + continue; + tb[type] = nla; + } + return 0; +} + +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 (gnl_parse(nlm, tb, CTRL_ATTR_FAMILY_ID) == -1) + return -1; + if (tb[CTRL_ATTR_FAMILY_ID] == NULL) { + errno = ENOENT; + return -1; + } + family = *(uint16_t *)(void *)NLA_DATA(tb[CTRL_ATTR_FAMILY_ID]); + 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(__unused struct dhcpcd_ctx *ctx, struct interface *ifp, + struct nlmsghdr *nlm) +{ + struct nlattr *tb[NL80211_ATTR_SSID + 1]; + + if (gnl_parse(nlm, tb, NL80211_ATTR_SSID) == -1) + return -1; + + if (tb[NL80211_ATTR_SSID] == NULL) { + /* If the SSID is not found then it means that + * we're not associated to an AP. */ + ifp->ssid_len = 0; + goto out; + } + + ifp->ssid_len = NLA_LEN(tb[NL80211_ATTR_SSID]); + if (ifp->ssid_len > sizeof(ifp->ssid)) { + errno = ENOBUFS; + ifp->ssid_len = 0; + return -1; + } + memcpy(ifp->ssid, NLA_DATA(tb[NL80211_ATTR_SSID]), ifp->ssid_len); + +out: + ifp->ssid[ifp->ssid_len] = '\0'; + return (int)ifp->ssid_len; +} + +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; + 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_INTERFACE; + nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index); + + return send_netlink(ifp->ctx, ifp, + NETLINK_GENERIC, &nlm.hdr, &_if_getssid); +} +#endif + +int +if_getssid(struct interface *ifp) +{ + int r; + + r = if_getssid_wext(ifp->name, ifp->ssid); + if (r != -1) + ifp->ssid_len = (unsigned int)r; +#ifdef HAVE_NL80211_H + else if (r == -1) + r = if_getssid_nl80211(ifp); +#endif + return r; +} + +struct nlma +{ + struct nlmsghdr hdr; + struct ifaddrmsg ifa; + char buffer[64]; +}; + +struct nlmr +{ + struct nlmsghdr hdr; + struct rtmsg rt; + char buffer[256]; +}; + +#ifdef INET +const char *if_pfname = "Packet Socket"; + +int +if_openrawsocket(struct interface *ifp, uint16_t protocol) +{ + int s; + union sockunion { + struct sockaddr sa; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su; + struct sock_fprog pf; +#ifdef PACKET_AUXDATA + int n; +#endif + +#ifdef SOCK_CLOEXEC + if ((s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + htons(protocol))) == -1) + return -1; +#else + int flags; + + if ((s = socket(PF_PACKET, SOCK_DGRAM, htons(protocol))) == -1) + return -1; + if ((flags = fcntl(s, F_GETFD, 0)) == -1 || + fcntl(s, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(s); + return -1; + } + if ((flags = fcntl(s, F_GETFL, 0)) == -1 || + fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) + { + close(s); + return -1; + } +#endif + /* Install the DHCP filter */ + memset(&pf, 0, sizeof(pf)); + if (protocol == ETHERTYPE_ARP) { + pf.filter = UNCONST(arp_bpf_filter); + pf.len = arp_bpf_filter_len; + } else { + pf.filter = UNCONST(dhcp_bpf_filter); + pf.len = dhcp_bpf_filter_len; + } + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) != 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(protocol); + su.sll.sll_ifindex = (int)ifp->index; + if (bind(s, &su.sa, sizeof(su.sll)) == -1) + goto eexit; + return s; + +eexit: + close(s); + return -1; +} + +ssize_t +if_sendrawpacket(const struct interface *ifp, uint16_t protocol, + const void *data, size_t len) +{ + const struct dhcp_state *state; + union sockunion { + struct sockaddr sa; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su; + int fd; + + memset(&su, 0, sizeof(su)); + su.sll.sll_family = AF_PACKET; + su.sll.sll_protocol = htons(protocol); + su.sll.sll_ifindex = (int)ifp->index; + su.sll.sll_hatype = htons(ifp->family); + su.sll.sll_halen = (unsigned char)ifp->hwlen; + if (ifp->family == ARPHRD_INFINIBAND) + memcpy(&su.sll.sll_addr, + &ipv4_bcast_addr, sizeof(ipv4_bcast_addr)); + else + memset(&su.sll.sll_addr, 0xff, ifp->hwlen); + state = D_CSTATE(ifp); + if (protocol == ETHERTYPE_ARP) + fd = state->arp_fd; + else + fd = state->raw_fd; + + return sendto(fd, data, len, 0, &su.sa, sizeof(su.sll)); +} + +ssize_t +if_readrawpacket(struct interface *ifp, uint16_t protocol, + void *data, size_t len, int *flags) +{ + struct iovec iov = { + .iov_base = data, + .iov_len = len, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + struct dhcp_state *state; +#ifdef PACKET_AUXDATA + unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; + struct cmsghdr *cmsg; + struct tpacket_auxdata *aux; +#endif + + ssize_t bytes; + int fd = -1; + +#ifdef PACKET_AUXDATA + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); +#endif + + state = D_STATE(ifp); + if (protocol == ETHERTYPE_ARP) + fd = state->arp_fd; + else + fd = state->raw_fd; + bytes = recvmsg(fd, &msg, 0); + if (bytes == -1) + return -1; + *flags = RAW_EOF; /* We only ever read one packet */ + if (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 |= RAW_PARTIALCSUM; + } + } +#endif + } + return bytes; +} + +int +if_address(const struct interface *iface, + const struct in_addr *address, const struct in_addr *netmask, + const struct in_addr *broadcast, int action) +{ + struct nlma nlm; + int retval = 0; + + memset(&nlm, 0, sizeof(nlm)); + nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + if (action >= 0) { + nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + nlm.hdr.nlmsg_type = RTM_NEWADDR; + } else + nlm.hdr.nlmsg_type = RTM_DELADDR; + nlm.ifa.ifa_index = iface->index; + nlm.ifa.ifa_family = AF_INET; + nlm.ifa.ifa_prefixlen = inet_ntocidr(*netmask); + /* This creates the aliased interface */ + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, + iface->alias, (unsigned short)(strlen(iface->alias) + 1)); + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, + &address->s_addr, sizeof(address->s_addr)); + if (action >= 0 && broadcast) + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_BROADCAST, + &broadcast->s_addr, sizeof(broadcast->s_addr)); + + if (send_netlink(iface->ctx, NULL, NETLINK_ROUTE, &nlm.hdr, NULL) == -1) + retval = -1; + return retval; +} + +int +if_route(unsigned char cmd, const struct rt *rt) +{ + struct nlmr nlm; + int retval = 0; + struct dhcp_state *state; + + 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 = AF_INET; + nlm.rt.rtm_table = RT_TABLE_MAIN; + + state = D_STATE(rt->iface); + if (cmd == RTM_DELETE) + nlm.rt.rtm_scope = RT_SCOPE_NOWHERE; + else { + /* We only change route metrics for kernel routes */ + if (rt->dest.s_addr == + (state->addr.s_addr & state->net.s_addr) && + rt->net.s_addr == state->net.s_addr) + nlm.rt.rtm_protocol = RTPROT_KERNEL; + else + nlm.rt.rtm_protocol = RTPROT_BOOT; + if (rt->iface->flags & IFF_LOOPBACK) + nlm.rt.rtm_scope = RT_SCOPE_HOST; + else if (rt->gate.s_addr == INADDR_ANY || + (rt->gate.s_addr == rt->dest.s_addr && + rt->net.s_addr == INADDR_BROADCAST)) + nlm.rt.rtm_scope = RT_SCOPE_LINK; + else + nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE; + nlm.rt.rtm_type = RTN_UNICAST; + } + + nlm.rt.rtm_dst_len = inet_ntocidr(rt->net); + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_DST, + &rt->dest.s_addr, sizeof(rt->dest.s_addr)); + if (nlm.rt.rtm_protocol == RTPROT_KERNEL) { + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_PREFSRC, + &state->addr.s_addr, sizeof(state->addr.s_addr)); + } + /* If a host route then don't add the gateway */ + if ((cmd == RTM_ADD || cmd == RTM_CHANGE) && + rt->net.s_addr != INADDR_BROADCAST) + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_GATEWAY, + &rt->gate.s_addr, sizeof(rt->gate.s_addr)); + + if (rt->gate.s_addr != htonl(INADDR_LOOPBACK)) + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->iface->index); + if (rt->metric) + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_PRIORITY, rt->metric); + + if (send_netlink(rt->iface->ctx, NULL, + NETLINK_ROUTE, &nlm.hdr, NULL) == -1) + retval = -1; + return retval; +} + +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) + ipv4_handlert(ctx, RTM_ADD, &rt); + return 0; +} + +int +if_initrt(struct interface *ifp) +{ + struct nlmr nlm; + + ipv4_freerts(ifp->ctx->ipv4_kroutes); + + 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.hdr.nlmsg_flags |= NLM_F_REQUEST; + nlm.rt.rtm_family = AF_INET; + nlm.rt.rtm_table = RT_TABLE_MAIN; + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, ifp->index); + + return send_netlink(ifp->ctx, ifp, + NETLINK_ROUTE, &nlm.hdr, &_if_initrt); +} + +int +if_addrflags(__unused const struct in_addr *addr, + __unused const struct interface *ifp) +{ + + /* Linux has no support for IPv4 address flags */ + return 0; +} +#endif + +#ifdef INET6 +int +if_address6(const struct ipv6_addr *ap, int action) +{ + struct nlma nlm; + struct ifa_cacheinfo cinfo; + int retval = 0; +/* 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; + if (action >= 0) { + nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + nlm.hdr.nlmsg_type = RTM_NEWADDR; + } else + nlm.hdr.nlmsg_type = RTM_DELADDR; + nlm.ifa.ifa_index = ap->iface->index; + nlm.ifa.ifa_family = AF_INET6; + if (ap->addr_flags & IFA_F_TEMPORARY) { +#ifdef IFA_F_NOPREFIXROUTE + flags |= IFA_F_TEMPORARY; +#else + nlm.ifa.ifa_flags |= IFA_F_TEMPORARY; +#endif + } +#ifdef IFA_F_MANAGETEMPADDR + else if (ap->flags & IPV6_AF_AUTOCONF && + ip6_use_tempaddr(ap->iface->name)) + flags |= IFA_F_MANAGETEMPADDR; +#endif + + /* Add as /128 if no IFA_F_NOPREFIXROUTE ? */ + nlm.ifa.ifa_prefixlen = ap->prefix_len; + /* This creates the aliased interface */ + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, + ap->iface->alias, (unsigned short)(strlen(ap->iface->alias) + 1)); + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, + &ap->addr.s6_addr, sizeof(ap->addr.s6_addr)); + + if (action >= 0) { + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.ifa_prefered = ap->prefix_pltime; + cinfo.ifa_valid = ap->prefix_vltime; + add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO, + &cinfo, sizeof(cinfo)); + } + +#ifdef IFA_F_NOPREFIXROUTE + if (!IN6_IS_ADDR_LINKLOCAL(&ap->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 + + if (send_netlink(ap->iface->ctx, NULL, NETLINK_ROUTE, &nlm.hdr, + NULL) == -1) + retval = -1; + return retval; +} + +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 = (struct rtattr*)(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; +} + +int +if_route6(unsigned char cmd, const struct rt6 *rt) +{ + struct nlmr nlm; + int retval = 0; + + 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 = AF_INET6; + nlm.rt.rtm_table = RT_TABLE_MAIN; + + if (cmd == RTM_DELETE) + nlm.rt.rtm_scope = RT_SCOPE_NOWHERE; + else { + /* None interface subnet routes are static. */ + if (rt->iface->flags & IFF_LOOPBACK) + nlm.rt.rtm_scope = RT_SCOPE_HOST; + else if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) { + nlm.rt.rtm_protocol = RTPROT_KERNEL; + nlm.rt.rtm_scope = RT_SCOPE_LINK; + } else + nlm.rt.rtm_protocol = RTPROT_BOOT; + if (rt->flags & RTF_REJECT) + nlm.rt.rtm_type = RTN_UNREACHABLE; + else + nlm.rt.rtm_type = RTN_UNICAST; + } + + nlm.rt.rtm_dst_len = ipv6_prefixlen(&rt->net); + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_DST, + &rt->dest.s6_addr, sizeof(rt->dest.s6_addr)); + + if (cmd == RTM_ADD && !IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_GATEWAY, + &rt->gate.s6_addr, sizeof(rt->gate.s6_addr)); + + if (!(rt->flags & RTF_REJECT)) { + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->iface->index); + if (rt->metric) + add_attr_32(&nlm.hdr, sizeof(nlm), + RTA_PRIORITY, rt->metric); + } + if (cmd == RTM_ADD && 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->mtu); + add_attr_l(&nlm.hdr, sizeof(nlm), RTA_METRICS, + RTA_DATA(metrics), (unsigned short)RTA_PAYLOAD(metrics)); + } + + if (send_netlink(rt->iface->ctx, NULL, + NETLINK_ROUTE, &nlm.hdr, NULL) == -1) + retval = -1; + return retval; +} + +static int +_if_initrt6(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, + struct nlmsghdr *nlm) +{ + struct rt6 rt; + + if (if_copyrt6(ctx, &rt, nlm) == 0) + ipv6_handlert(ctx, RTM_ADD, &rt); + return 0; +} + +int +if_initrt6(struct interface *ifp) +{ + struct nlmr nlm; + + ipv6_freerts(&ifp->ctx->ipv6->kroutes); + + 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.hdr.nlmsg_flags |= NLM_F_REQUEST; + nlm.rt.rtm_family = AF_INET6; + nlm.rt.rtm_table = RT_TABLE_MAIN; + add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, ifp->index); + + return send_netlink(ifp->ctx, ifp, + NETLINK_ROUTE, &nlm.hdr, &_if_initrt6); +} + +int +if_addrflags6(const struct in6_addr *addr, const struct interface *ifp) +{ + 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 = ENOTSUP; + 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, int ifindex) +{ + 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 = 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); +} + +static const char *prefix = "/proc/sys/net/ipv6/conf"; + +int +if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *ifp, int own) +{ + const char *ifname; + int ra; + char path[256]; + + if (ifp == NULL) + ifname = "all"; + else if (own) { + if (if_disable_autolinklocal(ctx, (int)ifp->index) == -1) + logger(ctx, LOG_DEBUG, + "%s: if_disable_autolinklocal: %m", ifp->name); + } + if (ifp) + ifname = ifp->name; + + snprintf(path, sizeof(path), "%s/%s/autoconf", prefix, ifname); + ra = check_proc_int(path); + if (ra != 1) { + if (!own) + logger(ctx, LOG_WARNING, + "%s: IPv6 kernel autoconf disabled", ifname); + } else if (ra != -1 && own) { + if (write_path(path, "0") == -1) { + logger(ctx, LOG_ERR, "write_path: %s: %m", path); + return -1; + } + } + + snprintf(path, sizeof(path), "%s/%s/accept_ra", prefix, ifname); + ra = check_proc_int(path); + if (ra == -1) + /* The sysctl probably doesn't exist, but this isn't an + * error as such so just log it and continue */ + logger(ctx, errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "%s: %m", path); + else if (ra != 0 && own) { + logger(ctx, LOG_DEBUG, "%s: disabling kernel IPv6 RA support", + ifname); + if (write_path(path, "0") == -1) { + logger(ctx, LOG_ERR, "write_path: %s: %m", path); + return ra; + } + return 0; + } + + return ra; +} + +#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 */ +#endif /* INET6 */ diff --git a/if-options.c b/if-options.c new file mode 100644 index 0000000..85b6e8e --- /dev/null +++ b/if-options.c @@ -0,0 +1,2368 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _WITH_GETLINE /* Stop FreeBSD bitching */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <grp.h> +#include <inttypes.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "dhcpcd-embedded.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.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 +#define O_IPV6RA_OWN O_BASE + 7 +#define O_IPV6RA_OWN_D 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_PFXDLGMIX 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_IPV6RA_ACCEPT_NOPUBLIC O_BASE + 41 +#define O_BOOTP O_BASE + 42 + +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'}, + {"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'}, + {"nooption", optional_argument, NULL, 'O'}, + {"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'}, + {"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}, + {"ipv6ra_own", no_argument, NULL, O_IPV6RA_OWN}, + {"ipv6ra_own_default", no_argument, NULL, O_IPV6RA_OWN_D}, + {"ipv6ra_accept_nopublic", no_argument, NULL, O_IPV6RA_ACCEPT_NOPUBLIC}, + {"ipv4only", no_argument, NULL, '4'}, + {"ipv6only", no_argument, NULL, '6'}, + {"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}, + {"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}, + {"ia_pd_mix", no_argument, NULL, O_PFXDLGMIX}, + {"reject", required_argument, NULL, O_REJECT}, + {"bootp", no_argument, NULL, O_BOOTP}, + {NULL, 0, NULL, '\0'} +}; + +static char * +add_environ(struct dhcpcd_ctx *ctx, 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) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + p = strchr(match, '='); + if (p == NULL) { + logger(ctx, LOG_ERR, "%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) { + logger(ctx, LOG_ERR, + "%s: %m", __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) { + logger(ctx, LOG_ERR, + "%s: %m", __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) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + newlist = realloc(lst, sizeof(char *) * (i + 2)); + if (newlist == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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]; + + /* 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(*str) { + case '\0': + break; + case 'b': + if (sbuf) + *sbuf++ = '\b'; + str++; + break; + case 'n': + if (sbuf) + *sbuf++ = '\n'; + str++; + break; + case 'r': + if (sbuf) + *sbuf++ = '\r'; + str++; + break; + case 't': + if (sbuf) + *sbuf++ = '\t'; + str++; + 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++ = *str; + str++; + 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); +} + +static int +parse_uint32(uint32_t *i, const char *arg) +{ + + return parse_iaid1((uint8_t *)i, arg, sizeof(uint32_t), 0); +} + +static char ** +splitv(struct dhcpcd_ctx *ctx, int *argc, char **argv, const char *arg) +{ + char **n, **v = argv; + char *o = strdup(arg), *p, *t, *nt; + + if (o == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return v; + } + p = o; + while ((t = strsep(&p, ", "))) { + nt = strdup(t); + if (nt == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + free(o); + return v; + } + n = realloc(v, sizeof(char *) * ((size_t)(*argc) + 1)); + if (n == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + free(o); + free(nt); + return v; + } + v = n; + v[(*argc)++] = nt; + } + free(o); + return v; +} + +#ifdef INET +static int +parse_addr(struct dhcpcd_ctx *ctx, + struct in_addr *addr, struct in_addr *net, const char *arg) +{ + char *p; + int i; + + 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) { + *p++ = '\0'; + if (net != NULL && + (sscanf(p, "%d", &i) != 1 || + inet_cidrtoaddr(i, net) != 0)) + { + logger(ctx, LOG_ERR, "`%s' is not a valid CIDR", p); + return -1; + } + } + + if (addr != NULL && inet_aton(arg, addr) == 0) { + logger(ctx, LOG_ERR, "`%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(struct dhcpcd_ctx *ctx, + __unused struct in_addr *addr, __unused struct in_addr *net, + __unused const char *arg) +{ + + logger(ctx, LOG_ERR, "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) + /* Satisfy use */ + ctx = NULL; +#endif + +#ifdef INET6 + 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) + return NULL; + while (*s == ' ' || *s == '\t') { + if (*s == '\0') + return NULL; + s++; + } + return UNCONST(s); +} + +/* 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); +} + +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, *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 token *token; + struct group *grp; +#ifdef _REENTRANT + struct group grpbuf; +#endif +#ifdef INET6 + size_t sl; + struct if_ia *ia; + uint8_t iaid[4]; + struct if_sla *sla, *slap; +#endif + + dop = NULL; + dop_len = NULL; +#ifdef INET6 + i = 0; +#endif + switch(opt) { + case 'f': /* FALLTHROUGH */ + case 'g': /* FALLTHROUGH */ + case 'n': /* FALLTHROUGH */ + case 'x': /* 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': + free(ifo->script); + ifo->script = strdup(arg); + if (ifo->script == NULL) + logger(ctx, LOG_ERR, "%s: %m", __func__); + break; + case 'd': + ifo->options |= DHCPCD_DEBUG; + break; + case 'e': + add_environ(ctx, 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) { + logger(ctx, LOG_ERR, "hostname: %m"); + return -1; + } + if (s != 0 && ifo->hostname[0] == '.') { + logger(ctx, LOG_ERR, "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) { + logger(ctx, LOG_ERR, "vendorclassid: %m"); + return -1; + } + *ifo->vendorclassid = (uint8_t)s; + break; + case 'j': + /* per interface logging is not supported + * don't want to overide the commandline */ + if (ifname == NULL && ctx->logfile == NULL) { + logger_close(ctx); + ctx->logfile = strdup(arg); + logger_open(ctx); + } + break; + case 'k': + ifo->options |= DHCPCD_RELEASE; + break; + case 'l': + ifo->leasetime = (uint32_t)strtou(arg, NULL, + 0, 0, UINT32_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "failed to convert leasetime %s", arg); + return -1; + } + break; + case 'm': + ifo->metric = (int)strtoi(arg, NULL, 0, 0, INT32_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "failed to convert metric %s", arg); + return -1; + } + break; + case 'o': + 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) + { + logger(ctx, LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case O_REJECT: + 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) + { + logger(ctx, LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'p': + ifo->options |= DHCPCD_PERSISTENT; + break; + case 'q': + ifo->options |= DHCPCD_QUIET; + break; + case 'r': + if (parse_addr(ctx, &ifo->req_addr, NULL, arg) != 0) + return -1; + ifo->options |= DHCPCD_REQUEST; + ifo->req_mask.s_addr = 0; + break; + case 's': + if (ifo->options & DHCPCD_IPV6 && + !(ifo->options & DHCPCD_IPV4)) + { + ifo->options |= DHCPCD_INFORM; + break; + } + if (arg && *arg != '\0') { + if (parse_addr(ctx, + &ifo->req_addr, &ifo->req_mask, arg) != 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_ARP | DHCPCD_STATIC); + break; + case 't': + ifo->timeout = (time_t)strtoi(arg, NULL, 0, 0, INT32_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "failed to convert timeout"); + 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) { + logger(ctx, LOG_ERR, "userclass: %m"); + 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': + p = strchr(arg, ','); + if (!p || !p[1]) { + logger(ctx, LOG_ERR, "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) { + logger(ctx, LOG_ERR, "vendor: %m"); + 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) { + logger(ctx, LOG_ERR, "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) { + logger(ctx, LOG_ERR, "vendor: %m"); + 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': + ifo->reboot = (time_t)strtoi(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "failed to convert reboot %s", arg); + return -1; + } + break; + case 'z': + if (ifname == NULL) + ctx->ifav = splitv(ctx, &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': + /* 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) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + snprintf(p, dl, "skip_hooks=%s", arg); + add_environ(ctx, 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 { + logger(ctx, LOG_ERR, "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) { + logger(ctx, LOG_ERR, "clientid: %m"); + 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 = 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) + { + logger(ctx, LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'Q': + 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) + { + logger(ctx, LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'S': + p = strchr(arg, '='); + if (p == NULL) { + logger(ctx, LOG_ERR, "static assignment required"); + return -1; + } + p++; + if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { + if (parse_addr(ctx, &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(ctx, &ifo->req_mask, 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) + { + fp = np = strwhite(p); + if (np == NULL) { + logger(ctx, LOG_ERR, + "all routes need a gateway"); + return -1; + } + *np++ = '\0'; + np = strskipwhite(np); + if (ifo->routes == NULL) { + ifo->routes = malloc(sizeof(*ifo->routes)); + if (ifo->routes == NULL) { + logger(ctx, LOG_ERR, + "%s: %m", __func__); + return -1; + } + TAILQ_INIT(ifo->routes); + } + rt = calloc(1, sizeof(*rt)); + if (rt == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + *fp = ' '; + return -1; + } + if (parse_addr(ctx, &rt->dest, &rt->net, p) == -1 || + parse_addr(ctx, &rt->gate, NULL, np) == -1) + { + free(rt); + *fp = ' '; + return -1; + } + TAILQ_INSERT_TAIL(ifo->routes, rt, next); + *fp = ' '; + } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) { + if (ifo->routes == NULL) { + ifo->routes = malloc(sizeof(*ifo->routes)); + if (ifo->routes == NULL) { + logger(ctx, LOG_ERR, + "%s: %m", __func__); + return -1; + } + TAILQ_INIT(ifo->routes); + } + rt = calloc(1, sizeof(*rt)); + if (rt == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + rt->dest.s_addr = INADDR_ANY; + rt->net.s_addr = INADDR_ANY; + if (parse_addr(ctx, &rt->gate, NULL, p) == -1) { + free(rt); + return -1; + } + TAILQ_INSERT_TAIL(ifo->routes, rt, next); + } 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) { + logger(ctx, LOG_ERR, + "%s: %m", __func__); + return -1; + } + free(ifo->config[dl]); + ifo->config[dl] = p; + return 1; + } + dl++; + } + } + p = strdup(arg); + if (p == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + nconf = realloc(ifo->config, sizeof(char *) * (dl + 2)); + if (nconf == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + ifo->config = nconf; + ifo->config[dl] = p; + ifo->config[dl + 1] = NULL; + } + break; + case 'W': + if (parse_addr(ctx, &addr, &addr2, arg) != 0) + return -1; + if (strchr(arg, '/') == NULL) + addr2.s_addr = INADDR_BROADCAST; + naddr = realloc(ifo->whitelist, + sizeof(in_addr_t) * (ifo->whitelist_len + 2)); + if (naddr == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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(ctx, &addr, &addr2, arg) != 0) + return -1; + if (strchr(arg, '/') == NULL) + addr2.s_addr = INADDR_BROADCAST; + naddr = realloc(ifo->blacklist, + sizeof(in_addr_t) * (ifo->blacklist_len + 2)); + if (naddr == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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': + if (ifname == NULL) + ctx->ifdv = splitv(ctx, &ctx->ifdc, ctx->ifdv, arg); + 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 && *arg != '\0') { + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + if (parse_addr(ctx, &addr, NULL, arg) != 0) + return -1; + naddr = realloc(ifo->arping, + sizeof(in_addr_t) * (ifo->arping_len + 1)); + if (naddr == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + ifo->arping = naddr; + ifo->arping[ifo->arping_len++] = addr.s_addr; + arg = strskipwhite(fp); + } + break; + case O_DESTINATION: + 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) + logger(ctx, LOG_ERR, "option `%s' does not take" + " an IPv4 address", arg); + else + logger(ctx, LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case O_FALLBACK: + free(ifo->fallback); + ifo->fallback = strdup(arg); + if (ifo->fallback == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + break; +#endif + case O_IAID: + if (ifname == NULL) { + logger(ctx, LOG_ERR, + "IAID must belong in an interface block"); + return -1; + } + if (parse_iaid(ifo->iaid, arg, sizeof(ifo->iaid)) == -1) { + logger(ctx, LOG_ERR, "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_OWN: + ifo->options |= DHCPCD_IPV6RA_OWN; + break; + case O_IPV6RA_OWN_D: + ifo->options |= DHCPCD_IPV6RA_OWN_DEFAULT; + break; + case O_IPV6RA_ACCEPT_NOPUBLIC: + ifo->options |= DHCPCD_IPV6RA_ACCEPT_NOPUBLIC; + 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 INET6 + 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) { + if (ifname == NULL) { + logger(ctx, LOG_ERR, + "IA PD must belong in an interface block"); + return -1; + } + i = D6_OPTION_IA_PD; + } + if (ifname == NULL && arg) { + logger(ctx, LOG_ERR, + "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) { + logger(ctx, LOG_ERR, "invalid IAID: %s", arg); + return -1; + } + } + ia = NULL; + for (sl = 0; sl < ifo->ia_len; sl++) { + if ((arg == NULL && !ifo->ia[sl].iaid_set) || + (ifo->ia[sl].iaid_set && + 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 && ia->ia_type != (uint16_t)i) { + logger(ctx, LOG_ERR, "Cannot mix IA for the same IAID"); + break; + } + if (ia == NULL) { + ia = realloc(ifo->ia, + sizeof(*ifo->ia) * (ifo->ia_len + 1)); + if (ia == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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) { + logger(ctx, LOG_ERR, "%s: %m", arg); + memset(&ia->addr, 0, sizeof(ia->addr)); + } + if (p && ia->ia_type == D6_OPTION_IA_PD) { + i = (int)strtoi(p, NULL, 0, 8, 120, &e); + if (e) { + logger(ctx, LOG_ERR, + "%s: failed to convert" + " prefix len", + p); + ia->prefix_len = 0; + } else + ia->prefix_len = (uint8_t)i; + } + } + ia->sla_max = 0; + ia->sla_len = 0; + ia->sla = NULL; + } + if (ia->ia_type != D6_OPTION_IA_PD) + break; + for (p = fp; p; p = fp) { + fp = strwhite(p); + if (fp) { + *fp++ = '\0'; + fp = strskipwhite(fp); + } + sla = realloc(ia->sla, + sizeof(*ia->sla) * (ia->sla_len + 1)); + if (sla == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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)) + { + logger(ctx, LOG_ERR, "%s: interface name too long", + arg); + goto err_sla; + } + p = np; + if (p) { + np = strchr(p, '/'); + if (np) + *np++ = '\0'; + if (*p == '\0') + sla->sla_set = 0; + else { + sla->sla = (uint32_t)strtou(p, NULL, + 0, 0, UINT32_MAX, &e); + sla->sla_set = 1; + if (e) { + logger(ctx, LOG_ERR, + "%s: failed to convert sla", + ifname); + goto err_sla; + } + } + if (np) { + sla->prefix_len = (uint8_t)strtoi(np, + NULL, 0, 0, 128, &e); + if (e) { + logger(ctx, LOG_ERR, "%s: failed to " + "convert prefix len", + ifname); + goto err_sla; + } + } else + sla->prefix_len = 0; + } else { + sla->sla_set = 0; + sla->prefix_len = 0; + } + /* Sanity check */ + for (sl = 0; sl < ia->sla_len - 1; sl++) { + slap = &ia->sla[sl]; + if (slap->sla_set != sla->sla_set) { + logger(ctx, LOG_WARNING, + "%s: cannot mix automatic " + "and fixed SLA", + sla->ifname); + goto err_sla; + } + if (sla->sla_set == 0 && + strcmp(slap->ifname, sla->ifname) == 0) + { + logger(ctx, LOG_WARNING, + "%s: cannot specify the " + "same interface twice with " + "an automatic SLA", + sla->ifname); + goto err_sla; + } + if (slap->sla == 0 || sla->sla == 0) { + logger(ctx, LOG_ERR, "%s: cannot" + " assign multiple prefixes" + " with a SLA of 0", + ifname); + 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 + case O_HOSTNAME_SHORT: + ifo->options |= DHCPCD_HOSTNAME | DHCPCD_HOSTNAME_SHORT; + break; + case O_DEV: +#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_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 { + logger(ctx, LOG_ERR, + "embed must be after a define or encap"); + return -1; + } + } + /* FALLTHROUGH */ + case O_ENCAP: + if (dop == NULL) { + if (*ldop == NULL) { + logger(ctx, LOG_ERR, "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) { + logger(ctx, LOG_ERR, "invalid syntax: %s", arg); + return -1; + } + *fp++ = '\0'; + u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "invalid code: %s", arg); + return -1; + } + arg = strskipwhite(fp); + if (arg == NULL) { + logger(ctx, LOG_ERR, "invalid syntax"); + return -1; + } + } + /* type */ + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + np = strchr(arg, ':'); + /* length */ + if (np) { + *np++ = '\0'; + l = (long)strtou(np, NULL, 0, 0, LONG_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "failed to convert length"); + return -1; + } + } else + l = 0; + t = 0; + if (strcasecmp(arg, "request") == 0) { + t |= REQUEST; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logger(ctx, LOG_ERR, "incomplete request type"); + return -1; + } + *fp++ = '\0'; + } else if (strcasecmp(arg, "norequest") == 0) { + t |= NOREQ; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logger(ctx, LOG_ERR, "incomplete request type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "index") == 0) { + t |= INDEX; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logger(ctx, LOG_ERR, "incomplete index type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "array") == 0) { + t |= ARRAY; + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp == NULL) { + logger(ctx, LOG_ERR, "incomplete array type"); + return -1; + } + *fp++ = '\0'; + } + if (strcasecmp(arg, "ipaddress") == 0) + t |= ADDRIPV4; + else if (strcasecmp(arg, "ip6address") == 0) + t |= ADDRIPV6; + else if (strcasecmp(arg, "string") == 0) + t |= STRING; + else if (strcasecmp(arg, "byte") == 0) + t |= UINT8; + else if (strcasecmp(arg, "uint16") == 0) + t |= UINT16; + else if (strcasecmp(arg, "int16") == 0) + t |= SINT16; + else if (strcasecmp(arg, "uint32") == 0) + t |= UINT32; + else if (strcasecmp(arg, "int32") == 0) + t |= SINT32; + else if (strcasecmp(arg, "flag") == 0) + t |= FLAG; + else if (strcasecmp(arg, "raw") == 0) + t |= STRING | RAW; + else if (strcasecmp(arg, "ascii") == 0) + t |= STRING | ASCII; + else if (strcasecmp(arg, "domain") == 0) + t |= STRING | DOMAIN | RFC3397; + else if (strcasecmp(arg, "dname") == 0) + t |= STRING | DOMAIN; + else if (strcasecmp(arg, "binhex") == 0) + t |= STRING | BINHEX; + else if (strcasecmp(arg, "embed") == 0) + t |= EMBED; + else if (strcasecmp(arg, "encap") == 0) + t |= ENCAP; + else if (strcasecmp(arg, "rfc3361") ==0) + t |= STRING | RFC3361; + else if (strcasecmp(arg, "rfc3442") ==0) + t |= STRING | RFC3442; + else if (strcasecmp(arg, "rfc5969") == 0) + t |= STRING | RFC5969; + else if (strcasecmp(arg, "option") == 0) + t |= OPTION; + else { + logger(ctx, LOG_ERR, "unknown type: %s", arg); + return -1; + } + if (l && !(t & (STRING | BINHEX))) { + logger(ctx, LOG_WARNING, + "ignoring length for type `%s'", arg); + l = 0; + } + if (t & ARRAY && t & (STRING | BINHEX) && + !(t & (RFC3397 | DOMAIN))) + { + logger(ctx, LOG_WARNING, "ignoring array for strings"); + t &= ~ARRAY; + } + /* variable */ + if (!fp) { + if (!(t & OPTION)) { + logger(ctx, LOG_ERR, + "type %s requires a variable name", arg); + return -1; + } + np = NULL; + } else { + arg = strskipwhite(fp); + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + np = strdup(arg); + if (np == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + } + 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) { + if ((ndop = realloc(*dop, + sizeof(**dop) * ((*dop_len) + 1))) == NULL) + { + logger(ctx, LOG_ERR, "%s: %m", __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; + /* Save the define for embed and encap options */ + if (opt == O_DEFINE || opt == O_DEFINE6 || opt == O_VENDOPT) + *ldop = ndop; + else if (opt == O_ENCAP) + *edop = ndop; + break; + case O_VENDCLASS: + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); + if (e) { + logger(ctx, LOG_ERR, "invalid code: %s", arg); + return -1; + } + if (fp) { + s = parse_string(NULL, 0, fp); + if (s == -1) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + dl = (size_t)s; + if (dl + (sizeof(uint16_t) * 2) > UINT16_MAX) { + logger(ctx, LOG_ERR, "vendor class is too big"); + return -1; + } + np = malloc(dl); + if (np == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + parse_string(np, dl, fp); + } else { + dl = 0; + np = NULL; + } + vivco = realloc(ifo->vivco, sizeof(*ifo->vivco) * + (ifo->vivco_len + 1)); + if (vivco == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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: + 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 { + logger(ctx, LOG_ERR, "%s: unsupported protocol", arg); + return -1; + } + arg = strskipwhite(fp); + fp = strwhite(arg); + if (arg == NULL) { + ifo->auth.options |= DHCPCD_AUTH_SEND; + ifo->auth.algorithm = AUTH_ALG_HMAC_MD5; + ifo->auth.rdm = AUTH_RDM_MONOTONIC; + break; + } + if (fp) + *fp++ = '\0'; + if (strcasecmp(arg, "hmacmd5") == 0 || + strcasecmp(arg, "hmac-md5") == 0) + ifo->auth.algorithm = AUTH_ALG_HMAC_MD5; + else { + logger(ctx, LOG_ERR, "%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 { + logger(ctx, LOG_ERR, "%s: unsupported RDM", arg); + return -1; + } + ifo->auth.options |= DHCPCD_AUTH_SEND; + break; + case O_AUTHTOKEN: + fp = strwhite(arg); + if (fp == NULL) { + logger(ctx, LOG_ERR, "authtoken requires a realm"); + return -1; + } + *fp++ = '\0'; + token = malloc(sizeof(*token)); + if (token == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + free(token); + return -1; + } + if (parse_uint32(&token->secretid, arg) == -1) { + logger(ctx, LOG_ERR, "%s: not a number", arg); + free(token); + return -1; + } + arg = fp; + fp = strend(arg); + if (fp == NULL) { + logger(ctx, LOG_ERR, "authtoken requies an a key"); + free(token); + return -1; + } + *fp++ = '\0'; + s = parse_string(NULL, 0, arg); + if (s == -1) { + logger(ctx, LOG_ERR, "realm_len: %m"); + free(token); + return -1; + } + if (s) { + token->realm_len = (size_t)s; + token->realm = malloc(token->realm_len); + if (token->realm == NULL) { + free(token); + logger(ctx, LOG_ERR, "%s: %m", __func__); + 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) { + logger(ctx, LOG_ERR, "authtoken requies an 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) { + logger(ctx, LOG_ERR, "%s: invalid date time", arg); + free(token->realm); + free(token); + return -1; + } + if ((token->expire = mktime(&tm)) == (time_t)-1) { + logger(ctx, LOG_ERR, "%s: mktime: %m", __func__); + free(token->realm); + free(token); + return -1; + } + } + arg = fp; + s = parse_string(NULL, 0, arg); + if (s == -1 || s == 0) { + logger(ctx, LOG_ERR, s == -1 ? "token_len: %m" : + "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); + 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: +#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) { + logger(ctx, LOG_ERR, "%s: malloc: %m", __func__); + return -1; + } + while ((i = getgrnam_r(arg, &grpbuf, p, (size_t)l, &grp)) == + ERANGE) + { + size_t nl = dl * 2; + if (nl < dl) { + logger(ctx, LOG_ERR, "control_group: out of buffer"); + free(p); + return -1; + } + dl = nl; + np = realloc(p, dl); + if (np == NULL) { + logger(ctx, LOG_ERR, "control_group: realloc: %m"); + free(p); + return -1; + } + p = np; + } + if (i != 0) { + errno = i; + logger(ctx, LOG_ERR, "getgrnam_r: %m"); + free(p); + return -1; + } + if (grp == NULL) { + logger(ctx, LOG_ERR, "controlgroup: %s: not found", arg); + free(p); + return -1; + } + ctx->control_group = grp->gr_gid; + free(p); +#else + grp = getgrnam(arg); + if (grp == NULL) { + logger(ctx, LOG_ERR, "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_SLAAC: + 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_PFXDLGMIX: + ifo->options |= DHCPCD_PFXDLGMIX; + break; + case O_BOOTP: + ifo->options |= DHCPCD_BOOTP; + break; + default: + return 0; + } + + return 1; +} + +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) { + fprintf(stderr, + PACKAGE ": option requires an argument -- %s\n", + opt); + return -1; + } + + return parse_option(ctx, ifname, ifo, cf_options[i].val, line, + ldop, edop); + } + + logger(ctx, LOG_ERR, "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; + ssize_t bytes; + + 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'; + return p; +} + +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 = 0, have_profile = 0; +#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 */ + ifo = calloc(1, sizeof(*ifo)); + if (ifo == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + ifo->options |= DHCPCD_DAEMONISE | DHCPCD_LINK; +#ifdef PLUGIN_DEV + ifo->options |= DHCPCD_DEV; +#endif +#ifdef INET + ifo->options |= DHCPCD_IPV4 | DHCPCD_DHCP | DHCPCD_IPV4LL; + ifo->options |= DHCPCD_GATEWAY | DHCPCD_ARP; +#endif +#ifdef INET6 + ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS; + ifo->options |= DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS; + ifo->options |= DHCPCD_DHCP6; +#endif + ifo->timeout = DEFAULT_TIMEOUT; + ifo->reboot = DEFAULT_REBOOT; + ifo->metric = -1; + ifo->auth.options |= DHCPCD_AUTH_REQUIRE; + TAILQ_INIT(&ifo->auth.tokens); + + 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) { + /* Space for initial estimates */ +#if defined(INET) && defined(INITDEFINES) + ifo->dhcp_override = + calloc(INITDEFINES, sizeof(*ifo->dhcp_override)); + if (ifo->dhcp_override == NULL) + logger(ctx, LOG_ERR, "%s: %m", __func__); + else + ifo->dhcp_override_len = INITDEFINES; +#endif + +#if defined(INET6) && defined(INITDEFINE6S) + ifo->dhcp6_override = + calloc(INITDEFINE6S, sizeof(*ifo->dhcp6_override)); + if (ifo->dhcp6_override == NULL) + logger(ctx, LOG_ERR, "%s: %m", __func__); + else + ifo->dhcp6_override_len = INITDEFINE6S; +#endif + + /* Now load our embedded config */ +#ifdef EMBEDDED_CONFIG + fp = fopen(EMBEDDED_CONFIG, "r"); + if (fp == NULL) + logger(ctx, LOG_ERR, "fopen `%s': %m", EMBEDDED_CONFIG); + + while (fp && (line = get_line(&buf, &buflen, fp))) { +#else + buflen = 80; + buf = malloc(buflen); + if (buf == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + ldop = edop = NULL; + for (e = dhcpcd_embedded_conf; *e; e++) { + ol = strlen(*e) + 1; + if (ol > buflen) { + buflen = ol; + buf = realloc(buf, buflen); + if (buf == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + free(buf); + return NULL; + } + } + memcpy(buf, *e, ol); + line = buf; +#endif + option = strsep(&line, " \t"); + if (line) + line = strskipwhite(line); + /* Trim trailing whitespace */ + if (line && *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->dhcp6_opts = ifo->dhcp6_override; + ctx->dhcp6_opts_len = ifo->dhcp6_override_len; +#else + 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->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) { + if (strcmp(ctx->cffile, CONFIG)) + logger(ctx, LOG_ERR, "fopen `%s': %m", ctx->cffile); + free(buf); + return ifo; + } + if (stat(ctx->cffile, &sb) == 0) + ifo->mtime = sb.st_mtime; + + ldop = edop = NULL; + while ((line = get_line(&buf, &buflen, fp))) { + option = strsep(&line, " \t"); + if (line) + line = strskipwhite(line); + /* Trim trailing whitespace */ + if (line && *line) { + p = line + strlen(line) - 1; + while (p != line && + (*p == ' ' || *p == '\t') && + *(p - 1) != '\\') + *p-- = '\0'; + } + /* Start of an interface block, skip if not ours */ + if (strcmp(option, "interface") == 0) { + char **n; + + if (ifname && line && strcmp(line, ifname) == 0) + skip = 0; + else + skip = 1; + if (ifname) + continue; + + n = realloc(ctx->ifcv, + sizeof(char *) * ((size_t)ctx->ifcc + 1)); + if (n == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + continue; + } + ctx->ifcv = n; + ctx->ifcv[ctx->ifcc] = strdup(line); + if (ctx->ifcv[ctx->ifcc] == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + continue; + } + ctx->ifcc++; + logger(ctx, LOG_DEBUG, "allowing interface %s", + ctx->ifcv[ctx->ifcc - 1]); + continue; + } + /* Start of an ssid block, skip if not ours */ + if (strcmp(option, "ssid") == 0) { + 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) { + 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(ifo); + errno = ENOENT; + return NULL; + } + + 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; + + if (argc == 0) + return 1; + + optind = 0; + r = 1; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + r = parse_option(ctx, ifname, ifo, opt, optarg, NULL, NULL); + if (r != 1) + break; + } + + finish_config(ifo); + return r; +} + +void +free_options(struct if_options *ifo) +{ + size_t i; + struct dhcp_opt *opt; + struct vivco *vo; + struct token *token; + + 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); + } + ipv4_freeroutes(ifo->routes); + 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->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); + +#ifdef INET6 + for (; ifo->ia_len > 0; ifo->ia_len--) + free(ifo->ia[ifo->ia_len - 1].sla); +#endif + free(ifo->ia); + + 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); + } + free(ifo); + } +} diff --git a/if-options.h b/if-options.h new file mode 100644 index 0000000..4d1de15 --- /dev/null +++ b/if-options.h @@ -0,0 +1,210 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IF_OPTIONS_H +#define IF_OPTIONS_H + +#include <sys/param.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> + +#include <getopt.h> +#include <limits.h> +#include <stdint.h> + +#include "auth.h" + +/* Don't set any optional arguments here so we retain POSIX + * compatibility with getopt */ +#define IF_OPTS "46bc:de:f:gh:i:j:kl:m:no:pqr:s:t:u:v:wxy:z:" \ + "ABC:DEF:GHI:JKLMO:Q:S:TUVW:X:Z:" + +#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 DHCPCD_ARP (1ULL << 0) +#define DHCPCD_RELEASE (1ULL << 1) +#define DHCPCD_DOMAIN (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) +#define DHCPCD_QUIET (1ULL << 21) +#define DHCPCD_BACKGROUND (1ULL << 22) +#define DHCPCD_VENDORRAW (1ULL << 23) +#define DHCPCD_NOWAITIP (1ULL << 24) /* To force daemonise */ +#define DHCPCD_WAITIP (1ULL << 25) +#define DHCPCD_SLAACPRIVATE (1ULL << 26) +#define DHCPCD_CSR_WARNED (1ULL << 27) +#define DHCPCD_XID_HWADDR (1ULL << 28) +#define DHCPCD_BROADCAST (1ULL << 29) +#define DHCPCD_DUMPLEASE (1ULL << 30) +#define DHCPCD_IPV6RS (1ULL << 31) +#define DHCPCD_IPV6RA_REQRDNSS (1ULL << 32) +#define DHCPCD_IPV6RA_OWN (1ULL << 33) +#define DHCPCD_IPV6RA_OWN_DEFAULT (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_NOPFXDLG (1ULL << 51) +#define DHCPCD_PFXDLGONLY (1ULL << 52) +#define DHCPCD_PFXDLGMIX (1ULL << 53) +#define DHCPCD_IPV6RA_AUTOCONF (1ULL << 54) +#define DHCPCD_ROUTER_HOST_ROUTE_WARNED (1ULL << 55) +#define DHCPCD_IPV6RA_ACCEPT_NOPUBLIC (1ULL << 56) +#define DHCPCD_BOOTP (1ULL << 57) + +#define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \ + DHCPCD_ROUTER_HOST_ROUTE_WARNED) +#define DHCPCD_CONF (DHCPCD_NOPFXDLG | DHCPCD_PFXDLGONLY) + +extern const struct option cf_options[]; + +struct if_sla { + char ifname[IF_NAMESIZE]; + uint32_t sla; + uint8_t prefix_len; + 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; + uint32_t sla_max; + size_t sla_len; + struct if_sla *sla; +#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 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]; + uint8_t dstmask[256 / 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 rt_head *routes; + 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]; + + size_t blacklist_len; + in_addr_t *blacklist; + size_t whitelist_len; + in_addr_t *whitelist; + size_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 *dhcp6_override; + size_t dhcp6_override_len; + uint32_t vivco_en; + struct vivco *vivco; + size_t vivco_len; + struct dhcp_opt *vivso_override; + size_t vivso_override_len; + + struct auth auth; +}; + +struct if_options *read_config(struct dhcpcd_ctx *, + const char *, const char *, const char *); +int add_options(struct dhcpcd_ctx *, const char *, + struct if_options *, int, char **); +void free_dhcp_opt_embenc(struct dhcp_opt *); +void free_options(struct if_options *); + +#endif diff --git a/if-sun.c b/if-sun.c new file mode 100644 index 0000000..42082c0 --- /dev/null +++ b/if-sun.c @@ -0,0 +1,183 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/utsname.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv6.h" +#include "ipv6nd.h" + +int +if_init(__unused struct interface *iface) +{ + + return 0; +} + +int +if_conf(__unused struct interface *iface) +{ + + return 0; +} + +int +if_openlinksocket(void) +{ + + errno = ENOTSUP; + return -1; +} + +int +if_getssid(const char *ifname, char *ssid) +{ + + errno = ENOTSUP; + return -1; +} + +int +if_vimaster(const char *ifname) +{ + + return 0; +} + +#ifdef INET +int +if_openrawsocket(struct interface *ifp, int protocol) +{ + + errno = ENOTSUP; + return -1; +} + +ssize_t +if_sendrawpacket(const struct interface *ifp, int protocol, + const void *data, size_t len) +{ + + errno = ENOTSUP; + return -1; +} + +ssize_t +if_readrawpacket(struct interface *ifp, int protocol, + void *data, size_t len, int *flags) +{ + + errno = ENOTSUP; + return -1; +} + +int +if_address(const struct interface *iface, const struct in_addr *address, + const struct in_addr *netmask, const struct in_addr *broadcast, + int action) +{ + + errno = ENOTSUP; + return -1; +} + +int +if_route(const struct rt *rt, int action) +{ + + errno = ENOTSUP; + return -1; +} +#endif + +#ifdef INET6 +int +if_address6(const struct ipv6_addr *a, int action) +{ + + errno = ENOTSUP; + return -1; +} + +int +if_route6(const struct rt6 *rt, int action) +{ + + errno = ENOTSUP; + return -1; +} +#endif + +#ifdef INET6 +int +if_addrflags6(const struct in6_addr *addr, const struct interface *ifp) +{ + + errno = ENOTSUP; + return -1; +} +#endif + +int +if_managelink(struct dhcpcd_ctx *ctx) +{ + + errno = ENOTSUP; + return -1; +} + +if_machinearch(char *str, size_t len) +{ + + errno = ENOTSUP; + return -1; +} + +#ifdef INET6 +void +if_rarestore(struct dhcpcd_ctx *ctx) +{ + +} + +int +if_checkipv6(struct dhcpcd_ctx *ctx, const char *ifname, int own) +{ + + errno = ENOTSUP; + return -1; +} +#endif @@ -0,0 +1,674 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */ +# include <net/if_var.h> +#endif +#ifdef AF_LINK +# include <net/if_dl.h> +# include <net/if_types.h> +# include <netinet/in_var.h> +#endif +#ifdef AF_PACKET +# include <netpacket/packet.h> +#endif +#ifdef SIOCGIFMEDIA +# include <net/if_media.h> +#endif +#include <net/route.h> + +#include <ctype.h> +#include <errno.h> +#include <ifaddrs.h> +#include <fnmatch.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dev.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv6nd.h" + +#ifdef __QNX__ +/* QNX carries defines for, but does not actually support PF_LINK */ +#undef IFLR_ACTIVE +#endif + +void +if_free(struct interface *ifp) +{ + + if (ifp == NULL) + return; + ipv4_free(ifp); + dhcp_free(ifp); + dhcp6_free(ifp); + ipv6nd_free(ifp); + ipv6_free(ifp); + free_options(ifp->options); + free(ifp); +} + +int +if_carrier(struct interface *iface) +{ + int s, r; + struct ifreq ifr; +#ifdef SIOCGIFMEDIA + struct ifmediareq ifmr; +#endif + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return LINK_UNKNOWN; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) { + close(s); + return LINK_UNKNOWN; + } + iface->flags = (unsigned int)ifr.ifr_flags; + +#ifdef SIOCGIFMEDIA + memset(&ifmr, 0, sizeof(ifmr)); + strlcpy(ifmr.ifm_name, iface->name, sizeof(ifmr.ifm_name)); + if (ioctl(s, 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 + close(s); + return r; +} + +int +if_setflag(struct interface *ifp, short flag) +{ + struct ifreq ifr; + int s, r; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + r = -1; + if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) { + if (flag == 0 || (ifr.ifr_flags & flag) == flag) + r = 0; + else { + ifr.ifr_flags |= flag; + if (ioctl(s, SIOCSIFFLAGS, &ifr) == 0) + r = 0; + } + ifp->flags = (unsigned int)ifr.ifr_flags; + } + close(s); + 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; +} + +static void if_learnaddrs1(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, *dst; +#endif +#ifdef INET6 + struct sockaddr_in6 *sin6, *net6; +#endif + int ifa_flags; + + + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if ((ifp = if_find(ifs, ifa->ifa_name)) == NULL) + continue; + switch(ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + addr = (const struct sockaddr_in *) + (void *)ifa->ifa_addr; + net = (const struct sockaddr_in *) + (void *)ifa->ifa_netmask; + if (ifa->ifa_flags & IFF_POINTOPOINT) + dst = (const struct sockaddr_in *) + (void *)ifa->ifa_dstaddr; + else + dst = NULL; + ifa_flags = if_addrflags(&addr->sin_addr, ifp); + ipv4_handleifa(ctx, RTM_NEWADDR, ifs, ifa->ifa_name, + &addr->sin_addr, + &net->sin_addr, + dst ? &dst->sin_addr : NULL, ifa_flags); + break; +#endif +#ifdef INET6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr; + net6 = (struct sockaddr_in6 *)(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 + ifa_flags = if_addrflags6(&sin6->sin6_addr, ifp); + if (ifa_flags != -1) + ipv6_handleifa(ctx, RTM_NEWADDR, ifs, + ifa->ifa_name, + &sin6->sin6_addr, + ipv6_prefixlen(&net6->sin6_addr), + ifa_flags); + break; +#endif + } + } +} + +struct if_head * +if_discover(struct dhcpcd_ctx *ctx, int argc, char * const *argv) +{ + struct ifaddrs *ifaddrs, *ifa; + char *p; + int i; + struct if_head *ifs; + struct interface *ifp; +#ifdef __linux__ + char ifn[IF_NAMESIZE]; +#endif +#ifdef AF_LINK + const struct sockaddr_dl *sdl; +#ifdef SIOCGIFPRIORITY + struct ifreq ifr; + int s_inet; +#endif +#ifdef IFLR_ACTIVE + struct if_laddrreq iflr; + int s_link; +#endif + +#ifdef SIOCGIFPRIORITY + if ((s_inet = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return NULL; +#endif +#ifdef IFLR_ACTIVE + if ((s_link = socket(PF_LINK, SOCK_DGRAM, 0)) == -1) { +#ifdef SIOCGIFPRIORITY + close(s_inet); +#endif + return NULL; + } + memset(&iflr, 0, sizeof(iflr)); +#endif +#elif AF_PACKET + const struct sockaddr_ll *sll; +#endif + + if (getifaddrs(&ifaddrs) == -1) + return NULL; + ifs = malloc(sizeof(*ifs)); + if (ifs == NULL) + return NULL; + TAILQ_INIT(ifs); + + 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 + } + + /* 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, ifa->ifa_name) == 0) + break; + } + if (ifp) + continue; + + if (argc > 0) { + for (i = 0; i < argc; i++) { +#ifdef __linux__ + /* Check the real interface name */ + strlcpy(ifn, argv[i], sizeof(ifn)); + p = strchr(ifn, ':'); + if (p) + *p = '\0'; + if (strcmp(ifn, ifa->ifa_name) == 0) + break; +#else + if (strcmp(argv[i], ifa->ifa_name) == 0) + break; +#endif + } + if (i == argc) + continue; + p = argv[i]; + } else { + p = ifa->ifa_name; +#ifdef __linux__ + strlcpy(ifn, ifa->ifa_name, sizeof(ifn)); +#endif + /* -1 means we're discovering against a specific + * interface, but we still need the below rules + * to apply. */ + if (argc == -1 && strcmp(argv[0], ifa->ifa_name) != 0) + continue; + } + for (i = 0; i < ctx->ifdc; i++) + if (!fnmatch(ctx->ifdv[i], p, 0)) + break; + if (i < ctx->ifdc) + continue; + for (i = 0; i < ctx->ifac; i++) + if (!fnmatch(ctx->ifav[i], p, 0)) + break; + if (ctx->ifac && i == ctx->ifac) + continue; + + /* Ensure that the interface name has settled */ + if (!dev_initialized(ctx, p)) + continue; + + /* 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, p)) + continue; + } + + if (if_vimaster(p) == 1) { + logger(ctx, argc ? LOG_ERR : LOG_DEBUG, + "%s: is a Virtual Interface Master, skipping", p); + continue; + } + + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + break; + } + ifp->ctx = ctx; +#ifdef __linux__ + strlcpy(ifp->name, ifn, sizeof(ifp->name)); + strlcpy(ifp->alias, p, sizeof(ifp->alias)); +#else + strlcpy(ifp->name, p, sizeof(ifp->name)); +#endif + ifp->flags = ifa->ifa_flags; + ifp->carrier = if_carrier(ifp); + + if (ifa->ifa_addr != NULL) { +#ifdef AF_LINK + sdl = (const struct sockaddr_dl *)(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(s_link, 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 && + !if_hasconf(ctx, ifp->name)) + { + logger(ifp->ctx, LOG_DEBUG, + "%s: ignoring due to" + " interface type and" + " no config", + ifp->name); + if_free(ifp); + continue; + } + /* 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)) + { + if_free(ifp); + continue; + } + logger(ifp->ctx, LOG_WARNING, + "%s: unsupported interface type %.2x", + ifp->name, sdl->sdl_type); + /* Pretend it's ethernet */ + ifp->family = ARPHRD_ETHER; + break; + } + ifp->hwlen = sdl->sdl_alen; +#ifndef CLLADDR +# define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen)) +#endif + memcpy(ifp->hwaddr, CLLADDR(sdl), ifp->hwlen); +#elif AF_PACKET + sll = (const struct sockaddr_ll *)(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 + + /* 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)) + { + if_free(ifp); + continue; + } + 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: + logger(ifp->ctx, LOG_WARNING, + "%s: unsupported interface family %.2x", + ifp->name, ifp->family); + break; +#endif + } + } + + /* Handle any platform init for the interface */ + if (if_init(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: if_init: %m", p); + if_free(ifp); + continue; + } + + /* Ensure that the MTU is big enough for DHCP */ + if (if_getmtu(ifp->name) < MTU_MIN && + if_setmtu(ifp->name, MTU_MIN) == -1) + { + logger(ifp->ctx, LOG_ERR, "%s: set_mtu: %m", p); + if_free(ifp); + continue; + } + +#ifdef SIOCGIFPRIORITY + /* Respect the interface priority */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(s_inet, SIOCGIFPRIORITY, &ifr) == 0) + ifp->metric = ifr.ifr_metric; +#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 + + TAILQ_INSERT_TAIL(ifs, ifp, next); + } + + if_learnaddrs1(ctx, ifs, ifaddrs); + freeifaddrs(ifaddrs); + +#ifdef SIOCGIFPRIORITY + close(s_inet); +#endif +#ifdef IFLR_ACTIVE + close(s_link); +#endif + + return ifs; +} + +static struct interface * +if_findindexname(struct if_head *ifaces, unsigned int idx, const char *name) +{ + + if (ifaces != NULL) { + struct interface *ifp; + + TAILQ_FOREACH(ifp, ifaces, next) { + if ((ifp->options == NULL || + !(ifp->options->options & DHCPCD_PFXDLGONLY)) && + ((name && strcmp(ifp->name, name) == 0) || +#ifdef __linux__ + (name && strcmp(ifp->alias, name) == 0) || +#endif + (!name && ifp->index == idx))) + return ifp; + } + } + + errno = ESRCH; + 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); +} + +int +if_domtu(const char *ifname, short int mtu) +{ + int s, r; + struct ifreq ifr; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + r = ioctl(s, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); + close(s); + 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 + + /* Always prefer master interfaces */ + if (!(si->options->options & DHCPCD_PFXDLGONLY) && + ti->options->options & DHCPCD_PFXDLGONLY) + return -1; + if (si->options->options & DHCPCD_PFXDLGONLY && + !(ti->options->options & DHCPCD_PFXDLGONLY)) + return 1; + + /* Check carrier status first */ + 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); +} @@ -0,0 +1,172 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include <net/if.h> +#ifdef __FreeBSD__ +#include <net/if_var.h> +#endif +#include <net/route.h> /* for RTM_ADD et all */ +#include <netinet/in.h> +#ifdef BSD +#include <netinet/in_var.h> /* for IN_IFF_TENTATIVE et all */ +#endif + +/* 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 + +/* 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 + +#include "config.h" +#include "dhcpcd.h" +#include "ipv4.h" +#include "ipv6.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 LINKLOCAL_ADDR 0xa9fe0000 +#define LINKLOCAL_MASK IN_CLASSB_NET +#define LINKLOCAL_BRDC (LINKLOCAL_ADDR | ~LINKLOCAL_MASK) + +#ifndef IN_LINKLOCAL +# define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR) +#endif + +#define RAW_EOF 1 << 0 +#define RAW_PARTIALCSUM 2 << 0 + +int if_setflag(struct interface *ifp, short flag); +#define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING)) +struct if_head *if_discover(struct dhcpcd_ctx *, int, char * const *); +struct interface *if_find(struct if_head *, const char *); +struct interface *if_findindex(struct if_head *, unsigned int); +void if_sortinterfaces(struct dhcpcd_ctx *); +void if_free(struct interface *); +int if_domtu(const char *, short int); +#define if_getmtu(iface) if_domtu(iface, 0) +#define if_setmtu(iface, mtu) if_domtu(iface, mtu) +int if_carrier(struct interface *); + +/* 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 char *); +int if_openlinksocket(void); +int if_managelink(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 + +#ifdef INET +extern const char *if_pfname; +int if_openrawsocket(struct interface *, uint16_t); +ssize_t if_sendrawpacket(const struct interface *, + uint16_t, const void *, size_t); +ssize_t if_readrawpacket(struct interface *, uint16_t, void *, size_t, int *); + +int if_address(const struct interface *, + const struct in_addr *, const struct in_addr *, + const struct in_addr *, int); +#define if_addaddress(ifp, addr, net, brd) \ + if_address(ifp, addr, net, brd, 1) +#define if_deladdress(ifp, addr, net) \ + if_address(ifp, addr, net, NULL, -1) + +int if_addrflags(const struct in_addr *, const struct interface *); + +int if_route(unsigned char, const struct rt *rt); +int if_initrt(struct interface *); +#endif + +#ifdef INET6 +int if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *, int); +#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 if_address6(const struct ipv6_addr *, int); +#define if_addaddress6(a) if_address6(a, 1) +#define if_deladdress6(a) if_address6(a, -1) + +int if_addrflags6(const struct in6_addr *, const struct interface *); +int if_getlifetime6(struct ipv6_addr *); + +int if_route6(unsigned char, const struct rt6 *rt); +int if_initrt6(struct interface *); +#else +#define if_checkipv6(a, b, c) (-1) +#endif + +int if_machinearch(char *, size_t); +#endif @@ -0,0 +1,1093 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/socket.h> +#include <sys/types.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/route.h> + +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "arp.h" +#include "common.h" +#include "dhcpcd.h" +#include "dhcp.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "script.h" + +#define IPV4_LOOPBACK_ROUTE +#if defined(__linux__) || (defined(BSD) && defined(RTF_LOCAL)) +/* Linux has had loopback routes in the local table since 2.2 */ +#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 *net) +{ + 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) && + (net == NULL || ap->net.s_addr == net->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; +} + +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; +} + +int +ipv4_addrexists(struct dhcpcd_ctx *ctx, const struct in_addr *addr) +{ + struct interface *ifp; + struct dhcp_state *state; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = D_STATE(ifp); + if (state) { + if (addr == NULL) { + if (state->addr.s_addr != INADDR_ANY) + return 1; + } else if (addr->s_addr == state->addr.s_addr) + return 1; + } + if (addr != NULL && ipv4_iffindaddr(ifp, addr, NULL)) + return 1; + } + return 0; +} + +void +ipv4_freeroutes(struct rt_head *rts) +{ + + if (rts) { + ipv4_freerts(rts); + free(rts); + } +} + +int +ipv4_init(struct dhcpcd_ctx *ctx) +{ + + if (ctx->ipv4_routes == NULL) { + ctx->ipv4_routes = malloc(sizeof(*ctx->ipv4_routes)); + if (ctx->ipv4_routes == NULL) + return -1; + TAILQ_INIT(ctx->ipv4_routes); + } + if (ctx->ipv4_kroutes == NULL) { + ctx->ipv4_kroutes = malloc(sizeof(*ctx->ipv4_kroutes)); + if (ctx->ipv4_kroutes == NULL) + return -1; + TAILQ_INIT(ctx->ipv4_kroutes); + } + return 0; +} + +/* 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) && (sis->added & STATE_FAKE)) + return -1; + if ((sis->added & STATE_FAKE) && !(sis->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) { + int sill = (sis->new->cookie == htonl(MAGIC_COOKIE)); + int till = (tis->new->cookie == htonl(MAGIC_COOKIE)); + if (sill && !till) + return -1; + if (!sill && till) + return 1; + } + return 0; +} + +static struct rt * +find_route(struct rt_head *rts, const struct rt *r, const struct rt *srt) +{ + struct rt *rt; + + if (rts == NULL) + return NULL; + TAILQ_FOREACH(rt, rts, next) { + if (rt->dest.s_addr == r->dest.s_addr && +#ifdef HAVE_ROUTE_METRIC + (srt || (r->iface == NULL || rt->iface == NULL || + rt->iface->metric == r->iface->metric)) && +#endif + (!srt || srt != rt) && + rt->net.s_addr == r->net.s_addr) + return rt; + } + return NULL; +} + +static void +desc_route(const char *cmd, const struct rt *rt) +{ + char addr[sizeof("000.000.000.000") + 1]; + struct dhcpcd_ctx *ctx = rt->iface ? rt->iface->ctx : NULL; + const char *ifname = rt->iface ? rt->iface->name : NULL; + + strlcpy(addr, inet_ntoa(rt->dest), sizeof(addr)); + if (rt->net.s_addr == htonl(INADDR_BROADCAST) && + rt->gate.s_addr == htonl(INADDR_ANY)) + logger(ctx, LOG_INFO, "%s: %s host route to %s", + ifname, cmd, addr); + else if (rt->net.s_addr == htonl(INADDR_BROADCAST)) + logger(ctx, LOG_INFO, "%s: %s host route to %s via %s", + ifname, cmd, addr, inet_ntoa(rt->gate)); + else if (rt->gate.s_addr == htonl(INADDR_ANY)) + logger(ctx, LOG_INFO, "%s: %s route to %s/%d", + ifname, cmd, addr, inet_ntocidr(rt->net)); + else if (rt->dest.s_addr == htonl(INADDR_ANY) && + rt->net.s_addr == htonl(INADDR_ANY)) + logger(ctx, LOG_INFO, "%s: %s default route via %s", + ifname, cmd, inet_ntoa(rt->gate)); + else + logger(ctx, LOG_INFO, "%s: %s route to %s/%d via %s", + ifname, cmd, addr, inet_ntocidr(rt->net), + inet_ntoa(rt->gate)); +} + +static struct rt * +ipv4_findrt(struct dhcpcd_ctx *ctx, const struct rt *rt, int flags) +{ + struct rt *r; + + if (ctx->ipv4_kroutes == NULL) + return NULL; + TAILQ_FOREACH(r, ctx->ipv4_kroutes, next) { + if (rt->dest.s_addr == r->dest.s_addr && +#ifdef HAVE_ROUTE_METRIC + rt->iface == r->iface && + (!flags || rt->metric == r->metric) && +#else + (!flags || rt->iface == r->iface) && +#endif + rt->net.s_addr == r->net.s_addr) + return r; + } + return NULL; +} + +void +ipv4_freerts(struct rt_head *routes) +{ + struct rt *rt; + + while ((rt = TAILQ_FIRST(routes))) { + TAILQ_REMOVE(routes, rt, next); + free(rt); + } +} + +/* If something other than dhcpcd removes a route, + * we need to remove it from our internal table. */ +int +ipv4_handlert(struct dhcpcd_ctx *ctx, int cmd, struct rt *rt) +{ + struct rt *f; + + if (ctx->ipv4_kroutes == NULL) + return 0; + + f = ipv4_findrt(ctx, rt, 1); + switch (cmd) { + case RTM_ADD: + if (f == NULL) { + if ((f = malloc(sizeof(*f))) == NULL) + return -1; + *f = *rt; + TAILQ_INSERT_TAIL(ctx->ipv4_kroutes, f, next); + } + break; + case RTM_DELETE: + if (f) { + TAILQ_REMOVE(ctx->ipv4_kroutes, f, next); + free(f); + } + + /* If we manage the route, remove it */ + if ((f = find_route(ctx->ipv4_routes, rt, NULL))) { + desc_route("removing", f); + TAILQ_REMOVE(ctx->ipv4_routes, f, next); + free(f); + } + break; + } + return 0; +} + +#define n_route(a) nc_route(NULL, a) +#define c_route(a, b) nc_route(a, b) +static int +nc_route(struct rt *ort, struct rt *nrt) +{ + + /* Don't set default routes if not asked to */ + if (nrt->dest.s_addr == 0 && + nrt->net.s_addr == 0 && + !(nrt->iface->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route(ort == NULL ? "adding" : "changing", nrt); + + if (ort == NULL) { + ort = ipv4_findrt(nrt->iface->ctx, nrt, 0); + if (ort && + ((ort->flags & RTF_REJECT && nrt->flags & RTF_REJECT) || + (ort->iface == nrt->iface && +#ifdef HAVE_ROUTE_METRIC + ort->metric == nrt->metric && +#endif + ort->gate.s_addr == nrt->gate.s_addr))) + return 0; + } else if (ort->flags & STATE_FAKE && !(nrt->flags & STATE_FAKE) && + ort->iface == nrt->iface && +#ifdef HAVE_ROUTE_METRIC + ort->metric == nrt->metric && +#endif + ort->dest.s_addr == nrt->dest.s_addr && + ort->net.s_addr == nrt->net.s_addr && + ort->gate.s_addr == nrt->gate.s_addr) + return 0; + +#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) == 0) { + if (ort && if_route(RTM_DELETE, ort) == -1 && errno != ESRCH) + logger(nrt->iface->ctx, LOG_ERR, "if_route (DEL): %m"); + return 0; + } + + /* 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. */ + if (ort && if_route(RTM_DELETE, ort) == -1 && errno != ESRCH) + logger(nrt->iface->ctx, LOG_ERR, "if_route (DEL): %m"); + if (if_route(RTM_ADD, nrt) == 0) + return 0; +#ifdef HAVE_ROUTE_METRIC +logerr: +#endif + logger(nrt->iface->ctx, LOG_ERR, "if_route (ADD): %m"); + return -1; +} + +static int +d_route(struct rt *rt) +{ + int retval; + + desc_route("deleting", rt); + retval = if_route(RTM_DELETE, rt); + if (retval != 0 && errno != ENOENT && errno != ESRCH) + logger(rt->iface->ctx, LOG_ERR, + "%s: if_delroute: %m", rt->iface->name); + return retval; +} + +static struct rt_head * +add_subnet_route(struct rt_head *rt, const struct interface *ifp) +{ + const struct dhcp_state *s; + struct rt *r; + + if (rt == NULL) /* earlier malloc failed */ + return NULL; + + s = D_CSTATE(ifp); + /* Don't create a subnet route for these addresses */ + if (s->net.s_addr == INADDR_ANY) + return rt; +#ifndef BSD + /* BSD adds a route in this instance */ + if (s->net.s_addr == INADDR_BROADCAST) + return rt; +#endif + + if ((r = malloc(sizeof(*r))) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(rt); + return NULL; + } + r->dest.s_addr = s->addr.s_addr & s->net.s_addr; + r->net.s_addr = s->net.s_addr; + r->gate.s_addr = INADDR_ANY; + + TAILQ_INSERT_HEAD(rt, r, next); + return rt; +} + +#ifdef IPV4_LOOPBACK_ROUTE +static struct rt_head * +add_loopback_route(struct rt_head *rt, const struct interface *ifp) +{ + struct rt *r; + const struct dhcp_state *s; + + if (rt == NULL) /* earlier malloc failed */ + return NULL; + + s = D_CSTATE(ifp); + if (s->addr.s_addr == INADDR_ANY) + return rt; + + r = malloc(sizeof(*r)); + if (r == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(rt); + return NULL; + } + r->dest.s_addr = s->addr.s_addr; + r->net.s_addr = INADDR_BROADCAST; + r->gate.s_addr = htonl(INADDR_LOOPBACK); + TAILQ_INSERT_HEAD(rt, r, next); + return rt; +} +#endif + +static struct rt_head * +get_routes(struct interface *ifp) +{ + struct rt_head *nrt; + struct rt *rt, *r = NULL; + + if (ifp->options->routes && TAILQ_FIRST(ifp->options->routes)) { + nrt = malloc(sizeof(*nrt)); + TAILQ_INIT(nrt); + TAILQ_FOREACH(rt, ifp->options->routes, next) { + if (rt->gate.s_addr == 0) + break; + r = malloc(sizeof(*r)); + if (r == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(nrt); + return NULL; + } + memcpy(r, rt, sizeof(*r)); + TAILQ_INSERT_TAIL(nrt, r, next); + } + return nrt; + } + + return get_option_routes(ifp, D_STATE(ifp)->new); +} + +/* Some DHCP servers add set host routes by setting the gateway + * to the assigned IP address or the destination address. + * We need to change this. */ +static struct rt_head * +massage_host_routes(struct rt_head *rt, const struct interface *ifp) +{ + struct rt *r; + + if (rt) { + TAILQ_FOREACH(r, rt, next) { + if (r->gate.s_addr == D_CSTATE(ifp)->addr.s_addr || + r->gate.s_addr == r->dest.s_addr) + { + r->gate.s_addr = htonl(INADDR_ANY); + r->net.s_addr = htonl(INADDR_BROADCAST); + } + } + } + return rt; +} + + +static struct rt_head * +add_destination_route(struct rt_head *rt, const struct interface *ifp) +{ + struct rt *r; + + if (rt == NULL || /* failed a malloc earlier probably */ + !(ifp->flags & IFF_POINTOPOINT) || + !has_option_mask(ifp->options->dstmask, DHO_ROUTER)) + return rt; + + r = malloc(sizeof(*r)); + if (r == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(rt); + return NULL; + } + r->dest.s_addr = INADDR_ANY; + r->net.s_addr = INADDR_ANY; + r->gate.s_addr = D_CSTATE(ifp)->dst.s_addr; + TAILQ_INSERT_HEAD(rt, r, next); + return rt; +} + +/* 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 struct rt_head * +add_router_host_route(struct rt_head *rt, const struct interface *ifp) +{ + struct rt *rtp, *rtn; + const char *cp, *cp2, *cp3, *cplim; + struct if_options *ifo; + const struct dhcp_state *state; + + if (rt == NULL) /* earlier malloc failed */ + return NULL; + + TAILQ_FOREACH(rtp, rt, next) { + if (rtp->dest.s_addr != INADDR_ANY) + continue; + /* Scan for a route to match */ + TAILQ_FOREACH(rtn, rt, next) { + if (rtn == rtp) + break; + /* match host */ + if (rtn->dest.s_addr == rtp->gate.s_addr) + break; + /* match subnet */ + cp = (const char *)&rtp->gate.s_addr; + cp2 = (const char *)&rtn->dest.s_addr; + cp3 = (const char *)&rtn->net.s_addr; + cplim = cp3 + sizeof(rtn->net.s_addr); + while (cp3 < cplim) { + if ((*cp++ ^ *cp2++) & *cp3++) + break; + } + if (cp3 == cplim) + break; + } + if (rtn != rtp) + continue; + state = D_CSTATE(ifp); + ifo = ifp->options; + if (ifp->flags & IFF_NOARP) { + if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) && + !(state->added & STATE_FAKE)) + { + ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED; + logger(ifp->ctx, LOG_WARNING, + "%s: forcing router %s through interface", + ifp->name, inet_ntoa(rtp->gate)); + } + rtp->gate.s_addr = 0; + continue; + } + if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) && + !(state->added & STATE_FAKE)) + { + ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED; + logger(ifp->ctx, LOG_WARNING, + "%s: router %s requires a host route", + ifp->name, inet_ntoa(rtp->gate)); + } + rtn = malloc(sizeof(*rtn)); + if (rtn == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(rt); + return NULL; + } + rtn->dest.s_addr = rtp->gate.s_addr; + rtn->net.s_addr = htonl(INADDR_BROADCAST); + rtn->gate.s_addr = htonl(INADDR_ANY); + TAILQ_INSERT_BEFORE(rtp, rtn, next); + } + return rt; +} + +void +ipv4_buildroutes(struct dhcpcd_ctx *ctx) +{ + struct rt_head *nrs, *dnr; + struct rt *or, *rt, *rtn; + struct interface *ifp; + const struct dhcp_state *state; + + /* We need to have the interfaces in the correct order to ensure + * our routes are managed correctly. */ + if_sortinterfaces(ctx); + + nrs = malloc(sizeof(*nrs)); + if (nrs == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return; + } + TAILQ_INIT(nrs); + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = D_CSTATE(ifp); + if (state == NULL || state->new == NULL || !state->added) + continue; + dnr = get_routes(ifp); + dnr = massage_host_routes(dnr, ifp); + dnr = add_subnet_route(dnr, ifp); +#ifdef IPV4_LOOPBACK_ROUTE + dnr = add_loopback_route(dnr, ifp); +#endif + if (ifp->options->options & DHCPCD_GATEWAY) { + dnr = add_router_host_route(dnr, ifp); + dnr = add_destination_route(dnr, ifp); + } + if (dnr == NULL) /* failed to malloc all new routes */ + continue; + TAILQ_FOREACH_SAFE(rt, dnr, next, rtn) { + rt->iface = ifp; +#ifdef HAVE_ROUTE_METRIC + rt->metric = ifp->metric; +#endif + rt->flags = state->added & STATE_FAKE; + /* Is this route already in our table? */ + if ((find_route(nrs, rt, NULL)) != NULL) + continue; + rt->src.s_addr = state->addr.s_addr; + /* Do we already manage it? */ + if ((or = find_route(ctx->ipv4_routes, rt, NULL))) { + if (state->added & STATE_FAKE) + continue; + if (or->flags & STATE_FAKE || + or->iface != ifp || +#ifdef HAVE_ROUTE_METRIC + rt->metric != or->metric || +#endif + or->src.s_addr != state->addr.s_addr || + rt->gate.s_addr != or->gate.s_addr) + { + if (c_route(or, rt) != 0) + continue; + } + TAILQ_REMOVE(ctx->ipv4_routes, or, next); + free(or); + } else { + if (state->added & STATE_FAKE) { + or = ipv4_findrt(ctx, rt, 1); + if (or == NULL || + or->gate.s_addr != rt->gate.s_addr) + continue; + } else { + if (n_route(rt) != 0) + continue; + } + } + rt->flags |= STATE_ADDED; + TAILQ_REMOVE(dnr, rt, next); + TAILQ_INSERT_TAIL(nrs, rt, next); + } + ipv4_freeroutes(dnr); + } + + /* Remove old routes we used to manage */ + if (ctx->ipv4_routes) { + TAILQ_FOREACH(rt, ctx->ipv4_routes, next) { + if (find_route(nrs, rt, NULL) == NULL && + (rt->iface->options->options & + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + d_route(rt); + } + } + ipv4_freeroutes(ctx->ipv4_routes); + ctx->ipv4_routes = nrs; +} + +int +ipv4_deladdr(struct interface *ifp, + const struct in_addr *addr, const struct in_addr *net) +{ + struct dhcp_state *dstate; + int r; + struct ipv4_state *state; + struct ipv4_addr *ap; + + logger(ifp->ctx, LOG_DEBUG, "%s: deleting IP address %s/%d", + ifp->name, inet_ntoa(*addr), inet_ntocidr(*net)); + + r = if_deladdress(ifp, addr, net); + if (r == -1 && errno != EADDRNOTAVAIL && errno != ENXIO && + errno != ENODEV) + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__); + + dstate = D_STATE(ifp); + if (dstate->addr.s_addr == addr->s_addr && + dstate->net.s_addr == net->s_addr) + { + dstate->added = 0; + dstate->addr.s_addr = 0; + dstate->net.s_addr = 0; + } + + state = IPV4_STATE(ifp); + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->addr.s_addr == addr->s_addr && + ap->net.s_addr == net->s_addr) + { + TAILQ_REMOVE(&state->addrs, ap, next); + free(ap); + 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; + if (ifo->options & DHCPCD_INFORM || + (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0)) + return 0; + r = ipv4_deladdr(ifp, &state->addr, &state->net); + return r; +} + +static 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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + TAILQ_INIT(&state->addrs); + TAILQ_INIT(&state->routes); + } + return state; +} + +static int +ipv4_addaddr(struct interface *ifp, const struct dhcp_lease *lease) +{ + struct ipv4_state *state; + struct ipv4_addr *ia; + + if ((state = ipv4_getstate(ifp)) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: ipv4_getstate: %m", __func__); + return -1; + } + if (ifp->options->options & DHCPCD_NOALIAS) { + struct ipv4_addr *ian; + + TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ian) { + if (ia->addr.s_addr != lease->addr.s_addr) + ipv4_deladdr(ifp, &ia->addr, &ia->net); + } + } + + if ((ia = malloc(sizeof(*ia))) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return -1; + } + + logger(ifp->ctx, LOG_DEBUG, "%s: adding IP address %s/%d", + ifp->name, inet_ntoa(lease->addr), + inet_ntocidr(lease->net)); + if (if_addaddress(ifp, &lease->addr, &lease->net, &lease->brd) == -1) { + if (errno != EEXIST) + logger(ifp->ctx, LOG_ERR, "%s: if_addaddress: %m", + __func__); + free(ia); + return -1; + } + + ia->iface = ifp; + ia->addr = lease->addr; + ia->net = lease->net; +#ifdef IN_IFF_TENTATIVE + ia->addr_flags = IN_IFF_TENTATIVE; +#endif + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + return 0; +} + +static void +ipv4_finalisert(struct interface *ifp) +{ + const struct dhcp_state *state = D_CSTATE(ifp); + + /* 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); + ipv4_buildroutes(ifp->ctx); + script_runreason(ifp, state->reason); + + dhcpcd_daemonise(ifp->ctx); +} + +void +ipv4_finaliseaddr(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease; + + lease = &state->lease; + + /* Delete the old address if different */ + if (state->addr.s_addr != lease->addr.s_addr && + state->addr.s_addr != 0 && + ipv4_iffindaddr(ifp, &lease->addr, NULL)) + delete_address(ifp); + + state->added = STATE_ADDED; + state->defend = 0; + state->addr.s_addr = lease->addr.s_addr; + state->net.s_addr = lease->net.s_addr; + ipv4_finalisert(ifp); +} + +void +ipv4_applyaddr(void *arg) +{ + struct interface *ifp = arg, *ifn; + struct dhcp_state *state = D_STATE(ifp), *nstate; + struct dhcp_message *dhcp; + struct dhcp_lease *lease; + struct if_options *ifo = ifp->options; + struct ipv4_addr *ap; + int r; + + if (state == NULL) + return; + dhcp = state->new; + lease = &state->lease; + + if (dhcp == NULL) { + if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + { + if (state->added) { + struct in_addr addr; + + addr = state->addr; + delete_address(ifp); + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp || + strcmp(ifn->name, ifp->name) == 0) + continue; + nstate = D_STATE(ifn); + if (nstate && !nstate->added && + nstate->addr.s_addr == addr.s_addr) + { + if (ifn->options->options & + DHCPCD_ARP) + { + dhcp_bind(ifn, NULL); + } else { + ipv4_addaddr(ifn, + &nstate->lease); + nstate->added = + STATE_ADDED; + } + break; + } + } + } + ipv4_buildroutes(ifp->ctx); + script_runreason(ifp, state->reason); + } else + ipv4_buildroutes(ifp->ctx); + return; + } + + /* Ensure only one interface has the address */ + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0) + continue; + nstate = D_STATE(ifn); + if (nstate && nstate->added && + nstate->addr.s_addr == lease->addr.s_addr) + { + if (ifn->metric <= ifp->metric) { + logger(ifp->ctx, LOG_INFO, + "%s: preferring %s on %s", + ifp->name, + inet_ntoa(lease->addr), + ifn->name); + state->addr.s_addr = lease->addr.s_addr; + state->net.s_addr = lease->net.s_addr; + ipv4_finalisert(ifp); + } + logger(ifp->ctx, LOG_INFO, "%s: preferring %s on %s", + ifn->name, + inet_ntoa(lease->addr), + ifp->name); + ipv4_deladdr(ifn, &nstate->addr, &nstate->net); + nstate->added = 0; + break; + } + } + + /* Does another interface already have the address from a prior boot? */ + if (ifn == NULL) { + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0) + continue; + ap = ipv4_iffindaddr(ifn, &lease->addr, NULL); + if (ap) + ipv4_deladdr(ifn, &ap->addr, &ap->net); + } + } + + /* If the netmask is different, delete the addresss */ + ap = ipv4_iffindaddr(ifp, &lease->addr, NULL); + if (ap && ap->net.s_addr != lease->net.s_addr) + ipv4_deladdr(ifp, &ap->addr, &ap->net); + + if (ipv4_iffindaddr(ifp, &lease->addr, &lease->net)) + logger(ifp->ctx, LOG_DEBUG, + "%s: IP address %s/%d already exists", + ifp->name, inet_ntoa(lease->addr), + inet_ntocidr(lease->net)); + else { + r = ipv4_addaddr(ifp, lease); + if (r == -1 && errno != EEXIST) + return; + } + +#ifdef IN_IFF_NOTUSEABLE + ap = ipv4_iffindaddr(ifp, &lease->addr, NULL); + if (ap == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: added address vanished", + ifp->name); + return; + } else if (ap->addr_flags & IN_IFF_NOTUSEABLE) + return; +#endif + + ipv4_finaliseaddr(ifp); +} + +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 *net, + const struct in_addr *dst, int flags) +{ + struct interface *ifp; + struct ipv4_state *state; + struct ipv4_addr *ap; + + if (ifs == NULL) + ifs = ctx->ifaces; + if (ifs == NULL) { + errno = ESRCH; + return; + } + if (addr->s_addr == INADDR_ANY) { + errno = EINVAL; + return; + } + if ((ifp = if_find(ifs, ifname)) == NULL) + return; + if ((state = ipv4_getstate(ifp)) == NULL) { + errno = ENOENT; + return; + } + + ap = ipv4_iffindaddr(ifp, addr, net); + if (cmd == RTM_NEWADDR) { + if (ap == NULL) { + if ((ap = malloc(sizeof(*ap))) == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return; + } + ap->iface = ifp; + ap->addr = *addr; + ap->net = *net; + if (dst) + ap->dst.s_addr = dst->s_addr; + else + ap->dst.s_addr = INADDR_ANY; + TAILQ_INSERT_TAIL(&state->addrs, ap, next); + } + ap->addr_flags = flags; + } else if (cmd == RTM_DELADDR) { + if (ap) { + TAILQ_REMOVE(&state->addrs, ap, next); + free(ap); + } + } + + dhcp_handleifa(cmd, ifp, addr, net, dst, flags); + arp_handleifa(cmd, ifp, addr, flags); +} + +void +ipv4_free(struct interface *ifp) +{ + struct ipv4_state *state; + struct ipv4_addr *addr; + + if (ifp) { + state = IPV4_STATE(ifp); + if (state) { + while ((addr = TAILQ_FIRST(&state->addrs))) { + TAILQ_REMOVE(&state->addrs, addr, next); + free(addr); + } + ipv4_freerts(&state->routes); + free(state); + } + } +} + +void +ipv4_ctxfree(struct dhcpcd_ctx *ctx) +{ + + ipv4_freeroutes(ctx->ipv4_routes); + ipv4_freeroutes(ctx->ipv4_kroutes); +} @@ -0,0 +1,113 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV4_H +#define IPV4_H + +#include "dhcpcd.h" + +#ifdef IN_IFF_TENTATIVE +#define IN_IFF_NOTUSEABLE \ + (IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED) +#endif + +struct rt { + TAILQ_ENTRY(rt) next; + struct in_addr dest; + struct in_addr net; + struct in_addr gate; + const struct interface *iface; +#ifdef HAVE_ROUTE_METRIC + unsigned int metric; +#endif + struct in_addr src; + unsigned int flags; +}; +TAILQ_HEAD(rt_head, rt); + +struct ipv4_addr { + TAILQ_ENTRY(ipv4_addr) next; + struct in_addr addr; + struct in_addr net; + struct in_addr dst; + struct interface *iface; + int addr_flags; +}; +TAILQ_HEAD(ipv4_addrhead, ipv4_addr); + +struct ipv4_state { + struct ipv4_addrhead addrs; + struct rt_head routes; +}; + +#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 +int ipv4_init(struct dhcpcd_ctx *); +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_addrexists(struct dhcpcd_ctx *, const struct in_addr *); + +#define STATE_ADDED 0x01 +#define STATE_FAKE 0x02 + +void ipv4_buildroutes(struct dhcpcd_ctx *); +void ipv4_finaliseaddr(struct interface *); +int ipv4_deladdr(struct interface *ifp, const struct in_addr *, + const struct in_addr *); +void ipv4_applyaddr(void *); +int ipv4_handlert(struct dhcpcd_ctx *, int, struct rt *); +void ipv4_freerts(struct rt_head *); + +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 *); +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); + +void ipv4_freeroutes(struct rt_head *); + +void ipv4_free(struct interface *); +void ipv4_ctxfree(struct dhcpcd_ctx *); +#else +#define ipv4_init(a) (-1) +#define ipv4_sortinterfaces(a) {} +#define ipv4_applyaddr(a) {} +#define ipv4_freeroutes(a) {} +#define ipv4_free(a) {} +#define ipv4_ctxfree(a) {} +#define ipv4_addrexists(a, b) (0) +#endif + +#endif diff --git a/ipv4ll.c b/ipv4ll.c new file mode 100644 index 0000000..5f08980 --- /dev/null +++ b/ipv4ll.c @@ -0,0 +1,281 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define ELOOP_QUEUE 6 +#include "config.h" +#include "arp.h" +#include "common.h" +#include "dhcp.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4ll.h" + +static struct dhcp_message * +ipv4ll_make_lease(uint32_t addr) +{ + uint32_t u32; + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) + return NULL; + /* Put some LL options in */ + dhcp->yiaddr = addr; + p = dhcp->options; + *p++ = DHO_SUBNETMASK; + *p++ = sizeof(u32); + u32 = htonl(LINKLOCAL_MASK); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + *p++ = DHO_BROADCAST; + *p++ = sizeof(u32); + u32 = htonl(LINKLOCAL_BRDC); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + *p++ = DHO_END; + + return dhcp; +} + +static in_addr_t +ipv4ll_pick_addr(const struct arp_state *astate) +{ + in_addr_t addr; + struct interface *ifp; + const struct dhcp_state *state; + + for (;;) { + /* 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 arc4_random. */ + addr = ntohl(LINKLOCAL_ADDR | + ((uint32_t)(random() % 0xFD00) + 0x0100)); + + /* No point using a failed address */ + if (addr == astate->failed.s_addr) + continue; + + /* Ensure we don't have the address on another interface */ + TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) { + state = D_CSTATE(ifp); + if (state && state->addr.s_addr == addr) + break; + } + + /* Yay, this should be a unique and workable IPv4LL address */ + if (ifp == NULL) + break; + } + return addr; +} + +static void +ipv4ll_probed(struct arp_state *astate) +{ + struct dhcp_state *state = D_STATE(astate->iface); + + if (state->state == DHS_IPV4LL_BOUND) { + ipv4_finaliseaddr(astate->iface); + return; + } + + if (state->state != DHS_BOUND) { + struct dhcp_message *offer; + + /* A DHCP lease could have already been offered. + * Backup and replace once the IPv4LL address is bound */ + offer = state->offer; + state->offer = ipv4ll_make_lease(astate->addr.s_addr); + if (state->offer == NULL) + logger(astate->iface->ctx, LOG_ERR, "%s: %m", __func__); + else + dhcp_bind(astate->iface, astate); + state->offer = offer; + } +} + +static void +ipv4ll_announced(struct arp_state *astate) +{ + struct dhcp_state *state = D_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 dhcp_state *state = D_STATE(astate->iface); + in_addr_t fail; + + fail = 0; + /* RFC 3927 2.2.1, Probe Conflict Detection */ + if (amsg == NULL || + (amsg->sip.s_addr == astate->addr.s_addr || + (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr))) + fail = astate->addr.s_addr; + + /* RFC 3927 2.5, Conflict Defense */ + if (IN_LINKLOCAL(htonl(state->addr.s_addr)) && + amsg && amsg->sip.s_addr == state->addr.s_addr) + fail = state->addr.s_addr; + + if (fail == 0) + return; + + astate->failed.s_addr = fail; + arp_report_conflicted(astate, amsg); + + if (astate->failed.s_addr == state->addr.s_addr) { + time_t up; + + /* RFC 3927 Section 2.5 */ + up = uptime(); + if (state->defend + DEFEND_INTERVAL > up) { + logger(astate->iface->ctx, LOG_WARNING, + "%s: IPv4LL %d second defence failed for %s", + astate->iface->name, DEFEND_INTERVAL, + inet_ntoa(state->addr)); + dhcp_drop(astate->iface, "EXPIRE"); + } else { + logger(astate->iface->ctx, LOG_DEBUG, + "%s: defended IPv4LL address %s", + astate->iface->name, inet_ntoa(state->addr)); + state->defend = up; + return; + } + } + + arp_cancel(astate); + if (++state->conflicts == MAX_CONFLICTS) + logger(astate->iface->ctx, LOG_ERR, + "%s: failed to acquire an IPv4LL address", + astate->iface->name); + astate->addr.s_addr = ipv4ll_pick_addr(astate); + eloop_timeout_add_sec(astate->iface->ctx->eloop, + state->conflicts >= MAX_CONFLICTS ? + RATE_LIMIT_INTERVAL : PROBE_WAIT, + ipv4ll_probe, astate); +} + +void +ipv4ll_start(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct arp_state *astate; + struct ipv4_addr *ap; + + if (state->arp_ipv4ll) + 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->conflicts == 0) { + unsigned int seed; + + if (sizeof(seed) > ifp->hwlen) { + seed = 0; + memcpy(&seed, ifp->hwaddr, ifp->hwlen); + } else + memcpy(&seed, ifp->hwaddr + ifp->hwlen - sizeof(seed), + sizeof(seed)); + initstate(seed, state->randomstate, sizeof(state->randomstate)); + } + + if ((astate = arp_new(ifp, NULL)) == NULL) + return; + + state->arp_ipv4ll = astate; + astate->probed_cb = ipv4ll_probed; + astate->announced_cb = ipv4ll_announced; + astate->conflicted_cb = ipv4ll_conflicted; + + if (IN_LINKLOCAL(htonl(state->addr.s_addr))) { + astate->addr = state->addr; + arp_announce(astate); + return; + } + + if (state->offer && IN_LINKLOCAL(ntohl(state->offer->yiaddr))) { + astate->addr.s_addr = state->offer->yiaddr; + free(state->offer); + state->offer = NULL; + ap = ipv4_iffindaddr(ifp, &astate->addr, NULL); + } else + ap = ipv4_iffindlladdr(ifp); + if (ap) { + astate->addr = ap->addr; + ipv4ll_probed(astate); + return; + } + + setstate(state->randomstate); + /* We maybe rebooting an IPv4LL address. */ + if (!IN_LINKLOCAL(htonl(astate->addr.s_addr))) { + logger(ifp->ctx, LOG_INFO, "%s: probing for an IPv4LL address", + ifp->name); + astate->addr.s_addr = INADDR_ANY; + } + if (astate->addr.s_addr == INADDR_ANY) + astate->addr.s_addr = ipv4ll_pick_addr(astate); +#ifdef IN_IFF_TENTATIVE + ipv4ll_probed(astate); +#else + arp_probe(astate); +#endif +} + +void +ipv4ll_stop(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + + eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp_ipv4ll); +} diff --git a/ipv4ll.h b/ipv4ll.h new file mode 100644 index 0000000..19349de --- /dev/null +++ b/ipv4ll.h @@ -0,0 +1,36 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV4LL_H +#define IPV4LL_H + +void ipv4ll_start(void *); +void ipv4ll_claimed(void *); +void ipv4ll_handle_failure(void *); +void ipv4ll_stop(struct interface *); + +#endif @@ -0,0 +1,2122 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <net/if.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#ifndef __linux__ +# ifndef __QNX__ +# include <sys/endian.h> +# endif +# include <net/if.h> +# ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */ +# include <net/if_var.h> +# endif +# ifndef __sun +# include <netinet6/in6_var.h> +# endif +#endif + +#include <errno.h> +#include <ifaddrs.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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" + +#ifdef HAVE_MD5_H +# ifndef DEPGEN +# include <md5.h> +# endif +#else +# include "md5.h" +#endif + +#ifdef SHA2_H +# include SHA2_H +#else +# include "sha256.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 + +#ifdef __linux__ + /* Match Linux defines to BSD */ +# define IN6_IFF_TEMPORARY IFA_F_TEMPORARY +# 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 + +#define IN6_IFF_NOTUSEABLE \ + (IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED) + +/* 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 + + +#ifdef IPV6_MANAGETEMPADDR +static void ipv6_regentempifid(void *); +static void ipv6_regentempaddr(void *); +#else +#define ipv6_regentempifid(a) {} +#endif + +struct ipv6_ctx * +ipv6_init(struct dhcpcd_ctx *dhcpcd_ctx) +{ + struct ipv6_ctx *ctx; + + if (dhcpcd_ctx->ipv6) + return dhcpcd_ctx->ipv6; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) + return NULL; + + ctx->routes = malloc(sizeof(*ctx->routes)); + if (ctx->routes == NULL) { + free(ctx); + return NULL; + } + TAILQ_INIT(ctx->routes); + + ctx->ra_routers = malloc(sizeof(*ctx->ra_routers)); + if (ctx->ra_routers == NULL) { + free(ctx->routes); + free(ctx); + return NULL; + } + TAILQ_INIT(ctx->ra_routers); + + TAILQ_INIT(&ctx->kroutes); + + 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->rcviov; + ctx->rcvhdr.msg_iovlen = 1; + ctx->rcvhdr.msg_control = ctx->rcvbuf; + // controllen is set at recieve + //ctx->rcvhdr.msg_controllen = sizeof(ctx->rcvbuf); + ctx->rcviov[0].iov_base = ctx->ansbuf; + ctx->rcviov[0].iov_len = sizeof(ctx->ansbuf); + + ctx->nd_fd = -1; + ctx->dhcp_fd = -1; + + dhcpcd_ctx->ipv6 = ctx; + + return ctx; +} + +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 +ipv6_readsecret(struct dhcpcd_ctx *ctx) +{ + FILE *fp; + char line[1024]; + unsigned char *p; + size_t len; + uint32_t r; + int x; + + if ((fp = fopen(SECRET, "r"))) { + len = 0; + while (fgets(line, sizeof(line), fp)) { + len = strlen(line); + if (len) { + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + } + len = hwaddr_aton(NULL, line); + if (len) { + ctx->secret_len = hwaddr_aton(ctx->secret, + line); + break; + } + len = 0; + } + fclose(fp); + if (len) + return (ssize_t)len; + } else { + if (errno != ENOENT) + logger(ctx, LOG_ERR, + "error reading secret: %s: %m", 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). */ + p = ctx->secret; + ctx->secret_len = 0; + for (len = 0; len < 512 / NBBY; len += sizeof(r)) { + r = arc4random(); + memcpy(p, &r, sizeof(r)); + p += sizeof(r); + ctx->secret_len += 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))); + fclose(fp); + if (x > 0) + return (ssize_t)ctx->secret_len; + +eexit: + logger(ctx, LOG_ERR, "error writing secret: %s: %m", SECRET); + 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, + 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 (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; + 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; + + 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, + &dad, + ifp->ctx->secret, ifp->ctx->secret_len); + + if (r == 0) + *dad_counter = (int)dad; + return r; +} + +int +ipv6_makeaddr(struct in6_addr *addr, const 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) { + if (ifp->ctx->secret_len == 0) { + if (ipv6_readsecret(ifp->ctx) == -1) + return -1; + } + 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; +} + +int +ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len) +{ + int bytelen, bitlen; + + if (len < 0 || len > 128) { + errno = EINVAL; + return -1; + } + + bytelen = len / NBBY; + bitlen = len % NBBY; + memcpy(&prefix->s6_addr, &addr->s6_addr, (size_t)bytelen); + if (bitlen != 0) + prefix->s6_addr[bytelen] = + (uint8_t)(prefix->s6_addr[bytelen] >> (NBBY - bitlen)); + memset((char *)prefix->s6_addr + bytelen, 0, + sizeof(prefix->s6_addr) - (size_t)bytelen); + 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) + 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 < 0 || prefix_len > 120 || + result_len < 0 || result_len > 120) + { + errno = EINVAL; + return -1; + } + + /* Check that the user_number fits inside result_len less prefix_len */ + if (result_len < prefix_len || user_number > INT_MAX || + ffs((int)user_number) > result_len - prefix_len) + { + errno = ERANGE; + return -1; + } + + /* virtually shift user number by dest_len, then split at 64 */ + if (result_len >= 64) { + user_high = user_number << (result_len - 64); + user_low = 0; + } else { + user_high = user_number >> (64 - result_len); + user_low = user_number << result_len; + } + + /* 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 *ap; + int ifa_flags; + + ap = arg; + ifa_flags = if_addrflags6(&ap->addr, ap->iface); + if (ifa_flags == -1) + logger(ap->iface->ctx, LOG_ERR, + "%s: if_addrflags6: %m", ap->iface->name); + else if (!(ifa_flags & IN6_IFF_TENTATIVE)) { + ipv6_handleifa(ap->iface->ctx, RTM_NEWADDR, + ap->iface->ctx->ifaces, ap->iface->name, + &ap->addr, ap->prefix_len, ifa_flags); + } else { + struct timespec tv; + + ms_to_ts(&tv, RETRANS_TIMER / 2); + eloop_timeout_add_tv(ap->iface->ctx->eloop, &tv, + ipv6_checkaddrflags, ap); + } +} +#endif + + +static void +ipv6_deleteaddr(struct ipv6_addr *ia) +{ + struct ipv6_state *state; + struct ipv6_addr *ap; + + logger(ia->iface->ctx, LOG_INFO, "%s: deleting address %s", + ia->iface->name, ia->saddr); + if (if_deladdress6(ia) == -1 && + errno != EADDRNOTAVAIL && errno != ENXIO && errno != ENODEV) + logger(ia->iface->ctx, LOG_ERR, "if_deladdress6: :%m"); + + 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; + } + } +} + +int +ipv6_addaddr(struct ipv6_addr *ap, const struct timespec *now) +{ + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *nap; + uint32_t pltime, vltime; + + /* Ensure no other interface has this address */ + TAILQ_FOREACH(ifp, ap->iface->ctx->ifaces, next) { + if (ifp == ap->iface || strcmp(ifp->name, ap->iface->name) == 0) + continue; + state = IPV6_STATE(ifp); + if (state == NULL) + continue; + TAILQ_FOREACH(nap, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&nap->addr, &ap->addr)) { + ipv6_deleteaddr(nap); + break; + } + } + } + + if (!(ap->flags & IPV6_AF_DADCOMPLETED) && + ipv6_iffindaddr(ap->iface, &ap->addr)) + ap->flags |= IPV6_AF_DADCOMPLETED; + + logger(ap->iface->ctx, ap->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG, + "%s: adding address %s", ap->iface->name, ap->saddr); + if (ap->prefix_pltime == ND6_INFINITE_LIFETIME && + ap->prefix_vltime == ND6_INFINITE_LIFETIME) + logger(ap->iface->ctx, LOG_DEBUG, + "%s: pltime infinity, vltime infinity", + ap->iface->name); + else if (ap->prefix_pltime == ND6_INFINITE_LIFETIME) + logger(ap->iface->ctx, LOG_DEBUG, + "%s: pltime infinity, vltime %"PRIu32" seconds", + ap->iface->name, ap->prefix_vltime); + else if (ap->prefix_vltime == ND6_INFINITE_LIFETIME) + logger(ap->iface->ctx, LOG_DEBUG, + "%s: pltime %"PRIu32"seconds, vltime infinity", + ap->iface->name, ap->prefix_pltime); + else + logger(ap->iface->ctx, LOG_DEBUG, + "%s: pltime %"PRIu32" seconds, vltime %"PRIu32" seconds", + ap->iface->name, ap->prefix_pltime, ap->prefix_vltime); + + /* Adjust plftime and vltime based on acquired time */ + pltime = ap->prefix_pltime; + vltime = ap->prefix_vltime; + if (timespecisset(&ap->acquired) && + (ap->prefix_pltime != ND6_INFINITE_LIFETIME || + ap->prefix_vltime != ND6_INFINITE_LIFETIME)) + { + struct timespec n; + + if (now == NULL) { + get_monotonic(&n); + now = &n; + } + timespecsub(now, &ap->acquired, &n); + if (ap->prefix_pltime != ND6_INFINITE_LIFETIME) + ap->prefix_pltime -= (uint32_t)n.tv_sec; + if (ap->prefix_vltime != ND6_INFINITE_LIFETIME) + ap->prefix_vltime -= (uint32_t)n.tv_sec; + } + + if (if_addaddress6(ap) == -1) { + logger(ap->iface->ctx, LOG_ERR, "if_addaddress6: %m"); +#if 0 + logger(ap->iface->ctx, LOG_DEBUG, + "%s: adj pltime %"PRIu32" seconds, " + "vltime %"PRIu32" seconds", + ap->iface->name, ap->prefix_pltime, ap->prefix_vltime); +#endif + /* Restore real pltime and vltime */ + ap->prefix_pltime = pltime; + ap->prefix_vltime = vltime; + return -1; + } + +#ifdef IPV6_MANAGETEMPADDR + /* RFC4941 Section 3.4 */ + if (ap->flags & IPV6_AF_TEMPORARY && + ap->prefix_pltime && + ap->prefix_vltime && + ap->iface->options->options & DHCPCD_IPV6RA_OWN && + ip6_use_tempaddr(ap->iface->name)) + eloop_timeout_add_sec(ap->iface->ctx->eloop, + (time_t)ap->prefix_pltime - REGEN_ADVANCE, + ipv6_regentempaddr, ap); +#endif + + /* Restore real pltime and vltime */ + ap->prefix_pltime = pltime; + ap->prefix_vltime = vltime; + + ap->flags &= ~IPV6_AF_NEW; + ap->flags |= IPV6_AF_ADDED; + if (ap->delegating_iface) + ap->flags |= IPV6_AF_DELEGATED; + +#ifdef IPV6_POLLADDRFLAG + eloop_timeout_delete(ap->iface->ctx->eloop, + ipv6_checkaddrflags, ap); + if (!(ap->flags & IPV6_AF_DADCOMPLETED)) { + struct timespec tv; + + ms_to_ts(&tv, RETRANS_TIMER / 2); + eloop_timeout_add_tv(ap->iface->ctx->eloop, + &tv, ipv6_checkaddrflags, ap); + } +#endif + + return 0; +} + +int +ipv6_publicaddr(const struct ipv6_addr *ia) +{ + return (ia->prefix_pltime && + (ia->addr.s6_addr[0] & 0xfe) != 0xc && + !(ia->addr_flags & IN6_IFF_NOTUSEABLE)); +} + +struct ipv6_addr * +ipv6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, short 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, *apf; + ssize_t i; + struct timespec now; + + i = 0; + timespecclear(&now); + TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { + 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)) + { + apf = ipv6_findaddr(ap->iface->ctx, + &ap->addr, IPV6_AF_ADDED); + if (apf && apf->iface != ap->iface && + strcmp(apf->iface->name, ap->iface->name)) + { + if (apf->iface->metric <= ap->iface->metric) { + logger(apf->iface->ctx, LOG_INFO, + "%s: preferring %s on %s", + ap->iface->name, + ap->saddr, + apf->iface->name); + continue; + } + logger(apf->iface->ctx, LOG_INFO, + "%s: preferring %s on %s", + apf->iface->name, + ap->saddr, + ap->iface->name); + if (if_deladdress6(apf) == -1 && + errno != EADDRNOTAVAIL && errno != ENXIO) + logger(apf->iface->ctx, LOG_ERR, + "if_deladdress6: %m"); + apf->flags &= + ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); + } else if (apf) + apf->flags &= ~IPV6_AF_ADDED; + if (ap->flags & IPV6_AF_NEW) + i++; + if (!timespecisset(&now)) + get_monotonic(&now); + ipv6_addaddr(ap, &now); + } + } + + return i; +} + +void +ipv6_freeaddr(struct ipv6_addr *ap) +{ + + eloop_q_timeout_delete(ap->iface->ctx->eloop, 0, NULL, ap); + free(ap); +} + +void +ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop, + const struct interface *ifd) +{ + struct ipv6_addr *ap, *apn, *apf; + struct timespec now; + + timespecclear(&now); + TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { + if (ifd && ap->delegating_iface != ifd) + continue; + 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)) + { + 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 && + strcmp(apf->iface->name, ap->iface->name))) + ipv6_deleteaddr(ap); + if (!(ap->iface->options->options & + DHCPCD_EXITING) && apf) + { + if (!timespecisset(&now)) + get_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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + TAILQ_INIT(&state->addrs); + TAILQ_INIT(&state->ll_callbacks); + + /* Regenerate new ids */ + if (ifp->options && + ifp->options->options & DHCPCD_IPV6RA_OWN && + 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 flags) +{ + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *ap; + struct ll_callback *cb; + +#if 0 + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &addr->s6_addr, + buf, INET6_ADDRSTRLEN); + logger(ctx, LOG_DEBUG, "%s: cmd %d addr %s flags %d", + ifname, cmd, buf, flags); +#endif + + if (ifs == NULL) + ifs = ctx->ifaces; + if (ifs == NULL) { + errno = ESRCH; + return; + } + TAILQ_FOREACH(ifp, ifs, next) { + /* Each psuedo interface also stores addresses */ + if (strcmp(ifp->name, ifname)) + continue; + state = ipv6_getstate(ifp); + if (state == NULL) + continue; + + if (!IN6_IS_ADDR_LINKLOCAL(addr)) { + ipv6nd_handleifa(ctx, cmd, ifname, addr, flags); + dhcp6_handleifa(ctx, cmd, ifname, addr, flags); + } + + TAILQ_FOREACH(ap, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr)) + break; + } + + switch (cmd) { + case RTM_DELADDR: + if (ap) { + TAILQ_REMOVE(&state->addrs, ap, next); + ipv6_freeaddr(ap); + } + break; + case RTM_NEWADDR: + if (ap == NULL) { + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + + ap = calloc(1, sizeof(*ap)); + ap->iface = ifp; + ap->addr = *addr; + ap->prefix_len = prefix_len; + ipv6_makeprefix(&ap->prefix, &ap->addr, + ap->prefix_len); + cbp = inet_ntop(AF_INET6, &addr->s6_addr, + buf, sizeof(buf)); + if (cbp) + snprintf(ap->saddr, sizeof(ap->saddr), + "%s/%d", cbp, prefix_len); + if (if_getlifetime6(ap) == -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. */ + ap->prefix_pltime = 0; + ap->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. */ + ap->acquired = ap->created; + TAILQ_INSERT_TAIL(&state->addrs, + ap, next); + } + ap->addr_flags = flags; +#ifdef IPV6_MANAGETEMPADDR + if (ap->addr_flags & IN6_IFF_TEMPORARY) + ap->flags |= IPV6_AF_TEMPORARY; +#endif + if (IN6_IS_ADDR_LINKLOCAL(&ap->addr)) { +#ifdef IPV6_POLLADDRFLAG + if (ap->addr_flags & IN6_IFF_TENTATIVE) { + struct timespec tv; + + ms_to_ts(&tv, RETRANS_TIMER / 2); + eloop_timeout_add_tv( + ap->iface->ctx->eloop, + &tv, ipv6_checkaddrflags, ap); + break; + } +#endif + + if (!(ap->addr_flags & IN6_IFF_NOTUSEABLE)) { + /* Now run any callbacks. + * Typically IPv6RS or DHCPv6 */ + while ((cb = + TAILQ_FIRST(&state->ll_callbacks))) + { + TAILQ_REMOVE( + &state->ll_callbacks, + cb, next); + cb->callback(cb->arg); + free(cb); + } + } + } + break; + } + } +} + +const struct ipv6_addr * +ipv6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr) +{ + const struct ipv6_state *state; + const struct ipv6_addr *ap; + + state = IPV6_CSTATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (addr == NULL) { + if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) && + !(ap->addr_flags & IN6_IFF_NOTUSEABLE)) + return ap; + } else { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr) && + !(ap->addr_flags & IN6_IFF_TENTATIVE)) + 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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __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 *ap; + + ap = calloc(1, sizeof(*ap)); + if (ap != NULL) { + ap->iface = ifp; + ap->prefix.s6_addr32[0] = htonl(0xfe800000); + ap->prefix.s6_addr32[1] = 0; + ap->prefix_len = 64; + ap->dadcounter = 0; + ap->prefix_pltime = ND6_INFINITE_LIFETIME; + ap->prefix_vltime = ND6_INFINITE_LIFETIME; + ap->flags = IPV6_AF_NEW; + ap->addr_flags = IN6_IFF_TENTATIVE; + } + return ap; +} + +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; + + if (ifp->options->options & DHCPCD_SLAACPRIVATE) { + dadcounter = 0; +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; + } + + logger(ap2->iface->ctx, LOG_WARNING, + "%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; +} + +/* Ensure the interface has a link-local address */ +int +ipv6_start(struct interface *ifp) +{ + const struct ipv6_state *state; + const struct ipv6_addr *ap; + + /* We can't assign a link-locak address to this, + * the ppp process has to. */ + if (ifp->flags & IFF_POINTOPOINT) + return 0; + + state = IPV6_CSTATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) && + !(ap->addr_flags & IN6_IFF_DUPLICATED)) + break; + } + /* Regenerate new ids */ + if (ifp->options->options & DHCPCD_IPV6RA_OWN && + ip6_use_tempaddr(ifp->name)) + ipv6_regentempifid(ifp); + } else + ap = NULL; + + if (ap == NULL && ipv6_addlinklocal(ifp) == -1) + return -1; + + /* Load existing routes */ + if_initrt6(ifp); + 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; + + ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL); + + /* Becuase we need to cache the addresses we don't control, + * we only free the state on when NOT dropping addresses. */ + if (drop == 0) { + while ((cb = TAILQ_FIRST(&state->ll_callbacks))) { + TAILQ_REMOVE(&state->ll_callbacks, cb, next); + free(cb); + } + free(state); + ifp->if_data[IF_DATA_IPV6] = NULL; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + } +} + +void +ipv6_ctxfree(struct dhcpcd_ctx *ctx) +{ + + if (ctx->ipv6 == NULL) + return; + + ipv6_freerts(ctx->ipv6->routes); + free(ctx->ipv6->routes); + free(ctx->ipv6->ra_routers); + ipv6_freerts(&ctx->ipv6->kroutes); + free(ctx->ipv6); +} + +int +ipv6_handleifa_addrs(int cmd, + struct ipv6_addrhead *addrs, const struct in6_addr *addr, int flags) +{ + struct ipv6_addr *ap, *apn; + uint8_t found, alldadcompleted; + + alldadcompleted = 1; + found = 0; + TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { + if (!IN6_ARE_ADDR_EQUAL(addr, &ap->addr)) { + if (ap->flags & IPV6_AF_ADDED && + !(ap->flags & IPV6_AF_DADCOMPLETED)) + alldadcompleted = 0; + continue; + } + switch (cmd) { + case RTM_DELADDR: + if (ap->flags & IPV6_AF_ADDED) { + logger(ap->iface->ctx, LOG_INFO, + "%s: deleted address %s", + ap->iface->name, ap->saddr); + ap->flags &= ~IPV6_AF_ADDED; + } + break; + case RTM_NEWADDR: + /* Safety - ignore tentative announcements */ + if (flags & (IN6_IFF_DETACHED |IN6_IFF_TENTATIVE)) + break; + if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { + found++; + if (flags & IN6_IFF_DUPLICATED) + ap->flags |= IPV6_AF_DUPLICATED; + else + ap->flags &= ~IPV6_AF_DUPLICATED; + if (ap->dadcallback) + ap->dadcallback(ap); + /* We need to set this here in-case the + * dadcallback function checks it */ + ap->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: + /* RFC4941 Section 3.2.1.1 + * Take the left-most 64bits and set bit 6 to zero */ + 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) { + logger(ia->iface->ctx, LOG_ERR, + "%s: too many duplicate temporary addresses", + ia->iface->name); + return; + } + get_monotonic(&tv); + if ((ia1 = ipv6_createtempaddr(ia, &tv)) == NULL) + logger(ia->iface->ctx, LOG_ERR, + "ipv6_createtempaddr: %m"); + 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; + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + + 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; + } + } + } + } + + if ((ia = calloc(1, sizeof(*ia))) == NULL) + return NULL; + + ia->iface = ia0->iface; + ia->addr = addr; + /* Must be made tentative, for our DaD to work */ + ia->addr_flags = IN6_IFF_TENTATIVE; + ia->dadcallback = ipv6_tempdadcallback; + ia->flags = IPV6_AF_NEW | IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY; + ia->prefix = ia0->prefix; + ia->prefix_len = ia0->prefix_len; + 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; + } + + cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); + if (cbp) + snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", + cbp, ia->prefix_len); + else + ia->saddr[0] = '\0'; + + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + return ia; +} + +void +ipv6_settempstale(struct interface *ifp) +{ + 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; + } +} + +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; + + logger(ia->iface->ctx, LOG_DEBUG, "%s: regen temp addr %s", + ia->iface->name, ia->saddr); + get_monotonic(&tv); + ia1 = ipv6_createtempaddr(ia, &tv); + if (ia1) + ipv6_addaddr(ia1, &tv); + else + logger(ia->iface->ctx, LOG_ERR, "ipv6_createtempaddr: %m"); +} + +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 */ + +static struct rt6 * +find_route6(struct rt6_head *rts, const struct rt6 *r) +{ + struct rt6 *rt; + + TAILQ_FOREACH(rt, rts, next) { + if (IN6_ARE_ADDR_EQUAL(&rt->dest, &r->dest) && +#ifdef HAVE_ROUTE_METRIC + (r->iface == NULL || rt->iface == NULL || + rt->iface->metric == r->iface->metric) && +#endif + IN6_ARE_ADDR_EQUAL(&rt->net, &r->net)) + return rt; + } + return NULL; +} + +static void +desc_route(const char *cmd, const struct rt6 *rt) +{ + char destbuf[INET6_ADDRSTRLEN]; + char gatebuf[INET6_ADDRSTRLEN]; + const char *ifname, *dest, *gate; + struct dhcpcd_ctx *ctx; + + ctx = rt->iface ? rt->iface->ctx : NULL; + ifname = rt->iface ? rt->iface->name : "(no iface)"; + dest = inet_ntop(AF_INET6, &rt->dest, destbuf, INET6_ADDRSTRLEN); + gate = inet_ntop(AF_INET6, &rt->gate, gatebuf, INET6_ADDRSTRLEN); + if (IN6_ARE_ADDR_EQUAL(&rt->gate, &in6addr_any)) + logger(ctx, LOG_INFO, "%s: %s route to %s/%d", + ifname, cmd, dest, ipv6_prefixlen(&rt->net)); + else if (IN6_ARE_ADDR_EQUAL(&rt->dest, &in6addr_any) && + IN6_ARE_ADDR_EQUAL(&rt->net, &in6addr_any)) + logger(ctx, LOG_INFO, "%s: %s default route via %s", + ifname, cmd, gate); + else + logger(ctx, LOG_INFO, "%s: %s%s route to %s/%d via %s", + ifname, cmd, + rt->flags & RTF_REJECT ? " reject" : "", + dest, ipv6_prefixlen(&rt->net), gate); +} + +static struct rt6* +ipv6_findrt(struct dhcpcd_ctx *ctx, const struct rt6 *rt, int flags) +{ + struct rt6 *r; + + TAILQ_FOREACH(r, &ctx->ipv6->kroutes, next) { + if (IN6_ARE_ADDR_EQUAL(&rt->dest, &r->dest) && +#ifdef HAVE_ROUTE_METRIC + (rt->iface == r->iface || + (rt->flags & RTF_REJECT && r->flags & RTF_REJECT)) && + (!flags || rt->metric == r->metric) && +#else + (!flags || rt->iface == r->iface || + (rt->flags & RTF_REJECT && r->flags & RTF_REJECT)) && +#endif + IN6_ARE_ADDR_EQUAL(&rt->net, &r->net)) + return r; + } + return NULL; +} + +void +ipv6_freerts(struct rt6_head *routes) +{ + struct rt6 *rt; + + while ((rt = TAILQ_FIRST(routes))) { + TAILQ_REMOVE(routes, rt, next); + free(rt); + } +} + +/* If something other than dhcpcd removes a route, + * we need to remove it from our internal table. */ +int +ipv6_handlert(struct dhcpcd_ctx *ctx, int cmd, struct rt6 *rt) +{ + struct rt6 *f; + + if (ctx->ipv6 == NULL) + return 0; + + f = ipv6_findrt(ctx, rt, 1); + switch(cmd) { + case RTM_ADD: + if (f == NULL) { + if ((f = malloc(sizeof(*f))) == NULL) + return -1; + *f = *rt; + TAILQ_INSERT_TAIL(&ctx->ipv6->kroutes, f, next); + } + break; + case RTM_DELETE: + if (f) { + TAILQ_REMOVE(&ctx->ipv6->kroutes, f, next); + free(f); + } + /* If we manage the route, remove it */ + if ((f = find_route6(ctx->ipv6->routes, rt))) { + desc_route("removing", f); + TAILQ_REMOVE(ctx->ipv6->routes, f, next); + free(f); + } + break; + } + return 0; +} + +#define n_route(a) nc_route(NULL, a) +#define c_route(a, b) nc_route(a, b) +static int +nc_route(struct rt6 *ort, struct rt6 *nrt) +{ + + /* Don't set default routes if not asked to */ + if (IN6_IS_ADDR_UNSPECIFIED(&nrt->dest) && + IN6_IS_ADDR_UNSPECIFIED(&nrt->net) && + !(nrt->iface->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route(ort == NULL ? "adding" : "changing", nrt); + + if (ort == NULL) { + ort = ipv6_findrt(nrt->iface->ctx, nrt, 0); + if (ort && + ((ort->flags & RTF_REJECT && nrt->flags & RTF_REJECT) || + (ort->iface == nrt->iface && +#ifdef HAVE_ROUTE_METRIC + ort->metric == nrt->metric && +#endif + IN6_ARE_ADDR_EQUAL(&ort->gate, &nrt->gate)))) + return 0; + } + +#ifdef HAVE_ROUTE_METRIC + /* With route metrics, we can safely add the new route before + * deleting the old route. */ + if (if_route6(RTM_ADD, nrt) == 0) { + if (ort && if_route6(RTM_DELETE, ort) == -1 && + errno != ESRCH) + logger(nrt->iface->ctx, LOG_ERR, "if_route6 (DEL): %m"); + return 0; + } + + /* 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. */ + if (ort && if_route6(RTM_DELETE, ort) == -1 && errno != ESRCH) + logger(nrt->iface->ctx, LOG_ERR, "if_route6: %m"); + if (if_route6(RTM_ADD, nrt) == 0) + return 0; +#ifdef HAVE_ROUTE_METRIC +logerr: +#endif + logger(nrt->iface->ctx, LOG_ERR, "if_route6 (ADD): %m"); + return -1; +} + +static int +d_route(struct rt6 *rt) +{ + int retval; + + desc_route("deleting", rt); + retval = if_route6(RTM_DELETE, rt); + if (retval != 0 && errno != ENOENT && errno != ESRCH) + logger(rt->iface->ctx, LOG_ERR, + "%s: if_delroute6: %m", rt->iface->name); + return retval; +} + +static struct rt6 * +make_route(const struct interface *ifp, const struct ra *rap) +{ + struct rt6 *r; + + r = calloc(1, sizeof(*r)); + if (r == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + r->iface = ifp; +#ifdef HAVE_ROUTE_METRIC + r->metric = ifp->metric; +#endif + if (rap) + r->mtu = rap->mtu; + else + r->mtu = 0; + return r; +} + +static struct rt6 * +make_prefix(const struct interface *ifp, const struct ra *rap, + const struct ipv6_addr *addr) +{ + struct rt6 *r; + + 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 or ones not on link or delegated */ + if (addr->prefix_len == 128 || + addr->prefix_vltime == 0 || + !(addr->flags & (IPV6_AF_ONLINK | IPV6_AF_DELEGATEDPFX))) + return NULL; + + /* Don't install a blackhole route when not creating bigger prefixes */ + if (addr->flags & IPV6_AF_DELEGATEDZERO) + return NULL; + + r = make_route(ifp, rap); + if (r == NULL) + return NULL; + r->dest = addr->prefix; + ipv6_mask(&r->net, addr->prefix_len); + if (addr->flags & IPV6_AF_DELEGATEDPFX) { + r->flags |= RTF_REJECT; + r->gate = in6addr_loopback; + } else + r->gate = in6addr_any; + return r; +} + +static struct rt6 * +make_router(const struct ra *rap) +{ + struct rt6 *r; + + r = make_route(rap->iface, rap); + if (r == NULL) + return NULL; + r->dest = in6addr_any; + r->net = in6addr_any; + r->gate = rap->from; + return r; +} + +#define RT_IS_DEFAULT(rtp) \ + (IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) && \ + IN6_ARE_ADDR_EQUAL(&((rtp)->net), &in6addr_any)) + +static void +ipv6_build_ra_routes(struct ipv6_ctx *ctx, struct rt6_head *dnr, int expired) +{ + struct rt6 *rt; + struct ra *rap; + const struct ipv6_addr *addr; + + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + if (rap->expired != expired) + continue; + if (rap->iface->options->options & DHCPCD_IPV6RA_OWN) { + TAILQ_FOREACH(addr, &rap->addrs, next) { + rt = make_prefix(rap->iface, rap, addr); + if (rt) + TAILQ_INSERT_TAIL(dnr, rt, next); + } + } + if (rap->lifetime && rap->iface->options->options & + (DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT) && + !rap->no_public_warned) + { + rt = make_router(rap); + if (rt) + TAILQ_INSERT_TAIL(dnr, rt, next); + } + } +} + +static void +ipv6_build_dhcp_routes(struct dhcpcd_ctx *ctx, + struct rt6_head *dnr, enum DH6S dstate) +{ + const struct interface *ifp; + const struct dhcp6_state *d6_state; + const struct ipv6_addr *addr; + struct rt6 *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 = make_prefix(ifp, NULL, addr); + if (rt) + TAILQ_INSERT_TAIL(dnr, rt, next); + } + } + } +} + +void +ipv6_buildroutes(struct dhcpcd_ctx *ctx) +{ + struct rt6_head dnr, *nrs; + struct rt6 *rt, *rtn, *or; + uint8_t have_default; + 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(&dnr); + + /* First add reachable routers and their prefixes */ + ipv6_build_ra_routes(ctx->ipv6, &dnr, 0); +#ifdef HAVE_ROUTE_METRIC + have_default = (TAILQ_FIRST(&dnr) != NULL); +#endif + + /* 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 */ + ipv6_build_dhcp_routes(ctx, &dnr, DH6S_BOUND); + ipv6_build_dhcp_routes(ctx, &dnr, DH6S_DELEGATED); + +#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 */ + ipv6_build_ra_routes(ctx->ipv6, &dnr, 1); + + nrs = malloc(sizeof(*nrs)); + if (nrs == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return; + } + TAILQ_INIT(nrs); + have_default = 0; + + TAILQ_FOREACH_SAFE(rt, &dnr, next, rtn) { + /* Is this route already in our table? */ + if (find_route6(nrs, rt) != NULL) + continue; + //rt->src.s_addr = ifp->addr.s_addr; + /* Do we already manage it? */ + if ((or = find_route6(ctx->ipv6->routes, rt))) { + if (or->iface != rt->iface || +#ifdef HAVE_ROUTE_METRIC + rt->metric != or->metric || +#endif + // or->src.s_addr != ifp->addr.s_addr || + !IN6_ARE_ADDR_EQUAL(&rt->gate, &or->gate)) + { + if (c_route(or, rt) != 0) + continue; + } + TAILQ_REMOVE(ctx->ipv6->routes, or, next); + free(or); + } else { + if (n_route(rt) != 0) + continue; + } + if (RT_IS_DEFAULT(rt)) + have_default = 1; + TAILQ_REMOVE(&dnr, rt, next); + TAILQ_INSERT_TAIL(nrs, rt, next); + } + + /* Free any routes we failed to add/change */ + while ((rt = TAILQ_FIRST(&dnr))) { + TAILQ_REMOVE(&dnr, rt, next); + free(rt); + } + + /* Remove old routes we used to manage + * If we own the default route, but not RA management itself + * then we need to preserve the last best default route we had */ + while ((rt = TAILQ_LAST(ctx->ipv6->routes, rt6_head))) { + TAILQ_REMOVE(ctx->ipv6->routes, rt, next); + if (find_route6(nrs, rt) == NULL) { + o = rt->iface->options->options; + if (!have_default && + (o & DHCPCD_IPV6RA_OWN_DEFAULT) && + !(o & DHCPCD_IPV6RA_OWN) && + RT_IS_DEFAULT(rt)) + have_default = 1; + /* no need to add it back to our routing table + * as we delete an exiting route when we add + * a new one */ + else if ((rt->iface->options->options & + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + d_route(rt); + } + free(rt); + } + + free(ctx->ipv6->routes); + ctx->ipv6->routes = nrs; +} @@ -0,0 +1,276 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV6_H +#define IPV6_H + +#include <sys/uio.h> +#include <netinet/in.h> + +#include "config.h" +#include "dhcpcd.h" + +#define ALLROUTERS "ff02::2" + +#define ROUNDUP8(a) (1 + (((a) - 1) | 7)) +#define ROUNDUP16(a) (1 + (((a) - 1) | 16)) + +#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 */ + +/* + * BSD kernels don't inform userland of DAD results. + * See the discussion here: + * http://mail-index.netbsd.org/tech-net/2013/03/15/msg004019.html + */ +#ifndef __linux__ +/* We guard here to avoid breaking a compile on linux ppc-64 headers */ +# include <sys/param.h> +#endif +#ifdef BSD +# define IPV6_POLLADDRFLAG +#endif + +/* This was fixed in NetBSD */ +#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 699002000 +# undef IPV6_POLLADDRFLAG +#endif + +/* Linux-3.18 can manage temporary addresses even with RA + * processing disabled. */ +//#undef IFA_F_MANAGETEMPADDR +#if defined(__linux__) && defined(IFA_F_MANAGETEMPADDR) +#define IPV6_MANAGETEMPADDR +#endif + +/* Some BSDs do not allow userland to set temporary addresses. */ +#if defined(BSD) && defined(IN6_IFF_TEMPORARY) +#define IPV6_MANAGETEMPADDR +#endif + +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; + short flags; + char saddr[INET6_ADDRSTRLEN]; + uint8_t iaid[4]; + uint16_t ia_type; + struct interface *delegating_iface; + uint8_t prefix_exclude_len; + struct in6_addr prefix_exclude; + + void (*dadcallback)(void *); + int dadcounter; + uint8_t *ns; + size_t nslen; + int nsprobes; +}; +TAILQ_HEAD(ipv6_addrhead, ipv6_addr); + +#define IPV6_AF_ONLINK 0x0001 +#define IPV6_AF_NEW 0x0002 +#define IPV6_AF_STALE 0x0004 +#define IPV6_AF_ADDED 0x0008 +#define IPV6_AF_AUTOCONF 0x0010 +#define IPV6_AF_DUPLICATED 0x0020 +#define IPV6_AF_DADCOMPLETED 0x0040 +#define IPV6_AF_DELEGATED 0x0080 +#define IPV6_AF_DELEGATEDPFX 0x0100 +#define IPV6_AF_DELEGATEDZERO 0x0200 +#define IPV6_AF_REQUEST 0x0400 +#ifdef IPV6_MANAGETEMPADDR +#define IPV6_AF_TEMPORARY 0X0800 +#endif + +struct rt6 { + TAILQ_ENTRY(rt6) next; + struct in6_addr dest; + struct in6_addr net; + struct in6_addr gate; + const struct interface *iface; + unsigned int flags; +#ifdef HAVE_ROUTE_METRIC + unsigned int metric; +#endif + unsigned int mtu; +}; +TAILQ_HEAD(rt6_head, rt6); + +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]) + +/* dhcpcd requires CMSG_SPACE to evaluate to a compile time constant. */ +#ifdef __QNX__ +#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))) + + +#ifdef INET6 +struct ipv6_ctx { + struct sockaddr_in6 from; + struct msghdr sndhdr; + struct iovec sndiov[2]; + unsigned char sndbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + struct msghdr rcvhdr; + struct iovec rcviov[2]; + unsigned char rcvbuf[IP6BUFLEN]; + unsigned char ansbuf[1500]; + char ntopbuf[INET6_ADDRSTRLEN]; + const char *sfrom; + + int nd_fd; + struct ra_head *ra_routers; + struct rt6_head *routes; + + struct rt6_head kroutes; + + int dhcp_fd; +}; + +struct ipv6_ctx *ipv6_init(struct dhcpcd_ctx *); +ssize_t ipv6_printaddr(char *, size_t, const uint8_t *, const char *); +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 *, const struct interface *, + const struct in6_addr *, int); +int ipv6_makeprefix(struct in6_addr *, 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 *); +int ipv6_addaddr(struct ipv6_addr *, const struct timespec *); +ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs); +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); +int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, + const struct in6_addr *, int); +int ipv6_publicaddr(const struct ipv6_addr *); +const struct ipv6_addr *ipv6_iffindaddr(const struct interface *, + const struct in6_addr *); +struct ipv6_addr *ipv6_findaddr(struct dhcpcd_ctx *, + const struct in6_addr *, short); +#define ipv6_linklocal(ifp) ipv6_iffindaddr((ifp), NULL) +int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *); +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 *); +void ipv6_settempstale(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 *); +void ipv6_ctxfree(struct dhcpcd_ctx *); +int ipv6_handlert(struct dhcpcd_ctx *, int cmd, struct rt6 *); +void ipv6_freerts(struct rt6_head *); +void ipv6_buildroutes(struct dhcpcd_ctx *); + +#else +#define ipv6_init(a) (NULL) +#define ipv6_start(a) (-1) +#define ipv6_free_ll_callbacks(a) {} +#define ipv6_free(a) {} +#define ipv6_drop(a) {} +#define ipv6_ctxfree(a) {} +#define ipv6_gentempifid(a) {} +#endif + +#endif diff --git a/ipv6nd.c b/ipv6nd.c new file mode 100644 index 0000000..89f6782 --- /dev/null +++ b/ipv6nd.c @@ -0,0 +1,1762 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <net/if.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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 "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 */ +} __packed; +#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 */ +} __packed; +#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 + +static int +ipv6nd_open(struct dhcpcd_ctx *dctx) +{ + struct ipv6_ctx *ctx; + int on; + struct icmp6_filter filt; + + ctx = dctx->ipv6; + if (ctx->nd_fd != -1) + return ctx->nd_fd; +#ifdef SOCK_CLOEXEC + ctx->nd_fd = socket(PF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + IPPROTO_ICMPV6); + if (ctx->nd_fd == -1) + return -1; +#else + if ((ctx->nd_fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1) + return -1; + if ((on = fcntl(ctx->nd_fd, F_GETFD, 0)) == -1 || + fcntl(ctx->nd_fd, F_SETFD, on | FD_CLOEXEC) == -1) + { + close(ctx->nd_fd); + ctx->nd_fd = -1; + return -1; + } + if ((on = fcntl(ctx->nd_fd, F_GETFL, 0)) == -1 || + fcntl(ctx->nd_fd, F_SETFL, on | O_NONBLOCK) == -1) + { + close(ctx->nd_fd); + ctx->nd_fd = -1; + return -1; + } +#endif + + /* 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(dctx->eloop, ctx->nd_fd, + ipv6nd_handledata, dctx, NULL, NULL); + return ctx->nd_fd; + +eexit: + if (ctx->nd_fd != -1) { + eloop_event_delete(dctx->eloop, ctx->nd_fd, 0); + 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; + struct nd_opt_hdr *nd; + + state = RS_STATE(ifp); + free(state->rs); + state->rslen = sizeof(*rs) + (size_t)ROUNDUP8(ifp->hwlen + 2); + state->rs = calloc(1, state->rslen); + if (state->rs == NULL) + return -1; + rs = (struct nd_router_solicit *)(void *)state->rs; + rs->nd_rs_type = ND_ROUTER_SOLICIT; + rs->nd_rs_code = 0; + rs->nd_rs_cksum = 0; + rs->nd_rs_reserved = 0; + nd = (struct nd_opt_hdr *)(state->rs + sizeof(*rs)); + 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 ipv6_ctx *ctx; + struct rs_state *state; + struct sockaddr_in6 dst; + struct cmsghdr *cm; + struct in6_pktinfo pi; + + if (ipv6_linklocal(ifp) == NULL) { + logger(ifp->ctx, LOG_DEBUG, + "%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 SIN6_LEN + dst.sin6_len = sizeof(dst); +#endif + dst.sin6_scope_id = ifp->index; + if (inet_pton(AF_INET6, ALLROUTERS, &dst.sin6_addr) != 1) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return; + } + + state = RS_STATE(ifp); + ctx = ifp->ctx->ipv6; + 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)); + + logger(ifp->ctx, LOG_DEBUG, + "%s: sending Router Solicitation", ifp->name); + if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: %s: sendmsg: %m", ifp->name, __func__); + ipv6nd_drop(ifp); + ifp->options->options &= ~(DHCPCD_IPV6 | DHCPCD_IPV6RS); + return; + } + + if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) + eloop_timeout_add_sec(ifp->ctx->eloop, + RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); + else { + logger(ifp->ctx, LOG_WARNING, + "%s: no IPv6 Routers available", ifp->name); + ipv6nd_drop(ifp); + dhcp6_drop(ifp, "EXPIRE6"); + } +} + +void +ipv6nd_expire(struct interface *ifp, uint32_t seconds) +{ + struct ra *rap; + struct timespec now; + + get_monotonic(&now); + + TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { + if (rap->iface == ifp) { + rap->received = now; + rap->expired = seconds ? 0 : 1; + if (seconds) { + struct ra_opt *rao; + struct ipv6_addr *ap; + + rap->lifetime = seconds; + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (ap->prefix_vltime) { + ap->prefix_vltime = seconds; + ap->prefix_pltime = seconds / 2; + } + } + ipv6_addaddrs(&rap->addrs); + TAILQ_FOREACH(rao, &rap->options, next) { + timespecclear(&rao->expire); + } + } + } + } + if (seconds) + ipv6nd_expirera(ifp); + else + ipv6_buildroutes(ifp->ctx); +} + +static void +ipv6nd_reachable(struct ra *rap, int flags) +{ + + if (flags & IPV6ND_REACHABLE) { + if (rap->lifetime && rap->expired) { + logger(rap->iface->ctx, LOG_INFO, + "%s: %s is reachable again", + rap->iface->name, rap->sfrom); + rap->expired = 0; + ipv6_buildroutes(rap->iface->ctx); + /* XXX Not really an RA */ + script_runreason(rap->iface, "ROUTERADVERT"); + } + } else { + if (rap->lifetime && !rap->expired) { + logger(rap->iface->ctx, LOG_WARNING, + "%s: %s is unreachable, expiring it", + rap->iface->name, rap->sfrom); + rap->expired = 1; + ipv6_buildroutes(rap->iface->ctx); + /* 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->ipv6) { + TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { + if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) { + ipv6nd_reachable(rap, flags); + break; + } + } + } +} + +static void +ipv6nd_free_opts(struct ra *rap) +{ + struct ra_opt *rao; + + while ((rao = TAILQ_FIRST(&rap->options))) { + TAILQ_REMOVE(&rap->options, rao, next); + free(rao->option); + free(rao); + } +} + +struct ipv6_addr * +ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, + short flags) +{ + struct ra *rap; + struct ipv6_addr *ap; + + if (ctx->ipv6 == NULL) + return NULL; + + TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (addr == NULL) { + if ((ap->flags & + (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) == + (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) + return ap; + } else if (ap->prefix_vltime && + IN6_ARE_ADDR_EQUAL(&ap->addr, addr) && + (!flags || ap->flags & flags)) + return ap; + } + } + return NULL; +} + +void ipv6nd_freedrop_ra(struct ra *rap, int drop) +{ + + eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); + eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); + if (!drop) + TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next); + ipv6_freedrop_addrs(&rap->addrs, drop, NULL); + ipv6nd_free_opts(rap); + free(rap->data); + free(rap); +} + +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->ipv6->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->ipv6->nd_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->ipv6->nd_fd, 0); + close(ctx->ipv6->nd_fd); + ctx->ipv6->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: + logger(rap->iface->ctx, LOG_ERR, + "rtpref: impossible RA flag %x", rap->flags); + return (RTPREF_INVALID); + } + /* NOTREACHED */ +} + +static void +add_router(struct ipv6_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; + const struct ra_opt *rao; + + 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)) + ap->flags |= IPV6_AF_DADCOMPLETED; + if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { + logger(ap->iface->ctx, LOG_DEBUG, + "%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 = 0; + TAILQ_FOREACH(rao, &rap->options, next) { + if (rao->type == ND_OPT_RDNSS && + rao->option && + timespecisset(&rao->expire)) + { + hasdns = 1; + break; + } + } + } + + 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) + logger(rap->iface->ctx, LOG_WARNING, + "%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->ipv6->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 *ap = arg, *rapap; + struct interface *ifp; + struct ra *rap; + int wascompleted, found; + struct timespec tv; + char buf[INET6_ADDRSTRLEN]; + const char *p; + int dadcounter; + + ifp = ap->iface; + wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED); + ap->flags |= IPV6_AF_DADCOMPLETED; + if (ap->flags & IPV6_AF_DUPLICATED) { + ap->dadcounter++; + logger(ifp->ctx, LOG_WARNING, "%s: DAD detected %s", + ifp->name, ap->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 (ap->dadcounter >= IDGEN_RETRIES) { + logger(ifp->ctx, LOG_ERR, + "%s: unable to obtain a" + " stable private address", + ifp->name); + goto try_script; + } + logger(ifp->ctx, LOG_INFO, "%s: deleting address %s", + ifp->name, ap->saddr); + if (if_deladdress6(ap) == -1 && + errno != EADDRNOTAVAIL && errno != ENXIO) + logger(ifp->ctx, LOG_ERR, "if_deladdress6: %m"); + dadcounter = ap->dadcounter; + if (ipv6_makestableprivate(&ap->addr, + &ap->prefix, ap->prefix_len, + ifp, &dadcounter) == -1) + { + logger(ifp->ctx, LOG_ERR, + "%s: ipv6_makestableprivate: %m", + ifp->name); + return; + } + ap->dadcounter = dadcounter; + ap->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); + ap->flags |= IPV6_AF_NEW; + p = inet_ntop(AF_INET6, &ap->addr, buf, sizeof(buf)); + if (p) + snprintf(ap->saddr, + sizeof(ap->saddr), + "%s/%d", + p, ap->prefix_len); + else + ap->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, ap); + return; + } + } + +try_script: + if (!wascompleted) { + TAILQ_FOREACH(rap, ifp->ctx->ipv6->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 == ap) + found = 1; + } + + if (wascompleted && found) { + logger(rap->iface->ctx, LOG_DEBUG, + "%s: Router Advertisement DAD completed", + rap->iface->name); + if (ipv6nd_scriptrun(rap)) + return; + } + } + } +} + +static int +ipv6nd_ra_has_public_addr(const struct ra *rap) +{ + const struct ipv6_addr *ia; + + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (ia->flags & IPV6_AF_AUTOCONF && + ipv6_publicaddr(ia)) + return 1; + } + return 0; +} + +static void +ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, + struct icmp6_hdr *icp, size_t len) +{ + struct ipv6_ctx *ctx = dctx->ipv6; + size_t olen, l, n; + ssize_t r; + struct nd_router_advert *nd_ra; + struct nd_opt_prefix_info *pi; + struct nd_opt_mtu *mtu; + struct nd_opt_rdnss *rdnss; + struct nd_opt_dnssl *dnssl; + uint32_t lifetime, mtuv; + uint8_t *p, *op; + struct in6_addr addr; + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + struct ra *rap; + struct nd_opt_hdr *ndo; + struct ra_opt *rao; + struct ipv6_addr *ap; + char *opt, *opt2, *tmp; + struct timespec expire; + uint8_t new_rap, new_data; +#ifdef IPV6_MANAGETEMPADDR + uint8_t new_ap; +#endif + + if (len < sizeof(struct nd_router_advert)) { + logger(dctx, LOG_ERR, + "IPv6 RA packet too short from %s", ctx->sfrom); + return; + } + + if (!IN6_IS_ADDR_LINKLOCAL(&ctx->from.sin6_addr)) { + logger(dctx, LOG_ERR, + "RA from non local address %s", ctx->sfrom); + return; + } + + if (ifp == NULL) { +#ifdef DEBUG_RS + logger(dctx, LOG_DEBUG, + "RA for unexpected interface from %s", ctx->sfrom); +#endif + return; + } + if (!(ifp->options->options & DHCPCD_IPV6RS)) { +#ifdef DEBUG_RS + logger(ifp->ctx, LOG_DEBUG, "%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 + logger(ifp->ctx, LOG_DEBUG, + "%s: received RA from %s (no link-local)", + ifp->name, ctx->sfrom); +#endif + return; + } + + if (ipv6_iffindaddr(ifp, &ctx->from.sin6_addr)) { + logger(ifp->ctx, LOG_DEBUG, + "%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; + rap->no_public_warned = 0; + } + new_data = 1; + } else + new_data = 0; + if (new_data || ifp->options->options & DHCPCD_DEBUG) + logger(ifp->ctx, LOG_INFO, "%s: Router Advertisement from %s", + ifp->name, ctx->sfrom); + + if (rap == NULL) { + rap = calloc(1, sizeof(*rap)); + if (rap == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return; + } + rap->iface = ifp; + rap->from = ctx->from.sin6_addr; + strlcpy(rap->sfrom, ctx->sfrom, sizeof(rap->sfrom)); + TAILQ_INIT(&rap->addrs); + TAILQ_INIT(&rap->options); + new_rap = 1; + } else + new_rap = 0; + if (rap->data_len == 0) { + rap->data = malloc(len); + if (rap->data == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + if (new_rap) + free(rap); + return; + } + memcpy(rap->data, icp, len); + rap->data_len = len; + } + + get_monotonic(&rap->received); + rap->flags = nd_ra->nd_ra_flags_reserved; + if (new_rap == 0 && rap->lifetime == 0) + logger(ifp->ctx, LOG_WARNING, "%s: %s router available", + ifp->name, rap->sfrom); + rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); + 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) + rap->retrans = ntohl(nd_ra->nd_ra_retransmit); + if (rap->lifetime) + rap->expired = 0; + + ipv6_settempstale(ifp); + 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); + lifetime = ~0U; + for (; len > 0; p += olen, len -= olen) { + if (len < sizeof(struct nd_opt_hdr)) { + logger(ifp->ctx, LOG_ERR, + "%s: short option", ifp->name); + break; + } + ndo = (struct nd_opt_hdr *)p; + olen = (size_t)ndo->nd_opt_len * 8; + if (olen == 0) { + logger(ifp->ctx, LOG_ERR, + "%s: zero length option", ifp->name); + break; + } + if (olen > len) { + logger(ifp->ctx, LOG_ERR, + "%s: option length exceeds message", ifp->name); + break; + } + + opt = opt2 = NULL; + switch (ndo->nd_opt_type) { + case ND_OPT_PREFIX_INFORMATION: + pi = (struct nd_opt_prefix_info *)(void *)ndo; + if (pi->nd_opt_pi_len != 4) { + logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, + "%s: invalid option len for prefix", + ifp->name); + continue; + } + if (pi->nd_opt_pi_prefix_len > 128) { + logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, + "%s: invalid prefix len", + ifp->name); + continue; + } + if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) || + IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) + { + logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, + "%s: invalid prefix in RA", ifp->name); + continue; + } + if (ntohl(pi->nd_opt_pi_preferred_time) > + ntohl(pi->nd_opt_pi_valid_time)) + { + logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, + "%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->nd_opt_pi_prefix)) + break; + if (ap == NULL) { + if (!(pi->nd_opt_pi_flags_reserved & + ND_OPT_PI_FLAG_AUTO) && + !(pi->nd_opt_pi_flags_reserved & + ND_OPT_PI_FLAG_ONLINK)) + continue; + ap = calloc(1, sizeof(*ap)); + if (ap == NULL) + break; + ap->iface = rap->iface; + ap->flags = IPV6_AF_NEW; + ap->prefix_len = pi->nd_opt_pi_prefix_len; + ap->prefix = pi->nd_opt_pi_prefix; + if (pi->nd_opt_pi_flags_reserved & + ND_OPT_PI_FLAG_AUTO && + ap->iface->options->options & + DHCPCD_IPV6RA_AUTOCONF) + { + ap->flags |= IPV6_AF_AUTOCONF; + ap->dadcounter = + ipv6_makeaddr(&ap->addr, ifp, + &ap->prefix, + pi->nd_opt_pi_prefix_len); + if (ap->dadcounter == -1) { + free(ap); + break; + } + cbp = inet_ntop(AF_INET6, + &ap->addr, + buf, sizeof(buf)); + if (cbp) + snprintf(ap->saddr, + sizeof(ap->saddr), + "%s/%d", + cbp, ap->prefix_len); + else + ap->saddr[0] = '\0'; + } else { + memset(&ap->addr, 0, sizeof(ap->addr)); + ap->saddr[0] = '\0'; + } + ap->dadcallback = ipv6nd_dadcallback; + ap->created = ap->acquired = rap->received; + 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 (ipv6_iffindaddr(ifp, &ap->addr) && + 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->received; + } + 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); + ap->nsprobes = 0; + cbp = inet_ntop(AF_INET6, &ap->prefix, buf, sizeof(buf)); + if (cbp) { + l = strlen(cbp); + opt = malloc(l + 5); + if (opt) { + snprintf(opt, l + 5, "%s/%d", cbp, + ap->prefix_len); + opt2 = strdup(ap->saddr); + } + } + +#ifdef IPV6_MANAGETEMPADDR + /* RFC4941 Section 3.3.3 */ + if (ap->flags & IPV6_AF_AUTOCONF && + ap->iface->options->options & DHCPCD_IPV6RA_OWN && + 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) + logger(ap->iface->ctx, LOG_ERR, + "ipv6_createtempaddr: %m"); + } + } +#endif + + lifetime = ap->prefix_vltime; + break; + + case ND_OPT_MTU: + mtu = (struct nd_opt_mtu *)(void *)p; + mtuv = ntohl(mtu->nd_opt_mtu_mtu); + if (mtuv < IPV6_MMTU) { + logger(ifp->ctx, LOG_ERR, "%s: invalid MTU %d", + ifp->name, mtuv); + break; + } + rap->mtu = mtuv; + snprintf(buf, sizeof(buf), "%d", mtuv); + opt = strdup(buf); + break; + + case ND_OPT_RDNSS: + rdnss = (struct nd_opt_rdnss *)p; + lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime); + op = (uint8_t *)ndo; + op += offsetof(struct nd_opt_rdnss, + nd_opt_rdnss_lifetime); + op += sizeof(rdnss->nd_opt_rdnss_lifetime); + l = 0; + for (n = (size_t)ndo->nd_opt_len - 1; n > 1; n -= 2, + op += sizeof(addr)) + { + r = ipv6_printaddr(NULL, 0, op, ifp->name); + if (r != -1) + l += (size_t)r + 1; + } + op = (uint8_t *)ndo; + op += offsetof(struct nd_opt_rdnss, + nd_opt_rdnss_lifetime); + op += sizeof(rdnss->nd_opt_rdnss_lifetime); + tmp = opt = malloc(l); + if (opt == NULL) + continue; + for (n = (size_t)ndo->nd_opt_len - 1; n > 1; n -= 2, + op += sizeof(addr)) + { + r = ipv6_printaddr(tmp, l, op, + ifp->name); + if (r != -1) { + l -= ((size_t)r + 1); + tmp += (size_t)r; + *tmp++ = ' '; + } + } + if (tmp != opt) + (*--tmp) = '\0'; + else + *opt = '\0'; + break; + + case ND_OPT_DNSSL: + dnssl = (struct nd_opt_dnssl *)p; + lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime); + op = p + offsetof(struct nd_opt_dnssl, + nd_opt_dnssl_lifetime); + op += sizeof(dnssl->nd_opt_dnssl_lifetime); + n = (size_t)(dnssl->nd_opt_dnssl_len - 1) * 8; + r = decode_rfc3397(NULL, 0, op, n); + if (r < 1) { + logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, + "%s: invalid DNSSL option", + ifp->name); + continue; + } else { + l = (size_t)r + 1; + tmp = malloc(l); + if (tmp) { + decode_rfc3397(tmp, l, op, n); + l -= 1; + n = (size_t)print_string(NULL, 0, + STRING | ARRAY | DOMAIN, + (const uint8_t *)tmp, l); + n++; + opt = malloc(n); + if (opt) { + print_string(opt, n, + STRING | ARRAY | DOMAIN, + (const uint8_t *)tmp, l); + } else + logger(ifp->ctx, LOG_ERR, + "%s: %m", __func__); + free(tmp); + } + } + break; + + default: + continue; + } + + if (opt == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + continue; + } + + n = ndo->nd_opt_type; +extra_opt: + TAILQ_FOREACH(rao, &rap->options, next) { + if (rao->type == n && + strcmp(rao->option, opt) == 0) + break; + } + if (lifetime == 0 || *opt == '\0') { + if (rao) { + TAILQ_REMOVE(&rap->options, rao, next); + free(rao->option); + free(rao); + } + free(opt); + free(opt2); + continue; + } + + if (rao == NULL) { + rao = malloc(sizeof(*rao)); + if (rao == NULL) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + continue; + } + rao->type = (uint16_t)n; + rao->option = opt; + TAILQ_INSERT_TAIL(&rap->options, rao, next); + } else + free(opt); + if (lifetime == ~0U) + timespecclear(&rao->expire); + else { + expire.tv_sec = (time_t)lifetime; + expire.tv_nsec = 0; + timespecadd(&rap->received, &expire, &rao->expire); + } + if (rao && rao->type == ND_OPT_PREFIX_INFORMATION && opt2) { + n = _ND_OPT_PREFIX_ADDR; + opt = opt2; + opt2 = NULL; + goto extra_opt; + } + } + + if (new_rap) + add_router(ifp->ctx->ipv6, rap); + if (!ipv6nd_ra_has_public_addr(rap) && + !(rap->iface->options->options & DHCPCD_IPV6RA_ACCEPT_NOPUBLIC) && + (!(rap->flags & ND_RA_FLAG_MANAGED) || + !dhcp6_has_public_addr(rap->iface))) + { + logger(rap->iface->ctx, + rap->no_public_warned ? LOG_DEBUG : LOG_WARNING, + "%s: ignoring RA from %s" + " (no public prefix, no managed address)", + rap->iface->name, rap->sfrom); + rap->no_public_warned = 1; + return; + } + if (ifp->ctx->options & DHCPCD_TEST) { + script_runreason(ifp, "TEST"); + goto handle_flag; + } + ipv6_addaddrs(&rap->addrs); +#ifdef IPV6_MANAGETEMPADDR + ipv6_addtempaddrs(ifp, &rap->received); +#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_initrt6(ifp); + + ipv6_buildroutes(ifp->ctx); + 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; + if (rap->flags & ND_RA_FLAG_MANAGED) { + if (new_data && dhcp6_start(ifp, DH6S_INIT) == -1) + logger(ifp->ctx, LOG_ERR, + "dhcp6_start: %s: %m", ifp->name); + } else if (rap->flags & ND_RA_FLAG_OTHER) { + if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1) + logger(ifp->ctx, LOG_ERR, + "dhcp6_start: %s: %m", ifp->name); + } else { + if (new_data) + logger(ifp->ctx, LOG_DEBUG, + "%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); +} + +/* Run RA's we ignored becuase they had no public addresses + * This should only be called when DHCPv6 applies a public address */ +void +ipv6nd_runignoredra(struct interface *ifp) +{ + struct ra *rap; + + TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { + if (rap->iface == ifp && + !rap->expired && + rap->no_public_warned) + { + rap->no_public_warned = 0; + logger(rap->iface->ctx, LOG_INFO, + "%s: applying ignored RA from %s", + rap->iface->name, rap->sfrom); + if (ifp->ctx->options & DHCPCD_TEST) { + script_runreason(ifp, "TEST"); + continue; + } + if (ipv6nd_scriptrun(rap)) + return; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); + } + } +} + +int +ipv6nd_hasra(const struct interface *ifp) +{ + const struct ra *rap; + + if (ifp->ctx->ipv6) { + TAILQ_FOREACH(rap, ifp->ctx->ipv6->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->ipv6) { + TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { + if (rap->iface == ifp && + !rap->expired && + (rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) + return 1; + } + } + return 0; +} + +ssize_t +ipv6nd_env(char **env, const char *prefix, const struct interface *ifp) +{ + size_t i, l, len; + const struct ra *rap; + const struct ra_opt *rao; + char buffer[32]; + const char *optn; + char **pref, **addr, **mtu, **rdnss, **dnssl, ***var, *new; + + i = l = 0; + TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { + if (rap->iface != ifp) + continue; + i++; + if (env) { + snprintf(buffer, sizeof(buffer), + "ra%zu_from", i); + setvar(ifp->ctx, &env, prefix, buffer, rap->sfrom); + } + l++; + + pref = addr = mtu = rdnss = dnssl = NULL; + TAILQ_FOREACH(rao, &rap->options, next) { + if (rao->option == NULL) + continue; + var = NULL; + switch(rao->type) { + case ND_OPT_PREFIX_INFORMATION: + optn = "prefix"; + var = &pref; + break; + case _ND_OPT_PREFIX_ADDR: + optn = "addr"; + var = &addr; + break; + case ND_OPT_MTU: + optn = "mtu"; + var = &mtu; + break; + case ND_OPT_RDNSS: + optn = "rdnss"; + var = &rdnss; + break; + case ND_OPT_DNSSL: + optn = "dnssl"; + var = &dnssl; + break; + default: + continue; + } + if (*var == NULL) { + *var = env ? env : &new; + l++; + } else if (env) { + /* With single only options, last one takes + * precedence */ + if (rao->type == ND_OPT_MTU) { + new = strchr(**var, '='); + if (new == NULL) { + logger(ifp->ctx, LOG_ERR, + "new is null"); + continue; + } else + new++; + len = (size_t)(new - **var) + + strlen(rao->option) + 1; + if (len > strlen(**var)) + new = realloc(**var, len); + else + new = **var; + if (new) { + **var = new; + new = strchr(**var, '='); + if (new) { + len -= + (size_t) + (new - **var); + strlcpy(new + 1, + rao->option, + len - 1); + } else + logger(ifp->ctx, + LOG_ERR, + "new is null"); + } + continue; + } + len = strlen(rao->option) + 1; + new = realloc(**var, strlen(**var) + 1 + len); + if (new) { + **var = new; + new += strlen(new); + *new++ = ' '; + strlcpy(new, rao->option, len); + } else + logger(ifp->ctx, LOG_ERR, + "%s: %m", __func__); + continue; + } + if (env) { + snprintf(buffer, sizeof(buffer), + "ra%zu_%s", i, optn); + setvar(ifp->ctx, &env, + prefix, buffer, rao->option); + } + } + } + + if (env) + setvard(ifp->ctx, &env, prefix, "ra_count", i); + l++; + return (ssize_t)l; +} + +void +ipv6nd_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname, + const struct in6_addr *addr, int flags) +{ + struct ra *rap; + + if (ctx->ipv6 == NULL) + return; + TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { + if (strcmp(rap->iface->name, ifname)) + continue; + ipv6_handleifa_addrs(cmd, &rap->addrs, addr, flags); + } +} + +void +ipv6nd_expirera(void *arg) +{ + struct interface *ifp; + struct ra *rap, *ran; + struct ra_opt *rao, *raon; + struct timespec now, lt, expire, next; + uint8_t expired, valid, validone; + + ifp = arg; + get_monotonic(&now); + expired = 0; + timespecclear(&next); + + validone = 0; + TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) { + if (rap->iface != ifp) + continue; + valid = 0; + if (rap->lifetime) { + lt.tv_sec = (time_t)rap->lifetime; + lt.tv_nsec = 0; + timespecadd(&rap->received, <, &expire); + if (rap->lifetime == 0 || timespeccmp(&now, &expire, >)) + { + if (!rap->expired) { + logger(ifp->ctx, LOG_WARNING, + "%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; + } + } + + /* Addresses are expired in ipv6_addaddrs + * so that DHCPv6 addresses can be removed also. */ + TAILQ_FOREACH_SAFE(rao, &rap->options, next, raon) { + if (rap->expired) { + switch(rao->type) { + case ND_OPT_RDNSS: /* FALLTHROUGH */ + case ND_OPT_DNSSL: + /* RFC6018 end of section 5.2 states + * that if tha RA has a lifetime of 0 + * then we should expire these + * options */ + TAILQ_REMOVE(&rap->options, rao, next); + expired = 1; + free(rao->option); + free(rao); + continue; + } + } + if (!timespecisset(&rao->expire)) + continue; + if (timespeccmp(&now, &rao->expire, >)) { + /* Expired prefixes are logged above */ + if (rao->type != ND_OPT_PREFIX_INFORMATION) + logger(ifp->ctx, LOG_WARNING, + "%s: %s: expired option %d", + ifp->name, rap->sfrom, rao->type); + TAILQ_REMOVE(&rap->options, rao, next); + expired = 1; + free(rao->option); + free(rao); + continue; + } + valid = 1; + timespecsub(&rao->expire, &now, <); + if (!timespecisset(&next) || timespeccmp(&next, <, >)) + next = lt; + } + + /* No valid lifetimes are left on the RA, so we might + * as well punt it. */ + if (!valid && TAILQ_FIRST(&rap->addrs) == NULL) + ipv6nd_free_ra(rap); + else + validone = 1; + } + + if (timespecisset(&next)) + eloop_timeout_add_tv(ifp->ctx->eloop, + &next, ipv6nd_expirera, ifp); + if (expired) { + ipv6_buildroutes(ifp->ctx); + script_runreason(ifp, "ROUTERADVERT"); + } + + /* No valid routers? Kill any DHCPv6. */ + if (!validone) + dhcp6_drop(ifp, "EXPIRE6"); +} + +void +ipv6nd_drop(struct interface *ifp) +{ + struct ra *rap; + uint8_t expired = 0; + TAILQ_HEAD(rahead, ra) rtrs; + + if (ifp->ctx->ipv6 == NULL) + return; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + TAILQ_INIT(&rtrs); + TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { + if (rap->iface == ifp) { + rap->expired = expired = 1; + TAILQ_REMOVE(ifp->ctx->ipv6->ra_routers, rap, next); + TAILQ_INSERT_TAIL(&rtrs, rap, next); + } + } + if (expired) { + while ((rap = TAILQ_FIRST(&rtrs))) { + TAILQ_REMOVE(&rtrs, rap, next); + ipv6nd_drop_ra(rap); + } + ipv6_buildroutes(ifp->ctx); + if ((ifp->options->options & + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != + (DHCPCD_EXITING | DHCPCD_PERSISTENT)) + script_runreason(ifp, "ROUTERADVERT"); + } +} + +static void +ipv6nd_handlena(struct dhcpcd_ctx *dctx, struct interface *ifp, + struct icmp6_hdr *icp, size_t len) +{ + struct ipv6_ctx *ctx = dctx->ipv6; + struct nd_neighbor_advert *nd_na; + struct ra *rap; + uint32_t is_router, is_solicited; + char buf[INET6_ADDRSTRLEN]; + const char *taddr; + + if (ifp == NULL) { +#ifdef DEBUG_NS + logger(ctx, LOG_DEBUG, "NA for unexpected interface from %s", + dctx->sfrom); +#endif + return; + } + + if ((size_t)len < sizeof(struct nd_neighbor_advert)) { + logger(ifp->ctx, LOG_ERR, "%s: IPv6 NA too short from %s", + ifp->name, 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); + + if (IN6_IS_ADDR_MULTICAST(&nd_na->nd_na_target)) { + logger(ifp->ctx, LOG_ERR, "%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->nd_na_target)) + break; + } + if (rap == NULL) { +#ifdef DEBUG_NS + logger(ifp->ctx, LOG_DEBUG, "%s: unexpected NA from %s for %s", + ifp->name, ctx->sfrom, taddr); +#endif + return; + } + +#ifdef DEBUG_NS + logger(ifp->ctx, LOG_DEBUG, "%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) { + logger(ifp->ctx, LOG_INFO, "%s: %s not a router (%s)", + ifp->name, taddr, ctx->sfrom); + rap->expired = 1; + ipv6_buildroutes(ifp->ctx); + script_runreason(ifp, "ROUTERADVERT"); + return; + } + + if (is_solicited && is_router && rap->lifetime) { + if (rap->expired) { + rap->expired = 0; + logger(ifp->ctx, LOG_INFO, "%s: %s reachable (%s)", + ifp->name, taddr, ctx->sfrom); + ipv6_buildroutes(ifp->ctx); + script_runreason(rap->iface, "ROUTERADVERT"); /* XXX */ + } + } +} + +static void +ipv6nd_handledata(void *arg) +{ + struct dhcpcd_ctx *dctx; + struct ipv6_ctx *ctx; + ssize_t len; + struct cmsghdr *cm; + int hoplimit; + struct in6_pktinfo pkt; + struct icmp6_hdr *icp; + struct interface *ifp; + + dctx = arg; + ctx = dctx->ipv6; + ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + + CMSG_SPACE(sizeof(int)); + len = recvmsg(ctx->nd_fd, &ctx->rcvhdr, 0); + if (len == -1) { + logger(dctx, LOG_ERR, "recvmsg: %m"); + eloop_event_delete(dctx->eloop, ctx->nd_fd, 0); + close(ctx->nd_fd); + ctx->nd_fd = -1; + return; + } + ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr, + ctx->ntopbuf, INET6_ADDRSTRLEN); + if ((size_t)len < sizeof(struct icmp6_hdr)) { + logger(dctx, LOG_ERR, "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 || hoplimit == 0) { + logger(dctx, LOG_ERR, + "IPv6 RA/NA did not contain index or hop limit from %s", + ctx->sfrom); + return; + } + + TAILQ_FOREACH(ifp, dctx->ifaces, next) { + if (ifp->index == (unsigned int)pkt.ipi6_ifindex && + !(ifp->options->options & DHCPCD_PFXDLGONLY)) + { + if (!(ifp->options->options & DHCPCD_IPV6)) + return; + break; + } + } + + 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(dctx, ifp, icp, (size_t)len); + return; + case ND_ROUTER_ADVERT: + ipv6nd_handlera(dctx, ifp, icp, (size_t)len); + return; + } + } + + logger(dctx, LOG_ERR, "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; + + logger(ifp->ctx, LOG_INFO, "%s: soliciting an IPv6 router", ifp->name); + if (ipv6nd_open(ifp->ctx) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: ipv6nd_open: %m", __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) { + logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); + return; + } + } + + /* Always make a new probe as the underlying hardware + * address could have changed. */ + ipv6nd_makersprobe(ifp); + if (state->rs == NULL) { + logger(ifp->ctx, LOG_ERR, + "%s: ipv6ns_makersprobe: %m", __func__); + return; + } + + state->rsprobes = 0; + ipv6nd_sendrsprobe(ifp); +} + +void +ipv6nd_startrs(struct interface *ifp) +{ + struct timespec tv; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + tv.tv_sec = 0; + tv.tv_nsec = (suseconds_t)arc4random_uniform( + MAX_RTR_SOLICITATION_DELAY * NSEC_PER_SEC); + timespecnorm(&tv); + logger(ifp->ctx, LOG_DEBUG, + "%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; +} diff --git a/ipv6nd.h b/ipv6nd.h new file mode 100644 index 0000000..2ab76df --- /dev/null +++ b/ipv6nd.h @@ -0,0 +1,121 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV6ND_H +#define IPV6ND_H + +#include <time.h> + +#include "config.h" +#include "dhcpcd.h" +#include "ipv6.h" + +struct ra_opt { + TAILQ_ENTRY(ra_opt) next; + uint16_t type; + struct timespec expire; + char *option; +}; + +struct ra { + TAILQ_ENTRY(ra) next; + struct interface *iface; + struct in6_addr from; + char sfrom[INET6_ADDRSTRLEN]; + unsigned char *data; + size_t data_len; + struct timespec received; + unsigned char flags; + uint32_t lifetime; + uint32_t reachable; + uint32_t retrans; + uint32_t mtu; + struct ipv6_addrhead addrs; + TAILQ_HEAD(, ra_opt) options; + uint8_t expired; + uint8_t no_public_warned; +}; + +TAILQ_HEAD(ra_head, ra); + +struct rs_state { + unsigned char *rs; + size_t rslen; + int rsprobes; +}; + +#define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND]) +#define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a))) + +#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 */ + +/* 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_startrs(struct interface *); +ssize_t ipv6nd_env(char **, const char *, const struct interface *); +struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *, + const struct in6_addr *, short); +void ipv6nd_freedrop_ra(struct ra *, int); +#define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra), 0) +#define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra), 1) +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_runignoredra(struct interface *); +void ipv6nd_handleifa(struct dhcpcd_ctx *, int, + const char *, const struct in6_addr *, int); +int ipv6nd_dadcompleted(const struct interface *); +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_findaddr(a, b, c) (0) +#define ipv6nd_free(a) {} +#define ipv6nd_hasra(a) (0) +#define ipv6nd_dadcompleted(a) (0) +#define ipv6nd_drop(a) {} +#endif + +#endif diff --git a/script.c b/script.c new file mode 100644 index 0000000..918f700 --- /dev/null +++ b/script.c @@ -0,0 +1,735 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <signal.h> +/* We can't include spawn.h here because it may not exist. + * config.h will pull it in, or our compat one. */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "if.h" +#include "if-options.h" +#include "ipv6nd.h" +#include "script.h" + +#ifdef HAVE_SPAWN_H +#include <spawn.h> +#else +#include "compat/posix_spawn.h" +#endif + +/* 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", + "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 i; +#ifdef USE_SIGNALS + 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; dhcpcd_handlesigs[i]; i++) + sigaddset(&defsigs, dhcpcd_handlesigs[i]); + posix_spawnattr_setsigdefault(&attr, &defsigs); + posix_spawnattr_setsigmask(&attr, &ctx->sigset); +#endif + errno = 0; + i = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); + if (i) { + errno = i; + return -1; + } + return pid; +} + +#ifdef INET +static char * +make_var(struct dhcpcd_ctx *ctx, const char *prefix, const char *var) +{ + size_t len; + char *v; + + len = strlen(prefix) + strlen(var) + 2; + v = malloc(len); + if (v == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __func__); + return NULL; + } + snprintf(v, len, "%s_%s", prefix, var); + return v; +} + + +static int +append_config(struct dhcpcd_ctx *ctx, 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] + strlen(prefix) + 1, + config[i], e1) == 0) + { + p = make_var(ctx, prefix, config[i]); + if (p == NULL) { + ret = -1; + break; + } + free(ne[j]); + ne[j] = p; + break; + } + } + if (j == *len) { + j++; + p = make_var(ctx, prefix, config[i]); + if (p == NULL) { + ret = -1; + break; + } + nep = realloc(ne, sizeof(char *) * (j + 1)); + if (nep == NULL) { + logger(ctx, LOG_ERR, "%s: %m", __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; +} + +static ssize_t +make_env(const struct interface *ifp, const char *reason, char ***argv) +{ + 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; +#ifdef INET + int dhcp; + const struct dhcp_state *state; +#endif +#ifdef INET6 + const struct dhcp6_state *d6_state; + int dhcp6, ra; +#endif + +#ifdef INET + dhcp = 0; + state = D_STATE(ifp); +#endif +#ifdef INET6 + dhcp6 = ra = 0; + d6_state = D6_CSTATE(ifp); +#endif + if (strcmp(reason, "TEST") == 0) { + if (1 == 2) {} +#ifdef INET6 + else if (d6_state && d6_state->new) + dhcp6 = 1; + else if (ipv6nd_hasra(ifp)) + ra = 1; +#endif +#ifdef INET + else + dhcp = 1; +#endif + } +#ifdef INET6 + else if (reason[strlen(reason) - 1] == '6') + dhcp6 = 1; + else if (strcmp(reason, "ROUTERADVERT") == 0) + ra = 1; +#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) + { + /* This space left intentionally blank */ + } +#ifdef INET + else + dhcp = 1; +#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 = 13; + +#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 + 3 + 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->name)); + l = e = strlen("interface_order="); + TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { + if (!(ifp2->options->options & DHCPCD_PFXDLGONLY)) + 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) { + if (!(ifp2->options->options & DHCPCD_PFXDLGONLY)) { + 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 + || (dhcp && state && state->new) +#endif +#ifdef INET6 + || (dhcp6 && d6_state && d6_state->new) + || (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 (dhcpcd_oneup(ifp->ctx)) + env[11] = strdup("if_oneup=true"); + else + env[11] = strdup("if_oneup=false"); + if (env[11] == NULL) + goto eexit; + if (dhcpcd_ipwaited(ifp->ctx)) + env[12] = strdup("if_ipwaited=true"); + else + env[12] = strdup("if_ipwaited=false"); + if (env[12] == NULL) + goto eexit; + 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, 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, + ESCSTRING, + (const uint8_t *)ifp->ssid, ifp->ssid_len); + elen++; + } + } +#ifdef INET + if (dhcp && state && state->old) { + n = dhcp_env(NULL, NULL, state->old, 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, ifp); + if (n == -1) + goto eexit; + elen += (size_t)n; + } + if (append_config(ifp->ctx, &env, &elen, "old", + (const char *const *)ifo->config) == -1) + goto eexit; + } +#endif +#ifdef INET6 + if (dhcp6 && d6_state && ifo->options & DHCPCD_PFXDLGONLY) { + nenv = realloc(env, sizeof(char *) * (elen + 2)); + if (nenv == NULL) + goto eexit; + env = nenv; + env[elen] = strdup("ifclass=pd"); + if (env[elen] == NULL) + goto eexit; + elen++; + } + if (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 + if (dhcp && state && state->new) { + n = dhcp_env(NULL, NULL, state->new, 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, ifp); + if (n == -1) + goto eexit; + elen += (size_t)n; + } + if (append_config(ifp->ctx, &env, &elen, "new", + (const char *const *)ifo->config) == -1) + goto eexit; + } +#endif +#ifdef INET6 + if (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 (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: + logger(ifp->ctx, LOG_ERR, "%s: %m", __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); + return -1; + } + 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; + } +#endif + +#ifdef INET6 + 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)) + return 0; + + argv[0] = ifp->options->script ? ifp->options->script : UNCONST(SCRIPT); + argv[1] = NULL; + logger(ifp->ctx, LOG_DEBUG, "%s: executing `%s' %s", + ifp->name, argv[0], reason); + + /* Make our env */ + elen = (size_t)make_env(ifp, reason, &env); + if (elen == (size_t)-1) { + logger(ifp->ctx, LOG_ERR, "%s: make_env: %m", ifp->name); + return -1; + } + /* Resize for PATH and RC_SVCNAME */ + svcname = getenv(RC_SVCNAME); + ep = realloc(env, sizeof(char *) * (elen + 2 + (svcname ? 1 : 0))); + 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) + logger(ifp->ctx, LOG_ERR, "%s: %s: %m", __func__, argv[0]); + else if (pid != 0) { + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + logger(ifp->ctx, LOG_ERR, "waitpid: %m"); + status = 0; + break; + } + } + if (WIFEXITED(status)) { + if (WEXITSTATUS(status)) + logger(ifp->ctx, LOG_ERR, + "%s: %s: WEXITSTATUS %d", + __func__, argv[0], WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) + logger(ifp->ctx, LOG_ERR, "%s: %s: %s", + __func__, argv[0], strsignal(WTERMSIG(status))); + } + + /* 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) { + logger(ifp->ctx, LOG_ERR, "%s: arraytostr: %m", + ifp->name); + break; + } + } + if (control_queue(fd, bigenv, elen, 1) == -1) + logger(ifp->ctx, LOG_ERR, + "%s: control_queue: %m", __func__); + else + status = 1; + } + if (!status) + free(bigenv); + +out: + /* Cleanup */ + ep = env; + while (*ep) + free(*ep++); + free(env); + if (elen == 0) + return -1; + return WEXITSTATUS(status); +} diff --git a/script.h b/script.h new file mode 100644 index 0000000..d090c5d --- /dev/null +++ b/script.h @@ -0,0 +1,37 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SCRIPT_H +#define SCRIPT_H + +#include "control.h" + +void if_printoptions(void); +int send_interface(struct fd_list *, const struct interface *); +int script_runreason(const struct interface *, const char *); + +#endif diff --git a/test/GNUmakefile b/test/GNUmakefile new file mode 100644 index 0000000..2e838d5 --- /dev/null +++ b/test/GNUmakefile @@ -0,0 +1,7 @@ +# GNU Make does not automagically include .depend +# Luckily it does read GNUmakefile over Makefile so we can work around it + +include Makefile +ifneq ($(wildcard .depend), ) +include .depend +endif diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..e29a7cd --- /dev/null +++ b/test/Makefile @@ -0,0 +1,35 @@ +TOP?= .. +include ${TOP}/iconfig.mk + +PROG= test +SRCS= test.c +SRCS+= test_hmac_md5.c ../crypt/hmac_md5.c + +CFLAGS?= -O2 +CSTD?= c99 +CFLAGS+= -std=${CSTD} + +CPPFLAGS+= -I../crypt + +T_COMPAT_SRCS= ${COMPAT_SRCS:compat/%=../compat/%} +T_MD5_SRC= ${MD5_SRC:crypt/%=../crypt/%} +OBJS+= ${SRCS:.c=.o} ${T_COMPAT_SRCS:.c=.o} ${T_MD5_SRC:.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} ${T_COMPAT_SRCS} ${T_CRYPT_SRCS} + ${CC} ${CPPFLAGS} -MM ${SRCS} ${T_COMPAT_SRCS} ${T_CRYPT_SRCS} > .depend + +depend: .depend + +${PROG}: ${DEPEND} ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..fd8d5b4 --- /dev/null +++ b/test/test.c @@ -0,0 +1,38 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "test.h" + +int main(void) +{ + int r = 0; + + if (test_hmac_md5()) + r = -1; + + return r; +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..0ca6182 --- /dev/null +++ b/test/test.h @@ -0,0 +1,32 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef TEST_H + +int test_hmac_md5(void); + +#endif diff --git a/test/test_hmac_md5.c b/test/test_hmac_md5.c new file mode 100644 index 0000000..ddb6875 --- /dev/null +++ b/test/test_hmac_md5.c @@ -0,0 +1,173 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdint.h> + +#include "../crypt/crypt.h" +#include "test.h" + +/* RFC2202 MD5 implementation */ + +static void +print_hmac(uint8_t *hmac) +{ + int i; + + printf("digest = 0x"); + for (i = 0; i < 16; i++) + printf("%02x", *hmac++); + printf("\n"); +} + +static void +hmac_md5_test1(void) +{ + uint8_t hmac[16]; + const uint8_t text[] = "Hi There"; + uint8_t key[16]; + int i; + + printf ("HMAC MD5 Test 1:\t\t"); + for (i = 0; i < 16; i++) + key[i] = 0x0b; + hmac_md5(text, 8, key, 16, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x9294727a3638bb1c13f48ef8158bfc9d\n"); +} + +static void +hmac_md5_test2(void) +{ + uint8_t hmac[16]; + const uint8_t text[] = "what do ya want for nothing?"; + const uint8_t key[] = "Jefe"; + + printf("HMAC MD5 Test 2:\t\t"); + hmac_md5(text, 28, key, 4, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x750c783e6ab0b503eaa863e10a5db738\n"); +} + +static void +hmac_md5_test3(void) +{ + uint8_t hmac[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(text, 50, key, 16, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x56be34521d144c88dbb8c733f0e8b3f6\n"); +} + +static void +hmac_md5_test4(void) +{ + uint8_t hmac[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(text, 50, key, 25, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x697eaf0aca3a3aea3a75164746ffaa79\n"); +} + +static void +hmac_md5_test5(void) +{ + uint8_t hmac[16]; + const uint8_t text[] = "Test With Truncation"; + uint8_t key[16]; + int i; + + printf ("HMAC MD5 Test 5:\t\t"); + for (i = 0; i < 16; i++) + key[i] = 0x0c; + hmac_md5(text, 20, key, 16, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x56461ef2342edc00f9bab995690efd4c\n"); +} + +static void +hmac_md5_test6(void) +{ + uint8_t hmac[16]; + const uint8_t text[] = "Test Using Larger Than Block-Size Key - Hash Key First"; + uint8_t key[80]; + int i; + + printf ("HMAC MD5 Test 6:\t\t"); + for (i = 0; i < 80; i++) + key[i] = 0xaa; + hmac_md5(text, 54, key, 80, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd\n"); +} + +static void +hmac_md5_test7(void) +{ + uint8_t hmac[16]; + const uint8_t text[] = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"; + uint8_t key[80]; + int i; + + printf ("HMAC MD5 Test 7:\t\t"); + for (i = 0; i < 80; i++) + key[i] = 0xaa; + hmac_md5(text, 73, key, 80, hmac); + print_hmac(hmac); + printf("\t\texpected result:\t 0x6f630fad67cda0ee1fb1f562db3aa53e\n"); +} + +int test_hmac_md5(void) +{ + + printf ("Starting 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("\nConfirm above results visually against RFC 2202.\n"); + return 0; +} |