Project import
diff --git a/dhcpcd/Android.mk b/dhcpcd/Android.mk new file mode 100644 index 0000000..4947cbd --- /dev/null +++ b/dhcpcd/Android.mk
@@ -0,0 +1,65 @@ +# Copyright 2006 The Android Open Source Project +LOCAL_PATH:= $(call my-dir) + +etc_dir := $(TARGET_OUT)/etc/dhcpcd +hooks_dir := dhcpcd-hooks +hooks_target := $(etc_dir)/$(hooks_dir) + +dhcpd_cflags := -Wno-error=duplicate-decl-specifier -D_BSD_SOURCE +# Clang complains about configure.c's comparing array with null. +dhcpd_cflags += -Wno-tautological-pointer-compare + +include $(CLEAR_VARS) +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 \ + platform-linux.c compat/closefrom.c ifaddrs.c ipv6rs.c + +LOCAL_CFLAGS := $(dhcpd_cflags) +LOCAL_SHARED_LIBRARIES := libc libcutils libnetutils +LOCAL_MODULE = dhcpcd +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := showlease.c +LOCAL_SHARED_LIBRARIES := libc +LOCAL_MODULE = showlease +LOCAL_MODULE_TAGS := debug +include $(BUILD_EXECUTABLE) + +#include $(CLEAR_VARS) +#LOCAL_MODULE := dhcpcd.conf +#LOCAL_MODULE_CLASS := ETC +#LOCAL_MODULE_PATH := $(etc_dir) +#LOCAL_SRC_FILES := android.conf +#include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := dhcpcd-run-hooks +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_PATH := $(etc_dir) +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := 20-dns.conf +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(hooks_target) +LOCAL_SRC_FILES := $(hooks_dir)/$(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := 95-configured +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(hooks_target) +LOCAL_SRC_FILES := $(hooks_dir)/$(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +# Unit tests. +include $(CLEAR_VARS) +LOCAL_MODULE := dhcpcd_test +LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter +LOCAL_SRC_FILES := dhcpcd_test.cpp dhcp.c common.c +LOCAL_CFLAGS := $(dhcpd_cflags) +LOCAL_MODULE_TAGS := eng tests +include $(BUILD_NATIVE_TEST)
diff --git a/dhcpcd/CleanSpec.mk b/dhcpcd/CleanSpec.mk new file mode 100644 index 0000000..b84e1b6 --- /dev/null +++ b/dhcpcd/CleanSpec.mk
@@ -0,0 +1,49 @@ +# Copyright (C) 2007 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************
diff --git a/dhcpcd/MODULE_LICENSE_BSD_LIKE b/dhcpcd/MODULE_LICENSE_BSD_LIKE new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dhcpcd/MODULE_LICENSE_BSD_LIKE
diff --git a/dhcpcd/Makefile b/dhcpcd/Makefile new file mode 100644 index 0000000..3b4dbfd --- /dev/null +++ b/dhcpcd/Makefile
@@ -0,0 +1,127 @@ +# dhcpcd Makefile + +PROG= dhcpcd +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 ipv6rs.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} + +SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks +HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks + +MAN5= dhcpcd.conf.5 +MAN8= dhcpcd.8 dhcpcd-run-hooks.8 +CLEANFILES= dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8 + +SCRIPTS= dhcpcd-run-hooks +SCRIPTSDIR= ${LIBEXECDIR} +CLEANFILES+= dhcpcd-run-hooks +CLEANFILES+= .depend + +FILES= dhcpcd.conf +FILESDIR= ${SYSCONFDIR} + +SUBDIRS= dhcpcd-hooks + +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}) + +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_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} + +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} $@ + +include Makefile.inc
diff --git a/dhcpcd/Makefile.inc b/dhcpcd/Makefile.inc new file mode 100644 index 0000000..fd7ead4 --- /dev/null +++ b/dhcpcd/Makefile.inc
@@ -0,0 +1,9 @@ +# System definitions + +BINMODE?= 0555 +NONBINMODE?= 0444 +MANMODE?= ${NONBINMODE} +CONFMODE?= 0644 + +INSTALL?= install +SED?= sed
diff --git a/dhcpcd/NOTICE b/dhcpcd/NOTICE new file mode 100644 index 0000000..73598aa --- /dev/null +++ b/dhcpcd/NOTICE
@@ -0,0 +1,49 @@ +dhcpcd - DHCP client daemon +Copyright (c) 2006-2012 Roy Marples <roy@marples.name> +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + + + +dhcp_bpf_filter taken from bpf.c in dhcp-3.1.0 + +Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") +Copyright (c) 1996-2003 by Internet Software Consortium + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Internet Systems Consortium, Inc. +950 Charter Street +Redwood City, CA 94063 +<info@isc.org> +http://www.isc.org/
diff --git a/dhcpcd/README b/dhcpcd/README new file mode 100644 index 0000000..32ae27a --- /dev/null +++ b/dhcpcd/README
@@ -0,0 +1,80 @@ +dhcpcd - DHCP client daemon +Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + + +Installation +------------ +./configure; make; make install +man dhcpcd for command line options +man dhcpcd.conf for configuration options +man dhcpcd-run-hooks to learn how to hook scripts into dhcpcd events + + +Notes +----- +If you're cross compiling you may need set the platform if OS is different +from the host. +--target=sparc-sun-netbsd5.0 + +If you're building for an MMU-less system where fork() does not work, you +should ./configure --disable-fork. +This also puts the --no-background flag on and stops the --background flag +from working. + +You can change the default dirs with these knobs. +For example, to satisfy FHS compliance you would do this:- +./configure --libexecdir=/lib/dhcpcd dbdir=/var/lib/dhcpcd + +We now default to using -std=c99. For 64-bit linux, this always works, but +for 32-bit linux it requires either gnu99 or a patch to asm/types.h. +Most distros patch linux headers so this should work fine. +linux-2.6.24 finally ships with a working 32-bit header. +If your linux headers are older, or your distro hasn't patched them you can +set CSTD=gnu99 to work around this. + +Some BSD systems do not allow the manipulation of automatically added subnet +routes. You can find discussion here: + http://mail-index.netbsd.org/tech-net/2008/12/03/msg000896.html +BSD systems where this has been fixed 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, +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 +------------- +dhcpcd-5.0 is only fully command line compatible with dhcpcd-4.0 +For compatibility with older versions, use dhcpcd-4.0 + +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 +--------- +We no longer supply a ChangeLog. +However, you're more than welcome to read the commit log at +http://roy.marples.name/projects/dhcpcd/log/
diff --git a/dhcpcd/android.conf b/dhcpcd/android.conf new file mode 100755 index 0000000..5f5fd54 --- /dev/null +++ b/dhcpcd/android.conf
@@ -0,0 +1,10 @@ +# dhcpcd configuration for Android Wi-Fi interface +# See dhcpcd.conf(5) for details. + +# IPv6 RS/RA processing is handled by the kernel. See also http://b/15268738 +noipv6rs + +interface wlan0 +# dhcpcd-run-hooks uses these options. +option subnet_mask, routers, domain_name_servers, domain_name, domain_search +
diff --git a/dhcpcd/arp.c b/dhcpcd/arp.c new file mode 100644 index 0000000..1905508 --- /dev/null +++ b/dhcpcd/arp.c
@@ -0,0 +1,310 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#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), NULL); + 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 (state->new == NULL) + return; + 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/dhcpcd/arp.h b/dhcpcd/arp.h new file mode 100644 index 0000000..b97c38b --- /dev/null +++ b/dhcpcd/arp.h
@@ -0,0 +1,49 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARP_H +#define ARP_H + +/* 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/dhcpcd/bind.c b/dhcpcd/bind.c new file mode 100644 index 0000000..eed64a6 --- /dev/null +++ b/dhcpcd/bind.c
@@ -0,0 +1,234 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/time.h> + +#include <fcntl.h> +#ifdef BSD +# include <paths.h> +#endif +#include <signal.h> +#include <stdlib.h> +#include <syslog.h> +#include <unistd.h> + +#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/dhcpcd/bind.h b/dhcpcd/bind.h new file mode 100644 index 0000000..375a0f3 --- /dev/null +++ b/dhcpcd/bind.h
@@ -0,0 +1,39 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef 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/dhcpcd/bpf-filter.h b/dhcpcd/bpf-filter.h new file mode 100644 index 0000000..b68ee49 --- /dev/null +++ b/dhcpcd/bpf-filter.h
@@ -0,0 +1,101 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef BPF_ETHCOOK +# define BPF_ETHCOOK 0 +#endif +#ifndef BPF_WHOLEPACKET +# define BPF_WHOLEPACKET ~0U +#endif +static const struct bpf_insn const arp_bpf_filter [] = { +#ifndef BPF_SKIPTYPE + /* Make sure this is an ARP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3), +#endif + /* Make sure this is an ARP REQUEST... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), + /* or ARP REPLY... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; +static const size_t arp_bpf_filter_len = + sizeof(arp_bpf_filter) / sizeof(arp_bpf_filter[0]); + + +/* dhcp_bpf_filter taken from bpf.c in dhcp-3.1.0 + * + * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * http://www.isc.org/ + */ + +static const struct bpf_insn const dhcp_bpf_filter [] = { +#ifndef BPF_SKIPTYPE + /* Make sure this is an IP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), +#endif + /* Make sure it's a UDP packet... */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + /* Get the IP header length... */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 + BPF_ETHCOOK), + /* Make sure it's to the right port... */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; +static const size_t dhcp_bpf_filter_len = + sizeof(dhcp_bpf_filter) / sizeof(dhcp_bpf_filter[0]);
diff --git a/dhcpcd/bpf.c b/dhcpcd/bpf.c new file mode 100644 index 0000000..1c30c76 --- /dev/null +++ b/dhcpcd/bpf.c
@@ -0,0 +1,209 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "net.h" +#include "bpf-filter.h" + +int +open_socket(struct interface *iface, int protocol) +{ + int fd = -1; + int *fdp = NULL; + struct ifreq ifr; + int buf_len = 0; + struct bpf_version pv; + struct bpf_program pf; +#ifdef BIOCIMMEDIATE + int flags; +#endif +#ifdef _PATH_BPF + fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK); +#else + char *device; + int n = 0; + + device = xmalloc(sizeof(char) * PATH_MAX); + do { + snprintf(device, PATH_MAX, "/dev/bpf%d", n++); + fd = open(device, O_RDWR | O_NONBLOCK); + } while (fd == -1 && errno == EBUSY); + free(device); +#endif + + if (fd == -1) + return -1; + + if (ioctl(fd, BIOCVERSION, &pv) == -1) + goto eexit; + if (pv.bv_major != BPF_MAJOR_VERSION || + pv.bv_minor < BPF_MINOR_VERSION) { + syslog(LOG_ERR, "BPF version mismatch - recompile"); + goto eexit; + } + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + if (ioctl(fd, BIOCSETIF, &ifr) == -1) + goto eexit; + + /* Get the required BPF buffer length from the kernel. */ + if (ioctl(fd, BIOCGBLEN, &buf_len) == -1) + goto eexit; + if (iface->buffer_size != (size_t)buf_len) { + free(iface->buffer); + iface->buffer_size = buf_len; + iface->buffer = xmalloc(buf_len); + iface->buffer_len = iface->buffer_pos = 0; + } + +#ifdef BIOCIMMEDIATE + flags = 1; + if (ioctl(fd, BIOCIMMEDIATE, &flags) == -1) + goto eexit; +#endif + + /* Install the DHCP filter */ + if (protocol == ETHERTYPE_ARP) { + pf.bf_insns = UNCONST(arp_bpf_filter); + pf.bf_len = arp_bpf_filter_len; + fdp = &iface->arp_fd; + } else { + pf.bf_insns = UNCONST(dhcp_bpf_filter); + pf.bf_len = dhcp_bpf_filter_len; + fdp = &iface->raw_fd; + } + if (ioctl(fd, BIOCSETF, &pf) == -1) + goto eexit; + if (set_cloexec(fd) == -1) + goto eexit; + if (fdp) { + if (*fdp != -1) + close(*fdp); + *fdp = fd; + } + return fd; + +eexit: + free(iface->buffer); + iface->buffer = NULL; + close(fd); + return -1; +} + +ssize_t +send_raw_packet(const struct interface *iface, int protocol, + const void *data, ssize_t len) +{ + struct iovec iov[2]; + struct ether_header hw; + int fd; + + memset(&hw, 0, ETHER_HDR_LEN); + memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN); + hw.ether_type = htons(protocol); + iov[0].iov_base = &hw; + iov[0].iov_len = ETHER_HDR_LEN; + iov[1].iov_base = UNCONST(data); + iov[1].iov_len = len; + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + return writev(fd, iov, 2); +} + +/* BPF requires that we read the entire buffer. + * So we pass the buffer in the API so we can loop on >1 packet. */ +ssize_t +get_raw_packet(struct interface *iface, int protocol, + void *data, ssize_t len, int *partialcsum) +{ + int fd = -1; + struct bpf_hdr packet; + ssize_t bytes; + const unsigned char *payload; + + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + + if (partialcsum != NULL) + *partialcsum = 0; /* Not supported on BSD */ + + for (;;) { + if (iface->buffer_len == 0) { + bytes = read(fd, iface->buffer, iface->buffer_size); + if (bytes == -1) + return errno == EAGAIN ? 0 : -1; + else if ((size_t)bytes < sizeof(packet)) + return -1; + iface->buffer_len = bytes; + iface->buffer_pos = 0; + } + bytes = -1; + memcpy(&packet, iface->buffer + iface->buffer_pos, + sizeof(packet)); + if (packet.bh_caplen != packet.bh_datalen) + goto next; /* Incomplete packet, drop. */ + if (iface->buffer_pos + packet.bh_caplen + packet.bh_hdrlen > + iface->buffer_len) + goto next; /* Packet beyond buffer, drop. */ + payload = iface->buffer + packet.bh_hdrlen + ETHER_HDR_LEN; + bytes = packet.bh_caplen - ETHER_HDR_LEN; + if (bytes > len) + bytes = len; + memcpy(data, payload, bytes); +next: + iface->buffer_pos += BPF_WORDALIGN(packet.bh_hdrlen + + packet.bh_caplen); + if (iface->buffer_pos >= iface->buffer_len) + iface->buffer_len = iface->buffer_pos = 0; + if (bytes != -1) + return bytes; + } +}
diff --git a/dhcpcd/common.c b/dhcpcd/common.c new file mode 100644 index 0000000..05b547b --- /dev/null +++ b/dhcpcd/common.c
@@ -0,0 +1,301 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* Needed define to get at getline for glibc and FreeBSD */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include <sys/cdefs.h> + +#ifdef __APPLE__ +# include <mach/mach_time.h> +# include <mach/kern_return.h> +#endif + +#include <sys/param.h> +#include <sys/time.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#ifdef BSD +# include <paths.h> +#endif +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "common.h" + +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/null" +#endif + +int clock_monotonic; +static char *lbuf; +static size_t lbuf_len; +#ifdef DEBUG_MEMORY +static char lbuf_set; +#endif + +#ifdef DEBUG_MEMORY +static void +free_lbuf(void) +{ + free(lbuf); + lbuf = NULL; +} +#endif + +/* 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) +{ + char *p; + ssize_t bytes; + +#ifdef DEBUG_MEMORY + if (lbuf_set == 0) { + atexit(free_lbuf); + lbuf_set = 1; + } +#endif + + 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 +set_cloexec(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + syslog(LOG_ERR, "fcntl: %m"); + return -1; + } + return 0; +} + +int +set_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + { + syslog(LOG_ERR, "fcntl: %m"); + return -1; + } + return 0; +} + +/* Handy function to get the time. + * We only care about time advancements, not the actual time itself + * Which is why we use CLOCK_MONOTONIC, but it is not available on all + * platforms. + */ +#define NO_MONOTONIC "host does not support a monotonic clock - timing can skew" +int +get_monotonic(struct timeval *tp) +{ + static int posix_clock_set = 0; +#if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC) + struct timespec ts; + static clockid_t posix_clock; + + if (!posix_clock_set) { + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + posix_clock = CLOCK_MONOTONIC; + clock_monotonic = posix_clock_set = 1; + } + } + + if (clock_monotonic) { + if (clock_gettime(posix_clock, &ts) == 0) { + tp->tv_sec = ts.tv_sec; + tp->tv_usec = ts.tv_nsec / 1000; + return 0; + } + } +#elif defined(__APPLE__) +#define NSEC_PER_SEC 1000000000 + /* We can use mach kernel functions here. + * This is crap though - why can't they implement clock_gettime?*/ + static struct mach_timebase_info info = { 0, 0 }; + static double factor = 0.0; + uint64_t nano; + long rem; + + if (!posix_clock_set) { + if (mach_timebase_info(&info) == KERN_SUCCESS) { + factor = (double)info.numer / (double)info.denom; + clock_monotonic = posix_clock_set = 1; + } + } + if (clock_monotonic) { + nano = mach_absolute_time(); + if ((info.denom != 1 || info.numer != 1) && factor != 0.0) + nano *= factor; + tp->tv_sec = nano / NSEC_PER_SEC; + rem = nano % NSEC_PER_SEC; + if (rem < 0) { + tp->tv_sec--; + rem += NSEC_PER_SEC; + } + tp->tv_usec = rem / 1000; + return 0; + } +#endif + + /* Something above failed, so fall back to gettimeofday */ + if (!posix_clock_set) { + syslog(LOG_WARNING, NO_MONOTONIC); + posix_clock_set = 1; + } + return gettimeofday(tp, NULL); +} + +ssize_t +setvar(char ***e, const char *prefix, const char *var, const char *value) +{ + size_t len = strlen(var) + strlen(value) + 3; + + if (prefix) + len += strlen(prefix) + 1; + **e = xmalloc(len); + if (prefix) + snprintf(**e, len, "%s_%s=%s", prefix, var, value); + else + snprintf(**e, len, "%s=%s", var, value); + (*e)++; + return len; +} + +ssize_t +setvard(char ***e, const char *prefix, const char *var, int value) +{ + char buffer[32]; + + snprintf(buffer, sizeof(buffer), "%d", value); + return setvar(e, prefix, var, buffer); +} + + +time_t +uptime(void) +{ + struct timeval tv; + + if (get_monotonic(&tv) == -1) + return -1; + return tv.tv_sec; +} + +int +writepid(int fd, pid_t pid) +{ + char spid[16]; + ssize_t len; + + if (ftruncate(fd, (off_t)0) == -1) + return -1; + snprintf(spid, sizeof(spid), "%u\n", pid); + len = pwrite(fd, spid, strlen(spid), (off_t)0); + if (len != (ssize_t)strlen(spid)) + return -1; + return 0; +} + +void * +xmalloc(size_t s) +{ + void *value = malloc(s); + + if (value != NULL) + return value; + syslog(LOG_ERR, "memory exhausted (xalloc %zu bytes)", s); + exit (EXIT_FAILURE); + /* NOTREACHED */ +} + +void * +xzalloc(size_t s) +{ + void *value = xmalloc(s); + + memset(value, 0, s); + return value; +} + +void * +xrealloc(void *ptr, size_t s) +{ + void *value = realloc(ptr, s); + + if (value != NULL) + return value; + syslog(LOG_ERR, "memory exhausted (xrealloc %zu bytes)", s); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} + +char * +xstrdup(const char *str) +{ + char *value; + + if (str == NULL) + return NULL; + + if ((value = strdup(str)) != NULL) + return value; + + syslog(LOG_ERR, "memory exhausted (xstrdup)"); + exit(EXIT_FAILURE); + /* NOTREACHED */ +}
diff --git a/dhcpcd/common.h b/dhcpcd/common.h new file mode 100644 index 0000000..4fe2444 --- /dev/null +++ b/dhcpcd/common.h
@@ -0,0 +1,113 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include <sys/time.h> +#include <stdio.h> + +#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 _noreturn __attribute__((__noreturn__)) +# define _packed __attribute__((__packed__)) +# define _unused __attribute__((__unused__)) +#else +# define _noreturn +# define _packed +# define _unused +#endif + +/* We don't really need this as our supported systems define __restrict + * automatically for us, but it is here for completeness. */ +#ifndef __restrict +# if defined(__lint__) +# define __restrict +# elif __STDC_VERSION__ >= 199901L +# define __restrict restrict +# elif !(2 < __GNUC__ || (2 == __GNU_C && 95 <= __GNUC_VERSION__)) +# define __restrict +# endif +#endif + +int set_cloexec(int); +int set_nonblock(int); +char *get_line(FILE * __restrict); +extern int clock_monotonic; +int get_monotonic(struct timeval *); +ssize_t setvar(char ***, const char *, const char *, const char *); +ssize_t setvard(char ***, const char *, const char *, int); +time_t uptime(void); +int writepid(int, pid_t); +void *xrealloc(void *, size_t); +void *xmalloc(size_t); +void *xzalloc(size_t); +char *xstrdup(const char *); + +/* Uncomment the #def below to send DHCPCD syslog messages to Android's logcat + * instead. */ +/* #define REDIRECT_SYSLOG_TO_ANDROID_LOGCAT */ +#ifdef REDIRECT_SYSLOG_TO_ANDROID_LOGCAT + +#define LOG_TAG "DHCPCD" +#include <utils/Log.h> + +#undef LOG_EMERG +#undef LOG_ALERT +#undef LOG_CRIT +#undef LOG_ERR +#undef LOG_WARNING +#undef LOG_NOTICE +#undef LOG_INFO +#undef LOG_DEBUG + +#define LOG_EMERG ANDROID_LOG_FATAL +#define LOG_ALERT ANDROID_LOG_FATAL +#define LOG_CRIT ANDROID_LOG_FATAL +#define LOG_ERR ANDROID_LOG_ERROR +#define LOG_WARNING ANDROID_LOG_WARN +#define LOG_NOTICE ANDROID_LOG_WARN +#define LOG_INFO ANDROID_LOG_INFO +#define LOG_DEBUG ANDROID_LOG_DEBUG +#define syslog(a, b...) android_printLog(a, LOG_TAG, b) + +#endif /* REDIRECT_SYSLOG_TO_ANDROID_LOGCAT */ + +#endif
diff --git a/dhcpcd/compat/closefrom.c b/dhcpcd/compat/closefrom.c new file mode 100644 index 0000000..9d34a53 --- /dev/null +++ b/dhcpcd/compat/closefrom.c
@@ -0,0 +1,49 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <unistd.h> + +#include "closefrom.h" + +int +closefrom(int fd) +{ + long max; + int i, r; + +#ifdef _SC_OPEN_MAX + max = sysconf(_SC_OPEN_MAX); +#else + max = getdtablesize(); +#endif + r = 0; + for (i = fd; i < max; i++) { + if (close(i) == -1) + r = -1; + } + return r; +}
diff --git a/dhcpcd/compat/closefrom.h b/dhcpcd/compat/closefrom.h new file mode 100644 index 0000000..b028507 --- /dev/null +++ b/dhcpcd/compat/closefrom.h
@@ -0,0 +1,31 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CLOSEFROM_H +#define CLOSEFROM_H +int closefrom(int); +#endif
diff --git a/dhcpcd/config.h b/dhcpcd/config.h new file mode 100644 index 0000000..e9f0af3 --- /dev/null +++ b/dhcpcd/config.h
@@ -0,0 +1,9 @@ +/* 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/closefrom.h" + +#include <sys/param.h>
diff --git a/dhcpcd/config.mk b/dhcpcd/config.mk new file mode 100644 index 0000000..c7a9eca --- /dev/null +++ b/dhcpcd/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/dhcpcd/configure.c b/dhcpcd/configure.c new file mode 100644 index 0000000..fe89532 --- /dev/null +++ b/dhcpcd/configure.c
@@ -0,0 +1,841 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "configure.h" +#include "dhcp.h" +#include "if-options.h" +#include "if-pref.h" +#include "ipv6rs.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) +{ + pid_t pid; + sigset_t full; + sigset_t old; + + /* OK, we need to block signals */ + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + signal_reset(); + + switch (pid = vfork()) { + case -1: + syslog(LOG_ERR, "vfork: %m"); + break; + case 0: + sigprocmask(SIG_SETMASK, &old, NULL); + execve(argv[0], argv, env); + syslog(LOG_ERR, "%s: %m", argv[0]); + _exit(127); + /* NOTREACHED */ + } + + /* Restore our signals */ + signal_setup(); + sigprocmask(SIG_SETMASK, &old, NULL); + return pid; +} + +static char * +make_var(const char *prefix, const char *var) +{ + size_t len; + char *v; + + len = strlen(prefix) + strlen(var) + 2; + v = xmalloc(len); + snprintf(v, len, "%s_%s", prefix, var); + return v; +} + + +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, const char *reason, char ***argv) +{ + char **env, *p; + ssize_t e, elen, l; + const struct if_options *ifo = iface->state->options; + const struct interface *ifp; + int dhcp, ra; + + dhcp = ra = 0; + if (strcmp(reason, "ROUTERADVERT") == 0) + ra = 1; + else + dhcp = 1; + + /* When dumping the lease, we only want to report interface and + reason - the other interface variables are meaningless */ + if (options & DHCPCD_DUMPLEASE) + elen = 2; + else + elen = 10; + + /* Make our env */ + env = xmalloc(sizeof(char *) * (elen + 1)); + e = strlen("interface") + strlen(iface->name) + 2; + env[0] = xmalloc(e); + snprintf(env[0], e, "interface=%s", iface->name); + e = strlen("reason") + strlen(reason) + 2; + env[1] = xmalloc(e); + snprintf(env[1], e, "reason=%s", reason); + if (options & DHCPCD_DUMPLEASE) + goto dumplease; + + e = 20; + env[2] = xmalloc(e); + snprintf(env[2], e, "pid=%d", getpid()); + env[3] = xmalloc(e); + snprintf(env[3], e, "ifmetric=%d", iface->metric); + env[4] = xmalloc(e); + 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 ((dhcp && iface->state->new) || (ra && iface->ras)) { + env[8] = strdup("if_up=true"); + env[9] = strdup("if_down=false"); + } else { + env[8] = strdup("if_up=false"); + env[9] = strdup("if_down=true"); + } + 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 (dhcp && 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", + iface->state->old, ifo); + } + append_config(&env, &elen, "old", + (const char *const *)ifo->config); + } + +dumplease: + if (dhcp && 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", + iface->state->new, ifo); + } + append_config(&env, &elen, "new", + (const char *const *)ifo->config); + } + if (ra) { + e = ipv6rs_env(NULL, NULL, iface); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += ipv6rs_env(env + elen, NULL, iface); + } + } + + /* Add our base environment */ + if (ifo->environ) { + e = 0; + while (ifo->environ[e++]) + ; + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + e = 0; + while (ifo->environ[e]) { + env[elen + e] = xstrdup(ifo->environ[e]); + e++; + } + elen += e; + } + env[elen] = '\0'; + + *argv = env; + return elen; +} + +static int +send_interface1(int fd, const struct interface *iface, const char *reason) +{ + char **env, **ep, *s; + ssize_t elen; + struct iovec iov[2]; + int retval; + + retval = 0; + make_env(iface, reason, &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 +send_interface(int fd, const struct interface *iface) +{ + int retval = 0; + if (send_interface1(fd, iface, iface->state->reason) == -1) + retval = -1; + if (iface->ras) { + if (send_interface1(fd, iface, "ROUTERADVERT") == -1) + retval = -1; + } + return retval; +} + +int +run_script_reason(const struct interface *iface, const char *reason) +{ + 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]; + + if (iface->state->options->script == NULL || + iface->state->options->script[0] == '\0' || + strcmp(iface->state->options->script, "/dev/null") == 0) + return 0; + + if (reason == NULL) + reason = iface->state->reason; + syslog(LOG_DEBUG, "%s: executing `%s', reason %s", + iface->name, argv[0], reason); + + /* Make our env */ + elen = make_env(iface, reason, &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; + else if (pid != 0) { + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + 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) + free(*ep++); + free(env); + return status; +} + +static struct rt * +find_route(struct rt *rts, const struct rt *r, struct rt **lrt, + const struct rt *srt) +{ + struct rt *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) +{ + char addr[sizeof("000.000.000.000") + 1]; + const char *ifname = rt->iface->name; + + 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); + if (l) + l->next = f->next; + else + routes = f->next; + free(f); + return 1; +} + +static int +n_route(struct rt *rt) +{ + /* Don't set default routes if not asked to */ + if (rt->dest.s_addr == 0 && + rt->net.s_addr == 0 && + !(rt->iface->state->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route("adding", rt); + if (!add_route(rt)) + return 0; + if (errno == EEXIST) { + /* Pretend we added the subnet route */ + if (rt->dest.s_addr == + (rt->iface->addr.s_addr & rt->iface->net.s_addr) && + rt->net.s_addr == rt->iface->net.s_addr && + rt->gate.s_addr == 0) + return 0; + else + return -1; + } + syslog(LOG_ERR, "%s: add_route: %m", rt->iface->name); + return -1; +} + +static int +c_route(struct rt *ort, struct rt *nrt) +{ + /* Don't set default routes if not asked to */ + if (nrt->dest.s_addr == 0 && + nrt->net.s_addr == 0 && + !(nrt->iface->state->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route("changing", nrt); + /* 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); + if (!add_route(nrt)) + return 0; + syslog(LOG_ERR, "%s: add_route: %m", nrt->iface->name); + return -1; +} + +static int +d_route(struct rt *rt) +{ + int retval; + + desc_route("deleting", rt); + retval = del_route(rt); + if (retval != 0 && errno != ENOENT && errno != ESRCH) + syslog(LOG_ERR,"%s: del_route: %m", rt->iface->name); + return retval; +} + +static struct rt * +get_subnet_route(struct dhcp_message *dhcp) +{ + in_addr_t addr; + struct in_addr net; + struct rt *rt; + + 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; +} + +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 struct rt * +get_routes(const struct interface *iface) +{ + 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 get_option_routes(iface->state->new, + iface->name, &iface->state->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 *r; + + 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; +} + +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; + } + 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; + } + return rt; +} + +void +build_routes(void) +{ + struct rt *nrs = NULL, *dnr, *or, *rt, *rtn, *rtl, *lrt = NULL; + const struct interface *ifp; + + if (avoid_routes) return; + + 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; + rt->metric = ifp->metric; + /* Is this route already in our table? */ + if ((find_route(nrs, rt, NULL, NULL)) != NULL) + continue; + rt->src.s_addr = ifp->addr.s_addr; + /* Do we already manage it? */ + if ((or = find_route(routes, rt, &rtl, NULL))) { + if (or->iface != ifp || + or->src.s_addr != ifp->addr.s_addr || + rt->gate.s_addr != or->gate.s_addr || + rt->metric != or->metric) + { + if (c_route(or, rt) != 0) + continue; + } + if (rtl != NULL) + rtl->next = or->next; + else + routes = or->next; + free(or); + } else { + if (n_route(rt) != 0) + continue; + } + if (dnr == rt) + dnr = rtn; + else if (lrt) + lrt->next = rtn; + rt->next = nrs; + nrs = rt; + rt = lrt; /* When we loop this makes lrt correct */ + } + free_routes(dnr); + } + + /* 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); + } + + free_routes(routes); + routes = nrs; +} + +static int +delete_address(struct interface *iface) +{ + int retval; + 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) + syslog(LOG_ERR, "del_address: %m"); + iface->addr.s_addr = 0; + iface->net.s_addr = 0; + return retval; +} + +int +configure(struct interface *iface) +{ + struct dhcp_message *dhcp = iface->state->new; + struct dhcp_lease *lease = &iface->state->lease; + struct if_options *ifo = iface->state->options; + struct rt *rt; + + /* 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 (dhcp == NULL) { + if (!(ifo->options & DHCPCD_PERSISTENT)) { + build_routes(); + if (iface->addr.s_addr != 0) + delete_address(iface); + run_script(iface); + } + return 0; + } + + /* This also changes netmask */ + 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) + { + syslog(LOG_ERR, "add_address: %m"); + return -1; + } + } + + /* Now delete the old address if different */ + if (iface->addr.s_addr != lease->addr.s_addr && + iface->addr.s_addr != 0) + delete_address(iface); + + iface->addr.s_addr = lease->addr.s_addr; + iface->net.s_addr = lease->net.s_addr; + + if (!avoid_routes) { + /* 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; + rt->metric = 0; + if (!find_route(routes, rt, NULL, NULL)) + del_route(rt); + free(rt); + } + + build_routes(); + } + + if (!iface->state->lease.frominfo && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) + if (write_lease(iface, dhcp) == -1) + syslog(LOG_ERR, "write_lease: %m"); + run_script(iface); + return 0; +}
diff --git a/dhcpcd/configure.h b/dhcpcd/configure.h new file mode 100644 index 0000000..e125369 --- /dev/null +++ b/dhcpcd/configure.h
@@ -0,0 +1,40 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCONFIG_H +#define DHCPCONFIG_H + +#include "net.h" + +int send_interface(int, const struct interface *); +int run_script_reason(const struct interface *, const char *); +void build_routes(void); +int configure(struct interface *); +int route_deleted(const struct rt *); + +#define run_script(ifp) run_script_reason(ifp, (ifp)->state->reason); +#endif
diff --git a/dhcpcd/control.c b/dhcpcd/control.c new file mode 100644 index 0000000..24fb354 --- /dev/null +++ b/dhcpcd/control.c
@@ -0,0 +1,208 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <linux/socket.h> +#include <sys/stat.h> +#include <sys/un.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcpcd.h" +#include "control.h" +#include "eloop.h" + +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/dhcpcd/control.h b/dhcpcd/control.h new file mode 100644 index 0000000..f0faa40 --- /dev/null +++ b/dhcpcd/control.h
@@ -0,0 +1,45 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include "dhcpcd.h" + +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/dhcpcd/defs.h b/dhcpcd/defs.h new file mode 100644 index 0000000..b4f2380 --- /dev/null +++ b/dhcpcd/defs.h
@@ -0,0 +1,52 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PACKAGE "dhcpcd" +#define VERSION "5.5.6" + +#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/dhcpcd/dhcp.c b/dhcpcd/dhcp.c new file mode 100644 index 0000000..b3f7cb1 --- /dev/null +++ b/dhcpcd/dhcp.c
@@ -0,0 +1,1510 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" + +#define REQUEST (1 << 0) +#define UINT8 (1 << 1) +#define UINT16 (1 << 2) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define IPV4 (1 << 6) +#define STRING (1 << 7) +#define PAIR (1 << 8) +#define ARRAY (1 << 9) +#define RFC3361 (1 << 10) +#define RFC3397 (1 << 11) +#define RFC3442 (1 << 12) +#define RFC5969 (1 << 13) + +#define IPV4R IPV4 | REQUEST + +#define DAD "Duplicate address detected" + +/* 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 *opt_buffer; + +struct dhcp_opt { + uint8_t option; + int type; + const char *var; +}; + +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, "classless_static_routes" }, + { 249, RFC3442, "ms_classless_static_routes" }, + { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, + { 3, IPV4 | ARRAY | REQUEST, "routers" }, + { 2, UINT32, "time_offset" }, + { 4, IPV4 | ARRAY, "time_servers" }, + { 5, IPV4 | ARRAY, "ien116_name_servers" }, + /* Explicitly include DNS in the list of parameters requested in the DNS request. + * Without this some DHCP servers may skip the DNS entries in the DHCP replies.*/ + { 6, IPV4 | ARRAY | REQUEST, "domain_name_servers" }, + { 7, IPV4 | ARRAY, "log_servers" }, + { 8, IPV4 | ARRAY, "cookie_servers" }, + { 9, IPV4 | ARRAY, "lpr_servers" }, + { 10, IPV4 | ARRAY, "impress_servers" }, + { 11, IPV4 | ARRAY, "resource_location_servers" }, + { 12, STRING, "host_name" }, + { 13, UINT16, "boot_size" }, + { 14, STRING, "merit_dump" }, + /* Explicitly include DNS in the list of parameters requested in the DNS request. + * Without this some DHCP servers may skip the DNS entries in the DHCP replies.*/ + { 15, STRING | REQUEST, "domain_name" }, + { 16, IPV4, "swap_server" }, + { 17, STRING, "root_path" }, + { 18, STRING, "extensions_path" }, + { 19, UINT8, "ip_forwarding" }, + { 20, UINT8, "non_local_source_routing" }, + { 21, IPV4 | ARRAY, "policy_filter" }, + { 22, SINT16, "max_dgram_reassembly" }, + { 23, UINT16, "default_ip_ttl" }, + { 24, UINT32, "path_mtu_aging_timeout" }, + { 25, UINT16 | ARRAY, "path_mtu_plateau_table" }, + { 26, UINT16, "interface_mtu" }, + { 27, UINT8, "all_subnets_local" }, + { 28, IPV4 | REQUEST, "broadcast_address" }, + { 29, UINT8, "perform_mask_discovery" }, + { 30, UINT8, "mask_supplier" }, + { 31, UINT8, "router_discovery" }, + { 32, IPV4, "router_solicitation_address" }, + { 34, UINT8, "trailer_encapsulation" }, + { 35, UINT32, "arp_cache_timeout" }, + { 36, UINT16, "ieee802_3_encapsulation" }, + { 37, UINT8, "default_tcp_ttl" }, + { 38, UINT32, "tcp_keepalive_interval" }, + { 39, UINT8, "tcp_keepalive_garbage" }, + { 40, STRING, "nis_domain" }, + { 41, IPV4 | ARRAY, "nis_servers" }, + { 42, IPV4 | ARRAY, "ntp_servers" }, + { 43, STRING, "vendor_encapsulated_options" }, + { 44, IPV4 | ARRAY, "netbios_name_servers" }, + { 45, IPV4, "netbios_dd_server" }, + { 46, UINT8, "netbios_node_type" }, + { 47, STRING, "netbios_scope" }, + { 48, IPV4 | ARRAY, "font_servers" }, + { 49, IPV4 | ARRAY, "x_display_manager" }, + { 50, IPV4, "dhcp_requested_address" }, + { 51, UINT32 | REQUEST, "dhcp_lease_time" }, + { 52, UINT8, "dhcp_option_overload" }, + { 53, UINT8, "dhcp_message_type" }, + { 54, IPV4, "dhcp_server_identifier" }, + { 55, UINT8 | ARRAY, "dhcp_parameter_request_list" }, + { 56, STRING, "dhcp_message" }, + { 57, UINT16, "dhcp_max_message_size" }, + { 58, UINT32 | REQUEST, "dhcp_renewal_time" }, + { 59, UINT32 | REQUEST, "dhcp_rebinding_time" }, + { 64, STRING, "nisplus_domain" }, + { 65, IPV4 | ARRAY, "nisplus_servers" }, + { 66, STRING, "tftp_server_name" }, + { 67, STRING, "bootfile_name" }, + { 68, IPV4 | ARRAY, "mobile_ip_home_agent" }, + { 69, IPV4 | ARRAY, "smtp_server" }, + { 70, IPV4 | ARRAY, "pop_server" }, + { 71, IPV4 | ARRAY, "nntp_server" }, + { 72, IPV4 | ARRAY, "www_server" }, + { 73, IPV4 | ARRAY, "finger_server" }, + { 74, IPV4 | ARRAY, "irc_server" }, + { 75, IPV4 | ARRAY, "streettalk_server" }, + { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, + { 77, STRING, "user_class" }, + { 81, STRING | RFC3397, "fqdn_name" }, + { 85, IPV4 | ARRAY, "nds_servers" }, + { 86, STRING, "nds_tree_name" }, + { 87, STRING, "nds_context" }, + { 88, STRING | RFC3397, "bcms_controller_names" }, + { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 91, UINT32, "client_last_transaction_time" }, + { 92, IPV4 | ARRAY, "associated_ip" }, + { 98, STRING, "uap_servers" }, + { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 113, STRING, "netinfo_server_tag" }, + { 114, STRING, "default_url" }, + { 118, IPV4, "subnet_selection" }, + { 119, STRING | RFC3397, "domain_search" }, + { 120, STRING | RFC3361, "sip_server" }, + { 212, RFC5969, "sixrd" }, + { 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, const char *opts, int add) +{ + 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; + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + match = 0; + if (strcmp(opt->var, token) == 0) + match = 1; + else { + errno = 0; + n = strtol(token, &t, 0); + if (errno == 0 && !*t) + if (opt->option == n) + match = 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); + else + del_option_mask(mask, + opt->option); + break; + } + } + if (!opt->option) { + free(o); + errno = ENOENT; + return -1; + } + } + free(o); + return 0; +} + +static int +valid_length(uint8_t option, int dl, int *type) +{ + const struct dhcp_opt *opt; + ssize_t sz; + + if (dl == 0) + return -1; + + for (opt = dhcp_opts; opt->option; opt++) { + if (opt->option != option) + continue; + + if (type) + *type = opt->type; + /* The size of RFC3442 and RFC5969 options is checked at a later + * stage in the code */ + if (opt->type == 0 || + opt->type & (STRING | RFC3442 | RFC5969)) + return 0; + /* The code does not use SINT16 / SINT32 together with ARRAY. + * It is however far easier to reason about the code if all + * possible array elements are included, and also does not code + * any additional CPU resources. sizeof(uintXX_t) == + * sizeof(intXX_t) can be assumed. */ + sz = 0; + if (opt->type & (UINT32 | SINT32 | IPV4)) + sz = sizeof(uint32_t); + else if (opt->type & (UINT16 | SINT16)) + sz = sizeof(uint16_t); + else if (opt->type & UINT8) + sz = sizeof(uint8_t); + if (opt->type & ARRAY) { + /* The result of modulo zero is undefined. There are no + * options defined in this file that do not match one of + * the if-clauses above, so the following is not really + * necessary. However, to avoid confusion and unexpected + * behavior if the defined options are ever extended, + * returning false here seems sensible. */ + if (!sz) return -1; + return (dl % sz == 0) ? 0 : -1; + } + return (sz == dl) ? 0 : -1; + } + + /* unknown option, so let it pass */ + return 0; +} + +#ifdef DEBUG_MEMORY +static void +free_option_buffer(void) +{ + free(opt_buffer); +} +#endif + +#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL) +static const uint8_t * +get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + int bl = 0; + + /* DHCP Options are in TLV format with T and L each being a single + * byte. In general, here we have p -> T, ol=p+1 -> L, op -> V. + * We must make sure there is enough room to read both T and L. + */ + while (p + 1 < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!opt_buffer) { + opt_buffer = xmalloc(sizeof(*dhcp)); +#ifdef DEBUG_MEMORY + atexit(free_option_buffer); +#endif + } + if (!bp) + bp = opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = (p + *p < e) ? *p : e - (p + 1); + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = 0x80 | p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (valid_length(opt, bl, type) == -1) { + errno = EINVAL; + return NULL; + } + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_addr(struct in_addr *a, const struct dhcp_message *dhcp, + uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + + if (!p) + return -1; + memcpy(&a->s_addr, p, sizeof(a->s_addr)); + return 0; +} + +int +get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + uint32_t d; + + if (!p) + return -1; + memcpy(&d, p, sizeof(d)); + *i = ntohl(d); + return 0; +} + +int +get_option_uint16(uint16_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + uint16_t d; + + if (!p) + return -1; + memcpy(&d, p, sizeof(d)); + *i = ntohs(d); + return 0; +} + +int +get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + + if (!p) + return -1; + if (i) + *i = *(p); + return 0; +} + +/* Decode an RFC3397 DNS search order option into a space + * separated string. Returns length of string (including + * terminating zero) or zero on error. out may be NULL + * to just determine output length. */ +ssize_t +decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const uint8_t *r, *q = p; + int count = 0, l, hops; + uint8_t ltype; + + while (q - p < pl) { + r = NULL; + hops = 0; + /* We check we are inside our length again incase + * the data is NOT terminated correctly. */ + while ((l = *q++) && q - p < pl) { + ltype = l & 0xc0; + if (ltype == 0x80 || ltype == 0x40) + return 0; + else if (ltype == 0xc0) { /* pointer */ + l = (l & 0x3f) << 8; + l |= *q++; + /* save source of first jump. */ + if (!r) + r = q; + hops++; + if (hops > 255) + return 0; + q = p + l; + if (q - p >= pl) + return 0; + } else { + /* straightforward name segment, add with '.' */ + count += l + 1; + if (out) { + if ((ssize_t)l + 1 > len) { + errno = ENOBUFS; + return -1; + } + memcpy(out, q, l); + out += l; + *out++ = '.'; + len -= l; + len--; + } + q += l; + } + } + /* change last dot to space */ + if (out) + *(out - 1) = ' '; + if (r) + q = r; + } + + /* change last space to zero terminator */ + if (out) + *(out - 1) = 0; + + return count; +} + +static ssize_t +decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const uint8_t *e; + ssize_t b, bytes = 0, ocets; + uint8_t cidr; + struct in_addr addr; + char *o = out; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (pl < 5) { + errno = EINVAL; + return -1; + } + + e = p + pl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (cidr + 7) / 8; + if (!out) { + p += 4 + ocets; + bytes += ((4 * 4) * 2) + 4; + continue; + } + if ((((4 * 4) * 2) + 4) > len) { + errno = ENOBUFS; + return -1; + } + if (o != out) { + *o++ = ' '; + len--; + } + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + addr.s_addr = 0; + memcpy(&addr.s_addr, p, ocets); + b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr); + p += ocets; + } else + b = snprintf(o, len, "0.0.0.0/0"); + o += b; + len -= b; + + /* Finally, snag the router */ + memcpy(&addr.s_addr, p, 4); + p += 4; + b = snprintf(o, len, " %s", inet_ntoa(addr)); + o += b; + len -= b; + } + + if (out) + return o - out; + return bytes; +} + +static struct rt * +decode_rfc3442_rt(int dl, const uint8_t *data) +{ + const uint8_t *p = data; + const uint8_t *e; + uint8_t cidr; + size_t ocets; + struct rt *routes = NULL; + struct rt *rt = NULL; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (dl < 5) + return NULL; + + e = p + dl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + free_routes(routes); + errno = EINVAL; + return NULL; + } + + if (rt) { + rt->next = xzalloc(sizeof(*rt)); + rt = rt->next; + } else { + routes = rt = xzalloc(sizeof(*routes)); + } + rt->next = NULL; + + ocets = (cidr + 7) / 8; + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + memcpy(&rt->dest.s_addr, p, ocets); + p += ocets; + rt->net.s_addr = htonl(~0U << (32 - cidr)); + } + + /* Finally, snag the router */ + memcpy(&rt->gate.s_addr, p, 4); + p += 4; + } + return routes; +} + +static char * +decode_rfc3361(int dl, const uint8_t *data) +{ + uint8_t enc; + unsigned int l; + char *sip = NULL; + struct in_addr addr; + char *p; + + if (dl < 2) { + errno = EINVAL; + return 0; + } + + enc = *data++; + dl--; + switch (enc) { + case 0: + if ((l = decode_rfc3397(NULL, 0, dl, data)) > 0) { + sip = xmalloc(l); + decode_rfc3397(sip, l, dl, data); + } + break; + case 1: + if (dl == 0 || dl % 4 != 0) { + errno = EINVAL; + break; + } + addr.s_addr = INADDR_BROADCAST; + l = ((dl / sizeof(addr.s_addr)) * ((4 * 4) + 1)) + 1; + sip = p = xmalloc(l); + while (dl != 0) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + data += sizeof(addr.s_addr); + p += snprintf(p, l - (p - sip), "%s ", inet_ntoa(addr)); + dl -= sizeof(addr.s_addr); + } + *--p = '\0'; + break; + default: + errno = EINVAL; + return 0; + } + + return sip; +} + +/* Decode an RFC5969 6rd order option into a space + * separated string. Returns length of string (including + * terminating zero) or zero on error. */ +static ssize_t +decode_rfc5969(char *out, ssize_t len, int pl, const uint8_t *p) +{ + uint8_t ipv4masklen, ipv6prefixlen; + uint8_t ipv6prefix[16]; + uint8_t br[4]; + int i; + ssize_t b, bytes = 0; + + if (pl < 22) { + errno = EINVAL; + return 0; + } + + ipv4masklen = *p++; + pl--; + ipv6prefixlen = *p++; + pl--; + + for (i = 0; i < 16; i++) { + ipv6prefix[i] = *p++; + pl--; + } + if (out) { + b= snprintf(out, len, + "%d %d " + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x", + ipv4masklen, ipv6prefixlen, + ipv6prefix[0], ipv6prefix[1], ipv6prefix[2], ipv6prefix[3], + ipv6prefix[4], ipv6prefix[5], ipv6prefix[6], ipv6prefix[7], + ipv6prefix[8], ipv6prefix[9], ipv6prefix[10],ipv6prefix[11], + ipv6prefix[12],ipv6prefix[13],ipv6prefix[14], ipv6prefix[15] + ); + + len -= b; + out += b; + bytes += b; + } else { + bytes += 16 * 2 + 8 + 2 + 1 + 2; + } + + while (pl >= 4) { + br[0] = *p++; + br[1] = *p++; + br[2] = *p++; + br[3] = *p++; + pl -= 4; + + if (out) { + b= snprintf(out, len, " %d.%d.%d.%d", + br[0], br[1], br[2], br[3]); + len -= b; + out += b; + bytes += b; + } else { + bytes += (4 * 4); + } + } + + return bytes; +} + +char * +get_option_string(const struct dhcp_message *dhcp, uint8_t option) +{ + int type = 0; + int len; + const uint8_t *p; + char *s; + + p = get_option(dhcp, option, &len, &type); + if (!p || *p == '\0') + return NULL; + + if (type & RFC3397) { + type = decode_rfc3397(NULL, 0, len, p); + if (!type) { + errno = EINVAL; + return NULL; + } + s = xmalloc(sizeof(char) * type); + decode_rfc3397(s, type, len, p); + return s; + } + + if (type & RFC3361) + return decode_rfc3361(len, p); + + s = xmalloc(sizeof(char) * (len + 1)); + memcpy(s, p, len); + s[len] = '\0'; + return s; +} + +/* This calculates the netmask that we should use for static routes. + * This IS different from the calculation used to calculate the netmask + * for an interface address. */ +static uint32_t +route_netmask(uint32_t ip_in) +{ + /* used to be unsigned long - check if error */ + uint32_t p = ntohl(ip_in); + uint32_t t; + + if (IN_CLASSA(p)) + t = ~IN_CLASSA_NET; + else { + if (IN_CLASSB(p)) + t = ~IN_CLASSB_NET; + else { + if (IN_CLASSC(p)) + t = ~IN_CLASSC_NET; + else + t = 0; + } + } + + while (t & p) + t >>= 1; + + return (htonl(~t)); +} + +/* We need to obey routing options. + * If we have a CSR then we only use that. + * Otherwise we add static routes and then routers. */ +struct rt * +get_option_routes(const struct dhcp_message *dhcp, + const char *ifname, unsigned long long *opts) +{ + const uint8_t *p; + const uint8_t *e; + struct rt *routes = NULL; + struct rt *route = NULL; + int len; + + /* If we have CSR's then we MUST use these only */ + p = get_option(dhcp, DHO_CSR, &len, NULL); + /* Check for crappy MS option */ + if (!p) + p = get_option(dhcp, DHO_MSCSR, &len, NULL); + if (p) { + routes = decode_rfc3442_rt(len, p); + if (routes) { + if (!(*opts & DHCPCD_CSR_WARNED)) { + syslog(LOG_DEBUG, + "%s: using Classless Static Routes", + ifname); + *opts |= DHCPCD_CSR_WARNED; + } + return routes; + } + } + + /* OK, get our static routes first. */ + p = get_option(dhcp, DHO_STATICROUTE, &len, NULL); + if (p) { + e = p + len; + while (p < e) { + if (route) { + route->next = xmalloc(sizeof(*route)); + route = route->next; + } else + routes = route = xmalloc(sizeof(*routes)); + route->next = NULL; + memcpy(&route->dest.s_addr, p, 4); + p += 4; + memcpy(&route->gate.s_addr, p, 4); + p += 4; + route->net.s_addr = route_netmask(route->dest.s_addr); + } + } + + /* Now grab our routers */ + p = get_option(dhcp, DHO_ROUTER, &len, NULL); + if (p) { + e = p + len; + while (p < e) { + if (route) { + route->next = xzalloc(sizeof(*route)); + route = route->next; + } else + routes = route = xzalloc(sizeof(*route)); + memcpy(&route->gate.s_addr, p, 4); + p += 4; + } + } + + return routes; +} + +static size_t +encode_rfc1035(const char *src, uint8_t *dst) +{ + uint8_t *p = dst; + uint8_t *lp = p++; + + if (*src == '\0') + return 0; + for (; *src; src++) { + if (*src == '\0') + break; + if (*src == '.') { + /* Skip the trailing . */ + if (src[1] == '\0') + break; + *lp = p - lp - 1; + if (*lp == '\0') + return p - dst; + lp = p++; + } else + *p++ = (uint8_t)*src; + } + *lp = p - lp - 1; + *p++ = '\0'; + return p - dst; +} + +#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, + uint8_t type) +{ + struct dhcp_message *dhcp; + uint8_t *m, *lp, *p; + uint8_t *n_params = NULL; + time_t up = uptime() - iface->start_uptime; + uint32_t ul; + uint16_t sz; + 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; + + dhcp = xzalloc(sizeof (*dhcp)); + m = (uint8_t *)dhcp; + p = dhcp->options; + + 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; + /* 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; + } + + dhcp->op = DHCP_BOOTREQUEST; + dhcp->hwtype = iface->family; + switch (iface->family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + 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 = iface->state->xid; + dhcp->cookie = htonl(MAGIC_COOKIE); + + *p++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (iface->clientid) { + *p++ = DHO_CLIENTID; + memcpy(p, iface->clientid, iface->clientid[0] + 1); + p += iface->clientid[0] + 1; + } + + if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { + if (type == DHCP_DECLINE || + (type == DHCP_REQUEST && + 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) { + *p++ = DHO_MESSAGE; + len = strlen(DAD); + *p++ = len; + memcpy(p, DAD, len); + p += len; + } + + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) + PUTADDR(DHO_IPADDRESS, ifo->req_addr); + + if (type == DHCP_DISCOVER || + type == DHCP_INFORM || + type == DHCP_REQUEST) + { + *p++ = DHO_MAXMESSAGESIZE; + *p++ = 2; + sz = get_mtu(iface->name); + 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 (ifo->userclass[0]) { + *p++ = DHO_USERCLASS; + memcpy(p, ifo->userclass, ifo->userclass[0] + 1); + p += ifo->userclass[0] + 1; + } + + if (ifo->vendorclassid[0]) { + *p++ = DHO_VENDORCLASSID; + memcpy(p, ifo->vendorclassid, + ifo->vendorclassid[0] + 1); + p += ifo->vendorclassid[0] + 1; + } + + + if (type != DHCP_INFORM) { + if (ifo->leasetime != 0) { + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(ifo->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + /* Regardless of RFC2132, we should always send a hostname + * 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 (ifo->options & DHCPCD_HOSTNAME && ifo->hostname[0]) { + *p++ = DHO_HOSTNAME; + hp = strchr(ifo->hostname, '.'); + if (hp) + len = hp - ifo->hostname; + else + len = strlen(ifo->hostname); + *p++ = len; + memcpy(p, ifo->hostname, len); + p += len; + } + if (ifo->fqdn != FQDN_DISABLE && ifo->hostname[0]) { + /* IETF DHC-FQDN option (81), RFC4702 */ + *p++ = DHO_FQDN; + lp = p; + *p++ = 3; + /* + * Flags: 0000NEOS + * S: 1 => Client requests Server to update + * a RR in DNS as well as PTR + * O: 1 => Server indicates to client that + * DNS has been updated + * E: 1 => Name data is DNS format + * N: 1 => Client requests Server to not + * update DNS + */ + *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(ifo->hostname, p); + *lp += ul; + p += ul; + } + + /* vendor is already encoded correctly, so just add it */ + if (ifo->vendor[0]) { + *p++ = DHO_VENDOR; + memcpy(p, ifo->vendor, ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; + } + + *p++ = DHO_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (opt = dhcp_opts; opt->option; opt++) { + if (!(opt->type & REQUEST || + has_option_mask(ifo->requestmask, opt->option))) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + *p++ = opt->option; + } + *n_params = p - n_params - 1; + } + *p++ = DHO_END; + +#ifdef BOOTP_MESSAGE_LENTH_MIN + /* Some crappy DHCP servers think they have to obey the BOOTP minimum + * message length. + * They are wrong, but we should still cater for them. */ + while (p - m < BOOTP_MESSAGE_LENTH_MIN) + *p++ = DHO_PAD; +#endif + + *message = dhcp; + return p - m; +} + +ssize_t +write_lease(const struct interface *iface, const struct dhcp_message *dhcp) +{ + int fd; + ssize_t bytes = sizeof(*dhcp); + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l; + uint8_t o = 0; + + /* We don't write BOOTP leases */ + if (is_bootp(dhcp)) { + unlink(iface->leasefile); + return 0; + } + + syslog(LOG_DEBUG, "%s: writing lease `%s'", + iface->name, iface->leasefile); + + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0444); +#ifdef ANDROID + if (fd == -1 && errno == EACCES) { + /* the lease file might have been created when dhcpcd was running as root */ + unlink(iface->leasefile); + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0444); + } +#endif + if (fd == -1) { + syslog(LOG_ERR, "%s: open: %m", iface->name); + return -1; + } + + /* Only write as much as we need */ + while (p < e) { + o = *p; + if (o == DHO_END) { + bytes = p - (const uint8_t *)dhcp; + break; + } + p++; + if (o != DHO_PAD) { + l = *p++; + p += l; + } + } + bytes = write(fd, dhcp, bytes); + close(fd); + return bytes; +} + +struct dhcp_message * +read_lease(const struct interface *iface) +{ + int fd; + struct dhcp_message *dhcp; + ssize_t bytes; + + fd = open(iface->leasefile, O_RDONLY); + 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)); + close(fd); + if (bytes < 0) { + free(dhcp); + dhcp = NULL; + } + return dhcp; +} + +static ssize_t +print_string(char *s, ssize_t len, int dl, const uint8_t *data) +{ + uint8_t c; + const uint8_t *e, *p; + ssize_t bytes = 0; + ssize_t r; + + e = data + dl; + while (data < e) { + c = *data++; + if (c == '\0') { + /* If rest is all NULL, skip it. */ + for (p = data; p < e; p++) + if (*p != '\0') + break; + if (p == e) + break; + } + if (!isascii(c) || !isprint(c)) { + if (s) { + if (len < 5) { + errno = ENOBUFS; + return -1; + } + r = snprintf(s, len, "\\%03o", c); + len -= r; + bytes += r; + s += r; + } else + bytes += 4; + continue; + } + switch (c) { + case '"': /* FALLTHROUGH */ + case '\'': /* FALLTHROUGH */ + case '$': /* FALLTHROUGH */ + case '`': /* FALLTHROUGH */ + case '\\': /* FALLTHROUGH */ + case '|': /* FALLTHROUGH */ + case '&': + if (s) { + if (len < 3) { + errno = ENOBUFS; + return -1; + } + *s++ = '\\'; + len--; + } + bytes++; + break; + } + if (s) { + *s++ = c; + len--; + } + bytes++; + } + + /* NULL */ + if (s) + *s = '\0'; + bytes++; + return bytes; +} + +static ssize_t +print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data) +{ + const uint8_t *e, *t; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + struct in_addr addr; + ssize_t bytes = 0; + ssize_t l; + char *tmp; + + if (type & RFC3397) { + l = decode_rfc3397(NULL, 0, dl, data); + if (l < 1) + return l; + tmp = xmalloc(l); + decode_rfc3397(tmp, l, dl, data); + l = print_string(s, len, l - 1, (uint8_t *)tmp); + free(tmp); + return l; + } + + if (type & RFC3361) { + if ((tmp = decode_rfc3361(dl, data)) == NULL) + return -1; + l = strlen(tmp); + l = print_string(s, len, l - 1, (uint8_t *)tmp); + free(tmp); + return l; + } + + if (type & RFC3442) + return decode_rfc3442(s, len, dl, data); + + if (type & RFC5969) + return decode_rfc5969(s, len, dl, data); + + if (type & STRING) { + /* Some DHCP servers return NULL strings */ + if (*data == '\0') + return 0; + return print_string(s, len, dl, data); + } + + if (!s) { + if (type & UINT8) + l = 3; + else if (type & UINT16) { + l = 5; + dl /= 2; + } else if (type & SINT16) { + l = 6; + dl /= 2; + } else if (type & UINT32) { + l = 10; + dl /= 4; + } else if (type & SINT32) { + l = 11; + dl /= 4; + } else if (type & IPV4) { + l = 16; + dl /= 4; + } else { + errno = EINVAL; + return -1; + } + return (l + 1) * dl; + } + + t = data; + e = data + dl; + while (data < e) { + if (data != t) { + *s++ = ' '; + bytes++; + len--; + } + if (type & UINT8) { + l = snprintf(s, len, "%d", *data); + data++; + } else if (type & UINT16) { + memcpy(&u16, data, sizeof(u16)); + u16 = ntohs(u16); + l = snprintf(s, len, "%d", u16); + data += sizeof(u16); + } else if (type & SINT16) { + memcpy(&s16, data, sizeof(s16)); + s16 = ntohs(s16); + l = snprintf(s, len, "%d", s16); + data += sizeof(s16); + } else if (type & UINT32) { + memcpy(&u32, data, sizeof(u32)); + u32 = ntohl(u32); + l = snprintf(s, len, "%d", u32); + data += sizeof(u32); + } else if (type & SINT32) { + memcpy(&s32, data, sizeof(s32)); + s32 = ntohl(s32); + l = snprintf(s, len, "%d", s32); + data += sizeof(s32); + } else if (type & IPV4) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + l = snprintf(s, len, "%s", inet_ntoa(addr)); + data += sizeof(addr.s_addr); + } else + l = 0; + if (len <= l) { + bytes += len; + break; + } + len -= l; + bytes += l; + s += l; + } + + return bytes; +} + +ssize_t +configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, + const struct if_options *ifo) +{ + const uint8_t *p; + int pl; + struct in_addr addr; + struct in_addr net; + struct in_addr brd; + char *val, *v; + const struct dhcp_opt *opt; + ssize_t len, e = 0; + char **ep; + char cidr[4]; + uint8_t overl = 0; + + get_option_uint8(&overl, dhcp, DHO_OPTIONSOVERLOADED); + + if (!env) { + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (has_option_mask(ifo->nomask, opt->option)) + continue; + if (get_option_raw(dhcp, opt->option)) + e++; + } + if (dhcp->yiaddr || dhcp->ciaddr) + e += 5; + if (*dhcp->bootfile && !(overl & 1)) + e++; + if (*dhcp->servername && !(overl & 2)) + e++; + return e; + } + + ep = env; + if (dhcp->yiaddr || dhcp->ciaddr) { + /* Set some useful variables that we derive from the DHCP + * message but are not necessarily in the options */ + addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr; + setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); + if (get_option_addr(&net, dhcp, DHO_SUBNETMASK) == -1) { + net.s_addr = get_netmask(addr.s_addr); + setvar(&ep, prefix, "subnet_mask", inet_ntoa(net)); + } + snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); + setvar(&ep, prefix, "subnet_cidr", cidr); + if (get_option_addr(&brd, dhcp, DHO_BROADCAST) == -1) { + brd.s_addr = addr.s_addr | ~net.s_addr; + setvar(&ep, prefix, "broadcast_address", inet_ntoa(brd)); + } + addr.s_addr = dhcp->yiaddr & net.s_addr; + setvar(&ep, prefix, "network_number", inet_ntoa(addr)); + } + + if (*dhcp->bootfile && !(overl & 1)) + setvar(&ep, prefix, "filename", (const char *)dhcp->bootfile); + if (*dhcp->servername && !(overl & 2)) + setvar(&ep, prefix, "server_name", (const char *)dhcp->servername); + + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (has_option_mask(ifo->nomask, opt->option)) + continue; + val = NULL; + p = get_option(dhcp, opt->option, &pl, NULL); + if (!p) + continue; + /* We only want the FQDN name */ + if (opt->option == DHO_FQDN) { + p += 3; + pl -= 3; + } + len = print_option(NULL, 0, opt->type, pl, p); + if (len < 0) + return -1; + e = strlen(prefix) + strlen(opt->var) + len + 4; + v = val = *ep++ = xmalloc(e); + v += snprintf(val, e, "%s_%s=", prefix, opt->var); + if (len != 0) + print_option(v, len, opt->type, pl, p); + } + + 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/dhcpcd/dhcp.h b/dhcpcd/dhcp.h new file mode 100644 index 0000000..fb27e71 --- /dev/null +++ b/dhcpcd/dhcp.h
@@ -0,0 +1,204 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <stdint.h> + +#include "common.h" + +/* Max MTU - defines dhcp option length */ +#define MTU_MAX 1500 +#define MTU_MIN 576 + +/* UDP port numbers for DHCP */ +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 + +#define MAGIC_COOKIE 0x63825363 +#define BROADCAST_FLAG 0x8000 + +/* DHCP message OP code */ +#define DHCP_BOOTREQUEST 1 +#define DHCP_BOOTREPLY 2 + +/* DHCP message type */ +#define DHCP_DISCOVER 1 +#define DHCP_OFFER 2 +#define DHCP_REQUEST 3 +#define DHCP_DECLINE 4 +#define DHCP_ACK 5 +#define DHCP_NAK 6 +#define DHCP_RELEASE 7 +#define DHCP_INFORM 8 + +/* 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 { + DHO_PAD = 0, + DHO_SUBNETMASK = 1, + DHO_ROUTER = 3, + DHO_DNSSERVER = 6, + DHO_HOSTNAME = 12, + DHO_DNSDOMAIN = 15, + DHO_MTU = 26, + DHO_BROADCAST = 28, + DHO_STATICROUTE = 33, + DHO_NISDOMAIN = 40, + DHO_NISSERVER = 41, + DHO_NTPSERVER = 42, + DHO_VENDOR = 43, + DHO_IPADDRESS = 50, + DHO_LEASETIME = 51, + DHO_OPTIONSOVERLOADED = 52, + DHO_MESSAGETYPE = 53, + DHO_SERVERID = 54, + DHO_PARAMETERREQUESTLIST = 55, + DHO_MESSAGE = 56, + DHO_MAXMESSAGESIZE = 57, + DHO_RENEWALTIME = 58, + DHO_REBINDTIME = 59, + DHO_VENDORCLASSID = 60, + DHO_CLIENTID = 61, + DHO_USERCLASS = 77, /* RFC 3004 */ + DHO_FQDN = 81, + DHO_DNSSEARCH = 119, /* RFC 3397 */ + DHO_CSR = 121, /* RFC 3442 */ + DHO_SIXRD = 212, /* RFC 5969 */ + DHO_MSCSR = 249, /* MS code for RFC 3442 */ + DHO_END = 255 +}; + +/* FQDN values - lsnybble used in flags + * hsnybble to create order + * and to allow 0x00 to mean disable + */ +enum FQDN { + FQDN_DISABLE = 0x00, + FQDN_NONE = 0x18, + FQDN_PTR = 0x20, + FQDN_BOTH = 0x31 +}; + +/* Sizes for DHCP options */ +#define DHCP_CHADDR_LEN 16 +#define SERVERNAME_LEN 64 +#define BOOTFILE_LEN 128 +#define DHCP_UDP_LEN (14 + 20 + 8) +#define DHCP_FIXED_LEN (DHCP_UDP_LEN + 226) +#define DHCP_OPTION_LEN (MTU_MAX - DHCP_FIXED_LEN) + +/* Some crappy DHCP servers require the BOOTP minimum length */ +#define BOOTP_MESSAGE_LENTH_MIN 300 + +struct dhcp_message { + uint8_t op; /* message type */ + uint8_t hwtype; /* hardware address type */ + uint8_t hwlen; /* hardware address length */ + uint8_t hwopcount; /* should be zero in client message */ + uint32_t xid; /* transaction id */ + uint16_t secs; /* elapsed time in sec. from boot */ + uint16_t flags; + uint32_t ciaddr; /* (previously allocated) client IP */ + uint32_t yiaddr; /* 'your' client IP address */ + uint32_t siaddr; /* should be zero in client's messages */ + uint32_t giaddr; /* should be zero in client's messages */ + uint8_t chaddr[DHCP_CHADDR_LEN]; /* client's hardware address */ + uint8_t servername[SERVERNAME_LEN]; /* server host name */ + uint8_t bootfile[BOOTFILE_LEN]; /* boot file name */ + uint32_t cookie; + uint8_t options[DHCP_OPTION_LEN]; /* message options - cookie */ +} _packed; + +struct dhcp_lease { + struct in_addr addr; + struct in_addr net; + struct in_addr brd; + uint32_t leasetime; + uint32_t renewaltime; + uint32_t rebindtime; + struct in_addr server; + 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 *, 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); +#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 *, + unsigned long long *); +ssize_t decode_rfc3397(char *, ssize_t, int, const uint8_t *); +ssize_t configure_env(char **, const char *, const struct dhcp_message *, + const struct if_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 *); +void get_lease(struct dhcp_lease *, const struct dhcp_message *); + +#endif
diff --git a/dhcpcd/dhcpcd-hooks/01-test b/dhcpcd/dhcpcd-hooks/01-test new file mode 100644 index 0000000..ef7896d --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/01-test
@@ -0,0 +1,6 @@ +# Just echo our DHCP options we have + +if [ "${reason}" = "TEST" ]; then + set | grep "^\(interface\|metric\|pid\|reason\|skip_hooks\)=" | sort + set | grep "^\(new_\|old_\)" | sort +fi
diff --git a/dhcpcd/dhcpcd-hooks/02-dump b/dhcpcd/dhcpcd-hooks/02-dump new file mode 100644 index 0000000..cbb46ea --- /dev/null +++ b/dhcpcd/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/dhcpcd-hooks/10-mtu b/dhcpcd/dhcpcd-hooks/10-mtu new file mode 100644 index 0000000..4265b48 --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/10-mtu
@@ -0,0 +1,5 @@ +# Configure the MTU for the interface + +if [ -n "${new_interface_mtu}" ]; then + ifconfig "${interface}" mtu "${new_interface_mtu}" +fi
diff --git a/dhcpcd/dhcpcd-hooks/20-dns.conf b/dhcpcd/dhcpcd-hooks/20-dns.conf new file mode 100755 index 0000000..d19c6a9 --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/20-dns.conf
@@ -0,0 +1,51 @@ +# Set net.<iface>.dnsN properties that contain the +# DNS server addresses given by the DHCP server. + +if [[ $interface == p2p* ]] + then + intf=p2p + else + intf=$interface +fi + +set_dns_props() +{ + case "${new_domain_name_servers}" in + "") return 0;; + esac + + count=1 + for i in 1 2 3 4; do + setprop dhcp.${intf}.dns${i} "" + done + + count=1 + for dnsaddr in ${new_domain_name_servers}; do + setprop dhcp.${intf}.dns${count} ${dnsaddr} + count=$(($count + 1)) + done + + separator=" " + if [ -z "$new_domain_name" ]; then + separator="" + else + if [ -z "$new_domain_search" ]; then + separator="" + fi + fi + setprop dhcp.${interface}.domain "${new_domain_name}$separator${new_domain_search}" +} + +unset_dns_props() +{ + for i in 1 2 3 4; do + setprop dhcp.${intf}.dns${i} "" + done + + setprop dhcp.${interface}.domain "" +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) set_dns_props;; +EXPIRE|FAIL|IPV4LL|RELEASE|STOP) unset_dns_props;; +esac
diff --git a/dhcpcd/dhcpcd-hooks/20-resolv.conf b/dhcpcd/dhcpcd-hooks/20-resolv.conf new file mode 100644 index 0000000..628636d --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/20-resolv.conf
@@ -0,0 +1,126 @@ +# Generate /etc/resolv.conf +# Support resolvconf(8) if available +# We can merge other dhcpcd resolv.conf files into one like resolvconf, +# but resolvconf is preferred as other applications like VPN clients +# can readily hook into it. +# Also, resolvconf can configure local nameservers such as bind +# or dnsmasq. This is important as the libc resolver isn't that powerful. + +resolv_conf_dir="$state_dir/resolv.conf" + +build_resolv_conf() +{ + 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") + + # Build the resolv.conf + if [ -n "$interfaces" ]; then + # Build the header + for x in ${interfaces}; do + header="$header${header:+, }$x" + done + + # Build the search list + domain=$(cd "$resolv_conf_dir"; \ + key_get_value "domain " ${interfaces}) + search=$(cd "$resolv_conf_dir"; \ + key_get_value "search " ${interfaces}) + set -- ${domain} + 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"; \ + key_get_value "nameserver " ${interfaces}) + for x in $(uniqify ${srvs}); do + servers="${servers}nameserver $x\n" + done + fi + header="$signature_base${header:+ $from }$header" + + # Assemble resolv.conf using our head and tail files + [ -f "$cf" ] && rm -f "$cf" + [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" + echo "$header" > "$cf" + if [ -f /etc/resolv.conf.head ]; then + cat /etc/resolv.conf.head >> "$cf" + else + echo "# /etc/resolv.conf.head can replace this line" >> "$cf" + fi + printf "$domain$search$servers" >> "$cf" + if [ -f /etc/resolv.conf.tail ]; then + cat /etc/resolv.conf.tail >> "$cf" + else + echo "# /etc/resolv.conf.tail can replace this line" >> "$cf" + fi + cat "$cf" > /etc/resolv.conf + chmod 644 /etc/resolv.conf + rm -f "$cf" +} + +add_resolv_conf() +{ + 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 + remove_resolv_conf + return $? + fi + + 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" + done + if type resolvconf >/dev/null 2>&1; then + [ -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 + [ -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 + else + if [ -e "$resolv_conf_dir/$interface" ]; then + rm -f "$resolv_conf_dir/$interface" + fi + build_resolv_conf + fi +} + +if $if_up; then + add_resolv_conf +elif $if_down; then + remove_resolv_conf +fi
diff --git a/dhcpcd/dhcpcd-hooks/25-static-routes.conf b/dhcpcd/dhcpcd-hooks/25-static-routes.conf new file mode 100755 index 0000000..c6b7c4a --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/25-static-routes.conf
@@ -0,0 +1,61 @@ +# Set dhcp.<iface>.routeN.dest and dhcp.<iface>.routeN.via properties with +# the static routes provided by the DHCP server. + +# CAUTION +# +# Placing this in the hooks directory will allow DHCP servers to push static +# routes to ALL of the interfaces that use the directory. +# +# To avoid this, create separate dhcpcd configurations, one for the interfaces +# that should accept static routes and another for the interfaces that should +# not accept static routes routes. + +# Add this script in the hooks directory only for the interfaces that should +# accept static routes. +# +# Add "nooption classless_static_routes, static_routes" to the dhcpcd.conf +# file for the interfaces that should not accept static routes. Do not add the +# script to the hooks directory. + +next_set_interface=1 + +set_route_props_from_list() +{ + while [[ $# -ge 2 ]]; do + setprop dhcp.${interface}.route${next_set_interface}.dest $1 + shift + setprop dhcp.${interface}.route${next_set_interface}.via $1 + shift + next_set_interface=$(($next_set_interface + 1)) + done +} + +unset_route_props() +{ + next_clear_interface=1 + while [[ ! -z "$(getprop dhcp.${interface}.route${next_clear_interface}.dest)" ]]; do + setprop dhcp.${interface}.route${next_clear_interface}.dest "" + setprop dhcp.${interface}.route${next_clear_interface}.via "" + next_clear_interface=$(($next_clear_interface + 1)) + done + while [[ ! -z "$(getprop dhcp.${interface}.route${next_clear_interface}.via)" ]]; do + setprop dhcp.${interface}.route${next_clear_interface}.dest "" + setprop dhcp.${interface}.route${next_clear_interface}.via "" + next_clear_interface=$(($next_clear_interface + 1)) + done +} + +set_route_props() +{ + unset_route_props + if [[ ! -z "${new_classless_static_routes}" ]]; then + set_route_props_from_list ${new_classless_static_routes} + else + set_route_props_from_list ${new_static_routes} + fi +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) set_route_props;; +EXPIRE|FAIL|IPV4LL|RELEASE|STOP) unset_route_props;; +esac
diff --git a/dhcpcd/dhcpcd-hooks/29-lookup-hostname b/dhcpcd/dhcpcd-hooks/29-lookup-hostname new file mode 100644 index 0000000..8661fcc --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/29-lookup-hostname
@@ -0,0 +1,34 @@ +# Lookup the hostname in DNS if not set + +lookup_hostname() +{ + [ -z "$new_ip_address" ] && return 1 + local h= + # Silly ISC programs love to send error text to stdout + if type dig >/dev/null 2>&1; then + h=$(dig +short -x $new_ip_address) + if [ $? = 0 ]; then + echo "$h" | sed 's/\.$//' + return 0 + fi + elif type host >/dev/null 2>&1; then + h=$(host $new_ip_address) + if [ $? = 0 ]; then + echo "$h" \ + | sed 's/.* domain name pointer \(.*\)./\1/' + return 0 + fi + fi + return 1 +} + +set_hostname() +{ + if [ -z "$new_host_name" -a -z "$new_fqdn_name" ]; then + export new_host_name="$(lookup_hostname)" + fi +} + +if $if_up; then + set_hostname +fi
diff --git a/dhcpcd/dhcpcd-hooks/30-hostname b/dhcpcd/dhcpcd-hooks/30-hostname new file mode 100644 index 0000000..87446fb --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/30-hostname
@@ -0,0 +1,34 @@ +# Set the hostname from DHCP data if required + +need_hostname() +{ + 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") + true;; + *) + false;; + esac +} + +set_hostname() +{ + if need_hostname; then + if [ -n "$new_host_name" ]; then + hostname "$new_host_name" + elif [ -n "$new_fqdn_name" ]; then + hostname "$new_fqdn_name" + fi + fi +} + +if $if_up; then + set_hostname +fi
diff --git a/dhcpcd/dhcpcd-hooks/50-dhcpcd-compat b/dhcpcd/dhcpcd-hooks/50-dhcpcd-compat new file mode 100644 index 0000000..651bc08 --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/50-dhcpcd-compat
@@ -0,0 +1,41 @@ +# Compat enter hook shim for older dhcpcd versions + +IPADDR=$new_ip_address +INTERFACE=$interface +NETMASK=$new_subnet_mask +BROADCAST=$new_broadcast_address +NETWORK=$new_network_number +DHCPSID=$new_dhcp_server_identifier +GATEWAYS=$new_routers +DNSSERVERS=$new_domain_name_servers +DNSDOMAIN=$new_domain_name +DNSSEARCH=$new_domain_search +NISDOMAIN=$new_nis_domain +NISSERVERS=$new_nis_servers +NTPSERVERS=$new_ntp_servers + +GATEWAY= +for x in $new_routers; do + GATEWAY="$GATEWAY${GATEWAY:+,}$x" +done +DNS= +for x in $new_domain_name_servers; do + DNS="$DNS${DNS:+,}$x" +done + +x="down" +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 + for x in IPADDR INTERFACE NETMASK BROADCAST NETWORK DHCPSID GATEWAYS \ + DNSSERVERS DNSDOMAIN DNSSEARCH NISDOMAIN NISSERVERS \ + NTPSERVERS GATEWAY DNS; do + eval echo "$x=\'\$$x\'" >> /var/lib/dhcpcd-"$INTERFACE".info + done +fi + +set -- /var/lib/dhcpcd-"$INTERFACE".info "$x"
diff --git a/dhcpcd/dhcpcd-hooks/50-ntp.conf b/dhcpcd/dhcpcd-hooks/50-ntp.conf new file mode 100644 index 0000000..765baa7 --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/50-ntp.conf
@@ -0,0 +1,98 @@ +# Sample dhcpcd hook script for ntp +# Like our resolv.conf hook script, we store a database of ntp.conf files +# and merge into /etc/ntp.conf + +# You can set the env var NTP_CONF to another file like this +# dhcpcd -e NTP_CONF=/usr/pkg/etc/ntpd.conf +# or by adding this to /etc/dhcpcd.enter-hook +# NTP_CONF=/usr/pkg/etc/ntpd.conf +# to use openntpd from pkgsrc instead of the system provided ntp. + +: ${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=${NTP_CONF:-/etc/ntp.conf} + +build_ntp_conf() +{ + local cf="$state_dir/ntp.conf.$interface" + local interfaces= header= srvs= servers= x= + + # Build a list of interfaces + interfaces=$(list_interfaces "$ntp_conf_dir") + + if [ -n "$interfaces" ]; then + # Build the header + for x in ${interfaces}; do + header="$header${header:+, }$x" + done + + # Build a server list + srvs=$(cd "$ntp_conf_dir"; + key_get_value "server " $interfaces) + if [ -n "$srvs" ]; then + for x in $(uniqify $srvs); do + servers="${servers}server $x\n" + done + fi + fi + + # Merge our config into ntp.conf + [ -e "$cf" ] && rm -f "$cf" + [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" + + if [ -n "$NTP_DHCP_CONF" ]; then + 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 + fi +} + +add_ntp_conf() +{ + 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" + done + fi + build_ntp_conf +} + +remove_ntp_conf() +{ + if [ -e "$ntp_conf_dir/$interface" ]; then + rm "$ntp_conf_dir/$interface" + fi + build_ntp_conf +} + +if $if_up; then + add_ntp_conf add +elif $if_down; then + remove_ntp_conf del +fi
diff --git a/dhcpcd/dhcpcd-hooks/50-yp.conf b/dhcpcd/dhcpcd-hooks/50-yp.conf new file mode 100644 index 0000000..a1f5798 --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/50-yp.conf
@@ -0,0 +1,51 @@ +# Sample dhcpcd hook for ypbind +# This script is only suitable for the Linux version. + +ypbind_pid() +{ + [ -s /var/run/ypbind.pid ] && cat /var/run/ypbind.pid +} + +make_yp_conf() +{ + [ -z "$new_nis_domain" -a -z "$new_nis_servers" ] && return 0 + local cf=/etc/yp.conf."$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" + fi + else + prefix="ypserver " + fi + for x in $new_nis_servers; do + echo "$prefix$x" >> "$cf" + done + save_conf /etc/yp.conf + cat "$cf" > /etc/yp.conf + rm -f "$cf" + pid="$(ypbind_pid)" + if [ -n "$pid" ]; then + kill -HUP "$pid" + fi +} + +restore_yp_conf() +{ + [ -n "$old_nis_domain" ] && domainname "" + restore_conf /etc/yp.conf || return 0 + local pid="$(ypbind_pid)" + if [ -n "$pid" ]; then + kill -HUP "$pid" + fi +} + +if $if_up; then + make_yp_conf +elif $if_down; then + restore_yp_conf +fi
diff --git a/dhcpcd/dhcpcd-hooks/50-ypbind b/dhcpcd/dhcpcd-hooks/50-ypbind new file mode 100644 index 0000000..9a1fecc --- /dev/null +++ b/dhcpcd/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/dhcpcd-hooks/90-NetworkManager b/dhcpcd/dhcpcd-hooks/90-NetworkManager new file mode 100644 index 0000000..c4d69fe --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/90-NetworkManager
@@ -0,0 +1,8 @@ +# Hook for NetworkManager-0.7.0 +# NOTE: NetworkManager will override the script dhcpcd calls, so this hook +# only makes sense if NetworkManager is patched NOT to override the +# script dhcpcd would call. + +if [ -x /usr/libexec/nm-dhcp-client.action ]; then + /usr/libexec/nm-dhcp-client.action +fi
diff --git a/dhcpcd/dhcpcd-hooks/95-configured b/dhcpcd/dhcpcd-hooks/95-configured new file mode 100644 index 0000000..fe76c22 --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/95-configured
@@ -0,0 +1,35 @@ +# This script runs last, after all network configuration +# has completed. It sets a property to let the framework +# know that setting up the interface is complete. + +if [[ $interface == p2p* ]] + then + intf=p2p + else + intf=$interface +fi + +# For debugging: +setprop dhcp.${intf}.reason "${reason}" + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) + setprop dhcp.${intf}.ipaddress "${new_ip_address}" + setprop dhcp.${intf}.gateway "${new_routers%% *}" + setprop dhcp.${intf}.mask "${new_subnet_mask}" + setprop dhcp.${intf}.leasetime "${new_dhcp_lease_time}" + setprop dhcp.${intf}.server "${new_dhcp_server_identifier}" + setprop dhcp.${intf}.vendorInfo "${new_vendor_encapsulated_options}" + setprop dhcp.${intf}.mtu "${new_interface_mtu}" + + setprop dhcp.${intf}.result "ok" + ;; + +EXPIRE|FAIL|IPV4LL|STOP) + setprop dhcp.${intf}.result "failed" + ;; + +RELEASE) + setprop dhcp.${intf}.result "released" + ;; +esac
diff --git a/dhcpcd/dhcpcd-hooks/Makefile b/dhcpcd/dhcpcd-hooks/Makefile new file mode 100644 index 0000000..7563d2d --- /dev/null +++ b/dhcpcd/dhcpcd-hooks/Makefile
@@ -0,0 +1,19 @@ +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: + +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/dhcpcd-run-hooks b/dhcpcd/dhcpcd-run-hooks new file mode 100755 index 0000000..a39a133 --- /dev/null +++ b/dhcpcd/dhcpcd-run-hooks
@@ -0,0 +1,31 @@ +#!/system/bin/sh +# dhcpcd client configuration script + +# Handy variables and functions for our hooks to use +from="from" +signature_base="# Generated by dhcpcd" +signature="${signature_base} ${from} ${interface}" +signature_base_end="# End of dhcpcd" +signature_end="${signature_base_end} ${from} ${interface}" +state_dir="/data/misc/dhcpcd" + +# We source each script into this one so that scripts run earlier can +# remove variables from the environment so later scripts don't see them. +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + /system/etc/dhcpcd/dhcpcd.enter-hook \ + /system/etc/dhcpcd/dhcpcd-hooks/* \ + /system/etc/dhcpcd/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;; + esac + done + if [ -f "${hook}" ]; then + . "${hook}" + fi +done
diff --git a/dhcpcd/dhcpcd-run-hooks.8 b/dhcpcd/dhcpcd-run-hooks.8 new file mode 100644 index 0000000..882b7d5 --- /dev/null +++ b/dhcpcd/dhcpcd-run-hooks.8
@@ -0,0 +1,137 @@ +.\" 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. +.\" +.Dd August 24, 2010 +.Dt DHCPCD-RUN-HOOKS 8 SMM +.Os +.Sh NAME +.Nm dhcpcd-run-hooks +.Nd DHCP client configuration script +.Sh DESCRIPTION +.Nm +is used by +.Xr dhcpcd 8 +to run any system and user defined hook scripts. +System hook scripts are found in +.Pa /libexec/dhcpcd-hooks +and the user defined hooks are +.Pa /etc/dhcpcd.enter-hook . +and +.Pa /etc/dhcpcd.exit-hook . +The default install supplies hook scripts for configuring +.Pa /etc/resolv.conf +and the hostname. +Your distribution may have included other hook scripts to say configure +ntp or ypbind. +A test hook is also supplied that simply echos the dhcp variables to the +console from DISCOVER message. +.Pp +Each time +.Nm +is invoked, +.Ev $interface +is set to the interface that +.Nm dhcpcd +is run on and +.Ev $reason +is to the reason why +.Nm +was invoked. +DHCP information to be configured is held in variables starting with the word +new_ and old DHCP information to be removed is held in variables starting with +the word old_. +.Nm dhcpcd +can display the full list of variables it knows how about by using the +.Fl V , -variables +argument. +.Pp +Here's a list of reasons why +.Nm +could be invoked: +.Bl -tag -width 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. +.It Dv BOUND +dhcpcd obtained a new lease from a DHCP server. +.It Dv RENEW +dhcpcd renewed it's lease. +.It Dv REBIND +dhcpcd has rebound to a new DHCP server. +.It Dv REBOOT +dhcpcd successfully requested a lease from a DHCP server. +.It Dv IPV4LL +dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. +.It Dv STATIC +dhcpcd has been configured with a static configuration which has not been +obtained from a DHCP server. +.It Dv 3RDPARTY +dhcpcd is monitoring the interface for a 3rd party to give it an IP address. +.It Dv TIMEOUT +dhcpcd failed to contact any DHCP servers but was able to use an old lease. +.It Dv EXPIRE +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 +When +.Nm +runs, it loads +.Pa /etc/dhcpcd.enter-hook +and any scripts found in +.Pa /libexec/dhcpcd-hooks +in a lexical order and then finally +.Pa /etc/dhcpcd.exit-hook +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples Aq roy@marples.name +.Sh BUGS +Please report them to http://roy.marples.name/projects/dhcpcd
diff --git a/dhcpcd/dhcpcd-run-hooks.8.in b/dhcpcd/dhcpcd-run-hooks.8.in new file mode 100644 index 0000000..c0dc081 --- /dev/null +++ b/dhcpcd/dhcpcd-run-hooks.8.in
@@ -0,0 +1,148 @@ +.\" Copyright (c) 2006-2012 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd March 19, 2012 +.Dt DHCPCD-RUN-HOOKS 8 +.Os +.Sh NAME +.Nm dhcpcd-run-hooks +.Nd DHCP client configuration script +.Sh DESCRIPTION +.Nm +is used by +.Xr dhcpcd 8 +to run any system and user defined hook scripts. +System hook scripts are found in +.Pa @HOOKDIR@ +and the user defined hooks are +.Pa @SYSCONFDIR@/dhcpcd.enter-hook . +and +.Pa @SYSCONFDIR@/dhcpcd.exit-hook . +The default install supplies hook scripts for configuring +.Pa /etc/resolv.conf +and the hostname. +Your distribution may have included other hook scripts to say configure +ntp or ypbind. +A test hook is also supplied that simply echos the dhcp variables to the +console from DISCOVER message. +.Pp +Each time +.Nm +is invoked, +.Ev $interface +is set to the interface that +.Nm dhcpcd +is run on and +.Ev $reason +is to the reason why +.Nm +was invoked. +DHCP information to be configured is held in variables starting with the word +new_ and old DHCP information to be removed is held in variables starting with +the word old_. +.Nm dhcpcd +can display the full list of variables it knows how about by using the +.Fl V , -variables +argument. +.Pp +Here's a list of reasons why +.Nm +could be invoked: +.Bl -tag -width ROUTERADVERT +.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. +.It Dv BOUND +dhcpcd obtained a new lease from a DHCP server. +.It Dv RENEW +dhcpcd renewed it's lease. +.It Dv REBIND +dhcpcd has rebound to a new DHCP server. +.It Dv REBOOT +dhcpcd successfully requested a lease from a DHCP server. +.It Dv IPV4LL +dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. +.It Dv STATIC +dhcpcd has been configured with a static configuration which has not been +obtained from a DHCP server. +.It Dv 3RDPARTY +dhcpcd is monitoring the interface for a 3rd party to give it an IP address. +.It Dv TIMEOUT +dhcpcd failed to contact any DHCP servers but was able to use an old lease. +.It Dv EXPIRE +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. +.It Dv ROUTERADVERT +dhcpcd has received an IPv6 Router Advertisment, or one has expired. +.El +.Sh FILES +When +.Nm +runs, it loads +.Pa @SYSCONFDIR@/dhcpcd.enter-hook +and any scripts found in +.Pa @HOOKDIR@ +in a lexical order and then finally +.Pa @SYSCONFDIR@/dhcpcd.exit-hook +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples Aq roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd +.Sh SECURITY CONSIDERATIONS +Little validation of DHCP options is done in dhcpcd itself. +Instead, it is up to the hooks to handle any validation needed. +To this end, some helper functions are provided, such as valid_domainname as +used by the +.Pa 20-resolv.conf +hook to ensure that the hostname is not set to an invalid value. +valid_path is also provided, but is currently unused by a stock hook script.
diff --git a/dhcpcd/dhcpcd-run-hooks.in b/dhcpcd/dhcpcd-run-hooks.in new file mode 100644 index 0000000..2125f9a --- /dev/null +++ b/dhcpcd/dhcpcd-run-hooks.in
@@ -0,0 +1,237 @@ +#!/bin/sh +# dhcpcd client configuration script + +# Handy variables and functions for our hooks to use +if [ "$reason" = ROUTERADVERT ]; then + ifsuffix=":ra" +else + ifsuffix= +fi +ifname="$interface$ifsuffix" + +from=from +signature_base="# Generated by dhcpcd" +signature="$signature_base $from $ifname" +signature_base_end="# End of dhcpcd" +signature_end="$signature_base_end $from $ifname" +state_dir=/var/run/dhcpcd + +# Ensure that all arguments are unique +uniqify() +{ + local result= i= + for i do + case " $result " in + *" $i "*);; + *) result="$result $i";; + esac + done + echo "${result# *}" +} + +# List interface config files in a directory. +# If dhcpcd is running as a single instance then it will have a list of +# interfaces in the preferred order. +# Otherwise we just use what we have. +list_interfaces() +{ + local i= x= ifaces= + for i in $interface_order; do + [ -e "$1/$i" ] && ifaces="$ifaces${ifaces:+ }$i" + done + for x in "$1"/*; do + [ -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 "$ifaces" +} + +# We normally use sed to extract values using a key from a list of files +# but sed may not always be available at the time. +key_get_value() +{ + local key="$1" value= x= line= + + shift + if type sed >/dev/null 2>&1; then + sed -n "s/^$key//p" $@ + else + for x do + while read line; do + case "$line" in + "$key"*) echo "${line##$key}";; + esac + done < "$x" + done + fi +} + +# We normally use sed to remove markers from a configuration file +# but sed may not always be available at the time. +remove_markers() +{ + local m1="$1" m2="$2" x= line= in_marker=0 + + shift; shift + if type sed >/dev/null 2>&1; then + sed "/^$m1/,/^$m2/d" $@ + else + for x do + while read line; do + case "$line" in + "$m1"*) in_marker=1;; + "$m2"*) in_marker=0;; + *) [ $in_marker = 0 ] && echo "$line";; + esac + done < "$x" + done + fi +} + +# Compare two files. +# If different, replace first with second otherwise remove second. +change_file() +{ + 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 + cat "$2" > "$1" + rm -f "$2" + return 0 +} + +# Save a config file +save_conf() +{ + if [ -f "$1" ]; then + rm -f "$1-pre.$interface" + cat "$1" > "$1-pre.$interface" + fi +} + +# Restore a config file +restore_conf() +{ + [ -f "$1-pre.$interface" ] || return 1 + cat "$1-pre.$interface" > "$1" + rm -f "$1-pre.$interface" +} + +# Write a syslog entry +syslog() +{ + local lvl="$1" + + [ -n "$lvl" ] && shift + if [ -n "$*" ]; then + if type logger >/dev/null 2>&1; then + logger -t dhcpcd -p daemon."$lvl" -is "$interface: $*" + fi + fi +} + +# Check for a valid domain name as per RFC1123 with the exception of +# allowing - and _ as they seem to be widely used. +valid_domainname() +{ + local name="$1" label + + [ -z "$name" -o ${#name} -gt 255 ] && return 1 + + while [ -n "$name" ]; do + label="${name%%.*}" + [ -z "$label" -o ${#label} -gt 63 ] && return 1 + case "$label" in + -*|_*|*-|*_) return 1;; + *[![:alnum:]-_]*) return 1;; + esac + [ "$name" = "${name#*.}" ] && break + name="${name#*.}" + done + return 0 +} + +valid_domainname_list() +{ + local name + + for name do + valid_domainname "$name" || return $? + done + return 0 +} + +# Check for a valid path +valid_path() +{ + case "$@" in + *[![:alnum:]#%+-_:\.,@~\\/\[\]=\ ]*) return 1;; + esac + return 0 +} + +# 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. +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + @SYSCONFDIR@/dhcpcd.enter-hook \ + @HOOKDIR@/* \ + @SYSCONFDIR@/dhcpcd.exit-hook +do + for skip in $skip_hooks; do + case "$hook" in + */"$skip") continue 2;; + */[0-9][0-9]"-$skip") continue 2;; + */[0-9][0-9]"-$skip.sh") continue 2;; + esac + done + if [ -f "$hook" ]; then + . "$hook" + fi +done
diff --git a/dhcpcd/dhcpcd.8 b/dhcpcd/dhcpcd.8 new file mode 100644 index 0000000..bfeb614 --- /dev/null +++ b/dhcpcd/dhcpcd.8
@@ -0,0 +1,601 @@ +.\" Copyright (c) 2006-2012 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd June 7, 2012 +.Dt DHCPCD 8 +.Os +.Sh NAME +.Nm dhcpcd +.Nd an RFC 2131 compliant DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl ABbDdEGgHJKkLnpqTVw +.Op Fl C , Fl Fl nohook Ar hook +.Op Fl c , Fl Fl script Ar script +.Op Fl e , Fl Fl env Ar value +.Op Fl F , Fl Fl fqdn Ar FQDN +.Op Fl f , Fl Fl config Ar file +.Op Fl h , Fl Fl hostname Ar hostname +.Op Fl I , Fl Fl clientid Ar clientid +.Op Fl i , Fl Fl vendorclassid Ar vendorclassid +.Op Fl l , Fl Fl leasetime Ar seconds +.Op Fl m , Fl Fl metric Ar metric +.Op Fl O , Fl Fl nooption Ar option +.Op Fl o , Fl Fl option Ar option +.Op Fl Q , Fl Fl require Ar option +.Op Fl r , Fl Fl request Ar address +.Op Fl S , Fl Fl static Ar value +.Op Fl s , Fl Fl inform Ar address Ns Op Ar /cidr +.Op Fl t , Fl Fl timeout Ar seconds +.Op Fl u , Fl Fl userclass Ar class +.Op Fl v , Fl Fl vendor Ar code , Ar value +.Op Fl W , Fl Fl whitelist Ar address Ns Op Ar /cidr +.Op Fl y , Fl Fl reboot Ar seconds +.Op Fl X , Fl Fl blacklist Ar address Ns Op Ar /cidr +.Op Fl Z , Fl Fl denyinterfaces Ar pattern +.Op Fl z , Fl Fl allowinterfaces Ar pattern +.Op interface +.Op ... +.Nm +.Fl k , Fl Fl release +.Op interface +.Nm +.Fl U, Fl Fl dumplease +.Ar interface +.Nm +.Fl Fl version +.Nm +.Fl x , Fl Fl exit +.Op interface +.Sh DESCRIPTION +.Nm +is an implementation of the DHCP client specified in +.Li RFC 2131 . +.Nm +gets the host information +.Po +IP address, routes, etc +.Pc +from a DHCP server and configures the network +.Ar interface +of the +machine on which it is running. +.Nm +then runs the configuration script which writes DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currently blank, (null) or localhost, or +.Va force_hostname +is YES or TRUE or 1 then +.Nm +sets the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +It will then attempt to renew its lease and reconfigure if the new lease +changes. +.Pp +.Nm +is also an implementation of the BOOTP client specified in +.Li RFC 951 . +.Pp +.Nm +is also an implementation of an IPv6 Router Solicitor as specified in +.Li RFC 4861 +and +.Li RFC 6106 . +.Nm +can optionally handle address and route management itself, +and will do so by default if Router Solicitation is disabled in the kernel. +If +.Nm +is managing routes, +.Nm +sends Neighbor Solicitions to each advertising router periodically and will +expire the ones that do not respond. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it probes for a valid IPv4LL address +.Po +aka ZeroConf, aka APIPA +.Pc . +Once obtained it restarts the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the +.Fl L , Fl Fl noipv4ll +option. +.Ss Multiple interfaces +If a list of interfaces are given on the command line, then +.Nm +only works with those interfaces, otherwise +.Nm +discovers available Ethernet interfaces. +If any interface reports a working carrier then +.Nm +will try and obtain a lease before forking to the background, +otherwise it will fork right away. +This behaviour can be modified with the +.Fl b , Fl Fl background +and +.Fl w , Fl Fl waitip +options. +.Pp +If a single interface is given then +.Nm +only works for that interface and runs as a separate instance. +The +.Fl w , Fl Fl waitip +option is enabled in this instance to maintain compatibility with older +versions. +.Pp +Interfaces are preferred by carrier, DHCP lease/IPv4LL and then lowest metric. +For systems that support route metrics, each route will be tagged with the +metric, otherwise +.Nm +changes the routes to use the interface with the same route and the lowest +metric. +See options below for controlling which interfaces we allow and deny through +the use of patterns. +.Ss Hooking into DHCP events +.Nm +runs +.Pa /libexec/dhcpcd-run-hooks , +or the script specified by the +.Fl c , Fl Fl script +option. +This script runs each script found in +.Pa /libexec/dhcpcd-hooks +in a lexical order. +The default installation supplies the scripts +.Pa 01-test , +.Pa 10-mtu , +.Pa 20-resolv.conf +and +.Pa 30-hostname . +You can disable each script by using the +.Fl C , Fl Fl nohook +option. +See +.Xr dhcpcd-run-hooks 8 +for details on how these scripts work. +.Nm +currently ignores the exit code of the script. +.Ss Fine tuning +You can fine-tune the behaviour of +.Nm +with the following options: +.Bl -tag -width indent +.It Fl b , Fl Fl background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.It Fl c , Fl Fl script Ar script +Use this +.Ar script +instead of the default +.Pa /libexec/dhcpcd-run-hooks . +.It Fl D , Fl Fl duid +Generate an +.Li RFC 4361 +compliant clientid. +This requires persistent storage and not all DHCP servers work with it so it +is not enabled by default. +.Nm +generates the DUID and stores it in +.Pa /etc/dhcpcd.duid . +This file should not be copied to other hosts. +.It Fl d , Fl Fl debug +Echo debug messages to the stderr and syslog. +.It Fl E , Fl Fl lastlease +If +.Nm +cannot obtain a lease, then try to use the last lease acquired for the +interface. +If the +.Fl p, Fl Fl persistent +option is not given then the lease is used if it hasn't expired. +.It Fl e , Fl Fl env Ar value +Push +.Ar value +to the environment for use in +.Xr dhcpcd-run-hooks 8 . +For example, you can force the hostname hook to always set the hostname with +.Fl e +.Va force_hostname=YES . +.It Fl g , Fl Fl reconfigure +.Nm +will re-apply IP address, routing and run +.Xr dhcpcd-run-hooks 8 +for each interface. +This is useful so that a 3rd party such as PPP or VPN can change the routing +table and / or DNS, etc and then instruct +.Nm +to put things back afterwards. +.Nm +does not read a new configuration when this happens - you should rebind if you +need that functionality. +.It Fl F , Fl Fl fqdn Ar fqdn +Requests that the DHCP server updates DNS using FQDN instead of just a +hostname. +Valid values for +.Ar fqdn +are disable, none, ptr and both. +.Nm +itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Fl f , Fl Fl config Ar file +Specify a config to load instead of +.Pa /etc/dhcpcd.conf . +.Nm +always processes the config file before any command line options. +.It Fl h , Fl Fl hostname Ar hostname +Sends +.Ar hostname +to the DHCP server so it can be registered in DNS. +If +.Ar hostname +is an empty string then the current system hostname is sent. +If +.Ar hostname +is a FQDN (ie, contains a .) then it will be encoded as such. +.It Fl I , Fl Fl clientid Ar clientid +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Fl i , Fl Fl vendorclassid Ar vendorclassid +Override the +.Ar vendorclassid +field sent. +The default is +dhcpcd-<version>:<os>:<machine>:<platform>. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 +If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. +.It Fl k , Fl Fl release +This causes an existing +.Nm +process running on the +.Ar interface +to release its lease, de-configure the +.Ar interface +and then exit. +.Nm +then waits until this process has exited. +.It Fl l , Fl Fl leasetime Ar seconds +Request a specific lease time in +.Ar seconds . +By default +.Nm +does not request any lease time and leaves it in the hands of the +DHCP server. +.It Fl m , Fl Fl metric Ar metric +Metrics are used to prefer an interface over another one, lowest wins. +.Nm +will supply a default metic of 200 + +.Xr if_nametoindex 3 . +An extra 100 will be added for wireless interfaces. +.It Fl n , Fl Fl rebind +Notifies +.Nm +to reload its configuration and rebind its interfaces. +If +.Nm +is not running, then it starts up as normal. +.It Fl o , Fl Fl option Ar option +Request the DHCP +.Ar option +variable for use in +.Pa /libexec/dhcpcd-run-hooks . +.It Fl p , Fl Fl persistent +.Nm +normally de-configures the +.Ar interface +and configuration when it exits. +Sometimes, this isn't desirable if, for example, you have root mounted over +NFS. +You can use this option to stop this from happening. +.It Fl r , Fl Fl request Op Ar address +Request the +.Ar address +in the DHCP DISCOVER message. +There is no guarantee this is the address the DHCP server will actually give. +If no +.Ar address +is given then the first address currently assigned to the +.Ar interface +is used. +.It Fl s , Fl Fl inform Op Ar address Ns Op Ar /cidr +Behaves like +.Fl r , Fl Fl request +as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST. +This does not get a lease as such, just notifies the DHCP server of the +.Ar address +in use. +You should also include the optional +.Ar cidr +network number in case the address is not already configured on the interface. +.Nm +remains running and pretends it has an infinite lease. +.Nm +will not de-configure the interface when it exits. +If +.Nm +fails to contact a DHCP server then it returns a failure instead of falling +back on IPv4LL. +.It Fl t , Fl Fl timeout Ar seconds +Timeout after +.Ar seconds , +instead of the default 30. +A setting of 0 +.Ar seconds +causes +.Nm +to wait forever to get a lease. +.It Fl u , Fl Fl userclass Ar class +Tags the DHCP message with the userclass +.Ar class . +DHCP servers use this to give members of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , Fl Fl vendor Ar code , Ns Ar value +Add an encapsulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +To add a raw vendor string, omit +.Ar code +but keep the comma. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 dhcpcd \-v 01,192.168.0.2 eth0 +Set the vendor option 02 with a hex code. +.D1 dhcpcd \-v 02,01:02:03:04:05 eth0 +Set the vendor option 03 with an IP address as a string. +.D1 dhcpcd \-v 03,\e"192.168.0.2\e" eth0 +Set un-encapsulated vendor option to hello world. +.D1 dhcpcd \-v ,"hello world" eth0 +.It Fl Fl version +Display both program version and copyright information. +.Nm +then exits before doing any configuration. +.It Fl w , Fl Fl waitip +Wait for an address to be assigned before forking to the background. +.It Fl x , Fl Fl exit +This will signal an existing +.Nm +process running on the +.Ar interface +to de-configure the +.Ar interface +and exit. +.Nm +then waits until this process has exited. +.It Fl y , Fl Fl reboot Ar seconds +Allow +.Ar reboot +seconds before moving to the discover phase if we have an old lease to use. +The default is 5 seconds. +A setting of 0 seconds causes +.Nm +to skip the reboot phase and go straight into discover. +.El +.Ss Restricting behaviour +.Nm +will try to do as much as it can by default. +However, there are sometimes situations where you don't want the things to be +configured exactly how the the DHCP server wants. +Here are some options that deal with turning these bits off. +.Bl -tag -width indent +.It Fl A , Fl Fl noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl B , Fl Fl nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl C , Fl Fl nohook Ar script +Don't run this hook script. +Matches full name, or prefixed with 2 numbers optionally ending with +.Pa .sh . +.Pp +So to stop +.Nm +from touching your DNS or MTU settings you would do:- +.D1 dhcpcd -C resolv.conf -C mtu eth0 +.It Fl G , Fl Fl nogateway +Don't set any default routes. +.It Fl H , Fl Fl xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. +.It Fl J , Fl Fl broadcast +Instructs the DHCP server to broadcast replies back to the client. +Normally this is only set for non Ethernet interfaces, +such as FireWire and InfiniBand. +In most instances, +.Nm +will set this automatically. +.It Fl K , Fl Fl nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. +.It Fl L , Fl Fl noipv4ll +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). +.It Fl O , Fl Fl nooption Ar option +Don't request the specified option. +If no option given, then don't request any options other than those to +configure the interface and routing. +.It Fl Q , Fl Fl require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +To enforce that +.Nm +only responds to DHCP servers and not BOOTP servers, you can +.Fl Q +.Ar dhcp_message_type . +.It Fl q , Fl Fl quiet +Quiet +.Nm +on the command line, only warnings and errors will be displayed. +The messages are still logged though. +.It Fl S, Fl Fl static Ar value +Configures a static +.Ar value . +If you set +.Ic ip_address +then +.Nm +will not attempt to obtain a lease and just use the value for the address with +an infinite lease time. +.Pp +Here is an example which configures a static address, routes and dns. +.D1 dhcpcd -S ip_address=192.168.0.10/24 \e +.D1 -S routers=192.168.0.1 \e +.D1 -S domain_name_servers=192.168.0.1 \e +.D1 eth0 +.It Fl T, Fl Fl test +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, Fl Fl dumplease Ar interface +Dumps the last lease for the +.Ar interface +to stdout. +.Ar interface +could also be a path to a DHCP wire formatted file. +.It Fl V, Fl Fl variables +Display a list of option codes and the associated variable for use in +.Xr dhcpcd-run-hooks 8 . +Variables are prefixed with new_ and old_ unless the option number is -. +Variables without an option are part of the DHCP message and cannot be +directly requested. +.It Fl W, Fl Fl whitelist Ar address Ns Op /cidr +Only accept packets from +.Ar address Ns Op /cidr . +.Fl X, Fl Fl blacklist +is ignored if +.Fl W, Fl Fl whitelist +is set. +.It Fl X, Fl Fl blacklist Ar address Ns Op Ar /cidr +Ignore all packets from +.Ar address Ns Op Ar /cidr . +.It Fl Z , Fl Fl denyinterfaces Ar pattern +When discovering interfaces, the interface name must not match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +.It Fl z , Fl Fl allowinterfaces Ar pattern +When discovering interfaces, the interface name must match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +If the same interface is matched in +.Fl Z , Fl Fl denyinterfaces +then it is still denied. +.El +.Sh 3RDPARTY LINK MANAGEMENT +Some interfaces require configuration by 3rd parties, such as PPP or VPN. +When an interface configuration in +.Nm +is marked as STATIC or INFORM without an address then +.Nm +will monitor the interface until an address is added or removed from it and +act accordingly. +For point to point interfaces (like PPP), a default route to its +destination is automatically added to the configuration. +If the point to point interface is configured for INFORM, then +.Nm +unicasts INFORM to the destination, otherwise it defaults to STATIC. +.Sh NOTES +.Nm +requires a Berkley Packet Filter, or BPF device on BSD based systems and a +Linux Socket Filter, or LPF device on Linux based systems. +.Sh FILES +.Bl -ohang +.It Pa /etc/dhcpcd.conf +Configuration file for dhcpcd. +If you always use the same options, put them here. +.It Pa /etc/dhcpcd.duid +Text file that holds the DUID used to identify the host. +.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 , Fl Fl nohook +option described above. +.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 +running on the +.Ar interface . +.El +.Sh SEE ALSO +.Xr fnmatch 3 , +.Xr if_nametoindex 3 , +.Xr dhcpcd.conf 5 , +.Xr resolv.conf 5 , +.Xr dhcpcd-run-hooks 8 , +.Xr resolvconf 8 +.Sh STANDARDS +RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, +RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, RFC 4861, RFC 5969, +RFC 6106. +.Sh AUTHORS +.An Roy Marples Aq roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd
diff --git a/dhcpcd/dhcpcd.8.in b/dhcpcd/dhcpcd.8.in new file mode 100644 index 0000000..fc3c04c --- /dev/null +++ b/dhcpcd/dhcpcd.8.in
@@ -0,0 +1,586 @@ +.\" Copyright (c) 2006-2012 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd March 19, 2012 +.Dt DHCPCD 8 +.Os +.Sh NAME +.Nm dhcpcd +.Nd an RFC 2131 compliant DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl ABbDdEGgHJKkLnpqTVw +.Op Fl C , Fl Fl nohook Ar hook +.Op Fl c , Fl Fl script Ar script +.Op Fl e , Fl Fl env Ar value +.Op Fl F , Fl Fl fqdn Ar FQDN +.Op Fl f , Fl Fl config Ar file +.Op Fl h , Fl Fl hostname Ar hostname +.Op Fl I , Fl Fl clientid Ar clientid +.Op Fl i , Fl Fl vendorclassid Ar vendorclassid +.Op Fl l , Fl Fl leasetime Ar seconds +.Op Fl m , Fl Fl metric Ar metric +.Op Fl O , Fl Fl nooption Ar option +.Op Fl o , Fl Fl option Ar option +.Op Fl Q , Fl Fl require Ar option +.Op Fl r , Fl Fl request Ar address +.Op Fl S , Fl Fl static Ar value +.Op Fl s , Fl Fl inform Ar address Ns Op Ar /cidr +.Op Fl t , Fl Fl timeout Ar seconds +.Op Fl u , Fl Fl userclass Ar class +.Op Fl v , Fl Fl vendor Ar code , Ar value +.Op Fl W , Fl Fl whitelist Ar address Ns Op Ar /cidr +.Op Fl y , Fl Fl reboot Ar seconds +.Op Fl X , Fl Fl blacklist Ar address Ns Op Ar /cidr +.Op Fl Z , Fl Fl denyinterfaces Ar pattern +.Op Fl z , Fl Fl allowinterfaces Ar pattern +.Op interface +.Op ... +.Nm +.Fl k , Fl Fl release +.Op interface +.Nm +.Fl U, Fl Fl dumplease +.Ar interface +.Nm +.Fl Fl version +.Nm +.Fl x , Fl Fl exit +.Op interface +.Sh DESCRIPTION +.Nm +is an implementation of the DHCP client specified in +.Li RFC 2131 . +.Nm +gets the host information +.Po +IP address, routes, etc +.Pc +from a DHCP server and configures the network +.Ar interface +of the +machine on which it is running. +.Nm +then runs the configuration script which writes DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currently blank, (null) or localhost, or +.Va force_hostname +is YES or TRUE or 1 then +.Nm +sets the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +It will then attempt to renew its lease and reconfigure if the new lease +changes. +.Pp +.Nm +is also an implementation of the BOOTP client specified in +.Li RFC 951 . +.Pp +.Nm +is also an implementation of an IPv6 Router Solicitor as specified in +.Li RFC 6106 +with regard to the RDNSS and DNSSL options. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it probes for a valid IPv4LL address +.Po +aka ZeroConf, aka APIPA +.Pc . +Once obtained it restarts the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the +.Fl L , Fl Fl noipv4ll +option. +.Ss Multiple interfaces +If a list of interfaces are given on the command line, then +.Nm +only works with those interfaces, otherwise +.Nm +discovers available Ethernet interfaces. +If any interface reports a working carrier then +.Nm +will try and obtain a lease before forking to the background, +otherwise it will fork right away. +This behaviour can be modified with the +.Fl b , Fl Fl background +and +.Fl w , Fl Fl waitip +options. +.Pp +If a single interface is given then +.Nm +only works for that interface and runs as a separate instance. +The +.Fl w , Fl Fl waitip +option is enabled in this instance to maintain compatibility with older +versions. +.Pp +Interfaces are preferred by carrier, DHCP lease/IPv4LL and then lowest metric. +For systems that support route metrics, each route will be tagged with the +metric, otherwise +.Nm +changes the routes to use the interface with the same route and the lowest +metric. +See options below for controlling which interfaces we allow and deny through +the use of patterns. +.Ss Hooking into DHCP events +.Nm +runs +.Pa @SCRIPT@ , +or the script specified by the +.Fl c , Fl Fl script +option. +This script runs each script found in +.Pa @HOOKDIR@ +in a lexical order. +The default installation supplies the scripts +.Pa 01-test , +.Pa 10-mtu , +.Pa 20-resolv.conf +and +.Pa 30-hostname . +You can disable each script by using the +.Fl C , Fl Fl nohook +option. +See +.Xr dhcpcd-run-hooks 8 +for details on how these scripts work. +.Nm +currently ignores the exit code of the script. +.Ss Fine tuning +You can fine-tune the behaviour of +.Nm +with the following options: +.Bl -tag -width indent +.It Fl b , Fl Fl background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.It Fl c , Fl Fl script Ar script +Use this +.Ar script +instead of the default +.Pa @SCRIPT@ . +.It Fl D , Fl Fl duid +Generate an +.Li RFC 4361 +compliant clientid. +This requires persistent storage and not all DHCP servers work with it so it +is not enabled by default. +.Nm +generates the DUID and stores it in +.Pa @SYSCONFDIR@/dhcpcd.duid . +This file should not be copied to other hosts. +.It Fl d , Fl Fl debug +Echo debug messages to the stderr and syslog. +.It Fl E , Fl Fl lastlease +If +.Nm +cannot obtain a lease, then try to use the last lease acquired for the +interface. +If the +.Fl p, Fl Fl persistent +option is not given then the lease is used if it hasn't expired. +.It Fl e , Fl Fl env Ar value +Push +.Ar value +to the environment for use in +.Xr dhcpcd-run-hooks 8 . +For example, you can force the hostname hook to always set the hostname with +.Fl e +.Va force_hostname=YES . +.It Fl g , Fl Fl reconfigure +.Nm +will re-apply IP address, routing and run +.Xr dhcpcd-run-hooks 8 +for each interface. +This is useful so that a 3rd party such as PPP or VPN can change the routing +table and / or DNS, etc and then instruct +.Nm +to put things back afterwards. +.Nm +does not read a new configuration when this happens - you should rebind if you +need that functionality. +.It Fl F , Fl Fl fqdn Ar fqdn +Requests that the DHCP server updates DNS using FQDN instead of just a +hostname. +Valid values for +.Ar fqdn +are disable, none, ptr and both. +.Nm +itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Fl f , Fl Fl config Ar file +Specify a config to load instead of +.Pa @SYSCONFDIR@/dhcpcd.conf . +.Nm +always processes the config file before any command line options. +.It Fl h , Fl Fl hostname Ar hostname +Sends +.Ar hostname +to the DHCP server so it can be registered in DNS. +If +.Ar hostname +is an empty string then the current system hostname is sent. +If +.Ar hostname +is a FQDN (ie, contains a .) then it will be encoded as such. +.It Fl I , Fl Fl clientid Ar clientid +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Fl i , Fl Fl vendorclassid Ar vendorclassid +Override the +.Ar vendorclassid +field sent. +The default is +dhcpcd <version>. +If not set then none is sent. +.It Fl k , Fl Fl release +This causes an existing +.Nm +process running on the +.Ar interface +to release its lease, de-configure the +.Ar interface +and then exit. +.Nm +then waits until this process has exited. +.It Fl l , Fl Fl leasetime Ar seconds +Request a specific lease time in +.Ar seconds . +By default +.Nm +does not request any lease time and leaves it in the hands of the +DHCP server. +.It Fl m , Fl Fl metric Ar metric +Metrics are used to prefer an interface over another one, lowest wins. +.Nm +will supply a default metic of 200 + +.Xr if_nametoindex 3 . +An extra 100 will be added for wireless interfaces. +.It Fl n , Fl Fl rebind +Notifies +.Nm +to reload its configuration and rebind its interfaces. +If +.Nm +is not running, then it starts up as normal. +.It Fl o , Fl Fl option Ar option +Request the DHCP +.Ar option +variable for use in +.Pa @SCRIPT@ . +.It Fl p , Fl Fl persistent +.Nm +normally de-configures the +.Ar interface +and configuration when it exits. +Sometimes, this isn't desirable if, for example, you have root mounted over +NFS. +You can use this option to stop this from happening. +.It Fl r , Fl Fl request Op Ar address +Request the +.Ar address +in the DHCP DISCOVER message. +There is no guarantee this is the address the DHCP server will actually give. +If no +.Ar address +is given then the first address currently assigned to the +.Ar interface +is used. +.It Fl s , Fl Fl inform Op Ar address Ns Op Ar /cidr +Behaves like +.Fl r , Fl Fl request +as above, but sends a DHCP INFORM instead of DISCOVER/REQUEST. +This does not get a lease as such, just notifies the DHCP server of the +.Ar address +in use. +You should also include the optional +.Ar cidr +network number in case the address is not already configured on the interface. +.Nm +remains running and pretends it has an infinite lease. +.Nm +will not de-configure the interface when it exits. +If +.Nm +fails to contact a DHCP server then it returns a failure instead of falling +back on IPv4LL. +.It Fl t , Fl Fl timeout Ar seconds +Timeout after +.Ar seconds , +instead of the default 30. +A setting of 0 +.Ar seconds +causes +.Nm +to wait forever to get a lease. +.It Fl u , Fl Fl userclass Ar class +Tags the DHCP message with the userclass +.Ar class . +DHCP servers use this to give members of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , Fl Fl vendor Ar code , Ns Ar value +Add an encapsulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +To add a raw vendor string, omit +.Ar code +but keep the comma. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 dhcpcd \-v 01,192.168.0.2 eth0 +Set the vendor option 02 with a hex code. +.D1 dhcpcd \-v 02,01:02:03:04:05 eth0 +Set the vendor option 03 with an IP address as a string. +.D1 dhcpcd \-v 03,\e"192.168.0.2\e" eth0 +Set un-encapsulated vendor option to hello world. +.D1 dhcpcd \-v ,"hello world" eth0 +.It Fl Fl version +Display both program version and copyright information. +.Nm +then exits before doing any configuration. +.It Fl w , Fl Fl waitip +Wait for an address to be assigned before forking to the background. +.It Fl x , Fl Fl exit +This will signal an existing +.Nm +process running on the +.Ar interface +to de-configure the +.Ar interface +and exit. +.Nm +then waits until this process has exited. +.It Fl y , Fl Fl reboot Ar seconds +Allow +.Ar reboot +seconds before moving to the discover phase if we have an old lease to use. +The default is 5 seconds. +A setting of 0 seconds causes +.Nm +to skip the reboot phase and go straight into discover. +.El +.Ss Restricting behaviour +.Nm +will try to do as much as it can by default. +However, there are sometimes situations where you don't want the things to be +configured exactly how the the DHCP server wants. +Here are some options that deal with turning these bits off. +.Bl -tag -width indent +.It Fl A , Fl Fl noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl B , Fl Fl nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl C , Fl Fl nohook Ar script +Don't run this hook script. +Matches full name, or prefixed with 2 numbers optionally ending with +.Pa .sh . +.Pp +So to stop +.Nm +from touching your DNS or MTU settings you would do:- +.D1 dhcpcd -C resolv.conf -C mtu eth0 +.It Fl G , Fl Fl nogateway +Don't set any default routes. +.It Fl H , Fl Fl xidhwaddr +Use the last four bytes of the hardware address as the DHCP xid instead +of a randomly generated number. +.It Fl J , Fl Fl broadcast +Instructs the DHCP server to broadcast replies back to the client. +Normally this is only set for non Ethernet interfaces, +such as FireWire and InfiniBand. +In most instances, +.Nm +will set this automatically. +.It Fl K , Fl Fl nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. +.It Fl L , Fl Fl noipv4ll +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). +.It Fl O , Fl Fl nooption Ar option +Don't request the specified option. +If no option given, then don't request any options other than those to +configure the interface and routing. +.It Fl Q , Fl Fl require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +To enforce that +.Nm +only responds to DHCP servers and not BOOTP servers, you can +.Fl Q +.Ar dhcp_message_type . +.It Fl q , Fl Fl quiet +Quiet +.Nm +on the command line, only warnings and errors will be displayed. +The messages are still logged though. +.It Fl S, Fl Fl static Ar value +Configures a static +.Ar value . +If you set +.Ic ip_address +then +.Nm +will not attempt to obtain a lease and just use the value for the address with +an infinite lease time. +.Pp +Here is an example which configures a static address, routes and dns. +.D1 dhcpcd -S ip_address=192.168.0.10/24 \e +.D1 -S routers=192.168.0.1 \e +.D1 -S domain_name_servers=192.168.0.1 \e +.D1 eth0 +.It Fl T, Fl Fl test +On receipt of DHCP messages just call +.Pa @SCRIPT@ +with the reason of TEST which echos the DHCP variables found in the message +to the console. +The interface configuration isn't touched and neither are any configuration +files. +To test INFORM the interface needs to be configured with the desired address +before starting +.Nm . +.It Fl U, Fl Fl dumplease Ar interface +Dumps the last lease for the +.Ar interface +to stdout. +.Ar interface +could also be a path to a DHCP wire formatted file. +.It Fl V, Fl Fl variables +Display a list of option codes and the associated variable for use in +.Xr dhcpcd-run-hooks 8 . +Variables are prefixed with new_ and old_ unless the option number is -. +Variables without an option are part of the DHCP message and cannot be +directly requested. +.It Fl W, Fl Fl whitelist Ar address Ns Op /cidr +Only accept packets from +.Ar address Ns Op /cidr . +.Fl X, Fl Fl blacklist +is ignored if +.Fl W, Fl Fl whitelist +is set. +.It Fl X, Fl Fl blacklist Ar address Ns Op Ar /cidr +Ignore all packets from +.Ar address Ns Op Ar /cidr . +.It Fl Z , Fl Fl denyinterfaces Ar pattern +When discovering interfaces, the interface name must not match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +.It Fl z , Fl Fl allowinterfaces Ar pattern +When discovering interfaces, the interface name must match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +If the same interface is matched in +.Fl Z , Fl Fl denyinterfaces +then it is still denied. +.El +.Sh 3RDPARTY LINK MANAGEMENT +Some interfaces require configuration by 3rd parties, such as PPP or VPN. +When an interface configuration in +.Nm +is marked as STATIC or INFORM without an address then +.Nm +will monitor the interface until an address is added or removed from it and +act accordingly. +For point to point interfaces (like PPP), a default route to its +destination is automatically added to the configuration. +If the point to point interface is configured for INFORM, then +.Nm +unicasts INFORM to the destination, otherwise it defaults to STATIC. +.Sh NOTES +.Nm +requires a Berkley Packet Filter, or BPF device on BSD based systems and a +Linux Socket Filter, or LPF device on Linux based systems. +.Sh FILES +.Bl -ohang +.It Pa @SYSCONFDIR@/dhcpcd.conf +Configuration file for dhcpcd. +If you always use the same options, put them here. +.It Pa @SYSCONFDIR@/dhcpcd.duid +Text file that holds the DUID used to identify the host. +.It Pa @SCRIPT@ +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 +.Fl C , Fl Fl nohook +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 +running on the +.Ar interface . +.El +.Sh SEE ALSO +.Xr fnmatch 3 , +.Xr if_nametoindex 3 , +.Xr dhcpcd.conf 5 , +.Xr resolv.conf 5 , +.Xr dhcpcd-run-hooks 8 , +.Xr resolvconf 8 +.Sh STANDARDS +RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, +RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, RFC 5969, RFC 6106. +.Sh AUTHORS +.An Roy Marples Aq roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd
diff --git a/dhcpcd/dhcpcd.c b/dhcpcd/dhcpcd.c new file mode 100644 index 0000000..6eae970 --- /dev/null +++ b/dhcpcd/dhcpcd.c
@@ -0,0 +1,2206 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; + +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> + +#include <arpa/inet.h> +#include <net/route.h> + +#ifdef __linux__ +# include <asm/types.h> /* for systems with broken headers */ +# include <linux/rtnetlink.h> +#endif + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <time.h> + +#include "arp.h" +#include "bind.h" +#include "config.h" +#include "common.h" +#include "configure.h" +#include "control.h" +#include "dhcpcd.h" +#include "duid.h" +#include "eloop.h" +#include "if-options.h" +#include "if-pref.h" +#include "ipv4ll.h" +#include "ipv6rs.h" +#include "net.h" +#include "platform.h" +#include "signals.h" + +#ifdef ANDROID +#include <sys/capability.h> +#include <sys/prctl.h> +#include <cutils/properties.h> +#include <private/android_filesystem_config.h> +#endif + +/* 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 + +unsigned long long options = 0; +int pidfd = -1; +struct interface *ifaces = NULL; +int ifac = 0; +char **ifav = NULL; +int ifdc = 0; +char **ifdv = NULL; +/* If set, avoid routes after a DHCP success */ +int avoid_routes = 0; + +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, ipv6rsfd = -1; + +struct dhcp_op { + uint8_t value; + const char *name; +}; + +static const struct dhcp_op dhcp_ops[] = { + { DHCP_DISCOVER, "DISCOVER" }, + { DHCP_OFFER, "OFFER" }, + { DHCP_REQUEST, "REQUEST" }, + { DHCP_DECLINE, "DECLINE" }, + { DHCP_ACK, "ACK" }, + { DHCP_NAK, "NAK" }, + { DHCP_RELEASE, "RELEASE" }, + { DHCP_INFORM, "INFORM" }, + { 0, NULL } +}; + +static void send_release(struct interface *); + +static const char * +get_dhcp_op(uint8_t type) +{ + const struct dhcp_op *d; + + for (d = dhcp_ops; d->name; d++) + if (d->value == type) + return d->name; + return NULL; +} + +static pid_t +read_pid(void) +{ + FILE *fp; + pid_t pid; + + if ((fp = fopen(pidfile, "r")) == NULL) { + errno = ENOENT; + return 0; + } + if (fscanf(fp, "%d", &pid) != 1) + pid = 0; + fclose(fp); + return pid; +} + +static void +usage(void) +{ + +printf("usage: "PACKAGE"\t[-aABbDdEGgHJKkLnpqTVw]\n" + "\t\t[-C, --nohook hook] [-c, --script script]\n" + "\t\t[-e, --env value] [-F, --fqdn FQDN] [-f, --config file]\n" + "\t\t[-h, --hostname hostname] [-I, --clientid clientid]\n" + "\t\t[-i, --vendorclassid vendorclassid] [-l, --leasetime seconds]\n" + "\t\t[-m, --metric metric] [-O, --nooption option]\n" + "\t\t[-o, --option option] [-Q, --require option]\n" + "\t\t[-r, --request address] [-S, --static value]\n" + "\t\t[-s, --inform address[/cidr]] [-t, --timeout seconds]\n" + "\t\t[-u, --userclass class] [-v, --vendor code, value]\n" + "\t\t[-W, --whitelist address[/cidr]] [-y, --reboot seconds]\n" + "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n" + "\t\t[-z, --allowinterfaces pattern] [interface] [...]\n" + " "PACKAGE"\t-k, --release [interface]\n" + " "PACKAGE"\t-U, --dumplease interface\n" + " "PACKAGE"\t-v, --version\n" + " "PACKAGE"\t-x, --exit [interface]\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 * 2); + syslog(LOG_WARNING, "allowing %d seconds for IPv4LL timeout", timeout); + add_timeout_sec(timeout, handle_exit_timeout, NULL); +} + +void +drop_dhcp(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 (iface->ras) { + ipv6rs_free(iface); + iface->ras = NULL; + run_script_reason(iface, "ROUTERADVERT"); + } + if (strcmp(iface->state->reason, "RELEASE") != 0) + drop_dhcp(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_dhcp(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_dhcp(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_dhcp(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; + } + } + + /* Ensure that the address offered is valid */ + if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && + (dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) && + (dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST)) + { + log_dhcp(LOG_WARNING, "reject invalid address", + 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, partialcsum = 0; + + /* 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, &partialcsum); + if (bytes == 0 || bytes == -1) + break; + if (valid_udp_packet(packet, bytes, &from, partialcsum) == -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_dhcp(iface, "RELEASE"); + } + unlink(iface->leasefile); +} + +void +send_decline(struct interface *iface) +{ + 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 (!(iface->flags & (IFF_POINTOPOINT | IFF_LOOPBACK | IFF_MULTICAST))) + ifo->options &= ~DHCPCD_IPV6RS; + 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 { + ifl = htonl(if_nametoindex(iface->name)); + memcpy(iface->clientid + 2, &ifl, 4); + } + } 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 if (iface->hwlen) + 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); +} + +void +handle_carrier(int action, int flags, 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) { + if (options & DHCPCD_LINK) + handle_interface(1, ifname); + return; + } + if (!(iface->state->options->options & DHCPCD_LINK)) + return; + + if (action) { + carrier = action == 1 ? 1 : 0; + iface->flags = flags; + } else + carrier = carrier_status(iface); + + if (carrier == -1) + syslog(LOG_ERR, "%s: carrier_status: %m", ifname); + else if (carrier == 0 || ~iface->flags & IFF_UP) { + 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); + if (iface->ras) { + ipv6rs_free(iface); + iface->ras = NULL; + run_script_reason(iface, "ROUTERADVERT"); + } + drop_dhcp(iface, "NOCARRIER"); + } + } else if (carrier == 1 && !(~iface->flags & IFF_UP)) { + 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); + } + } +} + +void +start_discover(void *arg) +{ + struct interface *iface = arg; + struct if_options *ifo = iface->state->options; + int timeout = ifo->timeout; + + /* If we're rebooting and we're not daemonised then we need + * to shorten the normal timeout to ensure we try correctly + * for a fallback or IPv4LL address. */ + if (iface->state->state == DHS_REBOOT && + !(options & DHCPCD_DAEMONISED)) + { + timeout -= ifo->reboot; + if (timeout <= 0) + timeout = 2; + } + + iface->state->state = DHS_DISCOVER; + iface->state->xid = dhcp_xid(iface); + delete_timeout(NULL, iface); + if (ifo->fallback) + add_timeout_sec(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(timeout, start_ipv4ll, iface); + } + if (ifo->options & DHCPCD_REQUEST) + syslog(LOG_INFO, "%s: broadcasting for a lease (requesting %s)", + iface->name, inet_ntoa(ifo->req_addr)); + else + syslog(LOG_INFO, "%s: broadcasting for a lease", iface->name); + send_discover(iface); +} + +void +start_request(void *arg) +{ + struct interface *iface = arg; + + iface->state->state = DHS_REQUEST; + send_request(iface); +} + +void +start_renew(void *arg) +{ + 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 { + 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(0, 0, 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 (options & DHCPCD_IPV6RS && ifo->options & DHCPCD_IPV6RS) { + if (check_ipv6(iface->name) == 1) + ipv6rs_start(iface); + else + ifo->options &= ~DHCPCD_IPV6RS; + } + + 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_dhcp(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 < + (time_t)(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; + } + } + } + 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); +} + +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 (!(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; + } + + /* 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; + 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; + 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); + } +} + +#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_dhcp(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; + + 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; + + if (type == RTM_DELADDR) { + if (ifp->state->new && + ifp->state->new->yiaddr == addr->s_addr) + syslog(LOG_INFO, "%s: removing IP address %s/%d", + ifp->name, inet_ntoa(ifp->state->lease.addr), + inet_ntocidr(ifp->state->lease.net)); + return; + } + + if (type != RTM_NEWADDR) + return; + + ifo = ifp->state->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) == 0 || + ifo->req_addr.s_addr != INADDR_ANY) + return; + + 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); + } + 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); + } +} + +/* 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_dhcp(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; + + 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; + } + if (ifn) { + if (action) + if_reboot(ifn, argc, argv); + else if (ifn->state->new) + configure(ifn); + free_interface(ifp); + } else { + ifp->next = NULL; + init_state(ifp, argc, argv); + start_interface(ifp); + if (ifl) + ifl->next = ifp; + else + ifaces = ifp; + } + } + + sort_interfaces(); +} + +/* ARGSUSED */ +static void +handle_signal(_unused void *arg) +{ + 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: +#ifdef ANDROID + syslog(LOG_INFO, "received SIGALRM, renewing"); + for (ifp = ifaces; ifp; ifp = ifp->next) { + start_renew(ifp); + } +#else + 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, ifc, ifv, 0); +#endif + 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; + } + + if (options & DHCPCD_TEST) + exit(EXIT_FAILURE); + + /* As drop_dhcp 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; + } + if (ifp == NULL) + break; + if (ifp->carrier != LINK_DOWN && + (do_release || + ifp->state->options->options & DHCPCD_RELEASE)) + send_release(ifp); + stop_interface(ifp); + } + exit(EXIT_FAILURE); +} + +int +handle_args(struct fd_list *fd, int argc, char **argv) +{ + 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++; + if (ifp->ras) + 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++; + if (ifp->ras) + 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; + } + } + + /* 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); + + optind = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + switch (opt) { + case 'g': + do_reconf = 1; + break; + case 'k': + do_release = 1; + break; + case 'n': + do_reboot = 1; + break; + case 'x': + do_exit = 1; + break; + } + } + + /* We need at least one interface */ + if (optind == argc) { + syslog(LOG_ERR, "handle_args: no interface"); + return -1; + } + + 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 (!ifp) + continue; + 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); + } + 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; + } +} + +#ifdef ANDROID +void switchUser(void) +{ + gid_t groups[] = { AID_INET, AID_SHELL }; + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + + setgroups(sizeof(groups)/sizeof(groups[0]), groups); + + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + + setgid(AID_DHCP); + setuid(AID_DHCP); + 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); +} +#endif /* ANDROID */ + +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; + +#ifdef ANDROID + /* Reuse system properties for a p2p interface */ + char p2p_interface[PROPERTY_KEY_MAX]; + switchUser(); +#endif + 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(); + exit(EXIT_SUCCESS); + } else if (strcmp(argv[1], "--version") == 0) { + printf(""PACKAGE" "VERSION"\n%s\n", copyright); + exit(EXIT_SUCCESS); + } + } + + i = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + switch (opt) { + case 'f': + cffile = optarg; + break; + case 'g': + sig = SIGUSR1; + break; + case 'k': + sig = SIGHUP; + break; + case 'n': + sig = SIGALRM; + break; + case 'x': + sig = SIGTERM; + break; + case 'T': + i = 1; + break; + case 'U': + i = 2; + break; + case 'a': + avoid_routes = 1; + break; + 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 &= ~DHCPCD_DAEMONISE; +#endif + + 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 (chdir("/") == -1) + syslog(LOG_ERR, "chdir `/': %m"); + atexit(cleanup); + + 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); + } + + 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"); + } + } + +#ifndef ANDROID + /* android runs us as user "dhcp" */ + if (geteuid()) + syslog(LOG_WARNING, + PACKAGE " will not work correctly unless run as root"); +#endif + if (sig != 0) { +#ifdef ANDROID + char pidpropname[PROPERTY_KEY_MAX]; + char pidpropval[PROPERTY_VALUE_MAX]; + + if (optind != argc - 1) { + syslog(LOG_ERR, "Android requires an interface"); + exit(EXIT_FAILURE); + } + + if (strncmp(argv[optind], "p2p", 3) == 0) { + strncpy(p2p_interface, "p2p", sizeof(p2p_interface)); + } else { + strncpy(p2p_interface, argv[optind], sizeof(p2p_interface)); + } + + if (snprintf(pidpropname, + sizeof(pidpropname), + "dhcp.%s.pid", p2p_interface) >= PROPERTY_KEY_MAX) + exit(EXIT_FAILURE); + property_get(pidpropname, pidpropval, NULL); + if (strlen(pidpropval) == 0) + exit(EXIT_FAILURE); + pid = atoi(pidpropval); +#else + pid = read_pid(); +#endif + if (pid != 0) + syslog(LOG_INFO, "sending signal %d to pid %d", + sig, pid); + if (pid == 0 || kill(pid, sig) != 0) { + if (sig != SIGALRM) + 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 || sig == SIGUSR1) + exit(EXIT_SUCCESS); + /* Spin until it exits */ + 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() == 0) + exit(EXIT_SUCCESS); + } + syslog(LOG_ERR, "pid %d failed to exit", pid); + exit(EXIT_FAILURE); + } + } + + if (!(options & DHCPCD_TEST)) { +#ifdef ANDROID + char pidpropname[PROPERTY_KEY_MAX]; + char pidpropval[PROPERTY_VALUE_MAX]; +#endif +#ifndef ANDROID + if ((pid = read_pid()) > 0 && + kill(pid, 0) == 0) + { + syslog(LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, pidfile); + exit(EXIT_FAILURE); + } + + /* Ensure we have the needed directories */ + if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) + syslog(LOG_ERR, "mkdir `%s': %m", RUNDIR); + if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) + syslog(LOG_ERR, "mkdir `%s': %m", DBDIR); +#endif + + pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); + if (pidfd == -1) + syslog(LOG_ERR, "open `%s': %m", pidfile); + else { + /* Lock the file so that only one instance of dhcpcd + * runs on an interface */ + 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); +#ifdef ANDROID + if (optind != argc - 1) { + syslog(LOG_ERR, "Android requires an interface"); + exit(EXIT_FAILURE); + } + + if (strncmp(argv[optind], "p2p", 3) == 0) { + strncpy(p2p_interface, "p2p", sizeof(p2p_interface)); + } else { + strncpy(p2p_interface, argv[optind], sizeof(p2p_interface)); + } + + if (snprintf(pidpropname, + sizeof(pidpropname), + "dhcp.%s.pid", p2p_interface) >= PROPERTY_KEY_MAX) + exit(EXIT_FAILURE); + if (snprintf(pidpropval, sizeof(pidpropval), "%d", getpid()) >= PROPERTY_VALUE_MAX) + exit(EXIT_FAILURE); + property_set(pidpropname, pidpropval); +#else + writepid(pidfd, getpid()); +#endif + } + } + + 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"); + } + + 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); + } + +#if 0 + if (options & DHCPCD_IPV6RS && disable_rtadv() == -1) { + syslog(LOG_ERR, "ipv6rs: %m"); + options &= ~DHCPCD_IPV6RS; + } +#endif + + if (options & DHCPCD_IPV6RS && !check_ipv6(NULL)) + options &= ~DHCPCD_IPV6RS; + if (options & DHCPCD_IPV6RS) { + ipv6rsfd = ipv6rs_open(); + if (ipv6rsfd == -1) { + syslog(LOG_ERR, "ipv6rs: %m"); + options &= ~DHCPCD_IPV6RS; + } else { + add_event(ipv6rsfd, ipv6rs_handledata, NULL); +// atexit(restore_rtadv); + } + } + + 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 (options & DHCPCD_BACKGROUND) + daemonise(); + + opt = 0; + for (iface = ifaces; iface; iface = iface->next) { + init_state(iface, argc, argv); + if (iface->carrier != LINK_DOWN) + opt = 1; + } + + 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(0, 0, iface->name); + if (iface->carrier != LINK_DOWN) { + opt = 1; + break; + } + } + } + if (options & DHCPCD_MASTER) + i = if_options->timeout; + else + i = ifaces->state->options->timeout; + if (opt == 0 && + options & DHCPCD_LINK && + !(options & DHCPCD_WAITIP)) + { + syslog(LOG_WARNING, "no interfaces have a carrier"); + daemonise(); + } else if (i > 0) { + if (options & DHCPCD_IPV4LL) + options |= DHCPCD_TIMEOUT_IPV4LL; + add_timeout_sec(i, handle_exit_timeout, NULL); + } + } + 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/dhcpcd.conf b/dhcpcd/dhcpcd.conf new file mode 100644 index 0000000..eb625a7 --- /dev/null +++ b/dhcpcd/dhcpcd.conf
@@ -0,0 +1,23 @@ +# A sample configuration for dhcpcd. +# See dhcpcd.conf(5) for details. + +# 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 + +# 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 + +# 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/dhcpcd.conf.5 b/dhcpcd/dhcpcd.conf.5 new file mode 100644 index 0000000..89cb793 --- /dev/null +++ b/dhcpcd/dhcpcd.conf.5
@@ -0,0 +1,326 @@ +.\" Copyright (c) 2006-2012 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd May 21, 2012 +.Dt DHCPCD.CONF 5 SMM +.Os +.Sh NAME +.Nm dhcpcd.conf +.Nd dhcpcd configuration file +.Sh DESCRIPTION +Although +.Nm dhcpcd +can do everything from the command line, there are cases where it's just easier +to do it once in a configuration file. +Most of the options found in +.Xr dhcpcd 8 +can be used here. +The first word on the line is the option and the rest of the line is the value. +Leading and trailing whitespace for the option and value are trimmed. +You can escape characters in the value using the \\ character. +.Pp +Blank lines and lines starting with # are ignored. +.Pp +Here's a list of available options: +.Bl -tag -width indent +.It Ic allowinterfaces Ar pattern +When discovering interfaces, the interface name must match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +If the same interface is matched in +.Ic denyinterfaces +then it is still denied. +.It Ic denyinterfaces Ar pattern +When discovering interfaces, the interface name must not match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +.It Ic arping Ar address Op address +.Nm dhcpcd +will arping each address in order before attempting DHCP. +If an address is found, we will select the replying hardware address as the +profile, otherwise the ip address. +Example: +.Pp +.D1 interface bge0 +.D1 arping 192.168.0.1 +.Pp +.D1 profile 192.168.0.1 +.D1 static ip_address=192.168.0.10/24 +.It Ic 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 +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm dhcpcd +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Ic duid +Generate an +.Rs +.%T "RFC 4361" +.Re +compliant 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 /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 +.Ar hostname +to the DHCP server so it can be registered in DNS. +If +.Ar hostname +is an empty string then the current system hostname is sent. +If +.Ar hostname +is a FQDN (ie, contains a .) then it will be encoded as such. +.It Ic 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. +.Nm dhcpcd +itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic ipv6ra_fork +By default, when +.Nm dhcpcd +receives an IPv6 RA, +.Nm dhcpcd +will only fork to the background if the RA contains at least one unexpired +RDNSS option. +Set this option so to make +.Nm dhcpcd +always fork on an RA. +.It ic ipv6ra_own +Disables kernel IPv6 Router Advertisment processing so dhcpcd can manage +addresses and routes. +.It ic ipv6ra_own_default +Each time dhcpcd receives an IPv6 Router Adveristment, dhcpcd will manage +the default route only. +This allows dhcpcd to prefer an interface for outbound traffic based on metric +and/or user selection rather than the kernel. +.It ic ipv6rs +Enables IPv6 Router Advertisment solicitation. +This is on by default, but is documented here in the case where it is disabled +globally but needs to be enabled for one interface. +.It Ic leasetime Ar seconds +Request a leasetime of +.Ar seconds . +.It Ic 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. +.It Ic nogateway +Don't install any default routes. +.It Ic nohook Ar script +Don't run this hook script. +Matches full name, or prefixed with 2 numbers optionally ending with +.Pa .sh . +.Pp +So to stop +.Nm dhcpcd +from touching your DNS or MTU settings you would do:- +.D1 nohook resolv.conf, mtu +.It Ic noipv4ll +Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP. +See +.Rs +.%T "RFC 3927" +.Re +.It Ic noipv6rs +Disable solicition of IPv6 Router Advertisements. +.It Ic nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic option Ar option +Requests the +.Ar option +from the server. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more 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 5 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 +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options separated by commas, spaces or more require lines. +To enforce that +.Nm dhcpcd +only responds to DHCP servers and not BOOTP servers, you can +.Ic require +.Ar dhcp_message_type . +.It Ic script Ar script +Use +.Ar script +instead of the default +.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. +.It Ic userclass Ar string +Tag the DHCP messages with the userclass. +You can specify more than one. +.It Ic vendor Ar code , Ns Ar value +Add an encapsulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +To add a raw vendor string, omit +.Ar code +but keep the comma. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 vendor 01,192.168.0.2 +Set the vendor option 02 with a hex code. +.D1 vendor 02,01:02:03:04:05 +Set the vendor option 03 with an IP address as a string. +.D1 vendor 03,\e"192.168.0.2\e" +Set un-encapsulated vendor option to hello world. +.D1 vendor ,"hello world" +.It Ic vendorclassid Ar string +The default is +dhcpcd-<version>:<os>:<machine>:<platform>. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 +If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. +.It Ic 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 if_nametoindex 3 , +.Xr fnmatch 3 +.Sh AUTHORS +.An Roy Marples Aq roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd
diff --git a/dhcpcd/dhcpcd.conf.5.in b/dhcpcd/dhcpcd.conf.5.in new file mode 100644 index 0000000..9f0ef27 --- /dev/null +++ b/dhcpcd/dhcpcd.conf.5.in
@@ -0,0 +1,309 @@ +.\" Copyright (c) 2006-2012 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd March 19, 2012 +.Dt DHCPCD.CONF 5 SMM +.Os +.Sh NAME +.Nm dhcpcd.conf +.Nd dhcpcd configuration file +.Sh DESCRIPTION +Although +.Nm dhcpcd +can do everything from the command line, there are cases where it's just easier +to do it once in a configuration file. +Most of the options found in +.Xr dhcpcd 8 +can be used here. +The first word on the line is the option and the rest of the line is the value. +Leading and trailing whitespace for the option and value are trimmed. +You can escape characters in the value using the \\ character. +.Pp +Blank lines and lines starting with # are ignored. +.Pp +Here's a list of available options: +.Bl -tag -width indent +.It Ic allowinterfaces Ar pattern +When discovering interfaces, the interface name must match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +If the same interface is matched in +.Ic denyinterfaces +then it is still denied. +.It Ic denyinterfaces Ar pattern +When discovering interfaces, the interface name must not match +.Ar pattern +which is a space or comma separated list of patterns passed to +.Xr fnmatch 3 . +.It Ic arping Ar address Op address +.Nm dhcpcd +will arping each address in order before attempting DHCP. +If an address is found, we will select the replying hardware address as the +profile, otherwise the ip address. +Example: +.Pp +.D1 interface bge0 +.D1 arping 192.168.0.1 +.Pp +.D1 profile 192.168.0.1 +.D1 static ip_address=192.168.0.10/24 +.It Ic 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 +Send the +.Ar clientid . +If the string is of the format 01:02:03 then it is encoded as hex. +For interfaces whose hardware address is longer than 8 bytes, or if the +.Ar clientid +is an empty string then +.Nm dhcpcd +sends a default +.Ar clientid +of the hardware family and the hardware address. +.It Ic duid +Generate an +.Rs +.%T "RFC 4361" +.Re +compliant 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 @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 +.Ar hostname +to the DHCP server so it can be registered in DNS. +If +.Ar hostname +is an empty string then the current system hostname is sent. +If +.Ar hostname +is a FQDN (ie, contains a .) then it will be encoded as such. +.It Ic 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. +.Nm dhcpcd +itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic ipv6ra_fork +By default, when +.Nm dhcpcd +receives an IPv6 RA, +.Nm dhcpcd +will only fork to the background if the RA contains at least one unexpired +RDNSS option. +Set this option so to make +.Nm dhcpcd +always fork on an RA. +.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. +.It Ic nogateway +Don't install any default routes. +.It Ic nohook Ar script +Don't run this hook script. +Matches full name, or prefixed with 2 numbers optionally ending with +.Pa .sh . +.Pp +So to stop +.Nm dhcpcd +from touching your DNS or MTU settings you would do:- +.D1 nohook resolv.conf, mtu +.It Ic noipv4ll +Don't attempt to obtain an IPv4LL address if we failed to get one via DHCP. +See +.Rs +.%T "RFC 3927" +.Re +.It Ic noipv6rs +Disable solicition of IPv6 Router Advertisements. +.It Ic nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic option Ar option +Requests the +.Ar option +from the server. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more 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 5 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 +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options separated by commas, spaces or more require lines. +To enforce that +.Nm dhcpcd +only responds to DHCP servers and not BOOTP servers, you can +.Ic require +.Ar dhcp_message_type . +.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. +.It Ic userclass Ar string +Tag the DHCP messages with the userclass. +You can specify more than one. +.It Ic vendor Ar code , Ns Ar value +Add an encapsulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +To add a raw vendor string, omit +.Ar code +but keep the comma. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 vendor 01,192.168.0.2 +Set the vendor option 02 with a hex code. +.D1 vendor 02,01:02:03:04:05 +Set the vendor option 03 with an IP address as a string. +.D1 vendor 03,\e"192.168.0.2\e" +Set un-encapsulated vendor option to hello world. +.D1 vendor ,"hello world" +.It Ic vendorclassid Ar string +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 if_nametoindex 3 , +.Xr fnmatch 3 +.Sh AUTHORS +.An Roy Marples Aq roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd
diff --git a/dhcpcd/dhcpcd.h b/dhcpcd/dhcpcd.h new file mode 100644 index 0000000..c2fdfdf --- /dev/null +++ b/dhcpcd/dhcpcd.h
@@ -0,0 +1,176 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCD_H +#define DHCPCD_H + +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> + +#include <limits.h> + +#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 ra_opt { + uint8_t type; + struct timeval expire; + char *option; + struct ra_opt *next; +}; + +struct ra { + struct in6_addr from; + char sfrom[INET6_ADDRSTRLEN]; + unsigned char *data; + ssize_t data_len; + struct timeval received; + uint32_t lifetime; + struct in6_addr prefix; + int prefix_len; + uint32_t prefix_vltime; + uint32_t prefix_pltime; + char sprefix[INET6_ADDRSTRLEN]; + struct ra_opt *options; + int expired; + struct ra *next; +}; + +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; + 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; + + unsigned char *rs; + size_t rslen; + int rsprobes; + struct ra *ras; + + struct interface *next; +}; + +extern int pidfd; +extern unsigned long long options; +extern int ifac; +extern char **ifav; +extern int ifdc; +extern char **ifdv; +extern struct interface *ifaces; +extern int avoid_routes; + +struct interface *find_interface(const char *); +int handle_args(struct fd_list *, int, char **); +void handle_carrier(int, int, const 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_dhcp(struct interface *, const char *); +void drop_interface(struct interface *, const char *); +int select_profile(struct interface *, const char *); + +#endif
diff --git a/dhcpcd/dhcpcd_test.cpp b/dhcpcd/dhcpcd_test.cpp new file mode 100644 index 0000000..a254d4d --- /dev/null +++ b/dhcpcd/dhcpcd_test.cpp
@@ -0,0 +1,146 @@ +/* + * Copyright 2014 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. + * + * dhcpcd_test.cpp - unit tests for dhcpcd + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string> + +#include <gtest/gtest.h> + +// For convenience. +#define ARRAYSIZE(x) sizeof((x)) / sizeof((x)[0]) + +// Regrettably, copy these defines and the dhcp_message structure in from +// dhcp.h. This header file is not easily included, since subsequent +// includes use C++ reserved keywords (like "new") as structure member names. +extern "C" { + +#define DHO_PAD 0 +#define DHO_DNSDOMAIN 15 + +/* Max MTU - defines dhcp option length */ +#define MTU_MAX 1500 + +/* Sizes for DHCP options */ +#define DHCP_CHADDR_LEN 16 +#define SERVERNAME_LEN 64 +#define BOOTFILE_LEN 128 +#define DHCP_UDP_LEN (14 + 20 + 8) +#define DHCP_FIXED_LEN (DHCP_UDP_LEN + 226) +#define DHCP_OPTION_LEN (MTU_MAX - DHCP_FIXED_LEN) + +/* Some crappy DHCP servers require the BOOTP minimum length */ +#define BOOTP_MESSAGE_LENTH_MIN 300 + +struct dhcp_message { + uint8_t op; /* message type */ + uint8_t hwtype; /* hardware address type */ + uint8_t hwlen; /* hardware address length */ + uint8_t hwopcount; /* should be zero in client message */ + uint32_t xid; /* transaction id */ + uint16_t secs; /* elapsed time in sec. from boot */ + uint16_t flags; + uint32_t ciaddr; /* (previously allocated) client IP */ + uint32_t yiaddr; /* 'your' client IP address */ + uint32_t siaddr; /* should be zero in client's messages */ + uint32_t giaddr; /* should be zero in client's messages */ + uint8_t chaddr[DHCP_CHADDR_LEN]; /* client's hardware address */ + uint8_t servername[SERVERNAME_LEN]; /* server host name */ + uint8_t bootfile[BOOTFILE_LEN]; /* boot file name */ + uint32_t cookie; + uint8_t options[DHCP_OPTION_LEN]; /* message options - cookie */ +} _packed; + +char * get_option_string(const struct dhcp_message *dhcp, uint8_t option); + +} + + +static const char kOptionString[] = "hostname"; + +class DhcpcdGetOptionTest : public ::testing::Test { + protected: + virtual void SetUp() { + memset(dhcpmsgs, 0, ARRAYSIZE(dhcpmsgs) * sizeof(struct dhcp_message)); + // Technically redundant. + memset(&(dhcpmsgs[0].options), DHO_PAD, sizeof(dhcpmsgs[0].options)); + + type_index = 0; + length_index = 0; + value_index = 0; + } + + void PopulateTLV() { + // May very well write off the end of the first struct dhcp_message, + // by design. + length_index = type_index + 1; + value_index = length_index + 1; + dhcpmsgs[0].options[type_index] = DHO_DNSDOMAIN; + dhcpmsgs[0].options[length_index] = strlen(kOptionString); + memcpy(&(dhcpmsgs[0].options[value_index]), + kOptionString, strlen(kOptionString)); + } + + struct dhcp_message dhcpmsgs[2]; + volatile size_t type_index; + volatile size_t length_index; + volatile size_t value_index; +}; + +TEST_F(DhcpcdGetOptionTest, OptionNotPresent) { + // An entire option block of padding (all zeros). + EXPECT_EQ(NULL, ::get_option_string(&(dhcpmsgs[0]), DHO_DNSDOMAIN)); +} + +TEST_F(DhcpcdGetOptionTest, TypeIsOffTheEnd) { + type_index = sizeof(dhcpmsgs[0].options); + PopulateTLV(); + EXPECT_EQ(NULL, ::get_option_string(&(dhcpmsgs[0]), DHO_DNSDOMAIN)); +} + +TEST_F(DhcpcdGetOptionTest, LengthIsOffTheEnd) { + type_index = sizeof(dhcpmsgs[0].options) - 1; + PopulateTLV(); + EXPECT_EQ(NULL, ::get_option_string(&(dhcpmsgs[0]), DHO_DNSDOMAIN)); +} + +TEST_F(DhcpcdGetOptionTest, ValueIsOffTheEnd) { + type_index = sizeof(dhcpmsgs[0].options) - 2; + PopulateTLV(); + EXPECT_EQ(NULL, ::get_option_string(&(dhcpmsgs[0]), DHO_DNSDOMAIN)); +} + +TEST_F(DhcpcdGetOptionTest, InsufficientSpaceForValue) { + type_index = sizeof(dhcpmsgs[0].options) - 6; + PopulateTLV(); + char* value = ::get_option_string(&(dhcpmsgs[0]), DHO_DNSDOMAIN); + EXPECT_TRUE(NULL != value); + EXPECT_EQ("host", ::std::string(value)); + free(value); +} + +TEST_F(DhcpcdGetOptionTest, InsufficientSpaceForContinuedValue) { + type_index = sizeof(dhcpmsgs[0].options) - 16; + PopulateTLV(); + type_index = sizeof(dhcpmsgs[0].options) - 6; + PopulateTLV(); + char* value = ::get_option_string(&(dhcpmsgs[0]), DHO_DNSDOMAIN); + EXPECT_TRUE(NULL != value); + EXPECT_EQ("hostnamehost", ::std::string(value)); + free(value); +}
diff --git a/dhcpcd/duid.c b/dhcpcd/duid.c new file mode 100644 index 0000000..d67e45e --- /dev/null +++ b/dhcpcd/duid.c
@@ -0,0 +1,100 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define THIRTY_YEARS_IN_SECONDS 946707779 + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#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/dhcpcd/duid.h b/dhcpcd/duid.h new file mode 100644 index 0000000..98c1bbd --- /dev/null +++ b/dhcpcd/duid.h
@@ -0,0 +1,35 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DUID_H +#define DUID_H + +#include "net.h" + +size_t get_duid(unsigned char *duid, const struct interface *iface); + +#endif
diff --git a/dhcpcd/eloop.c b/dhcpcd/eloop.c new file mode 100644 index 0000000..1115d89 --- /dev/null +++ b/dhcpcd/eloop.c
@@ -0,0 +1,366 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/time.h> + +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <stdarg.h> +#include <stdlib.h> +#include <syslog.h> + +#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/dhcpcd/eloop.h b/dhcpcd/eloop.h new file mode 100644 index 0000000..ece43c1 --- /dev/null +++ b/dhcpcd/eloop.h
@@ -0,0 +1,51 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ELOOP_H +#define ELOOP_H + +#include <time.h> + +#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/dhcpcd/if-bsd.c b/dhcpcd/if-bsd.c new file mode 100644 index 0000000..c3ffcac --- /dev/null +++ b/dhcpcd/if-bsd.c
@@ -0,0 +1,446 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/types.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <netinet/in.h> +#ifdef __DragonFly__ +# include <netproto/802_11/ieee80211_ioctl.h> +#elif __APPLE__ + /* FIXME: Add apple includes so we can work out SSID */ +#else +# include <net80211/ieee80211_ioctl.h> +#endif + +#include <errno.h> +#include <fnmatch.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "configure.h" +#include "dhcp.h" +#include "if-options.h" +#include "net.h" + +#ifndef RT_ROUNDUP +#define RT_ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) +#define RT_ADVANCE(x, n) (x += RT_ROUNDUP((n)->sa_len)) +#endif + +/* 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; +} + +#ifdef DEBUG_MEMORY +static void +cleanup(void) +{ + + free(link_buf); +} +#endif + +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; + memset(nwid, 0, sizeof(nwid)); + 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 struct interface *iface, const struct in_addr *address, + const struct in_addr *netmask, const struct in_addr *broadcast, + int action) +{ + int retval; + struct ifaliasreq ifa; + union { + struct sockaddr *sa; + struct sockaddr_in *sin; + } _s; + + memset(&ifa, 0, sizeof(ifa)); + 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)); \ + } + + ADDADDR(ifa.ifra_addr, address); + ADDADDR(ifa.ifra_mask, netmask); + if (action >= 0 && broadcast) { + ADDADDR(ifa.ifra_broadaddr, broadcast); + } +#undef ADDADDR + + if (action < 0) + retval = ioctl(socket_afnet, SIOCDIFADDR, &ifa); + else + retval = ioctl(socket_afnet, SIOCAIFADDR, &ifa); + return retval; +} + +/* ARGSUSED4 */ +int +if_route(const struct rt *rt, int action) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef INET6 + struct sockaddr_in6 sin6; +#endif + struct sockaddr_dl sdl; + struct sockaddr_storage ss; + } su; + struct rtm + { + struct rt_msghdr hdr; + char buffer[sizeof(su) * 4]; + } rtm; + char *bp = rtm.buffer; + size_t l; + int retval = 0; + +#define ADDSU(_su) { \ + l = RT_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; + rtm.hdr.rtm_seq = 1; + if (action == 0) + rtm.hdr.rtm_type = RTM_CHANGE; + else if (action > 0) + rtm.hdr.rtm_type = RTM_ADD; + else + rtm.hdr.rtm_type = RTM_DELETE; + rtm.hdr.rtm_flags = RTF_UP; + /* None interface subnet routes are static. */ + if (rt->gate.s_addr != INADDR_ANY || + rt->net.s_addr != rt->iface->net.s_addr || + rt->dest.s_addr != (rt->iface->addr.s_addr & rt->iface->net.s_addr)) + rtm.hdr.rtm_flags |= RTF_STATIC; + rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY; + if (rt->dest.s_addr == rt->gate.s_addr && + rt->net.s_addr == INADDR_BROADCAST) + rtm.hdr.rtm_flags |= RTF_HOST; + else { + rtm.hdr.rtm_addrs |= RTA_NETMASK; + if (rtm.hdr.rtm_flags & RTF_STATIC) + rtm.hdr.rtm_flags |= RTF_GATEWAY; + if (action >= 0) + rtm.hdr.rtm_addrs |= RTA_IFA; + } + + ADDADDR(&rt->dest); + if (rtm.hdr.rtm_flags & RTF_HOST || + !(rtm.hdr.rtm_flags & RTF_STATIC)) + { + /* Make us a link layer socket for the host gateway */ + memset(&su, 0, sizeof(su)); + su.sdl.sdl_len = sizeof(struct sockaddr_dl); + link_addr(rt->iface->name, &su.sdl); + ADDSU(su); + } else + ADDADDR(&rt->gate); + + if (rtm.hdr.rtm_addrs & RTA_NETMASK) + ADDADDR(&rt->net); + + if (rtm.hdr.rtm_addrs & RTA_IFA) + ADDADDR(&rt->iface->addr); + + rtm.hdr.rtm_msglen = l = bp - (char *)&rtm; + if (write(r_fd, &rtm, l) == -1) + retval = -1; + return retval; +} + +int +open_link_socket(void) +{ + int fd; + +#ifdef DEBUG_MEMORY + if (link_buf == NULL) + atexit(cleanup); +#endif + + fd = socket(PF_ROUTE, SOCK_RAW, 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 + RT_ADVANCE(cp, sa[i]); + } else + sa[i] = NULL; + } +} + +int +manage_link(int fd) +{ + char *p, *e, *cp; + char ifname[IF_NAMESIZE]; + ssize_t bytes; + struct rt_msghdr *rtm; + struct if_announcemsghdr *ifan; + struct if_msghdr *ifm; + 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 + + for (;;) { + 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; + if (errno == EINTR) + continue; + 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))) + break; + switch (ifm->ifm_data.ifi_link_state) { + case LINK_STATE_DOWN: + len = -1; + break; + case LINK_STATE_UP: + len = 1; + break; + default: + /* handle_carrier will re-load + * the interface flags and check for + * IFF_RUNNING as some drivers that + * don't handle link state also don't + * set IFF_RUNNING when this routing + * message is generated. + * As such, it is a race ...*/ + len = 0; + break; + } + handle_carrier(len, ifm->ifm_flags, ifname); + break; + case RTM_DELETE: + if (~rtm->rtm_addrs & + (RTA_DST | RTA_GATEWAY | 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; + } + } + } +}
diff --git a/dhcpcd/if-linux-wireless.c b/dhcpcd/if-linux-wireless.c new file mode 100644 index 0000000..f4b649b --- /dev/null +++ b/dhcpcd/if-linux-wireless.c
@@ -0,0 +1,90 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2009-2010 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * THIS IS A NASTY HACK THAT SHOULD NEVER HAVE HAPPENED + * Basically we cannot include linux/if.h and net/if.h because + * they have conflicting structures. + * Sadly, linux/wireless.h includes linux/if.h all the time. + * Some kernel-header installs fix this and some do not. + * This file solely exists for those who do not. + * + * We *could* include wireless.h as that is designed for userspace, + * but that then depends on the correct version of wireless-tools being + * installed which isn't always the case. + */ + +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <linux/types.h> +#include <linux/rtnetlink.h> +/* Support older kernels */ +#ifdef IFLA_WIRELESS +# include <linux/if.h> +# include <linux/wireless.h> +#else +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +#include <string.h> +#include <unistd.h> + +#include "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/dhcpcd/if-linux.c b/dhcpcd/if-linux.c new file mode 100644 index 0000000..3d0bcad --- /dev/null +++ b/dhcpcd/if-linux.c
@@ -0,0 +1,597 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <asm/types.h> /* Needed for 2.4 kernels */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/param.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +/* Support older kernels */ +#ifndef IFLA_WIRELESS +# define IFLA_WIRELESS (IFLA_MASTER + 1) +#endif + +#include <errno.h> +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "configure.h" +#include "dhcp.h" +#include "net.h" + +/* ANDROID change, moved this below all includes. */ +/* For some reason, glibc doesn't include newer flags from linux/if.h + * However, we cannot include linux/if.h directly as it conflicts + * with the glibc version. D'oh! */ +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 /* driver signals L1 up */ +#endif +/* End of ANDROID change */ + +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 +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; + + if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + return -1; + nl->nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) + return -1; + set_cloexec(fd); + 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 *)) +{ + char *buf = NULL, *nbuf; + ssize_t buflen = 0, bytes; + struct nlmsghdr *nlm; + int r = -1; + + for (;;) { + bytes = recv(fd, NULL, 0, + flags | MSG_PEEK | MSG_DONTWAIT | MSG_TRUNC); + if (bytes == -1) { + if (errno == EAGAIN) { + r = 0; + goto eexit; + } + 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; + } + 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); + if (r != 0) + goto eexit; + } + } + +eexit: + free(buf); + return r; +} + +static int +err_netlink(struct nlmsghdr *nlm) +{ + struct nlmsgerr *err; + int l; + + if (nlm->nlmsg_type != NLMSG_ERROR) + return 0; + l = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)l < sizeof(*err)) { + errno = EBADMSG; + return -1; + } + err = (struct nlmsgerr *)NLMSG_DATA(nlm); + if (err->error == 0) + return l; + errno = -err->error; + return -1; +} + +static int +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); + if ((size_t)len < sizeof(*ifi)) { + errno = EBADMSG; + return -1; + } + ifi = NLMSG_DATA(nlm); + if (ifi->ifi_flags & IFF_LOOPBACK) + return 1; + rta = (struct rtattr *) ((char *)ifi + NLMSG_ALIGN(sizeof(*ifi))); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifi)); + *ifn = '\0'; + while (RTA_OK(rta, len)) { + switch (rta->rta_type) { + case IFLA_WIRELESS: + /* Ignore wireless messages */ + if (nlm->nlmsg_type == RTM_NEWLINK && + ifi->ifi_change == 0) + return 1; + break; + case IFLA_IFNAME: + strlcpy(ifn, RTA_DATA(rta), sizeof(ifn)); + break; + } + rta = RTA_NEXT(rta, len); + } + + if (nlm->nlmsg_type == RTM_DELLINK) { + handle_interface(-1, ifn); + return 1; + } + + /* Bridge interfaces set IFF_LOWER_UP when they have a valid + * hardware address. To trigger a valid hardware address pickup + * we need to pretend that that don't exist until they have + * IFF_LOWER_UP set. */ + if (ifi->ifi_flags & IFF_MASTER && !(ifi->ifi_flags & IFF_LOWER_UP)) { + handle_interface(-1, ifn); + return 1; + } + + handle_carrier(ifi->ifi_flags & IFF_RUNNING ? 1 : -1, + ifi->ifi_flags, ifn); + return 1; +} + +int +manage_link(int fd) +{ + return get_netlink(fd, MSG_DONTWAIT, &link_netlink); +} + +static int +send_netlink(struct nlmsghdr *hdr) +{ + int r; + struct iovec iov; + struct msghdr msg; + static unsigned int seq; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = hdr; + iov.iov_len = hdr->nlmsg_len; + memset(&msg, 0, sizeof(msg)); + 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(sock_fd, &msg, 0) != -1) + r = get_netlink(sock_fd, 0, &err_netlink); + else + r = -1; + return r; +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len))) + +static int +add_attr_l(struct nlmsghdr *n, unsigned int maxlen, int type, + const void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + errno = ENOBUFS; + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return 0; +} + +static int +add_attr_32(struct nlmsghdr *n, unsigned int maxlen, int type, uint32_t data) +{ + int len = RTA_LENGTH(sizeof(data)); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + errno = ENOBUFS; + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, sizeof(data)); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + + return 0; +} + +struct nlma +{ + struct nlmsghdr hdr; + struct ifaddrmsg ifa; + char buffer[64]; +}; + +struct nlmr +{ + struct nlmsghdr hdr; + struct rtmsg rt; + char buffer[256]; +}; + +int +if_address(const struct interface *iface, + const struct in_addr *address, const struct in_addr *netmask, + const struct in_addr *broadcast, int action) +{ + struct nlma *nlm; + int retval = 0; + + nlm = xzalloc(sizeof(*nlm)); + nlm->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + nlm->hdr.nlmsg_flags = NLM_F_REQUEST; + if (action >= 0) { + nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + nlm->hdr.nlmsg_type = RTM_NEWADDR; + } else + nlm->hdr.nlmsg_type = RTM_DELADDR; + if (!(nlm->ifa.ifa_index = if_nametoindex(iface->name))) { + free(nlm); + errno = ENODEV; + return -1; + } + nlm->ifa.ifa_family = AF_INET; + nlm->ifa.ifa_prefixlen = inet_ntocidr(*netmask); + /* This creates the aliased interface */ + add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LABEL, + iface->name, strlen(iface->name) + 1); + add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL, + &address->s_addr, sizeof(address->s_addr)); + if (action >= 0 && broadcast) + add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_BROADCAST, + &broadcast->s_addr, sizeof(broadcast->s_addr)); + + if (send_netlink(&nlm->hdr) == -1) + retval = -1; + free(nlm); + return retval; +} + +int +if_route(const struct rt *rt, int action) +{ + struct nlmr *nlm; + unsigned int ifindex; + int retval = 0; + + if (!(ifindex = if_nametoindex(rt->iface->name))) { + errno = ENODEV; + return -1; + } + + nlm = xzalloc(sizeof(*nlm)); + nlm->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nlm->hdr.nlmsg_type = RTM_NEWROUTE; + if (action == 0) + nlm->hdr.nlmsg_flags = NLM_F_REPLACE; + else if (action == 1) + nlm->hdr.nlmsg_flags = NLM_F_CREATE /*| NLM_F_EXCL*/; + else + nlm->hdr.nlmsg_type = RTM_DELROUTE; + nlm->hdr.nlmsg_flags |= NLM_F_REQUEST; + nlm->rt.rtm_family = AF_INET; + nlm->rt.rtm_table = RT_TABLE_MAIN; + + if (action == -1 || action == -2) + nlm->rt.rtm_scope = RT_SCOPE_NOWHERE; + else { + nlm->hdr.nlmsg_flags |= NLM_F_CREATE /*| NLM_F_EXCL*/; + /* We only change route metrics for kernel routes */ + if (rt->dest.s_addr == + (rt->iface->addr.s_addr & rt->iface->net.s_addr) && + rt->net.s_addr == rt->iface->net.s_addr) + nlm->rt.rtm_protocol = RTPROT_KERNEL; + else + nlm->rt.rtm_protocol = RTPROT_BOOT; + if (rt->gate.s_addr == INADDR_ANY || + (rt->gate.s_addr == rt->dest.s_addr && + rt->net.s_addr == INADDR_BROADCAST)) + nlm->rt.rtm_scope = RT_SCOPE_LINK; + else + nlm->rt.rtm_scope = RT_SCOPE_UNIVERSE; + nlm->rt.rtm_type = RTN_UNICAST; + } + + nlm->rt.rtm_dst_len = inet_ntocidr(rt->net); + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_DST, + &rt->dest.s_addr, sizeof(rt->dest.s_addr)); + if (nlm->rt.rtm_protocol == RTPROT_KERNEL) { + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_PREFSRC, + &rt->iface->addr.s_addr, sizeof(rt->iface->addr.s_addr)); + } + /* If destination == gateway then don't add the gateway */ + if (rt->dest.s_addr != rt->gate.s_addr || + rt->net.s_addr != INADDR_BROADCAST) + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_GATEWAY, + &rt->gate.s_addr, sizeof(rt->gate.s_addr)); + + add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_OIF, ifindex); + add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_PRIORITY, rt->metric); + + if (send_netlink(&nlm->hdr) == -1) + retval = -1; + free(nlm); + return retval; +}
diff --git a/dhcpcd/if-options.c b/dhcpcd/if-options.c new file mode 100644 index 0000000..4fbdf13 --- /dev/null +++ b/dhcpcd/if-options.c
@@ -0,0 +1,937 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/utsname.h> + +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <time.h> + +#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 +#define O_NOIPV6RS O_BASE + 4 +#define O_IPV6_RA_FORK O_BASE + 5 + +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}, + {"noipv6rs", no_argument, NULL, O_NOIPV6RS}, + {"ipv6ra_fork", no_argument, NULL, O_IPV6_RA_FORK}, + {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 'a': /* FALLTHROUGH */ + 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.s_addr == 0 ? &ifo->req_mask : NULL, + p) != 0) + return -1; + + ifo->options |= DHCPCD_STATIC; + ifo->options &= ~DHCPCD_INFORM; + } else if (strncmp(arg, "subnet_mask=", strlen("subnet_mask=")) == 0) { + if (parse_addr(&ifo->req_mask, NULL, p) != 0) + return -1; + } else if (strncmp(arg, "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; + case O_NOIPV6RS: + ifo->options &= ~DHCPCD_IPV6RS; + break; + case O_IPV6_RA_FORK: + ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; + 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 | DHCPCD_LINK; + ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL; +#ifndef ANDROID + /* On Android, don't enable IPv6 RS processing. This is because we + * already process RAs in the kernel and netd and because we've seen + * dhcpcd crashes when parsing certain RA options. + * See http://b/15268738 and http://b/15779617 . */ + ifo->options |= DHCPCD_IPV6RS | DHCPCD_IPV6RA_REQRDNSS; +#endif + 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(); +#ifndef ANDROID + 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 +#endif + 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/dhcpcd/if-options.h b/dhcpcd/if-options.h new file mode 100644 index 0000000..45fac33 --- /dev/null +++ b/dhcpcd/if-options.h
@@ -0,0 +1,125 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IF_OPTIONS_H +#define IF_OPTIONS_H + +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> + +#include <getopt.h> +#include <limits.h> + +/* Don't set any optional arguments here so we retain POSIX + * compatibility with getopt */ +#define IF_OPTS "abc: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 0 /* keep retrying forever... */ +#define DEFAULT_REBOOT 5 + +#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 (1ULL << 0) +#define DHCPCD_RELEASE (1ULL << 1) +#define DHCPCD_DOMAIN (1ULL << 2) +#define DHCPCD_GATEWAY (1ULL << 3) +#define DHCPCD_STATIC (1ULL << 4) +#define DHCPCD_DEBUG (1ULL << 5) +#define DHCPCD_LASTLEASE (1ULL << 7) +#define DHCPCD_INFORM (1ULL << 8) +#define DHCPCD_REQUEST (1ULL << 9) +#define DHCPCD_IPV4LL (1ULL << 10) +#define DHCPCD_DUID (1ULL << 11) +#define DHCPCD_PERSISTENT (1ULL << 12) +#define DHCPCD_DAEMONISE (1ULL << 14) +#define DHCPCD_DAEMONISED (1ULL << 15) +#define DHCPCD_TEST (1ULL << 16) +#define DHCPCD_MASTER (1ULL << 17) +#define DHCPCD_HOSTNAME (1ULL << 18) +#define DHCPCD_CLIENTID (1ULL << 19) +#define DHCPCD_LINK (1ULL << 20) +#define DHCPCD_QUIET (1ULL << 21) +#define DHCPCD_BACKGROUND (1ULL << 22) +#define DHCPCD_VENDORRAW (1ULL << 23) +#define DHCPCD_TIMEOUT_IPV4LL (1ULL << 24) +#define DHCPCD_WAITIP (1ULL << 25) +#define DHCPCD_WAITUP (1ULL << 26) +#define DHCPCD_CSR_WARNED (1ULL << 27) +#define DHCPCD_XID_HWADDR (1ULL << 28) +#define DHCPCD_BROADCAST (1ULL << 29) +#define DHCPCD_DUMPLEASE (1ULL << 30) +#define DHCPCD_IPV6RS (1ULL << 31) +#define DHCPCD_IPV6RA_REQRDNSS (1ULL << 32) + +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; + unsigned long long 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/dhcpcd/if-pref.c b/dhcpcd/if-pref.c new file mode 100644 index 0000000..6169dbe --- /dev/null +++ b/dhcpcd/if-pref.c
@@ -0,0 +1,108 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> + +#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/dhcpcd/if-pref.h b/dhcpcd/if-pref.h new file mode 100644 index 0000000..dcedd60 --- /dev/null +++ b/dhcpcd/if-pref.h
@@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IF_PREF_H +#define IF_PREF_H + +#include "dhcpcd.h" + +void sort_interfaces(void); +#endif
diff --git a/dhcpcd/ifaddrs.c b/dhcpcd/ifaddrs.c new file mode 100644 index 0000000..c36fd64 --- /dev/null +++ b/dhcpcd/ifaddrs.c
@@ -0,0 +1,148 @@ +/* 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 <arpa/inet.h> +#include <sys/socket.h> +#include "ifaddrs.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <dirent.h> +#include <netinet/ether.h> +#include <netdb.h> +#include <linux/if_packet.h> +#include <netinet/if_ether.h> +#include <linux/if_arp.h> +#include <netutils/ifc.h> + +struct ifaddrs *get_interface(const char *name, sa_family_t family) +{ + unsigned addr, flags; + int masklen; + 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, &masklen, &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 (masklen != 0) { + smask = malloc(sizeof(struct sockaddr_in)); + if (smask) { + smask->sin_addr.s_addr = prefixLengthToIpv4Netmask(masklen); + 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/dhcpcd/ifaddrs.h b/dhcpcd/ifaddrs.h new file mode 100644 index 0000000..6356653 --- /dev/null +++ b/dhcpcd/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 <sys/socket.h> + +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/dhcpcd/ipv4ll.c b/dhcpcd/ipv4ll.c new file mode 100644 index 0000000..622abb7 --- /dev/null +++ b/dhcpcd/ipv4ll.c
@@ -0,0 +1,156 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#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_dhcp(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/dhcpcd/ipv4ll.h b/dhcpcd/ipv4ll.h new file mode 100644 index 0000000..a5d8e9a --- /dev/null +++ b/dhcpcd/ipv4ll.h
@@ -0,0 +1,33 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV4LL_H +#define IPV4LL_H + +void start_ipv4ll(void *); +void handle_ipv4ll_failure(void *); +#endif
diff --git a/dhcpcd/ipv6rs.c b/dhcpcd/ipv6rs.c new file mode 100644 index 0000000..df96934 --- /dev/null +++ b/dhcpcd/ipv6rs.c
@@ -0,0 +1,762 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> + +#include <errno.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#ifdef __linux__ +# define _LINUX_IN6_H +# include <linux/ipv6.h> +#endif + +#define ELOOP_QUEUE 1 +#include "bind.h" +#include "common.h" +#include "configure.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "ipv6rs.h" + +#define ALLROUTERS "ff02::2" +#define HOPLIMIT 255 + +#define ROUNDUP8(a) (1 + (((a) - 1) | 7)) + +#define RTR_SOLICITATION_INTERVAL 4 /* seconds */ +#define MAX_RTR_SOLICITATIONS 3 /* times */ + +#ifndef ND_OPT_RDNSS +#define ND_OPT_RDNSS 25 +struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ + uint8_t nd_opt_rdnss_type; + uint8_t nd_opt_rdnss_len; + uint16_t nd_opt_rdnss_reserved; + uint32_t nd_opt_rdnss_lifetime; + /* followed by list of IP prefixes */ +} _packed; +#endif + +#ifndef ND_OPT_DNSSL +#define ND_OPT_DNSSL 31 +struct nd_opt_dnssl { /* DNSSL option RFC 6106 */ + uint8_t nd_opt_dnssl_type; + uint8_t nd_opt_dnssl_len; + uint16_t nd_opt_dnssl_reserved; + uint32_t nd_opt_dnssl_lifetime; + /* followed by list of DNS servers */ +} _packed; +#endif + +static int sock; +static struct sockaddr_in6 allrouters, from; +static struct msghdr sndhdr; +static struct iovec sndiov[2]; +static unsigned char *sndbuf; +static struct msghdr rcvhdr; +static struct iovec rcviov[2]; +static unsigned char *rcvbuf; +static unsigned char ansbuf[1500]; +static char ntopbuf[INET6_ADDRSTRLEN]; + +#if DEBUG_MEMORY +static void +ipv6rs_cleanup(void) +{ + + free(sndbuf); + free(rcvbuf); +} +#endif + +int +ipv6rs_open(void) +{ + int on; + int len; + struct icmp6_filter filt; + + memset(&allrouters, 0, sizeof(allrouters)); + allrouters.sin6_family = AF_INET6; +#ifdef SIN6_LEN + allrouters.sin6_len = sizeof(allrouters); +#endif + if (inet_pton(AF_INET6, ALLROUTERS, &allrouters.sin6_addr.s6_addr) != 1) + return -1; + sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (sock == -1) + return -1; + on = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof(on)) == -1) + return -1; + + on = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, + &on, sizeof(on)) == -1) + return -1; + + ICMP6_FILTER_SETBLOCKALL(&filt); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); + if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, + &filt, sizeof(filt)) == -1) + return -1; + +#if DEBUG_MEMORY + atexit(ipv6rs_cleanup); +#endif + + len = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); + sndbuf = xzalloc(len); + if (sndbuf == NULL) + return -1; + sndhdr.msg_namelen = sizeof(struct sockaddr_in6); + sndhdr.msg_iov = sndiov; + sndhdr.msg_iovlen = 1; + sndhdr.msg_control = sndbuf; + sndhdr.msg_controllen = len; + rcvbuf = xzalloc(len); + if (rcvbuf == NULL) + return -1; + rcvhdr.msg_name = &from; + rcvhdr.msg_namelen = sizeof(from); + rcvhdr.msg_iov = rcviov; + rcvhdr.msg_iovlen = 1; + rcvhdr.msg_control = rcvbuf; + rcvhdr.msg_controllen = len; + rcviov[0].iov_base = ansbuf; + rcviov[0].iov_len = sizeof(ansbuf); + return sock; +} + +static int +ipv6rs_makeprobe(struct interface *ifp) +{ + struct nd_router_solicit *rs; + struct nd_opt_hdr *nd; + + free(ifp->rs); + ifp->rslen = sizeof(*rs) + ROUNDUP8(ifp->hwlen + 2); + ifp->rs = xzalloc(ifp->rslen); + if (ifp->rs == NULL) + return -1; + rs = (struct nd_router_solicit *)ifp->rs; + rs->nd_rs_type = ND_ROUTER_SOLICIT; + rs->nd_rs_code = 0; + rs->nd_rs_cksum = 0; + rs->nd_rs_reserved = 0; + nd = (struct nd_opt_hdr *)(ifp->rs + sizeof(*rs)); + nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + nd->nd_opt_len = (ROUNDUP8(ifp->hwlen + 2)) >> 3; + memcpy(nd + 1, ifp->hwaddr, ifp->hwlen); + return 0; +} + +static void +ipv6rs_sendprobe(void *arg) +{ + struct interface *ifp = arg; + struct sockaddr_in6 dst; + struct cmsghdr *cm; + struct in6_pktinfo pi; + int hoplimit = HOPLIMIT; + + dst = allrouters; + //dst.sin6_scope_id = ifp->linkid; + + ipv6rs_makeprobe(ifp); + sndhdr.msg_name = (caddr_t)&dst; + sndhdr.msg_iov[0].iov_base = ifp->rs; + sndhdr.msg_iov[0].iov_len = ifp->rslen; + + /* Set the outbound interface */ + cm = CMSG_FIRSTHDR(&sndhdr); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(pi)); + memset(&pi, 0, sizeof(pi)); + pi.ipi6_ifindex = if_nametoindex(ifp->name); + memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); + + /* Hop limit */ + cm = CMSG_NXTHDR(&sndhdr, cm); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_HOPLIMIT; + cm->cmsg_len = CMSG_LEN(sizeof(hoplimit)); + memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit)); + + syslog(LOG_INFO, "%s: sending IPv6 Router Solicitation", ifp->name); + if (sendmsg(sock, &sndhdr, 0) == -1) + syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name); + + if (ifp->rsprobes++ < MAX_RTR_SOLICITATIONS) + add_timeout_sec(RTR_SOLICITATION_INTERVAL, + ipv6rs_sendprobe, ifp); + else + syslog(LOG_INFO, "%s: no IPv6 Routers available", ifp->name); +} + +static void +ipv6rs_sort(struct interface *ifp) +{ + struct ra *rap, *sorted, *ran, *rat; + + if (ifp->ras == NULL || ifp->ras->next == NULL) + return; + + /* Sort our RA's - most recent first */ + sorted = ifp->ras; + ifp->ras = ifp->ras->next; + sorted->next = NULL; + for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) { + /* Are we the new head? */ + if (timercmp(&rap->received, &sorted->received, <)) { + rap->next = sorted; + sorted = rap; + continue; + } + /* Do we fit in the middle? */ + for (rat = sorted; rat->next; rat = rat->next) { + if (timercmp(&rap->received, &rat->next->received, <)) { + rap->next = rat->next; + rat->next = rap; + break; + } + } + /* We must be at the end */ + if (!rat->next) { + rat->next = rap; + rap->next = NULL; + } + } +} + +void +ipv6rs_handledata(_unused void *arg) +{ + ssize_t len, l, n, olen; + struct cmsghdr *cm; + int hoplimit; + struct in6_pktinfo pkt; + struct icmp6_hdr *icp; + struct interface *ifp; + const char *sfrom; + struct nd_router_advert *nd_ra; + struct nd_opt_prefix_info *pi; + struct nd_opt_mtu *mtu; + struct nd_opt_rdnss *rdnss; + struct nd_opt_dnssl *dnssl; + uint32_t lifetime; + uint8_t *p, *op; + struct in6_addr addr; + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + struct ra *rap; + struct nd_opt_hdr *ndo; + struct ra_opt *rao, *raol; + char *opt; + struct timeval expire; + int has_dns; + + len = recvmsg(sock, &rcvhdr, 0); + if (len == -1) { + syslog(LOG_ERR, "recvmsg: %m"); + return; + } + sfrom = inet_ntop(AF_INET6, &from.sin6_addr, + ntopbuf, INET6_ADDRSTRLEN); + if ((size_t)len < sizeof(struct nd_router_advert)) { + syslog(LOG_ERR, "IPv6 RA packet too short from %s", sfrom); + return; + } + + pkt.ipi6_ifindex = hoplimit = 0; + for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvhdr); + cm; + cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvhdr, cm)) + { + if (cm->cmsg_level != IPPROTO_IPV6) + continue; + switch(cm->cmsg_type) { + case IPV6_PKTINFO: + if (cm->cmsg_len == CMSG_LEN(sizeof(pkt))) + memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt)); + break; + case IPV6_HOPLIMIT: + if (cm->cmsg_len == CMSG_LEN(sizeof(int))) + memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int)); + break; + } + } + + if (pkt.ipi6_ifindex == 0 || hoplimit == 0) { + syslog(LOG_ERR, + "IPv6 RA did not contain index or hop limit from %s", + sfrom); + return; + } + + icp = (struct icmp6_hdr *)rcvhdr.msg_iov[0].iov_base; + if (icp->icmp6_type != ND_ROUTER_ADVERT || + icp->icmp6_code != 0) + { + syslog(LOG_ERR, "invalid IPv6 type or code from %s", sfrom); + return; + } + + if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) { + syslog(LOG_ERR, "RA recieved from non local IPv6 address %s", + sfrom); + return; + } + + for (ifp = ifaces; ifp; ifp = ifp->next) + if (if_nametoindex(ifp->name) == (unsigned int)pkt.ipi6_ifindex) + break; + if (ifp == NULL) { + syslog(LOG_ERR,"received RA for unexpected interface from %s", + sfrom); + return; + } + for (rap = ifp->ras; rap; rap = rap->next) { + if (memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr, + sizeof(rap->from.s6_addr)) == 0) + break; + } + + /* We don't want to spam the log with the fact we got an RA every + * 30 seconds or so, so only spam the log if it's different. */ + if (options & DHCPCD_DEBUG || rap == NULL || + (rap->expired || rap->data_len != len || + memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) + { + if (rap) { + free(rap->data); + rap->data_len = 0; + } + syslog(LOG_INFO, "%s: Router Advertisement from %s", + ifp->name, sfrom); + } + + if (rap == NULL) { + rap = xmalloc(sizeof(*rap)); + rap->next = ifp->ras; + rap->options = NULL; + ifp->ras = rap; + memcpy(rap->from.s6_addr, from.sin6_addr.s6_addr, + sizeof(rap->from.s6_addr)); + strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom)); + rap->data_len = 0; + } + if (rap->data_len == 0) { + rap->data = xmalloc(len); + memcpy(rap->data, icp, len); + rap->data_len = len; + } + + get_monotonic(&rap->received); + nd_ra = (struct nd_router_advert *)icp; + rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); + rap->expired = 0; + + len -= sizeof(struct nd_router_advert); + p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); + olen = 0; + lifetime = ~0U; + has_dns = 0; + for (olen = 0; len > 0; p += olen, len -= olen) { + if ((size_t)len < sizeof(struct nd_opt_hdr)) { + syslog(LOG_ERR, "%s: Short option", ifp->name); + break; + } + ndo = (struct nd_opt_hdr *)p; + olen = ndo->nd_opt_len * 8 ; + if (olen == 0) { + syslog(LOG_ERR, "%s: zero length option", ifp->name); + break; + } + if (olen > len) { + syslog(LOG_ERR, + "%s: Option length exceeds message", ifp->name); + break; + } + + opt = NULL; + switch (ndo->nd_opt_type) { + case ND_OPT_PREFIX_INFORMATION: + pi = (struct nd_opt_prefix_info *)ndo; + if (pi->nd_opt_pi_len != 4) { + syslog(LOG_ERR, + "%s: invalid option len for prefix", + ifp->name); + break; + } + if (pi->nd_opt_pi_prefix_len > 128) { + syslog(LOG_ERR, "%s: invalid prefix len", + ifp->name); + break; + } + if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) || + IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) + { + syslog(LOG_ERR, + "%s: invalid prefix in RA", ifp->name); + break; + } + opt = xstrdup(inet_ntop(AF_INET6, + pi->nd_opt_pi_prefix.s6_addr, + ntopbuf, INET6_ADDRSTRLEN)); + if (opt) { + rap->prefix_len = pi->nd_opt_pi_prefix_len; + rap->prefix_vltime = + ntohl(pi->nd_opt_pi_valid_time); + rap->prefix_pltime = + ntohl(pi->nd_opt_pi_preferred_time); + } + break; + + case ND_OPT_MTU: + mtu = (struct nd_opt_mtu *)p; + snprintf(buf, sizeof(buf), "%d", + ntohl(mtu->nd_opt_mtu_mtu)); + opt = xstrdup(buf); + break; + + case ND_OPT_RDNSS: + rdnss = (struct nd_opt_rdnss *)p; + lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime); + op = (uint8_t *)ndo; + op += offsetof(struct nd_opt_rdnss, + nd_opt_rdnss_lifetime); + op += sizeof(rdnss->nd_opt_rdnss_lifetime); + l = 0; + for (n = ndo->nd_opt_len - 1; n > 1; n -= 2) { + memcpy(&addr.s6_addr, op, sizeof(addr.s6_addr)); + cbp = inet_ntop(AF_INET6, &addr, + ntopbuf, INET6_ADDRSTRLEN); + if (cbp == NULL) { + syslog(LOG_ERR, + "%s: invalid RDNSS address", + ifp->name); + } else { + if (opt) { + l = strlen(opt); + opt = xrealloc(opt, + l + strlen(cbp) + 2); + opt[l] = ' '; + strcpy(opt + l + 1, cbp); + } else + opt = xstrdup(cbp); + if (lifetime > 0) + has_dns = 1; + } + op += sizeof(addr.s6_addr); + } + break; + + case ND_OPT_DNSSL: + dnssl = (struct nd_opt_dnssl *)p; + lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime); + op = p + offsetof(struct nd_opt_dnssl, + nd_opt_dnssl_lifetime); + op += sizeof(dnssl->nd_opt_dnssl_lifetime); + n = (dnssl->nd_opt_dnssl_len - 1) * 8; + l = decode_rfc3397(NULL, 0, n, op); + if (l < 1) { + syslog(LOG_ERR, "%s: invalid DNSSL option", + ifp->name); + } else { + opt = xmalloc(l); + decode_rfc3397(opt, l, n, op); + } + break; + } + + if (opt == NULL) + continue; + for (raol = NULL, rao = rap->options; + rao; + raol = rao, rao = rao->next) + { + if (rao->type == ndo->nd_opt_type && + strcmp(rao->option, opt) == 0) + break; + } + if (lifetime == 0) { + if (rao) { + if (raol) + raol->next = rao->next; + else + rap->options = rao->next; + free(rao->option); + free(rao); + } + continue; + } + + if (rao == NULL) { + rao = xmalloc(sizeof(*rao)); + rao->next = rap->options; + rap->options = rao; + rao->type = ndo->nd_opt_type; + rao->option = opt; + } else + free(opt); + if (lifetime == ~0U) + timerclear(&rao->expire); + else { + expire.tv_sec = lifetime; + expire.tv_usec = 0; + timeradd(&rap->received, &expire, &rao->expire); + } + } + + ipv6rs_sort(ifp); + run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : "ROUTERADVERT"); + if (options & DHCPCD_TEST) + exit(EXIT_SUCCESS); + + /* If we don't require RDNSS then set has_dns = 1 so we fork */ + if (!(ifp->state->options->options & DHCPCD_IPV6RA_REQRDNSS)) + has_dns = 1; + + if (has_dns) + delete_q_timeout(0, handle_exit_timeout, NULL); + delete_timeout(NULL, ifp); + ipv6rs_expire(ifp); + if (has_dns) + daemonise(); + else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED)) + syslog(LOG_WARNING, + "%s: did not fork due to an absent RDNSS option in the RA", + ifp->name); +} + +ssize_t +ipv6rs_env(char **env, const char *prefix, const struct interface *ifp) +{ + ssize_t l; + struct timeval now; + const struct ra *rap; + const struct ra_opt *rao; + int i; + char buffer[32], buffer2[32]; + const char *optn; + + l = 0; + get_monotonic(&now); + for (rap = ifp->ras, i = 1; rap; rap = rap->next, i++) { + if (env) { + snprintf(buffer, sizeof(buffer), + "ra%d_from", i); + setvar(&env, prefix, buffer, rap->sfrom); + } + l++; + + for (rao = rap->options; rao; rao = rao->next) { + if (rao->option == NULL) + continue; + if (env == NULL) { + switch (rao->type) { + case ND_OPT_PREFIX_INFORMATION: + l += 4; + break; + default: + l++; + } + continue; + } + switch (rao->type) { + case ND_OPT_PREFIX_INFORMATION: + optn = "prefix"; + break; + case ND_OPT_MTU: + optn = "mtu"; + break; + case ND_OPT_RDNSS: + optn = "rdnss"; + break; + case ND_OPT_DNSSL: + optn = "dnssl"; + break; + default: + continue; + } + snprintf(buffer, sizeof(buffer), "ra%d_%s", i, optn); + setvar(&env, prefix, buffer, rao->option); + l++; + switch (rao->type) { + case ND_OPT_PREFIX_INFORMATION: + snprintf(buffer, sizeof(buffer), + "ra%d_prefix_len", i); + snprintf(buffer2, sizeof(buffer2), + "%d", rap->prefix_len); + setvar(&env, prefix, buffer, buffer2); + + snprintf(buffer, sizeof(buffer), + "ra%d_prefix_vltime", i); + snprintf(buffer2, sizeof(buffer2), + "%d", rap->prefix_vltime); + setvar(&env, prefix, buffer, buffer2); + + snprintf(buffer, sizeof(buffer), + "ra%d_prefix_pltime", i); + snprintf(buffer2, sizeof(buffer2), + "%d", rap->prefix_pltime); + setvar(&env, prefix, buffer, buffer2); + l += 3; + break; + } + + } + } + + if (env) + setvard(&env, prefix, "ra_count", i - 1); + l++; + return l; +} + +static void +ipv6rs_free_opts(struct ra *rap) +{ + struct ra_opt *rao, *raon; + + for (rao = rap->options; rao && (raon = rao->next, 1); rao = raon) { + free(rao->option); + free(rao); + } +} + +void +ipv6rs_free(struct interface *ifp) +{ + struct ra *rap, *ran; + + free(ifp->rs); + ifp->rs = NULL; + for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) { + ipv6rs_free_opts(rap); + free(rap->data); + free(rap); + } + ifp->ras = NULL; +} + +void +ipv6rs_expire(void *arg) +{ + struct interface *ifp; + struct ra *rap, *ran, *ral; + struct ra_opt *rao, *raol, *raon; + struct timeval now, lt, expire, next; + int expired; + uint32_t expire_secs; + + ifp = arg; + get_monotonic(&now); + expired = 0; + expire_secs = ~0U; + timerclear(&next); + + for (rap = ifp->ras, ral = NULL; + rap && (ran = rap->next, 1); + ral = rap, rap = ran) + { + lt.tv_sec = rap->lifetime; + lt.tv_usec = 0; + timeradd(&rap->received, <, &expire); + if (timercmp(&now, &expire, >)) { + syslog(LOG_INFO, "%s: %s: expired Router Advertisement", + ifp->name, rap->sfrom); + rap->expired = expired = 1; + if (ral) + ral->next = ran; + else + ifp->ras = ran; + ipv6rs_free_opts(rap); + free(rap); + continue; + } + timersub(&expire, &now, <); + if (!timerisset(&next) || timercmp(&next, <, >)) + next = lt; + + for (rao = rap->options, raol = NULL; + rao && (raon = rao->next); + raol = rao, rao = raon) + { + if (!timerisset(&rao->expire)) + continue; + if (timercmp(&now, &rao->expire, >)) { + syslog(LOG_INFO, + "%s: %s: expired option %d", + ifp->name, rap->sfrom, rao->type); + rap->expired = expired = 1; + if (raol) + raol = raon; + else + rap->options = raon; + continue; + } + timersub(&rao->expire, &now, <); + if (!timerisset(&next) || timercmp(&next, <, >)) + next = lt; + } + } + + if (timerisset(&next)) + add_timeout_tv(&next, ipv6rs_expire, ifp); + if (expired) + run_script_reason(ifp, "ROUTERADVERT"); +} + +int +ipv6rs_start(struct interface *ifp) +{ + + delete_timeout(NULL, ifp); + + /* Always make a new probe as the underlying hardware + * address could have changed. */ + ipv6rs_makeprobe(ifp); + if (ifp->rs == NULL) + return -1; + + ifp->rsprobes = 0; + ipv6rs_sendprobe(ifp); + return 0; +}
diff --git a/dhcpcd/ipv6rs.h b/dhcpcd/ipv6rs.h new file mode 100644 index 0000000..ae18d3a --- /dev/null +++ b/dhcpcd/ipv6rs.h
@@ -0,0 +1,40 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV6RS_H +#define IPV6RS_H + +#ifndef ICMP6_FILTER +#define ICMP6_FILTER 1 +#endif +int ipv6rs_open(void); +void ipv6rs_handledata(void *); +int ipv6rs_start(struct interface *); +ssize_t ipv6rs_env(char **, const char *, const struct interface *); +void ipv6rs_free(struct interface *ifp); +void ipv6rs_expire(void *arg); +#endif
diff --git a/dhcpcd/lpf.c b/dhcpcd/lpf.c new file mode 100644 index 0000000..853d0a3 --- /dev/null +++ b/dhcpcd/lpf.c
@@ -0,0 +1,214 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#ifdef __linux__ +# include <asm/types.h> /* needed for 2.4 kernels for the below header */ +# include <linux/filter.h> +# include <linux/if_packet.h> +# define bpf_insn sock_filter +# define BPF_SKIPTYPE +# define BPF_ETHCOOK -ETH_HLEN +# define BPF_WHOLEPACKET 0x0fffffff /* work around buggy LPF filters */ +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "net.h" +#include "bpf-filter.h" + +/* Broadcast address for IPoIB */ +static const uint8_t ipv4_bcast_addr[] = { + 0x00, 0xff, 0xff, 0xff, + 0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff +}; + +int +open_socket(struct interface *iface, int protocol) +{ + int s; + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su; + struct sock_fprog pf; + int *fd; +#ifdef PACKET_AUXDATA + int n; +#endif + + if ((s = socket(PF_PACKET, SOCK_DGRAM, htons(protocol))) == -1) + return -1; + + memset(&su, 0, sizeof(su)); + su.sll.sll_family = PF_PACKET; + su.sll.sll_protocol = htons(protocol); + if (!(su.sll.sll_ifindex = if_nametoindex(iface->name))) { + errno = ENOENT; + goto eexit; + } + /* Install the DHCP filter */ + memset(&pf, 0, sizeof(pf)); + if (protocol == ETHERTYPE_ARP) { + pf.filter = UNCONST(arp_bpf_filter); + pf.len = arp_bpf_filter_len; + } else { + pf.filter = UNCONST(dhcp_bpf_filter); + pf.len = dhcp_bpf_filter_len; + } + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) != 0) + goto eexit; +#ifdef PACKET_AUXDATA + n = 1; + if (setsockopt(s, SOL_PACKET, PACKET_AUXDATA, &n, sizeof(n)) != 0) { + if (errno != ENOPROTOOPT) + goto eexit; + } +#endif + if (set_cloexec(s) == -1) + goto eexit; + if (set_nonblock(s) == -1) + goto eexit; + if (bind(s, &su.sa, sizeof(su)) == -1) + goto eexit; + if (protocol == ETHERTYPE_ARP) + fd = &iface->arp_fd; + else + fd = &iface->raw_fd; + if (*fd != -1) + close(*fd); + *fd = s; + return s; + +eexit: + close(s); + return -1; +} + +ssize_t +send_raw_packet(const struct interface *iface, int protocol, + const void *data, ssize_t len) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su; + int fd; + + memset(&su, 0, sizeof(su)); + su.sll.sll_family = AF_PACKET; + su.sll.sll_protocol = htons(protocol); + if (!(su.sll.sll_ifindex = if_nametoindex(iface->name))) { + errno = ENOENT; + return -1; + } + su.sll.sll_hatype = htons(iface->family); + su.sll.sll_halen = iface->hwlen; + if (iface->family == ARPHRD_INFINIBAND) + memcpy(&su.sll.sll_addr, + &ipv4_bcast_addr, sizeof(ipv4_bcast_addr)); + else + memset(&su.sll.sll_addr, 0xff, iface->hwlen); + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + + return sendto(fd, data, len, 0, &su.sa, sizeof(su)); +} + +ssize_t +get_raw_packet(struct interface *iface, int protocol, + void *data, ssize_t len, int *partialcsum) +{ + struct iovec iov = { + .iov_base = data, + .iov_len = len, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; +#ifdef PACKET_AUXDATA + unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; + struct cmsghdr *cmsg; + struct tpacket_auxdata *aux; +#endif + + ssize_t bytes; + int fd = -1; + +#ifdef PACKET_AUXDATA + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); +#endif + + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + bytes = recvmsg(fd, &msg, 0); + if (bytes == -1) + return errno == EAGAIN ? 0 : -1; + if (partialcsum != NULL) { + *partialcsum = 0; +#ifdef PACKET_AUXDATA + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == SOL_PACKET && + cmsg->cmsg_type == PACKET_AUXDATA) { + aux = (void *)CMSG_DATA(cmsg); + *partialcsum = aux->tp_status & + TP_STATUS_CSUMNOTREADY; + } + } +#endif + } + return bytes; +}
diff --git a/dhcpcd/mk/cc.mk b/dhcpcd/mk/cc.mk new file mode 100644 index 0000000..fcf0535 --- /dev/null +++ b/dhcpcd/mk/cc.mk
@@ -0,0 +1,28 @@ +# Copyright 2008 Roy Marples <roy@marples.name> + +# Setup some good default CFLAGS +CFLAGS?= -Os + +# Default to using the C99 standard +CSTD?= c99 +_CSTD_SH= if test -n "${CSTD}"; then echo "-std=${CSTD}"; else echo ""; fi +_CSTD!= ${_CSTD_SH} +CFLAGS+= ${_CSTD}$(shell ${_CSTD_SH}) + +# Try and use some good cc flags if we're building from git +_CCFLAGS= -pedantic -Wall -Wunused -Wimplicit -Wshadow -Wformat=2 \ + -Wmissing-declarations -Wno-missing-prototypes -Wwrite-strings \ + -Wbad-function-cast -Wnested-externs -Wcomment -Winline \ + -Wchar-subscripts -Wcast-align -Wno-format-nonliteral \ + -Wdeclaration-after-statement -Wsequence-point -Wextra +_CC_FLAGS_SH= if ! test -d .git; then echo ""; else for f in ${_CCFLAGS}; do \ + if ${CC} $$f -S -o /dev/null -xc /dev/null >/dev/null 2>&1; \ + then printf "%s" "$$f "; fi \ + done; fi +_CC_FLAGS!= ${_CC_FLAGS_SH} +CFLAGS+= ${_CC_FLAGS}$(shell ${_CC_FLAGS_SH}) + +_GGDB_SH= if test "${DEBUG}" = "yes"; then echo "-ggdb -DDEBUG"; else echo ""; fi +_GGDB!= ${_GGDB_SH} +GGDB= ${_GGDB}$(shell ${_GGDB_SH}) +CFLAGS+= ${GGDB}
diff --git a/dhcpcd/mk/depend.mk b/dhcpcd/mk/depend.mk new file mode 100644 index 0000000..a4d717a --- /dev/null +++ b/dhcpcd/mk/depend.mk
@@ -0,0 +1,11 @@ +# This only works for make implementations that always include a .depend if +# it exists. Only GNU make does not do this. + +# Copyright 2008 Roy Marples <roy@marples.name> + +CLEANFILES+= .depend + +.depend: ${SRCS} + ${CC} ${CFLAGS} -MM ${SRCS} > .depend + +depend: .depend
diff --git a/dhcpcd/mk/dist.mk b/dhcpcd/mk/dist.mk new file mode 100644 index 0000000..c4c1947 --- /dev/null +++ b/dhcpcd/mk/dist.mk
@@ -0,0 +1,31 @@ +# rules to make a distribution tarball from a svn repo +# Copyright 2008-2009 Roy Marples <roy@marples.name> + +GITREF?= HEAD +DISTPREFIX?= ${PROG}-${VERSION} +DISTFILE?= ${DISTPREFIX}.tar.bz2 + +CLEANFILES+= *.tar.bz2 + +_VERSION_SH= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' config.h +_VERSION!= ${_VERSION_SH} +VERSION= ${_VERSION}$(shell ${_VERSION_SH}) + +_SNAP_SH= date -u +%Y%m%d%H%M +_SNAP!= ${_SNAP_SH} +SNAP= ${_SNAP}$(shell ${_SNAP_SH}) +SNAPDIR= ${DISTPREFIX}-${SNAP} +SNAPFILE= ${SNAPDIR}.tar.bz2 + +dist: + git archive --prefix=${DISTPREFIX}/ ${GITREF} | bzip2 > ${DISTFILE} + +snapshot: + mkdir /tmp/${SNAPDIR} + cp -RPp * /tmp/${SNAPDIR} + (cd /tmp/${SNAPDIR}; make clean) + tar -cvjpf ${SNAPFILE} -C /tmp ${SNAPDIR} + rm -rf /tmp/${SNAPDIR} + ls -l ${SNAPFILE} + +snap: snapshot
diff --git a/dhcpcd/mk/files.mk b/dhcpcd/mk/files.mk new file mode 100644 index 0000000..0682b03 --- /dev/null +++ b/dhcpcd/mk/files.mk
@@ -0,0 +1,9 @@ +# Quick and dirty files +# Copyright 2008 Roy Marples <roy@marples.name> + +FILESDIR?= ${BINDIR} +FILESMODE?= ${NONBINMODE} + +_filesinstall: + ${INSTALL} -d ${DESTDIR}${FILESDIR} + ${INSTALL} -m ${FILESMODE} ${FILES} ${DESTDIR}${FILESDIR}
diff --git a/dhcpcd/mk/man.mk b/dhcpcd/mk/man.mk new file mode 100644 index 0000000..f1570bb --- /dev/null +++ b/dhcpcd/mk/man.mk
@@ -0,0 +1,25 @@ +# rules to install manpages +# Copyright 2008 Roy Marples <roy@marples.name> + +_MANPREFIX_SH= if [ -n "${PREFIX}" ]; then echo "${PREFIX}"; else echo "/usr/share"; fi +_MANPREFIX!= ${_MANPREFIX_SH} +MANPREFIX?= ${_MANPREFIX}$(shell ${_MANPREFIX_SH}) + +MANDIR?= ${MANPREFIX}/man/man +MANMODE?= 0444 + +_MAN5_SH= for man in ${MAN}; do case $$man in *.5) echo $$man;; esac; done +_MAN5!= ${_MAN5_SH} +MAN5= ${_MAN5}$(shell ${_MAN5_SH}) + +_MAN8_SH= for man in ${MAN}; do case $$man in *.8) echo $$man;; esac; done +_MAN8!= ${_MAN8_SH} +MAN8= ${_MAN8}$(shell ${_MAN8_SH}) + +_man: ${MAN} + +_maninstall: _man + ${INSTALL} -d ${DESTDIR}${MANDIR}5 + ${INSTALL} -m ${MANMODE} ${MAN5} ${DESTDIR}${MANDIR}5 + ${INSTALL} -d ${DESTDIR}${MANDIR}8 + ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}8
diff --git a/dhcpcd/mk/os-BSD.mk b/dhcpcd/mk/os-BSD.mk new file mode 100644 index 0000000..f9d3397 --- /dev/null +++ b/dhcpcd/mk/os-BSD.mk
@@ -0,0 +1,5 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +SRC_PF= bpf.c +SRC_IF= if-bsd.c
diff --git a/dhcpcd/mk/os-Darwin.mk b/dhcpcd/mk/os-Darwin.mk new file mode 100644 index 0000000..f2c3104 --- /dev/null +++ b/dhcpcd/mk/os-Darwin.mk
@@ -0,0 +1,5 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +include ${MK}/os-BSD.mk +LINK_RPATH= --rpath
diff --git a/dhcpcd/mk/os-Linux.mk b/dhcpcd/mk/os-Linux.mk new file mode 100644 index 0000000..2d316b1 --- /dev/null +++ b/dhcpcd/mk/os-Linux.mk
@@ -0,0 +1,8 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +SRC_PF= lpf.c +SRC_IF= if-linux.c + +CPPFLAGS+= -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +LIBRT= -lrt
diff --git a/dhcpcd/mk/os.mk b/dhcpcd/mk/os.mk new file mode 100644 index 0000000..f3426e4 --- /dev/null +++ b/dhcpcd/mk/os.mk
@@ -0,0 +1,7 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +_OS_SH= case `uname -s` in Linux) echo "Linux";; Darwin) echo "Darwin";; *) echo "BSD";; esac +_OS!= ${_OS_SH} +OS= ${_OS}$(shell ${_OS_SH}) +include ${MK}/os-${OS}.mk
diff --git a/dhcpcd/mk/prog.mk b/dhcpcd/mk/prog.mk new file mode 100644 index 0000000..d970b2d --- /dev/null +++ b/dhcpcd/mk/prog.mk
@@ -0,0 +1,68 @@ +# rules to build a program +# based on FreeBSD's bsd.prog.mk + +# Copyright 2008 Roy Marples <roy@marples.name> + +include ${MK}/cc.mk + +OBJS+= ${SRCS:.c=.o} + +# If building for /, ensure we use the libc in / if different from +# the default one in /usr/lib +LINK_RPATH?= -Wl,-rpath +_RPATH_SH= if test "${PREFIX}" = "" -o "${PREIX}" = "/"; then \ + echo "${LINK_RPATH}=${PREFIX}/${LIBNAME}"; \ + else \ + echo ""; \ + fi +_RPATH!= ${_RPATH_SH} +LDFLAGS+= ${_RPATH}$(shell ${_RPATH_SH}) + +# If building for /, ensure we use the linker in /libexec if different from +# the default one in /usr/libexec +_DYNLINK_SH= if test "${PREFIX}" = "" -o "${PREFIX}" = "/" && test -e /libexec/ld.elf_so; then \ + echo "-Wl,-dynamic-linker=/libexec/ld.elf_so"; \ + else \ + echo ""; \ + fi +_DYNLINK!= ${_DYNLINK_SH} +LDFLAGS+= ${_DYNLINK}$(shell ${_DYNLINK_SH}) + +all: ${PROG} ${SCRIPTS} _man + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +${PROG}: ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} + +# We could save about 600 bytes by building it like this +# instead of the more traditional method above +small: ${SRCS} + echo "" > _${PROG}.c + for src in ${SRCS}; do echo "#include \"$$src\"" >> _${PROG}.c; done + ${CC} ${CFLAGS} ${CPPFLAGS} -c _${PROG}.c -o _${PROG}.o + ${CC} ${LDFLAGS} -o ${PROG} _${PROG}.o ${LDADD} + +_proginstall: ${PROG} + ${INSTALL} -d ${DESTDIR}${BINDIR} + ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${BINDIR} + ${INSTALL} -d ${DESTDIR}${DBDIR} + +include ${MK}/depend.mk +include ${MK}/files.mk +include ${MK}/scripts.mk +include ${MK}/man.mk +include ${MK}/dist.mk + +install: _proginstall _scriptsinstall _filesinstall _maninstall + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done + +clean: + rm -f ${OBJS} ${PROG} _${PROG}.c _${PROG}.o ${PROG}.core ${CLEANFILES} + +LINTFLAGS?= -hx +LINTFLAGS+= -X 159,247,352 + +lint: ${SRCS:.c=.c} + ${LINT} ${LINTFLAGS} ${CFLAGS:M-[DIU]*} $^ ${.ALLSRC}
diff --git a/dhcpcd/mk/scripts.mk b/dhcpcd/mk/scripts.mk new file mode 100644 index 0000000..d295163 --- /dev/null +++ b/dhcpcd/mk/scripts.mk
@@ -0,0 +1,9 @@ +# Quick and dirty scripts +# Copyright 2008 Roy Marples <roy@marples.name> + +SCRIPTSDIR?= ${BINDIR} +SCRIPTSMODE?= ${BINMODE} + +_scriptsinstall: ${SCRIPTS} + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${SCRIPTSMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR}
diff --git a/dhcpcd/mk/sys.mk b/dhcpcd/mk/sys.mk new file mode 100644 index 0000000..81882ab --- /dev/null +++ b/dhcpcd/mk/sys.mk
@@ -0,0 +1,14 @@ +# Simple defaults + +BINDIR?= ${PREFIX}/usr/bin +BINMODE?= 0755 +NONBINMODE?= 0644 + +SYSCONFDIR?= ${PREFIX}/etc + +INSTALL?= install +SED?= sed + +_LIBNAME_SH= case `readlink /lib` in "") echo "lib";; *) basename `readlink /lib`;; esac +_LIBNAME!= ${_LIBNAME_SH} +LIBNAME?= ${_LIBNAME}$(shell ${_LIBNAME_SH})
diff --git a/dhcpcd/net.c b/dhcpcd/net.c new file mode 100644 index 0000000..1a9780d --- /dev/null +++ b/dhcpcd/net.c
@@ -0,0 +1,810 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_arp.h> +#ifdef AF_LINK +# include <net/if_dl.h> +# include <net/if_types.h> +#endif +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include <netinet/udp.h> +#undef __FAVOR_BSD +#ifdef AF_PACKET +# include <netpacket/packet.h> +#endif +#ifdef SIOCGIFMEDIA +# include <net/if_media.h> +#endif + +#include <ctype.h> +#include <errno.h> +#include <ifaddrs.h> +#include <fnmatch.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "if-options.h" +#include "ipv6rs.h" +#include "net.h" +#include "signals.h" + +static char hwaddr_buffer[(HWADDR_LEN * 3) + 1]; + +int socket_afnet = -1; + +#if defined(__FreeBSD__) && defined(DEBUG_MEMORY) +/* FreeBSD does not zero the struct, causing valgrind errors */ +unsigned int +if_nametoindex(const char *ifname) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(socket_afnet, SIOCGIFINDEX, &ifr) != -1) + return ifr.ifr_index; + return 0; +} +#endif + +int +inet_ntocidr(struct in_addr address) +{ + int cidr = 0; + uint32_t mask = htonl(address.s_addr); + + while (mask) { + cidr++; + mask <<= 1; + } + return cidr; +} + +int +inet_cidrtoaddr(int cidr, struct in_addr *addr) +{ + int ocets; + + if (cidr < 1 || cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (cidr + 7) / 8; + + addr->s_addr = 0; + if (ocets > 0) { + memset(&addr->s_addr, 255, (size_t)ocets - 1); + memset((unsigned char *)&addr->s_addr + (ocets - 1), + (256 - (1 << (32 - cidr) % 8)), 1); + } + + return 0; +} + +uint32_t +get_netmask(uint32_t addr) +{ + uint32_t dst; + + if (addr == 0) + return 0; + + dst = htonl(addr); + if (IN_CLASSA(dst)) + return ntohl(IN_CLASSA_NET); + if (IN_CLASSB(dst)) + return ntohl(IN_CLASSB_NET); + if (IN_CLASSC(dst)) + return ntohl(IN_CLASSC_NET); + + return 0; +} + +char * +hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen) +{ + char *p = hwaddr_buffer; + size_t i; + + for (i = 0; i < hwlen && i < HWADDR_LEN; i++) { + if (i > 0) + *p ++= ':'; + p += snprintf(p, 3, "%.2x", hwaddr[i]); + } + + *p ++= '\0'; + + return hwaddr_buffer; +} + +size_t +hwaddr_aton(unsigned char *buffer, const char *addr) +{ + char c[3]; + const char *p = addr; + unsigned char *bp = buffer; + size_t len = 0; + + c[2] = '\0'; + while (*p) { + c[0] = *p++; + c[1] = *p++; + /* Ensure that digits are hex */ + if (isxdigit((unsigned char)c[0]) == 0 || + isxdigit((unsigned char)c[1]) == 0) + { + errno = EINVAL; + return 0; + } + /* We should have at least two entries 00:01 */ + if (len == 0 && *p == '\0') { + errno = EINVAL; + return 0; + } + /* Ensure that next data is EOL or a seperator with data */ + if (!(*p == '\0' || (*p == ':' && *(p + 1) != '\0'))) { + errno = EINVAL; + return 0; + } + if (*p) + p++; + if (bp) + *bp++ = (unsigned char)strtol(c, NULL, 16); + len++; + } + return len; +} + +struct interface * +init_interface(const char *ifname) +{ + struct ifreq ifr; + struct interface *iface = NULL; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(socket_afnet, SIOCGIFFLAGS, &ifr) == -1) + goto eexit; + + 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; + } + + 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; + } + + 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; + +eexit: + free(iface); + iface = NULL; +exit: + return iface; +} + +void +free_interface(struct interface *iface) +{ + if (!iface) + return; + ipv6rs_free(iface); + if (iface->state) { + free_options(iface->state->options); + free(iface->state->old); + free(iface->state->new); + free(iface->state->offer); + free(iface->state); + } + free(iface->buffer); + free(iface->clientid); + free(iface); +} + +int +carrier_status(struct interface *iface) +{ + int ret; + struct ifreq ifr; +#ifdef SIOCGIFMEDIA + struct ifmediareq ifmr; +#endif +#ifdef __linux__ + char *p; +#endif + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only test the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + + 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 +up_interface(struct interface *iface) +{ + struct ifreq ifr; + int retval = -1; +#ifdef __linux__ + char *p; +#endif + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only bring the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if (ioctl(socket_afnet, SIOCGIFFLAGS, &ifr) == 0) { + if ((ifr.ifr_flags & IFF_UP)) + retval = 0; + else { + ifr.ifr_flags |= IFF_UP; + if (ioctl(socket_afnet, SIOCSIFFLAGS, &ifr) == 0) + retval = 0; + } + iface->flags = ifr.ifr_flags; + } + return retval; +} + +struct interface * +discover_interfaces(int argc, char * const *argv) +{ + struct ifaddrs *ifaddrs, *ifa; + char *p; + int i, sdl_type; + 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; + + 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 + + if (getifaddrs(&ifaddrs) == -1) + return NULL; + + 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 + } + + /* 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 + 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; + + /* 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); + } + + sdl_type = 0; + /* 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 + + sdl_type = sdl->sdl_type; + switch(sdl->sdl_type) { + case IFT_BRIDGE: /* FALLTHROUGH */ + case IFT_L2VLAN: /* FALLTHOUGH */ + case IFT_L3IPVLAN: /* FALLTHROUGH */ + case IFT_ETHER: + ifp->family = ARPHRD_ETHER; + break; + case IFT_IEEE1394: + ifp->family = ARPHRD_IEEE1394; + break; +#ifdef IFT_INFINIBAND + case IFT_INFINIBAND: + ifp->family = ARPHRD_INFINIBAND; + break; +#endif + } + 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 = sdl_type = sll->sll_hatype; + ifp->hwlen = sll->sll_halen; + if (ifp->hwlen != 0) + memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen); +#endif + } + + /* 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: unsupported interface type %.2x" + ", falling back to ethernet", + ifp->name, sdl_type); + ifp->family = ARPHRD_ETHER; + break; + } + } + + /* 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); + +#ifdef IFLR_ACTIVE + close(socket_aflink); +#endif + + return ifs; +} + +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; + + if (getifaddrs(&ifaddrs) == -1) + return -1; + + 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) { + /* TODO: Fix getifaddrs() */ + if ((ifa->ifa_flags & IFF_POINTOPOINT) && d) + 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 +do_mtu(const char *ifname, short int mtu) +{ + struct ifreq ifr; + int r; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + r = ioctl(socket_afnet, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); + if (r == -1) + return -1; + return ifr.ifr_mtu; +} + +void +free_routes(struct rt *routes) +{ + struct rt *r; + + while (routes) { + r = routes->next; + free(routes); + routes = r; + } +} + +int +open_udp_socket(struct interface *iface) +{ + int s; + 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) + return -1; + + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto eexit; +#ifdef SO_BINDTODEVICE + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + /* We can only bind to the real device */ + p = strchr(ifr.ifr_name, ':'); + if (p) + *p = '\0'; + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, + sizeof(ifr)) == -1) + goto eexit; +#endif + /* As we don't use this socket for receiving, set the + * receive buffer to 1 */ + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + goto eexit; + 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; + set_cloexec(s); + return 0; + +eexit: + close(s); + return -1; +} + +ssize_t +send_packet(const struct interface *iface, struct in_addr to, + const uint8_t *data, ssize_t len) +{ + 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 +{ + struct ip ip; + struct udphdr udp; + struct dhcp_message dhcp; +}; +const size_t udp_dhcp_len = sizeof(struct udp_dhcp_packet); + +static uint16_t +checksum(const void *data, uint16_t len) +{ + const uint8_t *addr = data; + uint32_t sum = 0; + + while (len > 1) { + sum += addr[0] * 256 + addr[1]; + addr += 2; + len -= 2; + } + + if (len == 1) + sum += *addr * 256; + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + sum = htons(sum); + + return ~sum; +} + +ssize_t +make_udp_packet(uint8_t **packet, const uint8_t *data, size_t length, + struct in_addr source, struct in_addr dest) +{ + struct udp_dhcp_packet *udpp; + struct ip *ip; + struct udphdr *udp; + + udpp = xzalloc(sizeof(*udpp)); + ip = &udpp->ip; + udp = &udpp->udp; + + /* OK, this is important :) + * We copy the data to our packet and then create a small part of the + * ip structure and an invalid ip_len (basically udp length). + * We then fill the udp structure and put the checksum + * of the whole packet into the udp checksum. + * Finally we complete the ip structure and ip checksum. + * If we don't do the ordering like so then the udp checksum will be + * broken, so find another way of doing it! */ + + memcpy(&udpp->dhcp, data, length); + + ip->ip_p = IPPROTO_UDP; + ip->ip_src.s_addr = source.s_addr; + if (dest.s_addr == 0) + ip->ip_dst.s_addr = INADDR_BROADCAST; + else + ip->ip_dst.s_addr = dest.s_addr; + + udp->uh_sport = htons(DHCP_CLIENT_PORT); + udp->uh_dport = htons(DHCP_SERVER_PORT); + udp->uh_ulen = htons(sizeof(*udp) + length); + ip->ip_len = udp->uh_ulen; + udp->uh_sum = checksum(udpp, sizeof(*udpp)); + + ip->ip_v = IPVERSION; + ip->ip_hl = sizeof(*ip) >> 2; + ip->ip_id = 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; + return sizeof(*ip) + sizeof(*udp) + length; +} + +ssize_t +get_udp_data(const uint8_t **data, const uint8_t *udp) +{ + struct udp_dhcp_packet packet; + + 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); +} + +int +valid_udp_packet(const uint8_t *data, size_t data_len, struct in_addr *from, + int noudpcsum) +{ + 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; + } + if (checksum(&packet.ip, sizeof(packet.ip)) != 0) { + errno = EINVAL; + return -1; + } + + bytes = ntohs(packet.ip.ip_len); + if (data_len < bytes) { + errno = EINVAL; + return -1; + } + + if (noudpcsum == 0) { + udpsum = packet.udp.uh_sum; + packet.udp.uh_sum = 0; + packet.ip.ip_hl = 0; + packet.ip.ip_v = 0; + packet.ip.ip_tos = 0; + packet.ip.ip_len = packet.udp.uh_ulen; + packet.ip.ip_id = 0; + packet.ip.ip_off = 0; + packet.ip.ip_ttl = 0; + packet.ip.ip_sum = 0; + if (udpsum && checksum(&packet, bytes) != udpsum) { + errno = EINVAL; + return -1; + } + } + + return 0; +}
diff --git a/dhcpcd/net.h b/dhcpcd/net.h new file mode 100644 index 0000000..f181798 --- /dev/null +++ b/dhcpcd/net.h
@@ -0,0 +1,152 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <limits.h> + +#include "config.h" +#include "dhcp.h" +#include "dhcpcd.h" + +#ifndef DUID_LEN +# define DUID_LEN 128 + 2 +#endif + +#define EUI64_ADDR_LEN 8 +#define INFINIBAND_ADDR_LEN 20 + +/* Linux 2.4 doesn't define this */ +#ifndef ARPHRD_IEEE1394 +# define ARPHRD_IEEE1394 24 +#endif + +/* The BSD's don't define this yet */ +#ifndef ARPHRD_INFINIBAND +# define ARPHRD_INFINIBAND 32 +#endif + + +/* Work out if we have a private address or not + * 10/8 + * 172.16/12 + * 192.168/16 + */ +#ifndef IN_PRIVATE +# define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \ + ((addr & 0xfff00000) == 0xac100000) || \ + ((addr & IN_CLASSB_NET) == 0xc0a80000)) +#endif + +#define LINKLOCAL_ADDR 0xa9fe0000 +#define LINKLOCAL_MASK IN_CLASSB_NET +#define LINKLOCAL_BRDC (LINKLOCAL_ADDR | ~LINKLOCAL_MASK) + +#ifndef IN_LINKLOCAL +# define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR) +#endif + +struct rt { + struct in_addr dest; + struct in_addr net; + struct in_addr gate; + const struct interface *iface; + int metric; + struct in_addr src; + struct rt *next; +}; + +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 *); + +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) + +int inet_ntocidr(struct in_addr); +int inet_cidrtoaddr(int, struct in_addr *); + +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 rt *rt, int); +#define add_route(rt) if_route(rt, 1) +#define change_route(rt) if_route(rt, 0) +#define del_route(rt) if_route(rt, -1) +#define del_src_route(rt) if_route(rt, -2); +void free_routes(struct rt *); + +int open_udp_socket(struct interface *); +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); +ssize_t get_udp_data(const uint8_t **, const uint8_t *); +int valid_udp_packet(const uint8_t *, size_t, struct in_addr *, int); + +int open_socket(struct interface *, int); +ssize_t send_packet(const struct interface *, struct in_addr, + const uint8_t *, ssize_t); +ssize_t send_raw_packet(const struct interface *, int, + const void *, ssize_t); +ssize_t get_raw_packet(struct interface *, int, void *, ssize_t, int *); + +int init_sockets(void); +int open_link_socket(void); +int manage_link(int); +int carrier_status(struct interface *); +#endif
diff --git a/dhcpcd/platform-bsd.c b/dhcpcd/platform-bsd.c new file mode 100644 index 0000000..afa4384 --- /dev/null +++ b/dhcpcd/platform-bsd.c
@@ -0,0 +1,89 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/utsname.h> +#include <netinet/in.h> + +#include <syslog.h> + +#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; +} + +static int +inet6_sysctl(int code) +{ + int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 }; + int val; + size_t size; + + mib[3] = code; + size = sizeof(val); + if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &val, &size, NULL, 0) == -1) + return -1; + return val; +} + +int +check_ipv6(const char *ifname) +{ + + /* BSD doesn't support these values per iface, so just reutrn 1 */ + if (ifname) + return 1; + + if (inet6_sysctl(IPV6CTL_ACCEPT_RTADV) != 1) { + syslog(LOG_WARNING, + "Kernel is not configured to accept IPv6 RAs"); + return 0; + } + if (inet6_sysctl(IPV6CTL_FORWARDING) != 0) { + syslog(LOG_WARNING, + "Kernel is configured as a router, not a host"); + return 0; + } + return 1; +}
diff --git a/dhcpcd/platform-linux.c b/dhcpcd/platform-linux.c new file mode 100644 index 0000000..119ec50 --- /dev/null +++ b/dhcpcd/platform-linux.c
@@ -0,0 +1,153 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#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; +} + +static int +check_proc_int(const char *path) +{ + FILE *fp; + char *buf; + + fp = fopen(path, "r"); + if (fp == NULL) + return -1; + buf = get_line(fp); + fclose(fp); + if (buf == NULL) + return -1; + return atoi(buf); +} + +static const char *prefix = "/proc/sys/net/ipv6/conf"; + +int +check_ipv6(const char *ifname) +{ + int r; + char path[256]; + + if (ifname == NULL) + ifname = "all"; + + snprintf(path, sizeof(path), "%s/%s/accept_ra", prefix, ifname); + r = check_proc_int(path); + if (r != 1 && r != 2) { + syslog(LOG_WARNING, + "%s: not configured to accept IPv6 RAs", ifname); + return 0; + } + + if (r != 2) { + snprintf(path, sizeof(path), "%s/%s/forwarding", + prefix, ifname); + if (check_proc_int(path) != 0) { + syslog(LOG_WARNING, + "%s: configured as a router, not a host", ifname); + return 0; + } + } + return 1; +}
diff --git a/dhcpcd/platform.h b/dhcpcd/platform.h new file mode 100644 index 0000000..08ec368 --- /dev/null +++ b/dhcpcd/platform.h
@@ -0,0 +1,34 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +char *hardware_platform(void); +int check_ipv6(const char *); + +#endif
diff --git a/dhcpcd/showlease.c b/dhcpcd/showlease.c new file mode 100644 index 0000000..1257337 --- /dev/null +++ b/dhcpcd/showlease.c
@@ -0,0 +1,355 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#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) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define IPV4 (1 << 6) +#define STRING (1 << 7) +#define PAIR (1 << 8) +#define ARRAY (1 << 9) +#define RFC3361 (1 << 10) +#define RFC3397 (1 << 11) +#define RFC3442 (1 << 12) + +struct dhcp_opt { + uint8_t option; + int type; + const char *var; +}; + +static const struct dhcp_opt const dhcp_opts[] = { + { 1, IPV4 | REQUEST, "subnet_mask" }, + { 2, UINT32, "time_offset" }, + { 3, IPV4 | ARRAY | REQUEST, "routers" }, + { 4, IPV4 | ARRAY, "time_servers" }, + { 5, IPV4 | ARRAY, "ien116_name_servers" }, + { 6, IPV4 | ARRAY, "domain_name_servers" }, + { 7, IPV4 | ARRAY, "log_servers" }, + { 8, IPV4 | ARRAY, "cookie_servers" }, + { 9, IPV4 | ARRAY, "lpr_servers" }, + { 10, IPV4 | ARRAY, "impress_servers" }, + { 11, IPV4 | ARRAY, "resource_location_servers" }, + { 12, STRING, "host_name" }, + { 13, UINT16, "boot_size" }, + { 14, STRING, "merit_dump" }, + { 15, STRING, "domain_name" }, + { 16, IPV4, "swap_server" }, + { 17, STRING, "root_path" }, + { 18, STRING, "extensions_path" }, + { 19, UINT8, "ip_forwarding" }, + { 20, UINT8, "non_local_source_routing" }, + { 21, IPV4 | ARRAY, "policy_filter" }, + { 22, SINT16, "max_dgram_reassembly" }, + { 23, UINT16, "default_ip_ttl" }, + { 24, UINT32, "path_mtu_aging_timeout" }, + { 25, UINT16 | ARRAY, "path_mtu_plateau_table" }, + { 26, UINT16, "interface_mtu" }, + { 27, UINT8, "all_subnets_local" }, + { 28, IPV4 | REQUEST, "broadcast_address" }, + { 29, UINT8, "perform_mask_discovery" }, + { 30, UINT8, "mask_supplier" }, + { 31, UINT8, "router_discovery" }, + { 32, IPV4, "router_solicitation_address" }, + { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, + { 34, UINT8, "trailer_encapsulation" }, + { 35, UINT32, "arp_cache_timeout" }, + { 36, UINT16, "ieee802_3_encapsulation" }, + { 37, UINT8, "default_tcp_ttl" }, + { 38, UINT32, "tcp_keepalive_interval" }, + { 39, UINT8, "tcp_keepalive_garbage" }, + { 40, STRING, "nis_domain" }, + { 41, IPV4 | ARRAY, "nis_servers" }, + { 42, IPV4 | ARRAY, "ntp_servers" }, + { 43, STRING, "vendor_encapsulated_options" }, + { 44, IPV4 | ARRAY, "netbios_name_servers" }, + { 45, IPV4, "netbios_dd_server" }, + { 46, UINT8, "netbios_node_type" }, + { 47, STRING, "netbios_scope" }, + { 48, IPV4 | ARRAY, "font_servers" }, + { 49, IPV4 | ARRAY, "x_display_manager" }, + { 50, IPV4, "dhcp_requested_address" }, + { 51, UINT32 | REQUEST, "dhcp_lease_time" }, + { 52, UINT8, "dhcp_option_overload" }, + { 53, UINT8, "dhcp_message_type" }, + { 54, IPV4, "dhcp_server_identifier" }, + { 55, UINT8 | ARRAY, "dhcp_parameter_request_list" }, + { 56, STRING, "dhcp_message" }, + { 57, UINT16, "dhcp_max_message_size" }, + { 58, UINT32 | REQUEST, "dhcp_renewal_time" }, + { 59, UINT32 | REQUEST, "dhcp_rebinding_time" }, + { 64, STRING, "nisplus_domain" }, + { 65, IPV4 | ARRAY, "nisplus_servers" }, + { 66, STRING, "tftp_server_name" }, + { 67, STRING, "bootfile_name" }, + { 68, IPV4 | ARRAY, "mobile_ip_home_agent" }, + { 69, IPV4 | ARRAY, "smtp_server" }, + { 70, IPV4 | ARRAY, "pop_server" }, + { 71, IPV4 | ARRAY, "nntp_server" }, + { 72, IPV4 | ARRAY, "www_server" }, + { 73, IPV4 | ARRAY, "finger_server" }, + { 74, IPV4 | ARRAY, "irc_server" }, + { 75, IPV4 | ARRAY, "streettalk_server" }, + { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, + { 77, STRING, "user_class" }, + { 85, IPV4 | ARRAY, "nds_servers" }, + { 86, STRING, "nds_tree_name" }, + { 87, STRING, "nds_context" }, + { 88, STRING | RFC3397, "bcms_controller_names" }, + { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 91, UINT32, "client_last_transaction_time" }, + { 92, IPV4 | ARRAY, "associated_ip" }, + { 98, STRING, "uap_servers" }, + { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 113, STRING, "netinfo_server_tag" }, + { 114, STRING, "default_url" }, + { 118, IPV4, "subnet_selection" }, + { 119, STRING | RFC3397, "domain_search" }, + { 121, RFC3442 | REQUEST, "classless_static_routes" }, + { 249, RFC3442, "ms-classless_static_routes" }, + { 0, 0, NULL } +}; + +struct dhcp_message * +get_lease_from_file(const char *leasefile) +{ + int fd; + struct dhcp_message *dhcp; + ssize_t bytes; + + fd = open(leasefile, O_RDONLY); + if (fd == -1) + return NULL; + dhcp = malloc(sizeof(*dhcp)); + memset(dhcp, 0, sizeof(*dhcp)); + bytes = read(fd, dhcp, sizeof(*dhcp)); + close(fd); + if (bytes < 0) { + free(dhcp); + dhcp = NULL; + } + return dhcp; +} + +static uint8_t *dhcp_opt_buffer = NULL; + +static int +valid_length(uint8_t option, int dl, int *type) +{ + const struct dhcp_opt *opt; + ssize_t sz; + + if (dl == 0) + return -1; + + for (opt = dhcp_opts; opt->option; opt++) { + if (opt->option != option) + continue; + + if (type) + *type = opt->type; + + if (opt->type == 0 || opt->type & STRING || opt->type & RFC3442) + return 0; + + sz = 0; + if (opt->type & UINT32 || opt->type & IPV4) + sz = sizeof(uint32_t); + if (opt->type & UINT16) + sz = sizeof(uint16_t); + if (opt->type & UINT8) + sz = sizeof(uint8_t); + if (opt->type & IPV4 || opt->type & ARRAY) + return dl % sz; + return (dl == sz ? 0 : -1); + } + + /* unknown option, so let it pass */ + return 0; +} + +static void +free_option_buffer(void) +{ + free(dhcp_opt_buffer); +} + + +#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL) +static const uint8_t * +get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + int bl = 0; + + while (p < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!dhcp_opt_buffer) { + dhcp_opt_buffer = malloc(sizeof(struct dhcp_message)); + atexit(free_option_buffer); + } + if (!bp) + bp = dhcp_opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = *p; + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (valid_length(o, bl, type) == -1) { + errno = EINVAL; + return NULL; + } + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)&dhcp_opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_addr32(uint32_t *a, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + + if (!p) + return -1; + memcpy(a, p, sizeof(*a)); + return 0; +} + +int +get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + uint32_t a; + + if (get_option_addr32(&a, dhcp, option) == -1) + return -1; + + *i = ntohl(a); + return 0; +} + +uint32_t +get_netmask(uint32_t addr) +{ + uint32_t dst; + + if (addr == 0) + return 0; + + dst = htonl(addr); + if (IN_CLASSA(dst)) + return ntohl(IN_CLASSA_NET); + if (IN_CLASSB (dst)) + return ntohl(IN_CLASSB_NET); + if (IN_CLASSC (dst)) + return ntohl(IN_CLASSC_NET); + + return 0; +} + +void showlease(struct dhcp_lease *lease) +{ + printf("addr: %s\n", inet_ntoa(lease->addr)); + printf("net: %s\n", inet_ntoa(lease->net)); + printf("leasetime: %d\n", lease->leasetime); + printf("renew: %d\n", lease->renewaltime); + printf("rebind: %d\n", lease->rebindtime); + printf("server: %s\n", inet_ntoa(lease->server)); +} +#define MAX_LEASETIME 2147460 + +int +main(int argc, char *argv[]) +{ + struct dhcp_message *dhcp; + struct dhcp_lease *lease; + char leasefile[PATH_MAX]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <interface>\n", argv[0]); + exit(1); + } + snprintf(leasefile, PATH_MAX, LEASEFILE, argv[1]); + if ((dhcp = get_lease_from_file(leasefile)) == NULL) { + fprintf(stderr, "Couldn't read lease file: %s\n", strerror(errno)); + exit(1); + } + lease = malloc(sizeof(*lease)); + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + + if (get_option_addr32(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = get_netmask(dhcp->yiaddr); + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) != 0) + lease->leasetime = DEFAULT_LEASETIME; + get_option_addr32(&lease->server.s_addr, dhcp, DHO_SERVERID); + /* Dm: limit lease time value to avoid negative numbers when + converting to milliseconds */ + if ((lease->leasetime != ~0U) && (lease->leasetime > MAX_LEASETIME)) + lease->leasetime = MAX_LEASETIME; + 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; + showlease(lease); + free(lease); + return 0; +}
diff --git a/dhcpcd/signals.c b/dhcpcd/signals.c new file mode 100644 index 0000000..fd3b0c3 --- /dev/null +++ b/dhcpcd/signals.c
@@ -0,0 +1,124 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2009 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "common.h" +#include "signals.h" + +static int signal_pipe[2]; + +static const int handle_sigs[] = { + SIGALRM, + SIGHUP, + SIGINT, + SIGPIPE, + SIGTERM, + SIGUSR1, +}; + +static void +signal_handler(int sig) +{ + int serrno = errno; + + if (write(signal_pipe[1], &sig, sizeof(sig)) != sizeof(sig)) + syslog(LOG_ERR, "failed to write signal %d: %m", sig); + /* Restore errno */ + errno = serrno; +} + +/* 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 */ +int +signal_read(void) +{ + int sig = -1; + char buf[16]; + ssize_t bytes; + + memset(buf, 0, sizeof(buf)); + bytes = read(signal_pipe[0], buf, sizeof(buf)); + if (bytes >= 0 && (size_t)bytes >= sizeof(sig)) + memcpy(&sig, buf, sizeof(sig)); + return sig; +} + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +int +signal_init(void) +{ + if (pipe(signal_pipe) == -1) + return -1; + /* Don't block on read */ + if (set_nonblock(signal_pipe[0]) == -1) + return -1; + /* Stop any scripts from inheriting us */ + if (set_cloexec(signal_pipe[0]) == -1) + return -1; + if (set_cloexec(signal_pipe[1]) == -1) + return -1; + return signal_pipe[0]; +} + +static int +signal_handle(void (*func)(int)) +{ + unsigned int i; + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = func; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < sizeof(handle_sigs) / sizeof(handle_sigs[0]); i++) + if (sigaction(handle_sigs[i], &sa, NULL) == -1) + return -1; + return 0; +} + +int +signal_setup(void) +{ + return signal_handle(signal_handler); +} + +int +signal_reset(void) +{ + return signal_handle(SIG_DFL); +} +
diff --git a/dhcpcd/signals.h b/dhcpcd/signals.h new file mode 100644 index 0000000..7098cfb --- /dev/null +++ b/dhcpcd/signals.h
@@ -0,0 +1,36 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SIGNAL_H +#define SIGNAL_H + +int signal_init(void); +int signal_setup(void); +int signal_reset(void); +int signal_read(void); + +#endif