From e86eee143ed21592f88a46623a81f71002430459 Mon Sep 17 00:00:00 2001 From: Dmitry Shmidt Date: Mon, 24 Jan 2011 16:27:51 -0800 Subject: dhcpcd: Update to Version 5.2.10 Change-Id: I949331c7aad91b125decd51da4041983d3a352bc Signed-off-by: Dmitry Shmidt --- Android.mk | 13 +- MODULE_LICENSE_BSD_LIKE | 0 Makefile | 134 +- Makefile.inc | 9 + README | 76 +- ThirdPartyProject.prop | 4 +- arp.c | 308 +++++ arp.h | 49 + bind.c | 234 ++++ bind.h | 39 + bpf-filter.h | 2 +- bpf.c | 14 +- common.c | 173 +-- common.h | 63 +- compat/arc4random.c | 158 +++ compat/arc4random.h | 36 + compat/closefrom.c | 42 + compat/closefrom.h | 31 + compat/getline.c | 75 ++ compat/getline.h | 36 + compat/linkaddr.c | 120 ++ compat/strlcpy.c | 51 + compat/strlcpy.h | 34 + config.h | 84 +- config.mk | 20 + configure.c | 796 ++++++++---- configure.h | 15 +- control.c | 208 +++ control.h | 45 + defs.h | 52 + dhcp.c | 381 ++++-- dhcp.h | 65 +- dhcpcd-hooks/02-dump | 5 + dhcpcd-hooks/20-resolv.conf | 101 +- dhcpcd-hooks/29-lookup-hostname | 18 +- dhcpcd-hooks/30-hostname | 26 +- dhcpcd-hooks/50-dhcpcd-compat | 44 +- dhcpcd-hooks/50-ntp.conf | 87 +- dhcpcd-hooks/50-yp.conf | 44 +- dhcpcd-hooks/50-ypbind | 74 ++ dhcpcd-hooks/Makefile | 26 +- dhcpcd-run-hooks.8 | 60 +- dhcpcd-run-hooks.8.in | 47 +- dhcpcd-run-hooks.in | 167 ++- dhcpcd.8 | 343 +++-- dhcpcd.8.in | 305 +++-- dhcpcd.c | 2688 ++++++++++++++++++++++++++------------- dhcpcd.conf | 24 +- dhcpcd.conf.5 | 181 ++- dhcpcd.conf.5.in | 172 ++- dhcpcd.h | 164 ++- duid.c | 100 ++ duid.h | 35 + eloop.c | 366 ++++++ eloop.h | 51 + if-bsd.c | 323 +++-- if-linux-wireless.c | 89 ++ if-linux.c | 349 +++-- if-options.c | 912 +++++++++++++ if-options.h | 123 ++ if-pref.c | 108 ++ if-pref.h | 34 + ifaddrs.c | 146 +++ ifaddrs.h | 34 + ipv4ll.c | 156 +++ ipv4ll.h | 33 + lpf.c | 8 +- net.c | 654 +++++----- net.h | 114 +- platform-bsd.c | 50 + platform-linux.c | 104 ++ platform.h | 33 + showlease.c | 8 +- signals.c | 19 +- signals.h | 3 +- 75 files changed, 9212 insertions(+), 2583 deletions(-) create mode 100644 MODULE_LICENSE_BSD_LIKE create mode 100644 Makefile.inc create mode 100644 arp.c create mode 100644 arp.h create mode 100644 bind.c create mode 100644 bind.h create mode 100644 compat/arc4random.c create mode 100644 compat/arc4random.h create mode 100644 compat/closefrom.c create mode 100644 compat/closefrom.h create mode 100644 compat/getline.c create mode 100644 compat/getline.h create mode 100644 compat/linkaddr.c create mode 100644 compat/strlcpy.c create mode 100644 compat/strlcpy.h create mode 100644 config.mk create mode 100644 control.c create mode 100644 control.h create mode 100644 defs.h create mode 100644 dhcpcd-hooks/02-dump create mode 100644 dhcpcd-hooks/50-ypbind create mode 100644 duid.c create mode 100644 duid.h create mode 100644 eloop.c create mode 100644 eloop.h create mode 100644 if-linux-wireless.c create mode 100644 if-options.c create mode 100644 if-options.h create mode 100644 if-pref.c create mode 100644 if-pref.h create mode 100644 ifaddrs.c create mode 100644 ifaddrs.h create mode 100644 ipv4ll.c create mode 100644 ipv4ll.h create mode 100644 platform-bsd.c create mode 100644 platform-linux.c create mode 100644 platform.h diff --git a/Android.mk b/Android.mk index d9eeae5..3ced2e9 100644 --- a/Android.mk +++ b/Android.mk @@ -7,17 +7,20 @@ hooks_dir := dhcpcd-hooks hooks_target := $(etc_dir)/$(hooks_dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := common.c dhcp.c dhcpcd.c logger.c net.c \ - signals.c configure.c client.c if-linux.c lpf.c -LOCAL_C_INCLUDES := $(KERNEL_HEADERS) -LOCAL_SHARED_LIBRARIES := libc libcutils +LOCAL_SRC_FILES := arp.c bind.c common.c control.c dhcp.c dhcpcd.c duid.c \ + eloop.c if-options.c if-pref.c ipv4ll.c net.c signals.c configure.c \ + if-linux.c if-linux-wireless.c lpf.c compat/getline.c \ + platform-linux.c compat/closefrom.c ifaddrs.c + +#LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +LOCAL_SHARED_LIBRARIES := libc libcutils libnetutils LOCAL_MODULE = dhcpcd LOCAL_MODULE_TAGS := user include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_SRC_FILES := showlease.c -LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +#LOCAL_C_INCLUDES := $(KERNEL_HEADERS) LOCAL_SHARED_LIBRARIES := libc LOCAL_MODULE = showlease LOCAL_MODULE_TAGS := debug diff --git a/MODULE_LICENSE_BSD_LIKE b/MODULE_LICENSE_BSD_LIKE new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index a6b066b..ea6431f 100644 --- a/Makefile +++ b/Makefile @@ -1,51 +1,127 @@ -# Makefile based on BSD make. -# Our mk stubs also work with GNU make. -# Copyright 2008 Roy Marples +# dhcpcd Makefile PROG= dhcpcd -SRCS= common.c dhcp.c dhcpcd.c logger.c net.c signals.c -SRCS+= configure.c client.c -SRCS+= ${SRC_IF} ${SRC_PF} +SRCS= arp.c bind.c common.c control.c dhcp.c dhcpcd.c duid.c eloop.c +SRCS+= if-options.c if-pref.c ipv4ll.c net.c signals.c +SRCS+= configure.c + +CFLAGS?= -O2 +CSTD?= c99 +CFLAGS+= -std=${CSTD} +include config.mk + +OBJS+= ${SRCS:.c=.o} ${COMPAT_SRCS:.c=.o} -LIBEXECDIR?= ${PREFIX}/system/etc/dhcpcd SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks -BINDIR= ${PREFIX}/sbin -DBDIR= /data/misc/dhcp -SYSCONFDIR?= ${PREFIX}/system/etc/dhcpcd - -MAN= dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8 +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 +CLEANFILES+= .depend FILES= dhcpcd.conf FILESDIR= ${SYSCONFDIR} -CPPFLAGS+= -DDBDIR=\"${DBDIR}\" -CPPFLAGS+= -DSCRIPT=\"${SCRIPT}\" -CPPFLAGS+= -DSYSCONFDIR=\"${SYSCONFDIR}\" -LDADD+= ${LIBRT} - SUBDIRS= dhcpcd-hooks -.SUFFIXES: .in .sh.in +SED_DBDIR= -e 's:@DBDIR@:${DBDIR}: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_SH= test -e .depend && echo ".depend" || echo "" +_DEPEND!= ${_DEPEND_SH} +DEPEND= ${_DEPEND}$(shell ${_DEPEND_SH}) + +_VERSION_SH= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' defs.h +_VERSION!= ${_VERSION_SH} +VERSION= ${_VERSION}$(shell ${_VERSION_SH}) -SED_DBDIR= -e 's:@DBDIR@:${DBDIR}:g' -SED_HOOKDIR= -e 's:@HOOKDIR@:${HOOKDIR}:g' -SED_SCRIPT= -e 's:@SCRIPT@:${SCRIPT}:g' -SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' +GITREF?= HEAD +DISTPREFIX?= ${PROG}-${VERSION} +DISTFILE?= ${DISTPREFIX}.tar.bz2 + +CLEANFILES+= *.tar.bz2 + +.PHONY: import import-bsd + +.SUFFIXES: .in .in: - ${SED} ${SED_DBDIR} ${SED_HOOKDIR} ${SED_SCRIPT} ${SED_SYS} $< > $@ + ${SED} ${SED_DBDIR} ${SED_HOOKDIR} ${SED_SCRIPT} ${SED_SYS} \ + ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \ + $< > $@ + +all: config.h ${PROG} ${SCRIPTS} ${MAN5} ${MAN8} + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +.depend: ${SRCS} ${COMPAT_SRCS} + ${CC} ${CPPFLAGS} -MM ${SRCS} ${COMPAT_SRCS} > .depend + +depend: .depend + +${PROG}: ${DEPEND} ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} + +_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} + +_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 _scriptsinstall _maninstall _confinstall + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done + +clean: + rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} + +distclean: clean + rm -f .depend config.h config.mk + +dist: + git archive --prefix=${DISTPREFIX}/ ${GITREF} | bzip2 > ${DISTFILE} -.sh.in.sh: - ${SED} ${SED_HOOKDIR} ${SED_SCRIPT} ${SED_SYS} $< > $@ +import: + rm -rf /tmp/${DISTPREFIX} + ${INSTALL} -d /tmp/${DISTPREFIX} + cp ${SRCS} dhcpcd.conf *.in /tmp/${DISTPREFIX} + cp $$(${CC} ${CPPFLAGS} -MM ${SRCS} | \ + sed -e 's/^.*\.c //g' -e 's/.*\.c$$//g' -e 's/\\//g' | \ + tr ' ' '\n' | \ + sed -e '/^compat\//d' | \ + sort -u) /tmp/${DISTPREFIX} + if test -n "${COMPAT_SRCS}"; then \ + ${INSTALL} -d /tmp/${DISTPREFIX}/compat; \ + cp ${COMPAT_SRCS} /tmp/${DISTPREFIX}/compat; \ + cp $$(${CC} ${CPPFLAGS} -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} $@ -MK= mk -include ${MK}/sys.mk -include ${MK}/os.mk -include ${MK}/prog.mk +include Makefile.inc diff --git a/Makefile.inc b/Makefile.inc new file mode 100644 index 0000000..fd7ead4 --- /dev/null +++ b/Makefile.inc @@ -0,0 +1,9 @@ +# System definitions + +BINMODE?= 0555 +NONBINMODE?= 0444 +MANMODE?= ${NONBINMODE} +CONFMODE?= 0644 + +INSTALL?= install +SED?= sed diff --git a/README b/README index 1dff417..cfbf3cd 100644 --- a/README +++ b/README @@ -1,29 +1,29 @@ -dhcpcd-4 - DHCP client daemon -Copyright 2006-2008 Roy Marples +dhcpcd - DHCP client daemon +Copyright (c) 2006-2010 Roy Marples Installation ------------ -Edit config.h to match your building requirements. -Then just make; make install +./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 to set the below knobs to avoid -automatic tests. -OS=BSD | Linux +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 add -DTHERE_IS_NO_FORK to your CPPFLAGS. +should ./configure --disable-fork. This also puts the --no-background flag on and stops the --background flag from working. -You can change the default dir with these knobs. +You can change the default dirs with these knobs. For example, to satisfy FHS compliance you would do this:- -LIBEXECDIR=/lib/dhcpcd -DBDIR=/var/lib/dhcpcd +./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. @@ -32,39 +32,45 @@ 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 are: + NetBSD-5.0 + +We try and detect how dhcpcd should interact with system services during the +configure stage. If we cannot auto-detect how do to this, or it is wrong then +you can change this by passing shell commands to --service-exists, +--servicecmd and optionally --servicestatus. + +To prepare dhcpcd for import into a platform source tree (like NetBSD) +you can use the make import target to create /tmp/dhcpcd-$version and +populate it with all the source files and hooks needed. +In this instance, you may wish to disable some configured tests when +the binary has to run on older versions which lack support, such as getline. +./configure --without-getline + Hooks ----- Not all the hooks in dhcpcd-hooks are installed by default. -By default we install 01-test, 10-mtu, 20-resolv.conf and 30-hostname. -To add more simply add them in the HOOKSCRIPTS variable. -make HOOKSCRIPTS=50-ntp install +By default we install 01-test, 10-mtu, 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 Compatibility ------------- -If you require compatibility with dhcpcd-3 and older style variables, -you can install 50-dhcpcd-compat into the directory $LIBEXECDIR/dhcpcd-hooks -We don't install this by default. -You should also add -DCMDLINE_COMPAT to your CPPFLAGS if you need to be fully -commandline compatible with prior versions. - -dhcpcd-3 enabled DUID support by default - this has changed in dhcpcd-4. -You can enable it via the --duid, -D command line option or by using the -duid directive in dhcpcd.conf. -If CMDLINE_COMPAT is defined the we renable DUID support by default IF -the dhcpcd.duid file exits. This keeps the clients working as they were, -which is good. - -dhcpcd no longer sends a default ClientID for ethernet interfaces. -This is so we can re-use the address the kernel DHCP client found. -To retain the old behaviour of sending a default ClientID based on the -hardware address for interface, simply add the keyword clientid to dhcpcd.conf. -If CMDLINE_COMPAT is defined, we renable the sending of ClientID by default -AND adding clientid to dhcpcd.conf causes it NOT to be sent. +dhcpcd-5.0 is only fully command line compatible with dhcpcd-4.0 +For compatibility with older versions, use dhcpcd-4.0 -dhcpcd-4 is NOT fully commandline compatible with dhcpcd-2 and older and -changes the meaning of some options. +dhcpcd no longer sends a default ClientID for ethernet interfaces. +This is so we can re-use the address the kernel DHCP client found. +To retain the old behaviour of sending a default ClientID based on the +hardware address for interface, simply add the keyword clientid to dhcpcd.conf. ChangeLog diff --git a/ThirdPartyProject.prop b/ThirdPartyProject.prop index 1874fb9..e8bf489 100644 --- a/ThirdPartyProject.prop +++ b/ThirdPartyProject.prop @@ -1,7 +1,7 @@ # Copyright 2010 Google Inc. All Rights Reserved. #Fri Jul 16 10:03:08 PDT 2010 -currentVersion=5.1.5 -version=4.0.1 +currentVersion=5.2.10 +version=5.2.10 isNative=true feedurl=http\://git.marples.name/projects/dhcpcd/wiki name=dhcpcd diff --git a/arp.c b/arp.c new file mode 100644 index 0000000..89d63fe --- /dev/null +++ b/arp.c @@ -0,0 +1,308 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "arp.h" +#include "bind.h" +#include "common.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if-options.h" +#include "ipv4ll.h" +#include "net.h" + +#define ARP_LEN \ + (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) + +static int +send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip) +{ + uint8_t arp_buffer[ARP_LEN]; + struct arphdr ar; + size_t len; + uint8_t *p; + int retval; + + ar.ar_hrd = htons(iface->family); + ar.ar_pro = htons(ETHERTYPE_IP); + ar.ar_hln = iface->hwlen; + ar.ar_pln = sizeof(sip); + ar.ar_op = htons(op); + memcpy(arp_buffer, &ar, sizeof(ar)); + p = arp_buffer + sizeof(ar); + memcpy(p, iface->hwaddr, iface->hwlen); + p += iface->hwlen; + memcpy(p, &sip, sizeof(sip)); + p += sizeof(sip); + /* ARP requests should ignore this */ + retval = iface->hwlen; + while (retval--) + *p++ = '\0'; + memcpy(p, &tip, sizeof(tip)); + p += sizeof(tip); + len = p - arp_buffer; + retval = send_raw_packet(iface, ETHERTYPE_ARP, arp_buffer, len); + return retval; +} + +static void +handle_arp_failure(struct interface *iface) +{ + + /* If we failed without a magic cookie then we need to try + * and defend our IPv4LL address. */ + if ((iface->state->offer != NULL && + iface->state->offer->cookie != htonl(MAGIC_COOKIE)) || + (iface->state->new != NULL && + iface->state->new->cookie != htonl(MAGIC_COOKIE))) + { + handle_ipv4ll_failure(iface); + return; + } + + unlink(iface->leasefile); + if (!iface->state->lease.frominfo) + send_decline(iface); + close_sockets(iface); + delete_timeout(NULL, iface); + if (iface->state->lease.frominfo) + start_interface(iface); + else + add_timeout_sec(DHCP_ARP_FAIL, start_interface, iface); +} + +static void +handle_arp_packet(void *arg) +{ + struct interface *iface = arg; + uint8_t arp_buffer[ARP_LEN]; + struct arphdr ar; + uint32_t reply_s; + uint32_t reply_t; + uint8_t *hw_s, *hw_t; + ssize_t bytes; + struct if_state *state = iface->state; + struct if_options *opts = state->options; + const char *hwaddr; + struct in_addr ina; + + state->fail.s_addr = 0; + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_ARP, + arp_buffer, sizeof(arp_buffer)); + if (bytes == 0 || bytes == -1) + return; + /* We must have a full ARP header */ + if ((size_t)bytes < sizeof(ar)) + continue; + memcpy(&ar, arp_buffer, sizeof(ar)); + /* Protocol must be IP. */ + if (ar.ar_pro != htons(ETHERTYPE_IP)) + continue; + if (ar.ar_pln != sizeof(reply_s)) + 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 */ + if (ar.ar_hln == iface->hwlen && + memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0) + continue; + /* Copy out the IP addresses */ + memcpy(&reply_s, hw_s + ar.ar_hln, ar.ar_pln); + memcpy(&reply_t, hw_t + ar.ar_hln, ar.ar_pln); + + /* Check for arping */ + if (state->arping_index && + state->arping_index <= opts->arping_len && + (reply_s == opts->arping[state->arping_index - 1] || + (reply_s == 0 && + reply_t == opts->arping[state->arping_index - 1]))) + { + ina.s_addr = reply_s; + hwaddr = hwaddr_ntoa((unsigned char *)hw_s, + (size_t)ar.ar_hln); + syslog(LOG_INFO, + "%s: found %s on hardware address %s", + iface->name, inet_ntoa(ina), hwaddr); + if (select_profile(iface, hwaddr) == -1 && + errno == ENOENT) + select_profile(iface, inet_ntoa(ina)); + close_sockets(iface); + delete_timeout(NULL, iface); + start_interface(iface); + return; + } + + /* Check for conflict */ + if (state->offer && + (reply_s == state->offer->yiaddr || + (reply_s == 0 && reply_t == state->offer->yiaddr))) + state->fail.s_addr = state->offer->yiaddr; + + /* Handle IPv4LL conflicts */ + if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) && + (reply_s == iface->addr.s_addr || + (reply_s == 0 && reply_t == iface->addr.s_addr))) + state->fail.s_addr = iface->addr.s_addr; + + if (state->fail.s_addr) { + syslog(LOG_ERR, "%s: hardware address %s claims %s", + iface->name, + hwaddr_ntoa((unsigned char *)hw_s, + (size_t)ar.ar_hln), + inet_ntoa(state->fail)); + errno = EEXIST; + handle_arp_failure(iface); + return; + } + } +} + +void +send_arp_announce(void *arg) +{ + struct interface *iface = arg; + struct if_state *state = iface->state; + struct timeval tv; + + if (iface->arp_fd == -1) { + open_socket(iface, ETHERTYPE_ARP); + add_event(iface->arp_fd, handle_arp_packet, iface); + } + if (++state->claims < ANNOUNCE_NUM) + syslog(LOG_DEBUG, + "%s: sending ARP announce (%d of %d), " + "next in %d.00 seconds", + iface->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); + else + syslog(LOG_DEBUG, + "%s: sending ARP announce (%d of %d)", + iface->name, state->claims, ANNOUNCE_NUM); + if (send_arp(iface, ARPOP_REQUEST, + state->new->yiaddr, state->new->yiaddr) == -1) + syslog(LOG_ERR, "send_arp: %m"); + if (state->claims < ANNOUNCE_NUM) { + add_timeout_sec(ANNOUNCE_WAIT, send_arp_announce, iface); + return; + } + if (state->new->cookie != htonl(MAGIC_COOKIE)) { + /* We should pretend to be at the end + * of the DHCP negotation cycle unless we rebooted */ + if (state->interval != 0) + state->interval = 64; + state->probes = 0; + state->claims = 0; + tv.tv_sec = state->interval - DHCP_RAND_MIN; + tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&tv); + add_timeout_tv(&tv, start_discover, iface); + } else { + delete_event(iface->arp_fd); + close(iface->arp_fd); + iface->arp_fd = -1; + } +} + +void +send_arp_probe(void *arg) +{ + struct interface *iface = arg; + struct if_state *state = iface->state; + struct in_addr addr; + struct timeval tv; + int arping = 0; + + if (state->arping_index < state->options->arping_len) { + addr.s_addr = state->options->arping[state->arping_index]; + arping = 1; + } else if (state->offer) { + if (state->offer->yiaddr) + addr.s_addr = state->offer->yiaddr; + else + addr.s_addr = state->offer->ciaddr; + } else + addr.s_addr = iface->addr.s_addr; + + if (iface->arp_fd == -1) { + open_socket(iface, ETHERTYPE_ARP); + add_event(iface->arp_fd, handle_arp_packet, iface); + } + if (state->probes == 0) { + if (arping) + syslog(LOG_INFO, "%s: searching for %s", + iface->name, inet_ntoa(addr)); + else + syslog(LOG_INFO, "%s: checking for %s", + iface->name, inet_ntoa(addr)); + } + if (++state->probes < PROBE_NUM) { + tv.tv_sec = PROBE_MIN; + tv.tv_usec = arc4random() % (PROBE_MAX_U - PROBE_MIN_U); + timernorm(&tv); + add_timeout_tv(&tv, send_arp_probe, iface); + } else { + tv.tv_sec = ANNOUNCE_WAIT; + tv.tv_usec = 0; + if (arping) { + state->probes = 0; + if (++state->arping_index < state->options->arping_len) + add_timeout_tv(&tv, send_arp_probe, iface); + else + add_timeout_tv(&tv, start_interface, iface); + } else + add_timeout_tv(&tv, bind_interface, iface); + } + syslog(LOG_DEBUG, + "%s: sending ARP probe (%d of %d), next in %0.2f seconds", + iface->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM, + timeval_to_double(&tv)); + if (send_arp(iface, ARPOP_REQUEST, 0, addr.s_addr) == -1) + syslog(LOG_ERR, "send_arp: %m"); +} + +void +start_arping(struct interface *iface) +{ + iface->state->probes = 0; + iface->state->arping_index = 0; + send_arp_probe(iface); +} diff --git a/arp.h b/arp.h new file mode 100644 index 0000000..b97c38b --- /dev/null +++ b/arp.h @@ -0,0 +1,49 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARP_H +#define ARP_H + +/* These are for IPV4LL, RFC 3927. + * We put them here as we use the timings for all ARP foo. */ +#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" + +void send_arp_announce(void *); +void send_arp_probe(void *); +void start_arping(struct interface *); +#endif diff --git a/bind.c b/bind.c new file mode 100644 index 0000000..eed64a6 --- /dev/null +++ b/bind.c @@ -0,0 +1,234 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#ifdef BSD +# include +#endif +#include +#include +#include +#include + +#include "arp.h" +#include "bind.h" +#include "common.h" +#include "configure.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if-options.h" +#include "net.h" +#include "signals.h" + +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/null" +#endif + +/* We do things after aquiring the lease, so ensure we have enough time for them */ +#define DHCP_MIN_LEASE 20 + +#ifndef THERE_IS_NO_FORK +pid_t +daemonise(void) +{ + pid_t pid; + sigset_t full; + sigset_t old; + char buf = '\0'; + int sidpipe[2], fd; + + if (options & DHCPCD_DAEMONISED || !(options & DHCPCD_DAEMONISE)) + return 0; + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + /* Setup a signal pipe so parent knows when to exit. */ + if (pipe(sidpipe) == -1) { + syslog(LOG_ERR, "pipe: %m"); + return -1; + } + syslog(LOG_DEBUG, "forking to background"); + switch (pid = fork()) { + case -1: + syslog(LOG_ERR, "fork: %m"); + exit(EXIT_FAILURE); + /* NOTREACHED */ + case 0: + setsid(); + /* Notify parent it's safe to exit as we've detached. */ + close(sidpipe[0]); + if (write(sidpipe[1], &buf, 1) == -1) + syslog(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); + if (fd > STDERR_FILENO) + close(fd); + } + break; + default: + signal_reset(); + /* Wait for child to detach */ + close(sidpipe[1]); + if (read(sidpipe[0], &buf, 1) == -1) + syslog(LOG_ERR, "failed to read child: %m"); + close(sidpipe[0]); + break; + } + /* Done with the fd now */ + if (pid != 0) { + syslog(LOG_INFO, "forked to background, child pid %d",pid); + writepid(pidfd, pid); + close(pidfd); + pidfd = -1; + exit(EXIT_SUCCESS); + } + options |= DHCPCD_DAEMONISED; + sigprocmask(SIG_SETMASK, &old, NULL); + return pid; +} +#endif + +void +bind_interface(void *arg) +{ + struct interface *iface = arg; + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_lease *lease = &state->lease; + struct timeval tv; + + /* We're binding an address now - ensure that sockets are closed */ + close_sockets(iface); + state->reason = NULL; + delete_timeout(handle_exit_timeout, NULL); + if (clock_monotonic) + get_monotonic(&lease->boundtime); + state->xid = 0; + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + get_lease(lease, state->new); + if (ifo->options & DHCPCD_STATIC) { + syslog(LOG_INFO, "%s: using static address %s", + iface->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + lease->net.s_addr = ifo->req_mask.s_addr; + state->reason = "STATIC"; + } else if (state->new->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_INFO, "%s: using IPv4LL address %s", + iface->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "IPV4LL"; + } 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 = iface->addr.s_addr; + syslog(LOG_INFO, "%s: received approval for %s", iface->name, + inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "INFORM"; + } else { + if (gettimeofday(&tv, NULL) == 0) + lease->leasedfrom = tv.tv_sec; + else if (lease->frominfo) + state->reason = "TIMEOUT"; + if (lease->leasetime == ~0U) { + lease->renewaltime = + lease->rebindtime = + lease->leasetime; + syslog(LOG_INFO, "%s: leased %s for infinity", + iface->name, inet_ntoa(lease->addr)); + } else { + if (lease->leasetime < DHCP_MIN_LEASE) { + syslog(LOG_WARNING, + "%s: minimum lease is %d seconds", + iface->name, DHCP_MIN_LEASE); + lease->leasetime = DHCP_MIN_LEASE; + } + if (lease->rebindtime == 0) + lease->rebindtime = lease->leasetime * T2; + else if (lease->rebindtime >= lease->leasetime) { + lease->rebindtime = lease->leasetime * T2; + syslog(LOG_ERR, + "%s: rebind time greater than lease " + "time, forcing to %u seconds", + iface->name, lease->rebindtime); + } + if (lease->renewaltime == 0) + lease->renewaltime = lease->leasetime * T1; + else if (lease->renewaltime > lease->rebindtime) { + lease->renewaltime = lease->leasetime * T1; + syslog(LOG_ERR, + "%s: renewal time greater than rebind " + "time, forcing to %u seconds", + iface->name, lease->renewaltime); + } + syslog(LOG_INFO, + "%s: leased %s for %u seconds", iface->name, + inet_ntoa(lease->addr), lease->leasetime); + } + } + if (options & DHCPCD_TEST) { + state->reason = "TEST"; + run_script(iface); + exit(EXIT_SUCCESS); + } + if (state->reason == NULL) { + if (state->old) { + 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 { + add_timeout_sec(lease->renewaltime, start_renew, iface); + add_timeout_sec(lease->rebindtime, start_rebind, iface); + add_timeout_sec(lease->leasetime, start_expire, iface); + } + ifo->options &= ~ DHCPCD_CSR_WARNED; + configure(iface); + daemonise(); + state->state = DHS_BOUND; + if (ifo->options & DHCPCD_ARP) { + state->claims = 0; + send_arp_announce(iface); + } +} diff --git a/bind.h b/bind.h new file mode 100644 index 0000000..375a0f3 --- /dev/null +++ b/bind.h @@ -0,0 +1,39 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef BIND_H +#define BIND_H + +#include "config.h" +#ifdef THERE_IS_NO_FORK +# define daemonise() {} +#else +pid_t daemonise(void); +#endif + +void bind_interface(void *); +#endif diff --git a/bpf-filter.h b/bpf-filter.h index 881f678..b68ee49 100644 --- a/bpf-filter.h +++ b/bpf-filter.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2008 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions diff --git a/bpf.c b/bpf.c index 96e53a1..beda1ba 100644 --- a/bpf.c +++ b/bpf.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2008 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -39,12 +39,12 @@ #include #include #include +#include #include #include "config.h" #include "common.h" #include "dhcp.h" -#include "logger.h" #include "net.h" #include "bpf-filter.h" @@ -81,7 +81,7 @@ open_socket(struct interface *iface, int protocol) goto eexit; if (pv.bv_major != BPF_MAJOR_VERSION || pv.bv_minor < BPF_MINOR_VERSION) { - logger(LOG_ERR, "BPF version mismatch - recompile " PACKAGE); + syslog(LOG_ERR, "BPF version mismatch - recompile"); goto eexit; } @@ -136,7 +136,7 @@ eexit: ssize_t send_raw_packet(const struct interface *iface, int protocol, - const void *data, ssize_t len) + const void *data, ssize_t len) { struct iovec iov[2]; struct ether_header hw; @@ -160,7 +160,7 @@ send_raw_packet(const struct interface *iface, int protocol, * So we pass the buffer in the API so we can loop on >1 packet. */ ssize_t get_raw_packet(struct interface *iface, int protocol, - void *data, ssize_t len) + void *data, ssize_t len) { int fd = -1; struct bpf_hdr packet; @@ -184,7 +184,7 @@ get_raw_packet(struct interface *iface, int protocol, } bytes = -1; memcpy(&packet, iface->buffer + iface->buffer_pos, - sizeof(packet)); + sizeof(packet)); if (packet.bh_caplen != packet.bh_datalen) goto next; /* Incomplete packet, drop. */ if (iface->buffer_pos + packet.bh_caplen + packet.bh_hdrlen > @@ -197,7 +197,7 @@ get_raw_packet(struct interface *iface, int protocol, memcpy(data, payload, bytes); next: iface->buffer_pos += BPF_WORDALIGN(packet.bh_hdrlen + - packet.bh_caplen); + packet.bh_caplen); if (iface->buffer_pos >= iface->buffer_len) iface->buffer_len = iface->buffer_pos = 0; if (bytes != -1) diff --git a/common.c b/common.c index da22a5c..0642055 100644 --- a/common.c +++ b/common.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2009 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,6 +25,13 @@ * SUCH DAMAGE. */ +/* Needed define to get at getline for glibc and FreeBSD */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include + #ifdef __APPLE__ # include # include @@ -35,6 +42,7 @@ #include #include +#include #ifdef BSD # include #endif @@ -42,119 +50,60 @@ #include #include #include +#include #include #include #include "common.h" -#include "logger.h" #ifndef _PATH_DEVNULL # define _PATH_DEVNULL "/dev/null" #endif -int clock_monotonic = 0; - -/* Handy routine to read very long lines in text files. - * This means we read the whole line and avoid any nasty buffer overflows. */ -ssize_t -get_line(char **line, size_t *len, FILE *fp) -{ - char *p; - size_t last = 0; - - while(!feof(fp)) { - if (*line == NULL || last != 0) { - *len += BUFSIZ; - *line = xrealloc(*line, *len); - } - p = *line + last; - memset(p, 0, BUFSIZ); - if (fgets(p, BUFSIZ, fp) == NULL) - break; - last += strlen(p); - if (last && (*line)[last - 1] == '\n') { - (*line)[last - 1] = '\0'; - break; - } - } - return last; -} - -/* Simple hack to return a random number without arc4random */ -#ifndef HAVE_ARC4RANDOM -uint32_t arc4random(void) -{ - int fd; - static unsigned long seed = 0; - - if (!seed) { - fd = open("/dev/urandom", 0); - if (fd == -1 || read(fd, &seed, sizeof(seed)) == -1) - seed = time(0); - if (fd >= 0) - close(fd); - srandom(seed); - } - - return (uint32_t)random(); -} +int clock_monotonic; +static char *lbuf; +static size_t lbuf_len; +#ifdef DEBUG_MEMORY +static char lbuf_set; #endif -/* strlcpy is nice, shame glibc does not define it */ -#if HAVE_STRLCPY -#else -size_t -strlcpy(char *dst, const char *src, size_t size) +#ifdef DEBUG_MEMORY +static void +free_lbuf(void) { - 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 src - s - 1; + free(lbuf); + lbuf = NULL; } #endif -#if HAVE_CLOSEFROM -#else -int -closefrom(int fd) +/* 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. + * As we don't use threads, this API is clean too. */ +char * +get_line(FILE * __restrict fp) { - int max = getdtablesize(); - int i; - int r = 0; + char *p; + ssize_t bytes; - for (i = fd; i < max; i++) - r += close(i); - return r; -} +#ifdef DEBUG_MEMORY + if (lbuf_set == 0) { + atexit(free_lbuf); + lbuf_set = 1; + } #endif -/* Close our fd's */ -int -close_fds(void) -{ - int fd; - - if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) - return -1; - - dup2(fd, fileno(stdin)); - dup2(fd, fileno(stdout)); - dup2(fd, fileno(stderr)); - if (fd > 2) - close(fd); - return 0; + do { + bytes = getline(&lbuf, &lbuf_len, fp); + if (bytes == -1) + return NULL; + for (p = lbuf; *p == ' ' || *p == '\t'; p++) + ; + } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';'); + if (lbuf[--bytes] == '\n') + lbuf[bytes] = '\0'; + return p; } int @@ -162,10 +111,10 @@ set_cloexec(int fd) { int flags; - if ((flags = fcntl(fd, F_GETFD, 0)) == -1 - || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + if ((flags = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { - logger(LOG_ERR, "fcntl: %s", strerror(errno)); + syslog(LOG_ERR, "fcntl: %m"); return -1; } return 0; @@ -176,10 +125,10 @@ set_nonblock(int fd) { int flags; - if ((flags = fcntl(fd, F_GETFL, 0)) == -1 - || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + if ((flags = fcntl(fd, F_GETFL, 0)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { - logger(LOG_ERR, "fcntl: %s", strerror(errno)); + syslog(LOG_ERR, "fcntl: %m"); return -1; } return 0; @@ -199,7 +148,7 @@ get_monotonic(struct timeval *tp) struct timespec ts; static clockid_t posix_clock; - if (posix_clock_set == 0) { + if (!posix_clock_set) { if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { posix_clock = CLOCK_MONOTONIC; clock_monotonic = posix_clock_set = 1; @@ -222,7 +171,7 @@ get_monotonic(struct timeval *tp) uint64_t nano; long rem; - if (posix_clock_set == 0) { + if (!posix_clock_set) { if (mach_timebase_info(&info) == KERN_SUCCESS) { factor = (double)info.numer / (double)info.denom; clock_monotonic = posix_clock_set = 1; @@ -245,7 +194,7 @@ get_monotonic(struct timeval *tp) /* Something above failed, so fall back to gettimeofday */ if (!posix_clock_set) { - logger(LOG_WARNING, NO_MONOTONIC); + syslog(LOG_WARNING, NO_MONOTONIC); posix_clock_set = 1; } return gettimeofday(tp, NULL); @@ -281,9 +230,9 @@ xmalloc(size_t s) { void *value = malloc(s); - if (value) + if (value != NULL) return value; - logger(LOG_ERR, "memory exhausted"); + syslog(LOG_ERR, "memory exhausted (xalloc %zu bytes)", s); exit (EXIT_FAILURE); /* NOTREACHED */ } @@ -302,9 +251,9 @@ xrealloc(void *ptr, size_t s) { void *value = realloc(ptr, s); - if (value) - return (value); - logger(LOG_ERR, "memory exhausted"); + if (value != NULL) + return value; + syslog(LOG_ERR, "memory exhausted (xrealloc %zu bytes)", s); exit(EXIT_FAILURE); /* NOTREACHED */ } @@ -314,13 +263,13 @@ xstrdup(const char *str) { char *value; - if (!str) + if (str == NULL) return NULL; - if ((value = strdup(str))) + if ((value = strdup(str)) != NULL) return value; - logger(LOG_ERR, "memory exhausted"); + syslog(LOG_ERR, "memory exhausted (xstrdup)"); exit(EXIT_FAILURE); /* NOTREACHED */ } diff --git a/common.h b/common.h index 2522663..fbbfc18 100644 --- a/common.h +++ b/common.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2009 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -28,53 +28,48 @@ #ifndef COMMON_H #define COMMON_H -/* string.h pulls in features.h so the below define checks work */ -#include -#include -#include #include -#include +#include + +#include "config.h" +#include "defs.h" #define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#define timeval_to_double(tv) ((tv)->tv_sec * 1.0 + (tv)->tv_usec * 1.0e-6) +#define timernorm(tvp) \ + do { \ + while ((tvp)->tv_usec >= 1000000) { \ + (tvp)->tv_sec++; \ + (tvp)->tv_usec -= 1000000; \ + } \ + } while (0 /* CONSTCOND */); + #if __GNUC__ > 2 || defined(__INTEL_COMPILER) -# define _unused __attribute__((__unused__)) +# define _noreturn __attribute__((__noreturn__)) +# define _packed __attribute__((__packed__)) +# define _unused __attribute__((__unused__)) #else +# define _noreturn +# define _packed # define _unused #endif -#ifndef HAVE_ARC4RANDOM -# ifdef __GLIBC__ -uint32_t arc4random(void); -#else -# define HAVE_ARC4RANDOM -# endif -#endif - -#ifndef HAVE_STRLCPY -# define HAVE_STRLCPY 1 -#endif -/* Only GLIBC doesn't support strlcpy */ -#ifdef __GLIBC__ -# if !defined(__UCLIBC__) && !defined (__dietlibc__) -# undef HAVE_STRLCPY -size_t strlcpy(char *, const char *, size_t); -# endif -#endif - -#ifndef HAVE_CLOSEFROM -# if defined(__NetBSD__) || defined(__OpenBSD__) -# define HAVE_CLOSEFROM 1 +/* 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 -#ifndef HAVE_CLOSEFROM -int closefrom(int); -#endif -int close_fds(void); int set_cloexec(int); int set_nonblock(int); -ssize_t get_line(char **, size_t *, FILE *); +char *get_line(FILE * __restrict); extern int clock_monotonic; int get_monotonic(struct timeval *); time_t uptime(void); diff --git a/compat/arc4random.c b/compat/arc4random.c new file mode 100644 index 0000000..48ef29d --- /dev/null +++ b/compat/arc4random.c @@ -0,0 +1,158 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres . + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project by leaving this copyright notice intact. + */ + +/* + * This code is derived from section 17.1 of Applied Cryptography, + * second edition, which describes a stream cipher allegedly + * compatible with RSA Labs "RC4" cipher (the actual description of + * which is a trade secret). The same algorithm is used as a stream + * cipher called "arcfour" in Tatu Ylonen's ssh package. + * + * Here the stream cipher has been modified always to include the time + * when initializing the state. That makes it impossible to + * regenerate the same random sequence twice, so this can't be used + * for encryption, but will generate good random numbers. + * + * RC4 is a registered trademark of RSA Laboratories. + */ + +#include + +#include +#include +#include +#include + +#include "arc4random.h" + +struct arc4_stream { + uint8_t i; + uint8_t j; + uint8_t s[256]; +}; + +static int rs_initialized; +static struct arc4_stream rs; +static int arc4_count; + +static void +arc4_init(struct arc4_stream *as) +{ + int n; + + for (n = 0; n < 256; n++) + as->s[n] = n; + as->i = 0; + as->j = 0; +} + +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 = (as->i + 1); + si = as->s[as->i]; + as->j = (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 = (as->i + 1); + si = as->s[as->i]; + as->j = (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) +{ + uint32_t val; + + val = arc4_getbyte(as) << 24; + val |= arc4_getbyte(as) << 16; + val |= arc4_getbyte(as) << 8; + val |= arc4_getbyte(as); + return 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; + int n; + + gettimeofday(&rdat.tv, NULL); + fd = open("/dev/urandom", O_RDONLY); + if (fd != -1) { + n = 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 * 4; n++) + arc4_getbyte(as); + arc4_count = 1600000; +} + +void +arc4random_stir() +{ + + if (!rs_initialized) { + arc4_init(&rs); + rs_initialized = 1; + } + arc4_stir(&rs); +} + +void +arc4random_addrandom(unsigned char *dat, int datlen) +{ + + if (!rs_initialized) + arc4random_stir(); + arc4_addrandom(&rs, dat, datlen); +} + +uint32_t +arc4random() +{ + + arc4_count -= 4; + if (!rs_initialized || arc4_count <= 0) + arc4random_stir(); + return arc4_getword(&rs); +} diff --git a/compat/arc4random.h b/compat/arc4random.h new file mode 100644 index 0000000..2b10902 --- /dev/null +++ b/compat/arc4random.h @@ -0,0 +1,36 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARC4RANDOM_H +#define ARC4RANDOM_H + +#include + +void arc4random_stir(void); +void arc4random_addrandom(unsigned char *, int); +uint32_t arc4random(void); +#endif diff --git a/compat/closefrom.c b/compat/closefrom.c new file mode 100644 index 0000000..664c4bc --- /dev/null +++ b/compat/closefrom.c @@ -0,0 +1,42 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "closefrom.h" + +int +closefrom(int fd) +{ + int max = getdtablesize(); + int i; + int r = 0; + + for (i = fd; i < max; i++) + r += close(i); + return r; +} diff --git a/compat/closefrom.h b/compat/closefrom.h new file mode 100644 index 0000000..b028507 --- /dev/null +++ b/compat/closefrom.h @@ -0,0 +1,31 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CLOSEFROM_H +#define CLOSEFROM_H +int closefrom(int); +#endif diff --git a/compat/getline.c b/compat/getline.c new file mode 100644 index 0000000..3f01b66 --- /dev/null +++ b/compat/getline.c @@ -0,0 +1,75 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "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) { + 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..390632c --- /dev/null +++ b/compat/getline.h @@ -0,0 +1,36 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef GETLINE_H +#define GETLINE_H + +#include +#include + +ssize_t getline(char ** __restrict buf, size_t * __restrict buflen, + FILE * __restrict fp); +#endif diff --git a/compat/linkaddr.c b/compat/linkaddr.c new file mode 100644 index 0000000..c4e6fa5 --- /dev/null +++ b/compat/linkaddr.c @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)linkaddr.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include +#include + +#include + +/* States*/ +#define NAMING 0 +#define GOTONE 1 +#define GOTTWO 2 +#define RESET 3 +/* Inputs */ +#define DIGIT (4*0) +#define END (4*1) +#define DELIM (4*2) +#define LETTER (4*3) + +void +link_addr(addr, sdl) + const char *addr; + struct sockaddr_dl *sdl; +{ + char *cp = sdl->sdl_data; + char *cplim = sdl->sdl_len + (char *)(void *)sdl; + int byte = 0, state = NAMING; + int newaddr = 0; + + (void)memset(&sdl->sdl_family, 0, (size_t)sdl->sdl_len - 1); + sdl->sdl_family = AF_LINK; + do { + state &= ~LETTER; + if ((*addr >= '0') && (*addr <= '9')) { + newaddr = *addr - '0'; + } else if ((*addr >= 'a') && (*addr <= 'f')) { + newaddr = *addr - 'a' + 10; + } else if ((*addr >= 'A') && (*addr <= 'F')) { + newaddr = *addr - 'A' + 10; + } else if (*addr == 0) { + state |= END; + } else if (state == NAMING && + (((*addr >= 'A') && (*addr <= 'Z')) || + ((*addr >= 'a') && (*addr <= 'z')))) + state |= LETTER; + else + state |= DELIM; + addr++; + switch (state /* | INPUT */) { + case NAMING | DIGIT: + case NAMING | LETTER: + *cp++ = addr[-1]; + continue; + case NAMING | DELIM: + state = RESET; + sdl->sdl_nlen = cp - sdl->sdl_data; + continue; + case GOTTWO | DIGIT: + *cp++ = byte; + /* FALLTHROUGH */ + case RESET | DIGIT: + state = GOTONE; + byte = newaddr; + continue; + case GOTONE | DIGIT: + state = GOTTWO; + byte = newaddr + (byte << 4); + continue; + default: /* | DELIM */ + state = RESET; + *cp++ = byte; + byte = 0; + continue; + case GOTONE | END: + case GOTTWO | END: + *cp++ = byte; + /* FALLTHROUGH */ + case RESET | END: + break; + } + break; + } while (cp < cplim); + sdl->sdl_alen = cp - LLADDR(sdl); + newaddr = cp - (char *)(void *)sdl; + if ((size_t) newaddr > sizeof(*sdl)) + sdl->sdl_len = newaddr; + return; +} diff --git a/compat/strlcpy.c b/compat/strlcpy.c new file mode 100644 index 0000000..e44d19c --- /dev/null +++ b/compat/strlcpy.c @@ -0,0 +1,51 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "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 src - s - 1; +} diff --git a/compat/strlcpy.h b/compat/strlcpy.h new file mode 100644 index 0000000..0ff3854 --- /dev/null +++ b/compat/strlcpy.h @@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef STRLCPY_H +#define STRLCPY_H + +#include + +size_t strlcpy(char *, const char *, size_t); +#endif diff --git a/config.h b/config.h index 595fb94..47c795b 100644 --- a/config.h +++ b/config.h @@ -1,75 +1,17 @@ -/* - * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ +/* linux */ +#define SYSCONFDIR "/system/etc/dhcpcd" +#define SBINDIR "/system/etc/dhcpcd" +#define LIBEXECDIR "/system/etc/dhcpcd" +#define DBDIR "/data/misc/dhcp" +#define RUNDIR "/data/misc/dhcp" +#include "compat/arc4random.h" +#include "compat/closefrom.h" +#include "compat/strlcpy.h" -#ifndef CONFIG_H -#define CONFIG_H - -#define PACKAGE "dhcpcd" -#define VERSION "4.0.15" - -/* - * By default we don't add a local link route if we got a routeable address. - * This is because dhcpcd can't really decide which interface should allow - * link local routing when we have more than one interface. - * Ideally the host network scripts should add the link local route for us. - * If not, you can define this to get dhcpcd to always add the link local route. - */ -// #define IPV4LL_ALWAYSROUTE - -/* Some systems do not have a working fork. */ -/* #define THERE_IS_NO_FORK */ - -/* Paths to things */ -#ifndef SYSCONFDIR -# define SYSCONFDIR "/system/etc/dhcpcd" -#endif -#ifndef LIBEXECDIR -# define LIBEXECDIR "/system/etc/dhcpcd" -#endif -#ifndef RUNDIR -# define RUNDIR "/data/misc/dhcp" -#endif -#ifndef DBDIR -# define DBDIR "/data/misc/dhcp" -#endif - -#ifndef CONFIG -# define CONFIG SYSCONFDIR "/" PACKAGE ".conf" -#endif -#ifndef SCRIPT -# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks" -#endif -#ifndef DUID -# define DUID SYSCONFDIR "/" PACKAGE ".duid" -#endif -#ifndef LEASEFILE -# define LEASEFILE DBDIR "/" PACKAGE "-%s.lease" -#endif -#ifndef PIDFILE -# define PIDFILE RUNDIR "/" PACKAGE "-%s.pid" +#ifndef MAX +#define MAX(a,b) ((a) >= (b) ? (a) : (b)) #endif +#ifndef MIN +#define MIN(a,b) ((a) <= (b) ? (a) : (b)) #endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..c7a9eca --- /dev/null +++ b/config.mk @@ -0,0 +1,20 @@ +# linux +SYSCONFDIR= /etc +SBINDIR= /sbin +LIBEXECDIR= /libexec +DBDIR= /var/db +RUNDIR= /var/run +LIBDIR= /lib +MANDIR= /usr/share/man +CC= gcc +CPPFLAGS+= -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +SRCS+= if-linux.c if-linux-wireless.c lpf.c +SRCS+= platform-linux.c +LDADD+= -lrt +COMPAT_SRCS+= compat/arc4random.c +COMPAT_SRCS+= compat/closefrom.c +COMPAT_SRCS+= compat/strlcpy.c +SERVICEEXISTS= /usr/sbin/invoke-rc.d --query --quiet $$1 start >/dev/null 2>\&1 || [ $$? = 104 ] +SERVICECMD= /usr/sbin/invoke-rc.d $$1 $$2 +SERVICESTATUS= service_command $$1 status >/dev/null 2>\&1 +HOOKSCRIPTS= 50-ntp.conf diff --git a/configure.c b/configure.c index 1e6daeb..fb28669 100644 --- a/configure.c +++ b/configure.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,6 +26,7 @@ */ #include +#include #include #include @@ -35,19 +36,33 @@ #include #include #include +#include +#include #include #include "config.h" #include "common.h" #include "configure.h" #include "dhcp.h" -#include "dhcpcd.h" -#include "logger.h" +#include "if-options.h" +#include "if-pref.h" #include "net.h" #include "signals.h" #define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" +/* Some systems have route metrics */ +#ifndef HAVE_ROUTE_METRIC +# ifdef __linux__ +# define HAVE_ROUTE_METRIC 1 +# endif +# ifndef HAVE_ROUTE_METRIC +# define HAVE_ROUTE_METRIC 0 +# endif +#endif + +static struct rt *routes; + static int exec_script(char *const *argv, char *const *env) @@ -63,12 +78,12 @@ exec_script(char *const *argv, char *const *env) switch (pid = vfork()) { case -1: - logger(LOG_ERR, "vfork: %s", strerror(errno)); + syslog(LOG_ERR, "vfork: %m"); break; case 0: sigprocmask(SIG_SETMASK, &old, NULL); execve(argv[0], argv, env); - logger(LOG_ERR, "%s: %s", argv[0], strerror(errno)); + syslog(LOG_ERR, "%s: %m", argv[0]); _exit(127); /* NOTREACHED */ } @@ -79,70 +94,231 @@ exec_script(char *const *argv, char *const *env) return pid; } -int -run_script(const struct options *options, const char *iface, - const char *reason, - const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) +static char * +make_var(const char *prefix, const char *var) { - char *const argv[2] = { UNCONST(options->script), NULL }; - char **env = NULL, **ep; - char *path; - ssize_t e, elen; - pid_t pid; - int status = 0; + size_t len; + char *v; + + len = strlen(prefix) + strlen(var) + 2; + v = xmalloc(len); + snprintf(v, len, "%s_%s", prefix, var); + return v; +} + - logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason); +static void +append_config(char ***env, ssize_t *len, + const char *prefix, const char *const *config) +{ + ssize_t i, j, e1; + char **ne, *eq; + + if (config == NULL) + return; + + ne = *env; + for (i = 0; config[i] != NULL; i++) { + eq = strchr(config[i], '='); + e1 = eq - config[i] + 1; + for (j = 0; j < *len; j++) { + if (strncmp(ne[j] + strlen(prefix) + 1, + config[i], e1) == 0) + { + free(ne[j]); + ne[j] = make_var(prefix, config[i]); + break; + } + } + if (j == *len) { + j++; + ne = xrealloc(ne, sizeof(char *) * (j + 1)); + ne[j - 1] = make_var(prefix, config[i]); + *len = j; + } + } + *env = ne; +} + +static size_t +arraytostr(const char *const *argv, char **s) +{ + const char *const *ap; + char *p; + size_t len, l; + + len = 0; + ap = argv; + while (*ap) + len += strlen(*ap++) + 1; + *s = p = xmalloc(len); + ap = argv; + while (*ap) { + l = strlen(*ap) + 1; + memcpy(p, *ap, l); + p += l; + ap++; + } + return len; +} + +static ssize_t +make_env(const struct interface *iface, char ***argv) +{ + char **env, *p; + ssize_t e, elen, l; + const struct if_options *ifo = iface->state->options; + const struct interface *ifp; /* Make our env */ - elen = 5; + elen = 8; env = xmalloc(sizeof(char *) * (elen + 1)); - path = getenv("PATH"); - if (path) { - e = strlen("PATH") + strlen(path) + 2; - env[0] = xmalloc(e); - snprintf(env[0], e, "PATH=%s", path); - } else - env[0] = xstrdup(DEFAULT_PATH); - e = strlen("interface") + strlen(iface) + 2; + e = strlen("interface") + strlen(iface->name) + 2; + env[0] = xmalloc(e); + snprintf(env[0], e, "interface=%s", iface->name); + e = strlen("reason") + strlen(iface->state->reason) + 2; env[1] = xmalloc(e); - snprintf(env[1], e, "interface=%s", iface); - e = strlen("reason") + strlen(reason) + 2; - env[2] = xmalloc(e); - snprintf(env[2], e, "reason=%s", reason); + snprintf(env[1], e, "reason=%s", iface->state->reason); e = 20; + env[2] = xmalloc(e); + snprintf(env[2], e, "pid=%d", getpid()); env[3] = xmalloc(e); - snprintf(env[3], e, "pid=%d", getpid()); + snprintf(env[3], e, "ifmetric=%d", iface->metric); env[4] = xmalloc(e); - snprintf(env[4], e, "metric=%d", options->metric); - if (dhcpo) { - e = configure_env(NULL, NULL, dhcpo, options); + snprintf(env[4], e, "ifwireless=%d", iface->wireless); + env[5] = xmalloc(e); + snprintf(env[5], e, "ifflags=%u", iface->flags); + env[6] = xmalloc(e); + snprintf(env[6], e, "ifmtu=%d", get_mtu(iface->name)); + l = e = strlen("interface_order="); + for (ifp = ifaces; ifp; ifp = ifp->next) + e += strlen(ifp->name) + 1; + p = env[7] = xmalloc(e); + strlcpy(p, "interface_order=", e); + e -= l; + p += l; + for (ifp = ifaces; ifp; ifp = ifp->next) { + l = strlcpy(p, ifp->name, e); + p += l; + e -= l; + *p++ = ' '; + e--; + } + *--p = '\0'; + if (*iface->state->profile) { + e = strlen("profile=") + strlen(iface->state->profile) + 2; + env[elen] = xmalloc(e); + snprintf(env[elen++], e, "profile=%s", iface->state->profile); + } + if (iface->wireless) { + e = strlen("new_ssid=") + strlen(iface->ssid) + 2; + if (iface->state->new != NULL || + strcmp(iface->state->reason, "CARRIER") == 0) + { + env = xrealloc(env, sizeof(char *) * (elen + 2)); + env[elen] = xmalloc(e); + snprintf(env[elen++], e, "new_ssid=%s", iface->ssid); + } + if (iface->state->old != NULL || + strcmp(iface->state->reason, "NOCARRIER") == 0) + { + env = xrealloc(env, sizeof(char *) * (elen + 2)); + env[elen] = xmalloc(e); + snprintf(env[elen++], e, "old_ssid=%s", iface->ssid); + } + } + if (iface->state->old) { + e = configure_env(NULL, NULL, iface->state->old, ifo); if (e > 0) { env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += configure_env(env + elen, "old", dhcpo, options); + elen += configure_env(env + elen, "old", + iface->state->old, ifo); } + append_config(&env, &elen, "old", + (const char *const *)ifo->config); } - if (dhcpn) { - e = configure_env(NULL, NULL, dhcpn, options); + if (iface->state->new) { + e = configure_env(NULL, NULL, iface->state->new, ifo); if (e > 0) { env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += configure_env(env + elen, "new", dhcpn, options); + elen += configure_env(env + elen, "new", + iface->state->new, ifo); } + append_config(&env, &elen, "new", + (const char *const *)ifo->config); } + /* Add our base environment */ - if (options->environ) { + if (ifo->environ) { e = 0; - while (options->environ[e++]) + while (ifo->environ[e++]) ; env = xrealloc(env, sizeof(char *) * (elen + e + 1)); e = 0; - while (options->environ[e]) { - env[elen + e] = xstrdup(options->environ[e]); + while (ifo->environ[e]) { + env[elen + e] = xstrdup(ifo->environ[e]); e++; } elen += e; } env[elen] = '\0'; + *argv = env; + return elen; +} + +int +send_interface(int fd, const struct interface *iface) +{ + char **env, **ep, *s; + ssize_t elen; + struct iovec iov[2]; + int retval; + + retval = 0; + make_env(iface, &env); + elen = arraytostr((const char *const *)env, &s); + iov[0].iov_base = &elen; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = s; + iov[1].iov_len = elen; + retval = writev(fd, iov, 2); + ep = env; + while (*ep) + free(*ep++); + free(env); + free(s); + return retval; +} + +int +run_script(const struct interface *iface) +{ + char *const argv[2] = { UNCONST(iface->state->options->script), NULL }; + char **env = NULL, **ep; + char *path, *bigenv; + ssize_t e, elen = 0; + pid_t pid; + int status = 0; + const struct fd_list *fd; + struct iovec iov[2]; + + syslog(LOG_DEBUG, "%s: executing `%s', reason %s", + iface->name, argv[0], iface->state->reason); + + /* Make our env */ + elen = make_env(iface, &env); + env = xrealloc(env, sizeof(char *) * (elen + 2)); + /* Add path to it */ + path = getenv("PATH"); + if (path) { + e = strlen("PATH") + strlen(path) + 2; + env[elen] = xmalloc(e); + snprintf(env[elen], e, "PATH=%s", path); + } else + env[elen] = xstrdup(DEFAULT_PATH); + env[++elen] = '\0'; + pid = exec_script(argv, env); if (pid == -1) status = -1; @@ -150,13 +326,31 @@ run_script(const struct options *options, const char *iface, /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { - logger(LOG_ERR, "waitpid: %s", strerror(errno)); + syslog(LOG_ERR, "waitpid: %m"); status = -1; break; } } } + /* Send to our listeners */ + bigenv = NULL; + for (fd = fds; fd != NULL; fd = fd->next) { + if (fd->listener) { + if (bigenv == NULL) { + elen = arraytostr((const char *const *)env, + &bigenv); + iov[0].iov_base = &elen; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = bigenv; + iov[1].iov_len = elen; + } + if (writev(fd->fd, iov, 2) == -1) + syslog(LOG_ERR, "writev: %m"); + } + } + free(bigenv); + /* Cleanup */ ep = env; while (*ep) @@ -166,251 +360,419 @@ run_script(const struct options *options, const char *iface, } static struct rt * -reverse_routes(struct rt *routes) +find_route(struct rt *rts, const struct rt *r, struct rt **lrt, + const struct rt *srt) { struct rt *rt; - struct rt *rtn = NULL; - - while (routes) { - rt = routes->next; - routes->next = rtn; - rtn = routes; - routes = rt; + + if (lrt) + *lrt = NULL; + for (rt = rts; rt; rt = rt->next) { + if (rt->dest.s_addr == r->dest.s_addr && +#if HAVE_ROUTE_METRIC + (srt || (!rt->iface || + rt->iface->metric == r->iface->metric)) && +#endif + (!srt || srt != rt) && + rt->net.s_addr == r->net.s_addr) + return rt; + if (lrt) + *lrt = rt; + } + return NULL; +} + +static void +desc_route(const char *cmd, const struct rt *rt, const char *ifname) +{ + char addr[sizeof("000.000.000.000") + 1]; + + strlcpy(addr, inet_ntoa(rt->dest), sizeof(addr)); + if (rt->gate.s_addr == INADDR_ANY) + syslog(LOG_DEBUG, "%s: %s route to %s/%d", ifname, cmd, + addr, inet_ntocidr(rt->net)); + else if (rt->gate.s_addr == rt->dest.s_addr && + rt->net.s_addr == INADDR_BROADCAST) + syslog(LOG_DEBUG, "%s: %s host route to %s", ifname, cmd, + addr); + else if (rt->dest.s_addr == INADDR_ANY && rt->net.s_addr == INADDR_ANY) + syslog(LOG_DEBUG, "%s: %s default route via %s", ifname, cmd, + inet_ntoa(rt->gate)); + else + syslog(LOG_DEBUG, "%s: %s route to %s/%d via %s", ifname, cmd, + addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); +} + +/* If something other than dhcpcd removes a route, + * we need to remove it from our internal table. */ +int +route_deleted(const struct rt *rt) +{ + struct rt *f, *l; + + f = find_route(routes, rt, &l, NULL); + if (f == NULL) + return 0; + desc_route("removing", f, f->iface->name); + if (l) + l->next = f->next; + else + routes = f->next; + free(f); + return 1; +} + +static int +n_route(struct rt *rt, const struct interface *iface) +{ + /* Don't set default routes if not asked to */ + if (rt->dest.s_addr == 0 && + rt->net.s_addr == 0 && + !(iface->state->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route("adding", rt, iface->name); + if (!add_route(iface, &rt->dest, &rt->net, &rt->gate, iface->metric)) + return 0; + if (errno == EEXIST) { + /* Pretend we added the subnet route */ + if (rt->dest.s_addr == (iface->addr.s_addr & iface->net.s_addr) && + rt->net.s_addr == iface->net.s_addr && + rt->gate.s_addr == 0) + return 0; + else + return -1; } - return rtn; + syslog(LOG_ERR, "%s: add_route: %m", iface->name); + return -1; } static int -delete_route(const struct interface *iface, struct rt *rt, int metric) +c_route(struct rt *ort, struct rt *nrt, const struct interface *iface) +{ + /* Don't set default routes if not asked to */ + if (nrt->dest.s_addr == 0 && + nrt->net.s_addr == 0 && + !(iface->state->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route("changing", nrt, iface->name); + /* We delete and add the route so that we can change metric. + * This also has the nice side effect of flushing ARP entries so + * we don't have to do that manually. */ + del_route(ort->iface, &ort->dest, &ort->net, &ort->gate, + ort->iface->metric); + if (!add_route(iface, &nrt->dest, &nrt->net, &nrt->gate, + iface->metric)) + return 0; + syslog(LOG_ERR, "%s: add_route: %m", iface->name); + return -1; +} + +static int +d_route(struct rt *rt, const struct interface *iface, int metric) { - char *addr; int retval; - addr = xstrdup(inet_ntoa(rt->dest)); - logger(LOG_DEBUG, "deleting route %s/%d via %s", - addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); - free(addr); + desc_route("deleting", rt, iface->name); retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric); if (retval != 0 && errno != ENOENT && errno != ESRCH) - logger(LOG_ERR," del_route: %s", strerror(errno)); + syslog(LOG_ERR,"%s: del_route: %m", iface->name); return retval; } -static int -delete_routes(struct interface *iface, int metric) +static struct rt * +get_subnet_route(struct dhcp_message *dhcp) { + in_addr_t addr; + struct in_addr net; struct rt *rt; - struct rt *rtn; - int retval = 0; - rt = reverse_routes(iface->routes); - while (rt) { - rtn = rt->next; - retval += delete_route(iface, rt, metric); - free(rt); - rt = rtn; - } - iface->routes = NULL; + addr = dhcp->yiaddr; + if (addr == 0) + addr = dhcp->ciaddr; + /* Ensure we have all the needed values */ + if (get_option_addr(&net, dhcp, DHO_SUBNETMASK) == -1) + net.s_addr = get_netmask(addr); + if (net.s_addr == INADDR_BROADCAST || net.s_addr == INADDR_ANY) + return NULL; + rt = malloc(sizeof(*rt)); + rt->dest.s_addr = addr & net.s_addr; + rt->net.s_addr = net.s_addr; + rt->gate.s_addr = 0; + return rt; +} - return retval; +static struct rt * +add_subnet_route(struct rt *rt, const struct interface *iface) +{ + struct rt *r; + + if (iface->net.s_addr == INADDR_BROADCAST || + iface->net.s_addr == INADDR_ANY || + (iface->state->options->options & + (DHCPCD_INFORM | DHCPCD_STATIC) && + iface->state->options->req_addr.s_addr == INADDR_ANY)) + return rt; + + r = xmalloc(sizeof(*r)); + r->dest.s_addr = iface->addr.s_addr & iface->net.s_addr; + r->net.s_addr = iface->net.s_addr; + r->gate.s_addr = 0; + r->next = rt; + return r; } -static int -in_routes(const struct rt *routes, const struct rt *rt) +static struct rt * +get_routes(const struct interface *iface) { - while (routes) { - if (routes->dest.s_addr == rt->dest.s_addr && - routes->net.s_addr == rt->net.s_addr && - routes->gate.s_addr == rt->gate.s_addr) - return 0; - routes = routes->next; + struct rt *rt, *nrt = NULL, *r = NULL; + + if (iface->state->options->routes != NULL) { + for (rt = iface->state->options->routes; + rt != NULL; + rt = rt->next) + { + if (rt->gate.s_addr == 0) + break; + if (r == NULL) + r = nrt = xmalloc(sizeof(*r)); + else { + r->next = xmalloc(sizeof(*r)); + r = r->next; + } + memcpy(r, rt, sizeof(*r)); + r->next = NULL; + } + return nrt; } - return -1; + + return get_option_routes(iface->state->new, + iface->name, &iface->state->options->options); } -static int -configure_routes(struct interface *iface, const struct dhcp_message *dhcp, - const struct options *options) +/* Some DHCP servers add set host routes by setting the gateway + * to the assinged IP address. This differs from our notion of a host route + * where the gateway is the destination address, so we fix it. */ +static struct rt * +massage_host_routes(struct rt *rt, const struct interface *iface) { - struct rt *rt, *ort; - struct rt *rtn = NULL, *nr = NULL; - int remember; - int retval = 0; - char *addr; + struct rt *r; - ort = get_option_routes(dhcp); + for (r = rt; r; r = r->next) + if (r->gate.s_addr == iface->addr.s_addr && + r->net.s_addr == INADDR_BROADCAST) + r->gate.s_addr = r->dest.s_addr; + return rt; +} -#ifdef IPV4LL_ALWAYSROUTE - if (options->options & DHCPCD_IPV4LL && - IN_PRIVATE(ntohl(dhcp->yiaddr))) - { - for (rt = ort; rt; rt = rt->next) { - /* Check if we have already got a link locale route - * dished out by the DHCP server */ - if (rt->dest.s_addr == htonl(LINKLOCAL_ADDR) && - rt->net.s_addr == htonl(LINKLOCAL_MASK)) +static struct rt * +add_destination_route(struct rt *rt, const struct interface *iface) +{ + struct rt *r; + + if (!(iface->flags & IFF_POINTOPOINT) || + !has_option_mask(iface->state->options->dstmask, DHO_ROUTER)) + return rt; + r = xmalloc(sizeof(*r)); + r->dest.s_addr = INADDR_ANY; + r->net.s_addr = INADDR_ANY; + r->gate.s_addr = iface->dst.s_addr; + r->next = rt; + return r; +} + +/* 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 * +add_router_host_route(struct rt *rt, const struct interface *ifp) +{ + struct rt *rtp, *rtl, *rtn; + const char *cp, *cp2, *cp3, *cplim; + + for (rtp = rt, rtl = NULL; rtp; rtl = rtp, rtp = rtp->next) { + if (rtp->dest.s_addr != INADDR_ANY) + continue; + /* Scan for a route to match */ + for (rtn = rt; rtn != rtp; rtn = rtn->next) { + /* 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; - rtn = rt; } - - if (!rt) { - rt = xmalloc(sizeof(*rt)); - rt->dest.s_addr = htonl(LINKLOCAL_ADDR); - rt->net.s_addr = htonl(LINKLOCAL_MASK); - rt->gate.s_addr = 0; - rt->next = NULL; - if (rtn) - rtn->next = rt; - else - ort = rt; + if (rtn != rtp) + continue; + if (ifp->flags & IFF_NOARP) { + syslog(LOG_WARNING, + "%s: forcing router %s through interface", + ifp->name, inet_ntoa(rtp->gate)); + rtp->gate.s_addr = 0; + continue; } + syslog(LOG_WARNING, "%s: router %s requires a host route", + ifp->name, inet_ntoa(rtp->gate)); + rtn = xmalloc(sizeof(*rtn)); + rtn->dest.s_addr = rtp->gate.s_addr; + rtn->net.s_addr = INADDR_BROADCAST; + rtn->gate.s_addr = rtp->gate.s_addr; + rtn->next = rtp; + if (rtl == NULL) + rt = rtn; + else + rtl->next = rtn; } -#endif - - /* Now remove old routes we no longer use. - * We should do this in reverse order. */ - iface->routes = reverse_routes(iface->routes); - for (rt = iface->routes; rt; rt = rt->next) - if (in_routes(ort, rt) != 0) - delete_route(iface, rt, options->metric); - - for (rt = ort; rt; rt = rt->next) { - /* Don't set default routes if not asked to */ - if (rt->dest.s_addr == 0 && - rt->net.s_addr == 0 && - !(options->options & DHCPCD_GATEWAY)) - continue; + return rt; +} - addr = xstrdup(inet_ntoa(rt->dest)); - logger(LOG_DEBUG, "adding route to %s/%d via %s", - addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); - free(addr); - remember = add_route(iface, &rt->dest, - &rt->net, &rt->gate, - options->metric); - retval += remember; - - /* If we failed to add the route, we may have already added it - ourselves. If so, remember it again. */ - if (remember < 0) { - if (errno != EEXIST) - logger(LOG_ERR, "add_route: %s", - strerror(errno)); - if (in_routes(iface->routes, rt) == 0) - remember = 1; - } +void +build_routes(void) +{ + struct rt *nrs = NULL, *dnr, *or, *rt, *rtn, *rtl, *lrt = NULL; + const struct interface *ifp; - /* This login is split from above due to the #ifdef below */ - if (remember >= 0) { - if (nr) { - rtn->next = xmalloc(sizeof(*rtn)); - rtn = rtn->next; + for (ifp = ifaces; ifp; ifp = ifp->next) { + if (ifp->state->new == NULL) + continue; + dnr = get_routes(ifp); + dnr = massage_host_routes(dnr, ifp); + dnr = add_subnet_route(dnr, ifp); + dnr = add_router_host_route(dnr, ifp); + dnr = add_destination_route(dnr, ifp); + for (rt = dnr; rt && (rtn = rt->next, 1); lrt = rt, rt = rtn) { + rt->iface = ifp; + /* Is this route already in our table? */ + if ((find_route(nrs, rt, NULL, NULL)) != NULL) + continue; + /* Do we already manage it? */ + if ((or = find_route(routes, rt, &rtl, NULL))) { + if (or->iface != ifp || + rt->gate.s_addr != or->gate.s_addr) + { + if (c_route(or, rt, ifp) != 0) + continue; + } + if (rtl != NULL) + rtl->next = or->next; + else + routes = or->next; + free(or); } else { - nr = rtn = xmalloc(sizeof(*rtn)); + if (n_route(rt, ifp) != 0) + continue; } - rtn->dest.s_addr = rt->dest.s_addr; - rtn->net.s_addr = rt->net.s_addr; - rtn->gate.s_addr = rt->gate.s_addr; - rtn->next = NULL; + if (dnr == rt) + dnr = rtn; + else if (lrt) + lrt->next = rtn; + rt->next = nrs; + nrs = rt; } + free_routes(dnr); } - free_routes(ort); - free_routes(iface->routes); - iface->routes = nr; - return retval; + + /* Remove old routes we used to manage */ + for (rt = routes; rt; rt = rt->next) { + if (find_route(nrs, rt, NULL, NULL) == NULL) + d_route(rt, rt->iface, rt->iface->metric); + } + + free_routes(routes); + routes = nrs; } static int delete_address(struct interface *iface) { int retval; - logger(LOG_DEBUG, "deleting IP address %s/%d", - inet_ntoa(iface->addr), - inet_ntocidr(iface->net)); - retval = del_address(iface->name, &iface->addr, &iface->net); + struct if_options *ifo; + + ifo = iface->state->options; + if (ifo->options & DHCPCD_INFORM || + (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0)) + return 0; + syslog(LOG_DEBUG, "%s: deleting IP address %s/%d", + iface->name, + inet_ntoa(iface->addr), + inet_ntocidr(iface->net)); + retval = del_address(iface, &iface->addr, &iface->net); if (retval == -1 && errno != EADDRNOTAVAIL) - logger(LOG_ERR, "del_address: %s", strerror(errno)); + syslog(LOG_ERR, "del_address: %m"); iface->addr.s_addr = 0; iface->net.s_addr = 0; return retval; } int -configure(struct interface *iface, const char *reason, - const struct dhcp_message *dhcp, const struct dhcp_message *old, - const struct dhcp_lease *lease, const struct options *options, - int up) +configure(struct interface *iface) { - struct in_addr addr; - struct in_addr net; - struct in_addr brd; -#ifdef __linux__ - struct in_addr dest; - struct in_addr gate; -#endif + struct dhcp_message *dhcp = iface->state->new; + struct dhcp_lease *lease = &iface->state->lease; + struct if_options *ifo = iface->state->options; + struct rt *rt; - /* Grab our IP config */ - if (dhcp == NULL) - up = 0; - else { - addr.s_addr = dhcp->yiaddr; - if (addr.s_addr == 0) - addr.s_addr = lease->addr.s_addr; - /* Ensure we have all the needed values */ - if (get_option_addr(&net, dhcp, DHO_SUBNETMASK) == -1) - net.s_addr = get_netmask(addr.s_addr); - if (get_option_addr(&brd, dhcp, DHO_BROADCAST) == -1) - brd.s_addr = addr.s_addr | ~net.s_addr; - } + /* As we are now adjusting an interface, we need to ensure + * we have them in the right order for routing and configuration. */ + sort_interfaces(); - /* If we aren't up, then reset the interface as much as we can */ - if (!up) { - /* Only reset things if we had set them before */ - if (iface->addr.s_addr != 0) { - delete_routes(iface, options->metric); - delete_address(iface); + if (dhcp == NULL) { + if (!(ifo->options & DHCPCD_PERSISTENT)) { + build_routes(); + if (iface->addr.s_addr != 0) + delete_address(iface); + run_script(iface); } - - run_script(options, iface->name, reason, NULL, old); return 0; } /* This also changes netmask */ - if (!(options->options & DHCPCD_INFORM) || - !has_address(iface->name, &addr, &net)) { - logger(LOG_DEBUG, "adding IP address %s/%d", - inet_ntoa(addr), inet_ntocidr(net)); - if (add_address(iface->name, &addr, &net, &brd) == -1 && + if (!(ifo->options & DHCPCD_INFORM) || + !has_address(iface->name, &lease->addr, &lease->net)) + { + syslog(LOG_DEBUG, "%s: adding IP address %s/%d", + iface->name, inet_ntoa(lease->addr), + inet_ntocidr(lease->net)); + if (add_address(iface, + &lease->addr, &lease->net, &lease->brd) == -1 && errno != EEXIST) { - logger(LOG_ERR, "add_address: %s", strerror(errno)); + syslog(LOG_ERR, "add_address: %m"); return -1; } } /* Now delete the old address if different */ - if (iface->addr.s_addr != addr.s_addr && + if (iface->addr.s_addr != lease->addr.s_addr && iface->addr.s_addr != 0) delete_address(iface); -#ifdef __linux__ - /* On linux, we need to change the subnet route to have our metric. */ - if (iface->addr.s_addr != lease->addr.s_addr && - options->metric > 0 && net.s_addr != INADDR_BROADCAST) - { - dest.s_addr = addr.s_addr & net.s_addr; - gate.s_addr = 0; - add_route(iface, &dest, &net, &gate, options->metric); - del_route(iface, &dest, &net, &gate, 0); + iface->addr.s_addr = lease->addr.s_addr; + iface->net.s_addr = lease->net.s_addr; + + /* We need to delete the subnet route to have our metric or + * prefer the interface. */ + rt = get_subnet_route(dhcp); + if (rt != NULL) { + rt->iface = iface; + if (!find_route(routes, rt, NULL, NULL)) + del_route(iface, &rt->dest, &rt->net, &rt->gate, 0); + free(rt); } -#endif - iface->addr.s_addr = addr.s_addr; - iface->net.s_addr = net.s_addr; - configure_routes(iface, dhcp, options); - if (!lease->frominfo) + build_routes(); + if (!iface->state->lease.frominfo && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) if (write_lease(iface, dhcp) == -1) - logger(LOG_ERR, "write_lease: %s", strerror(errno)); - run_script(options, iface->name, reason, dhcp, old); + syslog(LOG_ERR, "write_lease: %m"); + run_script(iface); return 0; } diff --git a/configure.h b/configure.h index fe065db..17c506e 100644 --- a/configure.h +++ b/configure.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2009 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -28,14 +28,11 @@ #ifndef DHCPCONFIG_H #define DHCPCONFIG_H -#include "dhcpcd.h" -#include "dhcp.h" #include "net.h" -int run_script(const struct options *, const char *, const char *, - const struct dhcp_message *, const struct dhcp_message *); -int configure(struct interface *, const char *, - const struct dhcp_message *, const struct dhcp_message *, - const struct dhcp_lease *, const struct options *, int); - +int send_interface(int, const struct interface *); +int run_script(const struct interface *); +void build_routes(void); +int configure(struct interface *); +int route_deleted(const struct rt *); #endif diff --git a/control.c b/control.c new file mode 100644 index 0000000..24fb354 --- /dev/null +++ b/control.c @@ -0,0 +1,208 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcpcd.h" +#include "control.h" +#include "eloop.h" + +static int fd = -1; +static char buffer[1024]; +static char *argvp[255]; + +struct sockaddr_un sun; +struct fd_list *fds = NULL; + +static void +remove_control_data(void *arg) +{ + struct fd_list *l, *last = NULL; + + for (l = fds; l != NULL; l = l->next) { + if (l == arg) { + close(l->fd); + delete_event(l->fd); + if (last == NULL) + fds = l->next; + else + last->next = l->next; + free(l); + break; + } + last = l; + } +} + +static void +handle_control_data(void *arg) +{ + struct fd_list *l = arg; + ssize_t bytes; + int argc; + char *e, *p; + char **ap; + + bytes = read(l->fd, buffer, sizeof(buffer) - 1); + if (bytes == -1 || bytes == 0) { + remove_control_data(l); + return; + } + buffer[bytes] = '\0'; + p = buffer; + e = buffer + bytes; + argc = 0; + ap = argvp; + while (p < e && (size_t)argc < sizeof(argvp)) { + argc++; + *ap++ = p; + p += strlen(p) + 1; + } + handle_args(l, argc, argvp); +} + +/* ARGSUSED */ +static void +handle_control(_unused void *arg) +{ + struct sockaddr_un run; + socklen_t len; + struct fd_list *l; + int f; + + len = sizeof(run); + if ((f = accept(fd, (struct sockaddr *)&run, &len)) == -1) + return; + l = xmalloc(sizeof(*l)); + l->fd = f; + l->listener = 0; + l->next = fds; + fds = l; + add_event(l->fd, handle_control_data, l); +} + +static int +make_sock(void) +{ + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + return -1; + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, CONTROLSOCKET, sizeof(sun.sun_path)); + return sizeof(sun.sun_family) + strlen(sun.sun_path) + 1; +} + +int +start_control(void) +{ + int len; + + if ((len = make_sock()) == -1) + return -1; + unlink(CONTROLSOCKET); + if (bind(fd, (struct sockaddr *)&sun, len) == -1 || + chmod(CONTROLSOCKET, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1 || + set_cloexec(fd) == -1 || + set_nonblock(fd) == -1 || + listen(fd, sizeof(fds)) == -1) + { + close(fd); + return -1; + } + add_event(fd, handle_control, NULL); + return fd; +} + +int +stop_control(void) +{ + int retval = 0; + struct fd_list *l, *ll; + + delete_event(fd); + if (shutdown(fd, SHUT_RDWR) == -1) + retval = 1; + fd = -1; + if (unlink(CONTROLSOCKET) == -1) + retval = -1; + + l = fds; + while (l != NULL) { + ll = l->next; + delete_event(l->fd); + shutdown(l->fd, SHUT_RDWR); + free(l); + l = ll; + } + + return retval; +} + +int +open_control(void) +{ + int len; + + if ((len = make_sock()) == -1) + return -1; + return connect(fd, (struct sockaddr *)&sun, len); +} + +int +send_control(int argc, char * const *argv) +{ + char *p = buffer; + int i; + size_t len; + + if (argc > 255) { + errno = ENOBUFS; + return -1; + } + for (i = 0; i < argc; i++) { + len = strlen(argv[i]) + 1; + if ((p - buffer) + len > sizeof(buffer)) { + errno = ENOBUFS; + return -1; + } + memcpy(p, argv[i], len); + p += len; + } + return write(fd, buffer, p - buffer); +} diff --git a/control.h b/control.h new file mode 100644 index 0000000..f0faa40 --- /dev/null +++ b/control.h @@ -0,0 +1,45 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include "dhcpcd.h" + +struct fd_list { + int fd; + int listener; + struct fd_list *next; +}; +extern struct fd_list *fds; + +int start_control(void); +int stop_control(void); +int open_control(void); +int send_control(int, char * const *); + +#endif diff --git a/defs.h b/defs.h new file mode 100644 index 0000000..81cf213 --- /dev/null +++ b/defs.h @@ -0,0 +1,52 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PACKAGE "dhcpcd" +#define VERSION "5.2.10" + +#ifndef CONFIG +# define CONFIG SYSCONFDIR "/" PACKAGE ".conf" +#endif +#ifndef SCRIPT +# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks" +#endif +#ifndef DUID +# define DUID SYSCONFDIR "/" PACKAGE ".duid" +#endif +#ifndef LEASEFILE +# define LEASEFILE DBDIR "/" PACKAGE "-%s.lease" +#endif +#ifndef PIDFILE +# define PIDFILE RUNDIR "/" PACKAGE "%s%s.pid" +#endif +#ifndef CONTROLSOCKET +# define CONTROLSOCKET RUNDIR "/" PACKAGE ".sock" +#endif + +#endif diff --git a/dhcp.c b/dhcp.c index 1854cf2..bd6c719 100644 --- a/dhcp.c +++ b/dhcp.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "config.h" @@ -57,7 +58,7 @@ /* Our aggregate option buffer. * We ONLY use this when options are split, which for most purposes is * practically never. See RFC3396 for details. */ -static uint8_t *dhcp_opt_buffer = NULL; +static uint8_t *opt_buffer; struct dhcp_opt { uint8_t option; @@ -67,9 +68,10 @@ struct dhcp_opt { static const struct dhcp_opt const dhcp_opts[] = { { 1, IPV4 | REQUEST, "subnet_mask" }, - /* RFC 3442 states that the CSR has to come before all other routes. - * For completeness, we also specify static routes, then routers. */ - { 121, RFC3442 | REQUEST, "classless_static_routes" }, + /* RFC 3442 states that the CSR has to come before all other + * routes. For completeness, we also specify static routes, + * then routers. */ + { 121, RFC3442, "classless_static_routes" }, { 249, RFC3442, "ms_classless_static_routes" }, { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, { 3, IPV4 | ARRAY | REQUEST, "routers" }, @@ -160,22 +162,52 @@ static const struct dhcp_opt const dhcp_opts[] = { { 0, 0, NULL } }; +static const char *if_params[] = { + "interface", + "reason", + "pid", + "ifmetric", + "ifwireless", + "ifflags", + "profile", + "interface_order", + NULL +}; + +static const char *dhcp_params[] = { + "ip_address", + "subnet_cidr", + "network_number", + "ssid", + "filename", + "server_name", + NULL +}; + void print_options(void) { const struct dhcp_opt *opt; + const char **p; + + for (p = if_params; *p; p++) + printf(" - %s\n", *p); + + for (p = dhcp_params; *p; p++) + printf(" %s\n", *p); for (opt = dhcp_opts; opt->option; opt++) if (opt->var) printf("%03d %s\n", opt->option, opt->var); } -int make_option_mask(uint8_t *mask, char **opts, int add) +int make_option_mask(uint8_t *mask, const char *opts, int add) { - char *token, *p = *opts, *t; + char *token, *o, *p, *t; const struct dhcp_opt *opt; int match, n; + o = p = xstrdup(opts); while ((token = strsep(&p, ", "))) { if (*token == '\0') continue; @@ -192,22 +224,28 @@ int make_option_mask(uint8_t *mask, char **opts, int add) if (opt->option == n) match = 1; } - if (match) { - if (add == 1) + if (match) { + if (add == 2 && !(opt->type & IPV4)) { + free(o); + errno = EINVAL; + return -1; + } + if (add == 1 || add == 2) add_option_mask(mask, - opt->option); + opt->option); else del_option_mask(mask, - opt->option); + opt->option); break; } } if (!opt->option) { - *opts = token; + free(o); errno = ENOENT; return -1; } } + free(o); return 0; } @@ -227,7 +265,9 @@ valid_length(uint8_t option, int dl, int *type) if (type) *type = opt->type; - if (opt->type == 0 || opt->type & STRING || opt->type & RFC3442) + if (opt->type == 0 || + opt->type & STRING || + opt->type & RFC3442) return 0; sz = 0; @@ -246,11 +286,13 @@ valid_length(uint8_t option, int dl, int *type) return 0; } +#ifdef DEBUG_MEMORY static void free_option_buffer(void) { - free(dhcp_opt_buffer); + free(opt_buffer); } +#endif #define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL) static const uint8_t * @@ -269,12 +311,14 @@ get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) o = *p++; if (o == opt) { if (op) { - if (!dhcp_opt_buffer) { - dhcp_opt_buffer = xmalloc(sizeof(struct dhcp_message)); + if (!opt_buffer) { + opt_buffer = xmalloc(sizeof(*dhcp)); +#ifdef DEBUG_MEMORY atexit(free_option_buffer); +#endif } if (!bp) - bp = dhcp_opt_buffer; + bp = opt_buffer; memcpy(bp, op, ol); bp += ol; } @@ -318,7 +362,7 @@ exit: *len = bl; if (bp) { memcpy(bp, op, ol); - return (const uint8_t *)&dhcp_opt_buffer; + return (const uint8_t *)opt_buffer; } if (op) return op; @@ -371,12 +415,13 @@ get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) if (!p) return -1; - *i = *(p); + if (i) + *i = *(p); return 0; } /* Decode an RFC3397 DNS search order option into a space - * seperated string. Returns length of string (including + * separated string. Returns length of string (including * terminating zero) or zero on error. out may be NULL * to just determine output length. */ static ssize_t @@ -442,10 +487,8 @@ static ssize_t decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) { const uint8_t *e; - ssize_t bytes = 0; - ssize_t b; + ssize_t b, bytes = 0, ocets; uint8_t cidr; - uint8_t ocets; struct in_addr addr; char *o = out; @@ -479,7 +522,7 @@ decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) /* If we have ocets then we have a destination and netmask */ if (ocets > 0) { addr.s_addr = 0; - memcpy(&addr.s_addr, p, (size_t)ocets); + memcpy(&addr.s_addr, p, ocets); b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr); p += ocets; } else @@ -506,7 +549,7 @@ decode_rfc3442_rt(int dl, const uint8_t *data) const uint8_t *p = data; const uint8_t *e; uint8_t cidr; - uint8_t ocets; + size_t ocets; struct rt *routes = NULL; struct rt *rt = NULL; @@ -534,15 +577,9 @@ decode_rfc3442_rt(int dl, const uint8_t *data) ocets = (cidr + 7) / 8; /* If we have ocets then we have a destination and netmask */ if (ocets > 0) { - memcpy(&rt->dest.s_addr, p, (size_t)ocets); - memset(&rt->net.s_addr, 255, (size_t)ocets - 1); - memset((uint8_t *)&rt->net.s_addr + - (ocets - 1), - (256 - (1 << (32 - cidr) % 8)), 1); + memcpy(&rt->dest.s_addr, p, ocets); p += ocets; - } else { - rt->dest.s_addr = 0; - rt->net.s_addr = 0; + rt->net.s_addr = htonl(~0U << (32 - cidr)); } /* Finally, snag the router */ @@ -664,7 +701,8 @@ route_netmask(uint32_t ip_in) * If we have a CSR then we only use that. * Otherwise we add static routes and then routers. */ struct rt * -get_option_routes(const struct dhcp_message *dhcp) +get_option_routes(const struct dhcp_message *dhcp, + const char *ifname, int *opts) { const uint8_t *p; const uint8_t *e; @@ -679,8 +717,13 @@ get_option_routes(const struct dhcp_message *dhcp) p = get_option(dhcp, DHO_MSCSR, &len, NULL); if (p) { routes = decode_rfc3442_rt(len, p); - if (routes) + if (routes && !(*opts & DHCPCD_CSR_WARNED)) { + syslog(LOG_DEBUG, + "%s: using Classless Static Routes (RFC3442)", + ifname); + *opts |= DHCPCD_CSR_WARNED; return routes; + } } /* OK, get our static routes first. */ @@ -747,17 +790,42 @@ encode_rfc1035(const char *src, uint8_t *dst) return p - dst; } -#define PUTADDR(_type, _val) \ -{ \ - *p++ = _type; \ - *p++ = 4; \ - memcpy(p, &_val.s_addr, 4); \ - p += 4; \ +#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 = 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 *iface, const struct dhcp_lease *lease, - uint32_t xid, uint8_t type, const struct options *options) + const struct interface *iface, + uint8_t type) { struct dhcp_message *dhcp; uint8_t *m, *lp, *p; @@ -765,27 +833,27 @@ make_message(struct dhcp_message **message, time_t up = uptime() - iface->start_uptime; uint32_t ul; uint16_t sz; - const struct dhcp_opt *opt; size_t len; const char *hp; + const struct dhcp_opt *opt; + const struct if_options *ifo = iface->state->options; + const struct dhcp_lease *lease = &iface->state->lease; + printf("%s: Start\n", __func__); dhcp = xzalloc(sizeof (*dhcp)); m = (uint8_t *)dhcp; p = dhcp->options; - if ((type == DHCP_INFORM || - type == DHCP_RELEASE || - type == DHCP_REQUEST) && - !IN_LINKLOCAL(ntohl(iface->addr.s_addr))) + if ((type == DHCP_INFORM || type == DHCP_RELEASE || + (type == DHCP_REQUEST && + iface->net.s_addr == lease->net.s_addr && + (iface->state->new == NULL || + iface->state->new->cookie == htonl(MAGIC_COOKIE))))) { dhcp->ciaddr = iface->addr.s_addr; - /* Just incase we haven't actually configured the address yet */ + /* In-case we haven't actually configured the address yet */ if (type == DHCP_INFORM && iface->addr.s_addr == 0) dhcp->ciaddr = lease->addr.s_addr; - /* Zero the address if we're currently on a different subnet */ - if (type == DHCP_REQUEST && - iface->net.s_addr != lease->net.s_addr) - dhcp->ciaddr = 0; } dhcp->op = DHCP_BOOTREQUEST; @@ -793,25 +861,24 @@ make_message(struct dhcp_message **message, switch (iface->family) { case ARPHRD_ETHER: case ARPHRD_IEEE802: - dhcp->hwlen = ETHER_ADDR_LEN; - memcpy(&dhcp->chaddr, &iface->hwaddr, ETHER_ADDR_LEN); - break; - case ARPHRD_IEEE1394: - case ARPHRD_INFINIBAND: - dhcp->hwlen = 0; - if (dhcp->ciaddr == 0 && - type != DHCP_DECLINE && type != DHCP_RELEASE) - dhcp->flags = htons(BROADCAST_FLAG); + dhcp->hwlen = iface->hwlen; + memcpy(&dhcp->chaddr, &iface->hwaddr, iface->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(up); } - dhcp->xid = xid; + dhcp->xid = iface->state->xid; dhcp->cookie = htonl(MAGIC_COOKIE); *p++ = DHO_MESSAGETYPE; @@ -824,16 +891,20 @@ make_message(struct dhcp_message **message, p += iface->clientid[0] + 1; } - if (lease->addr.s_addr && !IN_LINKLOCAL(htonl(lease->addr.s_addr))) { + if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { if (type == DHCP_DECLINE || - type == DHCP_DISCOVER || (type == DHCP_REQUEST && - lease->addr.s_addr != iface->addr.s_addr)) + lease->addr.s_addr != iface->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) { @@ -844,10 +915,8 @@ make_message(struct dhcp_message **message, p += len; } - if (type == DHCP_RELEASE) { - if (lease->server.s_addr) - PUTADDR(DHO_SERVERID, lease->server); - } + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) + PUTADDR(DHO_IPADDRESS, ifo->req_addr); if (type == DHCP_DISCOVER || type == DHCP_INFORM || @@ -859,29 +928,35 @@ make_message(struct dhcp_message **message, if (sz < MTU_MIN) { if (set_mtu(iface->name, MTU_MIN) == 0) sz = MTU_MIN; + } else if (sz > MTU_MAX) { + /* Even though our MTU could be greater than + * MTU_MAX (1500) dhcpcd does not presently + * handle DHCP packets any bigger. */ + sz = MTU_MAX; } sz = htons(sz); memcpy(p, &sz, 2); p += 2; - if (options->userclass[0]) { + if (ifo->userclass[0]) { *p++ = DHO_USERCLASS; - memcpy(p, options->userclass, options->userclass[0] + 1); - p += options->userclass[0] + 1; + memcpy(p, ifo->userclass, ifo->userclass[0] + 1); + p += ifo->userclass[0] + 1; } - if (options->vendorclassid[0]) { + if (ifo->vendorclassid[0]) { *p++ = DHO_VENDORCLASSID; - memcpy(p, options->vendorclassid, - options->vendorclassid[0] + 1); - p += options->vendorclassid[0] + 1; + memcpy(p, ifo->vendorclassid, + ifo->vendorclassid[0] + 1); + p += ifo->vendorclassid[0] + 1; } + if (type != DHCP_INFORM) { - if (options->leasetime != 0) { + if (ifo->leasetime != 0) { *p++ = DHO_LEASETIME; *p++ = 4; - ul = htonl(options->leasetime); + ul = htonl(ifo->leasetime); memcpy(p, &ul, 4); p += 4; } @@ -891,18 +966,18 @@ make_message(struct dhcp_message **message, * upto the first dot (the short hostname) as otherwise * confuses some DHCP servers when updating DNS. * The FQDN option should be used if a FQDN is required. */ - if (options->hostname[0]) { + if (ifo->options & DHCPCD_HOSTNAME && ifo->hostname[0]) { *p++ = DHO_HOSTNAME; - hp = strchr(options->hostname, '.'); + hp = strchr(ifo->hostname, '.'); if (hp) - len = hp - options->hostname; + len = hp - ifo->hostname; else - len = strlen(options->hostname); + len = strlen(ifo->hostname); *p++ = len; - memcpy(p, options->hostname, len); + memcpy(p, ifo->hostname, len); p += len; } - if (options->fqdn != FQDN_DISABLE) { + if (ifo->fqdn != FQDN_DISABLE && ifo->hostname[0]) { /* IETF DHC-FQDN option (81), RFC4702 */ *p++ = DHO_FQDN; lp = p; @@ -917,19 +992,19 @@ make_message(struct dhcp_message **message, * N: 1 => Client requests Server to not * update DNS */ - *p++ = (options->fqdn & 0x09) | 0x04; + *p++ = (ifo->fqdn & 0x09) | 0x04; *p++ = 0; /* from server for PTR RR */ *p++ = 0; /* from server for A RR if S=1 */ - ul = encode_rfc1035(options->hostname, p); + ul = encode_rfc1035(ifo->hostname, p); *lp += ul; p += ul; } /* vendor is already encoded correctly, so just add it */ - if (options->vendor[0]) { + if (ifo->vendor[0]) { *p++ = DHO_VENDOR; - memcpy(p, options->vendor, options->vendor[0] + 1); - p += options->vendor[0] + 1; + memcpy(p, ifo->vendor, ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; } *p++ = DHO_PARAMETERREQUESTLIST; @@ -937,15 +1012,12 @@ make_message(struct dhcp_message **message, *p++ = 0; for (opt = dhcp_opts; opt->option; opt++) { if (!(opt->type & REQUEST || - has_option_mask(options->requestmask, opt->option))) + has_option_mask(ifo->requestmask, opt->option))) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) continue; - switch (opt->option) { - case DHO_RENEWALTIME: /* FALLTHROUGH */ - case DHO_REBINDTIME: - if (type == DHCP_INFORM) - continue; - break; - } *p++ = opt->option; } *n_params = p - n_params - 1; @@ -974,16 +1046,20 @@ write_lease(const struct interface *iface, const struct dhcp_message *dhcp) uint8_t l; uint8_t o = 0; - fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400); -#ifdef ANDROID - if (fd == -1 && errno == EACCES) { - /* the lease file might have been created when dhcpcd was running as root */ + /* We don't write BOOTP leases */ + if (is_bootp(dhcp)) { unlink(iface->leasefile); - fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400); + return 0; } -#endif - if (fd == -1) + + syslog(LOG_DEBUG, "%s: writing lease `%s'", + iface->name, iface->leasefile); + + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0444); + if (fd == -1) { + syslog(LOG_ERR, "%s: open: %m", iface->name); return -1; + } /* Only write as much as we need */ while (p < e) { @@ -1011,8 +1087,14 @@ read_lease(const struct interface *iface) ssize_t bytes; fd = open(iface->leasefile, O_RDONLY); - if (fd == -1) + if (fd == -1) { + if (errno != ENOENT) + syslog(LOG_ERR, "%s: open `%s': %m", + iface->name, iface->leasefile); return NULL; + } + syslog(LOG_DEBUG, "%s: reading lease `%s'", + iface->name, iface->leasefile); dhcp = xmalloc(sizeof(*dhcp)); memset(dhcp, 0, sizeof(*dhcp)); bytes = read(fd, dhcp, sizeof(*dhcp)); @@ -1058,21 +1140,21 @@ print_string(char *s, ssize_t len, int dl, const uint8_t *data) continue; } switch (c) { - case '"': /* FALLTHROUGH */ - case '\'': /* FALLTHROUGH */ - case '$': /* FALLTHROUGH */ - case '`': /* FALLTHROUGH */ - case '\\': /* FALLTHROUGH */ - if (s) { - if (len < 3) { - errno = ENOBUFS; - return -1; - } - *s++ = '\\'; - len--; + case '"': /* FALLTHROUGH */ + case '\'': /* FALLTHROUGH */ + case '$': /* FALLTHROUGH */ + case '`': /* FALLTHROUGH */ + case '\\': + if (s) { + if (len < 3) { + errno = ENOBUFS; + return -1; } - bytes++; - break; + *s++ = '\\'; + len--; + } + bytes++; + break; } if (s) { *s++ = c; @@ -1125,17 +1207,22 @@ print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data) if (!s) { if (type & UINT8) l = 3; - else if (type & UINT16) + else if (type & UINT16) { l = 5; - else if (type & SINT16) + dl /= 2; + } else if (type & SINT16) { l = 6; - else if (type & UINT32) + dl /= 2; + } else if (type & UINT32) { l = 10; - else if (type & SINT32) + dl /= 4; + } else if (type & SINT32) { l = 11; - else if (type & IPV4) + dl /= 4; + } else if (type & IPV4) { l = 16; - else { + dl /= 4; + } else { errno = EINVAL; return -1; } @@ -1199,7 +1286,7 @@ setvar(char ***e, const char *prefix, const char *var, const char *value) ssize_t configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, - const struct options *options) + const struct if_options *ifo) { unsigned int i; const uint8_t *p; @@ -1220,12 +1307,12 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (has_option_mask(options->nomask, opt->option)) + if (has_option_mask(ifo->nomask, opt->option)) continue; if (get_option_raw(dhcp, opt->option)) e++; } - if (dhcp->yiaddr) + if (dhcp->yiaddr || dhcp->ciaddr) e += 5; if (*dhcp->bootfile && !(overl & 1)) e++; @@ -1235,10 +1322,10 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, } ep = env; - if (dhcp->yiaddr) { + 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; + addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr; setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); if (get_option_addr(&net, dhcp, DHO_SUBNETMASK) == -1) { net.s_addr = get_netmask(addr.s_addr); @@ -1263,7 +1350,7 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (has_option_mask(options->nomask, opt->option)) + if (has_option_mask(ifo->nomask, opt->option)) continue; val = NULL; p = get_option(dhcp, opt->option, &pl, NULL); @@ -1286,3 +1373,33 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, return ep - env; } + +void +get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) +{ + struct timeval now; + + 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(&lease->net, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = get_netmask(lease->addr.s_addr); + if (get_option_addr(&lease->brd, dhcp, DHO_BROADCAST) == -1) + lease->brd.s_addr = lease->addr.s_addr | ~lease->net.s_addr; + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) { + /* Ensure that we can use the lease */ + get_monotonic(&now); + if (now.tv_sec + (time_t)lease->leasetime < now.tv_sec) + lease->leasetime = ~0U; /* Infinite lease */ + } else + lease->leasetime = ~0U; /* Default to infinite lease */ + if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) + lease->rebindtime = 0; + if (get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; +} diff --git a/dhcp.h b/dhcp.h index 09e8ecb..cb42275 100644 --- a/dhcp.h +++ b/dhcp.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2008 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -29,12 +29,11 @@ #define DHCP_H #include +#include #include -#include "config.h" -#include "dhcpcd.h" -#include "net.h" +#include "common.h" /* Max MTU - defines dhcp option length */ #define MTU_MAX 1500 @@ -61,9 +60,26 @@ #define DHCP_RELEASE 7 #define DHCP_INFORM 8 +/* 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 +#define DHCP_ARP_FAIL 10 + +/* number of usecs in a second. */ +#define USECS_SECOND 1000000 +/* As we use timevals, we should use the usec part for + * greater randomisation. */ +#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND +#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND +#define PROBE_MIN_U PROBE_MIN * USECS_SECOND +#define PROBE_MAX_U PROBE_MAX * USECS_SECOND + /* DHCP options */ -enum DHO -{ +enum DHO { DHO_PAD = 0, DHO_SUBNETMASK = 1, DHO_ROUTER = 3, @@ -112,13 +128,9 @@ enum FQDN { #define DHCP_CHADDR_LEN 16 #define SERVERNAME_LEN 64 #define BOOTFILE_LEN 128 -#define DHCP_UDP_LEN (20 + 8) -#define DHCP_BASE_LEN (4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4) -#define DHCP_RESERVE_LEN (4 + 4 + 4 + 4 + 2) -#define DHCP_FIXED_LEN (DHCP_BASE_LEN + DHCP_CHADDR_LEN + \ - + SERVERNAME_LEN + BOOTFILE_LEN) -#define DHCP_OPTION_LEN (MTU_MAX - DHCP_FIXED_LEN - DHCP_UDP_LEN \ - - DHCP_RESERVE_LEN) +#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 @@ -140,11 +152,12 @@ struct dhcp_message { 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; @@ -152,27 +165,37 @@ struct dhcp_lease { time_t leasedfrom; struct timeval boundtime; uint8_t frominfo; + uint32_t cookie; }; +#include "dhcpcd.h" +#include "if-options.h" +#include "net.h" + #define add_option_mask(var, val) (var[val >> 3] |= 1 << (val & 7)) #define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) #define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7))) -int make_option_mask(uint8_t *, char **, int); +int make_option_mask(uint8_t *, const char *, int); void print_options(void); char *get_option_string(const struct dhcp_message *, uint8_t); int get_option_addr(struct in_addr *, const struct dhcp_message *, uint8_t); int get_option_uint32(uint32_t *, const struct dhcp_message *, uint8_t); int get_option_uint16(uint16_t *, const struct dhcp_message *, uint8_t); int get_option_uint8(uint8_t *, const struct dhcp_message *, uint8_t); -struct rt *get_option_routes(const struct dhcp_message *); +#define is_bootp(m) (m && \ + !IN_LINKLOCAL(htonl((m)->yiaddr)) && \ + get_option_uint8(NULL, m, DHO_MESSAGETYPE) == -1) +struct rt *get_option_routes(const struct dhcp_message *, const char *, int *); ssize_t configure_env(char **, const char *, const struct dhcp_message *, - const struct options *); + const struct if_options *); -ssize_t make_message(struct dhcp_message **, - const struct interface *, const struct dhcp_lease *, - uint32_t, uint8_t, const struct options *); +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 *); ssize_t write_lease(const struct interface *, const struct dhcp_message *); -struct dhcp_message *read_lease(const struct interface *iface); +struct dhcp_message *read_lease(const struct interface *); +void get_lease(struct dhcp_lease *, const struct dhcp_message *); + #endif diff --git a/dhcpcd-hooks/02-dump b/dhcpcd-hooks/02-dump new file mode 100644 index 0000000..cbb46ea --- /dev/null +++ b/dhcpcd-hooks/02-dump @@ -0,0 +1,5 @@ +# Just echo our DHCP options we have + +if [ "$reason" = "DUMP" ]; then + set | sed -ne 's/^new_//p' | sort +fi diff --git a/dhcpcd-hooks/20-resolv.conf b/dhcpcd-hooks/20-resolv.conf index e757ddf..628636d 100644 --- a/dhcpcd-hooks/20-resolv.conf +++ b/dhcpcd-hooks/20-resolv.conf @@ -6,102 +6,121 @@ # 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" +resolv_conf_dir="$state_dir/resolv.conf" build_resolv_conf() { - local cf="/etc/resolv.conf.${interface}" + local cf="$state_dir/resolv.conf.$interface" local interfaces= header= search= srvs= servers= x= # Build a list of interfaces - interfaces=$(list_interfaces "${resolv_conf_dir}") + interfaces=$(list_interfaces "$resolv_conf_dir") # Build the resolv.conf - if [ -n "${interfaces}" ]; then + if [ -n "$interfaces" ]; then # Build the header for x in ${interfaces}; do - header="${header}${header:+, }${x}" + header="$header${header:+, }$x" done # Build the search list - search=$(cd "${resolv_conf_dir}"; \ + domain=$(cd "$resolv_conf_dir"; \ + key_get_value "domain " ${interfaces}) + search=$(cd "$resolv_conf_dir"; \ key_get_value "search " ${interfaces}) - [ -n "${search}" ] && search="search $(uniqify ${search})\n" + set -- ${domain} + unset domain + if [ -n "$2" ]; then + search="$search $@" + elif [ -n "$1" ]; then + domain="domain $1\n" + fi + [ -n "$search" ] && search="search $(uniqify $search)\n" # Build the nameserver list - srvs=$(cd "${resolv_conf_dir}"; \ + srvs=$(cd "$resolv_conf_dir"; \ key_get_value "nameserver " ${interfaces}) for x in $(uniqify ${srvs}); do - servers="${servers}nameserver ${x}\n" + servers="${servers}nameserver $x\n" done fi - header="${signature_base}${header:+ ${from} }${header}" + header="$signature_base${header:+ $from }$header" # Assemble resolv.conf using our head and tail files - [ -f "${cf}" ] && rm -f "${cf}" - echo "${header}" > "${cf}" + [ -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}" + cat /etc/resolv.conf.head >> "$cf" else - echo "# /etc/resolv.conf.head can replace this line" >> "${cf}" + echo "# /etc/resolv.conf.head can replace this line" >> "$cf" fi - printf "${search}${servers}" >> "${cf}" + printf "$domain$search$servers" >> "$cf" if [ -f /etc/resolv.conf.tail ]; then - cat /etc/resolv.conf.tail >> "${cf}" + cat /etc/resolv.conf.tail >> "$cf" else - echo "# /etc/resolv.conf.tail can replace this line" >> "${cf}" + echo "# /etc/resolv.conf.tail can replace this line" >> "$cf" fi - mv -f "${cf}" /etc/resolv.conf + cat "$cf" > /etc/resolv.conf + chmod 644 /etc/resolv.conf + rm -f "$cf" } add_resolv_conf() { - local x= conf="${signature}\n" + local x= conf="$signature\n" # 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 + if [ -z "$new_domain_name_servers" -a \ + -z "$new_domain_name" -a \ + -z "$new_domain_search" ]; then remove_resolv_conf return $? fi - if [ -n "${new_domain_search}" ]; then - conf="${conf}search ${new_domain_search}\n" - elif [ -n "${new_domain_name}" ]; then - conf="${conf}search ${new_domain_name}\n" + if [ -n "$new_domain_name" ]; then + set -- $new_domain_name + new_domain_name="$1" + conf="${conf}domain $new_domain_name\n" + # Support RFC violating search in domain + if [ -z "$new_domain_search" -a -n "$2" ]; then + new_domain_search="$@" + fi + fi + if [ -n "$new_domain_search" ]; then + conf="${conf}search $new_domain_search\n" fi for x in ${new_domain_name_servers}; do - conf="${conf}nameserver ${x}\n" + conf="${conf}nameserver $x\n" done if type resolvconf >/dev/null 2>&1; then - printf "${conf}" | resolvconf -a "${interface}" + [ -n "$metric" ] && export IF_METRIC="$metric" + printf "$conf" | resolvconf -a "$interface" return $? fi - if [ -e "${resolv_conf_dir}/${interface}" ]; then - rm -f "${resolv_conf_dir}/${interface}" - fi - if [ ! -d "${resolv_conf_dir}" ]; then - mkdir -p "${resolv_conf_dir}" + if [ -e "$resolv_conf_dir/$interface" ]; then + rm -f "$resolv_conf_dir/$interface" fi - printf "${conf}" > "${resolv_conf_dir}/${interface}" + [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" + printf "$conf" > "$resolv_conf_dir/$interface" build_resolv_conf } remove_resolv_conf() { if type resolvconf >/dev/null 2>&1; then - resolvconf -d "${interface}" -f + resolvconf -d "$interface" -f else - if [ -e "${resolv_conf_dir}/${interface}" ]; then - rm -f "${resolv_conf_dir}/${interface}" + if [ -e "$resolv_conf_dir/$interface" ]; then + rm -f "$resolv_conf_dir/$interface" fi build_resolv_conf fi } -case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) add_resolv_conf;; -PREINIT|EXPIRE|FAIL|IPV4LL|RELEASE|STOP) remove_resolv_conf;; -esac +if $if_up; 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 index 3dfade3..8661fcc 100644 --- a/dhcpcd-hooks/29-lookup-hostname +++ b/dhcpcd-hooks/29-lookup-hostname @@ -2,19 +2,19 @@ lookup_hostname() { - [ -z "${new_ip_address}" ] && return 1 + [ -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}` + h=$(dig +short -x $new_ip_address) if [ $? = 0 ]; then - echo "${h}" | sed 's/\.$//' + echo "$h" | sed 's/\.$//' return 0 fi elif type host >/dev/null 2>&1; then - h=`host ${new_ip_address}` + h=$(host $new_ip_address) if [ $? = 0 ]; then - echo "${h}" \ + echo "$h" \ | sed 's/.* domain name pointer \(.*\)./\1/' return 0 fi @@ -24,11 +24,11 @@ lookup_hostname() set_hostname() { - if [ -z "${new_host_name}" -a -z "${new_fqdn_name}" ]; then + if [ -z "$new_host_name" -a -z "$new_fqdn_name" ]; then export new_host_name="$(lookup_hostname)" fi } -case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) set_hostname;; -esac +if $if_up; then + set_hostname +fi diff --git a/dhcpcd-hooks/30-hostname b/dhcpcd-hooks/30-hostname index b2e5fc8..87446fb 100644 --- a/dhcpcd-hooks/30-hostname +++ b/dhcpcd-hooks/30-hostname @@ -2,10 +2,16 @@ need_hostname() { - case "$(hostname)" in + local hostname="" + + case "$force_hostname" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) ;; + *) hostname="$(hostname)";; + esac + case "$hostname" in ""|"(none)"|localhost|localhost.localdomain) - [ -n "${new_host_name}" -o -n "${new_fqdn_name}" ];; - "${old_host_name}"|"${old_fqdn_name}") + [ -n "$new_host_name" -o -n "$new_fqdn_name" ];; + "$old_host_name"|"$old_fqdn_name") true;; *) false;; @@ -15,14 +21,14 @@ need_hostname() set_hostname() { if need_hostname; then - if [ -n "${new_host_name}" ]; then - hostname "${new_host_name}" - else - hostname "${new_fqdn_name}" + if [ -n "$new_host_name" ]; then + hostname "$new_host_name" + elif [ -n "$new_fqdn_name" ]; then + hostname "$new_fqdn_name" fi fi } -case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) set_hostname;; -esac +if $if_up; then + set_hostname +fi diff --git a/dhcpcd-hooks/50-dhcpcd-compat b/dhcpcd-hooks/50-dhcpcd-compat index bb31fd3..651bc08 100644 --- a/dhcpcd-hooks/50-dhcpcd-compat +++ b/dhcpcd-hooks/50-dhcpcd-compat @@ -1,41 +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} +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}" +for x in $new_routers; do + GATEWAY="$GATEWAY${GATEWAY:+,}$x" done DNS= -for x in ${new_domain_name_servers}; do - DNS="${DNS}${DNS:+,}${x}" +for x in $new_domain_name_servers; do + DNS="$DNS${DNS:+,}$x" done x="down" -case "${reason}" in +case "$reason" in RENEW) x="up";; BOUND|INFORM|REBIND|REBOOT|TEST|TIMEOUT|IPV4LL) x="new";; esac -if [ "${reason}" != "down" ]; then - rm -f /var/lib/dhcpcd-"${INTERFACE}".info +if [ "$reason" != "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 + eval echo "$x=\'\$$x\'" >> /var/lib/dhcpcd-"$INTERFACE".info done fi -set -- /var/lib/dhcpcd-"${INTERFACE}".info "${x}" +set -- /var/lib/dhcpcd-"$INTERFACE".info "$x" diff --git a/dhcpcd-hooks/50-ntp.conf b/dhcpcd-hooks/50-ntp.conf index 8c92f27..765baa7 100644 --- a/dhcpcd-hooks/50-ntp.conf +++ b/dhcpcd-hooks/50-ntp.conf @@ -8,68 +8,76 @@ # NTP_CONF=/usr/pkg/etc/ntpd.conf # to use openntpd from pkgsrc instead of the system provided ntp. -# Detect OpenRC or BSD rc -# Distributions may want to just have their command here instead of this -if type rc-service >/dev/null 2>&1 && rc-service --exists ntpd; then - ntpd_restart_cmd="rc-service ntpd -- --ifstarted --quiet restart" -elif [ -x /etc/rc.d/ntpd ]; then - ntpd_restart_cmd="/etc/rc.d/ntpd status && /etc/rc.d/ntpd restart" -elif [ -x /usr/local/etc/rc.d/ntpd ]; then - ntpd_restart_cmd="/usr/local/etc/rc.d/ntpd status && /usr/local/etc/rc.d/ntpd restart" +: ${ntpd_restart_cmd:=service_condcommand ntpd restart} +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_DHCP_CONF:=/var/lib/ntp/ntp.conf.dhcp} fi -ntp_conf_dir="${state_dir}/ntp.conf" +ntp_conf_dir="$state_dir/ntp.conf" ntp_conf=${NTP_CONF:-/etc/ntp.conf} build_ntp_conf() { - local cf="${ntp_conf}.${interface}" + local cf="$state_dir/ntp.conf.$interface" local interfaces= header= srvs= servers= x= # Build a list of interfaces - interfaces=$(list_interfaces "${ntp_conf_dir}") + interfaces=$(list_interfaces "$ntp_conf_dir") - if [ -n "${interfaces}" ]; then + if [ -n "$interfaces" ]; then # Build the header for x in ${interfaces}; do - header="${header}${header:+, }${x}" + 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}\n" + srvs=$(cd "$ntp_conf_dir"; + key_get_value "server " $interfaces) + if [ -n "$srvs" ]; then + for x in $(uniqify $srvs); do + servers="${servers}server $x\n" done fi fi # Merge our config into ntp.conf - [ -e "${cf}" ] && rm -f "${cf}" - remove_markers "${signature_base}" "${signature_base_end}" \ - /etc/ntp.conf > "${cf}" - if [ -n "${servers}" ]; then - echo "${signature_base}${header:+ ${from} }${header}" >> "${cf}" - printf "${search}${servers}" >> "${cf}" - echo "${signature_base_end}${header:+ ${from} }${header}" >> "${cf}" + [ -e "$cf" ] && rm -f "$cf" + [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" + + if [ -n "$NTP_DHCP_CONF" ]; then + 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 "$search$servers" >> "$cf" + echo "$signature_base_end${header:+ $from }$header" >> "$cf" + else + [ -e "$ntp_conf" ] || return fi # If we changed anything, restart ntpd - if change_file "${ntp_conf}" "${cf}"; then - [ -n "${ntpd_restart_cmd}" ] && eval ${ntpd_restart_cmd} + if change_file "$ntp_conf" "$cf"; then + [ -n "$ntpd_restart_cmd" ] && eval $ntpd_restart_cmd fi } add_ntp_conf() { - local cf="${ntp_conf_dir}/${interface}" x= + local cf="$ntp_conf_dir/$interface" 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}" + [ -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 @@ -77,13 +85,14 @@ add_ntp_conf() remove_ntp_conf() { - if [ -e "${ntp_conf_dir}/${interface}" ]; then - rm "${ntp_conf_dir}/${interface}" + if [ -e "$ntp_conf_dir/$interface" ]; then + rm "$ntp_conf_dir/$interface" fi build_ntp_conf } -case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) add_ntp_conf add;; -PREINIT|EXPIRE|FAIL|IPV4LL|RELEASE|STOP) remove_ntp_conf del;; -esac +if $if_up; then + add_ntp_conf add +elif $if_down; then + remove_ntp_conf del +fi diff --git a/dhcpcd-hooks/50-yp.conf b/dhcpcd-hooks/50-yp.conf index a2296eb..a1f5798 100644 --- a/dhcpcd-hooks/50-yp.conf +++ b/dhcpcd-hooks/50-yp.conf @@ -8,42 +8,44 @@ ypbind_pid() make_yp_conf() { - [ -z "${new_nis_domain}" -a -z "${new_nis_servers}" ] && return 0 - local cf=/etc/yp.conf."${interface}" prefix= x= pid= - rm -f "${cf}" - echo "${signature}" > "${cf}" - if [ -n "${new_nis_domain}" ]; then - domainname "${new_nis_domain}" - if [ -n "${new_nis_servers}" ]; then - prefix="domain ${new_nis_domain} server " + [ -z "$new_nis_domain" -a -z "$new_nis_servers" ] && return 0 + local cf=/etc/yp.conf."$interface" prefix= x= pid= + rm -f "$cf" + echo "$signature" > "$cf" + if [ -n "$new_nis_domain" ]; then + domainname "$new_nis_domain" + if [ -n "$new_nis_servers" ]; then + prefix="domain $new_nis_domain server " else - echo "domain ${new_nis_domain} broadcast" >> "${cf}" + echo "domain $new_nis_domain broadcast" >> "$cf" fi else prefix="ypserver " fi - for x in ${new_nis_servers}; do - echo "${prefix}${x}" >> "${cf}" + for x in $new_nis_servers; do + echo "$prefix$x" >> "$cf" done save_conf /etc/yp.conf - mv -f "${cf}" /etc/yp.conf + cat "$cf" > /etc/yp.conf + rm -f "$cf" pid="$(ypbind_pid)" - if [ -n "${pid}" ]; then - kill -HUP "${pid}" + if [ -n "$pid" ]; then + kill -HUP "$pid" fi } restore_yp_conf() { - [ -n "${old_nis_domain}" ] && domainname "" + [ -n "$old_nis_domain" ] && domainname "" restore_conf /etc/yp.conf || return 0 local pid="$(ypbind_pid)" - if [ -n "${pid}" ]; then - kill -HUP "${pid}" + if [ -n "$pid" ]; then + kill -HUP "$pid" fi } -case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) make_yp_conf;; -EXPIRE|FAIL|IPV4LL|RELEASE|STOP) restore_yp_conf;; -esac +if $if_up; then + make_yp_conf +elif $if_down; then + restore_yp_conf +fi diff --git a/dhcpcd-hooks/50-ypbind b/dhcpcd-hooks/50-ypbind new file mode 100644 index 0000000..9a1fecc --- /dev/null +++ b/dhcpcd-hooks/50-ypbind @@ -0,0 +1,74 @@ +# Sample dhcpcd hook for ypbind +# This script is only suitable for the Linux version. + +: ${ypbind_restart_cmd:=service_command ypbind restart} +: ${ypbind_stop_cmd:=service_condcommand ypbind stop} +ypbind_dir="$state_dir/ypbind" + +best_domain() +{ + local i= + + for i in $interfaces; do + if [ -e "$ypbind_dir/$i" ]; then + cat "$ypbind_dir/$i" + fi + done + return 1 +} + +make_yp_binding() +{ + [ -d "$ypbind_dir" ] || mkdir -p "$ypbind_dir" + echo "$new_nis_domain" >"$ypbind_dir/$interface" + local nd="$(best_domain)" + + local cf=/var/yp/binding/"$new_nis_domain".ypservers + if [ -n "$new_nis_servers" ]; then + local ncf="$cf.$interface" x= + rm -f "$ncf" + for x in $new_nis_servers; do + echo "$x" >>"$ncf" + done + change_file "$cf" "$ncf" + else + # Because this is not an if .. fi then we can use $? below + [ -e "$cf" ] && rm "$cf" + fi + + if [ $? = 0 -o "$nd" != "$(domainname)" ]; then + domainname "$nd" + if [ -n "$ypbind_restart_cmd" ]; then + eval $ypbind_restart_cmd + fi + fi +} + +restore_yp_binding() +{ + rm -f "$ypbind_dir/$interface" + 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 + make_yp_binding + elif [ -n "$old_nis_domain" ]; then + restore_yp_binding + fi +fi diff --git a/dhcpcd-hooks/Makefile b/dhcpcd-hooks/Makefile index cfb19f7..7563d2d 100644 --- a/dhcpcd-hooks/Makefile +++ b/dhcpcd-hooks/Makefile @@ -1,13 +1,19 @@ -LIBEXECDIR?= ${PREFIX}/libexec -HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks -SYSTEMSCRIPTS= 01-test 10-mtu 20-resolv.conf 30-hostname -FILES= ${SYSTEMSCRIPTS} ${HOOKSCRIPTS} -FILESDIR= ${HOOKDIR} +TOP?= ../ +include ${TOP}/Makefile.inc +include ${TOP}/config.mk + +SCRIPTSDIR= ${LIBEXECDIR}/dhcpcd-hooks +SCRIPTS= 01-test 02-dump +SCRIPTS+= 10-mtu 20-resolv.conf 29-lookup-hostname 30-hostname +SCRIPTS+= ${HOOKSCRIPTS} all: -MK= ../mk -include ${MK}/os.mk -include ${MK}/sys.mk -include ${MK}/files.mk -install: _filesinstall +install: + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR} + +import: + ${INSTALL} -d /tmp/${DISTPREFIX}/dhcpcd-hooks + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} /tmp/${DISTPREFIX}/dhcpcd-hooks + diff --git a/dhcpcd-run-hooks.8 b/dhcpcd-run-hooks.8 index a6a1849..882b7d5 100644 --- a/dhcpcd-run-hooks.8 +++ b/dhcpcd-run-hooks.8 @@ -1,4 +1,4 @@ -.\" Copyright 2006-2008 Roy Marples +.\" Copyright (c) 2006-2010 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,22 +22,23 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 14, 2008 -.Dt DHCPCD.SH 8 SMM +.Dd August 24, 2010 +.Dt DHCPCD-RUN-HOOKS 8 SMM +.Os .Sh NAME .Nm dhcpcd-run-hooks -.Nd DHCP client configuration script +.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 /system/etc/dhcpcd/dhcpcd-hooks -and the user defined hooks are -.Pa /system/etc/dhcpcd/dhcpcd.enter-hook . +.Pa /libexec/dhcpcd-hooks +and the user defined hooks are +.Pa /etc/dhcpcd.enter-hook . and -.Pa /system/etc/dhcpcd/dhcpcd.exit-hook . +.Pa /etc/dhcpcd.exit-hook . The default install supplies hook scripts for configuring .Pa /etc/resolv.conf and the hostname. @@ -68,9 +69,12 @@ argument. Here's a list of reasons why .Nm could be invoked: -.Bl -tag -width indent +.Bl -tag -width PREINIT .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 INFORM dhcpcd informed a DHCP server about it's address and obtained other configuration details. @@ -82,32 +86,52 @@ dhcpcd renewed it's lease. dhcpcd has rebound to a new DHCP server. .It Dv REBOOT dhcpcd successfully requested a lease from a DHCP server. -.It Dv EXPIRE -dhcpcd's lease expired and it failed to obtain a new one. .It Dv IPV4LL dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. -.It Dv FAIL -dhcpcd failed to contact any DHCP servers or use an old lease. +.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 +dhcpcd's lease or state expired and it failed to obtain a new one. +.It Dv RELEASE +dhcpcd's lease was released back to the DHCP server for re-use. +.It Dv NAK +dhcpcd received a NAK from the DHCP server. +This should be treated as EXPIRE. +.It Dv NOCARRIER +dhcpcd lost the carrier. +The cable may have been unplugged or association to the wireless point lost. +.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 STOP +dhcpcd stopped running on the interface. +.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 FILES +.Sh FILES When .Nm runs, it loads -.Pa /system/etc/dhcpcd/dhcpcd.enter-hook +.Pa /etc/dhcpcd.enter-hook and any scripts found in -.Pa /system/etc/dhcpcd/dhcpcd-hooks +.Pa /libexec/dhcpcd-hooks in a lexical order and then finally -.Pa /system/etc/dhcpcd/dhcpcd.exit-hook +.Pa /etc/dhcpcd.exit-hook .Sh SEE ALSO .Xr dhcpcd 8 .Sh AUTHORS -.An Roy Marples +.An Roy Marples Aq roy@marples.name .Sh BUGS Please report them to http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd-run-hooks.8.in b/dhcpcd-run-hooks.8.in index 6776cf8..bcfc81d 100644 --- a/dhcpcd-run-hooks.8.in +++ b/dhcpcd-run-hooks.8.in @@ -1,4 +1,4 @@ -.\" Copyright 2006-2008 Roy Marples +.\" Copyright (c) 2006-2010 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,12 +22,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 14, 2008 -.Dt DHCPCD.SH 8 SMM +.Dd August 24, 2010 +.Dt DHCPCD-RUN-HOOKS 8 SMM .Os .Sh NAME .Nm dhcpcd-run-hooks -.Nd DHCP client configuration script +.Nd DHCP client configuration script .Sh DESCRIPTION .Nm is used by @@ -35,7 +35,7 @@ is used by to run any system and user defined hook scripts. System hook scripts are found in .Pa @HOOKDIR@ -and the user defined hooks are +and the user defined hooks are .Pa @SYSCONFDIR@/dhcpcd.enter-hook . and .Pa @SYSCONFDIR@/dhcpcd.exit-hook . @@ -69,9 +69,12 @@ argument. Here's a list of reasons why .Nm could be invoked: -.Bl -tag -width indent +.Bl -tag -width PREINIT .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 INFORM dhcpcd informed a DHCP server about it's address and obtained other configuration details. @@ -83,21 +86,41 @@ dhcpcd renewed it's lease. dhcpcd has rebound to a new DHCP server. .It Dv REBOOT dhcpcd successfully requested a lease from a DHCP server. -.It Dv EXPIRE -dhcpcd's lease expired and it failed to obtain a new one. .It Dv IPV4LL dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. -.It Dv FAIL -dhcpcd failed to contact any DHCP servers or use an old lease. +.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 +dhcpcd's lease or state expired and it failed to obtain a new one. +.It Dv RELEASE +dhcpcd's lease was released back to the DHCP server for re-use. +.It Dv NAK +dhcpcd received a NAK from the DHCP server. +This should be treated as EXPIRE. +.It Dv NOCARRIER +dhcpcd lost the carrier. +The cable may have been unplugged or association to the wireless point lost. +.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 STOP +dhcpcd stopped running on the interface. +.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 FILES +.Sh FILES When .Nm runs, it loads @@ -109,6 +132,6 @@ in a lexical order and then finally .Sh SEE ALSO .Xr dhcpcd 8 .Sh AUTHORS -.An Roy Marples +.An Roy Marples Aq roy@marples.name .Sh BUGS Please report them to http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd-run-hooks.in b/dhcpcd-run-hooks.in index a848260..cb897b4 100644 --- a/dhcpcd-run-hooks.in +++ b/dhcpcd-run-hooks.in @@ -2,38 +2,54 @@ # dhcpcd client configuration script # Handy variables and functions for our hooks to use -from="from" +from=from signature_base="# Generated by dhcpcd" -signature="${signature_base} ${from} ${interface}" +signature="$signature_base $from $interface" signature_base_end="# End of dhcpcd" -signature_end="${signature_base_end} ${from} ${interface}" -state_dir="/var/run/dhcpcd" +signature_end="$signature_base_end $from $interface" +state_dir=/var/run/dhcpcd + +if_up=false +if_down=false +case "$reason" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT|STATIC) if_up=true;; +PREINIT|EXPIRE|FAIL|IPV4LL|NAK|NOCARRIER|RELEASE|STOP) if_down=true;; +esac # Ensure that all arguments are unique uniqify() { - local result= - - while [ -n "$1" ]; do - case " ${result} " in - *" $1 "*);; - *) result="${result}${result:+ }$1";; + local result= i= + for i; do + case " $result " in + *" $i "*);; + *) result="$result $i";; esac - shift done - echo "${result}" + echo "${result# *}" } -# List interface config files in a dir -# We may wish to control the order at some point rather than just lexical +# 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 x= interfaces= + local i= x= ifaces= + for i in $interface_order; do + [ -e "$1/$i" ] && ifaces="$ifaces${ifaces:+ }$i" + done for x in "$1"/*; do - [ -e "${x}" ] || continue - interfaces="${interfaces}${interfaces:+ }${x##*/}" + [ -e "$x" ] || continue + for i in $interface_order; do + if [ $i = "${x##*/}" ]; then + unset x + break + fi + done + [ -n "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}" done - echo "${interfaces}" + echo "$ifaces" } # We normally use sed to extract values using a key from a list of files @@ -44,14 +60,14 @@ key_get_value() shift if type sed >/dev/null 2>&1; then - sed -n "s/^${key}//p" $@ + sed -n "s/^$key//p" $@ else for x; do while read line; do - case "${line}" in - "${key}"*) echo "${line##${key}}";; + case "$line" in + "$key"*) echo "${line##$key}";; esac - done < "${x}" + done < "$x" done fi } @@ -64,37 +80,40 @@ remove_markers() shift; shift if type sed >/dev/null 2>&1; then - sed "/^${m1}/,/^${m2}/d" $@ + 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}";; + case "$line" in + "$m1"*) in_marker=1;; + "$m2"*) in_marker=0;; + *) [ $in_marker = 0 ] && echo "$line";; esac - done < "${x}" + done < "$x" done fi } -# Compare two files -# If different, replace first with second otherwise remove second +# Compare two files. +# If different, replace first with second otherwise remove second. change_file() { - 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 - if [ $? -eq 0 ]; then - rm -f "$2" - return 1 + if [ -e "$1" ]; then + 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 + if [ $? -eq 0 ]; then + rm -f "$2" + return 1 + fi fi - mv -f "$2" "$1" + cat "$2" > "$1" + rm -f "$2" return 0 } @@ -102,19 +121,59 @@ change_file() save_conf() { if [ -f "$1" ]; then - rm -f "$1"-pre."${interface}" - mv -f "$1" "$1"-pre."${interface}" + rm -f "$1-pre.$interface" + cat "$1" > "$1-pre.$interface" fi } # Restore a config file restore_conf() { - [ -f "$1"-pre."${interface}" ] || return 1 - rm -f "$1" - mv -f "$1"-pre."${interface}" "$1" + [ -f "$1-pre.$interface" ] || return 1 + cat "$1-pre.$interface" > "$1" + rm -f "$1-pre.$interface" +} + +# Write a syslog entry +syslog() +{ + local lvl="$1" + + [ -n "$lvl" ] && shift + if [ -n "$*" ]; then + if type logger >/dev/null 2>&1; then + logger -t dhcpcd -p daemon."$lvl" -s "$*" + fi + fi +} + +# Check a system service exists +service_exists() +{ + @SERVICEEXISTS@ +} + +# Send a command to a system service +service_cmd() +{ + @SERVICECMD@ +} + +# Send a command to a system service if it is running +service_status() +{ + @SERVICESTATUS@ } +# 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. @@ -125,14 +184,14 @@ for hook in \ @HOOKDIR@/* \ @SYSCONFDIR@/dhcpcd.exit-hook do - for skip in ${skip_hooks}; do - case "${hook}" in - */"${skip}") continue 2;; - */[0-9][0-9]"-${skip}") continue 2;; - */[0-9][0-9]"-${skip}.sh") continue 2;; + for skip in $skip_hooks; do + case "$hook" in + */"$skip") continue 2;; + */[0-9][0-9]"-$skip") continue 2;; + */[0-9][0-9]"-$skip.sh") continue 2;; esac done - if [ -f "${hook}" ]; then - . "${hook}" + if [ -f "$hook" ]; then + . "$hook" fi done diff --git a/dhcpcd.8 b/dhcpcd.8 index ac6150c..2acfb90 100644 --- a/dhcpcd.8 +++ b/dhcpcd.8 @@ -1,4 +1,4 @@ -.\" Copyright 2006-2008 Roy Marples +.\" Copyright (c) 2006-2010 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,15 +22,17 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 20, 2008 +.Dd August 31, 2010 .Dt DHCPCD 8 SMM +.Os .Sh NAME .Nm dhcpcd .Nd an RFC 2131 compliant DHCP client .Sh SYNOPSIS .Nm -.Op Fl bdknpqABDEGKLSTV +.Op Fl bdgknpqwABDEGHJKLTV .Op Fl c , -script Ar script +.Op Fl e , -env Ar value .Op Fl f , -config Ar file .Op Fl h , -hostname Ar hostname .Op Fl i , -vendorclassid Ar vendorclassid @@ -42,43 +44,61 @@ .Op Fl t , -timeout Ar seconds .Op Fl u , -userclass Ar class .Op Fl v , -vendor Ar code , Ar value +.Op Fl y , -reboot Ar seconds +.Op Fl z , -allowinterfaces Ar pattern .Op Fl C , -nohook Ar hook .Op Fl F , -fqdn Ar FQDN .Op Fl I , -clientid Ar clientid .Op Fl O , -nooption Ar option .Op Fl Q , -require Ar option -.Op Fl X , -blacklist Ar address -.Ar interface +.Op Fl S , -static Ar value +.Op Fl W , -whitelist Ar address Ns Op Ar /cidr +.Op Fl X , -blacklist Ar address Ns Op Ar /cidr +.Op Fl Z , -denyinterfaces Ar pattern +.Op interface +.Op ... .Nm .Fl k , -release +.Op interface +.Nm +.Fl U, -dumplease .Ar interface .Nm .Fl x , -exit -.Ar interface +.Op interface +.Nm +.Fl v , -version .Sh DESCRIPTION .Nm is an implementation of the DHCP client specified in .Li RFC 2131 . .Nm gets the host information -.Po +.Po IP address, routes, etc .Pc from a DHCP server and configures the network .Ar interface of the -machine on which it is running. +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 currenly blank, (null) or localhost then +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. -Then it attempts to renew its lease and reconfigure if the new lease changes. +It will then attempt to renew its lease and reconfigure if the new lease +changes. +.Pp +.Nm +is also an implementation of the BOOTP client specified in +.Li RFC 951 . .Ss Local Link configuration If .Nm @@ -97,15 +117,47 @@ installed which always defeats IPv4LL probing. To disable this behaviour, you can use the .Fl L , -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. +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 , -background +and +.Fl w , -waitip +options. +.Pp +If a single interface is given then +.Nm +only works for that interface and runs as a separate instance. +The +.Fl w , -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 DHCP events .Nm runs -.Pa /system/etc/dhcpcd/dhcpcd-run-hooks , +.Pa /libexec/dhcpcd-run-hooks , or the script specified by the .Fl c , -script option. This script runs each script found in -.Pa /system/etc/dhcpcd/dhcpcd-hooks +.Pa /libexec/dhcpcd-hooks in a lexical order. The default installation supplies the scripts .Pa 01-test , @@ -122,7 +174,7 @@ 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 +You can fine-tune the behaviour of .Nm with the following options: .Bl -tag -width indent @@ -134,27 +186,44 @@ carrier status. Use this .Ar script instead of the default -.Pa /system/etc/dhcpcd/dhcpcd-run-hooks . +.Pa /libexec/dhcpcd-run-hooks . .It Fl d , -debug -Echo debug and informational messages to the console. -Subsequent debug options stop +Echo debug messages to the stderr and syslog. +.It Fl e , -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 , -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 -from daemonising. +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 , -config Ar file Specify a config to load instead of -.Pa /system/etc/dhcpcd/dhcpcd.conf . +.Pa /etc/dhcpcd.conf . .Nm always processes the config file before any command line options. .It Fl h , -hostname Ar hostname -By default, -.Nm -sends the current hostname to the DHCP server so it can register in DNS. -You can use this option to specify the +Sends .Ar hostname -sent, or an empty string to -stop any +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 -from being sent. +is a FQDN (ie, contains a .) then it will be encoded as such. .It Fl i , -vendorclassid Ar vendorclassid Override the .Ar vendorclassid @@ -166,7 +235,7 @@ This causes an existing .Nm process running on the .Ar interface -to release its lease, deconfigure the +to release its lease, de-configure the .Ar interface and then exit. .Nm @@ -176,61 +245,45 @@ Request a specific lease time in .Ar seconds . By default .Nm -does not request any lease time and leaves the it in the hands of the +does not request any lease time and leaves it in the hands of the DHCP server. .It Fl m , -metric Ar metric -Added routes will use the -.Ar metric -on systems where this is supported -.Po -presently only Linux -.Pc . -Route metrics allow the addition of routes to the same destination across -different interfaces, the lower the metric the more it is preferred. +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 o , -option Ar option Request the DHCP .Ar option variable for use in -.Pa /system/etc/dhcpcd/dhcpcd-run-hooks . +.Pa /libexec/dhcpcd-run-hooks . .It Fl n , -rebind -Notifies an existing -.Nm -process running on the -.Ar interface -to rebind it's lease. +Notifies .Nm -will not re-configure itself or use any other command line arguments. -.Nm -will timeout the rebind after 30 seconds at which point the lease will be -expired and -.Nm -will enter the discovery state to obtain a new lease. -Use the -.Fl t , -timeout -option to change this. +to reload its configuration and rebind its interfaces. If .Nm is not running, then it starts up as normal. -This option used to be renew, but rebind is more accurate as we need to -broadcast the request instead of unicasting. .It Fl p , -persistent .Nm -normally deconfigures the +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. +Sometimes, this isn't desirable if, for example, you have root mounted over +NFS. You can use this option to stop this from happening. .It Fl r , -request Op Ar address .Nm normally sends a DHCP DISCOVER to find servers to offer an address. .Nm then requests the address used. -You can use this option to skip the BROADCAST step and just request the +You can use this option to skip the DISCOVER phase and just request the .Ar address . The downside is if you request an .Ar address the DHCP server does not know about or the DHCP server is not -authorative, it will remain silent. +authoritative, it will remain silent. In this situation, we go back to the init state and DISCOVER again. If no .Ar address @@ -246,7 +299,7 @@ This does not get a lease as such, just notifies the DHCP server of the in use. You should also include the optional .Ar cidr -network number in-case the address is not already configured on the interface. +network number in case the address is not already configured on the interface. .Nm remains running and pretends it has an infinite lease. .Nm @@ -267,39 +320,58 @@ to wait forever to get a lease. .It Fl u , -userclass Ar class Tags the DHCP message with the userclass .Ar class . -DHCP servers use this give members of the class DHCP options other than the +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 , -vendor Ar code , Ns Ar value -Add an enscapulated vendor option. +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 -Do the above and set a third option with a string and not an IP address. -.D1 dhcpcd \-v 01,192.168.0.2 \-v 02,01:02:03:04:05 \-v 03,\e"192.168.0.2\e" 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 v , -version +Display both program version and copyright information. +.Nm +then exits before doing any configuration. +.It Fl w , -waitip +Wait for an address to be assigned before forking to the background. .It Fl x , -exit This will signal an existing .Nm process running on the .Ar interface -to deconfigure the +to de-configure the .Ar interface and exit. .Nm then waits until this process has exited. -.It Fl D , -duid +.It Fl y , -reboot Ar seconds +Allow +.Ar reboot +seconds before moving to the discover phase if we have an old lease to use. +The default is 10 seconds. +A setting of 0 seconds causes +.Nm +to skip the reboot phase and go straight into discover. +.It Fl D , -duid Generate an .Li RFC 4361 compliant clientid. -This requires persistent storage and not all DHCP servers work with it so it's -not enabled by default. +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 in it -.Pa /system/etc/dhcpcd/dhcpcd.duid +generates the DUID and stores it in +.Pa /etc/dhcpcd.duid . This file should not be copied to other hosts. .It Fl E , -lastlease If @@ -315,20 +387,24 @@ hostname. Valid values for .Ar fqdn are disable, none, ptr and both. -The current hostname or the hostname specified using the -.Fl h , -hostname -option must be a FQDN. .Nm itself never does any DNS updates. .Nm encodes the FQDN hostname as specified in .Li RFC1035 . .It Fl I , -clientid Ar clientid -Change the default clientid sent from the interface hardware address. +Send the +.Ar clientid . If the string is of the format 01:02:03 then it is encoded as hex. -If not set then none is sent. +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. .El -.Ss Restriciting behaviour +.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 @@ -340,6 +416,14 @@ Quiet .Nm on the command line, only warnings and errors will be displayed. The messages are still logged though. +.It Fl z , -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 , -denyinterfaces +then it is still denied. .It Fl A , -noarp Don't request or claim the address by ARP. This also disables IPv4LL. @@ -352,10 +436,22 @@ Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with .Pa .sh . .Pp -So to stop dhcpcd from touching your DNS or MTU settings you would do:- +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 , -nogateway Don't set any default routes. +.It Fl H , -xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. +.It Fl J , -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 , -nolink Don't receive link messages for carrier status. You should only have to use this with buggy device drivers or running @@ -371,45 +467,102 @@ configure the interface and routing. 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 S, -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, -test -On receipt of OFFER messages just call -.Pa /system/etc/dhcpcd/dhcpcd-run-hooks -with the reason of TEST which echo's the DHCP variables found in the message +On receipt of DHCP messages just call +.Pa /libexec/dhcpcd-run-hooks +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. +To test INFORM the interface needs to be configured with the desired address +before starting +.Nm . +.It Fl U, -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. .It Fl V, -variables Display a list of option codes and the associated variable for use in .Xr dhcpcd-run-hooks 8 . -.It Fl X, -blacklist Ar address -Ignores all DHCP messages which have this -.Ar address -as the server ID. -This may be expanded in future releases to ignore all packets -matching either the IP or hardware -.Ar address . +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, -whitelist Ar address Ns Op /cidr +Only accept packets from +.Ar address Ns Op /cidr . +.Fl X, -blacklist +is ignored if +.Fl W, -whitelist +is set. +.It Fl X, -blacklist Ar address Ns Op Ar /cidr +Ignore all packets from +.Ar address Ns Op Ar /cidr . +.It Fl Z , -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 . .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 if 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. .Sh FILES .Bl -ohang -.It Pa /system/etc/dhcpcd/dhcpcd.conf +.It Pa /etc/dhcpcd.conf Configuration file for dhcpcd. If you always use the same options, put them here. -.It Pa /system/etc/dhcpcd/dhcpcd.duid +.It Pa /etc/dhcpcd.duid Text file that holds the DUID used to identify the host. -.It Pa /system/etc/dhcpcd/dhcpcd-run-hooks -Bourne shell script that is run to configure or deconfigure an interface. -.It Pa /system/etc/dhcpcd/dhcpcd-hooks +.It Pa /libexec/dhcpcd-run-hooks +Bourne shell script that is run to configure or de-configure an interface. +.It Pa /libexec/dhcpcd-hooks A directory containing bourne shell scripts that are run by the above script. Each script can be disabled by using the .Fl C , -nohook option described above. -.It Pa /data/misc/dhcp/dhcpcd\- Ns Ar interface Ns .lease +.It Pa /var/db/dhcpcd\- Ns Ar interface Ns .lease The actual DHCP message send by the server. We use this when reading the last lease and use the files mtime as when it was issued. +.It Pa /var/run/dhcpcd.pid +Stores the PID of +.Nm +running on all interfaces. .It Pa /var/run/dhcpcd\- Ns Ar interface Ns .pid Stores the PID of .Nm @@ -421,10 +574,12 @@ running on the .Xr dhcpcd-run-hooks 8 , .Xr resolv.conf 5 , .Xr resolvconf 8 , +.Xr if_nametoindex 3 , +.Xr fnmatch 3 .Sh STANDARDS -RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, RFC 3397, -RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. +RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, +RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. .Sh AUTHORS -.An Roy Marples +.An Roy Marples Aq roy@marples.name .Sh BUGS -Please report them to http://bugs.marples.name +Please report them to http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd.8.in b/dhcpcd.8.in index d3fbebf..5993878 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -1,4 +1,4 @@ -.\" Copyright 2006-2008 Roy Marples +.\" Copyright (c) 2006-2010 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd November 18, 2008 +.Dd August 31, 2010 .Dt DHCPCD 8 SMM .Os .Sh NAME @@ -30,8 +30,9 @@ .Nd an RFC 2131 compliant DHCP client .Sh SYNOPSIS .Nm -.Op Fl bdknpqABDEGKLTV +.Op Fl bdgknpqwABDEGHJKLTV .Op Fl c , -script Ar script +.Op Fl e , -env Ar value .Op Fl f , -config Ar file .Op Fl h , -hostname Ar hostname .Op Fl i , -vendorclassid Ar vendorclassid @@ -43,43 +44,61 @@ .Op Fl t , -timeout Ar seconds .Op Fl u , -userclass Ar class .Op Fl v , -vendor Ar code , Ar value +.Op Fl y , -reboot Ar seconds +.Op Fl z , -allowinterfaces Ar pattern .Op Fl C , -nohook Ar hook .Op Fl F , -fqdn Ar FQDN .Op Fl I , -clientid Ar clientid .Op Fl O , -nooption Ar option .Op Fl Q , -require Ar option -.Op Fl X , -blacklist Ar address -.Ar interface +.Op Fl S , -static Ar value +.Op Fl W , -whitelist Ar address Ns Op Ar /cidr +.Op Fl X , -blacklist Ar address Ns Op Ar /cidr +.Op Fl Z , -denyinterfaces Ar pattern +.Op interface +.Op ... .Nm .Fl k , -release +.Op interface +.Nm +.Fl U, -dumplease .Ar interface .Nm .Fl x , -exit -.Ar interface +.Op interface +.Nm +.Fl v , -version .Sh DESCRIPTION .Nm is an implementation of the DHCP client specified in .Li RFC 2131 . .Nm gets the host information -.Po +.Po IP address, routes, etc .Pc from a DHCP server and configures the network .Ar interface of the -machine on which it is running. +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 currenly blank, (null) or localhost then +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. -Then it attempts to renew its lease and reconfigure if the new lease changes. +It will then attempt to renew its lease and reconfigure if the new lease +changes. +.Pp +.Nm +is also an implementation of the BOOTP client specified in +.Li RFC 951 . .Ss Local Link configuration If .Nm @@ -98,6 +117,38 @@ installed which always defeats IPv4LL probing. To disable this behaviour, you can use the .Fl L , -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. +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 , -background +and +.Fl w , -waitip +options. +.Pp +If a single interface is given then +.Nm +only works for that interface and runs as a separate instance. +The +.Fl w , -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 DHCP events .Nm runs @@ -123,7 +174,7 @@ 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 +You can fine-tune the behaviour of .Nm with the following options: .Bl -tag -width indent @@ -137,25 +188,42 @@ Use this instead of the default .Pa @SCRIPT@ . .It Fl d , -debug -Echo debug and informational messages to the console. -Subsequent debug options stop +Echo debug messages to the stderr and syslog. +.It Fl e , -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 , -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 -from daemonising. +does not read a new configuration when this happens - you should rebind if you +need that functionality. .It Fl f , -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 , -hostname Ar hostname -By default, -.Nm -sends the current hostname to the DHCP server so it can register in DNS. -You can use this option to specify the +Sends +.Ar hostname +to the DHCP server so it can be registered in DNS. +If .Ar hostname -sent, or an empty string to -stop any +is an empty string then the current system hostname is sent. +If .Ar hostname -from being sent. +is a FQDN (ie, contains a .) then it will be encoded as such. .It Fl i , -vendorclassid Ar vendorclassid Override the .Ar vendorclassid @@ -167,7 +235,7 @@ This causes an existing .Nm process running on the .Ar interface -to release its lease, deconfigure the +to release its lease, de-configure the .Ar interface and then exit. .Nm @@ -177,61 +245,45 @@ Request a specific lease time in .Ar seconds . By default .Nm -does not request any lease time and leaves the it in the hands of the +does not request any lease time and leaves it in the hands of the DHCP server. .It Fl m , -metric Ar metric -Added routes will use the -.Ar metric -on systems where this is supported -.Po -presently only Linux -.Pc . -Route metrics allow the addition of routes to the same destination across -different interfaces, the lower the metric the more it is preferred. +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 o , -option Ar option Request the DHCP .Ar option variable for use in .Pa @SCRIPT@ . .It Fl n , -rebind -Notifies an existing +Notifies .Nm -process running on the -.Ar interface -to rebind it's lease. -.Nm -will not re-configure itself or use any other command line arguments. -.Nm -will timeout the rebind after 30 seconds at which point the lease will be -expired and -.Nm -will enter the discovery state to obtain a new lease. -Use the -.Fl t , -timeout -option to change this. +to reload its configuration and rebind its interfaces. If .Nm is not running, then it starts up as normal. -This option used to be renew, but rebind is more accurate as we need to -broadcast the request instead of unicasting. .It Fl p , -persistent .Nm -normally deconfigures the +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. +Sometimes, this isn't desirable if, for example, you have root mounted over +NFS. You can use this option to stop this from happening. .It Fl r , -request Op Ar address .Nm normally sends a DHCP DISCOVER to find servers to offer an address. .Nm then requests the address used. -You can use this option to skip the BROADCAST step and just request the +You can use this option to skip the DISCOVER phase and just request the .Ar address . The downside is if you request an .Ar address the DHCP server does not know about or the DHCP server is not -authorative, it will remain silent. +authoritative, it will remain silent. In this situation, we go back to the init state and DISCOVER again. If no .Ar address @@ -247,7 +299,7 @@ This does not get a lease as such, just notifies the DHCP server of the in use. You should also include the optional .Ar cidr -network number in-case the address is not already configured on the interface. +network number in case the address is not already configured on the interface. .Nm remains running and pretends it has an infinite lease. .Nm @@ -268,39 +320,58 @@ to wait forever to get a lease. .It Fl u , -userclass Ar class Tags the DHCP message with the userclass .Ar class . -DHCP servers use this give members of the class DHCP options other than the +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 , -vendor Ar code , Ns Ar value -Add an enscapulated vendor option. +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 -Do the above and set a third option with a string and not an IP address. -.D1 dhcpcd \-v 01,192.168.0.2 \-v 02,01:02:03:04:05 \-v 03,\e"192.168.0.2\e" 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 v , -version +Display both program version and copyright information. +.Nm +then exits before doing any configuration. +.It Fl w , -waitip +Wait for an address to be assigned before forking to the background. .It Fl x , -exit This will signal an existing .Nm process running on the .Ar interface -to deconfigure the +to de-configure the .Ar interface and exit. .Nm then waits until this process has exited. -.It Fl D , -duid +.It Fl y , -reboot Ar seconds +Allow +.Ar reboot +seconds before moving to the discover phase if we have an old lease to use. +The default is 10 seconds. +A setting of 0 seconds causes +.Nm +to skip the reboot phase and go straight into discover. +.It Fl D , -duid Generate an .Li RFC 4361 compliant clientid. -This requires persistent storage and not all DHCP servers work with it so it's -not enabled by default. +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 in it -.Pa @SYSCONFDIR@/dhcpcd.duid +generates the DUID and stores it in +.Pa @SYSCONFDIR@/dhcpcd.duid . This file should not be copied to other hosts. .It Fl E , -lastlease If @@ -316,9 +387,6 @@ hostname. Valid values for .Ar fqdn are disable, none, ptr and both. -The current hostname or the hostname specified using the -.Fl h , -hostname -option must be a FQDN. .Nm itself never does any DNS updates. .Nm @@ -348,6 +416,14 @@ Quiet .Nm on the command line, only warnings and errors will be displayed. The messages are still logged though. +.It Fl z , -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 , -denyinterfaces +then it is still denied. .It Fl A , -noarp Don't request or claim the address by ARP. This also disables IPv4LL. @@ -360,10 +436,22 @@ Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with .Pa .sh . .Pp -So to stop dhcpcd from touching your DNS or MTU settings you would do:- +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 , -nogateway Don't set any default routes. +.It Fl H , -xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. +.It Fl J , -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 , -nolink Don't receive link messages for carrier status. You should only have to use this with buggy device drivers or running @@ -379,24 +467,77 @@ configure the interface and routing. 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 S, -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, -test -On receipt of OFFER messages just call +On receipt of DHCP messages just call .Pa @SCRIPT@ -with the reason of TEST which echo's the DHCP variables found in the message +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. +To test INFORM the interface needs to be configured with the desired address +before starting +.Nm . +.It Fl U, -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. .It Fl V, -variables Display a list of option codes and the associated variable for use in .Xr dhcpcd-run-hooks 8 . -.It Fl X, -blacklist Ar address -Ignores all DHCP messages which have this -.Ar address -as the server ID. -This may be expanded in future releases to ignore all packets -matching either the IP or hardware -.Ar address . +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, -whitelist Ar address Ns Op /cidr +Only accept packets from +.Ar address Ns Op /cidr . +.Fl X, -blacklist +is ignored if +.Fl W, -whitelist +is set. +.It Fl X, -blacklist Ar address Ns Op Ar /cidr +Ignore all packets from +.Ar address Ns Op Ar /cidr . +.It Fl Z , -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 . .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 if 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 @@ -409,7 +550,7 @@ 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 @SCRIPT@ -Bourne shell script that is run to configure or deconfigure an interface. +Bourne shell script that is run to configure or de-configure an interface. .It Pa @HOOKDIR@ A directory containing bourne shell scripts that are run by the above script. Each script can be disabled by using the @@ -418,6 +559,10 @@ option described above. .It Pa @DBDIR@/dhcpcd\- Ns Ar interface Ns .lease The actual DHCP message send by the server. We use this when reading the last lease and use the files mtime as when it was issued. +.It Pa /var/run/dhcpcd.pid +Stores the PID of +.Nm +running on all interfaces. .It Pa /var/run/dhcpcd\- Ns Ar interface Ns .pid Stores the PID of .Nm @@ -429,10 +574,12 @@ running on the .Xr dhcpcd-run-hooks 8 , .Xr resolv.conf 5 , .Xr resolvconf 8 , +.Xr if_nametoindex 3 , +.Xr fnmatch 3 .Sh STANDARDS -RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, RFC 3397, -RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. +RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, +RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. .Sh AUTHORS -.An Roy Marples +.An Roy Marples Aq roy@marples.name .Sh BUGS Please report them to http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd.c b/dhcpcd.c index ff95a83..704cbb5 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,123 +25,108 @@ * SUCH DAMAGE. */ -const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; +const char copyright[] = "Copyright (c) 2006-2010 Roy Marples"; #include +#include #include +#include #include +#include #include +#include + +#ifdef __linux__ +# include /* for systems with broken headers */ +# include +#endif #include #include #include +#include #include #include #include #include #include +#include #include #include +#include "arp.h" +#include "bind.h" #include "config.h" -#include "client.h" +#include "common.h" +#include "configure.h" +#include "control.h" #include "dhcpcd.h" -#include "dhcp.h" +#include "duid.h" +#include "eloop.h" +#include "if-options.h" +#include "if-pref.h" +#include "ipv4ll.h" #include "net.h" -#include "logger.h" - -#ifdef ANDROID -#include -#include -#include -#include -#endif - -/* Don't set any optional arguments here so we retain POSIX - * compatibility with getopt */ -#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:" - -static int doversion = 0; -static int dohelp = 0; -static const struct option longopts[] = { - {"background", no_argument, NULL, 'b'}, - {"script", required_argument, NULL, 'c'}, - {"debug", no_argument, NULL, 'd'}, - {"config", required_argument, NULL, 'f'}, - {"hostname", optional_argument, NULL, 'h'}, - {"vendorclassid", optional_argument, NULL, 'i'}, - {"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'}, - {"exit", no_argument, NULL, 'x'}, - {"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'}, - {"clientid", optional_argument, NULL, 'I'}, - {"nolink", no_argument, NULL, 'K'}, - {"noipv4ll", no_argument, NULL, 'L'}, - {"nooption", optional_argument, NULL, 'O'}, - {"require", required_argument, NULL, 'Q'}, - {"test", no_argument, NULL, 'T'}, - {"variables", no_argument, NULL, 'V'}, - {"blacklist", required_argument, NULL, 'X'}, - {"help", no_argument, &dohelp, 1}, - {"version", no_argument, &doversion, 1}, -#ifdef CMDLINE_COMPAT - {"classid", optional_argument, NULL, 'i'}, - {"renew", no_argument, NULL, 'n'}, - {"nohostname", no_argument, NULL, 'H'}, - {"nomtu", no_argument, NULL, 'M'}, - {"nontp", no_argument, NULL, 'N'}, - {"nodns", no_argument, NULL, 'R'}, - {"msscr", no_argument, NULL, 'S'}, - {"nonis", no_argument, NULL, 'Y'}, -#endif - {NULL, 0, NULL, '\0'} +#include "signals.h" + +/* 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 + +int options = 0; +int pidfd = -1; +struct interface *ifaces = NULL; +int ifac = 0; +char **ifav = NULL; +int ifdc = 0; +char **ifdv = NULL; + +static char **margv; +static int margc; +static struct if_options *if_options; +static char **ifv; +static int ifc; +static char *cffile; +static char *pidfile; +static int linkfd = -1; + +struct dhcp_op { + uint8_t value; + const char *name; }; -#ifdef CMDLINE_COMPAT -# define EXTRA_OPTS "HMNRSY" -#endif +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" }, + { 0, NULL } +}; -#ifndef EXTRA_OPTS -# define EXTRA_OPTS -#endif +static void send_release(struct interface *); -static int -atoint(const char *s) +static const char * +get_dhcp_op(uint8_t type) { - char *t; - long n; + const struct dhcp_op *d; - errno = 0; - n = strtol(s, &t, 0); - if ((errno != 0 && n == 0) || s == t || - (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN))) - { - logger(LOG_ERR, "`%s' out of range", s); - return -1; - } - - return (int)n; + for (d = dhcp_ops; d->name; d++) + if (d->value == type) + return d->name; + return NULL; } static pid_t -read_pid(const char *pidfile) +read_pid(void) { FILE *fp; pid_t pid; @@ -150,661 +135,1606 @@ read_pid(const char *pidfile) errno = ENOENT; return 0; } - if (fscanf(fp, "%d", &pid) != 1) pid = 0; fclose(fp); - return pid; } static void usage(void) { - printf("usage: "PACKAGE" [-dknpqxADEGHKLOTV] [-c script] [-f file ] [-h hostname]\n" - " [-i classID ] [-l leasetime] [-m metric] [-o option] [-r ipaddr]\n" - " [-s ipaddr] [-t timeout] [-u userclass] [-F none|ptr|both]\n" - " [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] \n"); + printf("usage: "PACKAGE" [-dgknpqwxyADEGHJKLOTV] [-c script] [-f file]" + " [-e var=val]\n" + " [-h hostname] [-i classID ] [-l leasetime]" + " [-m metric] [-o option]\n" + " [-r ipaddr] [-s ipaddr] [-t timeout]" + " [-u userclass]\n" + " [-F none|ptr|both] [-I clientID] [-C hookscript]" + " [-Q option]\n" + " [-X ipaddr] \n"); +} + +static void +cleanup(void) +{ +#ifdef DEBUG_MEMORY + struct interface *iface; + int i; + + free_options(if_options); + + while (ifaces) { + iface = ifaces; + ifaces = iface->next; + free_interface(iface); + } + + for (i = 0; i < ifac; i++) + free(ifav[i]); + free(ifav); + for (i = 0; i < ifdc; i++) + free(ifdv[i]); + free(ifdv); +#endif + + if (linkfd != -1) + close(linkfd); + if (pidfd > -1) { + if (options & DHCPCD_MASTER) { + if (stop_control() == -1) + syslog(LOG_ERR, "stop_control: %m"); + } + close(pidfd); + unlink(pidfile); + } +#ifdef DEBUG_MEMORY + free(pidfile); +#endif +} + +/* ARGSUSED */ +void +handle_exit_timeout(_unused void *arg) +{ + int timeout; + + syslog(LOG_ERR, "timed out"); + if (!(options & DHCPCD_TIMEOUT_IPV4LL)) { + if (options & DHCPCD_MASTER) { + daemonise(); + return; + } else + exit(EXIT_FAILURE); + } + options &= ~DHCPCD_TIMEOUT_IPV4LL; + timeout = (PROBE_NUM * PROBE_MAX) + PROBE_WAIT + 1; + syslog(LOG_WARNING, "allowing %d seconds for IPv4LL timeout", timeout); + add_timeout_sec(timeout, handle_exit_timeout, NULL); +} + +void +drop_config(struct interface *iface, const char *reason) +{ + free(iface->state->old); + iface->state->old = iface->state->new; + iface->state->new = NULL; + iface->state->reason = reason; + configure(iface); + free(iface->state->old); + iface->state->old = NULL; + iface->state->lease.addr.s_addr = 0; +} + +struct interface * +find_interface(const char *ifname) +{ + struct interface *ifp; + + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0) + return ifp; + return NULL; +} + +static void +stop_interface(struct interface *iface) +{ + struct interface *ifp, *ifl = NULL; + + syslog(LOG_INFO, "%s: removing interface", iface->name); + if (strcmp(iface->state->reason, "RELEASE") != 0) + drop_config(iface, "STOP"); + close_sockets(iface); + delete_timeout(NULL, iface); + for (ifp = ifaces; ifp; ifp = ifp->next) { + if (ifp == iface) + break; + ifl = ifp; + } + if (ifl) + ifl->next = ifp->next; + else + ifaces = ifp->next; + free_interface(ifp); + if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) + exit(EXIT_FAILURE); +} + +static uint32_t +dhcp_xid(struct interface *iface) +{ + uint32_t xid; + + if (iface->state->options->options & DHCPCD_XID_HWADDR && + iface->hwlen >= sizeof(xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&xid, (iface->hwaddr + iface->hwlen) - sizeof(xid), + sizeof(xid)); + else + xid = arc4random(); + + return xid; +} + +static void +send_message(struct interface *iface, int type, + void (*callback)(void *)) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp; + uint8_t *udp; + ssize_t len, r; + struct in_addr from, to; + in_addr_t a = 0; + struct timeval tv; + + if (!callback) + syslog(LOG_DEBUG, "%s: sending %s with xid 0x%x", + iface->name, 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_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + syslog(LOG_DEBUG, + "%s: sending %s (xid 0x%x), next in %0.2f seconds", + iface->name, get_dhcp_op(type), state->xid, + timeval_to_double(&tv)); + } + + /* Ensure sockets are open. */ + open_sockets(iface); + + /* 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 (iface->udp_fd == -1 || + (!(ifo->options & DHCPCD_INFORM) && is_bootp(iface->state->new))) + { + a = iface->addr.s_addr; + iface->addr.s_addr = 0; + } + len = make_message(&dhcp, iface, type); + if (a) + iface->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 = 0; + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { + r = send_packet(iface, to, (uint8_t *)dhcp, len); + if (r == -1) { + syslog(LOG_ERR, "%s: send_packet: %m", iface->name); + close_sockets(iface); + } + } else { + len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); + r = send_raw_packet(iface, ETHERTYPE_IP, udp, len); + 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) { + syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name); + if (!(options & DHCPCD_TEST)) + drop_config(iface, "FAIL"); + close_sockets(iface); + delete_timeout(NULL, iface); + callback = NULL; + } + } + free(dhcp); + + /* 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) + add_timeout_tv(&tv, callback, iface); +} + +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 +start_expire(void *arg) +{ + struct interface *iface = arg; + + iface->state->interval = 0; + if (iface->addr.s_addr == 0) { + /* We failed to reboot, so enter discovery. */ + iface->state->lease.addr.s_addr = 0; + start_discover(iface); + return; + } + + syslog(LOG_ERR, "%s: lease expired", iface->name); + delete_timeout(NULL, iface); + drop_config(iface, "EXPIRE"); + unlink(iface->leasefile); + if (iface->carrier != LINK_DOWN) + start_interface(iface); +} + +static void +log_dhcp(int lvl, const char *msg, + const struct interface *iface, const struct dhcp_message *dhcp, + const struct in_addr *from) +{ + const char *tfrom; + char *a; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) + a = get_option_string(dhcp, DHO_MESSAGE); + else if (dhcp->yiaddr != 0) { + addr.s_addr = dhcp->yiaddr; + a = xstrdup(inet_ntoa(addr)); + } else + a = NULL; + + tfrom = "from"; + r = get_option_addr(&addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + syslog(lvl, "%s: %s %s %s %s `%s'", iface->name, msg, a, + tfrom, inet_ntoa(addr), dhcp->servername); + else { + if (r != 0) { + tfrom = "via"; + addr = *from; + } + if (a == NULL) + syslog(lvl, "%s: %s %s %s", + iface->name, msg, tfrom, inet_ntoa(addr)); + else + syslog(lvl, "%s: %s %s %s %s", + iface->name, msg, a, tfrom, inet_ntoa(addr)); + } + free(a); +} + +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 +handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp, const struct in_addr *from) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp = *dhcpp; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; + struct in_addr addr; + size_t i; + + /* reset the message counter */ + state->interval = 0; + + /* We may have found a BOOTP server */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + + if (type == DHCP_NAK) { + /* For NAK, only check if we require the ServerID */ + if (has_option_mask(ifo->requiremask, DHO_SERVERID) && + get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) + { + log_dhcp(LOG_WARNING, "reject NAK", iface, dhcp, from); + return; + } + /* We should restart on a NAK */ + log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from); + if (!(options & DHCPCD_TEST)) { + drop_config(iface, "NAK"); + unlink(iface->leasefile); + } + close_sockets(iface); + /* If we constantly get NAKS then we should slowly back off */ + add_timeout_sec(state->nakoff, start_interface, iface); + 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(&tmp, dhcp, i) != 0) + { + /* If we are bootp, then ignore the need for serverid. + * To ignore bootp, require dhcp_message_type instead. */ + if (type == 0 && i == DHO_SERVERID) + continue; + log_dhcp(LOG_WARNING, "reject DHCP", iface, dhcp, from); + return; + } + } + + /* No NAK, so reset the backoff */ + state->nakoff = 1; + + if ((type == 0 || type == DHCP_OFFER) && + state->state == DHS_DISCOVER) + { + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + lease->cookie = dhcp->cookie; + if (type == 0 || + get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; + log_dhcp(LOG_INFO, "offered", iface, dhcp, from); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + if (options & DHCPCD_TEST) { + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + state->reason = "TEST"; + run_script(iface); + exit(EXIT_SUCCESS); + } + delete_timeout(send_discover, iface); + /* 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. */ + start_request(iface); + return; + } + } + + if (type) { + if (type == DHCP_OFFER) { + log_dhcp(LOG_INFO, "ignoring offer of", + iface, dhcp, from); + return; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", + iface, dhcp, from); + return; + } + + if (!(ifo->options & DHCPCD_INFORM)) + log_dhcp(LOG_INFO, "acknowledged", iface, dhcp, from); + } + + /* 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; + delete_timeout(NULL, iface); + + /* We now have an offer, so close the DHCP sockets. + * This allows us to safely ARP when broken DHCP servers send an ACK + * follows by an invalid NAK. */ + close_sockets(iface); + + if (ifo->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + addr.s_addr = state->offer->yiaddr; + if (has_address(iface->name, &addr, NULL) != 1) { + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + state->state = DHS_PROBE; + send_arp_probe(iface); + return; + } + } + + bind_interface(iface); +} + +static void +handle_dhcp_packet(void *arg) +{ + struct interface *iface = arg; + uint8_t *packet; + struct dhcp_message *dhcp = NULL; + const uint8_t *pp; + ssize_t bytes; + struct in_addr from; + int i; + + /* We loop through until our buffer is empty. + * The benefit is that if we get >1 DHCP packet in our buffer and + * the first one fails for any reason, we can use the next. */ + packet = xmalloc(udp_dhcp_len); + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_IP, + packet, udp_dhcp_len); + if (bytes == 0 || bytes == -1) + break; + if (valid_udp_packet(packet, bytes, &from) == -1) { + syslog(LOG_ERR, "%s: invalid UDP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } + i = whitelisted_ip(iface->state->options, from.s_addr); + if (i == 0) { + syslog(LOG_WARNING, + "%s: non whitelisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } else if (i != 1 && + blacklisted_ip(iface->state->options, from.s_addr) == 1) + { + syslog(LOG_WARNING, + "%s: blacklisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } + if (iface->flags & IFF_POINTOPOINT && + iface->dst.s_addr != from.s_addr) + { + syslog(LOG_WARNING, + "%s: server %s is not destination", + iface->name, inet_ntoa(from)); + } + bytes = get_udp_data(&pp, packet); + if ((size_t)bytes > sizeof(*dhcp)) { + syslog(LOG_ERR, + "%s: packet greater than DHCP size from %s", + iface->name, inet_ntoa(from)); + continue; + } + if (!dhcp) + dhcp = xzalloc(sizeof(*dhcp)); + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_DEBUG, "%s: bogus cookie from %s", + iface->name, inet_ntoa(from)); + continue; + } + /* Ensure it's the right transaction */ + if (iface->state->xid != dhcp->xid) { + syslog(LOG_DEBUG, + "%s: wrong xid 0x%x (expecting 0x%x) from %s", + iface->name, dhcp->xid, iface->state->xid, + inet_ntoa(from)); + continue; + } + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) + { + syslog(LOG_DEBUG, "%s: xid 0x%x is not for hwaddr %s", + iface->name, dhcp->xid, + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + continue; + } + handle_dhcp(iface, &dhcp, &from); + if (iface->raw_fd == -1) + break; + } + free(packet); + free(dhcp); +} + +static void +send_release(struct interface *iface) +{ + struct timespec ts; + + if (iface->state->new != NULL && + iface->state->new->cookie == htonl(MAGIC_COOKIE)) + { + syslog(LOG_INFO, "%s: releasing lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->xid = dhcp_xid(iface); + send_message(iface, DHCP_RELEASE, NULL); + /* Give the packet a chance to go before dropping the ip */ + ts.tv_sec = RELEASE_DELAY_S; + ts.tv_nsec = RELEASE_DELAY_NS; + nanosleep(&ts, NULL); + drop_config(iface, "RELEASE"); + } + unlink(iface->leasefile); } -static char * -add_environ(struct options *options, const char *value, int uniq) +void +send_decline(struct interface *iface) { - char **newlist; - char **lst = options->environ; - size_t i = 0, l, lv; - char *match = NULL, *p; - - match = xstrdup(value); - p = strchr(match, '='); - if (p) - *p++ = '\0'; - l = strlen(match); - - while (lst && lst[i]) { - if (match && strncmp(lst[i], match, l) == 0) { - if (uniq) { - free(lst[i]); - lst[i] = xstrdup(value); + send_message(iface, DHCP_DECLINE, NULL); +} + +static void +configure_interface1(struct interface *iface) +{ + struct if_state *ifs = iface->state; + struct if_options *ifo = ifs->options; + uint8_t *duid; + size_t len = 0, ifl; + + /* Do any platform specific configuration */ + if_conf(iface); + + if (iface->flags & IFF_POINTOPOINT && !(ifo->options & DHCPCD_INFORM)) + ifo->options |= DHCPCD_STATIC; + if (iface->flags & IFF_NOARP || + ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) + ifo->options &= ~(DHCPCD_ARP | DHCPCD_IPV4LL); + if (ifo->options & DHCPCD_LINK && carrier_status(iface) == -1) + ifo->options &= ~DHCPCD_LINK; + + if (ifo->metric != -1) + iface->metric = ifo->metric; + + /* 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 (iface->hwlen > DHCP_CHADDR_LEN) + ifo->options |= DHCPCD_CLIENTID; + + /* Firewire and InfiniBand interfaces require ClientID and + * the broadcast option being set. */ + switch (iface->family) { + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: + ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST; + break; + } + + free(iface->clientid); + iface->clientid = NULL; + if (*ifo->clientid) { + iface->clientid = xmalloc(ifo->clientid[0] + 1); + memcpy(iface->clientid, ifo->clientid, ifo->clientid[0] + 1); + } else if (ifo->options & DHCPCD_CLIENTID) { + if (ifo->options & DHCPCD_DUID) { + duid = xmalloc(DUID_LEN); + if ((len = get_duid(duid, iface)) == 0) + syslog(LOG_ERR, "get_duid: %m"); + } + if (len > 0) { + iface->clientid = xmalloc(len + 6); + iface->clientid[0] = len + 5; + iface->clientid[1] = 255; /* RFC 4361 */ + ifl = strlen(iface->name); + if (ifl < 5) { + memcpy(iface->clientid + 2, iface->name, ifl); + if (ifl < 4) + memset(iface->clientid + 2 + ifl, + 0, 4 - ifl); } else { - /* Append a space and the value to it */ - l = strlen(lst[i]); - lv = strlen(p); - lst[i] = xrealloc(lst[i], l + lv + 2); - lst[i][l] = ' '; - memcpy(lst[i] + l + 1, p, lv); - lst[i][l + lv + 1] = '\0'; + ifl = htonl(if_nametoindex(iface->name)); + memcpy(iface->clientid + 2, &ifl, 4); } - free(match); - return lst[i]; + } else if (len == 0) { + len = iface->hwlen + 1; + iface->clientid = xmalloc(len + 1); + iface->clientid[0] = len; + iface->clientid[1] = iface->family; + memcpy(iface->clientid + 2, iface->hwaddr, + iface->hwlen); + } + } + if (ifo->options & DHCPCD_CLIENTID) + syslog(LOG_DEBUG, "%s: using ClientID %s", iface->name, + hwaddr_ntoa(iface->clientid + 1, *iface->clientid)); + else + syslog(LOG_DEBUG, "%s: using hwaddr %s", iface->name, + hwaddr_ntoa(iface->hwaddr, iface->hwlen)); +} + +int +select_profile(struct interface *iface, const char *profile) +{ + struct if_options *ifo; + int ret; + + ret = 0; + ifo = read_config(cffile, iface->name, iface->ssid, profile); + if (ifo == NULL) { + syslog(LOG_DEBUG, "%s: no profile %s", iface->name, profile); + ret = -1; + goto exit; + } + if (profile != NULL) { + strlcpy(iface->state->profile, profile, + sizeof(iface->state->profile)); + syslog(LOG_INFO, "%s: selected profile %s", + iface->name, profile); + } else + *iface->state->profile = '\0'; + free_options(iface->state->options); + iface->state->options = ifo; + +exit: + if (profile) + configure_interface1(iface); + return ret; +} + +static void +start_fallback(void *arg) +{ + struct interface *iface; + + iface = (struct interface *)arg; + select_profile(iface, iface->state->options->fallback); + start_interface(iface); +} + +static void +configure_interface(struct interface *iface, int argc, char **argv) +{ + select_profile(iface, NULL); + add_options(iface->state->options, argc, argv); + configure_interface1(iface); +} + +static void +handle_carrier(const char *ifname) +{ + struct interface *iface; + int carrier; + + if (!(options & DHCPCD_LINK)) + return; + for (iface = ifaces; iface; iface = iface->next) + if (strcmp(iface->name, ifname) == 0) + break; + if (!iface || !(iface->state->options->options & DHCPCD_LINK)) + return; + carrier = carrier_status(iface); + if (carrier == -1) + syslog(LOG_ERR, "%s: carrier_status: %m", ifname); + else if (carrier == 0 || !(iface->flags & IFF_RUNNING)) { + if (iface->carrier != LINK_DOWN) { + iface->carrier = LINK_DOWN; + syslog(LOG_INFO, "%s: carrier lost", iface->name); + close_sockets(iface); + delete_timeouts(iface, start_expire, NULL); + drop_config(iface, "NOCARRIER"); + } + } else if (carrier == 1 && (iface->flags & IFF_RUNNING)) { + if (iface->carrier != LINK_UP) { + iface->carrier = LINK_UP; + syslog(LOG_INFO, "%s: carrier acquired", iface->name); + if (iface->wireless) + getifssid(iface->name, iface->ssid); + configure_interface(iface, margc, margv); + iface->state->interval = 0; + iface->state->reason = "CARRIER"; + run_script(iface); + start_interface(iface); } - i++; } +} + +void +start_discover(void *arg) +{ + struct interface *iface = arg; + struct if_options *ifo = iface->state->options; + + iface->state->state = DHS_DISCOVER; + iface->state->xid = dhcp_xid(iface); + delete_timeout(NULL, iface); + if (ifo->fallback) + add_timeout_sec(ifo->timeout, start_fallback, iface); + else if (ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(iface->addr.s_addr))) + { + if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) + add_timeout_sec(RATE_LIMIT_INTERVAL, start_ipv4ll, iface); + else + add_timeout_sec(ifo->timeout, start_ipv4ll, iface); + } + syslog(LOG_INFO, "%s: broadcasting for a lease", iface->name); + send_discover(iface); +} - newlist = xrealloc(lst, sizeof(char *) * (i + 2)); - newlist[i] = xstrdup(value); - newlist[i + 1] = NULL; - options->environ = newlist; - free(match); - return newlist[i]; +void +start_request(void *arg) +{ + struct interface *iface = arg; + + iface->state->state = DHS_REQUEST; + send_request(iface); } -#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) -static ssize_t -parse_string_hwaddr(char *sbuf, ssize_t slen, char *str, int clid) +void +start_renew(void *arg) { - ssize_t l; - char *p; - int i; - char c[4]; - - /* If surrounded by quotes then it's a string */ - if (*str == '"') { - str++; - l = strlen(str); - p = str + l - 1; - if (*p == '"') - *p = '\0'; + struct interface *iface = arg; + + syslog(LOG_INFO, "%s: renewing lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->state = DHS_RENEW; + iface->state->xid = dhcp_xid(iface); + send_renew(iface); +} + +void +start_rebind(void *arg) +{ + struct interface *iface = arg; + + syslog(LOG_ERR, "%s: failed to renew, attempting to rebind", + iface->name); + iface->state->state = DHS_REBIND; + delete_timeout(send_renew, iface); + iface->state->lease.server.s_addr = 0; + send_rebind(iface); +} + +static void +start_timeout(void *arg) +{ + struct interface *iface = arg; + + bind_interface(iface); + iface->state->interval = 0; + start_discover(iface); +} + +static struct dhcp_message * +dhcp_message_new(struct in_addr *addr, struct in_addr *mask) +{ + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = xzalloc(sizeof(*dhcp)); + 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 int +handle_3rdparty(struct interface *iface) +{ + struct if_options *ifo; + struct in_addr addr, net, dst; + + ifo = iface->state->options; + if (ifo->req_addr.s_addr != INADDR_ANY) + return 0; + + if (get_address(iface->name, &addr, &net, &dst) == 1) + handle_ifa(RTM_NEWADDR, iface->name, &addr, &net, &dst); + else { + syslog(LOG_INFO, + "%s: waiting for 3rd party to configure IP address", + iface->name); + iface->state->reason = "3RDPARTY"; + run_script(iface); + } + return 1; +} + +static void +start_static(struct interface *iface) +{ + struct if_options *ifo; + + if (handle_3rdparty(iface)) + return; + ifo = iface->state->options; + iface->state->offer = + dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + delete_timeout(NULL, iface); + bind_interface(iface); +} + +static void +start_inform(struct interface *iface) +{ + if (handle_3rdparty(iface)) + return; + + if (options & DHCPCD_TEST) { + iface->addr.s_addr = iface->state->options->req_addr.s_addr; + iface->net.s_addr = iface->state->options->req_mask.s_addr; } else { - l = hwaddr_aton(NULL, str); - if (l > 1) { - if (l > slen) { - errno = ENOBUFS; - return -1; + iface->state->options->options |= DHCPCD_STATIC; + start_static(iface); + } + + iface->state->state = DHS_INFORM; + iface->state->xid = dhcp_xid(iface); + send_inform(iface); +} + +void +start_reboot(struct interface *iface) +{ + struct if_options *ifo = iface->state->options; + + if (ifo->options & DHCPCD_LINK && iface->carrier == LINK_DOWN) { + syslog(LOG_INFO, "%s: waiting for carrier", iface->name); + return; + } + if (ifo->options & DHCPCD_STATIC) { + start_static(iface); + return; + } + if (ifo->reboot == 0 || iface->state->offer == NULL) { + start_discover(iface); + return; + } + if (ifo->options & DHCPCD_INFORM) { + syslog(LOG_INFO, "%s: informing address of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + } else if (iface->state->offer->cookie == 0) { + if (ifo->options & DHCPCD_IPV4LL) { + iface->state->claims = 0; + send_arp_announce(iface); + } else + start_discover(iface); + return; + } else { + syslog(LOG_INFO, "%s: rebinding lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + } + iface->state->state = DHS_REBOOT; + iface->state->xid = dhcp_xid(iface); + iface->state->lease.server.s_addr = 0; + delete_timeout(NULL, iface); + if (ifo->fallback) + add_timeout_sec(ifo->reboot, start_fallback, iface); + else if (ifo->options & DHCPCD_LASTLEASE && + iface->state->lease.frominfo) + add_timeout_sec(ifo->reboot, start_timeout, iface); + else if (!(ifo->options & DHCPCD_INFORM && + options & (DHCPCD_MASTER | DHCPCD_DAEMONISED))) + add_timeout_sec(ifo->reboot, start_expire, iface); + /* Don't bother ARP checking as the server could NAK us first. */ + if (ifo->options & DHCPCD_INFORM) + send_inform(iface); + else + send_request(iface); +} + +void +start_interface(void *arg) +{ + struct interface *iface = arg; + struct if_options *ifo = iface->state->options; + struct stat st; + struct timeval now; + uint32_t l; + int nolease; + + handle_carrier(iface->name); + if (iface->carrier == LINK_DOWN) { + syslog(LOG_INFO, "%s: waiting for carrier", iface->name); + return; + } + + iface->start_uptime = uptime(); + free(iface->state->offer); + iface->state->offer = NULL; + + if (iface->state->arping_index < ifo->arping_len) { + start_arping(iface); + return; + } + if (ifo->options & DHCPCD_STATIC) { + start_static(iface); + return; + } + if (ifo->options & DHCPCD_INFORM) { + start_inform(iface); + return; + } + if (iface->hwlen == 0 && ifo->clientid[0] == '\0') { + syslog(LOG_WARNING, "%s: needs a clientid to configure", + iface->name); + drop_config(iface, "FAIL"); + close_sockets(iface); + delete_timeout(NULL, iface); + return; + } + /* We don't want to read the old lease if we NAK an old test */ + nolease = iface->state->offer && options & DHCPCD_TEST; + if (!nolease) + iface->state->offer = read_lease(iface); + if (iface->state->offer) { + get_lease(&iface->state->lease, iface->state->offer); + iface->state->lease.frominfo = 1; + if (iface->state->offer->cookie == 0) { + if (iface->state->offer->yiaddr == + iface->addr.s_addr) + { + free(iface->state->offer); + iface->state->offer = NULL; + } + } else if (iface->state->lease.leasetime != ~0U && + stat(iface->leasefile, &st) == 0) + { + /* Offset lease times and check expiry */ + gettimeofday(&now, NULL); + if ((time_t)iface->state->lease.leasetime < + now.tv_sec - st.st_mtime) + { + syslog(LOG_DEBUG, + "%s: discarding expired lease", + iface->name); + free(iface->state->offer); + iface->state->offer = NULL; + iface->state->lease.addr.s_addr = 0; + } else { + l = now.tv_sec - st.st_mtime; + iface->state->lease.leasetime -= l; + iface->state->lease.renewaltime -= l; + iface->state->lease.rebindtime -= l; } - hwaddr_aton((uint8_t *)sbuf, str); - return l; } } + if (iface->state->offer == NULL) + start_discover(iface); + else if (iface->state->offer->cookie == 0 && + iface->state->options->options & DHCPCD_IPV4LL) + start_ipv4ll(iface); + else + start_reboot(iface); +} - /* 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) { - *sbuf++ = 0; - l++; - } - c[3] = '\0'; - while (*str) { - if (++l > slen) { - errno = ENOBUFS; - return -1; +static void +init_state(struct interface *iface, int argc, char **argv) +{ + struct if_state *ifs; + + if (iface->state) + ifs = iface->state; + else + ifs = iface->state = xzalloc(sizeof(*ifs)); + + ifs->state = DHS_INIT; + ifs->reason = "PREINIT"; + ifs->nakoff = 1; + configure_interface(iface, argc, argv); + if (!(options & DHCPCD_TEST)) + run_script(iface); + /* We need to drop the leasefile so that start_interface + * doesn't load it. */ + if (ifs->options->options & DHCPCD_REQUEST) + unlink(iface->leasefile); + + if (ifs->options->options & DHCPCD_LINK) { + switch (carrier_status(iface)) { + case 0: + iface->carrier = LINK_DOWN; + ifs->reason = "NOCARRIER"; + break; + case 1: + iface->carrier = LINK_UP; + ifs->reason = "CARRIER"; + break; + default: + iface->carrier = LINK_UNKNOWN; + return; } - if (*str == '\\') { - str++; - switch(*str++) { - case '\0': - break; - case 'b': - *sbuf++ = '\b'; - break; - case 'n': - *sbuf++ = '\n'; - break; - case 'r': - *sbuf++ = '\r'; - break; - case 't': - *sbuf++ = '\t'; - break; - case 'x': - /* Grab a hex code */ - c[1] = '\0'; - for (i = 0; i < 2; i++) { - if (isxdigit((unsigned char)*str) == 0) - break; - c[i] = *str++; - } - if (c[1] != '\0') { - c[2] = '\0'; - *sbuf++ = strtol(c, NULL, 16); - } else - l--; + if (!(options & DHCPCD_TEST)) + run_script(iface); + } else + iface->carrier = LINK_UNKNOWN; +} + +void +handle_interface(int action, const char *ifname) +{ + struct interface *ifs, *ifp, *ifn, *ifl = NULL; + const char * const argv[] = { ifname }; + int i; + + if (action == -1) { + ifp = find_interface(ifname); + if (ifp != NULL) + stop_interface(ifp); + return; + } else if (action == 0) { + handle_carrier(ifname); + return; + } + + /* If running off an interface list, check it's in it. */ + if (ifc) { + for (i = 0; i < ifc; i++) + if (strcmp(ifv[i], ifname) == 0) break; - case '0': - /* Grab an octal code */ - c[2] = '\0'; - for (i = 0; i < 3; i++) { - if (*str < '0' || *str > '7') - break; - c[i] = *str++; - } - if (c[2] != '\0') { - i = strtol(c, NULL, 8); - if (i > 255) - i = 255; - *sbuf ++= i; - } else - l--; + if (i >= ifc) + return; + } + + ifs = discover_interfaces(-1, UNCONST(argv)); + for (ifp = ifs; ifp; ifp = ifp->next) { + if (strcmp(ifp->name, ifname) != 0) + continue; + /* Check if we already have the interface */ + for (ifn = ifaces; ifn; ifn = ifn->next) { + if (strcmp(ifn->name, ifp->name) == 0) break; - default: - *sbuf++ = *str++; - } - } else - *sbuf++ = *str++; + ifl = ifn; + } + if (ifn) { + /* The flags and hwaddr could have changed */ + ifn->flags = ifp->flags; + ifn->hwlen = ifp->hwlen; + if (ifp->hwlen != 0) + memcpy(ifn->hwaddr, ifp->hwaddr, ifn->hwlen); + } else { + if (ifl) + ifl->next = ifp; + else + ifaces = ifp; + } + init_state(ifp, 0, NULL); + start_interface(ifp); } - return l; } -static int -parse_option(int opt, char *oarg, struct options *options) +#ifdef RTM_CHGADDR +void +handle_hwaddr(const char *ifname, unsigned char *hwaddr, size_t hwlen) +{ + struct interface *ifp; + struct if_options *ifo; + + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0 && ifp->hwlen <= hwlen) { + ifo = ifp->state->options; + if (!(ifo->options & + (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID)) + && ifp->state->new != NULL && + ifp->state->new->cookie == htonl(MAGIC_COOKIE)) + { + syslog(LOG_INFO, + "%s: expiring for new hardware address", + ifp->name); + drop_config(ifp, "EXPIRE"); + } + memcpy(ifp->hwaddr, hwaddr, hwlen); + ifp->hwlen = hwlen; + if (!(ifo->options & + (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID))) + { + syslog(LOG_DEBUG, "%s: using hwaddr %s", + ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen)); + ifp->state->interval = 0; + ifp->state->nakoff = 1; + start_interface(ifp); + } + } + free(hwaddr); +} +#endif + +void +handle_ifa(int type, const char *ifname, + struct in_addr *addr, struct in_addr *net, struct in_addr *dst) { + struct interface *ifp; + struct if_options *ifo; int i; - char *p; - ssize_t s; - struct in_addr addr; - switch(opt) { - case 'b': - options->options |= DHCPCD_BACKGROUND; - break; - case 'c': - strlcpy(options->script, oarg, sizeof(options->script)); - break; - case 'h': - if (oarg) - s = parse_string(options->hostname, - HOSTNAME_MAX_LEN, oarg); - else - s = 0; - if (s == -1) { - logger(LOG_ERR, "hostname: %s", strerror(errno)); - return -1; - } - if (s != 0 && options->hostname[0] == '.') { - logger(LOG_ERR, "hostname cannot begin with a ."); - return -1; - } - options->hostname[s] = '\0'; - break; - case 'i': - if (oarg) - s = parse_string((char *)options->vendorclassid + 1, - VENDORCLASSID_MAX_LEN, oarg); - else - s = 0; - if (s == -1) { - logger(LOG_ERR, "vendorclassid: %s", strerror(errno)); - return -1; - } - *options->vendorclassid = (uint8_t)s; - break; - case 'l': - if (*oarg == '-') { - logger(LOG_ERR, - "leasetime must be a positive value"); - return -1; - } - errno = 0; - options->leasetime = (uint32_t)strtol(oarg, NULL, 0); - if (errno == EINVAL || errno == ERANGE) { - logger(LOG_ERR, "`%s' out of range", oarg); - return -1; - } + if (addr->s_addr == INADDR_ANY) + return; + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0) + break; + if (ifp == NULL) + return; + ifo = ifp->state->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) == 0 || + ifo->req_addr.s_addr != INADDR_ANY) + return; + + switch (type) { + case RTM_DELADDR: + if (ifp->state->new && + ifp->state->new->yiaddr == addr->s_addr) + drop_config(ifp, "EXPIRE"); break; - case 'm': - options->metric = atoint(oarg); - if (options->metric < 0) { - logger(LOG_ERR, "metric must be a positive value"); - return -1; + case RTM_NEWADDR: + free(ifp->state->old); + ifp->state->old = ifp->state->new; + ifp->state->new = dhcp_message_new(addr, net); + ifp->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( + ifp->state->new, + i, *dst); } - break; - case 'o': - if (make_option_mask(options->requestmask, &oarg, 1) != 0) { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; + ifp->state->reason = "STATIC"; + build_routes(); + run_script(ifp); + if (ifo->options & DHCPCD_INFORM) { + ifp->state->state = DHS_INFORM; + ifp->state->xid = dhcp_xid(ifp); + ifp->state->lease.server.s_addr = + dst ? dst->s_addr : INADDR_ANY; + ifp->addr = *addr; + ifp->net = *net; + send_inform(ifp); } break; - case 'p': - options->options |= DHCPCD_PERSISTENT; - break; - case 'q': - setloglevel(LOG_WARNING); - break; - case 's': - options->options |= DHCPCD_INFORM; - options->options |= DHCPCD_PERSISTENT; - options->options &= ~DHCPCD_ARP; - if (!oarg || *oarg == '\0') { - options->request_address.s_addr = 0; - break; - } else { - if ((p = strchr(oarg, '/'))) { - /* nullify the slash, so the -r option - * can read the address */ - *p++ = '\0'; - if (sscanf(p, "%d", &i) != 1 || - inet_cidrtoaddr(i, &options->request_netmask) != 0) - { - logger(LOG_ERR, - "`%s' is not a valid CIDR", - p); - return -1; + } +} + +/* ARGSUSED */ +static void +handle_link(_unused void *arg) +{ + if (manage_link(linkfd) == -1) + syslog(LOG_ERR, "manage_link: %m"); +} + +static void +if_reboot(struct interface *iface, int argc, char **argv) +{ + const struct if_options *ifo; + int opt; + + ifo = iface->state->options; + opt = ifo->options; + configure_interface(iface, argc, argv); + ifo = iface->state->options; + iface->state->interval = 0; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) && + iface->addr.s_addr != ifo->req_addr.s_addr) || + (opt & (DHCPCD_INFORM | DHCPCD_STATIC) && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) + { + drop_config(iface, "EXPIRE"); + } else { + free(iface->state->offer); + iface->state->offer = NULL; + } + start_interface(iface); +} + +static void +reconf_reboot(int action, int argc, char **argv, int oi) +{ + struct interface *ifl, *ifn, *ifp, *ifs, *ift; + + ifs = discover_interfaces(argc - oi, argv + oi); + if (ifs == NULL) + return; + + /* Remove any old interfaces */ + if (ifaces) { + for (ifl = NULL; ifl != ifaces;) { + /* Work our way backwards */ + for (ifp = ifaces; ifp; ifp = ifp->next) + if (ifp->next == ifl) { + ifl = ifp; + break; } + for (ifn = ifs; ifn; ifn = ifn->next) + if (strcmp(ifn->name, ifp->name) == 0) + break; + if (ifn == NULL) { + ifl = ifp->next; + stop_interface(ifp); } } - /* FALLTHROUGH */ - case 'r': - if (!(options->options & DHCPCD_INFORM)) - options->options |= DHCPCD_REQUEST; - if (oarg && !inet_aton(oarg, &options->request_address)) { - logger(LOG_ERR, "`%s' is not a valid IP address", - oarg); - return -1; - } - break; - case 't': - options->timeout = atoint(oarg); - if (options->timeout < 0) { - logger (LOG_ERR, "timeout must be a positive value"); - return -1; - } - break; - case 'u': - s = USERCLASS_MAX_LEN - options->userclass[0] - 1; - s = parse_string((char *)options->userclass + options->userclass[0] + 2, - s, oarg); - if (s == -1) { - logger(LOG_ERR, "userclass: %s", strerror(errno)); - return -1; - } - if (s != 0) { - options->userclass[options->userclass[0] + 1] = s; - options->userclass[0] += s + 1; - } - break; - case 'v': - p = strchr(oarg, ','); - if (!p || !p[1]) { - logger(LOG_ERR, "invalid vendor format"); - return -1; - } - *p = '\0'; - i = atoint(oarg); - oarg = p + 1; - if (i < 1 || i > 254) { - logger(LOG_ERR, "vendor option should be between" - " 1 and 254 inclusive"); - return -1; + } + + for (ifp = ifs; ifp && (ift = ifp->next, 1); ifp = ift) { + ifl = NULL; + for (ifn = ifaces; ifn; ifn = ifn->next) { + if (strcmp(ifn->name, ifp->name) == 0) + break; + ifl = ifn; } - s = VENDOR_MAX_LEN - options->vendor[0] - 2; - if (inet_aton(oarg, &addr) == 1) { - if (s < 6) { - s = -1; - errno = ENOBUFS; - } else - memcpy(options->vendor + options->vendor[0] + 3, - &addr.s_addr, sizeof(addr.s_addr)); + if (ifn) { + if (action) + if_reboot(ifn, argc, argv); + else if (ifn->state->new) + configure(ifn); + free_interface(ifp); } else { - s = parse_string((char *)options->vendor + options->vendor[0] + 3, - s, oarg); - } - if (s == -1) { - logger(LOG_ERR, "vendor: %s", strerror(errno)); - return -1; - } - if (s != 0) { - options->vendor[options->vendor[0] + 1] = i; - options->vendor[options->vendor[0] + 2] = s; - options->vendor[0] += s + 2; - } - break; - case 'A': - options->options &= ~DHCPCD_ARP; - /* IPv4LL requires ARP */ - options->options &= ~DHCPCD_IPV4LL; - break; - case 'B': - options->options &= ~DHCPCD_DAEMONISE; - break; - case 'C': - /* Commas to spaces for shell */ - while ((p = strchr(oarg, ','))) - *p = ' '; - s = strlen("skip_hooks=") + strlen(oarg) + 1; - p = xmalloc(sizeof(char) * s); - snprintf(p, s, "skip_hooks=%s", oarg); - add_environ(options, p, 0); - free(p); - break; - case 'D': - options->options |= DHCPCD_DUID; - break; - case 'E': - options->options |= DHCPCD_LASTLEASE; - break; - case 'F': - if (!oarg) { - options->fqdn = FQDN_BOTH; - break; - } - if (strcmp(oarg, "none") == 0) - options->fqdn = FQDN_NONE; - else if (strcmp(oarg, "ptr") == 0) - options->fqdn = FQDN_PTR; - else if (strcmp(oarg, "both") == 0) - options->fqdn = FQDN_BOTH; - else if (strcmp(oarg, "disable") == 0) - options->fqdn = FQDN_DISABLE; - else { - logger(LOG_ERR, "invalid value `%s' for FQDN", - oarg); - return -1; - } - break; - case 'G': - options->options &= ~DHCPCD_GATEWAY; - break; - case 'I': - /* Strings have a type of 0 */; - options->clientid[1] = 0; - if (oarg) - s = parse_string_hwaddr((char *)options->clientid + 1, - CLIENTID_MAX_LEN, oarg, 1); - else - s = 0; - if (s == -1) { - logger(LOG_ERR, "clientid: %s", strerror(errno)); - return -1; - } - options->clientid[0] = (uint8_t)s; -#ifdef CMDLINE_COMPAT - if (s == 0) { - options->options &= ~DHCPCD_DUID; - options->options &= ~DHCPCD_CLIENTID; - } -#else - options->options |= DHCPCD_CLIENTID; -#endif - break; - case 'K': - options->options &= ~DHCPCD_LINK; - break; - case 'L': - options->options &= ~DHCPCD_IPV4LL; - break; - case 'O': - if (make_option_mask(options->requestmask, &oarg, -1) != 0 || - make_option_mask(options->requiremask, &oarg, -1) != 0 || - make_option_mask(options->nomask, &oarg, 1) != 0) - { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; - } - break; - case 'Q': - if (make_option_mask(options->requiremask, &oarg, 1) != 0 || - make_option_mask(options->requestmask, &oarg, 1) != 0) - { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; - } - break; - case 'X': - if (!inet_aton(oarg, &addr)) { - logger(LOG_ERR, "`%s' is not a valid IP address", - oarg); - return -1; + ifp->next = NULL; + init_state(ifp, argc, argv); + start_interface(ifp); + if (ifl) + ifl->next = ifp; + else + ifaces = ifp; } - options->blacklist = xrealloc(options->blacklist, - sizeof(in_addr_t) * (options->blacklist_len + 1)); - options->blacklist[options->blacklist_len] = addr.s_addr; - options->blacklist_len++; - break; - default: - return 0; } - return 1; + sort_interfaces(); } -static int -parse_config_line(const char *opt, char *line, struct options *options) +/* ARGSUSED */ +static void +handle_signal(_unused void *arg) { - unsigned int i; + struct interface *ifp, *ifl; + struct if_options *ifo; + int sig = signal_read(); + int do_release, do_rebind, i; + + do_rebind = do_release = 0; + switch (sig) { + case SIGINT: + syslog(LOG_INFO, "received SIGINT, stopping"); + break; + case SIGTERM: + syslog(LOG_INFO, "received SIGTERM, stopping"); + break; + case SIGALRM: + syslog(LOG_INFO, "received SIGALRM, rebinding"); + for (i = 0; i < ifac; i++) + free(ifav[i]); + free(ifav); + ifav = NULL; + ifac = 0; + for (i = 0; i < ifdc; i++) + free(ifdv[i]); + free(ifdv); + ifdc = 0; + ifdv = NULL; + ifo = read_config(cffile, NULL, NULL, NULL); + add_options(ifo, margc, margv); + /* We need to preserve these two options. */ + if (options & DHCPCD_MASTER) + ifo->options |= DHCPCD_MASTER; + if (options & DHCPCD_DAEMONISED) + ifo->options |= DHCPCD_DAEMONISED; + options = ifo->options; + free_options(ifo); + reconf_reboot(1, 0, NULL, 0); + return; + case SIGHUP: + syslog(LOG_INFO, "received SIGHUP, releasing"); + do_release = 1; + break; + case SIGUSR1: + syslog(LOG_INFO, "received SIGUSR, reconfiguring"); + for (ifp = ifaces; ifp; ifp = ifp->next) + if (ifp->state->new) + configure(ifp); + return; + case SIGPIPE: + syslog(LOG_WARNING, "received SIGPIPE"); + return; + default: + syslog(LOG_ERR, + "received signal %d, but don't know what to do with it", + sig); + return; + } - for (i = 0; i < sizeof(longopts) / sizeof(longopts[0]); i++) { - if (!longopts[i].name || - strcmp(longopts[i].name, opt) != 0) - continue; + if (options & DHCPCD_TEST) + exit(EXIT_FAILURE); - if (longopts[i].has_arg == required_argument && !line) { - fprintf(stderr, - PACKAGE ": option requires an argument -- %s\n", - opt); - return -1; + /* As drop_config could re-arrange the order, we do it like this. */ + for (;;) { + /* Be sane and drop the last config first */ + ifl = NULL; + for (ifp = ifaces; ifp; ifp = ifp->next) { + if (ifp->next == NULL) + break; + ifl = ifp; } - - return parse_option(longopts[i].val, line, options); + if (ifp == NULL) + break; + if (ifp->carrier != LINK_DOWN && + (do_release || + ifp->state->options->options & DHCPCD_RELEASE)) + send_release(ifp); + stop_interface(ifp); } - - fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt); - return -1; -} - -#ifdef ANDROID -void switchUser() { - gid_t groups[] = { AID_INET, AID_SHELL }; - setgroups(sizeof(groups)/sizeof(groups[0]), groups); - - prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); - - setgid(AID_DHCP); - setuid(AID_DHCP); - - struct __user_cap_header_struct header; - struct __user_cap_data_struct cap; - header.version = _LINUX_CAPABILITY_VERSION; - header.pid = 0; - cap.effective = cap.permitted = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | - (1 << CAP_NET_BROADCAST) | (1 << CAP_NET_BIND_SERVICE); - cap.inheritable = 0; - capset(&header, &cap); + exit(EXIT_FAILURE); } -#endif /* ANDROID */ int -main(int argc, char **argv) +handle_args(struct fd_list *fd, int argc, char **argv) { - struct options *options; - int opt; - int option_index = 0; - char *prefix; - pid_t pid; - int debug = 0; - int i, r; - int pid_fd = -1; - int sig = 0; - int retval = EXIT_FAILURE; - char *line, *option, *p, *buffer = NULL; - size_t len = 0; - FILE *f; - char *cf = NULL; - char *intf = NULL; - struct timespec ts; - -#ifdef ANDROID - switchUser(); -#endif - closefrom(3); - /* Saves calling fflush(stream) in the logger */ - setlinebuf(stdout); - openlog(PACKAGE, LOG_PID, LOG_DAEMON); - setlogprefix(PACKAGE ": "); - - options = xzalloc(sizeof(*options)); - options->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE; - options->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK; - options->timeout = DEFAULT_TIMEOUT; - strlcpy(options->script, SCRIPT, sizeof(options->script)); - - options->vendorclassid[0] = snprintf((char *)options->vendorclassid + 1, - VENDORCLASSID_MAX_LEN, - "%s %s", PACKAGE, VERSION); - -#ifdef CMDLINE_COMPAT - options->options |= DHCPCD_CLIENTID; - add_option_mask(options->requestmask, DHO_DNSSERVER); - add_option_mask(options->requestmask, DHO_DNSDOMAIN); - add_option_mask(options->requestmask, DHO_DNSSEARCH); - add_option_mask(options->requestmask, DHO_NISSERVER); - add_option_mask(options->requestmask, DHO_NISDOMAIN); - add_option_mask(options->requestmask, DHO_NTPSERVER); - - /* If the duid file exists, then enable duid by default - * This means we don't break existing clients that easily :) */ - if ((f = fopen(DUID, "r"))) { - options->options |= DHCPCD_DUID; - fclose(f); + struct interface *ifp; + int do_exit = 0, do_release = 0, do_reboot = 0, do_reconf = 0; + int opt, oi = 0; + ssize_t len; + size_t l; + struct iovec iov[2]; + char *tmp, *p; + + if (fd != NULL) { + /* Special commands for our control socket */ + if (strcmp(*argv, "--version") == 0) { + len = strlen(VERSION) + 1; + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = UNCONST(VERSION); + iov[1].iov_len = len; + if (writev(fd->fd, iov, 2) == -1) { + syslog(LOG_ERR, "writev: %m"); + return -1; + } + return 0; + } else if (strcmp(*argv, "--getconfigfile") == 0) { + len = strlen(cffile ? cffile : CONFIG) + 1; + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = cffile ? cffile : UNCONST(CONFIG); + iov[1].iov_len = len; + if (writev(fd->fd, iov, 2) == -1) { + syslog(LOG_ERR, "writev: %m"); + return -1; + } + return 0; + } else if (strcmp(*argv, "--getinterfaces") == 0) { + len = 0; + if (argc == 1) { + for (ifp = ifaces; ifp; ifp = ifp->next) + len++; + len = write(fd->fd, &len, sizeof(len)); + if (len != sizeof(len)) + return -1; + for (ifp = ifaces; ifp; ifp = ifp->next) + send_interface(fd->fd, ifp); + return 0; + } + opt = 0; + while (argv[++opt] != NULL) { + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(argv[opt], ifp->name) == 0) + len++; + } + len = write(fd->fd, &len, sizeof(len)); + if (len != sizeof(len)) + return -1; + opt = 0; + while (argv[++opt] != NULL) { + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(argv[opt], ifp->name) == 0) + send_interface(fd->fd, ifp); + } + return 0; + } else if (strcmp(*argv, "--listen") == 0) { + fd->listener = 1; + return 0; + } } -#endif - gethostname(options->hostname, HOSTNAME_MAX_LEN); - /* Ensure that the hostname is NULL terminated */ - options->hostname[HOSTNAME_MAX_LEN] = '\0'; - if (strcmp(options->hostname, "(none)") == 0 || - strcmp(options->hostname, "localhost") == 0) - options->hostname[0] = '\0'; + /* Log the command */ + len = 0; + for (opt = 0; opt < argc; opt++) + len += strlen(argv[opt]) + 1; + tmp = p = xmalloc(len + 1); + for (opt = 0; opt < argc; opt++) { + l = strlen(argv[opt]); + strlcpy(p, argv[opt], l + 1); + p += l; + *p++ = ' '; + } + *--p = '\0'; + syslog(LOG_INFO, "control command: %s", tmp); + free(tmp); - while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, - longopts, &option_index)) != -1) + optind = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { - case 0: - if (longopts[option_index].flag) - break; - logger(LOG_ERR, "option `%s' should set a flag", - longopts[option_index].name); - goto abort; - case 'f': - cf = optarg; + case 'g': + do_reconf = 1; + break; + case 'k': + do_release = 1; + break; + case 'n': + do_reboot = 1; + break; + case 'x': + do_exit = 1; break; - case 'V': - print_options(); - goto abort; - case '?': - usage(); - goto abort; } } - if (doversion) - printf(""PACKAGE" "VERSION"\n%s\n", copyright); - - if (dohelp) - usage(); - - if (optind < argc) { - if (strlen(argv[optind]) >= IF_NAMESIZE) { - logger(LOG_ERR, - "`%s' too long for an interface name (max=%d)", - argv[optind], IF_NAMESIZE); - goto abort; - } - strlcpy(options->interface, argv[optind], - sizeof(options->interface)); - } else { - /* If only version was requested then exit now */ - if (doversion || dohelp) { - retval = 0; - goto abort; - } - - logger(LOG_ERR, "no interface specified"); - goto abort; + /* We need at least one interface */ + if (optind == argc) { + syslog(LOG_ERR, "handle_args: no interface"); + return -1; } - /* Parse our options file */ - f = fopen(cf ? cf : CONFIG, "r"); - if (f) { - r = 1; - while ((get_line(&buffer, &len, f))) { - line = buffer; - while ((option = strsep(&line, " \t"))) - if (*option != '\0') + if (do_release || do_exit) { + for (oi = optind; oi < argc; oi++) { + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, argv[oi]) == 0) break; - if (!option || *option == '\0' || *option == '#') - continue; - /* Trim leading whitespace */ - if (line) { - while (*line != '\0' && (*line == ' ' || *line == '\t')) - line++; - } - /* Trim trailing whitespace */ - if (line && *line) { - p = line + strlen(line) - 1; - while (p != line && - (*p == ' ' || *p == '\t') && - *(p - 1) != '\\') - *p-- = '\0'; - } - if (strcmp(option, "interface") == 0) { - free(intf); - intf = xstrdup(line); - continue; - } - /* If we're in an interface block don't use these - * options unless it's for us */ - if (intf && strcmp(intf, options->interface) != 0) + if (!ifp) continue; - r = parse_config_line(option, line, options); - if (r != 1) - break; + if (do_release) + ifp->state->options->options |= DHCPCD_RELEASE; + if (ifp->state->options->options & DHCPCD_RELEASE && + ifp->carrier != LINK_DOWN) + send_release(ifp); + stop_interface(ifp); } - free(buffer); - free(intf); - fclose(f); - if (r == 0) + return 0; + } + + reconf_reboot(do_reboot, argc, argv, optind); + return 0; +} + +void +open_sockets(struct interface *iface) +{ + if (iface->raw_fd == -1) { + if (open_socket(iface, ETHERTYPE_IP) == -1) + syslog(LOG_ERR, "%s: open_socket: %m", iface->name); + else + add_event(iface->raw_fd, handle_dhcp_packet, iface); + } + if (iface->udp_fd == -1 && + iface->addr.s_addr != 0 && + iface->state->new != NULL && + (iface->state->new->cookie == htonl(MAGIC_COOKIE) || + iface->state->options->options & DHCPCD_INFORM)) + { + if (open_udp_socket(iface) == -1 && errno != EADDRINUSE) + syslog(LOG_ERR, "%s: open_udp_socket: %m", iface->name); + } +} + +void +close_sockets(struct interface *iface) +{ + if (iface->arp_fd != -1) { + delete_event(iface->arp_fd); + close(iface->arp_fd); + iface->arp_fd = -1; + } + if (iface->raw_fd != -1) { + delete_event(iface->raw_fd); + close(iface->raw_fd); + iface->raw_fd = -1; + } + if (iface->udp_fd != -1) { + /* we don't listen to events on the udp */ + close(iface->udp_fd); + iface->udp_fd = -1; + } +} + +int +main(int argc, char **argv) +{ + struct interface *iface; + int opt, oi = 0, signal_fd, sig = 0, i, control_fd; + size_t len; + pid_t pid; + struct timespec ts; + + closefrom(3); + openlog(PACKAGE, LOG_PERROR | LOG_PID, LOG_DAEMON); + setlogmask(LOG_UPTO(LOG_INFO)); + + /* Test for --help and --version */ + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0) { usage(); - if (r != 1) - goto abort; - } else { - if (errno != ENOENT || cf) { - logger(LOG_ERR, "fopen `%s': %s", cf ? cf : CONFIG, - strerror(errno)); - goto abort; + exit(EXIT_SUCCESS); + } else if (strcmp(argv[1], "--version") == 0) { + printf(""PACKAGE" "VERSION"\n%s\n", copyright); + exit(EXIT_SUCCESS); } } - optind = 0; - while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, - longopts, &option_index)) != -1) + i = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { - case 'd': - debug++; - switch (debug) { - case 1: - setloglevel(LOG_DEBUG); - break; - case 2: - options->options &= ~DHCPCD_DAEMONISE; - break; - } - break; case 'f': + cffile = optarg; + break; + case 'g': + sig = SIGUSR1; break; case 'k': sig = SIGHUP; @@ -816,235 +1746,291 @@ main(int argc, char **argv) sig = SIGTERM; break; case 'T': - options->options |= DHCPCD_TEST | DHCPCD_PERSISTENT; - break; -#ifdef CMDLINE_COMPAT - case 'H': /* FALLTHROUGH */ - case 'M': - del_option_mask(options->requestmask, DHO_MTU); - add_environ(options, "skip_hooks=mtu", 0); - break; - case 'N': - del_option_mask(options->requestmask, DHO_NTPSERVER); - add_environ(options, "skip_hooks=ntp.conf", 0); + i = 1; break; - case 'R': - del_option_mask(options->requestmask, DHO_DNSSERVER); - del_option_mask(options->requestmask, DHO_DNSDOMAIN); - del_option_mask(options->requestmask, DHO_DNSSEARCH); - add_environ(options, "skip_hooks=resolv.conf", 0); + case 'U': + i = 2; break; - case 'S': - add_option_mask(options->requestmask, DHO_MSCSR); - break; - case 'Y': - del_option_mask(options->requestmask, DHO_NISSERVER); - del_option_mask(options->requestmask, DHO_NISDOMAIN); - add_environ(options, "skip_hooks=yp.conf", 0); - break; -#endif - default: - i = parse_option(opt, optarg, options); - if (i == 1) - break; - if (i == 0) - usage(); - goto abort; + case 'V': + print_options(); + exit(EXIT_SUCCESS); + case '?': + usage(); + exit(EXIT_FAILURE); } } + margv = argv; + margc = argc; + if_options = read_config(cffile, NULL, NULL, NULL); + opt = add_options(if_options, argc, argv); + if (opt != 1) { + if (opt == 0) + usage(); + exit(EXIT_FAILURE); + } + options = if_options->options; + if (i != 0) { + if (i == 1) + options |= DHCPCD_TEST; + else + options |= DHCPCD_DUMPLEASE; + options |= DHCPCD_PERSISTENT; + options &= ~DHCPCD_DAEMONISE; + } + #ifdef THERE_IS_NO_FORK - options->options &= ~DHCPCD_DAEMONISE; -#endif - -#ifndef ANDROID - /* android runs us as user "dhcp" */ - if (geteuid()) - logger(LOG_WARNING, PACKAGE " will not work correctly unless" - " run as root"); + options &= ~DHCPCD_DAEMONISE; #endif - if (options->options & DHCPCD_TEST) { - if (options->options & DHCPCD_REQUEST || - options->options & DHCPCD_INFORM) { - logger(LOG_ERR, - "cannot test with --inform or --request"); - goto abort; + if (options & DHCPCD_DEBUG) + setlogmask(LOG_UPTO(LOG_DEBUG)); + if (options & DHCPCD_QUIET) + close(STDERR_FILENO); + + if (!(options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { + /* If we have any other args, we should run as a single dhcpcd + * instance for that interface. */ + len = strlen(PIDFILE) + IF_NAMESIZE + 2; + pidfile = xmalloc(len); + if (optind == argc - 1) + snprintf(pidfile, len, PIDFILE, "-", argv[optind]); + else { + snprintf(pidfile, len, PIDFILE, "", ""); + options |= DHCPCD_MASTER; } + } - if (options->options & DHCPCD_LASTLEASE) { - logger(LOG_ERR, "cannot test with --lastlease"); - goto abort; - } + if (chdir("/") == -1) + syslog(LOG_ERR, "chdir `/': %m"); + atexit(cleanup); - if (sig != 0) { - logger(LOG_ERR, - "cannot test with --release or --renew"); - goto abort; + if (options & DHCPCD_DUMPLEASE) { + if (optind != argc - 1) { + syslog(LOG_ERR, "dumplease requires an interface"); + exit(EXIT_FAILURE); } + ifaces = iface = xzalloc(sizeof(*iface)); + strlcpy(iface->name, argv[optind], sizeof(iface->name)); + snprintf(iface->leasefile, sizeof(iface->leasefile), + LEASEFILE, iface->name); + iface->state = xzalloc(sizeof(*iface->state)); + iface->state->options = xzalloc(sizeof(*iface->state->options)); + strlcpy(iface->state->options->script, if_options->script, + sizeof(iface->state->options->script)); + iface->state->new = read_lease(iface); + if (iface->state->new == NULL && errno == ENOENT) { + strlcpy(iface->leasefile, argv[optind], + sizeof(iface->leasefile)); + iface->state->new = read_lease(iface); + } + if (iface->state->new == NULL) { + if (errno == ENOENT) + syslog(LOG_ERR, "%s: no lease to dump", + iface->name); + exit(EXIT_FAILURE); + } + iface->state->reason = "DUMP"; + run_script(iface); + exit(EXIT_SUCCESS); } - prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3)); - snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface); - setlogprefix(prefix); - snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE, - options->interface); - free(prefix); - - if (options->request_address.s_addr == 0 && - (options->options & DHCPCD_INFORM || - options->options & DHCPCD_REQUEST)) - { - errno = 0; - if (get_address(options->interface, - &options->request_address, - &options->request_netmask) != 1) - { - if (errno) - logger(LOG_ERR, "get_address: %s", - strerror(errno)); - else - logger(LOG_ERR, "no existing address"); - goto abort; + if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) { + control_fd = open_control(); + if (control_fd != -1) { + syslog(LOG_INFO, + "sending commands to master dhcpcd process"); + i = send_control(argc, argv); + if (i > 0) { + syslog(LOG_DEBUG, "send OK"); + exit(EXIT_SUCCESS); + } else { + syslog(LOG_ERR, "failed to send commands"); + exit(EXIT_FAILURE); + } + } else { + if (errno != ENOENT) + syslog(LOG_ERR, "open_control: %m"); } } - if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) { - logger(LOG_ERR, - "you are not allowed to request a link local address"); - goto abort; - } - if (chdir("/") == -1) - logger(LOG_ERR, "chdir `/': %s", strerror(errno)); - umask(022); - - if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) { -#ifdef ANDROID - char pidpropname[PROPERTY_KEY_MAX]; - char pidpropval[PROPERTY_VALUE_MAX]; - - i = -1; - if (snprintf(pidpropname, - sizeof(pidpropname), - "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { - goto abort; - } - property_get(pidpropname, pidpropval, NULL); - if (strlen(pidpropval) == 0) { - goto abort; - } - pid = atoi(pidpropval); -#else - i = -1; - pid = read_pid(options->pidfile); -#endif - if (pid != 0) - logger(LOG_INFO, "sending signal %d to pid %d", - sig, pid); + if (geteuid()) + syslog(LOG_WARNING, + PACKAGE " will not work correctly unless run as root"); - if (!pid || (i = kill(pid, sig))) { + if (sig != 0) { + pid = read_pid(); + if (pid != 0) + syslog(LOG_INFO, "sending signal %d to pid %d", + sig, pid); + if (pid == 0 || kill(pid, sig) != 0) { if (sig != SIGALRM) - logger(LOG_ERR, ""PACKAGE" not running"); - unlink(options->pidfile); - } - if (i == 0) { - if (sig == SIGALRM) { - retval = EXIT_SUCCESS; - goto abort; + syslog(LOG_ERR, ""PACKAGE" not running"); + if (pid != 0 && errno != ESRCH) { + syslog(LOG_ERR, "kill: %m"); + exit(EXIT_FAILURE); } + unlink(pidfile); + if (sig != SIGALRM) + exit(EXIT_FAILURE); + } else { + if (sig == SIGALRM) + exit(EXIT_SUCCESS); /* Spin until it exits */ - logger(LOG_INFO, "waiting for pid %d to exit", pid); + syslog(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(options->pidfile) == 0) { - retval = EXIT_SUCCESS; - break; - } + if (read_pid() == 0) + exit(EXIT_SUCCESS); } - if (retval != EXIT_SUCCESS) - logger(LOG_ERR, "pid %d failed to exit", pid); - goto abort; + syslog(LOG_ERR, "pid %d failed to exit", pid); + exit(EXIT_FAILURE); } - if (sig != SIGALRM) - goto abort; } - if (!(options->options & DHCPCD_TEST) && - !(options->options & DHCPCD_DAEMONISED)) - { -#ifdef ANDROID - char pidpropname[PROPERTY_KEY_MAX]; - char pidpropval[PROPERTY_VALUE_MAX]; -#endif -#ifndef ANDROID - if ((pid = read_pid(options->pidfile)) > 0 && + if (!(options & DHCPCD_TEST)) { + if ((pid = read_pid()) > 0 && kill(pid, 0) == 0) { - logger(LOG_ERR, ""PACKAGE - " already running on pid %d (%s)", - pid, options->pidfile); - goto abort; + syslog(LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, pidfile); + exit(EXIT_FAILURE); } -#endif - pid_fd = open(options->pidfile, - O_WRONLY | O_CREAT | O_NONBLOCK, 0664); - if (pid_fd == -1) { - logger(LOG_ERR, "open `%s': %s", - options->pidfile, strerror(errno)); - goto abort; + + /* Ensure we have the needed directories */ + if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) { + syslog(LOG_ERR, "mkdir `%s': %m", RUNDIR); + exit(EXIT_FAILURE); + } + if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) { + syslog(LOG_ERR, "mkdir `%s': %m", DBDIR); + exit(EXIT_FAILURE); } + pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); + if (pidfd == -1) { + syslog(LOG_ERR, "open `%s': %m", pidfile); + exit(EXIT_FAILURE); + } /* Lock the file so that only one instance of dhcpcd runs * on an interface */ - if (flock(pid_fd, LOCK_EX | LOCK_NB) == -1) { - logger(LOG_ERR, "flock `%s': %s", - options->pidfile, strerror(errno)); - goto abort; + if (flock(pidfd, LOCK_EX | LOCK_NB) == -1) { + syslog(LOG_ERR, "flock `%s': %m", pidfile); + exit(EXIT_FAILURE); + } + if (set_cloexec(pidfd) == -1) + exit(EXIT_FAILURE); + writepid(pidfd, getpid()); + } + + syslog(LOG_INFO, "version " VERSION " starting"); + + if ((signal_fd = signal_init()) == -1) + exit(EXIT_FAILURE); + if (signal_setup() == -1) + exit(EXIT_FAILURE); + add_event(signal_fd, handle_signal, NULL); + + if (options & DHCPCD_MASTER) { + if (start_control() == -1) { + syslog(LOG_ERR, "start_control: %m"); + exit(EXIT_FAILURE); } + } - if (set_cloexec(pid_fd) == -1) - goto abort; -#ifdef ANDROID - if (snprintf(pidpropname, - sizeof(pidpropname), - "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { - goto abort; - } - if (snprintf(pidpropval, sizeof(pidpropval), "%d", getpid()) >= PROPERTY_VALUE_MAX) { - goto abort; - } - property_set(pidpropname, pidpropval); -#else - writepid(pid_fd, getpid()); -#endif - logger(LOG_INFO, PACKAGE " " VERSION " starting"); + if (init_sockets() == -1) { + syslog(LOG_ERR, "init_socket: %m"); + exit(EXIT_FAILURE); + } + if (if_options->options & DHCPCD_LINK) { + linkfd = open_link_socket(); + if (linkfd == -1) + syslog(LOG_ERR, "open_link_socket: %m"); + else + add_event(linkfd, handle_link, NULL); } - /* Terminate the encapsulated options */ - if (options->vendor[0]) { - options->vendor[0]++; - options->vendor[options->vendor[0]] = DHO_END; + ifc = argc - optind; + ifv = argv + optind; + + /* When running dhcpcd against a single interface, we need to retain + * the old behaviour of waiting for an IP address */ + if (ifc == 1) + options |= DHCPCD_WAITIP; + + ifaces = discover_interfaces(ifc, ifv); + for (i = 0; i < ifc; i++) { + for (iface = ifaces; iface; iface = iface->next) + if (strcmp(iface->name, ifv[i]) == 0) + break; + if (!iface) + syslog(LOG_ERR, "%s: interface not found or invalid", + ifv[i]); + } + if (!ifaces) { + if (ifc == 0) + syslog(LOG_ERR, "no valid interfaces found"); + else + exit(EXIT_FAILURE); + if (!(options & DHCPCD_LINK)) { + syslog(LOG_ERR, + "aborting as link detection is disabled"); + exit(EXIT_FAILURE); + } } - if (dhcp_run(options, &pid_fd) == 0) - retval = EXIT_SUCCESS; + if (options & DHCPCD_BACKGROUND) + daemonise(); -abort: - /* If we didn't daemonise then we need to punt the pidfile now */ - if (pid_fd > -1) { - close(pid_fd); - unlink(options->pidfile); + opt = 0; + for (iface = ifaces; iface; iface = iface->next) { + init_state(iface, argc, argv); + if (iface->carrier != LINK_DOWN) + opt = 1; } - if (options->environ) { - len = 0; - while (options->environ[len]) - free(options->environ[len++]); - free(options->environ); + + if (!(options & DHCPCD_BACKGROUND)) { + /* If we don't have a carrier, we may have to wait for a second + * before one becomes available if we brought an interface up. */ + if (opt == 0 && + options & DHCPCD_LINK && + options & DHCPCD_WAITUP && + !(options & DHCPCD_WAITIP)) + { + ts.tv_sec = 1; + ts.tv_nsec = 0; + nanosleep(&ts, NULL); + for (iface = ifaces; iface; iface = iface->next) { + handle_carrier(iface->name); + if (iface->carrier != LINK_DOWN) { + opt = 1; + break; + } + } + } + if (opt == 0 && + options & DHCPCD_LINK && + !(options & DHCPCD_WAITIP)) + { + syslog(LOG_WARNING, "no interfaces have a carrier"); + daemonise(); + } else if (if_options->timeout > 0) { + if (options & DHCPCD_IPV4LL) + options |= DHCPCD_TIMEOUT_IPV4LL; + add_timeout_sec(if_options->timeout, + handle_exit_timeout, NULL); + } } - free(options->blacklist); - free(options); - exit(retval); - /* NOTREACHED */ + free_options(if_options); + if_options = NULL; + + sort_interfaces(); + for (iface = ifaces; iface; iface = iface->next) + add_timeout_sec(0, start_interface, iface); + + start_eloop(); + exit(EXIT_SUCCESS); } diff --git a/dhcpcd.conf b/dhcpcd.conf index cce1795..eb625a7 100644 --- a/dhcpcd.conf +++ b/dhcpcd.conf @@ -1,13 +1,23 @@ # A sample configuration for dhcpcd. # See dhcpcd.conf(5) for details. -# dhcpcd-run-hooks uses these options. -option domain_name_servers, domain_name, domain_search, host_name +# Inform the DHCP server of our hostname for DDNS. +hostname +# To share the DHCP lease across OSX and Windows a ClientID is needed. +# Enabling this may get a different lease than the kernel DHCP client. +# Some upstream DHCP servers may also require a ClientID, such as FRITZ!Box. +#clientid -# Most distros have ntp support. +# 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. +option interface_mtu +# A ServerID is required by RFC2131. +require dhcp_server_identifier -# We should behave nicely on networks and respect their MTU. -# However, a lot of buggy DHCP servers set invalid MTUs so this is not -# enabled by default. -#option interface_mtu +# 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 b/dhcpcd.conf.5 index 217ecba..03b3521 100644 --- a/dhcpcd.conf.5 +++ b/dhcpcd.conf.5 @@ -1,4 +1,4 @@ -.\" Copyright 2006-2008 Roy Marples +.\" Copyright (c) 2006-2010 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,11 +22,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 18, 2008 +.Dd January 28, 2010 .Dt DHCPCD.CONF 5 SMM +.Os .Sh NAME .Nm dhcpcd.conf -.Nd dhcpcd configuration file +.Nd dhcpcd configuration file .Sh DESCRIPTION Although .Nm dhcpcd @@ -43,14 +44,71 @@ 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 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 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 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 . .It Ic clientid Ar string -Change the default clientid sent from the interface hardware address. +Send the +.Ar clientid . If the string is of the format 01:02:03 then it is encoded as hex. -If not set then none is sent. +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 @@ -60,20 +118,24 @@ compliant clientid. This requires persistent storage and not all DHCP servers work with it so it's not enabled by default. The duid generated will be held in -.Pa /system/etc/dhcpcd/dhcpcd.duid +.Pa /etc/dhcpcd.duid and should not be copied to other hosts. +.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 specified -.Ar hostname -to the DHCP server so it can be registered in DNS. If +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 -if a FQDN (ie, contains a .) then it will be encoded as such. +is a FQDN (ie, contains a .) then it will be encoded as such. .It Ic fqdn Op none | ptr | both none disables FQDN encoding, ptr just asks the DHCP server to update the PTR record of the host in DNS whereas both also updates the A record. -The current hostname or the hostname specified using the -.Ic hostname -option must be a FQDN. .Nm dhcpcd itself never does any DNS updates. .Nm dhcpcd @@ -85,6 +147,12 @@ Subsequent options are only parsed for this .It Ic leasetime Ar seconds Request a leasetime of .Ar seconds . +.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 noarp Don't send any ARP requests. This also disables IPv4LL. @@ -94,6 +162,11 @@ Don't install any default routes. 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 noipv4ll Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP. See @@ -110,7 +183,33 @@ 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 options seperated by commas, spaces or more option lines. +You can specify more options separated by commas, spaces or more option lines. +.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. +The default is 10 seconds. +A setting if 0 seconds causes +.Nm dhcpcd +to skip the reboot phase and go straight into discover. +.It Ic release +.Nm dhcpcd +will release the lease prior to stopping the interface. .It Ic require Ar option Requires the .Ar option @@ -118,12 +217,42 @@ 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 seperated by commas, spaces or more require lines. +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 . .It Ic script Ar script Use .Ar script instead of the default -.Pa /system/etc/dhcpcd/dhcpcd-run-hooks . +.Pa /libexec/dhcpcd-run-hooks . +.It Ic ssid Ar ssid +Subsequent options are only parsed for this wireless +.Ar ssid . +.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 The default timeout for waiting for a DHCP response is 30 seconds which may be too long or too short and can be changed here. @@ -131,9 +260,12 @@ be too long or too short and can be changed here. Tag the DHCP messages with the userclass. You can specify more than one. .It Ic vendor Ar code , Ns Ar value -Add an enscapulated vendor option. +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. @@ -142,14 +274,23 @@ 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 Change the default vendorclassid sent from dhcpcd-version. If not set then none is sent. +.It Ic waitip +Wait for an address to be assigned before forking to the background. +.It Ic xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. .El .Sh SEE ALSO .Xr dhcpcd-run-hooks 8 , -.Xr dhcpcd 8 +.Xr dhcpcd 8 , +.Xr if_nametoindex 3 , +.Xr fnmatch 3 .Sh AUTHORS -.An Roy Marples +.An Roy Marples Aq roy@marples.name .Sh BUGS -Please report them to http://bugs.marples.name +Please report them to http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 9e0a023..c3bfa8f 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -1,4 +1,4 @@ -.\" Copyright 2006-2008 Roy Marples +.\" Copyright (c) 2006-2010 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,12 +22,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd November 18, 2008 +.Dd January 28, 2010 .Dt DHCPCD.CONF 5 SMM .Os .Sh NAME .Nm dhcpcd.conf -.Nd dhcpcd configuration file +.Nd dhcpcd configuration file .Sh DESCRIPTION Although .Nm dhcpcd @@ -44,17 +44,60 @@ 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 background Background immediately. This is useful for startup scripts which don't disable link messages for carrier status. -.It Ic blacklist Ar address -Ignores all DHCP messages which have this -.Ar address -as the server ID. -This may be expanded in future releases to ignore all packets -matching either the IP or hardware -.Ar address . +.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 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 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 . .It Ic clientid Ar string Send the .Ar clientid . @@ -77,18 +120,22 @@ not enabled by default. The duid generated will be held in .Pa @SYSCONFDIR@/dhcpcd.duid and should not be copied to other hosts. +.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 specified -.Ar hostname -to the DHCP server so it can be registered in DNS. If +Sends +.Ar hostname +to the DHCP server so it can be registered in DNS. +If .Ar hostname -if a FQDN (ie, contains a .) then it will be encoded as such. +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 fqdn Op none | ptr | both none disables FQDN encoding, ptr just asks the DHCP server to update the PTR record of the host in DNS whereas both also updates the A record. -The current hostname or the hostname specified using the -.Ic hostname -option must be a FQDN. .Nm dhcpcd itself never does any DNS updates. .Nm dhcpcd @@ -100,6 +147,12 @@ Subsequent options are only parsed for this .It Ic leasetime Ar seconds Request a leasetime of .Ar seconds . +.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 noarp Don't send any ARP requests. This also disables IPv4LL. @@ -109,6 +162,11 @@ Don't install any default routes. 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 noipv4ll Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP. See @@ -125,9 +183,33 @@ 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 options seperated by commas, spaces or more option lines. -.Ic quiet -Supress any dhcpcd output to the console, except for errors. +You can specify more options separated by commas, spaces or more option lines. +.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. +The default is 10 seconds. +A setting if 0 seconds causes +.Nm dhcpcd +to skip the reboot phase and go straight into discover. +.It Ic release +.Nm dhcpcd +will release the lease prior to stopping the interface. .It Ic require Ar option Requires the .Ar option @@ -135,12 +217,42 @@ 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 seperated by commas, spaces or more require lines. +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 . .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 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 The default timeout for waiting for a DHCP response is 30 seconds which may be too long or too short and can be changed here. @@ -148,9 +260,12 @@ be too long or too short and can be changed here. Tag the DHCP messages with the userclass. You can specify more than one. .It Ic vendor Ar code , Ns Ar value -Add an enscapulated vendor option. +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. @@ -159,14 +274,23 @@ 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 Change the default vendorclassid sent from dhcpcd-version. If not set then none is sent. +.It Ic waitip +Wait for an address to be assigned before forking to the background. +.It Ic xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. .El .Sh SEE ALSO .Xr dhcpcd-run-hooks 8 , -.Xr dhcpcd 8 +.Xr dhcpcd 8 , +.Xr if_nametoindex 3 , +.Xr fnmatch 3 .Sh AUTHORS -.An Roy Marples +.An Roy Marples Aq roy@marples.name .Sh BUGS Please report them to http://roy.marples.name/projects/dhcpcd diff --git a/dhcpcd.h b/dhcpcd.h index 7d93315..ec32474 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -28,68 +28,116 @@ #ifndef DHCPCD_H #define DHCPCD_H -#include #include - #include -#include #include -#include "common.h" - -#define DEFAULT_TIMEOUT 30 -#define DEFAULT_LEASETIME 3600 /* 1 hour */ - -#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ -#define VENDORCLASSID_MAX_LEN 48 -#define CLIENTID_MAX_LEN 48 -#define USERCLASS_MAX_LEN 255 -#define VENDOR_MAX_LEN 255 - -#define DHCPCD_ARP (1 << 0) -#define DHCPCD_DOMAIN (1 << 2) -#define DHCPCD_GATEWAY (1 << 3) -#define DHCPCD_LASTLEASE (1 << 7) -#define DHCPCD_INFORM (1 << 8) -#define DHCPCD_REQUEST (1 << 9) -#define DHCPCD_IPV4LL (1 << 10) -#define DHCPCD_DUID (1 << 11) -#define DHCPCD_PERSISTENT (1 << 12) -#define DHCPCD_DAEMONISE (1 << 14) -#define DHCPCD_DAEMONISED (1 << 15) -#define DHCPCD_TEST (1 << 16) -#define DHCPCD_FORKED (1 << 17) -#define DHCPCD_HOSTNAME (1 << 18) -#define DHCPCD_CLIENTID (1 << 19) -#define DHCPCD_LINK (1 << 20) -#define DHCPCD_BACKGROUND (1 << 21) - -struct options { - char interface[IF_NAMESIZE]; +#include "control.h" +#include "dhcp.h" +#include "if-options.h" + +#define HWADDR_LEN 20 +#define IF_SSIDSIZE 33 +#define PROFILE_LEN 64 + +enum DHS { + DHS_INIT, + DHS_DISCOVER, + DHS_REQUEST, + DHS_BOUND, + DHS_RENEW, + DHS_REBIND, + DHS_REBOOT, + DHS_INFORM, + DHS_RENEW_REQUESTED, + DHS_INIT_IPV4LL, + DHS_PROBE +}; + +#define LINK_UP 1 +#define LINK_UNKNOWN 0 +#define LINK_DOWN -1 + +struct if_state { + enum DHS state; + char profile[PROFILE_LEN]; + struct if_options *options; + 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 probes; + int claims; + int conflicts; + time_t defend; + struct in_addr fail; + size_t arping_index; +}; + +struct interface { + char name[IF_NAMESIZE]; + struct if_state *state; + + int flags; + sa_family_t family; + unsigned char hwaddr[HWADDR_LEN]; + size_t hwlen; int metric; - uint8_t requestmask[256 / 8]; - uint8_t requiremask[256 / 8]; - uint8_t nomask[256 / 8]; - uint32_t leasetime; - time_t timeout; - int options; - - struct in_addr request_address; - struct in_addr request_netmask; - - char **environ; - char script[PATH_MAX]; - char pidfile[PATH_MAX]; - - char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the lenth */ - int fqdn; - uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 2]; - char 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; + int carrier; + int wireless; + char ssid[IF_SSIDSIZE]; + + int raw_fd; + int udp_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; + + char leasefile[PATH_MAX]; + time_t start_uptime; + + unsigned char *clientid; + + struct interface *next; }; + +extern int pidfd; +extern int options; +extern int ifac; +extern char **ifav; +extern int ifdc; +extern char **ifdv; +extern struct interface *ifaces; + +struct interface *find_interface(const char *); +int handle_args(struct fd_list *, int, char **); +void handle_interface(int, const char *); +void handle_hwaddr(const char *, unsigned char *, size_t); +void handle_ifa(int, const char *, + struct in_addr *, struct in_addr *, struct in_addr *); +void handle_exit_timeout(void *); +void start_interface(void *); +void start_discover(void *); +void start_request(void *); +void start_renew(void *); +void start_rebind(void *); +void start_reboot(struct interface *); +void start_expire(void *); +void send_decline(struct interface *); +void open_sockets(struct interface *); +void close_sockets(struct interface *); +void drop_config(struct interface *, const char *); +int select_profile(struct interface *, const char *); + #endif diff --git a/duid.c b/duid.c new file mode 100644 index 0000000..d67e45e --- /dev/null +++ b/duid.c @@ -0,0 +1,100 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define THIRTY_YEARS_IN_SECONDS 946707779 + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "duid.h" +#include "net.h" + +size_t +get_duid(unsigned char *duid, const struct interface *iface) +{ + FILE *f; + uint16_t type = 0; + uint16_t hw = 0; + uint32_t ul; + time_t t; + int x = 0; + unsigned char *p = duid; + size_t len = 0; + char *line; + + /* 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 ((f = fopen(DUID, "r"))) { + while ((line = get_line(f))) { + len = hwaddr_aton(NULL, line); + if (len && len <= DUID_LEN) { + hwaddr_aton(duid, line); + break; + } + len = 0; + } + fclose(f); + if (len) + return len; + } else { + if (errno != ENOENT) + return 0; + } + + /* No file? OK, lets make one based on our interface */ + if (!(f = fopen(DUID, "w"))) + return 0; + type = htons(1); /* DUI-D-LLT */ + memcpy(p, &type, 2); + p += 2; + hw = htons(iface->family); + memcpy(p, &hw, 2); + p += 2; + /* time returns seconds from jan 1 1970, but DUID-LLT is + * seconds from jan 1 2000 modulo 2^32 */ + t = time(NULL) - THIRTY_YEARS_IN_SECONDS; + ul = htonl(t & 0xffffffff); + memcpy(p, &ul, 4); + p += 4; + /* Finally, add the MAC address of the interface */ + memcpy(p, iface->hwaddr, iface->hwlen); + p += iface->hwlen; + len = p - duid; + x = fprintf(f, "%s\n", hwaddr_ntoa(duid, len)); + fclose(f); + /* Failed to write the duid? scrub it, we cannot use it */ + if (x < 1) { + len = 0; + unlink(DUID); + } + return len; +} diff --git a/duid.h b/duid.h new file mode 100644 index 0000000..98c1bbd --- /dev/null +++ b/duid.h @@ -0,0 +1,35 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DUID_H +#define DUID_H + +#include "net.h" + +size_t get_duid(unsigned char *duid, const struct interface *iface); + +#endif diff --git a/eloop.c b/eloop.c new file mode 100644 index 0000000..a5d08cb --- /dev/null +++ b/eloop.c @@ -0,0 +1,366 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "eloop.h" + +static struct timeval now; + +static struct event { + int fd; + void (*callback)(void *); + void *arg; + struct event *next; +} *events; +static struct event *free_events; + +static struct timeout { + struct timeval when; + void (*callback)(void *); + void *arg; + int queue; + struct timeout *next; +} *timeouts; +static struct timeout *free_timeouts; + +static struct pollfd *fds; +static size_t fds_len; + +void +add_event(int fd, void (*callback)(void *), void *arg) +{ + struct event *e, *last = NULL; + + /* We should only have one callback monitoring the fd */ + for (e = events; e; e = e->next) { + if (e->fd == fd) { + e->callback = callback; + e->arg = arg; + return; + } + last = e; + } + + /* Allocate a new event if no free ones already allocated */ + if (free_events) { + e = free_events; + free_events = e->next; + } else + e = xmalloc(sizeof(*e)); + e->fd = fd; + e->callback = callback; + e->arg = arg; + e->next = NULL; + if (last) + last->next = e; + else + events = e; +} + +void +delete_event(int fd) +{ + struct event *e, *last = NULL; + + for (e = events; e; e = e->next) { + if (e->fd == fd) { + if (last) + last->next = e->next; + else + events = e->next; + e->next = free_events; + free_events = e; + break; + } + last = e; + } +} + +void +add_q_timeout_tv(int queue, + const struct timeval *when, void (*callback)(void *), void *arg) +{ + struct timeval w; + struct timeout *t, *tt = NULL; + + get_monotonic(&now); + timeradd(&now, when, &w); + /* Check for time_t overflow. */ + if (timercmp(&w, &now, <)) { + errno = ERANGE; + return; + } + + /* Remove existing timeout if present */ + for (t = timeouts; t; t = t->next) { + if (t->callback == callback && t->arg == arg) { + if (tt) + tt->next = t->next; + else + timeouts = t->next; + break; + } + tt = t; + } + + if (!t) { + /* No existing, so allocate or grab one from the free pool */ + if (free_timeouts) { + t = free_timeouts; + free_timeouts = t->next; + } else + t = xmalloc(sizeof(*t)); + } + + t->when.tv_sec = w.tv_sec; + t->when.tv_usec = w.tv_usec; + t->callback = callback; + t->arg = arg; + t->queue = queue; + + /* The timeout list should be in chronological order, + * soonest first. + * This is the easiest algorithm - check the head, then middle + * and finally the end. */ + if (!timeouts || timercmp(&t->when, &timeouts->when, <)) { + t->next = timeouts; + timeouts = t; + return; + } + for (tt = timeouts; tt->next; tt = tt->next) + if (timercmp(&t->when, &tt->next->when, <)) { + t->next = tt->next; + tt->next = t; + return; + } + tt->next = t; + t->next = NULL; +} + +void +add_q_timeout_sec(int queue, time_t when, void (*callback)(void *), void *arg) +{ + struct timeval tv; + + tv.tv_sec = when; + tv.tv_usec = 0; + add_q_timeout_tv(queue, &tv, callback, arg); +} + +/* This deletes all timeouts for the interface EXCEPT for ones with the + * callbacks given. Handy for deleting everything apart from the expire + * timeout. */ +static void +v_delete_q_timeouts(int queue, void *arg, void (*callback)(void *), va_list v) +{ + struct timeout *t, *tt, *last = NULL; + va_list va; + void (*f)(void *); + + for (t = timeouts; t && (tt = t->next, 1); t = tt) { + if (t->queue == queue && t->arg == arg && + t->callback != callback) + { + va_copy(va, v); + while ((f = va_arg(va, void (*)(void *)))) + if (f == t->callback) + break; + va_end(va); + if (!f) { + if (last) + last->next = t->next; + else + timeouts = t->next; + t->next = free_timeouts; + free_timeouts = t; + continue; + } + } + last = t; + } +} + +void +delete_q_timeouts(int queue, void *arg, void (*callback)(void *), ...) +{ + va_list va; + + va_start(va, callback); + v_delete_q_timeouts(queue, arg, callback, va); + va_end(va); +} + +void +delete_q_timeout(int queue, void (*callback)(void *), void *arg) +{ + struct timeout *t, *tt, *last = NULL; + + for (t = timeouts; t && (tt = t->next, 1); t = tt) { + if (t->queue == queue && t->arg == arg && + (!callback || t->callback == callback)) + { + if (last) + last->next = t->next; + else + timeouts = t->next; + t->next = free_timeouts; + free_timeouts = t; + continue; + } + last = t; + } +} + +#ifdef DEBUG_MEMORY +/* Define this to free all malloced memory. + * Normally we don't do this as the OS will do it for us at exit, + * but it's handy for debugging other leaks in valgrind. */ +static void +cleanup(void) +{ + struct event *e; + struct timeout *t; + + while (events) { + e = events->next; + free(events); + events = e; + } + while (free_events) { + e = free_events->next; + free(free_events); + free_events = e; + } + while (timeouts) { + t = timeouts->next; + free(timeouts); + timeouts = t; + } + while (free_timeouts) { + t = free_timeouts->next; + free(free_timeouts); + free_timeouts = t; + } + free(fds); +} +#endif + +_noreturn void +start_eloop(void) +{ + int msecs, n; + nfds_t nfds, i; + struct event *e; + struct timeout *t; + struct timeval tv; + +#ifdef DEBUG_MEMORY + atexit(cleanup); +#endif + + for (;;) { + /* Run all timeouts first. + * When we have one that has not yet occured, + * calculate milliseconds until it does for use in poll. */ + if (timeouts) { + if (timercmp(&now, &timeouts->when, >)) { + t = timeouts; + timeouts = timeouts->next; + t->callback(t->arg); + t->next = free_timeouts; + free_timeouts = t; + continue; + } + timersub(&timeouts->when, &now, &tv); + if (tv.tv_sec > INT_MAX / 1000 || + (tv.tv_sec == INT_MAX / 1000 && + (tv.tv_usec + 999) / 1000 > INT_MAX % 1000)) + msecs = INT_MAX; + else + msecs = tv.tv_sec * 1000 + + (tv.tv_usec + 999) / 1000; + } else + /* No timeouts, so wait forever. */ + msecs = -1; + + /* Allocate memory for our pollfds as and when needed. + * We don't bother shrinking it. */ + nfds = 0; + for (e = events; e; e = e->next) + nfds++; + if (msecs == -1 && nfds == 0) { + syslog(LOG_ERR, "nothing to do"); + exit(EXIT_FAILURE); + } + if (nfds > fds_len) { + free(fds); + /* Allocate 5 more than we need for future use */ + fds_len = nfds + 5; + fds = xmalloc(sizeof(*fds) * fds_len); + } + nfds = 0; + for (e = events; e; e = e->next) { + fds[nfds].fd = e->fd; + fds[nfds].events = POLLIN; + fds[nfds].revents = 0; + nfds++; + } + n = poll(fds, nfds, msecs); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) { + get_monotonic(&now); + continue; + } + syslog(LOG_ERR, "poll: %m"); + exit(EXIT_FAILURE); + } + + /* Get the now time and process any triggered events. */ + get_monotonic(&now); + if (n == 0) + continue; + for (i = 0; i < nfds; i++) { + if (!(fds[i].revents & (POLLIN | POLLHUP))) + continue; + for (e = events; e; e = e->next) { + if (e->fd == fds[i].fd) { + e->callback(e->arg); + break; + } + } + } + } +} diff --git a/eloop.h b/eloop.h new file mode 100644 index 0000000..02c9438 --- /dev/null +++ b/eloop.h @@ -0,0 +1,51 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ELOOP_H +#define ELOOP_H + +#include + +#ifndef ELOOP_QUEUE + #define ELOOP_QUEUE 0 +#endif + +#define add_timeout_tv(a, b, c) add_q_timeout_tv(ELOOP_QUEUE, a, b, c) +#define add_timeout_sec(a, b, c) add_q_timeout_sec(ELOOP_QUEUE, a, b, c) +#define delete_timeout(a, b) delete_q_timeout(ELOOP_QUEUE, a, b) +#define delete_timeouts(a, ...) delete_q_timeouts(ELOOP_QUEUE, a, __VA_ARGS__) + +void add_event(int fd, void (*)(void *), void *); +void delete_event(int fd); +void add_q_timeout_sec(int queue, time_t, void (*)(void *), void *); +void add_q_timeout_tv(int queue, const struct timeval *, void (*)(void *), + void *); +void delete_q_timeout(int, void (*)(void *), void *); +void delete_q_timeouts(int, void *, void (*)(void *), ...); +void start_eloop(void); + +#endif diff --git a/if-bsd.c b/if-bsd.c index d0ff246..462ec2a 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,44 +25,123 @@ * SUCH DAMAGE. */ -#include -#include -#include #include #include +#include +#include +#include +#include #include +#include #include -#include #include #include +#ifdef __DragonFly__ +# include +#elif __APPLE__ + /* FIXME: Add apple includes so we can work out SSID */ +#else +# include +#endif #include +#include #include #include #include #include +#include #include #include "config.h" #include "common.h" +#include "configure.h" #include "dhcp.h" +#include "if-options.h" #include "net.h" -/* Darwin doesn't define this for some very odd reason */ -#ifndef SA_SIZE -# define SA_SIZE(sa) \ - ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \ - sizeof(long) : \ - 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) ) +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) +#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) + +/* FIXME: Why do we need to check for sa_family 255 */ +#define COPYOUT(sin, sa) \ + sin.s_addr = ((sa) != NULL) ? \ + (((struct sockaddr_in *)(void *)sa)->sin_addr).s_addr : 0 + +static int r_fd = -1; +static char *link_buf; +static ssize_t link_buflen; + +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 +init_sockets(void) +{ + if ((socket_afnet = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + set_cloexec(socket_afnet); + if ((r_fd = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) + return -1; + set_cloexec(r_fd); + return 0; +} + +int +getifssid(const char *ifname, char *ssid) +{ + int 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 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(socket_afnet, SIOCG80211NWID, &ifr) == 0) { + 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; + ireq.i_data = &nwid; + if (ioctl(socket_afnet, SIOCG80211, &ireq) == 0) { + retval = ireq.i_len; + memcpy(ssid, nwid, ireq.i_len); + ssid[ireq.i_len] = '\0'; + } +#endif + return retval; +} + int -if_address(const char *ifname, const struct in_addr *address, - const struct in_addr *netmask, const struct in_addr *broadcast, - int action) +if_address(const struct interface *iface, const struct in_addr *address, + const struct in_addr *netmask, const struct in_addr *broadcast, + int action) { - int s; int retval; struct ifaliasreq ifa; union { @@ -70,39 +149,36 @@ if_address(const char *ifname, const struct in_addr *address, struct sockaddr_in *sin; } _s; - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) - return -1; - memset(&ifa, 0, sizeof(ifa)); - strlcpy(ifa.ifra_name, ifname, sizeof(ifa.ifra_name)); + strlcpy(ifa.ifra_name, iface->name, sizeof(ifa.ifra_name)); -#define ADDADDR(_var, _addr) \ - _s.sa = &_var; \ - _s.sin->sin_family = AF_INET; \ - _s.sin->sin_len = sizeof(*_s.sin); \ - memcpy(&_s.sin->sin_addr, _addr, sizeof(_s.sin->sin_addr)); +#define ADDADDR(_var, _addr) { \ + _s.sa = &_var; \ + _s.sin->sin_family = AF_INET; \ + _s.sin->sin_len = sizeof(*_s.sin); \ + memcpy(&_s.sin->sin_addr, _addr, sizeof(_s.sin->sin_addr)); \ + } ADDADDR(ifa.ifra_addr, address); ADDADDR(ifa.ifra_mask, netmask); - if (action >= 0) { + if (action >= 0 && broadcast) { ADDADDR(ifa.ifra_broadaddr, broadcast); } #undef ADDADDR if (action < 0) - retval = ioctl(s, SIOCDIFADDR, &ifa); + retval = ioctl(socket_afnet, SIOCDIFADDR, &ifa); else - retval = ioctl(s, SIOCAIFADDR, &ifa); - close(s); + retval = ioctl(socket_afnet, SIOCAIFADDR, &ifa); return retval; } +/* ARGSUSED4 */ int if_route(const struct interface *iface, const struct in_addr *dest, - const struct in_addr *net, const struct in_addr *gate, - _unused int metric, int action) + const struct in_addr *net, const struct in_addr *gate, + _unused int metric, int action) { - int s; union sockunion { struct sockaddr sa; struct sockaddr_in sin; @@ -121,21 +197,18 @@ if_route(const struct interface *iface, const struct in_addr *dest, size_t l; int retval = 0; -#define ADDSU(_su) { \ - l = SA_SIZE(&(_su.sa)); \ - 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); \ - memcpy (&su.sin.sin_addr, _addr, sizeof(su.sin.sin_addr)); \ - ADDSU(su); \ -} - - if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) - return -1; +#define ADDSU(_su) { \ + l = ROUNDUP(_su.sa.sa_len); \ + memcpy(bp, &(_su), l); \ + bp += l; \ + } +#define ADDADDR(_a) { \ + memset (&su, 0, sizeof(su)); \ + su.sin.sin_family = AF_INET; \ + su.sin.sin_len = sizeof(su.sin); \ + memcpy (&su.sin.sin_addr, _a, sizeof(su.sin.sin_addr)); \ + ADDSU(su); \ + } memset(&rtm, 0, sizeof(rtm)); rtm.hdr.rtm_version = RTM_VERSION; @@ -194,41 +267,72 @@ if_route(const struct interface *iface, const struct in_addr *dest, ADDADDR(&iface->addr); rtm.hdr.rtm_msglen = l = bp - (char *)&rtm; - if (write(s, &rtm, l) == -1) + if (write(r_fd, &rtm, l) == -1) retval = -1; - close(s); return retval; } int -open_link_socket(struct interface *iface) +open_link_socket(void) { int fd; fd = socket(PF_ROUTE, SOCK_RAW, 0); - if (fd == -1) - return -1; - set_cloexec(fd); - if (iface->link_fd != -1) - close(iface->link_fd); - iface->link_fd = fd; - return 0; + if (fd != -1) { + set_cloexec(fd); + set_nonblock(fd); + } + return fd; +} + +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; +#ifdef DEBUG + printf ("got %d %d %s\n", i, sa[i]->sa_family, + inet_ntoa(((struct sockaddr_in *)sa[i])-> + sin_addr)); +#endif + ADVANCE(cp, sa[i]); + } else + sa[i] = NULL; + } } -#define BUFFER_LEN 2048 int -link_changed(struct interface *iface) +manage_link(int fd) { - char buffer[2048], *p; + char *p, *e, *cp; + char ifname[IF_NAMESIZE]; ssize_t bytes; struct rt_msghdr *rtm; + struct if_announcemsghdr *ifan; struct if_msghdr *ifm; - int i; + struct ifa_msghdr *ifam; + struct rt rt; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + int len; +#ifdef RTM_CHGADDR + struct sockaddr_dl sdl; + unsigned char *hwaddr; +#endif - if ((i = if_nametoindex(iface->name)) == -1) - return -1; for (;;) { - bytes = recv(iface->link_fd, buffer, BUFFER_LEN, MSG_DONTWAIT); + if (ioctl(fd, FIONREAD, &len) == -1) + return -1; + if (link_buflen < len) { + p = realloc(link_buf, len); + if (p == NULL) + return -1; + link_buf = p; + link_buflen = len; + } + bytes = read(fd, link_buf, link_buflen); if (bytes == -1) { if (errno == EAGAIN) return 0; @@ -236,20 +340,85 @@ link_changed(struct interface *iface) continue; return -1; } - for (p = buffer; bytes > 0; - bytes -= ((struct rt_msghdr *)p)->rtm_msglen, - p += ((struct rt_msghdr *)p)->rtm_msglen) - { - rtm = (struct rt_msghdr *)p; - if (rtm->rtm_type != RTM_IFINFO) - continue; - ifm = (struct if_msghdr *)p; - if (ifm->ifm_index != i) - continue; - - /* Link changed */ - return 1; + e = link_buf + bytes; + for (p = link_buf; p < e; p += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)p; + switch(rtm->rtm_type) { +#ifdef RTM_IFANNOUNCE + case RTM_IFANNOUNCE: + ifan = (struct if_announcemsghdr *)(void *)p; + switch(ifan->ifan_what) { + case IFAN_ARRIVAL: + handle_interface(1, ifan->ifan_name); + break; + case IFAN_DEPARTURE: + handle_interface(-1, ifan->ifan_name); + break; + } + break; +#endif + case RTM_IFINFO: + ifm = (struct if_msghdr *)(void *)p; + memset(ifname, 0, sizeof(ifname)); + if (if_indextoname(ifm->ifm_index, ifname)) + handle_interface(0, ifname); + break; + case RTM_DELETE: + if (!(rtm->rtm_addrs & RTA_DST) || + !(rtm->rtm_addrs & RTA_GATEWAY) || + !(rtm->rtm_addrs & RTA_NETMASK)) + break; + if (rtm->rtm_pid == getpid()) + break; + cp = (char *)(void *)(rtm + 1); + sa = (struct sockaddr *)(void *)cp; + if (sa->sa_family != AF_INET) + break; + get_addrs(rtm->rtm_addrs, cp, rti_info); + rt.iface = NULL; + rt.next = NULL; + COPYOUT(rt.dest, rti_info[RTAX_DST]); + COPYOUT(rt.net, rti_info[RTAX_NETMASK]); + COPYOUT(rt.gate, rti_info[RTAX_GATEWAY]); + route_deleted(&rt); + break; +#ifdef RTM_CHGADDR + case RTM_CHGADDR: /* FALLTHROUGH */ +#endif + case RTM_DELADDR: /* FALLTHROUGH */ + case RTM_NEWADDR: + ifam = (struct ifa_msghdr *)(void *)p; + if (!if_indextoname(ifam->ifam_index, ifname)) + 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) { +#ifdef RTM_CHGADDR + case AF_LINK: + if (rtm->rtm_type != RTM_CHGADDR) + break; + memcpy(&sdl, rti_info[RTAX_IFA], + rti_info[RTAX_IFA]->sa_len); + hwaddr = xmalloc(sdl.sdl_alen); + memcpy(hwaddr, LLADDR(&sdl), + sdl.sdl_alen); + handle_hwaddr(ifname, hwaddr, + sdl.sdl_alen); + break; +#endif + 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]); + handle_ifa(rtm->rtm_type, ifname, + &rt.dest, &rt.net, &rt.gate); + break; + } + break; + } } } - return 0; } diff --git a/if-linux-wireless.c b/if-linux-wireless.c new file mode 100644 index 0000000..dce1892 --- /dev/null +++ b/if-linux-wireless.c @@ -0,0 +1,89 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2009-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * THIS IS A NASTY HACK THAT SHOULD NEVER HAVE HAPPENED + * Basically we cannot include linux/if.h and net/if.h because + * they have conflicting structures. + * Sadly, linux/wireless.h includes linux/if.h all the time. + * Some kernel-header installs fix this and some do not. + * This file solely exists for those who do not. + * + * We *could* include wireless.h as that is designed for userspace, + * but that then depends on the correct version of wireless-tools being + * installed which isn't always the case. + */ + +#include +#include + +#include +/* Support older kernels */ +#ifdef IFLA_WIRELESS +# include +# include +#else +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +#include +#include + +#include "common.h" + +/* We can't include net.h or dhcpcd.h because + * they would pull in net/if.h, which defeats the purpose of this hack. */ +#define IF_SSIDSIZE 33 +int getifssid(const char *ifname, char *ssid); + +int +getifssid(const char *ifname, char *ssid) +{ +#ifdef SIOCGIWESSID + int s, retval; + struct iwreq iwr; + + if ((s = socket(AF_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 paramters */ + ifname = ssid; + return -1; +#endif +} diff --git a/if-linux.c b/if-linux.c index 114a084..4a06259 100644 --- a/if-linux.c +++ b/if-linux.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -29,68 +29,127 @@ #include #include -#include #include #include -#include #include #include -#include -#include + +/* Support older kernels */ +#ifndef IFLA_WIRELESS +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif #include +#include #include #include #include #include #include -/* Support older kernels */ -#ifndef IFLA_WIRELESS -# define IFLA_WIRELSSS (IFLFA_MASTER + 1) -#endif - #include "config.h" #include "common.h" +#include "configure.h" #include "dhcp.h" #include "net.h" -#define BUFFERLEN 256 +static int sock_fd; +static struct sockaddr_nl sock_nl; + +int +if_init(struct interface *iface) +{ + char path[PATH_MAX]; + FILE *fp; + 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/sys/net/ipv4/conf/%s/promote_secondaries", + iface->name); + + fp = fopen(path, "w"); + if (fp == NULL) + return errno == ENOENT ? 0 : -1; + n = fprintf(fp, "1"); + fclose(fp); + return n == -1 ? -1 : 0; +} int -open_link_socket(struct interface *iface) +if_conf(struct interface *iface) +{ + char path[PATH_MAX], buf[1]; + FILE *fp; + + /* Some qeth setups require the use of the broadcast flag. */ + snprintf(path, sizeof(path), + "/sys/class/net/%s/device/layer2", + iface->name); + + fp = fopen(path, "r"); + if (fp == NULL) + return errno == ENOENT ? 0 : -1; + if (fgets(buf, sizeof(buf), fp) != NULL && buf[0] == '0') + iface->state->options->options |= DHCPCD_BROADCAST; + fclose(fp); + return 0; +} + +static int +_open_link_socket(struct sockaddr_nl *nl) { int fd; - struct sockaddr_nl nl; if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) return -1; - memset(&nl, 0, sizeof(nl)); - nl.nl_family = AF_NETLINK; - nl.nl_groups = RTMGRP_LINK; - if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) + nl->nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) return -1; set_cloexec(fd); - if (iface->link_fd != -1) - close(iface->link_fd); - iface->link_fd = fd; - return 0; + return fd; +} + +int +init_sockets(void) +{ + if ((socket_afnet = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + set_cloexec(socket_afnet); + sock_fd = _open_link_socket(&sock_nl); + set_cloexec(sock_fd); + return sock_fd; +} + +int +open_link_socket(void) +{ + struct sockaddr_nl snl; + + memset(&snl, 0, sizeof(snl)); + snl.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR; + return _open_link_socket(&snl); } static int get_netlink(int fd, int flags, - int (*callback)(struct nlmsghdr *, const char *), - const char *ifname) + int (*callback)(struct nlmsghdr *)) { - char *buffer = NULL; - ssize_t bytes; + char *buf = NULL, *nbuf; + ssize_t buflen = 0, bytes; struct nlmsghdr *nlm; int r = -1; - buffer = xzalloc(sizeof(char) * BUFFERLEN); for (;;) { - bytes = recv(fd, buffer, BUFFERLEN, flags); + bytes = recv(fd, NULL, 0, + flags | MSG_PEEK | MSG_DONTWAIT | MSG_TRUNC); if (bytes == -1) { if (errno == EAGAIN) { r = 0; @@ -99,24 +158,48 @@ get_netlink(int fd, int flags, if (errno == EINTR) continue; goto eexit; + } else if (bytes == buflen) { + /* Support kernels older than 2.6.22 */ + if (bytes == 0) + bytes = 512; + else + bytes *= 2; } - for (nlm = (struct nlmsghdr *)buffer; + if (buflen < bytes) { + /* Alloc 1 more so we work with older kernels */ + buflen = bytes + 1; + nbuf = realloc(buf, buflen); + if (nbuf == NULL) + goto eexit; + buf = nbuf; + } + bytes = recv(fd, buf, buflen, flags); + if (bytes == -1) { + if (errno == EAGAIN) { + r = 0; + goto eexit; + } + if (errno == EINTR) + continue; + goto eexit; + } + for (nlm = (struct nlmsghdr *)buf; NLMSG_OK(nlm, (size_t)bytes); nlm = NLMSG_NEXT(nlm, bytes)) { - r = callback(nlm, ifname); + r = callback(nlm); if (r != 0) goto eexit; } } eexit: - free(buffer); + free(buf); return r; } static int -err_netlink(struct nlmsghdr *nlm, _unused const char *ifname) +err_netlink(struct nlmsghdr *nlm) { struct nlmsgerr *err; int l; @@ -136,13 +219,131 @@ err_netlink(struct nlmsghdr *nlm, _unused const char *ifname) } static int -link_netlink(struct nlmsghdr *nlm, const char *ifname) +link_route(struct nlmsghdr *nlm) +{ + int len, idx, metric; + struct rtattr *rta; + struct rtmsg *rtm; + struct rt rt; + char ifn[IF_NAMESIZE + 1]; + + if (nlm->nlmsg_type != RTM_DELROUTE) + return 0; + + len = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)len < sizeof(*rtm)) { + errno = EBADMSG; + return -1; + } + rtm = NLMSG_DATA(nlm); + if (rtm->rtm_type != RTN_UNICAST || + rtm->rtm_table != RT_TABLE_MAIN || + rtm->rtm_family != AF_INET || + nlm->nlmsg_pid == (uint32_t)getpid()) + return 1; + rta = (struct rtattr *) ((char *)rtm + NLMSG_ALIGN(sizeof(*rtm))); + len = NLMSG_PAYLOAD(nlm, sizeof(*rtm)); + rt.iface = NULL; + rt.dest.s_addr = INADDR_ANY; + rt.net.s_addr = INADDR_ANY; + rt.gate.s_addr = INADDR_ANY; + rt.next = NULL; + metric = 0; + 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_OIF: + idx = *(int *)RTA_DATA(rta); + if (if_indextoname(idx, ifn)) + rt.iface = find_interface(ifn); + break; + case RTA_PRIORITY: + metric = *(int *)RTA_DATA(rta); + break; + } + rta = RTA_NEXT(rta, len); + } + if (rt.iface != NULL) { + if (metric == rt.iface->metric) { + inet_cidrtoaddr(rtm->rtm_dst_len, &rt.net); + route_deleted(&rt); + } + } + return 1; +} + +static int +link_addr(struct nlmsghdr *nlm) +{ + int len; + struct rtattr *rta; + struct ifaddrmsg *ifa; + struct in_addr addr, net, dest; + char ifn[IF_NAMESIZE + 1]; + struct interface *iface; + + if (nlm->nlmsg_type != RTM_DELADDR && nlm->nlmsg_type != RTM_NEWADDR) + return 0; + + len = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)len < sizeof(*ifa)) { + errno = EBADMSG; + return -1; + } + if (nlm->nlmsg_pid == (uint32_t)getpid()) + return 1; + ifa = NLMSG_DATA(nlm); + if (if_indextoname(ifa->ifa_index, ifn) == NULL) + return -1; + iface = find_interface(ifn); + if (iface == NULL) + return 1; + rta = (struct rtattr *) IFA_RTA(ifa); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifa)); + 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 (iface->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); + } + handle_ifa(nlm->nlmsg_type, ifn, &addr, &net, &dest); + return 1; +} + +static int +link_netlink(struct nlmsghdr *nlm) { int len; struct rtattr *rta; struct ifinfomsg *ifi; char ifn[IF_NAMESIZE + 1]; + len = link_route(nlm); + if (len != 0) + return len; + len = link_addr(nlm); + if (len != 0) + return len; + if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK) return 0; len = nlm->nlmsg_len - sizeof(*nlm); @@ -152,7 +353,7 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname) } ifi = NLMSG_DATA(nlm); if (ifi->ifi_flags & IFF_LOOPBACK) - return 0; + return 1; rta = (struct rtattr *) ((char *)ifi + NLMSG_ALIGN(sizeof(*ifi))); len = NLMSG_PAYLOAD(nlm, sizeof(*ifi)); *ifn = '\0'; @@ -161,8 +362,8 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname) case IFLA_WIRELESS: /* Ignore wireless messages */ if (nlm->nlmsg_type == RTM_NEWLINK && - ifi->ifi_change == 0) - return 0; + ifi->ifi_change == 0) + return 1; break; case IFLA_IFNAME: strlcpy(ifn, RTA_DATA(rta), sizeof(ifn)); @@ -170,62 +371,53 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname) } rta = RTA_NEXT(rta, len); } - - if (strncmp(ifname, ifn, sizeof(ifn)) == 0) - return 1; - return 0; + if (nlm->nlmsg_type == RTM_NEWLINK) + len = ifi->ifi_change == ~0U ? 1 : 0; + else + len = -1; + handle_interface(len, ifn); + return 1; } int -link_changed(struct interface *iface) +manage_link(int fd) { - return get_netlink(iface->link_fd, MSG_DONTWAIT, - &link_netlink, iface->name); + return get_netlink(fd, MSG_DONTWAIT, &link_netlink); } static int send_netlink(struct nlmsghdr *hdr) { - int fd, r; - struct sockaddr_nl nl; + int r; struct iovec iov; struct msghdr msg; static unsigned int seq; - if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) - return -1; - memset(&nl, 0, sizeof(nl)); - nl.nl_family = AF_NETLINK; - if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) { - close(fd); - 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 = &nl; - msg.msg_namelen = sizeof(nl); + msg.msg_name = &sock_nl; + msg.msg_namelen = sizeof(sock_nl); msg.msg_iov = &iov; msg.msg_iovlen = 1; /* Request a reply */ hdr->nlmsg_flags |= NLM_F_ACK; hdr->nlmsg_seq = ++seq; - if (sendmsg(fd, &msg, 0) != -1) - r = get_netlink(fd, 0, &err_netlink, NULL); + if (sendmsg(sock_fd, &msg, 0) != -1) + r = get_netlink(sock_fd, 0, &err_netlink); else r = -1; - close(fd); return r; } -#define NLMSG_TAIL(nmsg) \ +#define NLMSG_TAIL(nmsg) \ ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len))) static int add_attr_l(struct nlmsghdr *n, unsigned int maxlen, int type, - const void *data, int alen) + const void *data, int alen) { int len = RTA_LENGTH(alen); struct rtattr *rta; @@ -279,9 +471,9 @@ struct nlmr }; int -if_address(const char *ifname, - const struct in_addr *address, const struct in_addr *netmask, - const struct in_addr *broadcast, int action) +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; @@ -294,7 +486,7 @@ if_address(const char *ifname, nlm->hdr.nlmsg_type = RTM_NEWADDR; } else nlm->hdr.nlmsg_type = RTM_DELADDR; - if (!(nlm->ifa.ifa_index = if_nametoindex(ifname))) { + if (!(nlm->ifa.ifa_index = if_nametoindex(iface->name))) { free(nlm); errno = ENODEV; return -1; @@ -303,12 +495,12 @@ if_address(const char *ifname, nlm->ifa.ifa_prefixlen = inet_ntocidr(*netmask); /* This creates the aliased interface */ add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LABEL, - ifname, strlen(ifname) + 1); + iface->name, strlen(iface->name) + 1); add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL, - &address->s_addr, sizeof(address->s_addr)); - if (action >= 0) + &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)); + &broadcast->s_addr, sizeof(broadcast->s_addr)); if (send_netlink(&nlm->hdr) == -1) retval = -1; @@ -318,8 +510,8 @@ if_address(const char *ifname, int if_route(const struct interface *iface, - const struct in_addr *destination, const struct in_addr *netmask, - const struct in_addr *gateway, int metric, int action) + const struct in_addr *destination, const struct in_addr *netmask, + const struct in_addr *gateway, int metric, int action) { struct nlmr *nlm; unsigned int ifindex; @@ -336,14 +528,7 @@ if_route(const struct interface *iface, if (action == 0) nlm->hdr.nlmsg_flags = NLM_F_REPLACE; else if (action == 1) - /* - * ers@google: - * commented out NLM_F_EXCL here and below. We - * sometimes keep one interface up while we are - * configuring the other one, and this flag - * causes route addition to fail. - */ - nlm->hdr.nlmsg_flags = NLM_F_CREATE /* | NLM_F_EXCL */; + nlm->hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL; else nlm->hdr.nlmsg_type = RTM_DELROUTE; nlm->hdr.nlmsg_flags |= NLM_F_REQUEST; @@ -353,7 +538,7 @@ if_route(const struct interface *iface, if (action == -1 || action == -2) nlm->rt.rtm_scope = RT_SCOPE_NOWHERE; else { - nlm->hdr.nlmsg_flags |= NLM_F_CREATE /*| NLM_F_EXCL*/; + nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; /* We only change route metrics for kernel routes */ if (destination->s_addr == (iface->addr.s_addr & iface->net.s_addr) && @@ -363,7 +548,7 @@ if_route(const struct interface *iface, nlm->rt.rtm_protocol = RTPROT_BOOT; if (gateway->s_addr == INADDR_ANY || (gateway->s_addr == destination->s_addr && - netmask->s_addr == INADDR_BROADCAST)) + netmask->s_addr == INADDR_BROADCAST)) nlm->rt.rtm_scope = RT_SCOPE_LINK; else nlm->rt.rtm_scope = RT_SCOPE_UNIVERSE; @@ -372,16 +557,16 @@ if_route(const struct interface *iface, nlm->rt.rtm_dst_len = inet_ntocidr(*netmask); add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_DST, - &destination->s_addr, sizeof(destination->s_addr)); + &destination->s_addr, sizeof(destination->s_addr)); if (nlm->rt.rtm_protocol == RTPROT_KERNEL) { add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_PREFSRC, - &iface->addr.s_addr, sizeof(iface->addr.s_addr)); + &iface->addr.s_addr, sizeof(iface->addr.s_addr)); } /* If destination == gateway then don't add the gateway */ if (destination->s_addr != gateway->s_addr || netmask->s_addr != INADDR_BROADCAST) add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_GATEWAY, - &gateway->s_addr, sizeof(gateway->s_addr)); + &gateway->s_addr, sizeof(gateway->s_addr)); add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_OIF, ifindex); add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_PRIORITY, metric); diff --git a/if-options.c b/if-options.c new file mode 100644 index 0000000..88b43d7 --- /dev/null +++ b/if-options.c @@ -0,0 +1,912 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "if-options.h" +#include "net.h" +#include "platform.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 + +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'}, + {"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", no_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'}, + {"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}, + {NULL, 0, NULL, '\0'} +}; + +static int +atoint(const char *s) +{ + char *t; + long n; + + errno = 0; + n = strtol(s, &t, 0); + if ((errno != 0 && n == 0) || s == t || + (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN))) + { + syslog(LOG_ERR, "`%s' out of range", s); + return -1; + } + + return (int)n; +} + +static char * +add_environ(struct if_options *ifo, const char *value, int uniq) +{ + char **newlist; + char **lst = ifo->environ; + size_t i = 0, l, lv; + char *match = NULL, *p; + + match = xstrdup(value); + p = strchr(match, '='); + if (p) + *p++ = '\0'; + l = strlen(match); + + while (lst && lst[i]) { + if (match && strncmp(lst[i], match, l) == 0) { + if (uniq) { + free(lst[i]); + lst[i] = xstrdup(value); + } else { + /* Append a space and the value to it */ + l = strlen(lst[i]); + lv = strlen(p); + lst[i] = xrealloc(lst[i], l + lv + 2); + lst[i][l] = ' '; + memcpy(lst[i] + l + 1, p, lv); + lst[i][l + lv + 1] = '\0'; + } + free(match); + return lst[i]; + } + i++; + } + + newlist = xrealloc(lst, sizeof(char *) * (i + 2)); + newlist[i] = xstrdup(value); + newlist[i + 1] = NULL; + ifo->environ = newlist; + free(match); + return newlist[i]; +} + +#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) +static ssize_t +parse_string_hwaddr(char *sbuf, ssize_t slen, const char *str, int clid) +{ + ssize_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 = hwaddr_aton(NULL, str); + if (l > 1) { + if (l > slen) { + errno = ENOBUFS; + return -1; + } + hwaddr_aton((uint8_t *)sbuf, str); + return 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) { + *sbuf++ = 0; + l++; + } + c[3] = '\0'; + while (*str) { + if (++l > slen) { + errno = ENOBUFS; + return -1; + } + if (*str == '\\') { + str++; + switch(*str) { + case '\0': + break; + case 'b': + *sbuf++ = '\b'; + str++; + break; + case 'n': + *sbuf++ = '\n'; + str++; + break; + case 'r': + *sbuf++ = '\r'; + str++; + break; + case 't': + *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') { + c[2] = '\0'; + *sbuf++ = strtol(c, NULL, 16); + } else + l--; + break; + case '0': + /* Grab an octal code */ + c[2] = '\0'; + for (i = 0; i < 3; i++) { + if (*str < '0' || *str > '7') + break; + c[i] = *str++; + } + if (c[2] != '\0') { + i = strtol(c, NULL, 8); + if (i > 255) + i = 255; + *sbuf ++= i; + } else + l--; + break; + default: + *sbuf++ = *str++; + } + } else + *sbuf++ = *str++; + } + if (punt_last) { + *--sbuf = '\0'; + l--; + } + return l; +} + +static char ** +splitv(int *argc, char **argv, const char *arg) +{ + char **v = argv; + char *o = xstrdup(arg), *p, *t; + + p = o; + while ((t = strsep(&p, ", "))) { + (*argc)++; + v = xrealloc(v, sizeof(char *) * ((*argc))); + v[(*argc) - 1] = xstrdup(t); + } + free(o); + return v; +} + +static int +parse_addr(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)) + { + syslog(LOG_ERR, "`%s' is not a valid CIDR", p); + return -1; + } + } + + if (addr != NULL && inet_aton(arg, addr) == 0) { + syslog(LOG_ERR, "`%s' is not a valid IP address", arg); + return -1; + } + if (p != NULL) + *--p = '/'; + else if (net != NULL) + net->s_addr = get_netmask(addr->s_addr); + return 0; +} + +static int +parse_option(struct if_options *ifo, int opt, const char *arg) +{ + int i; + char *p = NULL, *np; + ssize_t s; + struct in_addr addr, addr2; + struct rt *rt; + + switch(opt) { + case 'f': /* FALLTHROUGH */ + case 'g': /* FALLTHROUGH */ + case 'n': /* FALLTHROUGH */ + case 'x': /* FALLTHROUGH */ + case 'T': /* FALLTHROUGH */ + case 'U': /* We need to handle non interface options */ + break; + case 'b': + ifo->options |= DHCPCD_BACKGROUND; + break; + case 'c': + strlcpy(ifo->script, arg, sizeof(ifo->script)); + break; + case 'd': + ifo->options |= DHCPCD_DEBUG; + break; + case 'e': + add_environ(ifo, arg, 1); + break; + case 'h': + if (arg) { + s = parse_string(ifo->hostname, + HOSTNAME_MAX_LEN, arg); + if (s == -1) { + syslog(LOG_ERR, "hostname: %m"); + return -1; + } + if (s != 0 && ifo->hostname[0] == '.') { + syslog(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) { + syslog(LOG_ERR, "vendorclassid: %m"); + return -1; + } + *ifo->vendorclassid = (uint8_t)s; + break; + case 'k': + ifo->options |= DHCPCD_RELEASE; + break; + case 'l': + if (*arg == '-') { + syslog(LOG_ERR, + "leasetime must be a positive value"); + return -1; + } + errno = 0; + ifo->leasetime = (uint32_t)strtol(arg, NULL, 0); + if (errno == EINVAL || errno == ERANGE) { + syslog(LOG_ERR, "`%s' out of range", arg); + return -1; + } + break; + case 'm': + ifo->metric = atoint(arg); + if (ifo->metric < 0) { + syslog(LOG_ERR, "metric must be a positive value"); + return -1; + } + break; + case 'o': + if (make_option_mask(ifo->requestmask, arg, 1) != 0) { + syslog(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(&ifo->req_addr, NULL, arg) != 0) + return -1; + ifo->options |= DHCPCD_REQUEST; + ifo->req_mask.s_addr = 0; + break; + case 's': + if (arg && *arg != '\0') { + if (parse_addr(&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 = atoint(arg); + if (ifo->timeout < 0) { + syslog(LOG_ERR, "timeout must be a positive value"); + return -1; + } + break; + case 'u': + s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1; + s = parse_string((char *)ifo->userclass + + ifo->userclass[0] + 2, + s, arg); + if (s == -1) { + syslog(LOG_ERR, "userclass: %m"); + return -1; + } + if (s != 0) { + ifo->userclass[ifo->userclass[0] + 1] = s; + ifo->userclass[0] += s + 1; + } + break; + case 'v': + p = strchr(arg, ','); + if (!p || !p[1]) { + syslog(LOG_ERR, "invalid vendor format"); + 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) { + syslog(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; + } + + *p = '\0'; + i = atoint(arg); + arg = p + 1; + if (i < 1 || i > 254) { + syslog(LOG_ERR, "vendor option should be between" + " 1 and 254 inclusive"); + return -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)); + } else { + s = parse_string((char *)ifo->vendor + + ifo->vendor[0] + 3, s, arg); + } + if (s == -1) { + syslog(LOG_ERR, "vendor: %m"); + return -1; + } + if (s != 0) { + ifo->vendor[ifo->vendor[0] + 1] = i; + ifo->vendor[ifo->vendor[0] + 2] = s; + ifo->vendor[0] += s + 2; + } + break; + case 'w': + ifo->options |= DHCPCD_WAITIP; + break; + case 'y': + ifo->reboot = atoint(arg); + if (ifo->reboot < 0) { + syslog(LOG_ERR, "reboot must be a positive value"); + return -1; + } + break; + case 'z': + ifav = splitv(&ifac, 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 = ' '; + s = strlen("skip_hooks=") + strlen(arg) + 1; + p = xmalloc(sizeof(char) * s); + snprintf(p, s, "skip_hooks=%s", arg); + add_environ(ifo, p, 0); + free(p); + break; + case 'D': + ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; + break; + case 'E': + ifo->options |= DHCPCD_LASTLEASE; + break; + case 'F': + if (!arg) { + ifo->fqdn = FQDN_BOTH; + break; + } + if (strcmp(arg, "none") == 0) + ifo->fqdn = FQDN_NONE; + else if (strcmp(arg, "ptr") == 0) + ifo->fqdn = FQDN_PTR; + else if (strcmp(arg, "both") == 0) + ifo->fqdn = FQDN_BOTH; + else if (strcmp(arg, "disable") == 0) + ifo->fqdn = FQDN_DISABLE; + else { + syslog(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) { + syslog(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 'O': + if (make_option_mask(ifo->requestmask, arg, -1) != 0 || + make_option_mask(ifo->requiremask, arg, -1) != 0 || + make_option_mask(ifo->nomask, arg, 1) != 0) + { + syslog(LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'Q': + if (make_option_mask(ifo->requiremask, arg, 1) != 0 || + make_option_mask(ifo->requestmask, arg, 1) != 0) + { + syslog(LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'S': + p = strchr(arg, '='); + if (p == NULL) { + syslog(LOG_ERR, "static assignment required"); + return -1; + } + p++; + if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { + if (parse_addr(&ifo->req_addr, &ifo->req_mask, p) != 0) + return -1; + + ifo->options |= DHCPCD_STATIC; + ifo->options &= ~DHCPCD_INFORM; + } 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) + { + np = strchr(p, ' '); + if (np == NULL) { + syslog(LOG_ERR, "all routes need a gateway"); + return -1; + } + *np++ = '\0'; + while (*np == ' ') + np++; + if (ifo->routes == NULL) { + rt = ifo->routes = xmalloc(sizeof(*rt)); + } else { + rt = ifo->routes; + while (rt->next) + rt = rt->next; + rt->next = xmalloc(sizeof(*rt)); + rt = rt->next; + } + rt->next = NULL; + if (parse_addr(&rt->dest, &rt->net, p) == -1 || + parse_addr(&rt->gate, NULL, np) == -1) + return -1; + } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) { + if (ifo->routes == NULL) { + rt = ifo->routes = xzalloc(sizeof(*rt)); + } else { + rt = ifo->routes; + while (rt->next) + rt = rt->next; + rt->next = xmalloc(sizeof(*rt)); + rt = rt->next; + } + rt->dest.s_addr = INADDR_ANY; + rt->net.s_addr = INADDR_ANY; + rt->next = NULL; + if (parse_addr(&rt->gate, NULL, p) == -1) + return -1; + } else { + s = 0; + if (ifo->config != NULL) { + while (ifo->config[s] != NULL) { + if (strncmp(ifo->config[s], arg, + p - arg) == 0) + { + free(ifo->config[s]); + ifo->config[s] = xstrdup(arg); + return 1; + } + s++; + } + } + ifo->config = xrealloc(ifo->config, + sizeof(char *) * (s + 2)); + ifo->config[s] = xstrdup(arg); + ifo->config[s + 1] = NULL; + } + break; + case 'W': + if (parse_addr(&addr, &addr2, arg) != 0) + return -1; + if (strchr(arg, '/') == NULL) + addr2.s_addr = INADDR_BROADCAST; + ifo->whitelist = xrealloc(ifo->whitelist, + sizeof(in_addr_t) * (ifo->whitelist_len + 2)); + ifo->whitelist[ifo->whitelist_len++] = addr.s_addr; + ifo->whitelist[ifo->whitelist_len++] = addr2.s_addr; + break; + case 'X': + if (parse_addr(&addr, &addr2, arg) != 0) + return -1; + if (strchr(arg, '/') == NULL) + addr2.s_addr = INADDR_BROADCAST; + ifo->blacklist = xrealloc(ifo->blacklist, + sizeof(in_addr_t) * (ifo->blacklist_len + 2)); + ifo->blacklist[ifo->blacklist_len++] = addr.s_addr; + ifo->blacklist[ifo->blacklist_len++] = addr2.s_addr; + break; + case 'Z': + ifdv = splitv(&ifdc, ifdv, arg); + break; + case O_ARPING: + if (parse_addr(&addr, NULL, arg) != 0) + return -1; + ifo->arping = xrealloc(ifo->arping, + sizeof(in_addr_t) * (ifo->arping_len + 1)); + ifo->arping[ifo->arping_len++] = addr.s_addr; + break; + case O_DESTINATION: + if (make_option_mask(ifo->dstmask, arg, 2) != 0) { + if (errno == EINVAL) + syslog(LOG_ERR, "option `%s' does not take" + " an IPv4 address", arg); + else + syslog(LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case O_FALLBACK: + free(ifo->fallback); + ifo->fallback = xstrdup(arg); + break; + default: + return 0; + } + + return 1; +} + +static int +parse_config_line(struct if_options *ifo, const char *opt, char *line) +{ + 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(ifo, cf_options[i].val, line); + } + + fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt); + return -1; +} + +struct if_options * +read_config(const char *file, + const char *ifname, const char *ssid, const char *profile) +{ + struct if_options *ifo; + FILE *f; + char *line, *option, *p, *platform; + int skip = 0, have_profile = 0; + struct utsname utn; + + /* Seed our default options */ + ifo = xzalloc(sizeof(*ifo)); + ifo->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE; + ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK; + ifo->timeout = DEFAULT_TIMEOUT; + ifo->reboot = DEFAULT_REBOOT; + ifo->metric = -1; + strlcpy(ifo->script, SCRIPT, sizeof(ifo->script)); + gethostname(ifo->hostname, HOSTNAME_MAX_LEN); + /* Ensure that the hostname is NULL terminated */ + ifo->hostname[HOSTNAME_MAX_LEN] = '\0'; + if (strcmp(ifo->hostname, "(none)") == 0 || + strcmp(ifo->hostname, "localhost") == 0) + ifo->hostname[0] = '\0'; + + platform = hardware_platform(); + if (uname(&utn) == 0) + ifo->vendorclassid[0] = snprintf((char *)ifo->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, + "%s-%s:%s-%s:%s%s%s", PACKAGE, VERSION, + utn.sysname, utn.release, utn.machine, + platform ? ":" : "", platform ? platform : ""); + else + ifo->vendorclassid[0] = snprintf((char *)ifo->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, "%s-%s", PACKAGE, VERSION); + + /* Parse our options file */ + f = fopen(file ? file : CONFIG, "r"); + if (f == NULL) { + if (file != NULL) + syslog(LOG_ERR, "fopen `%s': %m", file); + return ifo; + } + + while ((line = get_line(f))) { + option = strsep(&line, " \t"); + /* 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) { + if (ifname && line && strcmp(line, ifname) == 0) + skip = 0; + else + skip = 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; + } + if (skip) + continue; + parse_config_line(ifo, option, line); + } + fclose(f); + + if (profile && !have_profile) { + free_options(ifo); + errno = ENOENT; + ifo = NULL; + } + + /* Terminate the encapsulated options */ + if (ifo && ifo->vendor[0] && !(ifo->options & DHCPCD_VENDORRAW)) { + ifo->vendor[0]++; + ifo->vendor[ifo->vendor[0]] = DHO_END; + } + return ifo; +} + +int +add_options(struct if_options *ifo, int argc, char **argv) +{ + int oi, opt, r = 1; + + optind = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + r = parse_option(ifo, opt, optarg); + if (r != 1) + break; + } + /* Terminate the encapsulated options */ + if (r == 1 && ifo->vendor[0] && !(ifo->options & DHCPCD_VENDORRAW)) { + ifo->vendor[0]++; + ifo->vendor[ifo->vendor[0]] = DHO_END; + } + return r; +} + +void +free_options(struct if_options *ifo) +{ + size_t i; + + 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); + } + free_routes(ifo->routes); + free(ifo->arping); + free(ifo->blacklist); + free(ifo->fallback); + free(ifo); + } +} diff --git a/if-options.h b/if-options.h new file mode 100644 index 0000000..241cb4d --- /dev/null +++ b/if-options.h @@ -0,0 +1,123 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IF_OPTIONS_H +#define IF_OPTIONS_H + +#include +#include +#include + +#include +#include + +/* Don't set any optional arguments here so we retain POSIX + * compatibility with getopt */ +#define IF_OPTS "bc:de:f:gh:i:kl:m:no:pqr:s:t:u:v:wxy:z:ABC:DEF:GHI:JKLO:Q:S:TUVW:X:Z:" + +#define DEFAULT_TIMEOUT 30 +#define DEFAULT_REBOOT 10 + +#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#define VENDORCLASSID_MAX_LEN 255 +#define CLIENTID_MAX_LEN 48 +#define USERCLASS_MAX_LEN 255 +#define VENDOR_MAX_LEN 255 + +#define DHCPCD_ARP (1 << 0) +#define DHCPCD_RELEASE (1 << 1) +#define DHCPCD_DOMAIN (1 << 2) +#define DHCPCD_GATEWAY (1 << 3) +#define DHCPCD_STATIC (1 << 4) +#define DHCPCD_DEBUG (1 << 5) +#define DHCPCD_LASTLEASE (1 << 7) +#define DHCPCD_INFORM (1 << 8) +#define DHCPCD_REQUEST (1 << 9) +#define DHCPCD_IPV4LL (1 << 10) +#define DHCPCD_DUID (1 << 11) +#define DHCPCD_PERSISTENT (1 << 12) +#define DHCPCD_DAEMONISE (1 << 14) +#define DHCPCD_DAEMONISED (1 << 15) +#define DHCPCD_TEST (1 << 16) +#define DHCPCD_MASTER (1 << 17) +#define DHCPCD_HOSTNAME (1 << 18) +#define DHCPCD_CLIENTID (1 << 19) +#define DHCPCD_LINK (1 << 20) +#define DHCPCD_QUIET (1 << 21) +#define DHCPCD_BACKGROUND (1 << 22) +#define DHCPCD_VENDORRAW (1 << 23) +#define DHCPCD_TIMEOUT_IPV4LL (1 << 24) +#define DHCPCD_WAITIP (1 << 25) +#define DHCPCD_WAITUP (1 << 26) +#define DHCPCD_CSR_WARNED (1 << 27) +#define DHCPCD_XID_HWADDR (1 << 28) +#define DHCPCD_BROADCAST (1 << 29) +#define DHCPCD_DUMPLEASE (1 << 30) + +extern const struct option cf_options[]; + +struct if_options { + int metric; + uint8_t requestmask[256 / 8]; + uint8_t requiremask[256 / 8]; + uint8_t nomask[256 / 8]; + uint8_t dstmask[256 / 8]; + uint32_t leasetime; + time_t timeout; + time_t reboot; + int options; + + struct in_addr req_addr; + struct in_addr req_mask; + struct rt *routes; + char **config; + + char **environ; + char script[PATH_MAX]; + + char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the length */ + int fqdn; + uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 2]; + char 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_options *read_config(const char *, + const char *, const char *, const char *); +int add_options(struct if_options *, int, char **); +void free_options(struct if_options *); + +#endif diff --git a/if-pref.c b/if-pref.c new file mode 100644 index 0000000..83b1b0f --- /dev/null +++ b/if-pref.c @@ -0,0 +1,108 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "config.h" +#include "dhcpcd.h" +#include "if-pref.h" +#include "net.h" + +/* Interface comparer for working out ordering. */ +static int +ifcmp(struct interface *si, struct interface *ti) +{ + int sill, till; + + if (si->state && !ti->state) + return -1; + if (!si->state && ti->state) + return 1; + if (!si->state && !ti->state) + return 0; + /* If one has a lease and the other not, it takes precedence. */ + if (si->state->new && !ti->state->new) + return -1; + if (!si->state->new && ti->state->new) + 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 (si->state->new && ti->state->new) { + sill = (si->state->new->cookie == htonl(MAGIC_COOKIE)); + till = (ti->state->new->cookie == htonl(MAGIC_COOKIE)); + if (!sill && till) + return -1; + if (sill && !till) + return 1; + } + /* Then carrier status. */ + if (si->carrier > ti->carrier) + return -1; + if (si->carrier < ti->carrier) + return 1; + /* 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 +sort_interfaces(void) +{ + struct interface *sorted, *ifp, *ifn, *ift; + + if (!ifaces || !ifaces->next) + return; + sorted = ifaces; + ifaces = ifaces->next; + sorted->next = NULL; + for (ifp = ifaces; ifp && (ifn = ifp->next, 1); ifp = ifn) { + /* Are we the new head? */ + if (ifcmp(ifp, sorted) == -1) { + ifp->next = sorted; + sorted = ifp; + continue; + } + /* Do we fit in the middle? */ + for (ift = sorted; ift->next; ift = ift->next) { + if (ifcmp(ifp, ift->next) == -1) { + ifp->next = ift->next; + ift->next = ifp; + break; + } + } + /* We must be at the end */ + if (!ift->next) { + ift->next = ifp; + ifp->next = NULL; + } + } + ifaces = sorted; +} diff --git a/if-pref.h b/if-pref.h new file mode 100644 index 0000000..dcedd60 --- /dev/null +++ b/if-pref.h @@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IF_PREF_H +#define IF_PREF_H + +#include "dhcpcd.h" + +void sort_interfaces(void); +#endif diff --git a/ifaddrs.c b/ifaddrs.c new file mode 100644 index 0000000..cb8fd76 --- /dev/null +++ b/ifaddrs.c @@ -0,0 +1,146 @@ +/* external/dhcpcd/ifaddrs.c +** +** Copyright 2011, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License");. +** you may not use this file except in compliance with the License.. +** You may obtain a copy of the License at. +** +** http://www.apache.org/licenses/LICENSE-2.0. +** +** Unless required by applicable law or agreed to in writing, software. +** distributed under the License is distributed on an "AS IS" BASIS,. +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.. +** See the License for the specific language governing permissions and. +** limitations under the License. +*/ + +#include +#include +#include "ifaddrs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ifaddrs *get_interface(const char *name, sa_family_t family) +{ + unsigned addr, mask, flags; + struct ifaddrs *ifa; + struct sockaddr_in *saddr = NULL; + struct sockaddr_in *smask = NULL; + struct sockaddr_ll *hwaddr = NULL; + unsigned char hwbuf[ETH_ALEN]; + + if(ifc_get_info(name, &addr, &mask, &flags)) + return NULL; + + if ((family == AF_INET) && (addr == 0)) + return NULL; + + ifa = malloc(sizeof(struct ifaddrs)); + if (!ifa) + return NULL; + memset(ifa, 0, sizeof(struct ifaddrs)); + + ifa->ifa_name = malloc(strlen(name)+1); + if (!ifa->ifa_name) { + free(ifa); + return NULL; + } + strcpy(ifa->ifa_name, name); + ifa->ifa_flags = flags; + + if (family == AF_INET) { + saddr = malloc(sizeof(struct sockaddr_in)); + if (saddr) { + saddr->sin_addr.s_addr = addr; + saddr->sin_family = family; + } + ifa->ifa_addr = (struct sockaddr *)saddr; + + if (mask != 0) { + smask = malloc(sizeof(struct sockaddr_in)); + if (smask) { + smask->sin_addr.s_addr = mask; + smask->sin_family = family; + } + } + ifa->ifa_netmask = (struct sockaddr *)smask; + } else if (family == AF_PACKET) { + if (!ifc_get_hwaddr(name, hwbuf)) { + hwaddr = malloc(sizeof(struct sockaddr_ll)); + if (hwaddr) { + memset(hwaddr, 0, sizeof(struct sockaddr_ll)); + hwaddr->sll_family = family; + /* hwaddr->sll_protocol = ETHERTYPE_IP; */ + hwaddr->sll_hatype = ARPHRD_ETHER; + hwaddr->sll_halen = ETH_ALEN; + memcpy(hwaddr->sll_addr, hwbuf, ETH_ALEN); + } + } + ifa->ifa_addr = (struct sockaddr *)hwaddr; + ifa->ifa_netmask = (struct sockaddr *)smask; + } + return ifa; +} + +int getifaddrs(struct ifaddrs **ifap) +{ + DIR *d; + struct dirent *de; + struct ifaddrs *ifa; + struct ifaddrs *ifah = NULL; + + if (!ifap) + return -1; + *ifap = NULL; + + if (ifc_init()) + return -1; + + d = opendir("/sys/class/net"); + if (d == 0) + return -1; + while ((de = readdir(d))) { + if (de->d_name[0] == '.') + continue; + ifa = get_interface(de->d_name, AF_INET); + if (ifa != NULL) { + ifa->ifa_next = ifah; + ifah = ifa; + } + ifa = get_interface(de->d_name, AF_PACKET); + if (ifa != NULL) { + ifa->ifa_next = ifah; + ifah = ifa; + } + } + *ifap = ifah; + closedir(d); + ifc_close(); + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *ifp; + + while (ifa) { + ifp = ifa; + free(ifp->ifa_name); + if (ifp->ifa_addr) + free(ifp->ifa_addr); + if (ifp->ifa_netmask) + free(ifp->ifa_netmask); + ifa = ifa->ifa_next; + free(ifp); + } +} diff --git a/ifaddrs.h b/ifaddrs.h new file mode 100644 index 0000000..6356653 --- /dev/null +++ b/ifaddrs.h @@ -0,0 +1,34 @@ +/**************************************************************************** + **************************************************************************** + *** + *** This header was generated from a glibc header of the same name. + *** It contains only constants, structures, and macros generated from + *** the original header, and thus, contains no copyrightable information. + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _IFADDRS_H +#define _IFADDRS_H + +#include + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + union { + struct sockaddr *ifu_broadaddr; + struct sockaddr *ifu_dstaddr; + } ifa_ifu; +#define ifa_broadaddr ifa_ifu.ifu_broadaddr +#define ifa_dstaddr ifa_ifu.ifu_dstaddr + void *ifa_data; +}; + +extern int getifaddrs(struct ifaddrs **ifap); + +extern void freeifaddrs(struct ifaddrs *ifa); + +#endif diff --git a/ipv4ll.c b/ipv4ll.c new file mode 100644 index 0000000..4336540 --- /dev/null +++ b/ipv4ll.c @@ -0,0 +1,156 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "arp.h" +#include "common.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if-options.h" +#include "ipv4ll.h" +#include "net.h" + +static struct dhcp_message * +make_ipv4ll_lease(uint32_t addr) +{ + uint32_t u32; + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = xzalloc(sizeof(*dhcp)); + /* 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 struct dhcp_message * +find_ipv4ll_lease(uint32_t old_addr) +{ + uint32_t addr; + + for (;;) { + addr = htonl(LINKLOCAL_ADDR | + (((uint32_t)abs((int)arc4random()) + % 0xFD00) + 0x0100)); + if (addr != old_addr && + IN_LINKLOCAL(ntohl(addr))) + break; + } + return make_ipv4ll_lease(addr); +} + +void +start_ipv4ll(void *arg) +{ + struct interface *iface = arg; + uint32_t addr; + + delete_timeout(NULL, iface); + iface->state->probes = 0; + iface->state->claims = 0; + if (iface->addr.s_addr) { + iface->state->conflicts = 0; + if (IN_LINKLOCAL(htonl(iface->addr.s_addr))) { + send_arp_announce(iface); + return; + } + } + + if (iface->state->offer == NULL) + addr = 0; + else { + addr = iface->state->offer->yiaddr; + free(iface->state->offer); + } + /* We maybe rebooting an IPv4LL address. */ + if (!IN_LINKLOCAL(htonl(addr))) { + syslog(LOG_INFO, "%s: probing for an IPv4LL address", + iface->name); + addr = 0; + } + if (addr == 0) + iface->state->offer = find_ipv4ll_lease(addr); + else + iface->state->offer = make_ipv4ll_lease(addr); + iface->state->lease.frominfo = 0; + send_arp_probe(iface); +} + +void +handle_ipv4ll_failure(void *arg) +{ + struct interface *iface = arg; + time_t up; + + if (iface->state->fail.s_addr == iface->addr.s_addr) { + up = uptime(); + if (iface->state->defend + DEFEND_INTERVAL > up) { + syslog(LOG_DEBUG, + "%s: IPv4LL %d second defence failed", + iface->name, DEFEND_INTERVAL); + drop_config(iface, "EXPIRE"); + iface->state->conflicts = -1; + } else { + syslog(LOG_DEBUG, "%s: defended IPv4LL address", + iface->name); + iface->state->defend = up; + return; + } + } + + close_sockets(iface); + free(iface->state->offer); + iface->state->offer = NULL; + delete_timeout(NULL, iface); + if (++iface->state->conflicts > MAX_CONFLICTS) { + syslog(LOG_ERR, "%s: failed to acquire an IPv4LL address", + iface->name); + iface->state->interval = RATE_LIMIT_INTERVAL / 2; + start_discover(iface); + } else { + add_timeout_sec(PROBE_WAIT, start_ipv4ll, iface); + } +} diff --git a/ipv4ll.h b/ipv4ll.h new file mode 100644 index 0000000..a5d8e9a --- /dev/null +++ b/ipv4ll.h @@ -0,0 +1,33 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV4LL_H +#define IPV4LL_H + +void start_ipv4ll(void *); +void handle_ipv4ll_failure(void *); +#endif diff --git a/lpf.c b/lpf.c index ae5dd03..2907d90 100644 --- a/lpf.c +++ b/lpf.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2009 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -37,7 +37,7 @@ # include /* needed for 2.4 kernels for the below header */ # include # include -# define bpf_insn sock_filter +# define bpf_insn sock_filter # define BPF_SKIPTYPE # define BPF_ETHCOOK -ETH_HLEN # define BPF_WHOLEPACKET 0x0fffffff /* work around buggy LPF filters */ @@ -120,7 +120,7 @@ eexit: ssize_t send_raw_packet(const struct interface *iface, int protocol, - const void *data, ssize_t len) + const void *data, ssize_t len) { union sockunion { struct sockaddr sa; @@ -140,7 +140,7 @@ send_raw_packet(const struct interface *iface, int protocol, su.sll.sll_halen = iface->hwlen; if (iface->family == ARPHRD_INFINIBAND) memcpy(&su.sll.sll_addr, - &ipv4_bcast_addr, sizeof(ipv4_bcast_addr)); + &ipv4_bcast_addr, sizeof(ipv4_bcast_addr)); else memset(&su.sll.sll_addr, 0xff, iface->hwlen); if (protocol == ETHERTYPE_ARP) diff --git a/net.c b/net.c index 29344f8..e26b8d4 100644 --- a/net.c +++ b/net.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -27,45 +27,52 @@ #include #include +#include #include #include +#include #include #include -#include -#include -#ifdef __linux__ -#include -#include +#ifdef AF_LINK +# include +# include #endif +#include #include #include #define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ #include #undef __FAVOR_BSD -#ifdef SIOCGIFMEDIA -#include +#ifdef AF_PACKET +# include #endif -#include -#ifdef AF_LINK -# include +#ifdef SIOCGIFMEDIA +# include #endif #include #include +#include +#include #include #include #include #include +#include #include #include "config.h" #include "common.h" #include "dhcp.h" -#include "logger.h" +#include "if-options.h" #include "net.h" #include "signals.h" +static char hwaddr_buffer[(HWADDR_LEN * 3) + 1]; + +int socket_afnet = -1; + int inet_ntocidr(struct in_addr address) { @@ -76,7 +83,6 @@ inet_ntocidr(struct in_addr address) cidr++; mask <<= 1; } - return cidr; } @@ -85,7 +91,7 @@ inet_cidrtoaddr(int cidr, struct in_addr *addr) { int ocets; - if (cidr < 0 || cidr > 32) { + if (cidr < 1 || cidr > 32) { errno = EINVAL; return -1; } @@ -95,7 +101,7 @@ inet_cidrtoaddr(int cidr, struct in_addr *addr) if (ocets > 0) { memset(&addr->s_addr, 255, (size_t)ocets - 1); memset((unsigned char *)&addr->s_addr + (ocets - 1), - (256 - (1 << (32 - cidr) % 8)), 1); + (256 - (1 << (32 - cidr) % 8)), 1); } return 0; @@ -123,8 +129,7 @@ get_netmask(uint32_t addr) char * hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen) { - static char buffer[(HWADDR_LEN * 3) + 1]; - char *p = buffer; + char *p = hwaddr_buffer; size_t i; for (i = 0; i < hwlen && i < HWADDR_LEN; i++) { @@ -135,7 +140,7 @@ hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen) *p ++= '\0'; - return buffer; + return hwaddr_buffer; } size_t @@ -176,274 +181,368 @@ hwaddr_aton(unsigned char *buffer, const char *addr) return len; } -int -do_interface(const char *ifname, - _unused unsigned char *hwaddr, _unused size_t *hwlen, - struct in_addr *addr, struct in_addr *net, int get) +struct interface * +init_interface(const char *ifname) { - int s; - struct ifconf ifc; - int retval = 0, found = 0; - int len = 10 * sizeof(struct ifreq); - int lastlen = 0; - char *p; - union { - char *buffer; - struct ifreq *ifr; - } ifreqs; - struct sockaddr_in address; - struct ifreq *ifr; - struct sockaddr_in netmask; -#ifdef AF_LINK - struct sockaddr_dl *sdl; -#endif - - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) - return -1; + struct ifreq ifr; + struct interface *iface = NULL; - /* Not all implementations return the needed buffer size for - * SIOGIFCONF so we loop like so for all until it works */ - memset(&ifc, 0, sizeof(ifc)); - for (;;) { - ifc.ifc_len = len; - ifc.ifc_buf = xmalloc((size_t)len); - if (ioctl(s, SIOCGIFCONF, &ifc) == -1) { - if (errno != EINVAL || lastlen != 0) { - close(s); - free(ifc.ifc_buf); - return -1; - } - } else { - if (ifc.ifc_len == lastlen) - break; - lastlen = ifc.ifc_len; - } + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(socket_afnet, SIOCGIFFLAGS, &ifr) == -1) + goto eexit; - free(ifc.ifc_buf); - ifc.ifc_buf = NULL; - len *= 2; + iface = xzalloc(sizeof(*iface)); + strlcpy(iface->name, ifname, sizeof(iface->name)); + iface->flags = ifr.ifr_flags; + /* We reserve the 100 range for virtual interfaces, if and when + * we can work them out. */ + iface->metric = 200 + if_nametoindex(iface->name); + if (getifssid(ifname, iface->ssid) != -1) { + iface->wireless = 1; + iface->metric += 100; } - for (p = (char *)ifc.ifc_buf; p < (char *)ifc.ifc_buf + ifc.ifc_len;) { - /* Cast the ifc buffer to an ifreq cleanly */ - ifreqs.buffer = p; - ifr = ifreqs.ifr; - -#ifndef __linux__ - if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_ifru)) - p += offsetof(struct ifreq, ifr_ifru) + - ifr->ifr_addr.sa_len; - else -#endif - p += sizeof(*ifr); - - if (strcmp(ifname, ifr->ifr_name) != 0) - continue; - - found = 1; + if (ioctl(socket_afnet, SIOCGIFMTU, &ifr) == -1) + goto eexit; + /* Ensure that the MTU is big enough for DHCP */ + if (ifr.ifr_mtu < MTU_MIN) { + ifr.ifr_mtu = MTU_MIN; + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(socket_afnet, SIOCSIFMTU, &ifr) == -1) + goto eexit; + } -#ifdef AF_LINK - if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) { - sdl = xmalloc(ifr->ifr_addr.sa_len); - memcpy(sdl, &ifr->ifr_addr, ifr->ifr_addr.sa_len); - *hwlen = sdl->sdl_alen; - memcpy(hwaddr, LLADDR(sdl), *hwlen); - free(sdl); - retval = 1; - break; - } -#endif + snprintf(iface->leasefile, sizeof(iface->leasefile), + LEASEFILE, ifname); + /* 0 is a valid fd, so init to -1 */ + iface->raw_fd = -1; + iface->udp_fd = -1; + iface->arp_fd = -1; + goto exit; - if (ifr->ifr_addr.sa_family == AF_INET) { - memcpy(&address, &ifr->ifr_addr, sizeof(address)); - if (ioctl(s, SIOCGIFNETMASK, ifr) == -1) - continue; - memcpy(&netmask, &ifr->ifr_addr, sizeof(netmask)); - if (get) { - addr->s_addr = address.sin_addr.s_addr; - net->s_addr = netmask.sin_addr.s_addr; - retval = 1; - break; - } else { - if (address.sin_addr.s_addr == addr->s_addr && - (!net || - netmask.sin_addr.s_addr == net->s_addr)) - { - retval = 1; - break; - } - } - } +eexit: + free(iface); + iface = NULL; +exit: + return iface; +} +void +free_interface(struct interface *iface) +{ + if (!iface) + return; + if (iface->state) { + free_options(iface->state->options); + free(iface->state->old); + free(iface->state->new); + free(iface->state->offer); + free(iface->state); } - - if (!found) - errno = ENXIO; - close(s); - free(ifc.ifc_buf); - return retval; + free(iface->clientid); + free(iface); } int -up_interface(const char *ifname) +carrier_status(struct interface *iface) { - int s; + int ret; struct ifreq ifr; - int retval = -1; +#ifdef SIOCGIFMEDIA + struct ifmediareq ifmr; +#endif #ifdef __linux__ char *p; #endif - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) - return -1; memset(&ifr, 0, sizeof(ifr)); - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); #ifdef __linux__ - /* We can only bring the real interface up */ + /* We can only test the real interface up */ if ((p = strchr(ifr.ifr_name, ':'))) *p = '\0'; #endif - if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) { - if ((ifr.ifr_flags & IFF_UP)) - retval = 0; - else { - ifr.ifr_flags |= IFF_UP; - if (ioctl(s, SIOCSIFFLAGS, &ifr) == 0) - retval = 0; - } - } - close(s); - return retval; + + if (ioctl(socket_afnet, SIOCGIFFLAGS, &ifr) == -1) + return -1; + iface->flags = ifr.ifr_flags; + + ret = -1; +#ifdef SIOCGIFMEDIA + memset(&ifmr, 0, sizeof(ifmr)); + strlcpy(ifmr.ifm_name, iface->name, sizeof(ifmr.ifm_name)); + if (ioctl(socket_afnet, SIOCGIFMEDIA, &ifmr) != -1 && + ifmr.ifm_status & IFM_AVALID) + ret = (ifmr.ifm_status & IFM_ACTIVE) ? 1 : 0; +#endif + if (ret == -1) + ret = (ifr.ifr_flags & IFF_RUNNING) ? 1 : 0; + return ret; } int -carrier_status(const char *ifname) +up_interface(struct interface *iface) { - int s; struct ifreq ifr; int retval = -1; -#ifdef SIOCGIFMEDIA - struct ifmediareq ifmr; -#endif #ifdef __linux__ char *p; #endif - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) - return -1; memset(&ifr, 0, sizeof(ifr)); - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); #ifdef __linux__ - /* We can only test the real interface up */ + /* We can only bring the real interface up */ if ((p = strchr(ifr.ifr_name, ':'))) *p = '\0'; #endif - if ((retval = ioctl(s, SIOCGIFFLAGS, &ifr)) == 0) { - if (ifr.ifr_flags & IFF_UP && ifr.ifr_flags & IFF_RUNNING) - retval = 1; - else + if (ioctl(socket_afnet, SIOCGIFFLAGS, &ifr) == 0) { + if ((ifr.ifr_flags & IFF_UP)) retval = 0; - } - -#ifdef SIOCGIFMEDIA - if (retval == 1) { - memset(&ifmr, 0, sizeof(ifmr)); - strncpy(ifmr.ifm_name, ifr.ifr_name, sizeof(ifmr.ifm_name)); - if (ioctl(s, SIOCGIFMEDIA, &ifmr) != -1 && - ifmr.ifm_status & IFM_AVALID) - { - if (!(ifmr.ifm_status & IFM_ACTIVE)) + else { + ifr.ifr_flags |= IFF_UP; + if (ioctl(socket_afnet, SIOCSIFFLAGS, &ifr) == 0) retval = 0; } + iface->flags = ifr.ifr_flags; } -#endif - close(s); return retval; } struct interface * -read_interface(const char *ifname, _unused int metric) +discover_interfaces(int argc, char * const *argv) { - int s; - struct ifreq ifr; - struct interface *iface = NULL; - unsigned char *hwaddr = NULL; - size_t hwlen = 0; - sa_family_t family = 0; - - memset(&ifr, 0, sizeof(ifr)); - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + struct ifaddrs *ifaddrs, *ifa; + char *p; + int i; + struct interface *ifp, *ifs, *ifl; +#ifdef __linux__ + char ifn[IF_NAMESIZE]; +#endif +#ifdef AF_LINK + const struct sockaddr_dl *sdl; +#ifdef IFLR_ACTIVE + struct if_laddrreq iflr; + int socket_aflink; - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + socket_aflink = socket(AF_LINK, SOCK_DGRAM, 0); + if (socket_aflink == -1) return NULL; + memset(&iflr, 0, sizeof(iflr)); +#endif +#elif AF_PACKET + const struct sockaddr_ll *sll; +#endif -#ifdef __linux__ - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); - if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1) - goto eexit; + if (getifaddrs(&ifaddrs) == -1) + return NULL; - switch (ifr.ifr_hwaddr.sa_family) { - case ARPHRD_ETHER: - case ARPHRD_IEEE802: - hwlen = ETHER_ADDR_LEN; - break; - case ARPHRD_IEEE1394: - hwlen = EUI64_ADDR_LEN; - case ARPHRD_INFINIBAND: - hwlen = INFINIBAND_ADDR_LEN; - break; - } + ifs = ifl = NULL; + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr != NULL) { +#ifdef AF_LINK + if (ifa->ifa_addr->sa_family != AF_LINK) + continue; +#elif AF_PACKET + if (ifa->ifa_addr->sa_family != AF_PACKET) + continue; +#endif + } - hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); - memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, hwlen); - family = ifr.ifr_hwaddr.sa_family; + /* It's possible for an interface to have >1 AF_LINK. + * For our purposes, we use the first one. */ + for (ifp = ifs; ifp; ifp = ifp->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 - ifr.ifr_metric = metric; - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); - if (ioctl(s, SIOCSIFMETRIC, &ifr) == -1) - goto eexit; + if (strcmp(argv[i], ifa->ifa_name) == 0) + break; +#endif + } + if (i == argc) + continue; + p = argv[i]; + } else { + /* -1 means we're discovering against a specific + * interface, but we still need the below rules + * to apply. */ + if (argc == -1 && strcmp(argv[0], ifa->ifa_name) != 0) + continue; + for (i = 0; i < ifdc; i++) + if (!fnmatch(ifdv[i], ifa->ifa_name, 0)) + break; + if (i < ifdc) + continue; + for (i = 0; i < ifac; i++) + if (!fnmatch(ifav[i], ifa->ifa_name, 0)) + break; + if (ifac && i == ifac) + continue; + p = ifa->ifa_name; + } + if ((ifp = init_interface(p)) == NULL) + continue; - hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); - if (do_interface(ifname, hwaddr, &hwlen, NULL, NULL, 0) != 1) - goto eexit; + /* Bring the interface up if not already */ + if (!(ifp->flags & IFF_UP) +#ifdef SIOCGIFMEDIA + && carrier_status(ifp) != -1 +#endif + ) + { + if (up_interface(ifp) == 0) + options |= DHCPCD_WAITUP; + else + syslog(LOG_ERR, "%s: up_interface: %m", ifp->name); + } - family = ARPHRD_ETHER; + /* Don't allow loopback unless explicit */ + if (ifp->flags & IFF_LOOPBACK) { + if (argc == 0 && ifac == 0) { + free_interface(ifp); + continue; + } + } else 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 = sdl->sdl_alen * NBBY; + if (ioctl(socket_aflink, SIOCGLIFADDR, &iflr) == -1 || + !(iflr.flags & IFLR_ACTIVE)) + { + free_interface(ifp); + continue; + } #endif - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); - if (ioctl(s, SIOCGIFMTU, &ifr) == -1) - goto eexit; + switch(sdl->sdl_type) { + case IFT_ETHER: + ifp->family = ARPHRD_ETHER; + break; + case IFT_IEEE1394: + ifp->family = ARPHRD_IEEE1394; + 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->family = sll->sll_hatype; + ifp->hwlen = sll->sll_halen; + if (ifp->hwlen != 0) + memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen); +#endif + } - /* Ensure that the MTU is big enough for DHCP */ - if (ifr.ifr_mtu < MTU_MIN) { - ifr.ifr_mtu = MTU_MIN; - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); - if (ioctl(s, SIOCSIFMTU, &ifr) == -1) - goto eexit; + /* We only work on ethernet by default */ + if (!(ifp->flags & IFF_POINTOPOINT) && + ifp->family != ARPHRD_ETHER) + { + if (argc == 0 && ifac == 0) { + free_interface(ifp); + continue; + } + switch (ifp->family) { + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: + /* We don't warn for supported families */ + break; + default: + syslog(LOG_WARNING, + "%s: unknown hardware family", p); + } + } + + /* Handle any platform init for the interface */ + if (if_init(ifp) == -1) { + syslog(LOG_ERR, "%s: if_init: %m", p); + free_interface(ifp); + continue; + } + + if (ifl) + ifl->next = ifp; + else + ifs = ifp; + ifl = ifp; } + freeifaddrs(ifaddrs); - if (up_interface(ifname) != 0) - goto eexit; +#ifdef IFLR_ACTIVE + close(socket_aflink); +#endif - iface = xzalloc(sizeof(*iface)); - strlcpy(iface->name, ifname, IF_NAMESIZE); - snprintf(iface->leasefile, PATH_MAX, LEASEFILE, ifname); - memcpy(&iface->hwaddr, hwaddr, hwlen); - iface->hwlen = hwlen; + return ifs; +} - iface->family = family; - iface->arpable = !(ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)); +int +do_address(const char *ifname, + struct in_addr *addr, struct in_addr *net, struct in_addr *dst, int act) +{ + struct ifaddrs *ifaddrs, *ifa; + const struct sockaddr_in *a, *n, *d; + int retval; - /* 0 is a valid fd, so init to -1 */ - iface->raw_fd = -1; - iface->udp_fd = -1; - iface->arp_fd = -1; - iface->link_fd = -1; + if (getifaddrs(&ifaddrs) == -1) + return -1; -eexit: - close(s); - free(hwaddr); - return iface; + retval = 0; + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || + ifa->ifa_addr->sa_family != AF_INET || + strcmp(ifa->ifa_name, ifname) != 0) + continue; + a = (const struct sockaddr_in *)(void *)ifa->ifa_addr; + n = (const struct sockaddr_in *)(void *)ifa->ifa_netmask; + if (ifa->ifa_flags & IFF_POINTOPOINT) + d = (const struct sockaddr_in *)(void *) + ifa->ifa_dstaddr; + else + d = NULL; + if (act == 1) { + addr->s_addr = a->sin_addr.s_addr; + net->s_addr = n->sin_addr.s_addr; + if (dst) { + if (ifa->ifa_flags & IFF_POINTOPOINT) + dst->s_addr = d->sin_addr.s_addr; + else + dst->s_addr = INADDR_ANY; + } + retval = 1; + break; + } + if (addr->s_addr == a->sin_addr.s_addr && + (net == NULL || net->s_addr == n->sin_addr.s_addr)) + { + retval = 1; + break; + } + } + freeifaddrs(ifaddrs); + return retval; } int @@ -451,16 +550,11 @@ do_mtu(const char *ifname, short int mtu) { struct ifreq ifr; int r; - int s; - - if ((s = socket(AF_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); + r = ioctl(socket_afnet, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); if (r == -1) return -1; return ifr.ifr_mtu; @@ -482,13 +576,11 @@ int open_udp_socket(struct interface *iface) { int s; - union sockunion { - struct sockaddr sa; - struct sockaddr_in sin; - } su; + struct sockaddr_in sin; int n; #ifdef SO_BINDTODEVICE struct ifreq ifr; + char *p; #endif if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) @@ -500,7 +592,12 @@ open_udp_socket(struct interface *iface) #ifdef SO_BINDTODEVICE memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); - if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + /* 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 /* As we don't use this socket for receiving, set the @@ -508,11 +605,11 @@ open_udp_socket(struct interface *iface) n = 1; if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) goto eexit; - memset(&su, 0, sizeof(su)); - su.sin.sin_family = AF_INET; - su.sin.sin_port = htons(DHCP_CLIENT_PORT); - su.sin.sin_addr.s_addr = iface->addr.s_addr; - if (bind(s, &su.sa, sizeof(su)) == -1) + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(DHCP_CLIENT_PORT); + sin.sin_addr.s_addr = iface->addr.s_addr; + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) goto eexit; iface->udp_fd = s; @@ -526,19 +623,16 @@ eexit: ssize_t send_packet(const struct interface *iface, struct in_addr to, - const uint8_t *data, ssize_t len) + const uint8_t *data, ssize_t len) { - union sockunion { - struct sockaddr sa; - struct sockaddr_in sin; - } su; - - memset(&su, 0, sizeof(su)); - su.sin.sin_family = AF_INET; - su.sin.sin_addr.s_addr = to.s_addr; - su.sin.sin_port = htons(DHCP_SERVER_PORT); - - return sendto(iface->udp_fd, data, len, 0, &su.sa, sizeof(su)); + 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); + return sendto(iface->udp_fd, data, len, 0, + (struct sockaddr *)&sin, sizeof(sin)); } struct udp_dhcp_packet @@ -574,7 +668,7 @@ checksum(const void *data, uint16_t len) ssize_t make_udp_packet(uint8_t **packet, const uint8_t *data, size_t length, - struct in_addr source, struct in_addr dest) + struct in_addr source, struct in_addr dest) { struct udp_dhcp_packet *udpp; struct ip *ip; @@ -609,14 +703,10 @@ make_udp_packet(uint8_t **packet, const uint8_t *data, size_t length, udp->uh_sum = checksum(udpp, sizeof(*udpp)); ip->ip_v = IPVERSION; - ip->ip_hl = 5; - ip->ip_id = 0; - ip->ip_tos = IPTOS_LOWDELAY; - ip->ip_len = htons (sizeof(*ip) + sizeof(*udp) + length); - ip->ip_id = 0; - ip->ip_off = htons(IP_DF); /* Don't fragment */ + ip->ip_hl = sizeof(*ip) >> 2; + ip->ip_id = arc4random() & UINT16_MAX; ip->ip_ttl = IPDEFTTL; - + ip->ip_len = htons(sizeof(*ip) + sizeof(*udp) + length); ip->ip_sum = checksum(ip, sizeof(*ip)); *packet = (uint8_t *)udpp; @@ -630,20 +720,30 @@ get_udp_data(const uint8_t **data, const uint8_t *udp) memcpy(&packet, udp, sizeof(packet)); *data = udp + offsetof(struct udp_dhcp_packet, dhcp); - return ntohs(packet.ip.ip_len) - sizeof(packet.ip) - sizeof(packet.udp); + return ntohs(packet.ip.ip_len) - + sizeof(packet.ip) - + sizeof(packet.udp); } int -valid_udp_packet(const uint8_t *data, size_t data_len) +valid_udp_packet(const uint8_t *data, size_t data_len, struct in_addr *from) { struct udp_dhcp_packet packet; uint16_t bytes, udpsum; + if (data_len < sizeof(packet.ip)) { + if (from) + from->s_addr = INADDR_ANY; + errno = EINVAL; + return -1; + } + memcpy(&packet, data, MIN(data_len, sizeof(packet))); + if (from) + from->s_addr = packet.ip.ip_src.s_addr; if (data_len > sizeof(packet)) { errno = EINVAL; return -1; } - memcpy(&packet, data, data_len); if (checksum(&packet.ip, sizeof(packet.ip)) != 0) { errno = EINVAL; return -1; @@ -671,35 +771,3 @@ valid_udp_packet(const uint8_t *data, size_t data_len) return 0; } - -int -send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip) -{ - struct arphdr *arp; - size_t arpsize; - uint8_t *p; - int retval; - - arpsize = sizeof(*arp) + 2 * iface->hwlen + 2 * sizeof(sip); - arp = xmalloc(arpsize); - arp->ar_hrd = htons(iface->family); - arp->ar_pro = htons(ETHERTYPE_IP); - arp->ar_hln = iface->hwlen; - arp->ar_pln = sizeof(sip); - arp->ar_op = htons(op); - p = (uint8_t *)arp; - p += sizeof(*arp); - memcpy(p, iface->hwaddr, iface->hwlen); - p += iface->hwlen; - memcpy(p, &sip, sizeof(sip)); - p += sizeof(sip); - /* ARP requests should ignore this */ - retval = iface->hwlen; - while (retval--) - *p++ = '\0'; - memcpy(p, &tip, sizeof(tip)); - p += sizeof(tip); - retval = send_raw_packet(iface, ETHERTYPE_ARP, arp, arpsize); - free(arp); - return retval; -} diff --git a/net.h b/net.h index 1447aba..6d85930 100644 --- a/net.h +++ b/net.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2010 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,8 @@ #include #include "config.h" +#include "dhcp.h" +#include "dhcpcd.h" #ifndef DUID_LEN # define DUID_LEN 128 + 2 @@ -57,7 +59,6 @@ # define ARPHRD_INFINIBAND 32 #endif -#define HWADDR_LEN 20 /* Work out if we have a private address or not * 10/8 @@ -65,9 +66,9 @@ * 192.168/16 */ #ifndef IN_PRIVATE -# define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \ - ((addr & 0xfff00000) == 0xac100000) || \ - ((addr & IN_CLASSB_NET) == 0xc0a80000)) +# define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \ + ((addr & 0xfff00000) == 0xac100000) || \ + ((addr & IN_CLASSB_NET) == 0xc0a80000)) #endif #define LINKLOCAL_ADDR 0xa9fe0000 @@ -78,51 +79,24 @@ # define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR) #endif -/* There is an argument that this should be converted to an STAIL using - * queue(3). However, that isn't readily available on all libc's that - * dhcpcd works on. The only benefit of STAILQ over this is the ability to - * quickly loop backwards through the list - currently we reverse the list - * and then move through it forwards. This isn't that much of a big deal - * though as the norm is to just have one default route, and an IPV4LL route. - * You can (and do) get more routes in the DHCP message, but not enough to - * really warrant a change to STAIL queue for performance reasons. */ struct rt { struct in_addr dest; struct in_addr net; struct in_addr gate; + const struct interface *iface; struct rt *next; }; -struct interface -{ - char name[IF_NAMESIZE]; - sa_family_t family; - unsigned char hwaddr[HWADDR_LEN]; - size_t hwlen; - int arpable; - - int raw_fd; - int udp_fd; - int arp_fd; - int link_fd; - size_t buffer_size, buffer_len, buffer_pos; - unsigned char *buffer; - - struct in_addr addr; - struct in_addr net; - struct rt *routes; - - char leasefile[PATH_MAX]; - time_t start_uptime; - - unsigned char *clientid; -}; +extern int socket_afnet; uint32_t get_netmask(uint32_t); char *hwaddr_ntoa(const unsigned char *, size_t); size_t hwaddr_aton(unsigned char *, const char *); -struct interface *read_interface(const char *, int); +int getifssid(const char *, char *); +struct interface *init_interface(const char *); +struct interface *discover_interfaces(int, char * const *); +void free_interface(struct interface *); int do_mtu(const char *, short int); #define get_mtu(iface) do_mtu(iface, 0) #define set_mtu(iface, mtu) do_mtu(iface, mtu) @@ -130,48 +104,52 @@ int do_mtu(const char *, short int); int inet_ntocidr(struct in_addr); int inet_cidrtoaddr(int, struct in_addr *); -int up_interface(const char *); -int do_interface(const char *, unsigned char *, size_t *, - struct in_addr *, struct in_addr *, int); -int if_address(const char *, const struct in_addr *, const struct in_addr *, - const struct in_addr *, int); -#define add_address(ifname, addr, net, brd) \ - if_address(ifname, addr, net, brd, 1) -#define del_address(ifname, addr, net) \ - if_address(ifname, addr, net, NULL, -1) -#define has_address(ifname, addr, net) \ - do_interface(ifname, NULL, NULL, addr, net, 0) -#define get_address(ifname, addr, net) \ - do_interface(ifname, NULL, NULL, addr, net, 1) - -int if_route(const struct interface *, - const struct in_addr *,const struct in_addr *, - const struct in_addr *, int, int); -#define add_route(iface, dest, mask, gate, metric) \ +int up_interface(struct interface *); +int if_conf(struct interface *); +int if_init(struct interface *); + +int do_address(const char *, + struct in_addr *, struct in_addr *, struct in_addr *, int); +int if_address(const struct interface *, + const struct in_addr *, const struct in_addr *, + const struct in_addr *, int); +#define add_address(iface, addr, net, brd) \ + if_address(iface, addr, net, brd, 1) +#define del_address(iface, addr, net) \ + if_address(iface, addr, net, NULL, -1) +#define has_address(iface, addr, net) \ + do_address(iface, addr, net, NULL, 0) +#define get_address(iface, addr, net, dst) \ + do_address(iface, addr, net, dst, 1) + +int if_route(const struct interface *, const struct in_addr *, + const struct in_addr *, const struct in_addr *, int, int); +#define add_route(iface, dest, mask, gate, metric) \ if_route(iface, dest, mask, gate, metric, 1) -#define change_route(iface, dest, mask, gate, metric) \ +#define change_route(iface, dest, mask, gate, metric) \ if_route(iface, dest, mask, gate, metric, 0) -#define del_route(iface, dest, mask, gate, metric) \ +#define del_route(iface, dest, mask, gate, metric) \ if_route(iface, dest, mask, gate, metric, -1) +#define del_src_route(iface, dest, mask, gate, metric) \ + if_route(iface, dest, mask, gate, metric, -2) void free_routes(struct rt *); int open_udp_socket(struct interface *); -const size_t udp_dhcp_len; +extern const size_t udp_dhcp_len; ssize_t make_udp_packet(uint8_t **, const uint8_t *, size_t, - struct in_addr, struct in_addr); + struct in_addr, struct in_addr); ssize_t get_udp_data(const uint8_t **, const uint8_t *); -int valid_udp_packet(const uint8_t *, size_t); +int valid_udp_packet(const uint8_t *, size_t, struct in_addr *); int open_socket(struct interface *, int); ssize_t send_packet(const struct interface *, struct in_addr, - const uint8_t *, ssize_t); + const uint8_t *, ssize_t); ssize_t send_raw_packet(const struct interface *, int, - const void *, ssize_t); + const void *, ssize_t); ssize_t get_raw_packet(struct interface *, int, void *, ssize_t); -int send_arp(const struct interface *, int, in_addr_t, in_addr_t); - -int open_link_socket(struct interface *); -int link_changed(struct interface *); -int carrier_status(const char *); +int init_sockets(void); +int open_link_socket(void); +int manage_link(int); +int carrier_status(struct interface *); #endif diff --git a/platform-bsd.c b/platform-bsd.c new file mode 100644 index 0000000..dd5791c --- /dev/null +++ b/platform-bsd.c @@ -0,0 +1,50 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include "platform.h" + +#ifndef SYS_NMLN /* OSX */ +# define SYS_NMLN 256 +#endif + +static char march[SYS_NMLN]; + +char * +hardware_platform(void) +{ + int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; + size_t len = sizeof(march); + + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), + march, &len, NULL, 0) != 0) + return NULL; + return march; +} diff --git a/platform-linux.c b/platform-linux.c new file mode 100644 index 0000000..79562c8 --- /dev/null +++ b/platform-linux.c @@ -0,0 +1,104 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include "common.h" +#include "platform.h" + +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 + ; + +char * +hardware_platform(void) +{ + FILE *fp; + char *buf, *p; + + if (mproc == NULL) { + errno = EINVAL; + return NULL; + } + + fp = fopen("/proc/cpuinfo", "r"); + if (fp == NULL) + return NULL; + + p = NULL; + while ((buf = get_line(fp))) { + if (strncmp(buf, mproc, strlen(mproc)) == 0) { + p = strchr(buf, ':'); + if (p != NULL && ++p != NULL) { + while (*p == ' ') + p++; + break; + } + } + } + fclose(fp); + + if (p == NULL) + errno = ESRCH; + return p; +} diff --git a/platform.h b/platform.h new file mode 100644 index 0000000..24731ac --- /dev/null +++ b/platform.h @@ -0,0 +1,33 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +char * hardware_platform(void); + +#endif diff --git a/showlease.c b/showlease.c index 27cc543..50f96df 100644 --- a/showlease.c +++ b/showlease.c @@ -6,6 +6,10 @@ #include "dhcp.h" #include "config.h" +#ifndef DEFAULT_LEASETIME +#define DEFAULT_LEASETIME 3600 /* 1 hour */ +#endif + #define REQUEST (1 << 0) #define UINT8 (1 << 1) #define UINT16 (1 << 2) @@ -119,7 +123,7 @@ static const struct dhcp_opt const dhcp_opts[] = { }; struct dhcp_message * -get_lease(const char *leasefile) +get_lease_from_file(const char *leasefile) { int fd; struct dhcp_message *dhcp; @@ -322,7 +326,7 @@ main(int argc, char *argv[]) exit(1); } snprintf(leasefile, PATH_MAX, LEASEFILE, argv[1]); - if ((dhcp = get_lease(leasefile)) == NULL) { + if ((dhcp = get_lease_from_file(leasefile)) == NULL) { fprintf(stderr, "Couldn't read lease file: %s\n", strerror(errno)); exit(1); } diff --git a/signals.c b/signals.c index 6576afd..fd3b0c3 100644 --- a/signals.c +++ b/signals.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2009 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -40,10 +40,12 @@ static int signal_pipe[2]; static const int handle_sigs[] = { - SIGHUP, SIGALRM, + SIGHUP, + SIGINT, + SIGPIPE, SIGTERM, - SIGINT + SIGUSR1, }; static void @@ -52,17 +54,11 @@ signal_handler(int sig) int serrno = errno; if (write(signal_pipe[1], &sig, sizeof(sig)) != sizeof(sig)) - syslog(LOG_ERR, "write signal %d: %s", sig, strerror(errno)); + syslog(LOG_ERR, "failed to write signal %d: %m", sig); /* Restore errno */ errno = serrno; } -int -signal_fd(void) -{ - return (signal_pipe[0]); -} - /* Read a signal from the signal pipe. Returns 0 if there is * no signal, -1 on error (and sets errno appropriately), and * your signal on success */ @@ -95,7 +91,7 @@ signal_init(void) return -1; if (set_cloexec(signal_pipe[1]) == -1) return -1; - return 0; + return signal_pipe[0]; } static int @@ -125,3 +121,4 @@ signal_reset(void) { return signal_handle(SIG_DFL); } + diff --git a/signals.h b/signals.h index f784a68..7098cfb 100644 --- a/signals.h +++ b/signals.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples + * Copyright (c) 2006-2008 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,6 @@ int signal_init(void); int signal_setup(void); int signal_reset(void); -int signal_fd(void); int signal_read(void); #endif -- cgit v1.1