From e95877ecfa1170d77b1ec1f66752725cdda01b64 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- Android.mk | 59 ++ Makefile | 51 ++ README | 69 ++ android.conf | 6 + bpf-filter.h | 107 +++ bpf.c | 205 ++++++ client.c | 1545 +++++++++++++++++++++++++++++++++++++++ client.h | 35 + common.c | 304 ++++++++ common.h | 86 +++ config.h | 102 +++ configure.c | 496 +++++++++++++ configure.h | 41 ++ dhcp.c | 1218 ++++++++++++++++++++++++++++++ dhcp.h | 185 +++++ dhcpcd-hooks/01-test | 7 + dhcpcd-hooks/10-mtu | 5 + dhcpcd-hooks/20-dns.conf | 32 + dhcpcd-hooks/20-resolv.conf | 40 + dhcpcd-hooks/29-lookup-hostname | 33 + dhcpcd-hooks/30-hostname | 21 + dhcpcd-hooks/50-dhcpcd-compat | 31 + dhcpcd-hooks/50-ntp.conf | 51 ++ dhcpcd-hooks/50-yp.conf | 48 ++ dhcpcd-hooks/90-NetworkManager | 7 + dhcpcd-hooks/95-configured | 22 + dhcpcd-hooks/Makefile | 11 + dhcpcd-run-hooks | 38 + dhcpcd-run-hooks.8 | 105 +++ dhcpcd-run-hooks.8.in | 105 +++ dhcpcd-run-hooks.in | 38 + dhcpcd.8 | 384 ++++++++++ dhcpcd.8.in | 384 ++++++++++ dhcpcd.c | 1041 ++++++++++++++++++++++++++ dhcpcd.conf | 13 + dhcpcd.conf.5 | 137 ++++ dhcpcd.conf.5.in | 137 ++++ dhcpcd.h | 98 +++ if-bsd.c | 187 +++++ if-linux.c | 328 +++++++++ logger.c | 118 +++ logger.h | 44 ++ lpf.c | 177 +++++ mk/cc.mk | 28 + mk/depend.mk | 11 + mk/dist.mk | 31 + mk/files.mk | 9 + mk/man.mk | 25 + mk/os-BSD.mk | 5 + mk/os-Linux.mk | 28 + mk/os.mk | 7 + mk/prog.mk | 67 ++ mk/scripts.mk | 9 + mk/sys.mk | 14 + net.c | 633 ++++++++++++++++ net.h | 172 +++++ signals.c | 137 ++++ signals.h | 40 + 58 files changed, 9367 insertions(+) create mode 100644 Android.mk create mode 100644 Makefile create mode 100644 README create mode 100644 android.conf create mode 100644 bpf-filter.h create mode 100644 bpf.c create mode 100644 client.c create mode 100644 client.h create mode 100644 common.c create mode 100644 common.h create mode 100644 config.h create mode 100644 configure.c create mode 100644 configure.h create mode 100644 dhcp.c create mode 100644 dhcp.h create mode 100644 dhcpcd-hooks/01-test create mode 100644 dhcpcd-hooks/10-mtu create mode 100644 dhcpcd-hooks/20-dns.conf create mode 100644 dhcpcd-hooks/20-resolv.conf create mode 100644 dhcpcd-hooks/29-lookup-hostname create mode 100644 dhcpcd-hooks/30-hostname create mode 100644 dhcpcd-hooks/50-dhcpcd-compat create mode 100644 dhcpcd-hooks/50-ntp.conf create mode 100644 dhcpcd-hooks/50-yp.conf create mode 100644 dhcpcd-hooks/90-NetworkManager create mode 100644 dhcpcd-hooks/95-configured create mode 100644 dhcpcd-hooks/Makefile create mode 100755 dhcpcd-run-hooks create mode 100644 dhcpcd-run-hooks.8 create mode 100644 dhcpcd-run-hooks.8.in create mode 100644 dhcpcd-run-hooks.in create mode 100644 dhcpcd.8 create mode 100644 dhcpcd.8.in create mode 100644 dhcpcd.c create mode 100644 dhcpcd.conf create mode 100644 dhcpcd.conf.5 create mode 100644 dhcpcd.conf.5.in create mode 100644 dhcpcd.h create mode 100644 if-bsd.c create mode 100644 if-linux.c create mode 100644 logger.c create mode 100644 logger.h create mode 100644 lpf.c create mode 100644 mk/cc.mk create mode 100644 mk/depend.mk create mode 100644 mk/dist.mk create mode 100644 mk/files.mk create mode 100644 mk/man.mk create mode 100644 mk/os-BSD.mk create mode 100644 mk/os-Linux.mk create mode 100644 mk/os.mk create mode 100644 mk/prog.mk create mode 100644 mk/scripts.mk create mode 100644 mk/sys.mk create mode 100644 net.c create mode 100644 net.h create mode 100644 signals.c create mode 100644 signals.h diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..b1e0e55 --- /dev/null +++ b/Android.mk @@ -0,0 +1,59 @@ +# Copyright 2006 The Android Open Source Project +ifneq ($(TARGET_SIMULATOR),true) +LOCAL_PATH:= $(call my-dir) + +etc_dir := $(TARGET_OUT)/etc/dhcpcd +hooks_dir := dhcpcd-hooks +hooks_target := $(etc_dir)/$(hooks_dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := common.c dhcp.c dhcpcd.c logger.c net.c \ + signals.c configure.c client.c if-linux.c lpf.c +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +LOCAL_CFLAGS := -DDISABLE_ARP +LOCAL_SHARED_LIBRARIES := libc +LOCAL_MODULE = dhcpcd +LOCAL_MODULE_TAGS := user development +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_MODULE := dhcpcd.conf +LOCAL_MODULE_TAGS := user development +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_TAGS := user development +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(etc_dir) +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := 01-test +LOCAL_MODULE_TAGS := user development +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(hooks_target) +LOCAL_SRC_FILES := $(hooks_dir)/$(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := 20-dns.conf +LOCAL_MODULE_TAGS := user development +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_TAGS := user development +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(hooks_target) +LOCAL_SRC_FILES := $(hooks_dir)/$(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f93d256 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +# Makefile based on BSD make. +# Our mk stubs also work with GNU make. +# Copyright 2008 Roy Marples + +PROG= dhcpcd +SRCS= common.c dhcp.c dhcpcd.c logger.c net.c signals.c +SRCS+= configure.c client.c +SRCS+= ${SRC_IF} ${SRC_PF} + +LIBEXECDIR?= ${PREFIX}/system/etc/dhcpcd +SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks +HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks + +BINDIR= ${PREFIX}/sbin +DBDIR= /data/misc/dhcp +SYSCONFDIR?= ${PREFIX}/system/etc/dhcpcd + +MAN= dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8 +CLEANFILES= dhcpcd.conf.5 dhcpcd.8 dhcpcd-run-hooks.8 + +SCRIPTS= dhcpcd-run-hooks +SCRIPTSDIR= ${LIBEXECDIR} +CLEANFILES+= dhcpcd-run-hooks + +FILES= dhcpcd.conf +FILESDIR= ${SYSCONFDIR} + +CPPFLAGS+= -DDBDIR=\"${DBDIR}\" +CPPFLAGS+= -DSCRIPT=\"${SCRIPT}\" +CPPFLAGS+= -DSYSCONFDIR=\"${SYSCONFDIR}\" +LDADD+= ${LIBRT} + +SUBDIRS= dhcpcd-hooks + +.SUFFIXES: .in .sh.in + +SED_DBDIR= -e 's:@DBDIR@:${DBDIR}:g' +SED_HOOKDIR= -e 's:@HOOKDIR@:${HOOKDIR}:g' +SED_SCRIPT= -e 's:@SCRIPT@:${SCRIPT}:g' +SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' + +.in: + ${SED} ${SED_DBDIR} ${SED_HOOKDIR} ${SED_SCRIPT} ${SED_SYS} $< > $@ + +.sh.in.sh: + ${SED} ${SED_HOOKDIR} ${SED_SCRIPT} ${SED_SYS} $< > $@ + +MK= mk +include ${MK}/os.mk +include ${MK}/sys.mk +include ${MK}/prog.mk diff --git a/README b/README new file mode 100644 index 0000000..27aadf0 --- /dev/null +++ b/README @@ -0,0 +1,69 @@ +dhcpcd-4 - DHCP client daemon +Copyright 2006-2008 Roy Marples + + +Installation +------------ +Edit config.h to match your building requirements. +Then just make; make install +man dhcpcd for command line options + + +Notes +----- +If you're cross compiling you may need to set the below knobs to avoid +automatic tests. +OS=BSD | Linux + +If size is your thing, you can remove all non-essential userland options +by adding -DMINIMAL to your CPPFLAGS. This currently shaves off around 6k. +You can save a futher 600 bytes or so by using the small make target. + +If you're building for a NOMMU system where fork() does not work, you should +add -DTHERE_IS_NO_FORK to your CPPFLAGS. + +You can change the default dir with these knobs. +For example, to satisfy FHS compliance you would do this:- +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. + + +Hooks +----- +Not all the hooks in dhcpcd-hooks are installed by default. +By default we install 01-test, 10-mtu, 20-resolv.conf and 30-hostname. +To add more simply add them in the HOOKSCRIPTS variable. +make HOOKSCRIPTS=50-ntp install + + +Compatibility +------------- +If you require compatibility with dhcpcd-3 and older style variables, +you can install 50-dhcpcd-compat into the directory $LIBEXECDIR/dhcpcd-hooks +We don't install this by default. +You should also add -DCMDLINE_COMPAT to your CPPFLAGS if you need to be fully +commandline compatible with prior versions. + +dhcpcd-3 enabled DUID support by default - this has changed in dhcpcd-4. +You can enable it via the --duid, -D command line option or by using the +duid directive in dhcpcd.conf. +If CMDLINE_COMPAT is defined the we renable DUID support by default IF +the dhcpcd.duid file exits. This keeps the clients working as they were, +which is good. + +dhcpcd-4 is NOT fully commandline compatible with dhcpcd-2 and older and +changes the meaning of some options. + + +ChangeLog +--------- +We no longer supply a ChangeLog. +However, you're more than welcome to read the git commit comments at +http://git.marples.name/?p=dhcpcd/.git;a=summary diff --git a/android.conf b/android.conf new file mode 100644 index 0000000..5aa418d --- /dev/null +++ b/android.conf @@ -0,0 +1,6 @@ +# dhcpcd configuration for Android Wi-Fi interface +# See dhcpcd.conf(5) for details. + +interface tiwlan0 +# dhcpcd-run-hooks uses these options. +option subnet_mask, routers, domain_name_servers diff --git a/bpf-filter.h b/bpf-filter.h new file mode 100644 index 0000000..adcc8bb --- /dev/null +++ b/bpf-filter.h @@ -0,0 +1,107 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#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 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 + * + * 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/bpf.c b/bpf.c new file mode 100644 index 0000000..f015344 --- /dev/null +++ b/bpf.c @@ -0,0 +1,205 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "logger.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) { + logger(LOG_ERR, "BPF version mismatch - recompile " PACKAGE); + 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) { +#ifdef ENABLE_ARP + pf.bf_insns = UNCONST(arp_bpf_filter); + pf.bf_len = arp_bpf_filter_len; + fdp = &iface->arp_fd; +#endif + } else { + pf.bf_insns = UNCONST(dhcp_bpf_filter); + pf.bf_len = dhcp_bpf_filter_len; + fdp = &iface->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; + + 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; + return writev(iface->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 fd = -1; + struct bpf_hdr packet; + ssize_t bytes; + const unsigned char *payload; + + if (protocol == ETHERTYPE_ARP) { +#ifdef ENABLE_ARP + fd = iface->arp_fd; +#endif + } else + fd = iface->fd; + + 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/client.c b/client.c new file mode 100644 index 0000000..99d0fa8 --- /dev/null +++ b/client.c @@ -0,0 +1,1545 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#ifdef __linux__ +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "client.h" +#include "configure.h" +#include "dhcp.h" +#include "dhcpcd.h" +#include "net.h" +#include "logger.h" +#include "signals.h" + +#ifdef ENABLE_IPV4LL +# ifndef ENABLE_ARP + # error "IPv4LL requires ENABLE_ARP to work" +# endif +# define IPV4LL_LEASETIME 2 +#endif + +/* Some platforms don't define INFTIM */ +#ifndef INFTIM +# define INFTIM -1 +#endif + +#define STATE_INIT 0 +#define STATE_DISCOVERING 1 +#define STATE_REQUESTING 2 +#define STATE_BOUND 3 +#define STATE_RENEWING 4 +#define STATE_REBINDING 5 +#define STATE_REBOOT 6 +#define STATE_RENEW_REQUESTED 7 +#define STATE_PROBING 8 +#define STATE_ANNOUNCING 9 + +/* Constants taken from RFC 2131. + * We multiply some numbers by 1000 so they are suitable for use in poll(). */ +#define T1 0.5 +#define T2 0.875 +#define DHCP_BASE 4 * 1000 +#define DHCP_RAND_MIN -1 * 1000 +#define DHCP_RAND_MAX 1 * 1000 +#define DHCP_MAX 64 * 1000 + +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 + +#define SOCKET_CLOSED 0 +#define SOCKET_OPEN 1 + +/* Indexes for pollfds */ +#define POLLFD_SIGNAL 0 +#define POLLFD_IFACE 1 +#define POLLFD_ARP 2 + +/* These are really for IPV4LL, RFC 3927. + * We multiply some numbers by 1000 so they are suitable for use in poll(). */ +#define PROBE_WAIT 1 * 1000 +#define PROBE_NUM 3 +#define PROBE_MIN 1 * 1000 +#define PROBE_MAX 2 * 1000 +#define ANNOUNCE_WAIT 2 * 1000 +#define ANNOUNCE_NUM 2 +#define ANNOUNCE_INTERVAL 2 * 1000 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + +struct if_state { + int options; + struct interface *interface; + struct dhcp_message *offer; + struct dhcp_message *new; + struct dhcp_message *old; + struct dhcp_lease lease; + struct timeval start; + struct timeval stop; + int state; + int messages; + long timeout; + time_t nakoff; + uint32_t xid; + int socket; + int *pid_fd; + int signal_fd; +#ifdef ENABLE_ARP + int probes; + int claims; + int conflicts; + time_t defend; + struct in_addr fail; +#endif +}; + +struct dhcp_op { + uint8_t value; + const char *name; +}; + +static const struct dhcp_op const dhcp_ops[] = { + { DHCP_DISCOVER, "DHCP_DISCOVER" }, + { DHCP_OFFER, "DHCP_OFFER" }, + { DHCP_REQUEST, "DHCP_REQUEST" }, + { DHCP_DECLINE, "DHCP_DECLINE" }, + { DHCP_ACK, "DHCP_ACK" }, + { DHCP_NAK, "DHCP_NAK" }, + { DHCP_RELEASE, "DHCP_RELEASE" }, + { DHCP_INFORM, "DHCP_INFORM" }, + { 0, NULL } +}; + +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 int +daemonise(struct if_state *state, const struct options *options) +{ + pid_t pid; + sigset_t full; + sigset_t old; +#ifdef THERE_IS_NO_FORK + char **argv; + int i; +#else + char buf = '\0'; + int sidpipe[2]; +#endif + + if (state->options & DHCPCD_DAEMONISED || + !(options->options & DHCPCD_DAEMONISE)) + return 0; + + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + +#ifndef THERE_IS_NO_FORK + /* Setup a signal pipe so parent knows when to exit. */ + if (pipe(sidpipe) == -1) { + logger(LOG_ERR,"pipe: %s", strerror(errno)); + return -1; + } + + logger(LOG_DEBUG, "forking to background"); + switch (pid = fork()) { + case -1: + logger(LOG_ERR, "fork: %s", strerror(errno)); + exit(EXIT_FAILURE); + /* NOTREACHED */ + case 0: + setsid(); + /* Notify parent it's safe to exit as we've detached. */ + close(sidpipe[0]); + write(sidpipe[1], &buf, 1); + close(sidpipe[1]); + close_fds(); + break; + default: + /* Reset signals as we're the parent about to exit. */ + signal_reset(); + /* Wait for child to detach */ + close(sidpipe[1]); + read(sidpipe[0], &buf, 1); + close(sidpipe[0]); + break; + } +#else + logger(LOG_INFO, "forking to background"); + + /* We need to add --daemonise to our options */ + argv = xmalloc(sizeof(char *) * (dhcpcd_argc + 4)); + argv[0] = dhcpcd; + for (i = 1; i < dhcpcd_argc; i++) + argv[i] = dhcpcd_argv[i]; + argv[i] = (char *)"--daemonised"; + if (dhcpcd_skiproutes) { + argv[++i] = (char *)"--skiproutes"; + argv[++i] = dhcpcd_skiproutes; + } + argv[i + 1] = NULL; + + switch (pid = vfork()) { + case -1: + logger(LOG_ERR, "vfork: %s", strerror(errno)); + _exit(EXIT_FAILURE); + case 0: + signal_reset(); + sigprocmask(SIG_SETMASK, &old, NULL); + execvp(dhcpcd, argv); + /* Must not use stdio here. */ + write(STDERR_FILENO, "exec failed\n", 12); + _exit(EXIT_FAILURE); + } + + free(argv); +#endif + + /* Done with the fd now */ + if (pid != 0) { + writepid(*state->pid_fd, pid); + close(*state->pid_fd); + *state->pid_fd = -1; + } + + sigprocmask(SIG_SETMASK, &old, NULL); + + state->state = STATE_BOUND; + if (pid == 0) { + state->options |= DHCPCD_DAEMONISED; + return 0; + } + + state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED; + return -1; +} + +#ifndef MINIMAL +#define THIRTY_YEARS_IN_SECONDS 946707779 +static 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, l = 0; + char *buffer = NULL, *line, *option; + + /* 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 ((get_line(&buffer, &len, f))) { + line = buffer; + while ((option = strsep(&line, " \t"))) + if (*option != '\0') + break; + if (!option || *option == '\0' || *option == '#') + continue; + l = hwaddr_aton(NULL, option); + if (l && l <= DUID_LEN) { + hwaddr_aton(duid, option); + break; + } + l = 0; + } + fclose(f); + free(buffer); + if (l) + return l; + } 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; +} +#endif + +#ifdef ENABLE_IPV4LL +static struct dhcp_message* +ipv4ll_get_dhcp(uint32_t old_addr) +{ + uint32_t u32; + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = xzalloc(sizeof(*dhcp)); + /* Put some LL options in */ + p = dhcp->options; + *p++ = DHCP_SUBNETMASK; + *p += sizeof(u32); + u32 = LINKLOCAL_MASK; + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + *p++ = DHCP_BROADCAST; + *p += sizeof(u32); + u32 = LINKLOCAL_BRDC; + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + *p++ = DHCP_END; + + for (;;) { + dhcp->yiaddr = htonl(LINKLOCAL_ADDR | + (((uint32_t)abs((int)arc4random()) + % 0xFD00) + 0x0100)); + if (dhcp->yiaddr != old_addr) + break; + } + return dhcp; +} +#endif + +static void +get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) +{ + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + + if (get_option_addr(&lease->net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) + lease->net.s_addr = get_netmask(dhcp->yiaddr); + if (get_option_uint32(&lease->leasetime, dhcp, DHCP_LEASETIME) != 0) + lease->leasetime = DEFAULT_LEASETIME; + if (get_option_uint32(&lease->renewaltime, dhcp, DHCP_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(&lease->rebindtime, dhcp, DHCP_REBINDTIME) != 0) + lease->rebindtime = 0; +} + +static int +get_old_lease(struct if_state *state) +{ + struct interface *iface = state->interface; + struct dhcp_lease *lease = &state->lease; + struct dhcp_message *dhcp; + struct timeval tv; + unsigned int offset = 0; + struct stat sb; + + if (!IN_LINKLOCAL(ntohl(iface->addr.s_addr))) + logger(LOG_INFO, "trying to use old lease in `%s'", + iface->leasefile); + if ((dhcp = read_lease(iface)) == NULL) { + if (errno != ENOENT) + logger(LOG_INFO, "read_lease: %s", strerror(errno)); + goto eexit; + } + if (stat(iface->leasefile, &sb) == -1) { + logger(LOG_ERR, "stat: %s", strerror(errno)); + goto eexit; + } + get_lease(&state->lease, dhcp); + lease->frominfo = 1; + lease->leasedfrom = sb.st_mtime; + + /* Vitaly important we remove the server information here */ + state->lease.server.s_addr = 0; + dhcp->servername[0] = '\0'; + + if (!IN_LINKLOCAL(ntohl(dhcp->yiaddr))) { +#ifndef THERE_IS_NO_FORK + if (!(state->options & DHCPCD_LASTLEASE)) + goto eexit; +#endif + + /* Ensure that we can still use the lease */ + if (gettimeofday(&tv, NULL) == -1) { + logger(LOG_ERR, "gettimeofday: %s", strerror(errno)); + goto eexit; + } + + offset = tv.tv_sec - lease->leasedfrom; + if (lease->leasedfrom && + tv.tv_sec - lease->leasedfrom > lease->leasetime) + { + logger(LOG_ERR, "lease expired %u seconds ago", + offset + lease->leasetime); + /* Persistent interfaces should still try and use the + * lease if we can't contact a DHCP server. + * We just set the timeout to 1 second. */ + if (state->options & DHCPCD_PERSISTENT) + offset = lease->renewaltime - 1; + else + goto eexit; + } + } + + if (lease->leasedfrom == 0) + offset = 0; + state->timeout = lease->renewaltime - offset; + iface->start_uptime = uptime(); + free(state->old); + state->old = state->new; + state->new = NULL; + state->offer = dhcp; + return 0; + +eexit: + lease->addr.s_addr = 0; + free(dhcp); + return -1; +} + +static int +client_setup(struct if_state *state, const struct options *options) +{ + struct interface *iface = state->interface; + struct dhcp_lease *lease = &state->lease; + struct in_addr addr; +#ifndef MINIMAL + size_t len = 0; + unsigned char *duid = NULL; + uint32_t ul; +#endif + + state->state = STATE_INIT; + state->nakoff = 1; + state->options = options->options; + + if (options->request_address.s_addr == 0 && + (options->options & DHCPCD_INFORM || + options->options & DHCPCD_REQUEST || + options->options & DHCPCD_DAEMONISED)) + { + if (get_old_lease(state) != 0) + return -1; + state->timeout = 0; + + if (!(options->options & DHCPCD_DAEMONISED) && + IN_LINKLOCAL(ntohl(lease->addr.s_addr))) + { + logger(LOG_ERR, "cannot request a link local address"); + return -1; + } +#ifdef THERE_IS_NO_FORK + if (options->options & DHCPCD_DAEMONISED) { + state->state = STATE_BOUND; + state->timeout = state->lease.renewaltime; + iface->addr.s_addr = lease->addr.s_addr; + iface->net.s_addr = lease->net.s_addr; + get_option_addr(&lease->server.s_addr, + state->offer, DHCP_SERVERID); + } +#endif + } else { + lease->addr.s_addr = options->request_address.s_addr; + lease->net.s_addr = options->request_netmask.s_addr; + } + + /* If INFORMing, ensure the interface has the address */ + if (state->options & DHCPCD_INFORM && + has_address(iface->name, &lease->addr, &lease->net) < 1) + { + addr.s_addr = lease->addr.s_addr | ~lease->net.s_addr; + logger(LOG_DEBUG, "adding IP address %s/%d", + inet_ntoa(lease->addr), inet_ntocidr(lease->net)); + if (add_address(iface->name, &lease->addr, + &lease->net, &addr) == -1) + { + logger(LOG_ERR, "add_address: %s", strerror(errno)); + return -1; + } + iface->addr.s_addr = lease->addr.s_addr; + iface->net.s_addr = lease->net.s_addr; + } + +#ifndef MINIMAL + if (*options->clientid) { + iface->clientid = xmalloc(options->clientid[0] + 1); + memcpy(iface->clientid, + options->clientid, options->clientid[0] + 1); + } else if (options->options & DHCPCD_CLIENTID) { + if (options->options & DHCPCD_DUID) { + duid = xmalloc(DUID_LEN); + if ((len = get_duid(duid, iface)) == 0) + logger(LOG_ERR, "get_duid: %s", + strerror(errno)); + } + + if (len > 0) { + logger(LOG_INFO, "DUID = %s", + hwaddr_ntoa(duid, len)); + + iface->clientid = xmalloc(len + 6); + iface->clientid[0] = len + 5; + iface->clientid[1] = 255; /* RFC 4361 */ + + /* IAID is 4 bytes, so if the iface name is 4 bytes + * or less, use it */ + ul = strlen(iface->name); + if (ul < 5) { + memcpy(iface->clientid + 2, iface->name, ul); + if (ul < 4) + memset(iface->clientid + 2 + ul, + 0, 4 - ul); + } else { + /* Name isn't 4 bytes, so use the index */ + ul = htonl(if_nametoindex(iface->name)); + memcpy(iface->clientid + 2, &ul, 4); + } + + memcpy(iface->clientid + 6, duid, len); + free(duid); + } + 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); + } + } +#endif + + return 0; +} + +static int +do_socket(struct if_state *state, int mode) +{ + if (state->interface->fd >= 0) { + close(state->interface->fd); + state->interface->fd = -1; + } + if (mode == SOCKET_CLOSED && state->interface->udp_fd >= 0) { + close(state->interface->udp_fd); + state->interface->udp_fd = -1; + } + + /* We need to bind to a port, otherwise we generate ICMP messages + * that cannot connect the port when we have an address. + * We don't actually use this fd at all, instead using our packet + * filter socket. */ + if (mode == SOCKET_OPEN && + state->interface->udp_fd == -1 && + state->lease.addr.s_addr != 0) + if (open_udp_socket(state->interface) == -1) { + logger(LOG_ERR, "open_udp_socket: %s", strerror(errno)); + return -1; + } + + if (mode == SOCKET_OPEN) + if (open_socket(state->interface, ETHERTYPE_IP) == -1) { + logger(LOG_ERR, "open_socket: %s", strerror(errno)); + return -1; + } + state->socket = mode; + return 0; +} + +static ssize_t +send_message(struct if_state *state, int type, const struct options *options) +{ + struct dhcp_message *dhcp; + uint8_t *udp; + ssize_t len; + ssize_t r; + struct in_addr from; + struct in_addr to; + + logger(LOG_DEBUG, "sending %s with xid 0x%x", + get_dhcp_op(type), state->xid); + state->messages++; + len = make_message(&dhcp, state->interface, &state->lease, state->xid, + type, options); + 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) { + r = send_packet(state->interface, to, (uint8_t *)dhcp, len); + if (r == -1) + logger(LOG_ERR, "send_packet: %s", strerror(errno)); + } else { + len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); + free(dhcp); + r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len); + if (r == -1) + logger(LOG_ERR, "send_raw_packet: %s", strerror(errno)); + free(udp); + } + return r; +} + +static void +drop_config(struct if_state *state, const char *reason, const struct options *options) +{ + configure(state->interface, reason, NULL, state->new, + &state->lease, options, 0); + free(state->old); + state->old = NULL; + free(state->new); + state->new = NULL; + + state->lease.addr.s_addr = 0; +} + +static int +wait_for_packet(struct if_state *state) +{ + struct pollfd fds[3]; /* iface, arp, signal */ + int retval, timeout, nfds = 0; + time_t start; + struct timeval now, d; + + /* We always listen to signals */ + fds[nfds].fd = state->signal_fd; + fds[nfds].events = POLLIN; + nfds++; + + if (state->lease.leasetime == ~0U && state->state == STATE_BOUND) { + logger(LOG_DEBUG, "waiting for infinity"); + timeout = INFTIM; + } else { + timeout = state->timeout; + if (timerisset(&state->stop)) { + get_time(&now); + if (timercmp(&state->stop, &now, >)) { + timersub(&state->stop, &now, &d); + retval = d.tv_sec * 1000 + (d.tv_usec + 999) / 1000; + if (retval < timeout) + timeout = retval; + } + } + if (timeout <= 0) + return 0; + if (state->interface->fd != -1) { + fds[nfds].fd = state->interface->fd; + fds[nfds].events = POLLIN; + nfds++; + } +#ifdef ENABLE_ARP + if (state->interface->arp_fd != -1) { + fds[nfds].fd = state->interface->arp_fd; + fds[nfds].events = POLLIN; + nfds++; + } +#endif + logger(LOG_DEBUG, "waiting for %0.3f seconds", + (float)timeout / 1000); + } + + start = uptime(); + retval = poll(fds, nfds, timeout); + if (timeout != INFTIM) { + state->timeout -= uptime() - start; + if (state->timeout < 0) + state->timeout = 0; + } + if (retval == -1) { + if (errno == EINTR) + return 0; + logger(LOG_ERR, "poll: %s", strerror(errno)); + } + return retval; +} + +static int +handle_signal(int sig, struct if_state *state, const struct options *options) +{ + struct dhcp_lease *lease = &state->lease; + + switch (sig) { + case SIGINT: + logger(LOG_INFO, "received SIGINT, stopping"); + if (!(state->options & DHCPCD_PERSISTENT)) + drop_config(state, "STOP", options); + return -1; + case SIGTERM: + logger(LOG_INFO, "received SIGTERM, stopping"); + if (!(state->options & DHCPCD_PERSISTENT)) + drop_config(state, "STOP", options); + return -1; + + case SIGALRM: + logger (LOG_INFO, "received SIGALRM, renewing lease"); + switch (state->state) { + case STATE_BOUND: + case STATE_RENEWING: + case STATE_REBINDING: + case STATE_ANNOUNCING: + state->state = STATE_RENEW_REQUESTED; + break; + case STATE_RENEW_REQUESTED: + case STATE_REQUESTING: + state->state = STATE_INIT; + break; + } + timerclear(&state->stop); + state->timeout = 0; + return 0; + + case SIGHUP: + if (state->state != STATE_BOUND && + state->state != STATE_RENEWING && + state->state != STATE_REBINDING) + { + logger(LOG_ERR, + "received SIGHUP, but no lease to release"); + return -1; + } + + logger (LOG_INFO, "received SIGHUP, releasing lease"); + if (!IN_LINKLOCAL(ntohl(lease->addr.s_addr))) { + do_socket(state, SOCKET_OPEN); + state->xid = arc4random(); + send_message(state, DHCP_RELEASE, options); + do_socket(state, SOCKET_CLOSED); + } + drop_config(state, "RELEASE", options); + return -1; + + default: + logger (LOG_ERR, + "received signal %d, but don't know what to do with it", + sig); + } + + return -1; +} + +static int bind_dhcp(struct if_state *state, const struct options *options) +{ + struct interface *iface = state->interface; + struct dhcp_lease *lease = &state->lease; + const char *reason = NULL; + struct timeval tv; + int retval; + + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; +#ifdef ENABLE_ARP + state->conflicts = 0; + state->defend = 0; +#endif + + if (options->options & DHCPCD_INFORM) { + if (options->request_address.s_addr != 0) + lease->addr.s_addr = options->request_address.s_addr; + else + lease->addr.s_addr = iface->addr.s_addr; + logger(LOG_INFO, "received approval for %s", + inet_ntoa(lease->addr)); + state->state = STATE_BOUND; + state->lease.leasetime = ~0U; + reason = "INFORM"; + } else if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { + get_lease(lease, state->new); + logger(LOG_INFO, "using IPv4LL address %s", + inet_ntoa(lease->addr)); + state->state = STATE_INIT; + state->timeout = 0; + reason = "IPV4LL"; + } else { + if (gettimeofday(&tv, NULL) == 0) + lease->leasedfrom = tv.tv_sec; + + get_lease(lease, state->new); + if (lease->frominfo) + reason = "TIMEOUT"; + + if (lease->leasetime == ~0U) { + lease->renewaltime = lease->rebindtime = lease->leasetime; + state->timeout = 1; /* So we wait for infinity */ + logger(LOG_INFO, "leased %s for infinity", + inet_ntoa(lease->addr)); + state->state = STATE_BOUND; + } else { + logger(LOG_INFO, "leased %s for %u seconds", + inet_ntoa(lease->addr), lease->leasetime); + + if (lease->rebindtime >= lease->leasetime) { + lease->rebindtime = (lease->leasetime * T2); + logger(LOG_ERR, + "rebind time greater than lease " + "time, forcing to %u seconds", + lease->rebindtime); + } + + if (lease->renewaltime > lease->rebindtime) { + lease->renewaltime = (lease->leasetime * T1); + logger(LOG_ERR, + "renewal time greater than rebind time, " + "forcing to %u seconds", + lease->renewaltime); + } + + if (!lease->renewaltime) { + lease->renewaltime = (lease->leasetime * T1); + logger(LOG_INFO, + "no renewal time supplied, assuming %d seconds", + lease->renewaltime); + } else + logger(LOG_DEBUG, "renew in %u seconds", + lease->renewaltime); + + if (!lease->rebindtime) { + lease->rebindtime = (lease->leasetime * T2); + logger(LOG_INFO, + "no rebind time supplied, assuming %d seconds", + lease->rebindtime); + } else + logger(LOG_DEBUG, "rebind in %u seconds", + lease->rebindtime); + + state->timeout = lease->renewaltime * 1000; + } + state->state = STATE_BOUND; + } + + state->xid = 0; + timerclear(&state->stop); + if (!reason) { + if (state->old) { + if (state->old->yiaddr == state->new->yiaddr && + lease->server.s_addr) + reason = "RENEW"; + else + reason = "REBIND"; + } else + reason = "BOUND"; + } + retval = configure(iface, reason, state->new, state->old, + &state->lease, options, 1); + if (retval != 0) + return -1; + return daemonise(state, options); +} + +static int +handle_timeout_fail(struct if_state *state, const struct options *options) +{ + struct dhcp_lease *lease = &state->lease; + struct interface *iface = state->interface; + int gotlease = -1; + const char *reason = NULL; + struct timeval tv; + + timerclear(&tv); + /* Clear our timers and counters as we've failed. + * We'll either abort or move to another state with new timers */ + timerclear(&state->stop); + state->messages = 0; + state->timeout = 0; + + switch (state->state) { + case STATE_DISCOVERING: + /* FALLTHROUGH */ + case STATE_REQUESTING: + if (IN_LINKLOCAL(ntohl(iface->addr.s_addr))) { + if (!(state->options & DHCPCD_DAEMONISED)) + logger(LOG_ERR, "timed out"); + } else { + if (iface->addr.s_addr != 0 && + !(state->options & DHCPCD_INFORM)) + logger(LOG_ERR, "lost lease"); + else + logger(LOG_ERR, "timed out"); + } + do_socket(state, SOCKET_CLOSED); + if (state->options & DHCPCD_INFORM || + state->options & DHCPCD_TEST) + return -1; + + if (state->options & DHCPCD_IPV4LL || + state->options & DHCPCD_LASTLEASE) + gotlease = get_old_lease(state); + +#ifdef ENABLE_IPV4LL + if (state->options & DHCPCD_IPV4LL && gotlease != 0) { + logger(LOG_INFO, "probing for an IPV4LL address"); + free(state->offer); + state->offer = ipv4ll_get_dhcp(0); + gotlease = 0; + } +#endif + +#ifdef ENABLE_ARP + if (gotlease == 0 && + state->offer->yiaddr != iface->addr.s_addr) + { + state->state = STATE_PROBING; + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + return 0; + } +#endif + + if (gotlease == 0) + return bind_dhcp(state, options); + + reason = "FAIL"; + drop_config(state, reason, options); + if (!(state->options & DHCPCD_DAEMONISED) && + (state->options & DHCPCD_DAEMONISE)) + return -1; + state->state = STATE_INIT; + break; + case STATE_RENEWING: + logger(LOG_ERR, "failed to renew, attempting to rebind"); + lease->addr.s_addr = 0; + state->state = STATE_REBINDING; + tv.tv_sec = lease->rebindtime - lease->renewaltime; + break; + case STATE_REBINDING: + logger(LOG_ERR, "failed to rebind, attempting to discover"); + reason = "EXPIRE"; + drop_config(state, reason, options); + state->state = STATE_INIT; + break; + default: + logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d", + state->state); + } + + get_time(&state->start); + if (timerisset(&tv)) + timeradd(&state->start, &tv, &state->stop); + + /* This effectively falls through into the handle_timeout funtion */ + return 0; +} + +static int +handle_timeout(struct if_state *state, const struct options *options) +{ + struct dhcp_lease *lease = &state->lease; + struct interface *iface = state->interface; + int i; + struct timeval tv; + struct in_addr addr; + +#ifdef ENABLE_ARP + switch (state->state) { + case STATE_PROBING: + timerclear(&state->stop); + if (iface->arp_fd == -1) + open_socket(iface, ETHERTYPE_ARP); + if (state->probes < PROBE_NUM) { + if (state->probes == 0) { + addr.s_addr = state->offer->yiaddr; + logger(LOG_INFO, "checking %s is available" + " on attached networks", + inet_ntoa(addr)); + } + state->probes++; + logger(LOG_DEBUG, "sending ARP probe #%d", + state->probes); + if (state->probes < PROBE_NUM) + state->timeout = (arc4random() % + (PROBE_MAX - PROBE_MIN)) + PROBE_MIN; + else + state->timeout = ANNOUNCE_WAIT; + send_arp(iface, ARPOP_REQUEST, 0, state->offer->yiaddr); + return 0; + } else { + /* We've waited for ANNOUNCE_WAIT after the final probe + * so the address is now ours */ + i = bind_dhcp(state, options); + state->state = STATE_ANNOUNCING; + state->timeout = ANNOUNCE_INTERVAL; + return i; + } + case STATE_ANNOUNCING: + timerclear(&state->stop); + if (state->claims < ANNOUNCE_NUM) { + state->claims++; + logger(LOG_DEBUG, "sending ARP announce #%d", + state->claims); + send_arp(iface, ARPOP_REQUEST, + state->new->yiaddr, state->new->yiaddr); + if (state->claims < ANNOUNCE_NUM) + state->timeout = ANNOUNCE_INTERVAL; + else if (IN_LINKLOCAL(htonl(lease->addr.s_addr))) { + state->state = STATE_INIT; + state->timeout = 0; + } else { + state->state = STATE_BOUND; + state->timeout = lease->renewaltime * 1000 - + (ANNOUNCE_INTERVAL * ANNOUNCE_NUM); + close(iface->arp_fd); + iface->arp_fd = -1; + } + } + return 0; + } +#endif + + if (timerisset(&state->stop)) { + get_time(&tv); + if (timercmp(&tv, &state->stop, >)) + return handle_timeout_fail(state, options); + } + timerclear(&tv); + + switch (state->state) { + case STATE_INIT: /* FALLTHROUGH */ + case STATE_BOUND: /* FALLTHROUGH */ + case STATE_RENEW_REQUESTED: + do_socket(state, SOCKET_OPEN); + state->xid = arc4random(); + state->messages = 0; + state->nakoff = 1; + iface->start_uptime = uptime(); + get_time(&state->start); + timerclear(&state->stop); + } + + switch(state->state) { + case STATE_INIT: + /* 21Jul08 - was && DHCPCD_DAEMONISED */ + if (!(state->state & DHCPCD_DAEMONISED) && + options->timeout && + !IN_LINKLOCAL(htonl(iface->addr.s_addr))) + { + get_time(&state->start); + tv.tv_sec = options->timeout; + timeradd(&state->start, &tv, &state->stop); + } + if (lease->addr.s_addr == 0 || + IN_LINKLOCAL(ntohl(iface->addr.s_addr))) + { + logger(LOG_INFO, "broadcasting for a lease"); + state->state = STATE_DISCOVERING; + } else if (state->options & DHCPCD_INFORM) { + logger(LOG_INFO, "broadcasting inform for %s", + inet_ntoa(lease->addr)); + state->state = STATE_REQUESTING; + } else { + logger(LOG_INFO, "broadcasting for a lease of %s", + inet_ntoa(lease->addr)); + state->state = STATE_REQUESTING; + } + break; + case STATE_RENEW_REQUESTED: + case STATE_BOUND: + if (IN_LINKLOCAL(ntohl(lease->addr.s_addr))) { + lease->addr.s_addr = 0; + state->state = STATE_INIT; + state->timeout = 0; + break; + } + logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr)); + state->state = STATE_RENEWING; + break; + } + + switch (state->state) { + case STATE_DISCOVERING: + send_message(state, DHCP_DISCOVER, options); + break; + case STATE_REQUESTING: + if (state->options & DHCPCD_INFORM) { + send_message(state, DHCP_INFORM, options); + break; + } + /* FALLTHROUGH */ + case STATE_RENEWING: /* FALLTHROUGH */ + case STATE_REBINDING: + send_message(state, DHCP_REQUEST, options); + break; + } + + state->timeout = DHCP_BASE; + for (i = 1; i < state->messages; i++) { + state->timeout *= 2; + if (state->timeout > DHCP_MAX) { + state->timeout = DHCP_MAX; + break; + } + } + state->timeout += (arc4random() % (DHCP_RAND_MAX - DHCP_RAND_MIN)) + + DHCP_RAND_MIN; + return 0; +} + +static int +handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp, + const struct options *options) +{ + struct timespec ts; + struct dhcp_message *dhcp = *dhcpp; + struct interface *iface = state->interface; + struct dhcp_lease *lease = &state->lease; + char *addr; + struct in_addr saddr; + uint8_t type; + int r; + + if (get_option_uint8(&type, dhcp, DHCP_MESSAGETYPE) == -1) { + logger(LOG_ERR, "no DHCP type in message"); + return -1; + } + + /* reset the message counter */ + state->messages = 0; + + /* We should restart on a NAK */ + if (type == DHCP_NAK) { + addr = get_option_string(dhcp, DHCP_MESSAGE); + logger(LOG_WARNING, "received NAK: %s", addr); + free(addr); + state->state = STATE_INIT; + state->timeout = 0; + lease->addr.s_addr = 0; + timerclear(&state->stop); + + /* If we constantly get NAKS then we should slowly back off */ + if (state->nakoff > 0) { + logger(LOG_DEBUG, "sleeping for %lu seconds", + (unsigned long)state->nakoff); + ts.tv_sec = state->nakoff; + ts.tv_nsec = 0; + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + nanosleep(&ts, NULL); + } + + return 0; + } + + /* No NAK, so reset the backoff */ + state->nakoff = 1; + + if (type == DHCP_OFFER && state->state == STATE_DISCOVERING) { + lease->addr.s_addr = dhcp->yiaddr; + addr = xstrdup(inet_ntoa(lease->addr)); + r = get_option_addr(&lease->server.s_addr, dhcp, DHCP_SERVERID); + if (dhcp->servername[0] && r == 0) + logger(LOG_INFO, "offered %s from %s `%s'", + addr, inet_ntoa(lease->server), + dhcp->servername); + else if (r == 0) + logger(LOG_INFO, "offered %s from %s", + addr, inet_ntoa(lease->server)); + else + logger(LOG_INFO, "offered %s", addr); + free(addr); + + if (state->options & DHCPCD_TEST) { + exec_script(options, iface->name, "TEST", dhcp, NULL); + free(dhcp); + return 0; + } + + free(dhcp); + state->state = STATE_REQUESTING; + state->timeout = 0; + return 0; + } + + if (type == DHCP_OFFER) { + saddr.s_addr = dhcp->yiaddr; + logger(LOG_INFO, "got subsequent offer of %s, ignoring ", + inet_ntoa(saddr)); + free(dhcp); + return 0; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + logger(LOG_ERR, "%d not an ACK or OFFER", type); + free(dhcp); + return 0; + } + + switch (state->state) { + case STATE_RENEW_REQUESTED: + case STATE_REQUESTING: + case STATE_RENEWING: + case STATE_REBINDING: + if (!(state->options & DHCPCD_INFORM)) { + saddr.s_addr = dhcp->yiaddr; + logger(LOG_INFO, "lease of %s acknowledged", + inet_ntoa(saddr)); + } + break; + default: + logger(LOG_ERR, "wrong state %d", state->state); + } + + do_socket(state, SOCKET_CLOSED); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + +#ifdef ENABLE_ARP + if (state->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + state->state = STATE_PROBING; + state->timeout = 0; + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + timerclear(&state->stop); + return 0; + } +#endif + + return bind_dhcp(state, options); +} + +static int +handle_dhcp_packet(struct if_state *state, const struct options *options) +{ + uint8_t *packet; + struct interface *iface = state->interface; + struct dhcp_message *dhcp; + const uint8_t *pp; + uint8_t *p; + ssize_t bytes; + int retval = -1; + + /* 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); + dhcp = xmalloc(sizeof(*dhcp)); + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_IP, + packet, udp_dhcp_len); + if (bytes == 0) { + retval = 0; + break; + } + if (bytes == -1) + break; + if (valid_udp_packet(packet) == -1) + continue; + bytes = get_udp_data(&pp, packet); + if ((size_t)bytes > sizeof(*dhcp)) { + logger(LOG_ERR, "packet greater than DHCP size"); + continue; + } + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + logger(LOG_DEBUG, "bogus cookie, ignoring"); + continue; + } + if (state->xid != dhcp->xid) { + logger(LOG_DEBUG, + "ignoring packet with xid 0x%x as" + " it's not ours (0x%x)", + dhcp->xid, state->xid); + continue; + } + /* We should ensure that the packet is terminated correctly + * if we have space for the terminator */ + if ((size_t)bytes < sizeof(struct dhcp_message)) { + p = (uint8_t *)dhcp + bytes - 1; + while (p > dhcp->options && *p == DHCP_PAD) + p--; + if (*p != DHCP_END) + *++p = DHCP_END; + } + free(packet); + if (handle_dhcp(state, &dhcp, options) == 0) { + /* Fake the fact we forked so we return 0 to userland */ + if (state->options & DHCPCD_TEST) + state->options |= DHCPCD_FORKED; + else + return 0; + } + if (state->options & DHCPCD_FORKED) + return -1; + } + + free(packet); + free(dhcp); + return retval; +} + +#ifdef ENABLE_ARP +static int +handle_arp_packet(struct if_state *state) +{ + struct arphdr reply; + uint32_t reply_s; + uint32_t reply_t; + uint8_t arp_reply[sizeof(reply) + 2 * sizeof(reply_s) + 2 * HWADDR_LEN]; + uint8_t *hw_s, *hw_t; + ssize_t bytes; + struct interface *iface = state->interface; + + state->fail.s_addr = 0; + + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_ARP, + arp_reply, sizeof(arp_reply)); + if (bytes == 0 || bytes == -1) + return (int)bytes; + /* We must have a full ARP header */ + if ((size_t)bytes < sizeof(reply)) + continue; + memcpy(&reply, arp_reply, sizeof(reply)); + /* Protocol must be IP. */ + if (reply.ar_pro != htons(ETHERTYPE_IP)) + continue; + if (reply.ar_pln != sizeof(reply_s)) + continue; + /* Only these types are recognised */ + if (reply.ar_op != htons(ARPOP_REPLY) && + reply.ar_op != htons(ARPOP_REQUEST)) + continue; + + /* Get pointers to the hardware addreses */ + hw_s = arp_reply + sizeof(reply); + hw_t = hw_s + reply.ar_hln + reply.ar_pln; + /* Ensure we got all the data */ + if ((hw_t + reply.ar_hln + reply.ar_pln) - arp_reply > bytes) + continue; + /* Copy out the IP addresses */ + memcpy(&reply_s, hw_s + reply.ar_hln, reply.ar_pln); + memcpy(&reply_t, hw_t + reply.ar_hln, reply.ar_pln); + + /* Check for conflict */ + if (state->offer && + (reply_s == state->offer->yiaddr || + (reply_t == state->offer->yiaddr && + reply.ar_op == htons(ARPOP_REQUEST) && + (iface->hwlen != reply.ar_hln || + memcmp(hw_s, iface->hwaddr, iface->hwlen) != 0)))) + 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_t == iface->addr.s_addr && + reply.ar_op == htons(ARPOP_REQUEST) && + (iface->hwlen != reply.ar_hln || + memcmp(hw_s, iface->hwaddr, iface->hwlen) != 0)))) + state->fail.s_addr = iface->addr.s_addr; + + if (state->fail.s_addr) { + logger(LOG_ERR, "hardware address %s claims %s", + hwaddr_ntoa((unsigned char *)hw_s, + (size_t)reply.ar_hln), + inet_ntoa(state->fail)); + errno = EEXIST; + return -1; + } + } +} + +static int +handle_arp_fail(struct if_state *state, const struct options *options) +{ + struct timespec ts; + time_t up; + + if (IN_LINKLOCAL(htonl(state->fail.s_addr))) { + if (state->fail.s_addr == state->interface->addr.s_addr) { + up = uptime(); + if (state->defend + DEFEND_INTERVAL > up) { + drop_config(state, "FAIL", options); + state->state = STATE_PROBING; + state->timeout = 0; + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + timerclear(&state->stop); + } else + state->defend = up; + return 0; + } + + timerclear(&state->stop); + state->conflicts++; + state->timeout = 0; + state->claims = 0; + state->probes = 0; + state->state = STATE_PROBING; + free(state->offer); + if (state->conflicts > MAX_CONFLICTS) { + /* RFC 3927 says we should rate limit */ + logger(LOG_INFO, "sleeping for %d seconds", + RATE_LIMIT_INTERVAL); + ts.tv_sec = RATE_LIMIT_INTERVAL; + ts.tv_nsec = 0; + nanosleep(&ts, NULL); + } + state->offer = ipv4ll_get_dhcp(0); + return 0; + } + + do_socket(state, SOCKET_OPEN); + send_message(state, DHCP_DECLINE, options); + state->timeout = 0; + state->state = STATE_INIT; + /* RFC 2131 says that we should wait for 10 seconds + * before doing anything else */ + logger(LOG_INFO, "sleeping for 10 seconds"); + ts.tv_sec = 10; + ts.tv_nsec = 0; + nanosleep(&ts, NULL); + return 0; +} +#endif + +int +dhcp_run(const struct options *options, int *pid_fd) +{ + struct interface *iface; + struct if_state *state = NULL; + int retval = -1; + int sig; + + iface = read_interface(options->interface, options->metric); + if (!iface) { + logger(LOG_ERR, "read_interface: %s", strerror(errno)); + goto eexit; + } + + logger(LOG_INFO, "hardware address = %s", + hwaddr_ntoa(iface->hwaddr, iface->hwlen)); + + state = xzalloc(sizeof(*state)); + state->pid_fd = pid_fd; + state->interface = iface; + + if (client_setup(state, options) == -1) + goto eexit; + if (signal_init() == -1) + goto eexit; + if (signal_setup() == -1) + goto eexit; + + state->signal_fd = signal_fd(); + + for (;;) { + retval = wait_for_packet(state); + + /* We should always handle our signals first */ + if ((sig = (signal_read(state->signal_fd))) != -1) { + retval = handle_signal(sig, state, options); + } else if (retval == 0) + retval = handle_timeout(state, options); + else if (retval == -1) { + if (errno == EINTR) + /* The interupt will be handled above */ + retval = 0; + } else if (retval > 0) { + if (fd_hasdata(state->interface->fd) == 1) + retval = handle_dhcp_packet(state, options); +#ifdef ENABLE_ARP + else if (fd_hasdata(state->interface->arp_fd) == 1) { + retval = handle_arp_packet(state); + if (retval == -1) + retval = handle_arp_fail(state, options); + } +#endif + else + retval = 0; + } + + if (retval != 0) + break; + } + +eexit: + if (iface) { + do_socket(state, SOCKET_CLOSED); + free_routes(iface->routes); + free(iface->clientid); + free(iface->buffer); + free(iface); + } + + if (state) { + if (state->options & DHCPCD_FORKED) + retval = 0; + if (state->options & DHCPCD_DAEMONISED) + unlink(options->pidfile); + free(state->offer); + free(state->new); + free(state->old); + free(state); + } + + return retval; +} diff --git a/client.h b/client.h new file mode 100644 index 0000000..35a5e37 --- /dev/null +++ b/client.h @@ -0,0 +1,35 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "dhcpcd.h" + +int dhcp_run(const struct options *, int *); + +#endif diff --git a/common.c b/common.c new file mode 100644 index 0000000..8bc2b78 --- /dev/null +++ b/common.c @@ -0,0 +1,304 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#ifdef BSD +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "logger.h" + +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/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. */ +ssize_t +get_line(char **line, size_t *len, FILE *fp) +{ + char *p; + size_t last = 0; + + while(!feof(fp)) { + if (*line == NULL || last != 0) { + *len += BUFSIZ; + *line = xrealloc(*line, *len); + } + p = *line + last; + memset(p, 0, BUFSIZ); + fgets(p, BUFSIZ, fp); + last += strlen(p); + if (last && (*line)[last - 1] == '\n') { + (*line)[last - 1] = '\0'; + break; + } + } + return last; +} + +/* Simple hack to return a random number without arc4random */ +#ifndef HAVE_ARC4RANDOM +uint32_t arc4random(void) +{ + int fd; + static unsigned long seed = 0; + + if (!seed) { + fd = open("/dev/urandom", 0); + if (fd == -1 || read(fd, &seed, sizeof(seed)) == -1) + seed = time(0); + if (fd >= 0) + close(fd); + srandom(seed); + } + + return (uint32_t)random(); +} +#endif + +/* strlcpy is nice, shame glibc does not define it */ +#if HAVE_STRLCPY +#else +size_t +strlcpy(char *dst, const char *src, size_t size) +{ + const char *s = src; + size_t n = size; + + if (n && --n) + do { + if (!(*dst++ = *src++)) + break; + } while (--n); + + if (!n) { + if (size) + *dst = '\0'; + while (*src++); + } + + return src - s - 1; +} +#endif + +#if HAVE_CLOSEFROM +#else +int +closefrom(int fd) +{ + int max = getdtablesize(); + int i; + int r = 0; + + for (i = fd; i < max; i++) + r += close(i); + return r; +} +#endif + +/* Close our fd's */ +int +close_fds(void) +{ + int fd; + + if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) + return -1; + + dup2(fd, fileno(stdin)); + dup2(fd, fileno(stdout)); + dup2(fd, fileno(stderr)); + if (fd > 2) + close(fd); + return 0; +} + +int +fd_hasdata(int fd) +{ + struct pollfd fds; + int retval; + + if (fd == -1) + return -1; + fds.fd = fd; + fds.events = POLLIN; + fds.revents = 0; + retval = poll(&fds, 1, 0); + if (retval == -1) + return -1; + if (retval > 0 && fds.revents & POLLIN) + return retval; + return 0; +} + +int +set_cloexec(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFD, 0)) == -1 + || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + logger(LOG_ERR, "fcntl: %s", strerror(errno)); + 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) + { + logger(LOG_ERR, "fcntl: %s", strerror(errno)); + 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. + */ +int +get_time(struct timeval *tp) +{ +#if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC) + struct timespec ts; + static clockid_t posix_clock; + static int posix_clock_set = 0; + + if (!posix_clock_set) { + if (sysconf(_SC_MONOTONIC_CLOCK) >= 0) + posix_clock = CLOCK_MONOTONIC; + else + posix_clock = CLOCK_REALTIME; + posix_clock_set = 1; + } + + if (clock_gettime(posix_clock, &ts) == -1) + return -1; + + tp->tv_sec = ts.tv_sec; + tp->tv_usec = ts.tv_nsec / 1000; + return 0; +#else + return gettimeofday(tp, NULL); +#endif +} + +time_t +uptime(void) +{ + struct timeval tp; + + if (get_time(&tp) == -1) + return -1; + return tp.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", 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) + return value; + logger(LOG_ERR, "memory exhausted"); + 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) + return (value); + logger(LOG_ERR, "memory exhausted"); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} + +char * +xstrdup(const char *str) +{ + char *value; + + if (!str) + return NULL; + + if ((value = strdup(str))) + return value; + + logger(LOG_ERR, "memory exhausted"); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..94a1cc0 --- /dev/null +++ b/common.h @@ -0,0 +1,86 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef COMMON_H +#define COMMON_H + +/* string.h pulls in features.h so the below define checks work */ +#include +#include +#include +#include +#include + +#define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) + +#if __GNUC__ > 2 || defined(__INTEL_COMPILER) +# define _unused __attribute__((__unused__)) +#else +# define _unused +#endif + +#ifndef HAVE_ARC4RANDOM +# ifdef __GLIBC__ +uint32_t arc4random(void); +#else +# define HAVE_ARC4RANDOM +# endif +#endif + +#ifndef HAVE_STRLCPY +# define HAVE_STRLCPY 1 +#endif +/* Only GLIBC doesn't support strlcpy */ +#ifdef __GLIBC__ +# if !defined(__UCLIBC__) && !defined (__dietlibc__) +# undef HAVE_STRLCPY +size_t strlcpy(char *, const char *, size_t); +# endif +#endif + +#ifndef HAVE_CLOSEFROM +#define HAVE_CLOSEFROM 1 +#endif +#if defined(__linux__) || defined(__FreeBSD__) +# undef HAVE_CLOSEFROM +int closefrom(int); +#endif + +int close_fds(void); +int set_cloexec(int); +int set_nonblock(int); +int fd_hasdata(int); +ssize_t get_line(char **, size_t *, FILE *); +int get_time(struct timeval *); +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 *); + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..8539420 --- /dev/null +++ b/config.h @@ -0,0 +1,102 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PACKAGE "dhcpcd" +#define VERSION "4.0.0-beta9" + +/* You can enable/disable various chunks of optional code here. + * You would only do this to try and shrink the end binary if dhcpcd + * was running on a low memory device */ + +/* Disable everything we possibly can. */ +#ifdef MINIMAL +# ifndef DISABLE_ARP +# define DISABLE_ARP +# endif +# ifndef DISABLE_IPV4LL +# define DISABLE_IPV4LL +# endif +#endif + +/* Enable ARP by default. */ +#ifndef DISABLE_ARP +# define ENABLE_ARP +#endif + +/* IPV4LL, aka ZeroConf, aka APIPA, aka RFC 3927. + * Needs ARP. */ +#ifndef DISABLE_IPV4LL +# ifdef ENABLE_ARP +# define ENABLE_IPV4LL +# endif +#endif + +/* + * By default we don't add a local link route if we got a routeable address. + * This is because dhcpcd can't really decide which interface should allow + * link local routing when we have more than one interface. + * Ideally the host network scripts should add the link local route for us. + * If not, you can define this to get dhcpcd to always add the link local route. + */ +// #define ENABLE_IPV4LL_ALWAYSROUTE + +/* Some systems do not have a working fork. */ +/* #define THERE_IS_NO_FORK */ + +/* Paths to things */ +#ifndef SYSCONFDIR +# define SYSCONFDIR "/system/etc/dhcpcd" +#endif +#ifndef LIBEXECDIR +# define LIBEXECDIR "/system/etc/dhcpcd" +#endif +#ifndef RUNDIR +# define RUNDIR "/data/misc/dhcp" +#endif +#ifndef DBDIR +# define DBDIR "/data/misc/dhcp" +#endif + +#ifndef CONFIG +# define CONFIG SYSCONFDIR "/" PACKAGE ".conf" +#endif +#ifndef SCRIPT +# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks" +#endif +#ifndef DUID +# define DUID SYSCONFDIR "/" PACKAGE ".duid" +#endif +#ifndef LEASEFILE +# define LEASEFILE DBDIR "/" PACKAGE "-%s.lease" +#endif +#ifndef PIDFILE +# define PIDFILE RUNDIR "/" PACKAGE "-%s.pid" +#endif + +#endif diff --git a/configure.c b/configure.c new file mode 100644 index 0000000..8c024f8 --- /dev/null +++ b/configure.c @@ -0,0 +1,496 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "configure.h" +#include "dhcp.h" +#include "dhcpcd.h" +#include "logger.h" +#include "net.h" +#include "signals.h" + +#define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" + +int +exec_script(const struct options *options, const char *iface, + const char *reason, + const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) +{ + char *const argv[2] = { UNCONST(options->script), NULL }; + char **env = NULL, **ep; + char *path; + ssize_t e, elen; + int ret = 0; + pid_t pid; + int status = 0; + sigset_t full; + sigset_t old; + + logger(LOG_DEBUG, "executing `%s'", options->script); + + /* Make our env */ + elen = 5; + env = xmalloc(sizeof(char *) * (elen + 1)); + path = getenv("PATH"); + if (path) { + e = strlen("PATH") + strlen(path) + 2; + env[0] = xmalloc(e); + snprintf(env[0], e, "PATH=%s", path); + } else + env[0] = xstrdup(DEFAULT_PATH); + e = strlen("interface") + strlen(iface) + 2; + env[1] = xmalloc(e); + snprintf(env[1], e, "interface=%s", iface); + e = strlen("reason") + strlen(reason) + 2; + env[2] = xmalloc(e); + snprintf(env[2], e, "reason=%s", reason); + e = 20; + env[3] = xmalloc(e); + snprintf(env[3], e, "pid=%d", getpid()); + env[4] = xmalloc(e); + snprintf(env[4], e, "metric=%d", options->metric); + if (dhcpo) { + e = configure_env(NULL, NULL, dhcpo, options); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += configure_env(env + elen, "old", dhcpo, options); + } + } + if (dhcpn) { + e = configure_env(NULL, NULL, dhcpn, options); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += configure_env(env + elen, "new", dhcpn, options); + } + } + /* Add our base environment */ + if (options->environ) { + e = 0; + while (options->environ[e++]) + ; + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + e = 0; + while (options->environ[e]) { + env[elen + e] = xstrdup(options->environ[e]); + e++; + } + elen += e; + } + env[elen] = '\0'; + + /* OK, we need to block signals */ + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + +#ifdef THERE_IS_NO_FORK + signal_reset(); + pid = vfork(); +#else + pid = fork(); +#endif + + switch (pid) { + case -1: +#ifdef THERE_IS_NO_FORK + logger(LOG_ERR, "vfork: %s", strerror(errno)); +#else + logger(LOG_ERR, "fork: %s", strerror(errno)); +#endif + ret = -1; + break; + case 0: +#ifndef THERE_IS_NO_FORK + signal_reset(); +#endif + sigprocmask(SIG_SETMASK, &old, NULL); + execve(options->script, argv, env); + logger(LOG_ERR, "%s: %s", options->script, strerror(errno)); + _exit(111); + /* NOTREACHED */ + } + +#ifdef THERE_IS_NO_FORK + signal_setup(); +#endif + + /* Restore our signals */ + sigprocmask(SIG_SETMASK, &old, NULL); + + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + logger(LOG_ERR, "waitpid: %s", strerror(errno)); + status = -1; + break; + } + } + + /* Cleanup */ + ep = env; + while (*ep) + free(*ep++); + free(env); + + return status; +} + +static struct rt * +reverse_routes(struct rt *routes) +{ + struct rt *rt; + struct rt *rtn = NULL; + + while (routes) { + rt = routes->next; + routes->next = rtn; + rtn = routes; + routes = rt; + } + return rtn; +} + +static int +delete_route(const char *iface, struct rt *rt, int metric) +{ + char *addr; + int retval; + + addr = xstrdup(inet_ntoa(rt->dest)); + logger(LOG_DEBUG, "removing route %s/%d via %s", + addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); + free(addr); + retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric); + if (retval != 0) + logger(LOG_ERR," del_route: %s", strerror(errno)); + return retval; + +} + +static int +delete_routes(struct interface *iface, int metric) +{ + struct rt *rt; + struct rt *rtn; + int retval = 0; + + rt = reverse_routes(iface->routes); + while (rt) { + rtn = rt->next; + retval += delete_route(iface->name, rt, metric); + free(rt); + rt = rtn; + } + iface->routes = NULL; + + return retval; +} + +static int +in_routes(const struct rt *routes, const struct rt *rt) +{ + while (routes) { + if (routes->dest.s_addr == rt->dest.s_addr && + routes->net.s_addr == rt->net.s_addr && + routes->gate.s_addr == rt->gate.s_addr) + return 0; + routes = routes->next; + } + return -1; +} + +static int +configure_routes(struct interface *iface, const struct dhcp_message *dhcp, + const struct options *options) +{ + struct rt *rt, *ort; + struct rt *rtn = NULL, *nr = NULL; + int remember; + int retval = 0; + char *addr; + +#ifdef THERE_IS_NO_FORK + char *skipp; + size_t skiplen; + int skip = 0; + + free(dhcpcd_skiproutes); + /* We can never have more than 255 routes. So we need space + * for 255 3 digit numbers and commas */ + skiplen = 255 * 4 + 1; + skipp = dhcpcd_skiproutes = xmalloc(sizeof(char) * skiplen); + *skipp = '\0'; +#endif + + ort = get_option_routes(dhcp); + +#ifdef ENABLE_IPV4LL_ALWAYSROUTE + if (options->options & DHCPCD_IPV4LL && + IN_PRIVATE(ntohl(dhcp->yiaddr))) + { + for (rt = ort; rt; rt = rt->next) { + /* Check if we have already got a link locale route + * dished out by the DHCP server */ + if (rt->dest.s_addr == htonl(LINKLOCAL_ADDR) && + rt->net.s_addr == htonl(LINKLOCAL_MASK)) + break; + rtn = rt; + } + + if (!rt) { + rt = xmalloc(sizeof(*rt)); + rt->dest.s_addr = htonl(LINKLOCAL_ADDR); + rt->net.s_addr = htonl(LINKLOCAL_MASK); + rt->gate.s_addr = 0; + rt->next = NULL; + if (rtn) + rtn->next = rt; + else + ort = rt; + } + } +#endif + +#ifdef THERE_IS_NO_FORK + if (dhcpcd_skiproutes) { + int i = -1; + char *sk, *skp, *token; + free_routes(iface->routes); + for (rt = ort; rt; rt = rt->next) { + i++; + /* Check that we did add this route or not */ + sk = skp = xstrdup(dhcpcd_skiproutes); + while ((token = strsep(&skp, ","))) { + if (isdigit((unsigned char)*token) && + atoi(token) == i) + break; + } + free(sk); + if (token) + continue; + if (nr) { + rtn->next = xmalloc(sizeof(*rtn)); + rtn = rtn->next; + } else { + nr = rtn = xmalloc(sizeof(*rtn)); + } + rtn->dest.s_addr = rt->dest.s_addr; + rtn->net.s_addr = rt->net.s_addr; + rtn->gate.s_addr = rt->gate.s_addr; + rtn->next = NULL; + } + iface->routes = nr; + nr = NULL; + + /* We no longer need this */ + free(dhcpcd_skiproutes); + dhcpcd_skiproutes = NULL; + } +#endif + + /* Now remove old routes we no longer use. + * We should do this in reverse order. */ + iface->routes = reverse_routes(iface->routes); + for (rt = iface->routes; rt; rt = rt->next) + if (in_routes(ort, rt) != 0) + delete_route(iface->name, rt, options->metric); + + for (rt = ort; rt; rt = rt->next) { + /* Don't set default routes if not asked to */ + if (rt->dest.s_addr == 0 && + rt->net.s_addr == 0 && + !(options->options & DHCPCD_GATEWAY)) + continue; + + addr = xstrdup(inet_ntoa(rt->dest)); + logger(LOG_DEBUG, "adding route to %s/%d via %s", + addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); + free(addr); + remember = add_route(iface->name, &rt->dest, + &rt->net, &rt->gate, + options->metric); + retval += remember; + + /* If we failed to add the route, we may have already added it + ourselves. If so, remember it again. */ + if (remember < 0) { + if (errno != EEXIST) + logger(LOG_ERR, "add_route: %s", + strerror(errno)); + if (in_routes(iface->routes, rt) == 0) + remember = 1; + } + + /* This login is split from above due to the #ifdef below */ + if (remember >= 0) { + if (nr) { + rtn->next = xmalloc(sizeof(*rtn)); + rtn = rtn->next; + } else { + nr = rtn = xmalloc(sizeof(*rtn)); + } + rtn->dest.s_addr = rt->dest.s_addr; + rtn->net.s_addr = rt->net.s_addr; + rtn->gate.s_addr = rt->gate.s_addr; + rtn->next = NULL; + } +#ifdef THERE_IS_NO_FORK + /* If we have daemonised yet we need to record which routes + * we failed to add so we can skip them */ + else if (!(options->options & DHCPCD_DAEMONISED)) { + /* We can never have more than 255 / 4 routes, + * so 3 chars is plently */ + printf("foo\n"); + if (*skipp) + *skipp++ = ','; + skipp += snprintf(skipp, + dhcpcd_skiproutes + skiplen - skipp, + "%d", skip); + } + skip++; +#endif + } + free_routes(ort); + free_routes(iface->routes); + iface->routes = nr; + +#ifdef THERE_IS_NO_FORK + if (dhcpcd_skiproutes) { + if (*dhcpcd_skiproutes) + *skipp = '\0'; + else { + free(dhcpcd_skiproutes); + dhcpcd_skiproutes = NULL; + } + } +#endif + + return retval; +} + +int +configure(struct interface *iface, const char *reason, + const struct dhcp_message *dhcp, const struct dhcp_message *old, + const struct dhcp_lease *lease, const struct options *options, + int up) +{ + struct in_addr addr; + struct in_addr net; + struct in_addr brd; +#ifdef __linux__ + struct in_addr dest; + struct in_addr gate; +#endif + + /* Grab our IP config */ + if (dhcp == NULL || dhcp->yiaddr == 0) + up = 0; + else { + addr.s_addr = dhcp->yiaddr; + /* Ensure we have all the needed values */ + if (get_option_addr(&net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) + net.s_addr = get_netmask(addr.s_addr); + if (get_option_addr(&brd.s_addr, dhcp, DHCP_BROADCAST) == -1) + brd.s_addr = addr.s_addr | ~net.s_addr; + } + + /* If we aren't up, then reset the interface as much as we can */ + if (!up) { + /* Only reset things if we had set them before */ + if (iface->addr.s_addr != 0) { + delete_routes(iface, options->metric); + logger(LOG_DEBUG, "deleting IP address %s/%d", + inet_ntoa(iface->addr), + inet_ntocidr(iface->net)); + if (del_address(iface->name, &iface->addr, + &iface->net) == -1 && + errno != ENOENT) + logger(LOG_ERR, "del_address: %s", + strerror(errno)); + iface->addr.s_addr = 0; + iface->net.s_addr = 0; + } + + exec_script(options, iface->name, reason, NULL, old); + return 0; + } + + /* This also changes netmask */ + if (!(options->options & DHCPCD_INFORM) || + !has_address(iface->name, &addr, &net)) { + logger(LOG_DEBUG, "adding IP address %s/%d", + inet_ntoa(addr), inet_ntocidr(net)); + if (add_address(iface->name, &addr, &net, &brd) == -1 && + errno != EEXIST) + { + logger(LOG_ERR, "add_address: %s", strerror(errno)); + return -1; + } + } + + /* Now delete the old address if different */ + if (iface->addr.s_addr != addr.s_addr && + iface->addr.s_addr != 0) + del_address(iface->name, &iface->addr, &iface->net); + +#ifdef __linux__ + /* On linux, we need to change the subnet route to have our metric. */ + if (iface->addr.s_addr != lease->addr.s_addr && + options->metric > 0 && net.s_addr != INADDR_BROADCAST) + { + dest.s_addr = addr.s_addr & net.s_addr; + gate.s_addr = 0; + add_route(iface->name, &dest, &net, &gate, options->metric); + del_route(iface->name, &dest, &net, &gate, 0); + } +#endif + + configure_routes(iface, dhcp, options); + up = (iface->addr.s_addr != addr.s_addr || + iface->net.s_addr != net.s_addr); + iface->addr.s_addr = addr.s_addr; + iface->net.s_addr = net.s_addr; + + if (!lease->frominfo) + if (write_lease(iface, dhcp) == -1) + logger(LOG_ERR, "write_lease: %s", strerror(errno)); + + exec_script(options, iface->name, reason, dhcp, old); + return 0; +} diff --git a/configure.h b/configure.h new file mode 100644 index 0000000..c4c731c --- /dev/null +++ b/configure.h @@ -0,0 +1,41 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCONFIG_H +#define DHCPCONFIG_H + +#include "dhcpcd.h" +#include "dhcp.h" +#include "net.h" + +int exec_script(const struct options *, const char *, const char *, + const struct dhcp_message *, const struct dhcp_message *); +int configure(struct interface *, const char *, + const struct dhcp_message *, const struct dhcp_message *, + const struct dhcp_lease *, const struct options *, int); + +#endif diff --git a/dhcp.c b/dhcp.c new file mode 100644 index 0000000..0dbc39e --- /dev/null +++ b/dhcp.c @@ -0,0 +1,1218 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "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 IPV4R IPV4 | REQUEST + +/* Our aggregate option buffer. + * We ONLY use this when options are split, which for most purposes is + * practically never. See RFC3396 for details. */ +static uint8_t *dhcp_opt_buffer = NULL; + +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 } +}; + +void +print_options(void) +{ + const struct dhcp_opt *opt; + + for (opt = dhcp_opts; opt->option; opt++) + if (opt->var) + printf("%03d %s\n", opt->option, opt->var); +} + +int make_reqmask(uint8_t *mask, char **opts, int add) +{ + char *token; + char *p = *opts; + const struct dhcp_opt *opt; + + while ((token = strsep(&p, ", "))) { + if (*token == '\0') + continue; + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (strcmp(opt->var, token) == 0) { + if (add == 1) + add_reqmask(mask, + opt->option); + else + del_reqmask(mask, + opt->option); + break; + } + } + if (!opt->option) { + *opts = token; + errno = ENOENT; + return -1; + } + } + 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; + + 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 = xmalloc(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 DHCP_PAD: + continue; + case DHCP_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 DHCP_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_addr(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_addr(&a, dhcp, option) == -1) + return -1; + + *i = ntohl(a); + 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; + *i = *(p); + return 0; +} + +/* Decode an RFC3397 DNS search order option into a space + * seperated string. Returns length of string (including + * terminating zero) or zero on error. out may be NULL + * to just determine output length. */ +static 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; + while ((l = *q++)) { + 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 bytes = 0; + ssize_t b; + uint8_t cidr; + uint8_t ocets; + 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, (size_t)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; + uint8_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, (size_t)ocets); + memset(&rt->net.s_addr, 255, (size_t)ocets - 1); + memset((uint8_t *)&rt->net.s_addr + + (ocets - 1), + (256 - (1 << (32 - cidr) % 8)), 1); + p += ocets; + } else { + rt->dest.s_addr = 0; + rt->net.s_addr = 0; + } + + /* 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 (l != 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)); + l -= sizeof(addr.s_addr); + } + *--p = '\0'; + break; + default: + errno = EINVAL; + return 0; + } + + return sip; +} + +char * +get_option_string(const struct dhcp_message *dhcp, uint8_t option) +{ + int type; + 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 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, DHCP_CSR, &len, NULL); + /* Check for crappy MS option */ + if (!p) + p = get_option(dhcp, DHCP_MSCSR, &len, NULL); + if (p) { + routes = decode_rfc3442_rt(len, p); + if (routes) + return routes; + } + + /* OK, get our static routes first. */ + p = get_option(dhcp, DHCP_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, DHCP_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; +} + +ssize_t +make_message(struct dhcp_message **message, + const struct interface *iface, const struct dhcp_lease *lease, + uint32_t xid, uint8_t type, const struct options *options) +{ + struct dhcp_message *dhcp; + uint8_t *m, *p; + uint8_t *n_params = NULL; + time_t up = uptime() - iface->start_uptime; + uint32_t ul; + uint16_t sz; + const struct dhcp_opt *opt; +#ifndef MINIMAL + uint8_t *d; + const char *c; +#endif + + dhcp = xzalloc(sizeof (*dhcp)); + m = (uint8_t *)dhcp; + p = (uint8_t *)&dhcp->options; + + if ((type == DHCP_INFORM || + type == DHCP_RELEASE || + type == DHCP_REQUEST) && + !IN_LINKLOCAL(ntohl(iface->addr.s_addr))) + { + dhcp->ciaddr = iface->addr.s_addr; + /* Just incase we haven't actually configured the address yet */ + if (type == DHCP_INFORM && iface->addr.s_addr == 0) + dhcp->ciaddr = lease->addr.s_addr; + /* Zero the address if we're currently on a different subnet */ + if (type == DHCP_REQUEST && + iface->net.s_addr != lease->net.s_addr) + dhcp->ciaddr = 0; + } + + dhcp->op = DHCP_BOOTREQUEST; + dhcp->hwtype = iface->family; + switch (iface->family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + dhcp->hwlen = ETHER_ADDR_LEN; + memcpy(&dhcp->chaddr, &iface->hwaddr, ETHER_ADDR_LEN); + break; + case ARPHRD_IEEE1394: + case ARPHRD_INFINIBAND: + dhcp->hwlen = 0; + if (dhcp->ciaddr == 0) + dhcp->flags = htons(BROADCAST_FLAG); + break; + } + + if (up < 0 || up > (time_t)UINT16_MAX) + dhcp->secs = htons((uint16_t)UINT16_MAX); + else + dhcp->secs = htons(up); + dhcp->xid = xid; + dhcp->cookie = htonl(MAGIC_COOKIE); + + *p++ = DHCP_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (type == DHCP_REQUEST) { + *p++ = DHCP_MAXMESSAGESIZE; + *p++ = 2; + sz = get_mtu(iface->name); + if (sz < MTU_MIN) { + if (set_mtu(iface->name, MTU_MIN) == 0) + sz = MTU_MIN; + } + sz = htons(sz); + memcpy(p, &sz, 2); + p += 2; + } + +#ifndef MINIMAL + if (iface->clientid) { + *p++ = DHCP_CLIENTID; + memcpy(p, iface->clientid, iface->clientid[0] + 1); + p += iface->clientid[0] + 1; + } + + if (type != DHCP_DECLINE && type != DHCP_RELEASE) { + if (options->userclass[0]) { + *p++ = DHCP_USERCLASS; + memcpy(p, options->userclass, options->userclass[0] + 1); + p += options->userclass[0] + 1; + } + + if (options->classid[0]) { + *p++ = DHCP_CLASSID; + memcpy(p, options->classid, options->classid[0] + 1); + p += options->classid[0] + 1; + } + } +#endif + + if (type == DHCP_DISCOVER || type == DHCP_REQUEST) { +#define PUTADDR(_type, _val) \ + { \ + *p++ = _type; \ + *p++ = 4; \ + memcpy(p, &_val.s_addr, 4); \ + p += 4; \ + } + if (lease->addr.s_addr && + lease->addr.s_addr != iface->addr.s_addr && + !IN_LINKLOCAL(ntohl(lease->addr.s_addr))) + { + PUTADDR(DHCP_IPADDRESS, lease->addr); + if (lease->server.s_addr) + PUTADDR(DHCP_SERVERID, lease->server); + } +#undef PUTADDR + + if (options->leasetime != 0) { + *p++ = DHCP_LEASETIME; + *p++ = 4; + ul = htonl(options->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + if (type == DHCP_DISCOVER || + type == DHCP_INFORM || + type == DHCP_REQUEST) + { +#ifndef MINIMAL + if (options->hostname[0]) { + if (options->fqdn == FQDN_DISABLE) { + *p++ = DHCP_HOSTNAME; + memcpy(p, options->hostname, options->hostname[0] + 1); + p += options->hostname[0] + 1; + } else { + /* Draft IETF DHC-FQDN option (81) */ + *p++ = DHCP_FQDN; + *p++ = options->hostname[0] + 4; + /* + * 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++ = (options->fqdn & 0x9) | 0x4; + *p++ = 0; /* from server for PTR RR */ + *p++ = 0; /* from server for A RR if S=1 */ + c = options->hostname + 1; + d = p++; + while (*c) { + if (*c == '.') { + *d = p - d - 1; + d = p++; + } else + *p++ = (uint8_t) *c; + c++; + } + *p ++ = 0; + } + } + + /* vendor is already encoded correctly, so just add it */ + if (options->vendor[0]) { + *p++ = DHCP_VENDOR; + memcpy(p, options->vendor, options->vendor[0] + 1); + p += options->vendor[0] + 1; + } +#endif + + *p++ = DHCP_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (opt = dhcp_opts; opt->option; opt++) { + if (!(opt->type & REQUEST || + has_reqmask(options->reqmask, opt->option))) + continue; + switch (opt->option) { + case DHCP_RENEWALTIME: /* FALLTHROUGH */ + case DHCP_REBINDTIME: + if (type == DHCP_INFORM) + continue; + break; + } + *p++ = opt->option; + } + *n_params = p - n_params - 1; + } + *p++ = DHCP_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++ = DHCP_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; + + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400); +#ifdef ANDROID + if (fd == -1 && errno == EACCES) { + /* the lease file might have been created when dhcpcd was running as root */ + unlink(iface->leasefile); + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400); + } +#endif + if (fd == -1) + return -1; + + /* Only write as much as we need */ + while (p < e) { + o = *p; + if (o == DHCP_END) { + bytes = p - (const uint8_t *)dhcp; + break; + } + p++; + if (o != DHCP_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) + return NULL; + 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; + ssize_t bytes = 0; + ssize_t r; + + e = data + dl; + while (data < e) { + c = *data++; + 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 */ + 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 & RFC3442) + return decode_rfc3442(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; + else if (type & SINT16) + l = 6; + else if (type & UINT32) + l = 10; + else if (type & SINT32) + l = 11; + else if (type & IPV4) + l = 16; + 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; + len -= l; + bytes += l; + s += l; + } + + return bytes; +} + +static void +setvar(char ***e, const char *prefix, const char *var, const char *value) +{ + size_t len = strlen(prefix) + strlen(var) + strlen(value) + 4; + + **e = xmalloc(len); + snprintf(**e, len, "%s_%s=%s", prefix, var, value); + (*e)++; +} + +ssize_t +configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, + const struct options *options) +{ + unsigned int i; + 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, DHCP_OPTIONSOVERLOADED); + + if (!env) { + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (has_reqmask(options->nomask, opt->option)) + continue; + if (get_option_raw(dhcp, opt->option)) + e++; + } + if (dhcp->yiaddr) + e += 5; + if (*dhcp->bootfile && !(overl & 1)) + e++; + if (*dhcp->servername && !(overl & 2)) + e++; + return e; + } + + ep = env; + if (dhcp->yiaddr) { + /* Set some useful variables that we derive from the DHCP + * message but are not necessarily in the options */ + addr.s_addr = dhcp->yiaddr; + setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); + if (get_option_addr(&net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) { + net.s_addr = get_netmask(addr.s_addr); + setvar(&ep, prefix, "subnet_mask", inet_ntoa(net)); + } + i = inet_ntocidr(net); + snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); + setvar(&ep, prefix, "subnet_cidr", cidr); + if (get_option_addr(&brd.s_addr, dhcp, DHCP_BROADCAST) == -1) { + brd.s_addr = addr.s_addr | ~net.s_addr; + setvar(&ep, prefix, "broadcast_address", inet_ntoa(net)); + } + 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_reqmask(options->nomask, opt->option)) + continue; + val = NULL; + p = get_option(dhcp, opt->option, &pl, NULL); + if (!p) + continue; + 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; +} diff --git a/dhcp.h b/dhcp.h new file mode 100644 index 0000000..8b88c5e --- /dev/null +++ b/dhcp.h @@ -0,0 +1,185 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include + +#include + +#include "config.h" +#include "dhcpcd.h" +#include "net.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 + +/* DHCP options */ +enum DHCP_OPTIONS +{ + DHCP_PAD = 0, + DHCP_SUBNETMASK = 1, + DHCP_ROUTER = 3, + DHCP_DNSSERVER = 6, + DHCP_HOSTNAME = 12, + DHCP_DNSDOMAIN = 15, + DHCP_MTU = 26, + DHCP_BROADCAST = 28, + DHCP_STATICROUTE = 33, + DHCP_NISDOMAIN = 40, + DHCP_NISSERVER = 41, + DHCP_NTPSERVER = 42, + DHCP_VENDOR = 43, + DHCP_IPADDRESS = 50, + DHCP_LEASETIME = 51, + DHCP_OPTIONSOVERLOADED = 52, + DHCP_MESSAGETYPE = 53, + DHCP_SERVERID = 54, + DHCP_PARAMETERREQUESTLIST = 55, + DHCP_MESSAGE = 56, + DHCP_MAXMESSAGESIZE = 57, + DHCP_RENEWALTIME = 58, + DHCP_REBINDTIME = 59, + DHCP_CLASSID = 60, + DHCP_CLIENTID = 61, + DHCP_USERCLASS = 77, /* RFC 3004 */ + DHCP_FQDN = 81, + DHCP_DNSSEARCH = 119, /* RFC 3397 */ + DHCP_CSR = 121, /* RFC 3442 */ + DHCP_MSCSR = 249, /* MS code for RFC 3442 */ + DHCP_END = 255 +}; + +/* SetFQDNHostName values - lsnybble used in flags + * byte (see buildmsg.c), hsnybble to create order + * and to allow 0x00 to mean disable + */ +enum FQQN { + FQDN_DISABLE = 0x00, + FQDN_NONE = 0x18, + FQDN_PTR = 0x20, + FQDN_BOTH = 0x31 +}; + +struct fqdn +{ + uint8_t flags; + uint8_t r1; + uint8_t r2; + char *name; +}; + +/* Sizes for DHCP options */ +#define DHCP_CHADDR_LEN 16 +#define SERVERNAME_LEN 64 +#define BOOTFILE_LEN 128 +#define DHCP_UDP_LEN (20 + 8) +#define DHCP_BASE_LEN (4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4) +#define DHCP_RESERVE_LEN (4 + 4 + 4 + 4 + 2) +#define DHCP_FIXED_LEN (DHCP_BASE_LEN + DHCP_CHADDR_LEN + \ + + SERVERNAME_LEN + BOOTFILE_LEN) +#define DHCP_OPTION_LEN (MTU_MAX - DHCP_FIXED_LEN - DHCP_UDP_LEN \ + - DHCP_RESERVE_LEN) + +/* 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 */ +}; + +struct dhcp_lease { + struct in_addr addr; + struct in_addr net; + uint32_t leasetime; + uint32_t renewaltime; + uint32_t rebindtime; + struct in_addr server; + uint32_t leasedfrom; + uint8_t frominfo; +}; + +#define add_reqmask(var, val) (var[val >> 3] |= 1 << (val & 7)) +#define del_reqmask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) +#define has_reqmask(var, val) (var[val >> 3] & (1 << (val & 7))) +int make_reqmask(uint8_t *, char **, int); +void print_options(void); +char *get_option_string(const struct dhcp_message *, uint8_t); +int get_option_addr(uint32_t *, const struct dhcp_message *, uint8_t); +int get_option_uint32(uint32_t *, const struct dhcp_message *, uint8_t); +int get_option_uint16(uint16_t *, const struct dhcp_message *, uint8_t); +int get_option_uint8(uint8_t *, const struct dhcp_message *, uint8_t); +struct rt *get_option_routes(const struct dhcp_message *); +ssize_t configure_env(char **, const char *, const struct dhcp_message *, + const struct options *); + +ssize_t make_message(struct dhcp_message **, + const struct interface *, const struct dhcp_lease *, + uint32_t, uint8_t, const struct options *); +int valid_dhcp_packet(unsigned char *); + +ssize_t write_lease(const struct interface *, const struct dhcp_message *); +struct dhcp_message *read_lease(const struct interface *iface); +#endif diff --git a/dhcpcd-hooks/01-test b/dhcpcd-hooks/01-test new file mode 100644 index 0000000..d3ca40d --- /dev/null +++ b/dhcpcd-hooks/01-test @@ -0,0 +1,7 @@ +# Just echo our DHCP options we have + +case ${reason} in +TEST) set | grep "^\(interface\|metric\|pid\|reason\|skip_hooks\)=" | sort + set | grep "^\(new_\|old_\)" | sort + ;; +esac diff --git a/dhcpcd-hooks/10-mtu b/dhcpcd-hooks/10-mtu new file mode 100644 index 0000000..4265b48 --- /dev/null +++ b/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-hooks/20-dns.conf b/dhcpcd-hooks/20-dns.conf new file mode 100644 index 0000000..a92e91d --- /dev/null +++ b/dhcpcd-hooks/20-dns.conf @@ -0,0 +1,32 @@ +# Set net..dnsN properties that contain the +# DNS server addresses given by the DHCP server. + +set_dns_props() +{ + case "${new_domain_name_servers}" in + "") return 0;; + esac + + count=1 + for i in 1 2 3 4; do + setprop dhcp.${interface}.dns${i} "" + done + + count=1 + for dnsaddr in ${new_domain_name_servers}; do + setprop dhcp.${interface}.dns${count} ${dnsaddr} + count=$(($count + 1)) + done +} + +unset_dns_props() +{ + for i in 1 2 3 4; do + setprop dhcp.${interface}.dns${i} "" + done +} + +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-hooks/20-resolv.conf b/dhcpcd-hooks/20-resolv.conf new file mode 100644 index 0000000..437c116 --- /dev/null +++ b/dhcpcd-hooks/20-resolv.conf @@ -0,0 +1,40 @@ +# Generate /etc/resolv.conf +# Support resolvconf(8) if available + +make_resolv_conf() +{ + if [ -z "${new_domain_name_servers}" -a \ + -z "${new_domain_name}" -a \ + -z "${new_domain_search}" ]; then + return 0 + fi + local x= conf="${signature}\n" + if [ -n "${new_domain_search}" ]; then + conf="${conf}search ${new_domain_search}\n" + elif [ -n "${new_domain_name}" ]; then + conf="${conf}search ${new_domain_name}\n" + fi + for x in ${new_domain_name_servers}; do + conf="${conf}nameserver ${x}\n" + done + if type resolvconf >/dev/null 2>&1; then + printf "${conf}" | resolvconf -a "${interface}" + else + save_conf /etc/resolv.conf + printf "${conf}" > /etc/resolv.conf + fi +} + +restore_resolv_conf() +{ + if type resolvconf >/dev/null 2>&1; then + resolvconf -d "${interface}" -f + else + restore_conf /etc/resolv.conf || return 0 + fi +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) make_resolv_conf;; +EXPIRE|FAIL|IPV4LL|RELEASE|STOP) restore_resolv_conf;; +esac diff --git a/dhcpcd-hooks/29-lookup-hostname b/dhcpcd-hooks/29-lookup-hostname new file mode 100644 index 0000000..b9ce458 --- /dev/null +++ b/dhcpcd-hooks/29-lookup-hostname @@ -0,0 +1,33 @@ +# Lookup the hostname in DNS if not set + +lookup_hostname() +{ + 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}" ]; then + export new_host_name="$(lookup_hostname)" + fi +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) set_hostname;; +esac diff --git a/dhcpcd-hooks/30-hostname b/dhcpcd-hooks/30-hostname new file mode 100644 index 0000000..500ec0f --- /dev/null +++ b/dhcpcd-hooks/30-hostname @@ -0,0 +1,21 @@ +# Set the hostname from DHCP data if required + +need_hostname() +{ + case "$(hostname)" in + ""|"(none)"|localhost) [ -n "${new_host_name}" ];; + "${old_host_name}") true;; + *) false;; + esac +} + +set_hostname() +{ + if need_hostname; then + hostname "${new_host_name}" + fi +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) set_hostname;; +esac diff --git a/dhcpcd-hooks/50-dhcpcd-compat b/dhcpcd-hooks/50-dhcpcd-compat new file mode 100644 index 0000000..cba40a4 --- /dev/null +++ b/dhcpcd-hooks/50-dhcpcd-compat @@ -0,0 +1,31 @@ +# 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 +set -- "" "${x}" diff --git a/dhcpcd-hooks/50-ntp.conf b/dhcpcd-hooks/50-ntp.conf new file mode 100644 index 0000000..3772215 --- /dev/null +++ b/dhcpcd-hooks/50-ntp.conf @@ -0,0 +1,51 @@ +# Sample dhcpcd hook script for ntp + +# Detect OpenRC or BSD rc +# Distributions may want to just have their command here instead of this +if type rc-service >/dev/null 2>&1 && rc-service --exists ntpd; then + ntpd_restart_cmd="rc-service ntpd -- --ifstarted --quiet restart" +elif [ -x /etc/rc.d/ntpd ]; then + ntpd_restart_cmd="/etc/rc.d/ntpd restart" +elif [ -x /usr/local/etc/rc.d/ntpd ]; then + ntpd_restart_cmd="/usr/local/etc/rc.d/ntpd restart" +fi + +make_ntp_conf() +{ + [ -z "${new_ntp_servers}" ] && return 0 + local cf=/etc/ntp.conf."${interface}" x= + echo "${signature}" > "${cf}" + echo "restrict default noquery notrust nomodify" >> "${cf}" + echo "restrict 127.0.0.1" >> "${cf}" + for x in ${new_ntp_servers}; do + echo "restrict ${x} nomodify notrap noquery" >> "${cf}" + echo "server ${x}" >> "${cf}" + done + if [ ! -e /etc/ntp.conf ]; then + false + elif type cmp >/dev/null 2>&1; then + cmp -s /etc/ntp.conf "${cf}" + elif type diff >/dev/null 2>&1; then + diff -q /etc/ntp.conf "${cf}" >/dev/null + else + false + fi + if [ $? = 0 ]; then + rm -f "${cf}" + else + save_conf /etc/ntp.conf + mv -f "${cf}" /etc/ntp.conf + [ -n "${ntpd_restart_cmd}" ] && ${ntpd_restart_cmd} + fi +} + +restore_ntp_conf() +{ + restore_conf /etc/ntp.conf || return 0 + [ -n "${ntpd_restart_cmd}" ] && ${ntpd_restart_cmd} +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) make_ntp_conf;; +EXPIRE|FAIL|IPV4LL|RELEASE|STOP) restore_ntp_conf;; +esac diff --git a/dhcpcd-hooks/50-yp.conf b/dhcpcd-hooks/50-yp.conf new file mode 100644 index 0000000..603f267 --- /dev/null +++ b/dhcpcd-hooks/50-yp.conf @@ -0,0 +1,48 @@ +# 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= + 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 + mv -f "${cf}" /etc/yp.conf + 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 +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) make_yp_conf;; +EXPIRE|FAIL|IPV4LL|RELEASE|STOP) restore_yp_conf;; +esac diff --git a/dhcpcd-hooks/90-NetworkManager b/dhcpcd-hooks/90-NetworkManager new file mode 100644 index 0000000..f05ccd7 --- /dev/null +++ b/dhcpcd-hooks/90-NetworkManager @@ -0,0 +1,7 @@ +# Hook for NetworkManager, relies on D-Bus + +if type dbus-send >/dev/null 2>&1; then + dbus-send --system --dest=com.redhat.dhcp \ + --type=method_call /com/redhat/dhcp/"${interface}" \ + com.redhat.dhcp.set 'string:'"`env`" +fi diff --git a/dhcpcd-hooks/95-configured b/dhcpcd-hooks/95-configured new file mode 100644 index 0000000..1ff07cf --- /dev/null +++ b/dhcpcd-hooks/95-configured @@ -0,0 +1,22 @@ +# 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. + +# For debugging: +setprop dhcp.${interface}.reason "${reason}" + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) + setprop dhcp.${interface}.ipaddress "${new_ip_address}" + setprop dhcp.${interface}.gateway "${new_routers%% *}" + setprop dhcp.${interface}.mask "${new_subnet_mask}" + setprop dhcp.${interface}.leasetime "${new_dhcp_lease_time}" + setprop dhcp.${interface}.server "${new_dhcp_server_identifier}" + + setprop dhcp.${interface}.result "ok" + ;; + +EXPIRE|FAIL|IPV4LL|RELEASE|STOP) + setprop dhcp.${interface}.result "failed" + ;; +esac diff --git a/dhcpcd-hooks/Makefile b/dhcpcd-hooks/Makefile new file mode 100644 index 0000000..06660ac --- /dev/null +++ b/dhcpcd-hooks/Makefile @@ -0,0 +1,11 @@ +LIBEXECDIR= ${PREFIX}/libexec +HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks +SYSTEMSCRIPTS= 01-test 10-mtu 20-resolv.conf 30-hostname +FILES= ${SYSTEMSCRIPTS} ${HOOKSCRIPTS} +FILESDIR= ${HOOKDIR} + +MK= ../mk +include ${MK}/os.mk +include ${MK}/sys.mk +include ${MK}/files.mk +install: _filesinstall diff --git a/dhcpcd-run-hooks b/dhcpcd-run-hooks new file mode 100755 index 0000000..83534be --- /dev/null +++ b/dhcpcd-run-hooks @@ -0,0 +1,38 @@ +#!/system/bin/sh +# dhcpcd client configuration script + +# Handy functions for our hooks to use +signature="# Generated by dhcpcd for ${interface}" +save_conf() +{ + if ls "$1" >/dev/null 2>&1; then + rm -f "$1"-pre."${interface}" + mv -f "$1" "$1"-pre."${interface}" + fi +} +restore_conf() +{ + ls "$1"-pre."${interface}" >/dev/null 2>&1 || return 1 + rm -f "$1" + mv -f "$1"-pre."${interface}" "$1" +} + +# 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.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.hook \ + /system/etc/dhcpcd/dhcpcd-hooks/* +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 ls "${hook}" >/dev/null 2>&1; then + . "${hook}" + fi +done diff --git a/dhcpcd-run-hooks.8 b/dhcpcd-run-hooks.8 new file mode 100644 index 0000000..2ba9792 --- /dev/null +++ b/dhcpcd-run-hooks.8 @@ -0,0 +1,105 @@ +.\" Copyright 2006-2008 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd May 21, 2008 +.Dt DHCPCD.SH 8 SMM +.Sh NAME +.Nm dhcpcd-run-hooks +.Nd DHCP client configuration script +.Sh DESCRIPTION +.Nm +is used by +.Xr dhcpcd 8 +to run any system or user defined hook scripts. +System hook scripts are found in +.Pa /system/etc/dhcpcd/dhcpcd-hooks +and the user defined hook is +.Pa /system/etc/dhcpcd/dhcpcd.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 indent +.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 EXPIRE +dhcpcd's lease expired and it failed to obtain a new one. +.It Dv IPV4LL +dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. +.It Dv FAIL +dhcpcd failed to contact any DHCP servers or use an old lease. +.It Dv TIMEOUT +dhcpcd failed to contact any DHCP servers but was able to use an old lease. +.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 /system/etc/dhcpcd/dhcpcd.hook +and any scripts found in +.Pa /system/etc/dhcpcd/dhcpcd-hooks +in a lexical order. +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd-run-hooks.8.in b/dhcpcd-run-hooks.8.in new file mode 100644 index 0000000..a545203 --- /dev/null +++ b/dhcpcd-run-hooks.8.in @@ -0,0 +1,105 @@ +.\" Copyright 2006-2008 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd May 21, 2008 +.Dt DHCPCD.SH 8 SMM +.Sh NAME +.Nm dhcpcd-run-hooks +.Nd DHCP client configuration script +.Sh DESCRIPTION +.Nm +is used by +.Xr dhcpcd 8 +to run any system or user defined hook scripts. +System hook scripts are found in +.Pa @HOOKDIR@ +and the user defined hook is +.Pa @SYSCONFDIR@/dhcpcd.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 indent +.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 EXPIRE +dhcpcd's lease expired and it failed to obtain a new one. +.It Dv IPV4LL +dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. +.It Dv FAIL +dhcpcd failed to contact any DHCP servers or use an old lease. +.It Dv TIMEOUT +dhcpcd failed to contact any DHCP servers but was able to use an old lease. +.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 @SYSCONFDIR@/dhcpcd.hook +and any scripts found in +.Pa @HOOKDIR@ +in a lexical order. +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd-run-hooks.in b/dhcpcd-run-hooks.in new file mode 100644 index 0000000..7fd8b09 --- /dev/null +++ b/dhcpcd-run-hooks.in @@ -0,0 +1,38 @@ +#!/bin/sh +# dhcpcd client configuration script + +# Handy functions for our hooks to use +signature="# Generated by dhcpcd for ${interface}" +save_conf() +{ + if [ -f "$1" ]; then + rm -f "$1"-pre."${interface}" + mv -f "$1" "$1"-pre."${interface}" + fi +} +restore_conf() +{ + [ -f "$1"-pre."${interface}" ] || return 1 + rm -f "$1" + mv -f "$1"-pre."${interface}" "$1" +} + +# 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.hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + @SYSCONFDIR@/dhcpcd.hook \ + @HOOKDIR@/* +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.8 b/dhcpcd.8 new file mode 100644 index 0000000..fff2ca0 --- /dev/null +++ b/dhcpcd.8 @@ -0,0 +1,384 @@ +.\" Copyright 2006-2008 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd Jul 08, 2008 +.Dt DHCPCD 8 SMM +.Sh NAME +.Nm dhcpcd +.Nd an RFC 2131 compliant DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl dknpqADEGLSTXV +.Op Fl c , -script Ar script +.Op Fl f , -config Ar file +.Op Fl h , -hostname Ar hostname +.Op Fl i , -classid Ar classid +.Op Fl l , -leasetime Ar seconds +.Op Fl m , -metric Ar metric +.Op Fl o , -option Ar option +.Op Fl r , -request Ar address +.Op Fl s , -inform Ar address Ns Op Ar /cidr +.Op Fl t , -timeout Ar seconds +.Op Fl u , -userclass Ar class +.Op Fl v , -vendor Ar code , Ar value +.Op Fl C , -nohook Ar hook +.Op Fl F , -fqdn Ar FQDN +.Op Fl I , -clientid Ar clientid +.Op Fl O , -nooption Ar option +.Ar interface +.Nm +.Fl k , -release +.Ar interface +.Nm +.Fl x , -exit +.Ar interface +.Sh DESCRIPTION +.Nm +is an implementation of the DHCP client specified in +.Rs +.%T "RFC 2131" +.Re +.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 +will then write DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currenly blank, (null) or localhost then +.Nm +will set the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +Then it attempts to renew its lease and reconfigure if the new lease changes. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it will probe for a valid IPv4LL address +.Po +aka Zeroconf, aka APIPA +.Pc . +Once obtained it will restart the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +will always succeed and return a 0 exit code. To disable this behaviour, you +can use the +.Fl L , -noipv4ll +option. +.Ss Hooking into DHCP events +.Nm +will run +.Pa /system/etc/dhcpcd/dhcpcd-run-hooks , +or the script specified by the +.Fl c , -script +option. +This script will run each script found in +.Pa /system/etc/dhcpcd/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 , -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 c , -script Ar script +Use this +.Ar script +instead of the default +.Pa /system/etc/dhcpcd/dhcpcd-run-hooks . +.It Fl d , -debug +Echo debug and informational messages to the console. +Subsequent debug options stop +.Nm +from daemonising. +.It Fl f , -config Ar file +Specify a config to load instead of +.Pa /system/etc/dhcpcd/dhcpcd.conf . +.Nm +always processes the config file before any command line options. +.It Fl h , -hostname Ar hostname +By default, +.Nm +will send the current hostname to the DHCP server so it can register in DNS. +You can use this option to specify the +.Ar hostname +sent, or an empty string to +stop any +.Ar hostname +from being sent. +.It Fl i , -classid Ar classid +Override the +.Ar classid +field sent. The default is +dhcpcd . +If not set then none is sent. +.It Fl k , -release +This causes an existing +.Nm +process running on the +.Ar interface +to release its lease, deconfigure the +.Ar interface +and then exit. +.It Fl l , -leasetime Ar seconds +Request a specific lease time in +.Ar seconds . +By default +.Nm +does not request any lease time and leaves the it in the hands of the +DHCP server. +.It Fl m , -metric Ar metric +Added routes will use the +.Ar metric +on systems where this is supported +.Po +presently only Linux +.Pc . +Route metrics allow the addition of routes to the same destination across +different interfaces, the lower the metric the more it is preferred. +.It Fl o , -option Ar option +Request the DHCP +.Ar option +variable for use in +.Pa /system/etc/dhcpcd/dhcpcd-run-hooks . +.It Fl n , -renew +Notifies an existing +.Nm +process running on the +.Ar interface +to renew it's lease. If +.Nm +is not running, then it starts up as normal. +.It Fl p , -persistent +.Nm +normally deconfigures 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 , -request Op Ar address +.Nm +normally sends a DHCP Broadcast to find servers to offer an address. +.Nm +will then request the address used. +You can use this option to skip the broadcast step and just request an +.Ar address . +The downside is if you request an +.Ar address +the DHCP server does not know about or the DHCP server is not +authorative, it will remain silent. +In this situation, we go back to the init state and broadcast again. +If no +.Ar address +is given then the first address currently assigned to the +.Ar interface +is used. +.It Fl s , -inform Op Ar address Ns Op Ar /cidr +Behaves exactly like +.Fl r , -request +as above, but sends a DHCP INFORM instead of a REQUEST. +This does not get a lease as such, just notifies the DHCP server of the +.Ar address +in use. +.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 , -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 , -userclass Ar class +Tags the DHCP message with the userclass +.Ar class . +DHCP servers use this give memebers of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , -vendor Ar code , Ns Ar value +Add an enscapulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 dhcpcd -v 01,192.168.0.2 eth0 +Set the vendor option 02 with a hex code. +.D1 dhcpcd -v 02,01:02:03:04:05 eth0 +Do the above and set a third option with a string and not an IP address. +.D1 dhcpcd -v 01,192.168.0.2 -v 02,01:02:03:04:05 -v 03,\e"192.168.0.2\e" eth0 +.It Fl x , -exit +This causes an existing +.Nm +process running on the +.Ar interface +to deconfigure the +.Ar interface +and exit. +.It Fl D , -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 /system/etc/dhcpcd/dhcpcd.duid +and should not be copied to other hosts. +.It Fl E , -lastlease +If +.Nm +cannot obtain a lease, then try to use the last lease acquired for the +interface. +If the +.Fl p, -persistent +option is not given then the lease is used if it hasn't expired. +.It Fl F , -fqdn Ar fqdn +Requests that the DHCP server updates DNS using FQDN instead of just a +hostname. +Valid values for +.Ar fqdn +are none, ptr and both. +The current hostname or the hostname specified using the +.Fl h , -hostname +option must be a FQDN. +.Nm +itself never does any DNS updates. +.It Fl I , -clientid Ar clientid +Change the default clientid sent from the interface hardware address. +If the string is of the format 01:02:03 then it is encoded as hex. +If not set then none is sent. +.El +.Ss Restriciting 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 q , -quiet +Quiet +.Nm +on the command line, only warnings and errors will be displayed. +The messagea are still logged though. +.It Fl A , -noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl G , -nogateway +Don't set any default routes. +.It Fl C , -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 dhcpcd from touching your DNS or MTU settings you would do:- +.D1 dhcpcd -C resolv.conf -C mtu eth0 +.It Fl X , -nodaemonise +Don't daemonise when we acquire a lease. +This disables the +.Fl t, -timeout +option. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl L , -noipv4ll +Don't use IPv4LL at all. +.It Fl O , -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 T, -test +On receipt of OFFER messages just call +.Pa /system/etc/dhcpcd/dhcpcd-run-hooks +with the reason of TEST which echo's the DHCP variables found in the message +to the console. +The interface configuration isn't touched and neither are any configuration +files. +.It Fl V, -variables +Display a list of option codes and the associated variable for use in +.Xr dhcpcd-run-hooks 8 . +.El +.Sh NOTES +.Nm +requires a Berkley Packet Filter, or BPF device on BSD based systems and a +Linux Socket Filter, or LPF device on Linux based systems. +.Sh FILES +.Bl -ohang +.It Pa /system/etc/dhcpcd/dhcpcd.conf +Configuration file for dhcpcd. +If you always use the same options, put them here. +.It Pa /system/etc/dhcpcd/dhcpcd.duid +Text file that holds the DUID used to identify the host. +.It Pa /system/etc/dhcpcd/dhcpcd-run-hooks +Bourne shell script that is run to configure or deconfigure an interface. +.It Pa /system/etc/dhcpcd/dhcpcd-hooks +A directory containing bourne shell scripts that are run by the above script. +Each script can be disabled by using the +.Fl C , -nohook +option described above. +.It Pa /data/misc/dhcp/dhcpcd\- Ns Ar interface Ns .lease +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. +.El +.Sh SEE ALSO +.Xr dhcpcd.conf 5 , +.Xr dhcpcd-run-hooks 8 , +.Xr resolv.conf 5 , +.Xr resolvconf 8 , +.Sh STANDARDS +RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, RFC 3397, +RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. +.Sh AUTHORS +.An Roy Marples +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd.8.in b/dhcpcd.8.in new file mode 100644 index 0000000..c008a5a --- /dev/null +++ b/dhcpcd.8.in @@ -0,0 +1,384 @@ +.\" Copyright 2006-2008 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd Jul 08, 2008 +.Dt DHCPCD 8 SMM +.Sh NAME +.Nm dhcpcd +.Nd an RFC 2131 compliant DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl dknpqADEGLSTXV +.Op Fl c , -script Ar script +.Op Fl f , -config Ar file +.Op Fl h , -hostname Ar hostname +.Op Fl i , -classid Ar classid +.Op Fl l , -leasetime Ar seconds +.Op Fl m , -metric Ar metric +.Op Fl o , -option Ar option +.Op Fl r , -request Ar address +.Op Fl s , -inform Ar address Ns Op Ar /cidr +.Op Fl t , -timeout Ar seconds +.Op Fl u , -userclass Ar class +.Op Fl v , -vendor Ar code , Ar value +.Op Fl C , -nohook Ar hook +.Op Fl F , -fqdn Ar FQDN +.Op Fl I , -clientid Ar clientid +.Op Fl O , -nooption Ar option +.Ar interface +.Nm +.Fl k , -release +.Ar interface +.Nm +.Fl x , -exit +.Ar interface +.Sh DESCRIPTION +.Nm +is an implementation of the DHCP client specified in +.Rs +.%T "RFC 2131" +.Re +.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 +will then write DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currenly blank, (null) or localhost then +.Nm +will set the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +Then it attempts to renew its lease and reconfigure if the new lease changes. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it will probe for a valid IPv4LL address +.Po +aka Zeroconf, aka APIPA +.Pc . +Once obtained it will restart the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +will always succeed and return a 0 exit code. To disable this behaviour, you +can use the +.Fl L , -noipv4ll +option. +.Ss Hooking into DHCP events +.Nm +will run +.Pa @SCRIPT@ , +or the script specified by the +.Fl c , -script +option. +This script will run 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 , -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 c , -script Ar script +Use this +.Ar script +instead of the default +.Pa @SCRIPT@ . +.It Fl d , -debug +Echo debug and informational messages to the console. +Subsequent debug options stop +.Nm +from daemonising. +.It Fl f , -config Ar file +Specify a config to load instead of +.Pa @SYSCONFDIR@/dhcpcd.conf . +.Nm +always processes the config file before any command line options. +.It Fl h , -hostname Ar hostname +By default, +.Nm +will send the current hostname to the DHCP server so it can register in DNS. +You can use this option to specify the +.Ar hostname +sent, or an empty string to +stop any +.Ar hostname +from being sent. +.It Fl i , -classid Ar classid +Override the +.Ar classid +field sent. The default is +dhcpcd . +If not set then none is sent. +.It Fl k , -release +This causes an existing +.Nm +process running on the +.Ar interface +to release its lease, deconfigure the +.Ar interface +and then exit. +.It Fl l , -leasetime Ar seconds +Request a specific lease time in +.Ar seconds . +By default +.Nm +does not request any lease time and leaves the it in the hands of the +DHCP server. +.It Fl m , -metric Ar metric +Added routes will use the +.Ar metric +on systems where this is supported +.Po +presently only Linux +.Pc . +Route metrics allow the addition of routes to the same destination across +different interfaces, the lower the metric the more it is preferred. +.It Fl o , -option Ar option +Request the DHCP +.Ar option +variable for use in +.Pa @SCRIPT@ . +.It Fl n , -renew +Notifies an existing +.Nm +process running on the +.Ar interface +to renew it's lease. If +.Nm +is not running, then it starts up as normal. +.It Fl p , -persistent +.Nm +normally deconfigures 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 , -request Op Ar address +.Nm +normally sends a DHCP Broadcast to find servers to offer an address. +.Nm +will then request the address used. +You can use this option to skip the broadcast step and just request an +.Ar address . +The downside is if you request an +.Ar address +the DHCP server does not know about or the DHCP server is not +authorative, it will remain silent. +In this situation, we go back to the init state and broadcast again. +If no +.Ar address +is given then the first address currently assigned to the +.Ar interface +is used. +.It Fl s , -inform Op Ar address Ns Op Ar /cidr +Behaves exactly like +.Fl r , -request +as above, but sends a DHCP INFORM instead of a REQUEST. +This does not get a lease as such, just notifies the DHCP server of the +.Ar address +in use. +.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 , -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 , -userclass Ar class +Tags the DHCP message with the userclass +.Ar class . +DHCP servers use this give memebers of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , -vendor Ar code , Ns Ar value +Add an enscapulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +Examples. +.Pp +Set the vendor option 01 with an IP address. +.D1 dhcpcd -v 01,192.168.0.2 eth0 +Set the vendor option 02 with a hex code. +.D1 dhcpcd -v 02,01:02:03:04:05 eth0 +Do the above and set a third option with a string and not an IP address. +.D1 dhcpcd -v 01,192.168.0.2 -v 02,01:02:03:04:05 -v 03,\e"192.168.0.2\e" eth0 +.It Fl x , -exit +This causes an existing +.Nm +process running on the +.Ar interface +to deconfigure the +.Ar interface +and exit. +.It Fl D , -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 Fl E , -lastlease +If +.Nm +cannot obtain a lease, then try to use the last lease acquired for the +interface. +If the +.Fl p, -persistent +option is not given then the lease is used if it hasn't expired. +.It Fl F , -fqdn Ar fqdn +Requests that the DHCP server updates DNS using FQDN instead of just a +hostname. +Valid values for +.Ar fqdn +are none, ptr and both. +The current hostname or the hostname specified using the +.Fl h , -hostname +option must be a FQDN. +.Nm +itself never does any DNS updates. +.It Fl I , -clientid Ar clientid +Change the default clientid sent from the interface hardware address. +If the string is of the format 01:02:03 then it is encoded as hex. +If not set then none is sent. +.El +.Ss Restriciting 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 q , -quiet +Quiet +.Nm +on the command line, only warnings and errors will be displayed. +The messagea are still logged though. +.It Fl A , -noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl G , -nogateway +Don't set any default routes. +.It Fl C , -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 dhcpcd from touching your DNS or MTU settings you would do:- +.D1 dhcpcd -C resolv.conf -C mtu eth0 +.It Fl X , -nodaemonise +Don't daemonise when we acquire a lease. +This disables the +.Fl t, -timeout +option. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl L , -noipv4ll +Don't use IPv4LL at all. +.It Fl O , -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 T, -test +On receipt of OFFER messages just call +.Pa @SCRIPT@ +with the reason of TEST which echo's the DHCP variables found in the message +to the console. +The interface configuration isn't touched and neither are any configuration +files. +.It Fl V, -variables +Display a list of option codes and the associated variable for use in +.Xr dhcpcd-run-hooks 8 . +.El +.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 deconfigure 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 , -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. +.El +.Sh SEE ALSO +.Xr dhcpcd.conf 5 , +.Xr dhcpcd-run-hooks 8 , +.Xr resolv.conf 5 , +.Xr resolvconf 8 , +.Sh STANDARDS +RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396, RFC 3397, +RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. +.Sh AUTHORS +.An Roy Marples +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd.c b/dhcpcd.c new file mode 100644 index 0000000..4a6e4af --- /dev/null +++ b/dhcpcd.c @@ -0,0 +1,1041 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "client.h" +#include "dhcpcd.h" +#include "dhcp.h" +#include "net.h" +#include "logger.h" + +#ifdef ANDROID +#include +#include +#include +#endif + +/* Don't set any optional arguments here so we retain POSIX + * compatibility with getopt */ +#define OPTS "c:df:h:i:kl:m:no:pqr:s:t:u:v:xAC:DEF:GI:LO:TVX" + +static int doversion = 0; +static int dohelp = 0; +static const struct option longopts[] = { + {"script", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"config", required_argument, NULL, 'f'}, + {"hostname", optional_argument, NULL, 'h'}, + {"classid", optional_argument, NULL, 'i'}, + {"release", no_argument, NULL, 'k'}, + {"leasetime", required_argument, NULL, 'l'}, + {"metric", required_argument, NULL, 'm'}, + {"renew", no_argument, NULL, 'n'}, + {"option", required_argument, NULL, 'o'}, + {"persistent", no_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"inform", optional_argument, NULL, 's'}, + {"request", optional_argument, NULL, 'r'}, + {"timeout", required_argument, NULL, 't'}, + {"userclass", required_argument, NULL, 'u'}, + {"vendor", required_argument, NULL, 'v'}, + {"exit", no_argument, NULL, 'x'}, + {"noarp", no_argument, NULL, 'A'}, + {"nohook", required_argument, NULL, 'C'}, + {"duid", no_argument, NULL, 'D'}, + {"lastlease", no_argument, NULL, 'E'}, + {"fqdn", optional_argument, NULL, 'F'}, + {"nogateway", no_argument, NULL, 'G'}, + {"clientid", optional_argument, NULL, 'I'}, + {"noipv4ll", no_argument, NULL, 'L'}, + {"nooption", optional_argument, NULL, 'O'}, + {"test", no_argument, NULL, 'T'}, + {"variables", no_argument, NULL, 'V'}, + {"nodaemonise", no_argument, NULL, 'X'}, + {"help", no_argument, &dohelp, 1}, + {"version", no_argument, &doversion, 1}, +#ifdef THERE_IS_NO_FORK + {"daemonised", no_argument, NULL, 'z'}, + {"skiproutes", required_argument, NULL, 'Z'}, +#endif +#ifdef CMDLINE_COMPAT + {"nohostname", no_argument, NULL, 'H'}, + {"nomtu", no_argument, NULL, 'M'}, + {"nontp", no_argument, NULL, 'N'}, + {"nodns", no_argument, NULL, 'R'}, + {"msscr", no_argument, NULL, 'S'}, + {"nonis", no_argument, NULL, 'Y'}, +#endif + {NULL, 0, NULL, '\0'} +}; + +#ifdef THERE_IS_NO_FORK +char dhcpcd[PATH_MAX]; +char **dhcpcd_argv = NULL; +int dhcpcd_argc = 0; +char *dhcpcd_skiproutes = NULL; +#define EXTRA_OPTS "zZ:" +#endif + +#ifdef CMDLINE_COMPAT +# define EXTRA_OPTS "HMNRSY" +#endif + +#ifndef EXTRA_OPTS +# define EXTRA_OPTS +#endif + +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))) + { + logger(LOG_ERR, "`%s' out of range", s); + return -1; + } + + return (int)n; +} + +static pid_t +read_pid(const char *pidfile) +{ + FILE *fp; + pid_t pid = 0; + + if ((fp = fopen(pidfile, "r")) == NULL) { + errno = ENOENT; + return 0; + } + + fscanf(fp, "%d", &pid); + fclose(fp); + + return pid; +} + +static void +usage(void) +{ +#ifndef MINIMAL + printf("usage: "PACKAGE" [-dknpqxADEGHKLOTV] [-c script] [-f file ] [-h hostname]\n" + " [-i classID ] [-l leasetime] [-m metric] [-o option] [-r ipaddr]\n" + " [-s ipaddr] [-t timeout] [-u userclass] [-F none|ptr|both]\n" + " [-I clientID] [-C hookscript] \n"); +#endif +} + +static char * +add_environ(struct options *options, const char *value, int uniq) +{ + char **newlist; + char **lst = options->environ; + size_t i = 0, l, lv; + char *match = NULL, *p; + + match = xstrdup(value); + p = strchr(match, '='); + if (p) + *p++ = '\0'; + l = strlen(match); + + while (lst && lst[i]) { + if (match && strncmp(lst[i], match, l) == 0) { + if (uniq) { + free(lst[i]); + lst[i] = xstrdup(value); + } 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; + options->environ = newlist; + free(match); + return newlist[i]; +} + +#ifndef MINIMAL +#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) +static ssize_t +parse_string_hwaddr(char *sbuf, ssize_t slen, char *str, int clid) +{ + ssize_t l; + char *p; + int i; + char c[4]; + + /* If surrounded by quotes then it's a string */ + if (*str == '"') { + str++; + l = strlen(str); + p = str + l - 1; + if (*p == '"') + *p = '\0'; + } 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) { + *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'; + break; + case 'n': + *sbuf++ = '\n'; + break; + case 'r': + *sbuf++ = '\r'; + break; + case 't': + *sbuf++ = '\t'; + break; + case 'x': + /* Grab a hex code */ + c[1] = '\0'; + for (i = 0; i < 2; i++) { + if (isxdigit((unsigned char)*str) == 0) + break; + c[i] = *str++; + } + if (c[1] != '\0') { + c[2] = '\0'; + *sbuf++ = strtol(c, NULL, 16); + } else + l--; + 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++; + } + return l; +} +#endif + +static int +parse_option(int opt, char *oarg, struct options *options) +{ + int i; + char *p; + ssize_t s; +#ifndef MINIMAL + struct in_addr addr; +#endif + + switch(opt) { + case 'c': + strlcpy(options->script, oarg, sizeof(options->script)); + break; + case 'h': +#ifndef MINIMAL + if (oarg) + s = parse_string(options->hostname + 1, + MAXHOSTNAMELEN, oarg); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "hostname: %s", strerror(errno)); + return -1; + } + options->hostname[0] = (uint8_t)s; +#endif + break; + case 'i': +#ifndef MINIMAL + if (oarg) + s = parse_string((char *)options->classid + 1, + CLASSID_MAX_LEN, oarg); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "classid: %s", strerror(errno)); + return -1; + } + *options->classid = (uint8_t)s; +#endif + break; + case 'l': +#ifndef MINIMAL + if (*oarg == '-') { + logger(LOG_ERR, + "leasetime must be a positive value"); + return -1; + } + errno = 0; + options->leasetime = (uint32_t)strtol(oarg, NULL, 0); + if (errno == EINVAL || errno == ERANGE) { + logger(LOG_ERR, "`%s' out of range", oarg); + return -1; + } +#endif + break; + case 'm': + options->metric = atoint(oarg); + if (options->metric < 0) { + logger(LOG_ERR, "metric must be a positive value"); + return -1; + } + break; + case 'o': + if (make_reqmask(options->reqmask, &oarg, 1) != 0) { + logger(LOG_ERR, "unknown option `%s'", oarg); + return -1; + } + break; + case 'p': + options->options |= DHCPCD_PERSISTENT; + break; + case 'q': + setloglevel(LOG_WARNING); + break; + case 's': + options->options |= DHCPCD_INFORM; + options->options |= DHCPCD_PERSISTENT; + options->options &= ~DHCPCD_ARP; + if (!oarg || *oarg == '\0') { + options->request_address.s_addr = 0; + break; + } else { + if ((p = strchr(oarg, '/'))) { + /* nullify the slash, so the -r option + * can read the address */ + *p++ = '\0'; + if (sscanf(p, "%d", &i) != 1 || + inet_cidrtoaddr(i, &options->request_netmask) != 0) + { + logger(LOG_ERR, + "`%s' is not a valid CIDR", + p); + return -1; + } + } + } + /* FALLTHROUGH */ + case 'r': + if (!(options->options & DHCPCD_INFORM)) + options->options |= DHCPCD_REQUEST; + if (*oarg && !inet_aton(oarg, &options->request_address)) { + logger(LOG_ERR, "`%s' is not a valid IP address", + oarg); + return -1; + } + break; + case 't': + options->timeout = atoint(oarg); + if (options->timeout < 0) { + logger (LOG_ERR, "timeout must be a positive value"); + return -1; + } + break; + case 'u': +#ifndef MINIMAL + s = USERCLASS_MAX_LEN - options->userclass[0] - 1; + s = parse_string((char *)options->userclass + options->userclass[0] + 2, + s, oarg); + if (s == -1) { + logger(LOG_ERR, "userclass: %s", strerror(errno)); + return -1; + } + if (s != 0) { + options->userclass[options->userclass[0] + 1] = s; + options->userclass[0] += s + 1; + } +#endif + break; + case 'v': +#ifndef MINIMAL + p = strchr(oarg, ','); + if (!p || !p[1]) { + logger(LOG_ERR, "invalid vendor format"); + return -1; + } + *p = '\0'; + i = atoint(oarg); + oarg = p + 1; + if (i < 1 || i > 254) { + logger(LOG_ERR, "vendor option should be between" + " 1 and 254 inclusive"); + return -1; + } + s = VENDOR_MAX_LEN - options->vendor[0] - 2; + if (inet_aton(oarg, &addr) == 1) { + if (s < 6) { + s = -1; + errno = ENOBUFS; + } else + memcpy(options->vendor + options->vendor[0] + 3, + &addr.s_addr, sizeof(addr.s_addr)); + } else { + s = parse_string((char *)options->vendor + options->vendor[0] + 3, + s, oarg); + } + if (s == -1) { + logger(LOG_ERR, "vendor: %s", strerror(errno)); + return -1; + } + if (s != 0) { + options->vendor[options->vendor[0] + 1] = i; + options->vendor[options->vendor[0] + 2] = s; + options->vendor[0] += s + 2; + } +#endif + break; + case 'A': + options->options &= ~DHCPCD_ARP; + /* IPv4LL requires ARP */ + options->options &= ~DHCPCD_IPV4LL; + break; + case 'C': + /* Commas to spaces for shell */ + while ((p = strchr(oarg, ','))) + *p = ' '; + s = strlen("skip_hooks=") + strlen(oarg) + 1; + p = xmalloc(sizeof(char) * s); + snprintf(p, s, "skip_hooks=%s", oarg); + add_environ(options, p, 0); + free(p); + break; + case 'D': + options->options |= DHCPCD_DUID; + break; + case 'E': + options->options |= DHCPCD_LASTLEASE; + break; + case 'F': +#ifndef MINIMAL + if (!oarg) { + options->fqdn = FQDN_BOTH; + break; + } + if (strcmp(oarg, "none") == 0) + options->fqdn = FQDN_NONE; + else if (strcmp(oarg, "ptr") == 0) + options->fqdn = FQDN_PTR; + else if (strcmp(oarg, "both") == 0) + options->fqdn = FQDN_BOTH; + else { + logger(LOG_ERR, "invalid value `%s' for FQDN", + oarg); + return -1; + } +#endif + break; + case 'G': + options->options &= ~DHCPCD_GATEWAY; + break; + case 'I': +#ifndef MINIMAL + /* Strings have a type of 0 */; + options->classid[1] = 0; + if (oarg) + s = parse_string_hwaddr((char *)options->clientid + 1, + CLIENTID_MAX_LEN, oarg, 1); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "clientid: %s", strerror(errno)); + return -1; + } + options->clientid[0] = (uint8_t)s; + if (s == 0) { + options->options &= ~DHCPCD_DUID; + options->options &= ~DHCPCD_CLIENTID; + } +#endif + break; + case 'L': + options->options &= ~DHCPCD_IPV4LL; + break; + case 'O': + if (make_reqmask(options->reqmask, &optarg, -1) != 0 || + make_reqmask(options->nomask, &optarg, 1) != 0) + { + logger(LOG_ERR, "unknown option `%s'", optarg); + return -1; + } + break; + case 'X': + options->options &= ~DHCPCD_DAEMONISE; + break; + default: + return 0; + } + + return 1; +} + +static int +parse_config_line(const char *opt, char *line, struct options *options) +{ + unsigned int i; + + for (i = 0; i < sizeof(longopts) / sizeof(longopts[0]); i++) { + if (!longopts[i].name || + strcmp(longopts[i].name, opt) != 0) + continue; + + if (longopts[i].has_arg == required_argument && !line) { + fprintf(stderr, + PACKAGE ": option requires an argument -- %s\n", + opt); + return -1; + } + + return parse_option(longopts[i].val, line, options); + } + + fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt); + return -1; +} + +#ifdef ANDROID +void switchUser() { + gid_t groups[] = { AID_INET, AID_SHELL }; + setgroups(sizeof(groups)/sizeof(groups[0]), groups); + + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + + setgid(AID_DHCP); + setuid(AID_DHCP); + + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; + cap.effective = cap.permitted = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | + (1 << CAP_NET_BROADCAST) | (1 << CAP_NET_BIND_SERVICE); + cap.inheritable = 0; + capset(&header, &cap); +} +#endif /* ANDROID */ + +int +main(int argc, char **argv) +{ + struct options *options; + int opt; + int option_index = 0; + char *prefix; + pid_t pid; + int debug = 0; + int i, r; + int pid_fd = -1; + int sig = 0; + int retval = EXIT_FAILURE; + char *line, *option, *p, *buffer = NULL; + size_t len = 0; + FILE *f; + char *cf = NULL; + char *intf = NULL; + +#ifdef ANDROID + switchUser(); +#endif + + closefrom(3); + openlog(PACKAGE, LOG_PID, LOG_LOCAL0); + + options = xzalloc(sizeof(*options)); + options->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE; + options->timeout = DEFAULT_TIMEOUT; + strlcpy(options->script, SCRIPT, sizeof(options->script)); + +#ifndef MINIMAL + options->options |= DHCPCD_CLIENTID; + options->classid[0] = snprintf((char *)options->classid + 1, CLASSID_MAX_LEN, + "%s %s", PACKAGE, VERSION); +#endif +#ifdef ENABLE_ARP + options->options |= DHCPCD_ARP; + #ifdef ENABLE_IPV4LL + options->options |= DHCPCD_IPV4LL; + #endif +#endif + +#ifdef CMDLINE_COMPAT + add_reqmask(options->reqmask, DHCP_DNSSERVER); + add_reqmask(options->reqmask, DHCP_DNSDOMAIN); + add_reqmask(options->reqmask, DHCP_DNSSEARCH); + add_reqmask(options->reqmask, DHCP_NISSERVER); + add_reqmask(options->reqmask, DHCP_NISDOMAIN); + add_reqmask(options->reqmask, DHCP_NTPSERVER); + + /* If the duid file exists, then enable duid by default + * This means we don't break existing clients that easily :) */ + if ((f = fopen(DUID, "r"))) { + options->options |= DHCPCD_DUID; + fclose(f); + } +#endif + +#ifdef THERE_IS_NO_FORK + dhcpcd_argv = argv; + dhcpcd_argc = argc; + if (!realpath(argv[0], dhcpcd)) { + fprintf(stderr, "unable to resolve the path `%s': %s", + argv[0], strerror(errno)); + goto abort; + } +#endif + +#ifndef MINIMAL + gethostname(options->hostname + 1, sizeof(options->hostname)); + if (strcmp(options->hostname + 1, "(none)") == 0 || + strcmp(options->hostname + 1, "localhost") == 0) + options->hostname[1] = '\0'; + *options->hostname = strlen(options->hostname + 1); +#endif + + while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, + longopts, &option_index)) != -1) + { + switch (opt) { + case 0: + if (longopts[option_index].flag) + break; + logger(LOG_ERR, "option `%s' should set a flag", + longopts[option_index].name); + goto abort; + case 'f': + cf = optarg; + break; + case 'V': + print_options(); + goto abort; + case '?': + usage(); + goto abort; + } + } + + if (doversion) { + printf(""PACKAGE" "VERSION"\n%s\n", copyright); + printf("Compile time options:" +#ifdef ENABLE_ARP + " ARP" +#endif +#ifdef ENABLE_IPV4LL + " IPV4LL" +#endif +#ifdef MINIMAL + " MINIMAL" +#endif +#ifdef THERE_IS_NO_FORK + " THERE_IS_NO_FORK" +#endif + "\n"); + } + + if (dohelp) + usage(); + + if (optind < argc) { + if (strlen(argv[optind]) >= IF_NAMESIZE) { + logger(LOG_ERR, + "`%s' too long for an interface name (max=%d)", + argv[optind], IF_NAMESIZE); + goto abort; + } + strlcpy(options->interface, argv[optind], + sizeof(options->interface)); + } else { + /* If only version was requested then exit now */ + if (doversion || dohelp) { + retval = 0; + goto abort; + } + + logger(LOG_ERR, "no interface specified"); + goto abort; + } + + /* Parse our options file */ + f = fopen(cf ? cf : CONFIG, "r"); + if (f) { + r = 1; + while ((get_line(&buffer, &len, f))) { + line = buffer; + while ((option = strsep(&line, " \t"))) + if (*option != '\0') + break; + if (!option || *option == '\0' || *option == '#') + continue; + /* Trim leading whitespace */ + if (line) { + while (*line != '\0' && (*line == ' ' || *line == '\t')) + line++; + } + /* Trim trailing whitespace */ + if (line && *line) { + p = line + strlen(line) - 1; + while (p != line && + (*p == ' ' || *p == '\t') && + *(p - 1) != '\\') + *p-- = '\0'; + } + if (strcmp(option, "interface") == 0) { + free(intf); + intf = xstrdup(line); + continue; + } + /* If we're in an interface block don't use these + * options unless it's for us */ + if (intf && strcmp(intf, options->interface) != 0) + continue; + r = parse_config_line(option, line, options); + if (r != 1) + break; + } + free(buffer); + free(intf); + fclose(f); + if (r == 0) + usage(); + if (r != 1) + goto abort; + } else { + if (errno != ENOENT || cf) { + logger(LOG_ERR, "fopen `%s': %s", cf ? cf : CONFIG, + strerror(errno)); + goto abort; + } + } + + optind = 0; + while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, + longopts, &option_index)) != -1) + { + switch (opt) { + case 'd': + debug++; + switch (debug) { + case 1: + setloglevel(LOG_DEBUG); + break; + case 2: + options->options &= ~DHCPCD_DAEMONISE; + break; + } + break; + case 'f': + break; +#ifdef THERE_IS_NO_FORK + case 'z': + options->options |= DHCPCD_DAEMONISED; + close_fds(); + break; + case 'Z': + dhcpcd_skiproutes = xstrdup(optarg); + break; +#endif + case 'k': + sig = SIGHUP; + break; + case 'n': + sig = SIGALRM; + break; + case 'x': + sig = SIGTERM; + break; + case 'T': + options->options |= DHCPCD_TEST | DHCPCD_PERSISTENT; + break; +#ifdef CMDLINE_COMPAT + case 'H': /* FALLTHROUGH */ + case 'M': + del_reqmask(options->reqmask, DHCP_MTU); + break; + case 'N': + del_reqmask(options->reqmask, DHCP_NTPSERVER); + break; + case 'R': + del_reqmask(options->reqmask, DHCP_DNSSERVER); + del_reqmask(options->reqmask, DHCP_DNSDOMAIN); + del_reqmask(options->reqmask, DHCP_DNSSEARCH); + break; + case 'S': + add_reqmask(options->reqmask, DHCP_MSCSR); + break; + case 'Y': + del_reqmask(options->reqmask, DHCP_NISSERVER); + del_reqmask(options->reqmask, DHCP_NISDOMAIN); + break; +#endif + default: + i = parse_option(opt, optarg, options); + if (i == 1) + break; + if (i == 0) + usage(); + goto abort; + } + } + +#ifndef MINIMAL + if ((p = strchr(options->hostname, '.'))) { + if (options->fqdn == FQDN_DISABLE) + *p = '\0'; + } else { + if (options->fqdn != FQDN_DISABLE) { + logger(LOG_WARNING, "hostname `%s' is not a FQDN", + options->hostname); + options->fqdn = FQDN_DISABLE; + } + } + if (options->fqdn != FQDN_DISABLE) + del_reqmask(options->reqmask, DHCP_HOSTNAME); +#endif + + if (options->request_address.s_addr == 0 && + (options->options & DHCPCD_INFORM || + options->options & DHCPCD_REQUEST)) + { + if (get_address(options->interface, + &options->request_address, + &options->request_netmask) != 1) + { + logger(LOG_ERR, "no existing address"); + goto abort; + } + } + + if (!(options->options & DHCPCD_DAEMONISE)) + options->timeout = 0; + + if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) { + logger(LOG_ERR, + "you are not allowed to request a link local address"); + goto abort; + } + +/* android runs us as user "dhcp" */ +#ifndef ANDROID + if (geteuid()) + logger(LOG_WARNING, PACKAGE " will not work correctly unless" + " run as root"); +#endif + + prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3)); + snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface); + setlogprefix(prefix); + snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE, + options->interface); + free(prefix); + + chdir("/"); + umask(022); + + if (options->options & DHCPCD_TEST) { + if (options->options & DHCPCD_REQUEST || + options->options & DHCPCD_INFORM) { + logger(LOG_ERR, + "cannot test with --inform or --request"); + goto abort; + } + + if (options->options & DHCPCD_LASTLEASE) { + logger(LOG_ERR, "cannot test with --lastlease"); + goto abort; + } + + if (sig != 0) { + logger(LOG_ERR, + "cannot test with --release or --renew"); + goto abort; + } + } + + if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) { + i = -1; + pid = read_pid(options->pidfile); + if (pid != 0) + logger(LOG_INFO, "sending signal %d to pid %d", + sig, pid); + + if (!pid || (i = kill(pid, sig))) + logger(sig == SIGALRM ? LOG_INFO : LOG_ERR, + ""PACKAGE" not running"); + + if (pid != 0 && (sig != SIGALRM || i != 0)) + unlink(options->pidfile); + + if (i == 0) { + retval = EXIT_SUCCESS; + goto abort; + } + + if (sig != SIGALRM) + goto abort; + } +#ifndef ANDROID + if (!(options->options & DHCPCD_TEST) && + !(options->options & DHCPCD_DAEMONISED)) + { + if ((pid = read_pid(options->pidfile)) > 0 && + kill(pid, 0) == 0) + { + logger(LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, options->pidfile); + goto abort; + } + + pid_fd = open(options->pidfile, + O_WRONLY | O_CREAT | O_NONBLOCK, 0664); + if (pid_fd == -1) { + logger(LOG_ERR, "open `%s': %s", + options->pidfile, strerror(errno)); + goto abort; + } + + /* Lock the file so that only one instance of dhcpcd runs + * on an interface */ + if (flock(pid_fd, LOCK_EX | LOCK_NB) == -1) { + logger(LOG_ERR, "flock `%s': %s", + options->pidfile, strerror(errno)); + goto abort; + } + + if (set_cloexec(pid_fd) == -1) + goto abort; + writepid(pid_fd, getpid()); + logger(LOG_INFO, PACKAGE " " VERSION " starting"); + } +#endif /* ANDROID */ +#ifndef MINIMAL + /* Terminate the encapsulated options */ + if (options->vendor[0]) { + options->vendor[0]++; + options->vendor[options->vendor[0]] = DHCP_END; + } +#endif + + if (dhcp_run(options, &pid_fd) == 0) + retval = EXIT_SUCCESS; + +abort: + /* If we didn't daemonise then we need to punt the pidfile now */ + if (pid_fd > -1) { + close(pid_fd); + unlink(options->pidfile); + } + if (options->environ) { + len = 0; + while (options->environ[len]) + free(options->environ[len++]); + free(options->environ); + } + free(options); + +#ifdef THERE_IS_NO_FORK + /* There may have been an error before the dhcp_run function + * clears this, so just do it here to be safe */ + free(dhcpcd_skiproutes); +#endif + + exit(retval); + /* NOTREACHED */ +} diff --git a/dhcpcd.conf b/dhcpcd.conf new file mode 100644 index 0000000..cce1795 --- /dev/null +++ b/dhcpcd.conf @@ -0,0 +1,13 @@ +# A sample configuration for dhcpcd. +# See dhcpcd.conf(5) for details. + +# dhcpcd-run-hooks uses these options. +option domain_name_servers, domain_name, domain_search, host_name + +# Most distros have ntp support. +option ntp_servers + +# We should behave nicely on networks and respect their MTU. +# However, a lot of buggy DHCP servers set invalid MTUs so this is not +# enabled by default. +#option interface_mtu diff --git a/dhcpcd.conf.5 b/dhcpcd.conf.5 new file mode 100644 index 0000000..8fd4b90 --- /dev/null +++ b/dhcpcd.conf.5 @@ -0,0 +1,137 @@ +.\" Copyright 2006-2008 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd Jun 30, 2008 +.Dt DHCPCD.CONF 5 SMM +.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 classid Ar string +Change the default classid sent from dhcpcd-version. +If not set then none is sent. +.It Ic clientid Ar string +Change the default clientid sent from the interface hardware address. +If the string is of the format 01:02:03 then it is encoded as hex. +If not set then none is sent. +.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 /system/etc/dhcpcd/dhcpcd.duid +and should not be copied to other hosts. +.It Ic hostname Ar name +Sends specified +.Ar hostname +to the DHCP server so it can be registered in DNS. If +.Ar hostname +if a FQDN (ie, contains a .) then it will be encoded as such. +.It Ic fqdn Op none | ptr | both +none disables FQDN encoding, ptr just asks the DHCP server to update the PTR +record of the host in DNS whereas both also updates the A record. +The current hostname or the hostname specified using the +.Fl h , -hostname +option must be a FQDN. +.Nm dhcpcd +itself never does any DNS updates. +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic leasetime Ar seconds +Request a leasetime of +.Ar seconds . +.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 . +.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 option Ar dhcp-option +Requests the +.Ar dhcp-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 seperated by commas, spaces or more option lines. +.It Ic script Ar script +Use +.Ar script +instead of the default +.Pa /system/etc/dhcpcd/dhcpcd-run-hooks . +.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 vendor Ar code , Ns Ar value +Add an enscapulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +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" +.El +.Sh SEE ALSO +.Xr dhcpcd-run-hooks 8 , +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in new file mode 100644 index 0000000..5ba825f --- /dev/null +++ b/dhcpcd.conf.5.in @@ -0,0 +1,137 @@ +.\" Copyright 2006-2008 Roy Marples +.\" All rights reserved +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd Jun 30, 2008 +.Dt DHCPCD.CONF 5 SMM +.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 classid Ar string +Change the default classid sent from dhcpcd-version. +If not set then none is sent. +.It Ic clientid Ar string +Change the default clientid sent from the interface hardware address. +If the string is of the format 01:02:03 then it is encoded as hex. +If not set then none is sent. +.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 hostname Ar name +Sends specified +.Ar hostname +to the DHCP server so it can be registered in DNS. If +.Ar hostname +if a FQDN (ie, contains a .) then it will be encoded as such. +.It Ic fqdn Op none | ptr | both +none disables FQDN encoding, ptr just asks the DHCP server to update the PTR +record of the host in DNS whereas both also updates the A record. +The current hostname or the hostname specified using the +.Fl h , -hostname +option must be a FQDN. +.Nm dhcpcd +itself never does any DNS updates. +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic leasetime Ar seconds +Request a leasetime of +.Ar seconds . +.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 . +.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 option Ar dhcp-option +Requests the +.Ar dhcp-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 seperated by commas, spaces or more option lines. +.It Ic script Ar script +Use +.Ar script +instead of the default +.Pa @SCRIPT@ . +.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 vendor Ar code , Ns Ar value +Add an enscapulated vendor option. +.Ar code +should be between 1 and 254 inclusive. +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" +.El +.Sh SEE ALSO +.Xr dhcpcd-run-hooks 8 , +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd.h b/dhcpcd.h new file mode 100644 index 0000000..10d23ff --- /dev/null +++ b/dhcpcd.h @@ -0,0 +1,98 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCD_H +#define DHCPCD_H + +#include +#include + +#include +#include + +#include + +#include "common.h" + +#define DEFAULT_TIMEOUT 30 +#define DEFAULT_LEASETIME 3600 /* 1 hour */ + +#define CLASSID_MAX_LEN 48 +#define CLIENTID_MAX_LEN 48 +#define USERCLASS_MAX_LEN 255 +#define VENDOR_MAX_LEN 255 + +#ifdef THERE_IS_NO_FORK +extern char dhcpcd[PATH_MAX]; +extern char **dhcpcd_argv; +extern int dhcpcd_argc; +extern char *dhcpcd_skiproutes; +#endif + +#define DHCPCD_ARP (1 << 0) +#define DHCPCD_DOMAIN (1 << 2) +#define DHCPCD_GATEWAY (1 << 3) +#define DHCPCD_LASTLEASE (1 << 7) +#define DHCPCD_INFORM (1 << 8) +#define DHCPCD_REQUEST (1 << 9) +#define DHCPCD_IPV4LL (1 << 10) +#define DHCPCD_DUID (1 << 11) +#define DHCPCD_PERSISTENT (1 << 12) +#define DHCPCD_DAEMONISE (1 << 14) +#define DHCPCD_DAEMONISED (1 << 15) +#define DHCPCD_TEST (1 << 16) +#define DHCPCD_FORKED (1 << 17) +#define DHCPCD_HOSTNAME (1 << 18) +#define DHCPCD_CLIENTID (1 << 19) + +struct options { + char interface[IF_NAMESIZE]; + int metric; + uint8_t reqmask[256 / 8]; + uint8_t nomask[256 / 8]; + uint32_t leasetime; + time_t timeout; + int options; + + struct in_addr request_address; + struct in_addr request_netmask; + + char **environ; + char script[PATH_MAX]; + char pidfile[PATH_MAX]; + +#ifndef MINIMAL + char hostname[MAXHOSTNAMELEN]; + int fqdn; + uint8_t classid[CLASSID_MAX_LEN + 1]; + char clientid[CLIENTID_MAX_LEN + 1]; + uint8_t userclass[USERCLASS_MAX_LEN + 1]; + uint8_t vendor[VENDOR_MAX_LEN + 1]; +#endif +}; + +#endif diff --git a/if-bsd.c b/if-bsd.c new file mode 100644 index 0000000..2cc0c2f --- /dev/null +++ b/if-bsd.c @@ -0,0 +1,187 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "net.h" + +/* Darwin doesn't define this for some very odd reason */ +#ifndef SA_SIZE +# define SA_SIZE(sa) \ + ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \ + sizeof(long) : \ + 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) ) +#endif + +int +if_address(const char *ifname, const struct in_addr *address, + const struct in_addr *netmask, const struct in_addr *broadcast, + int action) +{ + int s; + int retval; + struct ifaliasreq ifa; + union { + struct sockaddr *sa; + struct sockaddr_in *sin; + } _s; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + memset(&ifa, 0, sizeof(ifa)); + strlcpy(ifa.ifra_name, ifname, sizeof(ifa.ifra_name)); + +#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) { + ADDADDR(ifa.ifra_broadaddr, broadcast); + } +#undef ADDADDR + + if (action < 0) + retval = ioctl(s, SIOCDIFADDR, &ifa); + else + retval = ioctl(s, SIOCAIFADDR, &ifa); + close(s); + return retval; +} + +int +if_route(const char *ifname, const struct in_addr *destination, + const struct in_addr *netmask, const struct in_addr *gateway, + _unused int metric, int action) +{ + int s; + static int seq; + 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) * 3]; + } rtm; + char *bp = rtm.buffer; + size_t l; + unsigned char *hwaddr; + size_t hwlen = 0; + int retval = 0; + + if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) + return -1; + + memset(&rtm, 0, sizeof(rtm)); + rtm.hdr.rtm_version = RTM_VERSION; + rtm.hdr.rtm_seq = ++seq; + 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 | RTF_STATIC; + if (netmask->s_addr == INADDR_BROADCAST) + rtm.hdr.rtm_flags |= RTF_HOST; + + /* This order is important */ + rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; + +#define ADDADDR(_addr) \ + memset (&su, 0, sizeof(su)); \ + su.sin.sin_family = AF_INET; \ + su.sin.sin_len = sizeof(su.sin); \ + memcpy (&su.sin.sin_addr, _addr, sizeof(su.sin.sin_addr)); \ + l = SA_SIZE (&(su.sa)); \ + memcpy (bp, &(su), l); \ + bp += l; + + ADDADDR(destination); + + if (gateway->s_addr == INADDR_ANY) { + /* Make us a link layer socket */ + hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); + do_interface(ifname, hwaddr, &hwlen, NULL, 0, 0); + memset(&su, 0, sizeof(su)); + su.sdl.sdl_len = sizeof(su.sdl); + su.sdl.sdl_family = AF_LINK; + su.sdl.sdl_nlen = strlen(ifname); + memcpy(&su.sdl.sdl_data, ifname, (size_t)su.sdl.sdl_nlen); + su.sdl.sdl_alen = hwlen; + memcpy(((unsigned char *)&su.sdl.sdl_data) + su.sdl.sdl_nlen, + hwaddr, (size_t)su.sdl.sdl_alen); + + l = SA_SIZE(&(su.sa)); + memcpy(bp, &su, l); + bp += l; + free(hwaddr); + } else { + rtm.hdr.rtm_flags |= RTF_GATEWAY; + ADDADDR(gateway); + } + + ADDADDR(netmask); +#undef ADDADDR + + rtm.hdr.rtm_msglen = l = bp - (char *)&rtm; + if (write(s, &rtm, l) == -1) + retval = -1; + close(s); + return retval; +} diff --git a/if-linux.c b/if-linux.c new file mode 100644 index 0000000..68af3f6 --- /dev/null +++ b/if-linux.c @@ -0,0 +1,328 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include /* Needed for 2.4 kernels */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "net.h" + +/* This netlink stuff is overly compex IMO. + * The BSD implementation is much cleaner and a lot less code. + * send_netlink handles the actual transmission so we can work out + * if there was an error or not. */ +#define BUFFERLEN 256 +static int +send_netlink(struct nlmsghdr *hdr) +{ + int s; + pid_t mypid = getpid (); + struct sockaddr_nl nl; + struct iovec iov; + struct msghdr msg; + static unsigned int seq; + char *buffer = NULL; + ssize_t bytes; + union + { + char *buffer; + struct nlmsghdr *nlm; + } h; + int len, l; + struct nlmsgerr *err; + + if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + return -1; + + memset(&nl, 0, sizeof(nl)); + nl.nl_family = AF_NETLINK; + if (bind(s, (struct sockaddr *)&nl, sizeof(nl)) == -1) + goto eexit; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = hdr; + iov.iov_len = hdr->nlmsg_len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &nl; + msg.msg_namelen = sizeof(nl); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + /* Request a reply */ + hdr->nlmsg_flags |= NLM_F_ACK; + hdr->nlmsg_seq = ++seq; + + if (sendmsg(s, &msg, 0) == -1) + goto eexit; + + buffer = xzalloc(sizeof(char) * BUFFERLEN); + iov.iov_base = buffer; + + for (;;) { + iov.iov_len = BUFFERLEN; + bytes = recvmsg(s, &msg, 0); + + if (bytes == -1) { + if (errno == EINTR) + continue; + goto eexit; + } + + if (bytes == 0) { + errno = ENODATA; + goto eexit; + } + + if (msg.msg_namelen != sizeof(nl)) { + errno = EBADMSG; + goto eexit; + } + + for (h.buffer = buffer; bytes >= (signed) sizeof(*h.nlm); ) { + len = h.nlm->nlmsg_len; + l = len - sizeof(*h.nlm); + err = (struct nlmsgerr *)NLMSG_DATA(h.nlm); + + if (l < 0 || len > bytes) { + errno = EBADMSG; + goto eexit; + } + + /* Ensure it's our message */ + if (nl.nl_pid != 0 || + (pid_t)h.nlm->nlmsg_pid != mypid || + h.nlm->nlmsg_seq != seq) + { + /* Next Message */ + bytes -= NLMSG_ALIGN(len); + h.buffer += NLMSG_ALIGN(len); + continue; + } + + /* We get an NLMSG_ERROR back with a code of zero for success */ + if (h.nlm->nlmsg_type != NLMSG_ERROR) + continue; + + if ((unsigned)l < sizeof(*err)) { + errno = EBADMSG; + goto eexit; + } + + if (err->error == 0) { + close(s); + free(buffer); + return l; + } + + errno = -err->error; + goto eexit; + } + } + +eexit: + close(s); + free(buffer); + return -1; +} + +#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 char *ifname, + 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(ifname))) { + 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, + ifname, strlen(ifname) + 1); + add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL, + &address->s_addr, sizeof(address->s_addr)); + if (action >= 0) + 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 char *ifname, + const struct in_addr *destination, const struct in_addr *netmask, + const struct in_addr *gateway, int metric, int action) +{ + struct nlmr *nlm; + unsigned int ifindex; + int retval = 0; + + + if (!(ifindex = if_nametoindex(ifname))) { + 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 > 0) + /* + * commented out NLM_F_EXCL here and + * below. We sometimes keep one interface up while + * we are configuring the other one, and this flag + * causes route addition to fail. + */ + nlm->hdr.nlmsg_flags = NLM_F_CREATE /* | NLM_F_EXCL*/; + 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 < 0) + nlm->rt.rtm_scope = RT_SCOPE_NOWHERE; + else { + nlm->hdr.nlmsg_flags |= NLM_F_CREATE /*| NLM_F_EXCL*/; + nlm->rt.rtm_protocol = RTPROT_BOOT; + if (gateway->s_addr == INADDR_ANY) + 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(*netmask); + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_DST, + &destination->s_addr, sizeof(destination->s_addr)); + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_GATEWAY, + &gateway->s_addr, sizeof(gateway->s_addr)); + + add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_OIF, ifindex); + add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_PRIORITY, metric); + + if (send_netlink(&nlm->hdr) == -1) + retval = -1; + free(nlm); + return retval; +} diff --git a/logger.c b/logger.c new file mode 100644 index 0000000..6eb0c29 --- /dev/null +++ b/logger.c @@ -0,0 +1,118 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "logger.h" + +static int loglevel = LOG_INFO; +static char logprefix[12] = {0}; + +struct logname { + int level; + const char *name; +}; +static const struct logname const lognames[] = { + { LOG_DEBUG, "debug" }, + { LOG_INFO, "info" }, + { LOG_WARNING, "warning" }, + { LOG_ERR, "error" }, + { -1, NULL } +}; + +int +logtolevel(const char *priority) +{ + const struct logname *lt; + + if (isdigit((unsigned char)*priority)) + return atoi(priority); + for (lt = lognames; lt->name; lt++) + if (!strcasecmp(priority, lt->name)) + return lt->level; + return -1; +} + +void +setloglevel(int level) +{ + loglevel = level; +} + +void +setlogprefix(const char *prefix) +{ + strlcpy(logprefix, prefix, sizeof(logprefix)); +} + +void +logger(int level, const char *fmt, ...) +{ + va_list p, p2; + FILE *f = stderr; + size_t len, fmt2len; + char *fmt2, *pf; + + va_start(p, fmt); + va_copy(p2, p); + + if (level <= LOG_ERR || level <= loglevel) { + fprintf(f, "%s", logprefix); + vfprintf(f, fmt, p); + fputc('\n', f); + + /* stdout, stderr may be re-directed to some kind of buffer. + * So we always flush to ensure it's written. */ + fflush(f); + } + + if (level < LOG_DEBUG || level <= loglevel) { + len = strlen(logprefix); + fmt2len = strlen(fmt) + len + 1; + fmt2 = pf = malloc(sizeof(char) * fmt2len); + if (fmt2) { + strlcpy(pf, logprefix, fmt2len); + pf += len; + strlcpy(pf, fmt, fmt2len - len); + vsyslog(level, fmt2, p2); + free(fmt2); + } else { + vsyslog(level, fmt, p2); + syslog(LOG_ERR, "logger: memory exhausted"); + exit(EXIT_FAILURE); + } + } + + va_end(p2); + va_end(p); +} diff --git a/logger.h b/logger.h new file mode 100644 index 0000000..cde4864 --- /dev/null +++ b/logger.h @@ -0,0 +1,44 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#if defined(__GNUC__) +# define _PRINTF_LIKE(_one, _two) __attribute__ ((__format__ (__printf__, _one, _two))) +#else +# define _PRINTF_LIKE(_one, _two) +#endif + +#include + +int logtolevel(const char *); +void setloglevel(int); +void setlogprefix(const char *); +void logger(int, const char *, ...) _PRINTF_LIKE (2, 3); + +#endif diff --git a/lpf.c b/lpf.c new file mode 100644 index 0000000..daa8a4c --- /dev/null +++ b/lpf.c @@ -0,0 +1,177 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef __linux__ +# include /* needed for 2.4 kernels for the below header */ +# include +# include +# define bpf_insn sock_filter +# define BPF_SKIPTYPE +# define BPF_ETHCOOK -ETH_HLEN +# define BPF_WHOLEPACKET 0x0fffffff /* work around buggy LPF filters */ +#endif + +#include +#include +#include +#include +#include +#include +#include + +#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; + + 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)); +#ifdef ENABLE_ARP + if (protocol == ETHERTYPE_ARP) { + pf.filter = UNCONST(arp_bpf_filter); + pf.len = arp_bpf_filter_len; + } else +#endif + { + 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; + if (set_cloexec(s) == -1) + goto eexit; + if (set_nonblock(s) == -1) + goto eexit; + if (bind(s, &su.sa, sizeof(su)) == -1) + goto eexit; +#ifdef ENABLE_ARP + if (protocol == ETHERTYPE_ARP) + fd = &iface->arp_fd; + else +#endif + fd = &iface->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); +#ifdef ENABLE_ARP + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else +#endif + fd = iface->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) +{ + ssize_t bytes; + int fd = -1; + + if (protocol == ETHERTYPE_ARP) { +#ifdef ENABLE_ARP + fd = iface->arp_fd; +#endif + } else + fd = iface->fd; + bytes = read(fd, data, len); + if (bytes == -1) + return errno == EAGAIN ? 0 : -1; + return bytes; +} diff --git a/mk/cc.mk b/mk/cc.mk new file mode 100644 index 0000000..fcf0535 --- /dev/null +++ b/mk/cc.mk @@ -0,0 +1,28 @@ +# Copyright 2008 Roy Marples + +# 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/mk/depend.mk b/mk/depend.mk new file mode 100644 index 0000000..a4d717a --- /dev/null +++ b/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 + +CLEANFILES+= .depend + +.depend: ${SRCS} + ${CC} ${CFLAGS} -MM ${SRCS} > .depend + +depend: .depend diff --git a/mk/dist.mk b/mk/dist.mk new file mode 100644 index 0000000..3d9385b --- /dev/null +++ b/mk/dist.mk @@ -0,0 +1,31 @@ +# rules to make a distribution tarball from a git repo +# Copyright 2008 Roy Marples + +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/mk/files.mk b/mk/files.mk new file mode 100644 index 0000000..0682b03 --- /dev/null +++ b/mk/files.mk @@ -0,0 +1,9 @@ +# Quick and dirty files +# Copyright 2008 Roy Marples + +FILESDIR?= ${BINDIR} +FILESMODE?= ${NONBINMODE} + +_filesinstall: + ${INSTALL} -d ${DESTDIR}${FILESDIR} + ${INSTALL} -m ${FILESMODE} ${FILES} ${DESTDIR}${FILESDIR} diff --git a/mk/man.mk b/mk/man.mk new file mode 100644 index 0000000..f1570bb --- /dev/null +++ b/mk/man.mk @@ -0,0 +1,25 @@ +# rules to install manpages +# Copyright 2008 Roy Marples + +_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/mk/os-BSD.mk b/mk/os-BSD.mk new file mode 100644 index 0000000..f9d3397 --- /dev/null +++ b/mk/os-BSD.mk @@ -0,0 +1,5 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples + +SRC_PF= bpf.c +SRC_IF= if-bsd.c diff --git a/mk/os-Linux.mk b/mk/os-Linux.mk new file mode 100644 index 0000000..16f0f60 --- /dev/null +++ b/mk/os-Linux.mk @@ -0,0 +1,28 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples + +SRC_PF= lpf.c +SRC_IF= if-linux.c + +CPPFLAGS+= -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +LIBRT= -lrt + +# Work out if our fork() works or not. +# If cross-compiling, you'll need to set HAVE_FORK to yes or no depending +# on your target arch. +_HAVE_FORK_SH= if test "${HAVE_FORK}" = "yes"; then \ + echo ""; \ + elif test -n "${HAVE_FORK}"; then \ + echo "-DTHERE_IS_NO_FORK"; \ + else \ + printf '\#include \n\#include \nint main (void) { pid_t pid = fork(); if (pid == -1) exit (-1); exit (0); }\n' > .fork.c; \ + ${CC} .fork.c -o .fork >/dev/null 2>&1; \ + if ./.fork; then \ + echo ""; \ + else \ + echo "-DTHERE_IS_NO_FORK"; \ + fi; \ + rm -f .fork.c .fork; \ + fi; +_HAVE_FORK!= ${_HAVE_FORK_SH} +CPPFLAGS+= ${_HAVE_FORK}$(shell ${_HAVE_FORK_SH}) diff --git a/mk/os.mk b/mk/os.mk new file mode 100644 index 0000000..f2a83f4 --- /dev/null +++ b/mk/os.mk @@ -0,0 +1,7 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples + +_OS_SH= case `uname -s` in Linux) echo "Linux";; *) echo "BSD";; esac +_OS!= ${_OS_SH} +OS= ${_OS}$(shell ${_OS_SH}) +include ${MK}/os-${OS}.mk diff --git a/mk/prog.mk b/mk/prog.mk new file mode 100644 index 0000000..3f763b8 --- /dev/null +++ b/mk/prog.mk @@ -0,0 +1,67 @@ +# rules to build a program +# based on FreeBSD's bsd.prog.mk + +# Copyright 2008 Roy Marples + +include ${MK}/cc.mk + +OBJS+= ${SRCS:.c=.o} + +# This is for NetBSD which has a different libc in /lib which we need +# to link to if installing in / +_RPATH_SH= if test "${PREFIX}" = "" -o "${PREIX}" = "/"; then \ + echo "-Wl,-rpath=${PREFIX}/${LIBNAME}"; \ + else \ + echo ""; \ + fi +_RPATH!= ${_RPATH_SH} +LDFLAGS+= ${_RPATH}$(shell ${_RPATH_SH}) + +# This is for NetBSD which has different dynamic linker in /lib which we need +# to use to if installing in / +_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/mk/scripts.mk b/mk/scripts.mk new file mode 100644 index 0000000..d295163 --- /dev/null +++ b/mk/scripts.mk @@ -0,0 +1,9 @@ +# Quick and dirty scripts +# Copyright 2008 Roy Marples + +SCRIPTSDIR?= ${BINDIR} +SCRIPTSMODE?= ${BINMODE} + +_scriptsinstall: ${SCRIPTS} + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${SCRIPTSMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR} diff --git a/mk/sys.mk b/mk/sys.mk new file mode 100644 index 0000000..81882ab --- /dev/null +++ b/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/net.c b/net.c new file mode 100644 index 0000000..f8aad4c --- /dev/null +++ b/net.c @@ -0,0 +1,633 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#ifdef __linux__ +#include +#include +#endif +#include +#include +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include +#undef __FAVOR_BSD +#include +#ifdef AF_LINK +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "logger.h" +#include "net.h" +#include "signals.h" + +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 < 0 || 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) +{ + static char buffer[(HWADDR_LEN * 3) + 1]; + char *p = 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 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; +} + +int +do_interface(const char *ifname, + _unused unsigned char *hwaddr, _unused size_t *hwlen, + struct in_addr *addr, struct in_addr *net, int get) +{ + int s; + struct ifconf ifc; + int retval = 0; + int len = 10 * sizeof(struct ifreq); + int lastlen = 0; + char *p; + union { + char *buffer; + struct ifreq *ifr; + } ifreqs; + struct sockaddr_in address; + struct ifreq *ifr; + struct sockaddr_in netmask; + +#ifdef AF_LINK + struct sockaddr_dl sdl; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + /* Not all implementations return the needed buffer size for + * SIOGIFCONF so we loop like so for all until it works */ + memset(&ifc, 0, sizeof(ifc)); + for (;;) { + ifc.ifc_len = len; + ifc.ifc_buf = xmalloc((size_t)len); + if (ioctl(s, SIOCGIFCONF, &ifc) == -1) { + if (errno != EINVAL || lastlen != 0) { + close(s); + free(ifc.ifc_buf); + return -1; + } + } else { + if (ifc.ifc_len == lastlen) + break; + lastlen = ifc.ifc_len; + } + + free(ifc.ifc_buf); + ifc.ifc_buf = NULL; + len *= 2; + } + + for (p = (char *)ifc.ifc_buf; p < (char *)ifc.ifc_buf + ifc.ifc_len;) { + /* Cast the ifc buffer to an ifreq cleanly */ + ifreqs.buffer = p; + ifr = ifreqs.ifr; + +#ifndef __linux__ + if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_ifru)) + p += offsetof(struct ifreq, ifr_ifru) + + ifr->ifr_addr.sa_len; + else +#endif + p += sizeof(*ifr); + + if (strcmp(ifname, ifr->ifr_name) != 0) + continue; + +#ifdef AF_LINK + if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) { + memcpy(&sdl, &ifr->ifr_addr, sizeof(sdl)); + *hwlen = sdl.sdl_alen; + memcpy(hwaddr, sdl.sdl_data + sdl.sdl_nlen, + (size_t)sdl.sdl_alen); + retval = 1; + break; + } +#endif + + if (ifr->ifr_addr.sa_family == AF_INET) { + memcpy(&address, &ifr->ifr_addr, sizeof(address)); + if (ioctl(s, SIOCGIFNETMASK, ifr) == -1) + continue; + memcpy(&netmask, &ifr->ifr_addr, sizeof(netmask)); + if (get) { + addr->s_addr = address.sin_addr.s_addr; + net->s_addr = netmask.sin_addr.s_addr; + retval = 1; + break; + } else { + if (address.sin_addr.s_addr == addr->s_addr && + (!net || + netmask.sin_addr.s_addr == net->s_addr)) + { + retval = 1; + break; + } + } + } + + } + + close(s); + free(ifc.ifc_buf); + return retval; +} + +struct interface * +read_interface(const char *ifname, _unused int metric) +{ + int s; + struct ifreq ifr; + struct interface *iface = NULL; + unsigned char *hwaddr = NULL; + size_t hwlen = 0; + sa_family_t family = 0; +#ifdef __linux__ + char *p; +#endif + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return NULL; + +#ifdef __linux__ + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1) + goto eexit; + + switch (ifr.ifr_hwaddr.sa_family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + hwlen = ETHER_ADDR_LEN; + break; + case ARPHRD_IEEE1394: + hwlen = EUI64_ADDR_LEN; + case ARPHRD_INFINIBAND: + hwlen = INFINIBAND_ADDR_LEN; + break; + } + + hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); + memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, hwlen); + family = ifr.ifr_hwaddr.sa_family; +#else + ifr.ifr_metric = metric; + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCSIFMETRIC, &ifr) == -1) + goto eexit; + + hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); + if (do_interface(ifname, hwaddr, &hwlen, NULL, NULL, 0) != 1) + goto eexit; + + family = ARPHRD_ETHER; +#endif + + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, 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(s, SIOCSIFMTU, &ifr) == -1) + goto eexit; + } + + /* Bring the interface up if it's down */ + strlcpy(ifr.ifr_name, ifname, 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(s, SIOCGIFFLAGS, &ifr) == -1) + goto eexit; + if (!(ifr.ifr_flags & IFF_UP)) { + ifr.ifr_flags |= IFF_UP; + if (ioctl(s, SIOCSIFFLAGS, &ifr) != 0) + goto eexit; + } + + iface = xzalloc(sizeof(*iface)); + strlcpy(iface->name, ifname, IF_NAMESIZE); + snprintf(iface->leasefile, PATH_MAX, LEASEFILE, ifname); + memcpy(&iface->hwaddr, hwaddr, hwlen); + iface->hwlen = hwlen; + + iface->family = family; + iface->arpable = !(ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)); + + /* 0 is a valid fd, so init to -1 */ + iface->fd = -1; + iface->udp_fd = -1; +#ifdef ENABLE_ARP + iface->arp_fd = -1; +#endif + +eexit: + close(s); + free(hwaddr); + return iface; +} + +int +do_mtu(const char *ifname, short int mtu) +{ + struct ifreq ifr; + int r; + int s; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + r = ioctl(s, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); + close(s); + 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; + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + } su; + int n = 1; + + if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + + memset(&su, 0, sizeof(su)); + su.sin.sin_family = AF_INET; + su.sin.sin_port = htons(DHCP_CLIENT_PORT); + su.sin.sin_addr.s_addr = iface->addr.s_addr; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto eexit; + /* As we don't actually use this socket for anything, set + * the receiver buffer to 1 */ + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + goto eexit; + if (bind(s, &su.sa, sizeof(su)) == -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) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + } su; + + memset(&su, 0, sizeof(su)); + su.sin.sin_family = AF_INET; + su.sin.sin_addr.s_addr = to.s_addr; + su.sin.sin_port = htons(DHCP_SERVER_PORT); + + return sendto(iface->udp_fd, data, len, 0, &su.sa, sizeof(su)); +} + +struct 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 = 5; + ip->ip_id = 0; + ip->ip_tos = IPTOS_LOWDELAY; + ip->ip_len = htons (sizeof(*ip) + sizeof(*udp) + length); + ip->ip_id = 0; + ip->ip_off = htons(IP_DF); /* Don't fragment */ + ip->ip_ttl = IPDEFTTL; + + 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) +{ + struct udp_dhcp_packet packet; + uint16_t bytes; + uint16_t ipsum; + uint16_t iplen; + uint16_t udpsum; + struct in_addr source; + struct in_addr dest; + int retval = 0; + + memcpy(&packet, data, sizeof(packet)); + bytes = ntohs(packet.ip.ip_len); + ipsum = packet.ip.ip_sum; + iplen = packet.ip.ip_len; + udpsum = packet.udp.uh_sum; + + if (0 != checksum(&packet.ip, sizeof(packet.ip))) { + errno = EINVAL; + return -1; + } + + packet.ip.ip_sum = 0; + memcpy(&source, &packet.ip.ip_src, sizeof(packet.ip.ip_src)); + memcpy(&dest, &packet.ip.ip_dst, sizeof(packet.ip.ip_dst)); + memset(&packet.ip, 0, sizeof(packet.ip)); + packet.udp.uh_sum = 0; + + packet.ip.ip_p = IPPROTO_UDP; + memcpy(&packet.ip.ip_src, &source, sizeof(packet.ip.ip_src)); + memcpy(&packet.ip.ip_dst, &dest, sizeof(packet.ip.ip_dst)); + packet.ip.ip_len = packet.udp.uh_ulen; + if (udpsum && udpsum != checksum(&packet, bytes)) { + errno = EINVAL; + retval = -1; + } + + return retval; +} + +#ifdef ENABLE_ARP +int +send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip) +{ + struct arphdr *arp; + size_t arpsize; + unsigned char *p; + int retval; + + arpsize = sizeof(*arp) + 2 * iface->hwlen + 2 *sizeof(sip); + + arp = xmalloc(arpsize); + arp->ar_hrd = htons(iface->family); + arp->ar_pro = htons(ETHERTYPE_IP); + arp->ar_hln = iface->hwlen; + arp->ar_pln = sizeof(sip); + arp->ar_op = htons(op); + p = (unsigned char *)arp; + p += sizeof(*arp); + memcpy(p, iface->hwaddr, iface->hwlen); + p += iface->hwlen; + memcpy(p, &sip, sizeof(sip)); + p += sizeof(sip); + /* ARP requests should ignore this, but we fill with 0xff + * for broadcast. */ + memset(p, 0xff, iface->hwlen); + p += iface->hwlen; + memcpy(p, &tip, sizeof(tip)); + + retval = send_raw_packet(iface, ETHERTYPE_ARP, arp, arpsize); + free(arp); + return retval; +} +#endif + diff --git a/net.h b/net.h new file mode 100644 index 0000000..0be56cf --- /dev/null +++ b/net.h @@ -0,0 +1,172 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include +#include +#include + +#include +#include +#include + +#include + +#include "config.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 + +#define HWADDR_LEN 20 + +/* 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 0xffff0000 +#define LINKLOCAL_BRDC 0xa9feffff + +#ifndef IN_LINKLOCAL +# define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR) +#endif + +/* There is an argument that this should be converted to an STAIL using + * queue(3). However, that isn't readily available on all libc's that + * dhcpcd works on. The only benefit of STAILQ over this is the ability to + * quickly loop backwards through the list - currently we reverse the list + * and then move through it forwards. This isn't that much of a big deal + * though as the norm is to just have one default route, and an IPV4LL route. + * You can (and do) get more routes in the DHCP message, but not enough to + * really warrant a change to STAIL queue for performance reasons. */ +struct rt { + struct in_addr dest; + struct in_addr net; + struct in_addr gate; + struct rt *next; +}; + +struct interface +{ + char name[IF_NAMESIZE]; + sa_family_t family; + unsigned char hwaddr[HWADDR_LEN]; + size_t hwlen; + int arpable; + + int fd; + int udp_fd; + size_t buffer_size, buffer_len, buffer_pos; + unsigned char *buffer; +#ifdef ENABLE_ARP + int arp_fd; +#endif + + struct in_addr addr; + struct in_addr net; + struct rt *routes; + + char leasefile[PATH_MAX]; + time_t start_uptime; + + unsigned char *clientid; +}; + +uint32_t get_netmask(uint32_t); +char *hwaddr_ntoa(const unsigned char *, size_t); +size_t hwaddr_aton(unsigned char *, const char *); + +struct interface *read_interface(const char *, int); +int 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 do_interface(const char *, unsigned char *, size_t *, + struct in_addr *, struct in_addr *, int); +int if_address(const char *, const struct in_addr *, const struct in_addr *, + const struct in_addr *, int); +#define add_address(ifname, addr, net, brd) \ + if_address(ifname, addr, net, brd, 1) +#define del_address(ifname, addr, net) \ + if_address(ifname, addr, net, NULL, -1) +#define has_address(ifname, addr, net) \ + do_interface(ifname, NULL, NULL, addr, net, 0) +#define get_address(ifname, addr, net) \ + do_interface(ifname, NULL, NULL, addr, net, 1) + +int if_route(const char *, const struct in_addr *, const struct in_addr *, + const struct in_addr *, int, int); +#define add_route(ifname, dest, mask, gate, metric) \ + if_route(ifname, dest, mask, gate, metric, 1) +#define del_route(ifname, dest, mask, gate, metric) \ + if_route(ifname, dest, mask, gate, metric, -1) +void free_routes(struct rt *); + +int open_udp_socket(struct interface *); +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 *); + +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); + +#ifdef ENABLE_ARP +int send_arp(const struct interface *, int, in_addr_t, in_addr_t); +#endif +#endif diff --git a/signals.c b/signals.c new file mode 100644 index 0000000..fb986d7 --- /dev/null +++ b/signals.c @@ -0,0 +1,137 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "common.h" +#include "signals.h" + +static int signal_pipe[2]; + +static const int handle_sigs[] = { + SIGHUP, + SIGALRM, + SIGTERM, + SIGINT +}; + +static void +signal_handler(int sig) +{ + int serrno = errno; + + write(signal_pipe[1], &sig, sizeof(sig)); + /* Restore errno */ + errno = serrno; +} + +int +signal_fd(void) +{ + return (signal_pipe[0]); +} + +/* Check if we have a signal or not */ +int +signal_exists(int fd) +{ + if (fd_hasdata(fd) == 1) + return 0; + return -1; +} + +/* 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(int fd) +{ + int sig = -1; + char buf[16]; + size_t bytes; + + if (fd_hasdata(fd) == 1) { + memset(buf, 0, sizeof(buf)); + bytes = read(signal_pipe[0], buf, sizeof(buf)); + if (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 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/signals.h b/signals.h new file mode 100644 index 0000000..5972b31 --- /dev/null +++ b/signals.h @@ -0,0 +1,40 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SIGNAL_H +#define SIGNAL_H + +#include + +int signal_init(void); +int signal_setup(void); +int signal_reset(void); +int signal_fd(void); +int signal_exists(int fd); +int signal_read(int fd); + +#endif -- cgit v1.1