diff options
-rw-r--r-- | Android.mk | 66 | ||||
-rw-r--r-- | Makefile | 51 | ||||
-rw-r--r-- | README | 67 | ||||
-rw-r--r-- | android.conf | 6 | ||||
-rw-r--r-- | bpf-filter.h | 101 | ||||
-rw-r--r-- | bpf.c | 206 | ||||
-rw-r--r-- | client.c | 1839 | ||||
-rw-r--r-- | client.h | 35 | ||||
-rw-r--r-- | common.c | 327 | ||||
-rw-r--r-- | common.h | 87 | ||||
-rw-r--r-- | config.h | 75 | ||||
-rw-r--r-- | configure.c | 420 | ||||
-rw-r--r-- | configure.h | 41 | ||||
-rw-r--r-- | dhcp.c | 1251 | ||||
-rw-r--r-- | dhcp.h | 178 | ||||
-rw-r--r-- | dhcpcd-hooks/01-test | 7 | ||||
-rw-r--r-- | dhcpcd-hooks/10-mtu | 5 | ||||
-rw-r--r-- | dhcpcd-hooks/20-dns.conf | 32 | ||||
-rw-r--r-- | dhcpcd-hooks/20-resolv.conf | 107 | ||||
-rw-r--r-- | dhcpcd-hooks/29-lookup-hostname | 34 | ||||
-rw-r--r-- | dhcpcd-hooks/30-hostname | 28 | ||||
-rw-r--r-- | dhcpcd-hooks/50-dhcpcd-compat | 31 | ||||
-rw-r--r-- | dhcpcd-hooks/50-ntp.conf | 82 | ||||
-rw-r--r-- | dhcpcd-hooks/50-yp.conf | 49 | ||||
-rw-r--r-- | dhcpcd-hooks/90-NetworkManager | 8 | ||||
-rw-r--r-- | dhcpcd-hooks/95-configured | 26 | ||||
-rw-r--r-- | dhcpcd-hooks/Makefile | 13 | ||||
-rwxr-xr-x | dhcpcd-run-hooks | 31 | ||||
-rw-r--r-- | dhcpcd-run-hooks.8 | 113 | ||||
-rw-r--r-- | dhcpcd-run-hooks.8.in | 113 | ||||
-rw-r--r-- | dhcpcd-run-hooks.in | 138 | ||||
-rw-r--r-- | dhcpcd.8 | 430 | ||||
-rw-r--r-- | dhcpcd.8.in | 430 | ||||
-rw-r--r-- | dhcpcd.c | 1038 | ||||
-rw-r--r-- | dhcpcd.conf | 13 | ||||
-rw-r--r-- | dhcpcd.conf.5 | 155 | ||||
-rw-r--r-- | dhcpcd.conf.5.in | 155 | ||||
-rw-r--r-- | dhcpcd.h | 95 | ||||
-rw-r--r-- | if-bsd.c | 241 | ||||
-rw-r--r-- | if-linux.c | 379 | ||||
-rw-r--r-- | logger.c | 89 | ||||
-rw-r--r-- | logger.h | 43 | ||||
-rw-r--r-- | lpf.c | 168 | ||||
-rw-r--r-- | mk/cc.mk | 28 | ||||
-rw-r--r-- | mk/depend.mk | 11 | ||||
-rw-r--r-- | mk/dist.mk | 31 | ||||
-rw-r--r-- | mk/files.mk | 9 | ||||
-rw-r--r-- | mk/man.mk | 25 | ||||
-rw-r--r-- | mk/os-BSD.mk | 5 | ||||
-rw-r--r-- | mk/os-Darwin.mk | 5 | ||||
-rw-r--r-- | mk/os-Linux.mk | 8 | ||||
-rw-r--r-- | mk/os.mk | 7 | ||||
-rw-r--r-- | mk/prog.mk | 68 | ||||
-rw-r--r-- | mk/scripts.mk | 9 | ||||
-rw-r--r-- | mk/sys.mk | 14 | ||||
-rw-r--r-- | net.c | 707 | ||||
-rw-r--r-- | net.h | 176 | ||||
-rw-r--r-- | showlease.c | 349 | ||||
-rw-r--r-- | signals.c | 125 | ||||
-rw-r--r-- | signals.h | 37 |
60 files changed, 10417 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..5cac7ea --- /dev/null +++ b/Android.mk @@ -0,0 +1,66 @@ +# 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_SHARED_LIBRARIES := libc libcutils +LOCAL_MODULE = dhcpcd +LOCAL_MODULE_TAGS := user development +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := showlease.c +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +LOCAL_SHARED_LIBRARIES := libc +LOCAL_MODULE = showlease +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..a6b066b --- /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 <roy@marples.name> + +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}/sys.mk +include ${MK}/os.mk +include ${MK}/prog.mk @@ -0,0 +1,67 @@ +dhcpcd-4 - DHCP client daemon +Copyright 2006-2008 Roy Marples <roy@marples.name> + + +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 you're building for an MMU-less system where fork() does not work, you +should add -DTHERE_IS_NO_FORK to your CPPFLAGS. +This also puts the --no-background flag on and stops the --background flag +from working. + +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..881f678 --- /dev/null +++ b/bpf-filter.h @@ -0,0 +1,101 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef BPF_ETHCOOK +# define BPF_ETHCOOK 0 +#endif +#ifndef BPF_WHOLEPACKET +# define BPF_WHOLEPACKET ~0U +#endif +static const struct bpf_insn const arp_bpf_filter [] = { +#ifndef BPF_SKIPTYPE + /* Make sure this is an ARP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3), +#endif + /* Make sure this is an ARP REQUEST... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), + /* or ARP REPLY... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; +static const size_t arp_bpf_filter_len = + sizeof(arp_bpf_filter) / sizeof(arp_bpf_filter[0]); + + +/* dhcp_bpf_filter taken from bpf.c in dhcp-3.1.0 + * + * Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * http://www.isc.org/ + */ + +static const struct bpf_insn const dhcp_bpf_filter [] = { +#ifndef BPF_SKIPTYPE + /* Make sure this is an IP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), +#endif + /* Make sure it's a UDP packet... */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + /* Get the IP header length... */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 + BPF_ETHCOOK), + /* Make sure it's to the right port... */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1), + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET), + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; +static const size_t dhcp_bpf_filter_len = + sizeof(dhcp_bpf_filter) / sizeof(dhcp_bpf_filter[0]); @@ -0,0 +1,206 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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) { + pf.bf_insns = UNCONST(arp_bpf_filter); + pf.bf_len = arp_bpf_filter_len; + fdp = &iface->arp_fd; + } else { + pf.bf_insns = UNCONST(dhcp_bpf_filter); + pf.bf_len = dhcp_bpf_filter_len; + fdp = &iface->raw_fd; + } + if (ioctl(fd, BIOCSETF, &pf) == -1) + goto eexit; + if (set_cloexec(fd) == -1) + goto eexit; + if (fdp) { + if (*fdp != -1) + close(*fdp); + *fdp = fd; + } + return fd; + +eexit: + free(iface->buffer); + iface->buffer = NULL; + close(fd); + return -1; +} + +ssize_t +send_raw_packet(const struct interface *iface, int protocol, + const void *data, ssize_t len) +{ + struct iovec iov[2]; + struct ether_header hw; + int fd; + + memset(&hw, 0, ETHER_HDR_LEN); + memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN); + hw.ether_type = htons(protocol); + iov[0].iov_base = &hw; + iov[0].iov_len = ETHER_HDR_LEN; + iov[1].iov_base = UNCONST(data); + iov[1].iov_len = len; + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + return writev(fd, iov, 2); +} + +/* BPF requires that we read the entire buffer. + * So we pass the buffer in the API so we can loop on >1 packet. */ +ssize_t +get_raw_packet(struct interface *iface, int protocol, + void *data, ssize_t len) +{ + int fd = -1; + struct bpf_hdr packet; + ssize_t bytes; + const unsigned char *payload; + + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + + 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..c34d318 --- /dev/null +++ b/client.c @@ -0,0 +1,1839 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <arpa/inet.h> + +#ifdef __linux__ +# include <netinet/ether.h> +#endif + +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#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" + +#define IPV4LL_LEASETIME 2 + +/* 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_INIT_IPV4LL 8 +#define STATE_PROBING 9 +#define STATE_ANNOUNCING 10 + +/* Constants taken from RFC 2131. */ +#define T1 0.5 +#define T2 0.875 +#define DHCP_BASE 4 +#define DHCP_MAX 64 +#define DHCP_RAND_MIN -1 +#define DHCP_RAND_MAX 1 +#define DHCP_ARP_FAIL 10 + +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 + +#define SOCKET_CLOSED 0 +#define SOCKET_OPEN 1 + +/* These are for IPV4LL, RFC 3927. */ +#define PROBE_WAIT 1 +#define PROBE_NUM 3 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +/* BSD systems always do a grauitous ARP when assigning an address, + * so we can do one less announce. */ +#ifdef BSD +# define ANNOUNCE_NUM 1 +#else +# define ANNOUNCE_NUM 2 +#endif +#define ANNOUNCE_INTERVAL 2 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + + +/* number of usecs in a second. */ +#define USECS_SECOND 1000000 +/* As we use timevals, we should use the usec part for + * greater randomisation. */ +#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND +#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND +#define PROBE_MIN_U PROBE_MIN * USECS_SECOND +#define PROBE_MAX_U PROBE_MAX * USECS_SECOND + +#define timernorm(tvp) \ + do { \ + while ((tvp)->tv_usec >= 1000000) { \ + (tvp)->tv_sec++; \ + (tvp)->tv_usec -= 1000000; \ + } \ + } while (0 /* CONSTCOND */); + +#define timerneg(tvp) ((tvp)->tv_sec < 0 || (tvp)->tv_usec < 0) + +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 timeout; + struct timeval stop; + struct timeval exit; + int state; + int messages; + time_t nakoff; + uint32_t xid; + int socket; + int *pid_fd; + int signal_fd; + int carrier; + int probes; + int claims; + int conflicts; + time_t defend; + struct in_addr fail; +}; + +#define LINK_UP 1 +#define LINK_UNKNOWN 0 +#define LINK_DOWN -1 + +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; +} + +#ifdef THERE_IS_NO_FORK +#define daemonise(a,b) 0 +#else +static int +daemonise(struct if_state *state, const struct options *options) +{ + pid_t pid; + sigset_t full; + sigset_t old; + char buf = '\0'; + int sidpipe[2]; + + if (state->options & DHCPCD_DAEMONISED || + !(options->options & DHCPCD_DAEMONISE)) + return 0; + + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + + /* Setup a signal pipe so parent knows when to exit. */ + if (pipe(sidpipe) == -1) { + 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; + } + + /* 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); + if (pid == 0) { + state->options |= DHCPCD_DAEMONISED; + timerclear(&state->exit); + return 0; + } + state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED; + return -1; +} +#endif + +#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; +} + +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++ = DHO_SUBNETMASK; + *p++ = sizeof(u32); + u32 = htonl(LINKLOCAL_MASK); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + *p++ = DHO_BROADCAST; + *p++ = sizeof(u32); + u32 = htonl(LINKLOCAL_BRDC); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + *p++ = DHO_END; + + for (;;) { + dhcp->yiaddr = htonl(LINKLOCAL_ADDR | + (((uint32_t)abs((int)arc4random()) + % 0xFD00) + 0x0100)); + if (dhcp->yiaddr != old_addr && + IN_LINKLOCAL(ntohl(dhcp->yiaddr))) + break; + } + return dhcp; +} + +static double +timeval_to_double(struct timeval *tv) +{ + return tv->tv_sec * 1.0 + tv->tv_usec * 1.0e-6; +} + +static void +get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) +{ + time_t t; + + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + + if (get_option_addr(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = get_netmask(dhcp->yiaddr); + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) { + /* Ensure that we can use the lease */ + t = 0; + if (t + (time_t)lease->leasetime < t) { + logger(LOG_WARNING, "lease of %u would overflow, " + "treating as infinite", lease->leasetime); + lease->leasetime = ~0U; /* Infinite lease */ + } + } else + lease->leasetime = DEFAULT_LEASETIME; + if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) + lease->rebindtime = 0; +} + +static int +get_old_lease(struct if_state *state) +{ + struct interface *iface = state->interface; + struct dhcp_lease *lease = &state->lease; + struct dhcp_message *dhcp = NULL; + struct timeval tv; + unsigned int offset = 0; + struct stat sb; + + if (stat(iface->leasefile, &sb) == -1) { + if (errno != ENOENT) + logger(LOG_ERR, "stat: %s", strerror(errno)); + goto eexit; + } + 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) { + logger(LOG_INFO, "read_lease: %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))) { + if (!(state->options & DHCPCD_LASTLEASE)) + goto eexit; + + /* 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 > (time_t)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; + iface->start_uptime = uptime(); + state->timeout.tv_sec = lease->renewaltime - offset; + 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; + struct timeval tv; + size_t len = 0; + unsigned char *duid = NULL; + uint32_t ul; + + state->state = STATE_INIT; + state->nakoff = 1; + state->options = options->options; + timerclear(&tv); + + if (options->request_address.s_addr == 0 && + (options->options & DHCPCD_INFORM || + options->options & DHCPCD_REQUEST || + (options->options & DHCPCD_DAEMONISED && + !(options->options & DHCPCD_BACKGROUND)))) + { + if (get_old_lease(state) != 0) + return -1; + timerclear(&state->timeout); + + if (!(options->options & DHCPCD_DAEMONISED) && + IN_LINKLOCAL(ntohl(lease->addr.s_addr))) + { + logger(LOG_ERR, "cannot request a link local address"); + return -1; + } + } else { + lease->addr.s_addr = options->request_address.s_addr; + lease->net.s_addr = options->request_netmask.s_addr; + } + + if (options->options & DHCPCD_REQUEST && + state->options & DHCPCD_ARP && + !state->offer) + { + state->offer = xzalloc(sizeof(*state->offer)); + state->offer->yiaddr = options->request_address.s_addr; + state->state = STATE_PROBING; + state->xid = arc4random(); + } + + /* 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; + } + + 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_DEBUG, "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); + } + } + + if (state->options & DHCPCD_LINK) { + open_link_socket(iface); + switch (carrier_status(iface->name)) { + case 0: + state->carrier = LINK_DOWN; + break; + case 1: + state->carrier = LINK_UP; + break; + default: + state->carrier = LINK_UNKNOWN; + } + } + + if (options->timeout > 0 && + !(state->options & DHCPCD_DAEMONISED)) + { + if (state->options & DHCPCD_IPV4LL) { + state->stop.tv_sec = options->timeout; + if (!(state->options & DHCPCD_BACKGROUND)) + state->exit.tv_sec = state->stop.tv_sec + 10; + } else if (!(state->options & DHCPCD_BACKGROUND)) + state->exit.tv_sec = options->timeout; + } + return 0; +} + +static int +do_socket(struct if_state *state, int mode) +{ + if (state->interface->raw_fd != -1) { + close(state->interface->raw_fd); + state->interface->raw_fd = -1; + } + if (mode == SOCKET_CLOSED) { + if (state->interface->udp_fd != -1) { + close(state->interface->udp_fd); + state->interface->udp_fd = -1; + } + if (state->interface->arp_fd != -1) { + close(state->interface->arp_fd); + state->interface->arp_fd = -1; + } + } + + /* Always have the UDP socket open to avoid the kernel sending + * ICMP unreachable messages. */ + /* For systems without SO_BINDTODEVICE, (ie BSD ones) we may get an + * error or EADDRINUSE when binding to INADDR_ANY as another dhcpcd + * instance could be running. + * Oddly enough, we don't care about this as the socket is there + * just to please the kernel - we don't care for reading from it. */ + if (mode == SOCKET_OPEN && + state->interface->udp_fd == -1 && + open_udp_socket(state->interface) == -1 && + (errno != EADDRINUSE || state->interface->addr.s_addr != 0)) + logger(LOG_ERR, "open_udp_socket: %s", strerror(errno)); + + 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, r; + struct in_addr from, to; + in_addr_t a = 0; + + if (state->carrier == LINK_DOWN) + return 0; + if (type == DHCP_RELEASE) + logger(LOG_DEBUG, "sending %s with xid 0x%x", + get_dhcp_op(type), state->xid); + else + logger(LOG_DEBUG, + "sending %s with xid 0x%x, next in %0.2f seconds", + get_dhcp_op(type), state->xid, + timeval_to_double(&state->timeout)); + state->messages++; + if (state->messages < 0) + state->messages = INT_MAX; + /* If we couldn't open a UDP port for our IP address + * then we cannot renew. + * This could happen if our IP was pulled out from underneath us. */ + if (state->interface->udp_fd == -1) { + a = state->interface->addr.s_addr; + state->interface->addr.s_addr = 0; + } + len = make_message(&dhcp, state->interface, &state->lease, state->xid, + type, options); + if (state->interface->udp_fd == -1) + state->interface->addr.s_addr = a; + from.s_addr = dhcp->ciaddr; + if (from.s_addr) + to.s_addr = state->lease.server.s_addr; + else + to.s_addr = 0; + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { + r = send_packet(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); + r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len); + free(udp); + if (r == -1) + logger(LOG_ERR, "send_raw_packet: %s", strerror(errno)); + } + free(dhcp); + /* Failed to send the packet? Return to the init state */ + if (r == -1) { + state->state = STATE_INIT; + timerclear(&state->timeout); + timerclear(&state->stop); + do_socket(state, SOCKET_CLOSED); + } + return r; +} + +static void +drop_config(struct if_state *state, const char *reason, + const struct options *options) +{ + if (state->new || strcmp(reason, "FAIL") == 0) { + 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 void +reduce_timers(struct if_state *state, const struct timeval *tv) +{ + if (timerisset(&state->exit)) { + timersub(&state->exit, tv, &state->exit); + if (!timerisset(&state->exit)) + state->exit.tv_sec = -1; + } + if (timerisset(&state->stop)) { + timersub(&state->stop, tv, &state->stop); + if (!timerisset(&state->stop)) + state->stop.tv_sec = -1; + } + if (timerisset(&state->timeout)) { + timersub(&state->timeout, tv, &state->timeout); + if (!timerisset(&state->timeout)) + state->timeout.tv_sec = -1; + } +} + +static struct timeval * +get_lowest_timer(struct if_state *state) +{ + struct timeval *ref = NULL; + + if (timerisset(&state->exit)) + ref = &state->exit; + if (timerisset(&state->stop)) { + if (!ref || timercmp(&state->stop, ref, <)) + ref = &state->stop; + } + if (timerisset(&state->timeout)) { + if (!ref || timercmp(&state->timeout, ref, <)) + ref = &state->timeout; + } + return ref; +} + +static int +wait_for_fd(struct if_state *state, int *fd) +{ + struct pollfd fds[4]; /* signal, link, raw, arp */ + struct interface *iface = state->interface; + int i, r, nfds = 0, msecs = -1; + struct timeval start, stop, diff, *ref; + static int lastinf = 0; + + /* Ensure that we haven't already timed out */ + ref = get_lowest_timer(state); + if (ref && timerneg(ref)) + return 0; + + /* We always listen to signals */ + fds[nfds].fd = state->signal_fd; + fds[nfds].events = POLLIN; + nfds++; + /* And links */ + if (iface->link_fd != -1) { + fds[nfds].fd = iface->link_fd; + fds[nfds].events = POLLIN; + nfds++; + } + + if (state->lease.leasetime == ~0U && + state->state == STATE_BOUND) + { + if (!lastinf) { + logger(LOG_DEBUG, "waiting for infinity"); + lastinf = 1; + } + ref = NULL; + } else if (state->carrier == LINK_DOWN && !ref) { + if (!lastinf) { + logger(LOG_DEBUG, "waiting for carrier"); + lastinf = 1; + } + if (timerisset(&state->exit)) + ref = &state->exit; + else + ref = NULL; + } else { + if (iface->raw_fd != -1) { + fds[nfds].fd = iface->raw_fd; + fds[nfds].events = POLLIN; + nfds++; + } + if (iface->arp_fd != -1) { + fds[nfds].fd = iface->arp_fd; + fds[nfds].events = POLLIN; + nfds++; + } + } + + /* Wait and then reduce the timers. + * If we reduce a timer to zero, set it negative to indicate timeout. + * We cannot reliably use select as there is no guarantee we will + * actually wait the whole time if greater than 31 days according + * to POSIX. So we loop on poll if needed as it's limitation of + * INT_MAX milliseconds is known. */ + for (;;) { + get_monotonic(&start); + if (ref) { + lastinf = 0; + if (ref->tv_sec > INT_MAX / 1000 || + (ref->tv_sec == INT_MAX / 1000 && + (ref->tv_usec + 999) / 1000 > INT_MAX % 1000)) + msecs = INT_MAX; + else + msecs = ref->tv_sec * 1000 + + (ref->tv_usec + 999) / 1000; + } else + msecs = -1; + r = poll(fds, nfds, msecs); + get_monotonic(&stop); + timersub(&stop, &start, &diff); + reduce_timers(state, &diff); + if (r == -1) { + if (errno != EINTR) + logger(LOG_ERR, "poll: %s", strerror(errno)); + return -1; + } + if (r) + break; + /* We should not have an infinite timeout if we get here */ + if (timerneg(ref)) + return 0; + } + + /* We configured our array in the order we should deal with them */ + for (i = 0; i < nfds; i++) + if (fds[i].revents & POLLIN) { + *fd = fds[i].fd; + return r; + } + return r; +} + +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"); + do_socket(state, SOCKET_CLOSED); + state->state = STATE_RENEW_REQUESTED; + timerclear(&state->timeout); + timerclear(&state->stop); + return 1; + case SIGHUP: + logger(LOG_INFO, "received SIGHUP, releasing lease"); + if (lease->addr.s_addr && + !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 0; +} + +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 start, stop, diff; + int retval; + + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + state->messages = 0; + state->conflicts = 0; + state->defend = 0; + timerclear(&state->exit); + if (clock_monotonic) + get_monotonic(&lease->boundtime); + + 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; + timerclear(&state->stop); + 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; + timerclear(&state->timeout); + reason = "IPV4LL"; + } else { + if (gettimeofday(&start, NULL) == 0) + lease->leasedfrom = start.tv_sec; + + get_lease(lease, state->new); + if (lease->frominfo) + reason = "TIMEOUT"; + + if (lease->leasetime == ~0U) { + lease->renewaltime = lease->rebindtime = lease->leasetime; + logger(LOG_INFO, "leased %s for infinity", + inet_ntoa(lease->addr)); + state->state = STATE_BOUND; + timerclear(&state->stop); + } else { + 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; + if (!lease->rebindtime) + lease->rebindtime = lease->leasetime * T2; + logger(LOG_INFO, + "leased %s for %u seconds", + inet_ntoa(lease->addr), lease->leasetime); + state->stop.tv_sec = lease->renewaltime; + state->stop.tv_usec = 0; + } + state->state = STATE_BOUND; + } + + state->xid = 0; + timerclear(&state->timeout); + if (!reason) { + if (state->old) { + if (state->old->yiaddr == state->new->yiaddr && + lease->server.s_addr) + reason = "RENEW"; + else + reason = "REBIND"; + } else + reason = "BOUND"; + } + /* If we have a monotonic clock we can safely substract the + * script execution time from our timers. + * Otherwise we can't as the script may update the real time. */ + if (clock_monotonic) + get_monotonic(&start); + retval = configure(iface, reason, state->new, state->old, + &state->lease, options, 1); + if (clock_monotonic) { + get_monotonic(&stop); + timersub(&stop, &start, &diff); + reduce_timers(state, &diff); + } + 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; + + timerclear(&state->stop); + timerclear(&state->exit); + if (state->state != STATE_DISCOVERING) + state->messages = 0; + + switch (state->state) { + case STATE_INIT: /* FALLTHROUGH */ + 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 if (state->carrier != LINK_DOWN || + !(state->options & DHCPCD_DAEMONISED)) + logger(LOG_ERR, "timed out"); + } + do_socket(state, SOCKET_CLOSED); + if (state->options & DHCPCD_INFORM || + state->options & DHCPCD_TEST) + return -1; + + if (state->carrier != LINK_DOWN && + (state->options & DHCPCD_IPV4LL || + state->options & DHCPCD_LASTLEASE)) + gotlease = get_old_lease(state); + + if (state->carrier != LINK_DOWN && + 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; + } + + if (gotlease == 0 && + state->offer->yiaddr != iface->addr.s_addr) + { + state->state = STATE_PROBING; + state->claims = 0; + state->probes = 0; + if (iface->addr.s_addr) + state->conflicts = 0; + return 1; + } + + if (gotlease == 0) + return bind_dhcp(state, options); + + if (iface->addr.s_addr) + reason = "EXPIRE"; + else + reason = "FAIL"; + drop_config(state, reason, options); + if (!(state->options & DHCPCD_DAEMONISED) && + (state->options & DHCPCD_DAEMONISE)) + return -1; + state->state = STATE_RENEW_REQUESTED; + return 1; + case STATE_BOUND: + logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr)); + if (state->carrier != LINK_DOWN) + do_socket(state, SOCKET_OPEN); + state->xid = arc4random(); + state->state = STATE_RENEWING; + state->stop.tv_sec = lease->rebindtime - lease->renewaltime; + break; + case STATE_RENEWING: + logger(LOG_ERR, "failed to renew, attempting to rebind"); + state->state = STATE_REBINDING; + if (lease->server.s_addr == 0) + state->stop.tv_sec = options->timeout; + else + state->stop.tv_sec = lease->rebindtime - \ + lease->renewaltime; + lease->server.s_addr = 0; + break; + case STATE_REBINDING: + logger(LOG_ERR, "failed to rebind"); + reason = "EXPIRE"; + drop_config(state, reason, options); + state->state = STATE_INIT; + break; + case STATE_PROBING: /* FALLTHROUGH */ + case STATE_ANNOUNCING: + /* We should have lost carrier here and exit timer went */ + logger(LOG_ERR, "timed out"); + return -1; + default: + logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d", + state->state); + } + + /* This effectively falls through into the handle_timeout funtion */ + return 1; +} + +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 = 0; + struct in_addr addr; + struct timeval tv; + + timerclear(&state->timeout); + if (timerneg(&state->exit)) + return handle_timeout_fail(state, options); + + if (state->state == STATE_RENEW_REQUESTED && + IN_LINKLOCAL(ntohl(lease->addr.s_addr))) + { + state->state = STATE_PROBING; + free(state->offer); + state->offer = read_lease(state->interface); + state->probes = 0; + state->claims = 0; + } + switch (state->state) { + case STATE_INIT_IPV4LL: + state->state = STATE_PROBING; + free(state->offer); + state->offer = ipv4ll_get_dhcp(0); + state->probes = 0; + state->claims = 0; + /* FALLTHROUGH */ + case STATE_PROBING: + 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++; + if (state->probes < PROBE_NUM) { + state->timeout.tv_sec = PROBE_MIN; + state->timeout.tv_usec = arc4random() % + (PROBE_MAX_U - PROBE_MIN_U); + timernorm(&state->timeout); + } else { + state->timeout.tv_sec = ANNOUNCE_WAIT; + state->timeout.tv_usec = 0; + } + logger(LOG_DEBUG, + "sending ARP probe (%d of %d), next in %0.2f seconds", + state->probes, PROBE_NUM, + timeval_to_double(&state->timeout)); + if (send_arp(iface, ARPOP_REQUEST, 0, + state->offer->yiaddr) == -1) + { + logger(LOG_ERR, "send_arp: %s", strerror(errno)); + return -1; + } + return 0; + } else { + /* We've waited for ANNOUNCE_WAIT after the final probe + * so the address is now ours */ + if (IN_LINKLOCAL(htonl(state->offer->yiaddr))) { + i = bind_dhcp(state, options); + state->state = STATE_ANNOUNCING; + state->timeout.tv_sec = ANNOUNCE_INTERVAL; + state->timeout.tv_usec = 0; + return i; + } + state->state = STATE_REQUESTING; + } + break; + case STATE_ANNOUNCING: + if (iface->arp_fd == -1) + open_socket(iface, ETHERTYPE_ARP); + if (state->claims < ANNOUNCE_NUM) { + state->claims++; + if (state->claims < ANNOUNCE_NUM) { + state->timeout.tv_sec = ANNOUNCE_INTERVAL; + state->timeout.tv_usec = 0; + logger(LOG_DEBUG, + "sending ARP announce (%d of %d)," + " next in %0.2f seconds", + state->claims, ANNOUNCE_NUM, + timeval_to_double(&state->timeout)); + } else + logger(LOG_DEBUG, + "sending ARP announce (%d of %d)", + state->claims, ANNOUNCE_NUM); + i = send_arp(iface, ARPOP_REQUEST, + state->new->yiaddr, state->new->yiaddr); + if (i == -1) { + logger(LOG_ERR, "send_arp: %s", strerror(errno)); + return -1; + } + } + if (state->claims < ANNOUNCE_NUM) + return 0; + if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { + /* We should pretend to be at the end + * of the DHCP negotation cycle */ + state->state = STATE_INIT; + state->messages = DHCP_MAX / DHCP_BASE; + state->probes = 0; + state->claims = 0; + timerclear(&state->stop); + goto dhcp_timeout; + } else { + state->state = STATE_BOUND; + close(iface->arp_fd); + iface->arp_fd = -1; + if (lease->leasetime != ~0U) { + state->stop.tv_sec = lease->renewaltime; + state->stop.tv_usec = 0; + if (clock_monotonic) { + get_monotonic(&tv); + timersub(&tv, &lease->boundtime, &tv); + timersub(&state->stop, &tv, &state->stop); + } else { + state->stop.tv_sec -= + (ANNOUNCE_INTERVAL * ANNOUNCE_NUM); + } + logger(LOG_DEBUG, "renew in %ld seconds", + (long int)state->stop.tv_sec); + } + } + return 0; + } + + if (timerneg(&state->stop)) + return handle_timeout_fail(state, options); + + switch (state->state) { + case STATE_BOUND: /* FALLTHROUGH */ + case STATE_RENEW_REQUESTED: + timerclear(&state->stop); + /* FALLTHROUGH */ + case STATE_INIT: + do_socket(state, SOCKET_OPEN); + state->xid = arc4random(); + iface->start_uptime = uptime(); + break; + } + + switch(state->state) { + case STATE_RENEW_REQUESTED: + /* If a renew was requested (ie, didn't timeout) we actually + * enter the REBIND state so that we broadcast to all servers. + * We need to do this for when we change networks. */ + lease->server.s_addr = 0; + state->messages = 0; + if (lease->addr.s_addr && !(state->options & DHCPCD_INFORM)) { + logger(LOG_INFO, "rebinding lease of %s", + inet_ntoa(lease->addr)); + state->state = STATE_REBINDING; + state->stop.tv_sec = options->timeout; + state->stop.tv_usec = 0; + break; + } + /* FALLTHROUGH */ + case STATE_INIT: + if (state->carrier == LINK_DOWN) + return 0; + 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; + } + if (!lease->addr.s_addr && !timerisset(&state->stop)) { + state->stop.tv_sec = DHCP_MAX + DHCP_RAND_MIN; + state->stop.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&state->stop); + } + break; + } + +dhcp_timeout: + if (state->carrier == LINK_DOWN) { + timerclear(&state->timeout); + return 0; + } + state->timeout.tv_sec = DHCP_BASE; + for (i = 0; i < state->messages; i++) { + state->timeout.tv_sec *= 2; + if (state->timeout.tv_sec > DHCP_MAX) { + state->timeout.tv_sec = DHCP_MAX; + break; + } + } + state->timeout.tv_sec += DHCP_RAND_MIN; + state->timeout.tv_usec = arc4random() % + (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&state->timeout); + + /* We send the message here so that the timeout is reported */ + 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: + if (iface->raw_fd == -1) + do_socket(state, SOCKET_OPEN); + send_message(state, DHCP_REQUEST, options); + break; + } + + return 0; +} + +static void +log_dhcp(int lvl, const char *msg, const struct dhcp_message *dhcp) +{ + char *a; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) + a = get_option_string(dhcp, DHO_MESSAGE); + else { + addr.s_addr = dhcp->yiaddr; + a = xstrdup(inet_ntoa(addr)); + } + r = get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + logger(lvl, "%s %s from %s `%s'", msg, a, + inet_ntoa(addr), dhcp->servername); + else if (r == 0) + logger(lvl, "%s %s from %s", msg, a, inet_ntoa(addr)); + else + logger(lvl, "%s %s", msg, a); + free(a); +} + +static int +handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp, + const struct options *options) +{ + struct dhcp_message *dhcp = *dhcpp; + struct interface *iface = state->interface; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; + struct in_addr addr; + size_t i; + int r; + + /* reset the message counter */ + state->messages = 0; + + /* We have to have DHCP type to work */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) { + log_dhcp(LOG_ERR, "no DHCP type in", dhcp); + return 0; + } + + /* Ensure that it's not from a blacklisted server. + * We should expand this to check IP and/or hardware address + * at the packet level. */ + if (options->blacklist_len != 0 && + get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID) == 0) + { + for (i = 0; i < options->blacklist_len; i++) { + if (options->blacklist[i] != addr.s_addr) + continue; + if (dhcp->servername[0]) + logger(LOG_WARNING, + "ignoring blacklisted server %s `%s'", + inet_ntoa(addr), dhcp->servername); + else + logger(LOG_WARNING, + "ignoring blacklisted server %s", + inet_ntoa(addr)); + return 0; + } + } + + /* We should restart on a NAK */ + if (type == DHCP_NAK) { + log_dhcp(LOG_WARNING, "NAK:", dhcp); + drop_config(state, "EXPIRE", options); + do_socket(state, SOCKET_CLOSED); + state->state = STATE_INIT; + /* If we constantly get NAKS then we should slowly back off */ + if (state->nakoff == 0) { + state->nakoff = 1; + timerclear(&state->timeout); + } else { + state->timeout.tv_sec = state->nakoff; + state->timeout.tv_usec = 0; + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + } + return 0; + } + + /* No NAK, so reset the backoff */ + state->nakoff = 1; + + /* Ensure that all required options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(options->requiremask, i) && + get_option_uint8(&tmp, dhcp, i) != 0) + { + log_dhcp(LOG_WARNING, "reject", dhcp); + return 0; + } + } + + if (type == DHCP_OFFER && state->state == STATE_DISCOVERING) { + lease->addr.s_addr = dhcp->yiaddr; + get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID); + log_dhcp(LOG_INFO, "offered", dhcp); + if (state->options & DHCPCD_TEST) { + run_script(options, iface->name, "TEST", dhcp, NULL); + /* Fake the fact we forked so we return 0 to userland */ + state->options |= DHCPCD_FORKED; + return -1; + } + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + timerclear(&state->timeout); + if (state->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + addr.s_addr = state->offer->yiaddr; + if (!has_address(iface->name, &addr, NULL)) { + state->state = STATE_PROBING; + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + timerclear(&state->stop); + return 1; + } + } + state->state = STATE_REQUESTING; + return 1; + } + + if (type == DHCP_OFFER) { + log_dhcp(LOG_INFO, "ignoring offer of", dhcp); + return 0; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", dhcp); + return 0; + } + + switch (state->state) { + case STATE_RENEW_REQUESTED: + case STATE_REQUESTING: + case STATE_RENEWING: + case STATE_REBINDING: + if (!(state->options & DHCPCD_INFORM)) { + get_option_addr(&lease->server.s_addr, + dhcp, DHO_SERVERID); + log_dhcp(LOG_INFO, "acknowledged", dhcp); + } + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + break; + default: + logger(LOG_ERR, "wrong state %d", state->state); + } + + do_socket(state, SOCKET_CLOSED); + r = bind_dhcp(state, options); + if (!(state->options & DHCPCD_ARP)) { + if (!(state->options & DHCPCD_INFORM)) + logger(LOG_DEBUG, "renew in %ld seconds", + (long int)state->stop.tv_sec); + return r; + } + state->state = STATE_ANNOUNCING; + if (state->options & DHCPCD_FORKED) + return r; + return 1; +} + +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 = NULL; + 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); + 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; + } + if (!dhcp) + dhcp = xmalloc(sizeof(*dhcp)); + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + logger(LOG_DEBUG, "bogus cookie, ignoring"); + continue; + } + /* Ensure it's the right transaction */ + 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; + } + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) + { + logger(LOG_DEBUG, "xid 0x%x is not for our hwaddr %s", + dhcp->xid, + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + 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 == DHO_PAD) + p--; + if (*p != DHO_END) + *++p = DHO_END; + } + retval = handle_dhcp(state, &dhcp, options); + if (retval == 0 && state->options & DHCPCD_TEST) + state->options |= DHCPCD_FORKED; + break; + } + + free(packet); + free(dhcp); + return retval; +} + +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; + /* Ignore messages from ourself */ + if (reply.ar_hln == iface->hwlen && + memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0) + 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_s == 0 && reply_t == state->offer->yiaddr))) + state->fail.s_addr = state->offer->yiaddr; + + /* Handle IPv4LL conflicts */ + if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) && + (reply_s == iface->addr.s_addr || + (reply_s == 0 && reply_t == iface->addr.s_addr))) + state->fail.s_addr = iface->addr.s_addr; + + if (state->fail.s_addr) { + 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) +{ + time_t up; + int cookie = state->offer->cookie; + + if (!IN_LINKLOCAL(htonl(state->fail.s_addr))) { + state->state = STATE_INIT; + free(state->offer); + state->offer = NULL; + state->lease.addr.s_addr = 0; + if (!cookie) + return 1; + state->timeout.tv_sec = DHCP_ARP_FAIL; + state->timeout.tv_usec = 0; + do_socket(state, SOCKET_OPEN); + send_message(state, DHCP_DECLINE, options); + do_socket(state, SOCKET_CLOSED); + return 0; + } + + if (state->fail.s_addr == state->interface->addr.s_addr) { + if (state->state == STATE_PROBING) + /* This should only happen when SIGALRM or + * link when down/up and we have a conflict. */ + drop_config(state, "EXPIRE", options); + else { + up = uptime(); + if (state->defend + DEFEND_INTERVAL > up) { + drop_config(state, "EXPIRE", options); + state->conflicts = -1; + /* drop through to set conflicts to 0 */ + } else { + state->defend = up; + return 0; + } + } + } + do_socket(state, SOCKET_CLOSED); + state->conflicts++; + timerclear(&state->stop); + if (state->conflicts > MAX_CONFLICTS) { + logger(LOG_ERR, "failed to obtain an IPv4LL address"); + state->state = STATE_INIT; + timerclear(&state->timeout); + if (!(state->options & DHCPCD_DAEMONISED) && + (state->options & DHCPCD_DAEMONISE)) + return -1; + return 1; + } + state->state = STATE_INIT_IPV4LL; + state->timeout.tv_sec = PROBE_WAIT; + state->timeout.tv_usec = 0; + return 0; +} + +static int +handle_link(struct if_state *state) +{ + int retval; + + retval = link_changed(state->interface); + if (retval == -1) { + logger(LOG_ERR, "link_changed: %s", strerror(errno)); + return -1; + } + if (retval == 0) + return 0; + + timerclear(&state->timeout); + switch (carrier_status(state->interface->name)) { + case -1: + logger(LOG_ERR, "carrier_status: %s", strerror(errno)); + return -1; + case 0: + if (state->carrier != LINK_DOWN) { + logger(LOG_INFO, "carrier lost"); + state->carrier = LINK_DOWN; + do_socket(state, SOCKET_CLOSED); + if (state->state != STATE_BOUND) + timerclear(&state->stop); + } + break; + default: + if (state->carrier != LINK_UP) { + logger(LOG_INFO, "carrier acquired"); + state->state = STATE_RENEW_REQUESTED; + state->carrier = LINK_UP; + timerclear(&state->stop); + return 1; + } + break; + } + return 0; +} + +int +dhcp_run(const struct options *options, int *pid_fd) +{ + struct interface *iface; + struct if_state *state = NULL; + int fd = -1, r = 0, sig; + + iface = read_interface(options->interface, options->metric); + if (!iface) { + logger(LOG_ERR, "read_interface: %s", strerror(errno)); + goto eexit; + } + logger(LOG_DEBUG, "hardware address = %s", + hwaddr_ntoa(iface->hwaddr, iface->hwlen)); + state = xzalloc(sizeof(*state)); + state->pid_fd = pid_fd; + state->interface = iface; + if (!(options->options & DHCPCD_TEST)) + run_script(options, iface->name, "PREINIT", NULL, NULL); + + 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(); + + if (state->options & DHCPCD_BACKGROUND && + !(state->options & DHCPCD_DAEMONISED)) + if (daemonise(state, options) == -1) + goto eexit; + + if (state->carrier == LINK_DOWN) + logger(LOG_INFO, "waiting for carrier"); + + for (;;) { + if (r == 0) + r = handle_timeout(state, options); + else if (r > 0) { + if (fd == state->signal_fd) { + if ((sig = signal_read()) != -1) + r = handle_signal(sig, state, options); + } else if (fd == iface->link_fd) + r = handle_link(state); + else if (fd == iface->raw_fd) + r = handle_dhcp_packet(state, options); + else if (fd == iface->arp_fd) { + if ((r = handle_arp_packet(state)) == -1) + r = handle_arp_fail(state, options); + } else + r = 0; + } + if (r == -1) + break; + if (r == 0) { + fd = -1; + r = wait_for_fd(state, &fd); + if (r == -1 && errno == EINTR) { + r = 1; + fd = state->signal_fd; + } + } else + r = 0; + } + +eexit: + if (iface) { + do_socket(state, SOCKET_CLOSED); + if (iface->link_fd != -1) + close(iface->link_fd); + free_routes(iface->routes); + free(iface->clientid); + free(iface->buffer); + free(iface); + } + + if (state) { + if (state->options & DHCPCD_FORKED) + r = 0; + if (state->options & DHCPCD_DAEMONISED) + unlink(options->pidfile); + free(state->offer); + free(state->new); + free(state->old); + free(state); + } + + return r; +} 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 <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef 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..d90c7d2 --- /dev/null +++ b/common.c @@ -0,0 +1,327 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef __APPLE__ +# include <mach/mach_time.h> +# include <mach/kern_return.h> +#endif + +#include <sys/param.h> +#include <sys/time.h> + +#include <errno.h> +#include <fcntl.h> +#ifdef BSD +# include <paths.h> +#endif +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "common.h" +#include "logger.h" + +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/null" +#endif + +int clock_monotonic = 0; + +/* Handy routine to read very long lines in text files. + * This means we read the whole line and avoid any nasty buffer overflows. */ +ssize_t +get_line(char **line, size_t *len, FILE *fp) +{ + char *p; + size_t last = 0; + + while(!feof(fp)) { + if (*line == NULL || last != 0) { + *len += BUFSIZ; + *line = xrealloc(*line, *len); + } + p = *line + last; + memset(p, 0, BUFSIZ); + 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 +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. + */ +#define NO_MONOTONIC "host does not support a monotonic clock - timing can skew" +int +get_monotonic(struct timeval *tp) +{ + static int posix_clock_set = 0; +#if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC) + struct timespec ts; + static clockid_t posix_clock; + + if (posix_clock_set == 0) { + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + posix_clock = CLOCK_MONOTONIC; + clock_monotonic = 1; + } + posix_clock_set = 1; + } + + if (clock_monotonic) { + if (clock_gettime(posix_clock, &ts) == 0) { + tp->tv_sec = ts.tv_sec; + tp->tv_usec = ts.tv_nsec / 1000; + return 0; + } + } +#elif defined(__APPLE__) +#define NSEC_PER_SEC 1000000000 + /* We can use mach kernel functions here. + * This is crap though - why can't they implement clock_gettime?*/ + static struct mach_timebase_info info = { 0, 0 }; + static double factor = 0.0; + uint64_t nano; + long rem; + + if (posix_clock_set == 0) { + if (mach_timebase_info(&info) == KERN_SUCCESS) { + factor = (double)info.numer / (double)info.denom; + clock_monotonic = 1; + } + posix_clock_set = 1; + } + if (clock_monotonic) { + nano = mach_absolute_time(); + if ((info.denom != 1 || info.numer != 1) && factor != 0.0) + nano *= factor; + tp->tv_sec = nano / NSEC_PER_SEC; + rem = nano % NSEC_PER_SEC; + if (rem < 0) { + tp->tv_sec--; + rem += NSEC_PER_SEC; + } + tp->tv_usec = rem / 1000; + return 0; + } +#endif + + /* Something above failed, so fall back to gettimeofday */ + if (!posix_clock_set) { + logger(LOG_WARNING, NO_MONOTONIC); + posix_clock_set = 1; + } + return gettimeofday(tp, NULL); +} + +time_t +uptime(void) +{ + struct timeval tv; + + if (get_monotonic(&tv) == -1) + return -1; + return tv.tv_sec; +} + +int +writepid(int fd, pid_t pid) +{ + char spid[16]; + ssize_t len; + + if (ftruncate(fd, (off_t)0) == -1) + return -1; + snprintf(spid, sizeof(spid), "%u\n", pid); + len = pwrite(fd, spid, strlen(spid), (off_t)0); + if (len != (ssize_t)strlen(spid)) + return -1; + return 0; +} + +void * +xmalloc(size_t s) +{ + void *value = malloc(s); + + if (value) + 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..2522663 --- /dev/null +++ b/common.h @@ -0,0 +1,87 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef COMMON_H +#define COMMON_H + +/* string.h pulls in features.h so the below define checks work */ +#include <sys/types.h> +#include <sys/time.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#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 +# if defined(__NetBSD__) || defined(__OpenBSD__) +# define HAVE_CLOSEFROM 1 +# endif +#endif +#ifndef HAVE_CLOSEFROM +int closefrom(int); +#endif + +int close_fds(void); +int set_cloexec(int); +int set_nonblock(int); +ssize_t get_line(char **, size_t *, FILE *); +extern int clock_monotonic; +int get_monotonic(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..cff7079 --- /dev/null +++ b/config.h @@ -0,0 +1,75 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PACKAGE "dhcpcd" +#define VERSION "4.0.1" + +/* + * By default we don't add a local link route if we got a routeable address. + * This is because dhcpcd can't really decide which interface should allow + * link local routing when we have more than one interface. + * Ideally the host network scripts should add the link local route for us. + * If not, you can define this to get dhcpcd to always add the link local route. + */ +// #define IPV4LL_ALWAYSROUTE + +/* Some systems do not have a working fork. */ +/* #define THERE_IS_NO_FORK */ + +/* Paths to things */ +#ifndef SYSCONFDIR +# define SYSCONFDIR "/system/etc/dhcpcd" +#endif +#ifndef LIBEXECDIR +# define LIBEXECDIR "/system/etc/dhcpcd" +#endif +#ifndef RUNDIR +# define RUNDIR "/data/misc/dhcp" +#endif +#ifndef DBDIR +# define DBDIR "/data/misc/dhcp" +#endif + +#ifndef CONFIG +# define CONFIG SYSCONFDIR "/" PACKAGE ".conf" +#endif +#ifndef SCRIPT +# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks" +#endif +#ifndef DUID +# define DUID SYSCONFDIR "/" PACKAGE ".duid" +#endif +#ifndef LEASEFILE +# define LEASEFILE DBDIR "/" PACKAGE "-%s.lease" +#endif +#ifndef PIDFILE +# define PIDFILE RUNDIR "/" PACKAGE "-%s.pid" +#endif + +#endif diff --git a/configure.c b/configure.c new file mode 100644 index 0000000..dad3bce --- /dev/null +++ b/configure.c @@ -0,0 +1,420 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +#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" + + +static int +exec_script(char *const *argv, char *const *env) +{ + pid_t pid; + sigset_t full; + sigset_t old; + + /* OK, we need to block signals */ + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + signal_reset(); + + switch (pid = vfork()) { + case -1: + logger(LOG_ERR, "vfork: %s", strerror(errno)); + break; + case 0: + sigprocmask(SIG_SETMASK, &old, NULL); + execve(argv[0], argv, env); + logger(LOG_ERR, "%s: %s", argv[0], strerror(errno)); + _exit(127); + /* NOTREACHED */ + } + + /* Restore our signals */ + signal_setup(); + sigprocmask(SIG_SETMASK, &old, NULL); + return pid; +} + +int +run_script(const struct options *options, const char *iface, + const char *reason, + const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) +{ + char *const argv[2] = { UNCONST(options->script), NULL }; + char **env = NULL, **ep; + char *path; + ssize_t e, elen; + pid_t pid; + int status = 0; + + logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason); + + /* 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'; + + pid = exec_script(argv, env); + if (pid == -1) + status = -1; + else if (pid != 0) { + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + 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, "deleting 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 && errno != ENOENT && errno != ESRCH) + 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; + + ort = get_option_routes(dhcp); + +#ifdef IPV4LL_ALWAYSROUTE + if (options->options & DHCPCD_IPV4LL && + IN_PRIVATE(ntohl(dhcp->yiaddr))) + { + for (rt = ort; rt; rt = rt->next) { + /* Check if we have already got a link locale route + * dished out by the DHCP server */ + if (rt->dest.s_addr == htonl(LINKLOCAL_ADDR) && + rt->net.s_addr == htonl(LINKLOCAL_MASK)) + 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 + + /* 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; + } + } + free_routes(ort); + free_routes(iface->routes); + iface->routes = nr; + return retval; +} + +static int +delete_address(struct interface *iface) +{ + int retval; + logger(LOG_DEBUG, "deleting IP address %s/%d", + inet_ntoa(iface->addr), + inet_ntocidr(iface->net)); + retval = del_address(iface->name, &iface->addr, &iface->net); + if (retval == -1 && errno != EADDRNOTAVAIL) + logger(LOG_ERR, "del_address: %s", strerror(errno)); + iface->addr.s_addr = 0; + iface->net.s_addr = 0; + return retval; +} + +int +configure(struct interface *iface, const char *reason, + const struct dhcp_message *dhcp, const struct dhcp_message *old, + const struct dhcp_lease *lease, const struct options *options, + int up) +{ + 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) + up = 0; + else { + addr.s_addr = dhcp->yiaddr; + if (addr.s_addr == 0) + addr.s_addr = lease->addr.s_addr; + /* Ensure we have all the needed values */ + if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1) + net.s_addr = get_netmask(addr.s_addr); + if (get_option_addr(&brd.s_addr, dhcp, DHO_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); + delete_address(iface); + } + + run_script(options, iface->name, reason, NULL, old); + return 0; + } + + /* This also changes netmask */ + if (!(options->options & DHCPCD_INFORM) || + !has_address(iface->name, &addr, &net)) { + logger(LOG_DEBUG, "adding IP address %s/%d", + inet_ntoa(addr), inet_ntocidr(net)); + if (add_address(iface->name, &addr, &net, &brd) == -1 && + 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) + delete_address(iface); + +#ifdef __linux__ + /* On linux, we need to change the subnet route to have our metric. */ + if (iface->addr.s_addr != lease->addr.s_addr && + options->metric > 0 && net.s_addr != INADDR_BROADCAST) + { + dest.s_addr = addr.s_addr & net.s_addr; + gate.s_addr = 0; + add_route(iface->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)); + + run_script(options, iface->name, reason, dhcp, old); + return 0; +} diff --git a/configure.h b/configure.h new file mode 100644 index 0000000..fe065db --- /dev/null +++ b/configure.h @@ -0,0 +1,41 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCONFIG_H +#define DHCPCONFIG_H + +#include "dhcpcd.h" +#include "dhcp.h" +#include "net.h" + +int run_script(const struct options *, const char *, const char *, + const struct dhcp_message *, const struct dhcp_message *); +int configure(struct interface *, const char *, + const struct dhcp_message *, const struct dhcp_message *, + const struct dhcp_lease *, const struct options *, int); + +#endif @@ -0,0 +1,1251 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" + +#define REQUEST (1 << 0) +#define UINT8 (1 << 1) +#define UINT16 (1 << 2) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define IPV4 (1 << 6) +#define STRING (1 << 7) +#define PAIR (1 << 8) +#define ARRAY (1 << 9) +#define RFC3361 (1 << 10) +#define RFC3397 (1 << 11) +#define RFC3442 (1 << 12) + +#define 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" }, + { 81, STRING | RFC3397, "fqdn_name" }, + { 85, IPV4 | ARRAY, "nds_servers" }, + { 86, STRING, "nds_tree_name" }, + { 87, STRING, "nds_context" }, + { 88, STRING | RFC3397, "bcms_controller_names" }, + { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 91, UINT32, "client_last_transaction_time" }, + { 92, IPV4 | ARRAY, "associated_ip" }, + { 98, STRING, "uap_servers" }, + { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 113, STRING, "netinfo_server_tag" }, + { 114, STRING, "default_url" }, + { 118, IPV4, "subnet_selection" }, + { 119, STRING | RFC3397, "domain_search" }, + { 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_option_mask(uint8_t *mask, char **opts, int add) +{ + char *token, *p = *opts, *t; + const struct dhcp_opt *opt; + int match, n; + + while ((token = strsep(&p, ", "))) { + if (*token == '\0') + continue; + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + match = 0; + if (strcmp(opt->var, token) == 0) + match = 1; + else { + errno = 0; + n = strtol(token, &t, 0); + if (errno == 0 && !*t) + if (opt->option == n) + match = 1; + } + if (match) { + if (add == 1) + add_option_mask(mask, + opt->option); + else + del_option_mask(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 DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (valid_length(opt, 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; + /* We check we are inside our length again incase + * the data is NOT terminated correctly. */ + while ((l = *q++) && q - p < pl) { + ltype = l & 0xc0; + if (ltype == 0x80 || ltype == 0x40) + return 0; + else if (ltype == 0xc0) { /* pointer */ + l = (l & 0x3f) << 8; + l |= *q++; + /* save source of first jump. */ + if (!r) + r = q; + hops++; + if (hops > 255) + return 0; + q = p + l; + if (q - p >= pl) + return 0; + } else { + /* straightforward name segment, add with '.' */ + count += l + 1; + if (out) { + if ((ssize_t)l + 1 > len) { + errno = ENOBUFS; + return -1; + } + memcpy(out, q, l); + out += l; + *out++ = '.'; + len -= l; + len--; + } + q += l; + } + } + /* change last dot to space */ + if (out) + *(out - 1) = ' '; + if (r) + q = r; + } + + /* change last space to zero terminator */ + if (out) + *(out - 1) = 0; + + return count; +} + +static ssize_t +decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const uint8_t *e; + ssize_t 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 = 0; + int len; + const uint8_t *p; + char *s; + + p = get_option(dhcp, option, &len, &type); + if (!p || *p == '\0') + return NULL; + + if (type & RFC3397) { + type = decode_rfc3397(NULL, 0, len, p); + if (!type) { + errno = EINVAL; + return NULL; + } + s = xmalloc(sizeof(char) * type); + decode_rfc3397(s, type, len, p); + return s; + } + + if (type & RFC3361) + return decode_rfc3361(len, p); + + s = xmalloc(sizeof(char) * (len + 1)); + memcpy(s, p, len); + s[len] = '\0'; + return s; +} + +/* This calculates the netmask that we should use for static routes. + * This IS different from the calculation used to calculate the netmask + * for an interface address. */ +static uint32_t +route_netmask(uint32_t ip_in) +{ + /* used to be unsigned long - check if error */ + uint32_t p = ntohl(ip_in); + uint32_t t; + + if (IN_CLASSA(p)) + t = ~IN_CLASSA_NET; + else { + if (IN_CLASSB(p)) + t = ~IN_CLASSB_NET; + else { + if (IN_CLASSC(p)) + t = ~IN_CLASSC_NET; + else + t = 0; + } + } + + while (t & p) + t >>= 1; + + return (htonl(~t)); +} + +/* We need to obey routing options. + * If we have a CSR then we only use that. + * Otherwise we add static routes and then routers. */ +struct rt * +get_option_routes(const struct dhcp_message *dhcp) +{ + const uint8_t *p; + const uint8_t *e; + struct rt *routes = NULL; + struct rt *route = NULL; + int len; + + /* If we have CSR's then we MUST use these only */ + p = get_option(dhcp, DHO_CSR, &len, NULL); + /* Check for crappy MS option */ + if (!p) + p = get_option(dhcp, DHO_MSCSR, &len, NULL); + if (p) { + routes = decode_rfc3442_rt(len, p); + if (routes) + return routes; + } + + /* OK, get our static routes first. */ + p = get_option(dhcp, DHO_STATICROUTE, &len, NULL); + if (p) { + e = p + len; + while (p < e) { + if (route) { + route->next = xmalloc(sizeof(*route)); + route = route->next; + } else + routes = route = xmalloc(sizeof(*routes)); + route->next = NULL; + memcpy(&route->dest.s_addr, p, 4); + p += 4; + memcpy(&route->gate.s_addr, p, 4); + p += 4; + route->net.s_addr = route_netmask(route->dest.s_addr); + } + } + + /* Now grab our routers */ + p = get_option(dhcp, DHO_ROUTER, &len, NULL); + if (p) { + e = p + len; + while (p < e) { + if (route) { + route->next = xzalloc(sizeof(*route)); + route = route->next; + } else + routes = route = xzalloc(sizeof(*route)); + memcpy(&route->gate.s_addr, p, 4); + p += 4; + } + } + + return routes; +} + +static size_t +encode_rfc1035(const char *src, uint8_t *dst, size_t len) +{ + const char *c = src; + uint8_t *p = dst; + uint8_t *lp = p++; + + if (len == 0) + return 0; + while (c < src + len) { + if (*c == '\0') + break; + if (*c == '.') { + /* Skip the trailing . */ + if (c == src + len - 1) + break; + *lp = p - lp - 1; + if (*lp == '\0') + return p - dst; + lp = p++; + } else + *p++ = (uint8_t) *c; + c++; + } + *lp = p - lp - 1; + *p++ = '\0'; + return p - dst; +} + +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, *lp, *p; + uint8_t *n_params = NULL; + time_t up = uptime() - iface->start_uptime; + uint32_t ul; + uint16_t sz; + const struct dhcp_opt *opt; + + dhcp = xzalloc(sizeof (*dhcp)); + m = (uint8_t *)dhcp; + p = dhcp->options; + + if ((type == DHCP_INFORM || + type == DHCP_RELEASE || + type == DHCP_REQUEST) && + !IN_LINKLOCAL(ntohl(iface->addr.s_addr))) + { + 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++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (type == DHCP_REQUEST) { + *p++ = DHO_MAXMESSAGESIZE; + *p++ = 2; + sz = get_mtu(iface->name); + if (sz < MTU_MIN) { + if (set_mtu(iface->name, MTU_MIN) == 0) + sz = MTU_MIN; + } + sz = htons(sz); + memcpy(p, &sz, 2); + p += 2; + } + + if (iface->clientid) { + *p++ = DHO_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++ = DHO_USERCLASS; + memcpy(p, options->userclass, options->userclass[0] + 1); + p += options->userclass[0] + 1; + } + + if (options->vendorclassid[0]) { + *p++ = DHO_VENDORCLASSID; + memcpy(p, options->vendorclassid, + options->vendorclassid[0] + 1); + p += options->vendorclassid[0] + 1; + } + } + + 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(DHO_IPADDRESS, lease->addr); + if (lease->server.s_addr) + PUTADDR(DHO_SERVERID, lease->server); + } +#undef PUTADDR + + if (options->leasetime != 0) { + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(options->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + if (type == DHCP_DISCOVER || + type == DHCP_INFORM || + type == DHCP_REQUEST) + { + if (options->hostname[0]) { + *p++ = DHO_HOSTNAME; + memcpy(p, options->hostname, options->hostname[0] + 1); + p += options->hostname[0] + 1; + } + if (options->fqdn != FQDN_DISABLE) { + /* IETF DHC-FQDN option (81), RFC4702 */ + *p++ = DHO_FQDN; + lp = p; + *p++ = 3; + /* + * Flags: 0000NEOS + * S: 1 => Client requests Server to update + * a RR in DNS as well as PTR + * O: 1 => Server indicates to client that + * DNS has been updated + * E: 1 => Name data is DNS format + * N: 1 => Client requests Server to not + * update DNS + */ + *p++ = (options->fqdn & 0x09) | 0x04; + *p++ = 0; /* from server for PTR RR */ + *p++ = 0; /* from server for A RR if S=1 */ + ul = encode_rfc1035(options->hostname + 1, p, + options->hostname[0]); + *lp += ul; + p += ul; + } + + /* vendor is already encoded correctly, so just add it */ + if (options->vendor[0]) { + *p++ = DHO_VENDOR; + memcpy(p, options->vendor, options->vendor[0] + 1); + p += options->vendor[0] + 1; + } + + *p++ = DHO_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (opt = dhcp_opts; opt->option; opt++) { + if (!(opt->type & REQUEST || + has_option_mask(options->requestmask, opt->option))) + continue; + switch (opt->option) { + case DHO_RENEWALTIME: /* FALLTHROUGH */ + case DHO_REBINDTIME: + if (type == DHCP_INFORM) + continue; + break; + } + *p++ = opt->option; + } + *n_params = p - n_params - 1; + } + *p++ = DHO_END; + +#ifdef BOOTP_MESSAGE_LENTH_MIN + /* Some crappy DHCP servers think they have to obey the BOOTP minimum + * message length. + * They are wrong, but we should still cater for them. */ + while (p - m < BOOTP_MESSAGE_LENTH_MIN) + *p++ = DHO_PAD; +#endif + + *message = dhcp; + return p - m; +} + +ssize_t +write_lease(const struct interface *iface, const struct dhcp_message *dhcp) +{ + int fd; + ssize_t bytes = sizeof(*dhcp); + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l; + uint8_t o = 0; + + 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 == DHO_END) { + bytes = p - (const uint8_t *)dhcp; + break; + } + p++; + if (o != DHO_PAD) { + l = *p++; + p += l; + } + } + bytes = write(fd, dhcp, bytes); + close(fd); + return bytes; +} + +struct dhcp_message * +read_lease(const struct interface *iface) +{ + int fd; + struct dhcp_message *dhcp; + ssize_t bytes; + + fd = open(iface->leasefile, O_RDONLY); + if (fd == -1) + 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, DHO_OPTIONSOVERLOADED); + + if (!env) { + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (has_option_mask(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, DHO_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, DHO_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_option_mask(options->nomask, opt->option)) + continue; + val = NULL; + p = get_option(dhcp, opt->option, &pl, NULL); + if (!p) + continue; + /* We only want the FQDN name */ + if (opt->option == DHO_FQDN) { + p += 3; + pl -= 3; + } + len = print_option(NULL, 0, opt->type, pl, p); + if (len < 0) + return -1; + e = strlen(prefix) + strlen(opt->var) + len + 4; + v = val = *ep++ = xmalloc(e); + v += snprintf(val, e, "%s_%s=", prefix, opt->var); + if (len != 0) + print_option(v, len, opt->type, pl, p); + } + + return ep - env; +} @@ -0,0 +1,178 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include <arpa/inet.h> + +#include <stdint.h> + +#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 DHO +{ + DHO_PAD = 0, + DHO_SUBNETMASK = 1, + DHO_ROUTER = 3, + DHO_DNSSERVER = 6, + DHO_HOSTNAME = 12, + DHO_DNSDOMAIN = 15, + DHO_MTU = 26, + DHO_BROADCAST = 28, + DHO_STATICROUTE = 33, + DHO_NISDOMAIN = 40, + DHO_NISSERVER = 41, + DHO_NTPSERVER = 42, + DHO_VENDOR = 43, + DHO_IPADDRESS = 50, + DHO_LEASETIME = 51, + DHO_OPTIONSOVERLOADED = 52, + DHO_MESSAGETYPE = 53, + DHO_SERVERID = 54, + DHO_PARAMETERREQUESTLIST = 55, + DHO_MESSAGE = 56, + DHO_MAXMESSAGESIZE = 57, + DHO_RENEWALTIME = 58, + DHO_REBINDTIME = 59, + DHO_VENDORCLASSID = 60, + DHO_CLIENTID = 61, + DHO_USERCLASS = 77, /* RFC 3004 */ + DHO_FQDN = 81, + DHO_DNSSEARCH = 119, /* RFC 3397 */ + DHO_CSR = 121, /* RFC 3442 */ + DHO_MSCSR = 249, /* MS code for RFC 3442 */ + DHO_END = 255 +}; + +/* FQDN values - lsnybble used in flags + * hsnybble to create order + * and to allow 0x00 to mean disable + */ +enum FQDN { + FQDN_DISABLE = 0x00, + FQDN_NONE = 0x18, + FQDN_PTR = 0x20, + FQDN_BOTH = 0x31 +}; + +/* Sizes for DHCP options */ +#define DHCP_CHADDR_LEN 16 +#define SERVERNAME_LEN 64 +#define BOOTFILE_LEN 128 +#define DHCP_UDP_LEN (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; + time_t leasedfrom; + struct timeval boundtime; + uint8_t frominfo; +}; + +#define add_option_mask(var, val) (var[val >> 3] |= 1 << (val & 7)) +#define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) +#define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7))) +int make_option_mask(uint8_t *, char **, int); +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..609b3a1 --- /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.<iface>.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..e757ddf --- /dev/null +++ b/dhcpcd-hooks/20-resolv.conf @@ -0,0 +1,107 @@ +# Generate /etc/resolv.conf +# Support resolvconf(8) if available +# We can merge other dhcpcd resolv.conf files into one like resolvconf, +# but resolvconf is preferred as other applications like VPN clients +# can readily hook into it. +# Also, resolvconf can configure local nameservers such as bind +# or dnsmasq. This is important as the libc resolver isn't that powerful. + +resolv_conf_dir="${state_dir}/resolv.conf" + +build_resolv_conf() +{ + local cf="/etc/resolv.conf.${interface}" + local interfaces= header= search= srvs= servers= x= + + # Build a list of interfaces + interfaces=$(list_interfaces "${resolv_conf_dir}") + + # Build the resolv.conf + if [ -n "${interfaces}" ]; then + # Build the header + for x in ${interfaces}; do + header="${header}${header:+, }${x}" + done + + # Build the search list + search=$(cd "${resolv_conf_dir}"; \ + key_get_value "search " ${interfaces}) + [ -n "${search}" ] && search="search $(uniqify ${search})\n" + + # Build the nameserver list + srvs=$(cd "${resolv_conf_dir}"; \ + key_get_value "nameserver " ${interfaces}) + for x in $(uniqify ${srvs}); do + servers="${servers}nameserver ${x}\n" + done + fi + header="${signature_base}${header:+ ${from} }${header}" + + # Assemble resolv.conf using our head and tail files + [ -f "${cf}" ] && rm -f "${cf}" + echo "${header}" > "${cf}" + if [ -f /etc/resolv.conf.head ]; then + cat /etc/resolv.conf.head >> "${cf}" + else + echo "# /etc/resolv.conf.head can replace this line" >> "${cf}" + fi + printf "${search}${servers}" >> "${cf}" + if [ -f /etc/resolv.conf.tail ]; then + cat /etc/resolv.conf.tail >> "${cf}" + else + echo "# /etc/resolv.conf.tail can replace this line" >> "${cf}" + fi + mv -f "${cf}" /etc/resolv.conf +} + +add_resolv_conf() +{ + local x= conf="${signature}\n" + + # If we don't have any configuration, remove it + if [ -z "${new_domain_name_servers}" -a \ + -z "${new_domain_name}" -a \ + -z "${new_domain_search}" ]; then + remove_resolv_conf + return $? + fi + + if [ -n "${new_domain_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}" + return $? + fi + + if [ -e "${resolv_conf_dir}/${interface}" ]; then + rm -f "${resolv_conf_dir}/${interface}" + fi + if [ ! -d "${resolv_conf_dir}" ]; then + mkdir -p "${resolv_conf_dir}" + fi + printf "${conf}" > "${resolv_conf_dir}/${interface}" + build_resolv_conf +} + +remove_resolv_conf() +{ + if type resolvconf >/dev/null 2>&1; then + resolvconf -d "${interface}" -f + else + if [ -e "${resolv_conf_dir}/${interface}" ]; then + rm -f "${resolv_conf_dir}/${interface}" + fi + build_resolv_conf + fi +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) add_resolv_conf;; +PREINIT|EXPIRE|FAIL|IPV4LL|RELEASE|STOP) remove_resolv_conf;; +esac diff --git a/dhcpcd-hooks/29-lookup-hostname b/dhcpcd-hooks/29-lookup-hostname new file mode 100644 index 0000000..3dfade3 --- /dev/null +++ b/dhcpcd-hooks/29-lookup-hostname @@ -0,0 +1,34 @@ +# Lookup the hostname in DNS if not set + +lookup_hostname() +{ + [ -z "${new_ip_address}" ] && return 1 + local h= + # Silly ISC programs love to send error text to stdout + if type dig >/dev/null 2>&1; then + h=`dig +short -x ${new_ip_address}` + if [ $? = 0 ]; then + echo "${h}" | sed 's/\.$//' + return 0 + fi + elif type host >/dev/null 2>&1; then + h=`host ${new_ip_address}` + if [ $? = 0 ]; then + echo "${h}" \ + | sed 's/.* domain name pointer \(.*\)./\1/' + return 0 + fi + fi + return 1 +} + +set_hostname() +{ + if [ -z "${new_host_name}" -a -z "${new_fqdn_name}" ]; then + export new_host_name="$(lookup_hostname)" + fi +} + +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..b2e5fc8 --- /dev/null +++ b/dhcpcd-hooks/30-hostname @@ -0,0 +1,28 @@ +# Set the hostname from DHCP data if required + +need_hostname() +{ + case "$(hostname)" in + ""|"(none)"|localhost|localhost.localdomain) + [ -n "${new_host_name}" -o -n "${new_fqdn_name}" ];; + "${old_host_name}"|"${old_fqdn_name}") + true;; + *) + false;; + esac +} + +set_hostname() +{ + if need_hostname; then + if [ -n "${new_host_name}" ]; then + hostname "${new_host_name}" + else + hostname "${new_fqdn_name}" + fi + 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..536f14e --- /dev/null +++ b/dhcpcd-hooks/50-ntp.conf @@ -0,0 +1,82 @@ +# Sample dhcpcd hook script for ntp +# Like our resolv.conf hook script, we store a database of ntp.conf files +# and merge into /etc/ntp.conf + +# 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 + +ntp_conf_dir="${state_dir}/ntp.conf" + +build_ntp_conf() +{ + local cf="/etc/ntp.conf.${interface}" + local interfaces= header= srvs= servers= x= + + # Build a list of interfaces + interfaces=$(list_interfaces "${ntp_conf_dir}") + + if [ -n "${interfaces}" ]; then + # Build the header + for x in ${interfaces}; do + header="${header}${header:+, }${x}" + done + + # Build a server list + srvs=$(cd "${ntp_conf_dir}"; + key_get_value "server " ${interfaces}) + if [ -n "${srvs}" ]; then + for x in $(uniqify ${srvs}); do + servers="${servers}server ${x}\n" + done + fi + fi + + # Merge our config into ntp.conf + [ -e "${cf}" ] && rm -f "${cf}" + remove_markers "${signature_base}" "${signature_base_end}" \ + /etc/ntp.conf > "${cf}" + if [ -n "${servers}" ]; then + echo "${signature_base}${header:+ ${from} }${header}" >> "${cf}" + printf "${search}${servers}" >> "${cf}" + echo "${signature_base_end}${header:+ ${from} }${header}" >> "${cf}" + fi + + # If we changed anything, restart ntpd + if change_file /etc/ntp.conf "${cf}"; then + [ -n "${ntpd_restart_cmd}" ] && ${ntpd_restart_cmd} + fi +} + +add_ntp_conf() +{ + local cf="${ntp_conf_dir}/${interface}" x= + + [ -e "${cf}" ] && rm "${cf}" + [ -d "${ntp_conf_dir}" ] || mkdir -p "${ntp_conf_dir}" + if [ -n "${new_ntp_servers}" ]; then + for x in ${new_ntp_servers}; do + echo "server ${x}" >> "${cf}" + done + fi + build_ntp_conf +} + +remove_ntp_conf() +{ + if [ -e "${ntp_conf_dir}/${interface}" ]; then + rm "${ntp_conf_dir}/${interface}" + fi + build_ntp_conf +} + +case "${reason}" in +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) add_ntp_conf add;; +PREINIT|EXPIRE|FAIL|IPV4LL|RELEASE|STOP) remove_ntp_conf del;; +esac diff --git a/dhcpcd-hooks/50-yp.conf b/dhcpcd-hooks/50-yp.conf new file mode 100644 index 0000000..a2296eb --- /dev/null +++ b/dhcpcd-hooks/50-yp.conf @@ -0,0 +1,49 @@ +# Sample dhcpcd hook for ypbind +# This script is only suitable for the Linux version. + +ypbind_pid() +{ + [ -s /var/run/ypbind.pid ] && cat /var/run/ypbind.pid +} + +make_yp_conf() +{ + [ -z "${new_nis_domain}" -a -z "${new_nis_servers}" ] && return 0 + local cf=/etc/yp.conf."${interface}" prefix= x= pid= + rm -f "${cf}" + echo "${signature}" > "${cf}" + if [ -n "${new_nis_domain}" ]; then + domainname "${new_nis_domain}" + if [ -n "${new_nis_servers}" ]; then + prefix="domain ${new_nis_domain} server " + else + echo "domain ${new_nis_domain} broadcast" >> "${cf}" + fi + else + prefix="ypserver " + fi + for x in ${new_nis_servers}; do + echo "${prefix}${x}" >> "${cf}" + done + save_conf /etc/yp.conf + 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..c4d69fe --- /dev/null +++ b/dhcpcd-hooks/90-NetworkManager @@ -0,0 +1,8 @@ +# Hook for NetworkManager-0.7.0 +# NOTE: NetworkManager will override the script dhcpcd calls, so this hook +# only makes sense if NetworkManager is patched NOT to override the +# script dhcpcd would call. + +if [ -x /usr/libexec/nm-dhcp-client.action ]; then + /usr/libexec/nm-dhcp-client.action +fi diff --git a/dhcpcd-hooks/95-configured b/dhcpcd-hooks/95-configured new file mode 100644 index 0000000..93f1c43 --- /dev/null +++ b/dhcpcd-hooks/95-configured @@ -0,0 +1,26 @@ +# 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|STOP) + setprop dhcp.${interface}.result "failed" + ;; + +RELEASE) + setprop dhcp.${interface}.result "released" + ;; +esac diff --git a/dhcpcd-hooks/Makefile b/dhcpcd-hooks/Makefile new file mode 100644 index 0000000..cfb19f7 --- /dev/null +++ b/dhcpcd-hooks/Makefile @@ -0,0 +1,13 @@ +LIBEXECDIR?= ${PREFIX}/libexec +HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks +SYSTEMSCRIPTS= 01-test 10-mtu 20-resolv.conf 30-hostname +FILES= ${SYSTEMSCRIPTS} ${HOOKSCRIPTS} +FILESDIR= ${HOOKDIR} + +all: + +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..db9c4f8 --- /dev/null +++ b/dhcpcd-run-hooks @@ -0,0 +1,31 @@ +#!/system/bin/sh +# dhcpcd client configuration script + +# Handy variables and functions for our hooks to use +from="from" +signature_base="# Generated by dhcpcd" +signature="${signature_base} ${from} ${interface}" +signature_base_end="# End of dhcpcd" +signature_end="${signature_base_end} ${from} ${interface}" +state_dir="/data/misc/dhcpcd" + +# We source each script into this one so that scripts run earlier can +# remove variables from the environment so later scripts don't see them. +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + /system/etc/dhcpcd/dhcpcd.enter-hook \ + /system/etc/dhcpcd/dhcpcd-hooks/* \ + /system/etc/dhcpcd/dhcpcd.exit-hook +do + for skip in ${skip_hooks}; do + case "${hook}" in + "${skip}") continue 2;; + */[0-9][0-9]"-${skip}") continue 2;; + */[0-9][0-9]"-${skip}.sh") continue 2;; + esac + done + if 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..dca5378 --- /dev/null +++ b/dhcpcd-run-hooks.8 @@ -0,0 +1,113 @@ +.\" 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 August 14, 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 and user defined hook scripts. +System hook scripts are found in +.Pa /system/etc/dhcpcd/dhcpcd-hooks +and the user defined hooks are +.Pa /system/etc/dhcpcd/dhcpcd.enter-hook . +and +.Pa /system/etc/dhcpcd/dhcpcd.exit-hook . +The default install supplies hook scripts for configuring +.Pa /etc/resolv.conf +and the hostname. +Your distribution may have included other hook scripts to say configure +ntp or ypbind. +A test hook is also supplied that simply echos the dhcp variables to the +console from DISCOVER message. +.Pp +Each time +.Nm +is invoked, +.Ev $interface +is set to the interface that +.Nm dhcpcd +is run on and +.Ev $reason +is to the reason why +.Nm +was invoked. +DHCP information to be configured is held in variables starting with the word +new_ and old DHCP information to be removed is held in variables starting with +the word old_. +.Nm dhcpcd +can display the full list of variables it knows how about by using the +.Fl V , -variables +argument. +.Pp +Here's a list of reasons why +.Nm +could be invoked: +.Bl -tag -width indent +.It Dv PREINIT +dhcpcd is starting up and any pre-initialisation should be done. +.It Dv INFORM +dhcpcd informed a DHCP server about it's address and obtained other +configuration details. +.It Dv BOUND +dhcpcd obtained a new lease from a DHCP server. +.It Dv RENEW +dhcpcd renewed it's lease. +.It Dv REBIND +dhcpcd has rebound to a new DHCP server. +.It Dv REBOOT +dhcpcd successfully requested a lease from a DHCP server. +.It Dv 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.enter-hook +and any scripts found in +.Pa /system/etc/dhcpcd/dhcpcd-hooks +in a lexical order and then finally +.Pa /system/etc/dhcpcd/dhcpcd.exit-hook +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples <roy@marples.name> +.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..72669f5 --- /dev/null +++ b/dhcpcd-run-hooks.8.in @@ -0,0 +1,113 @@ +.\" 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 August 14, 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 and user defined hook scripts. +System hook scripts are found in +.Pa @HOOKDIR@ +and the user defined hooks are +.Pa @SYSCONFDIR@/dhcpcd.enter-hook . +and +.Pa @SYSCONFDIR@/dhcpcd.exit-hook . +The default install supplies hook scripts for configuring +.Pa /etc/resolv.conf +and the hostname. +Your distribution may have included other hook scripts to say configure +ntp or ypbind. +A test hook is also supplied that simply echos the dhcp variables to the +console from DISCOVER message. +.Pp +Each time +.Nm +is invoked, +.Ev $interface +is set to the interface that +.Nm dhcpcd +is run on and +.Ev $reason +is to the reason why +.Nm +was invoked. +DHCP information to be configured is held in variables starting with the word +new_ and old DHCP information to be removed is held in variables starting with +the word old_. +.Nm dhcpcd +can display the full list of variables it knows how about by using the +.Fl V , -variables +argument. +.Pp +Here's a list of reasons why +.Nm +could be invoked: +.Bl -tag -width indent +.It Dv PREINIT +dhcpcd is starting up and any pre-initialisation should be done. +.It Dv INFORM +dhcpcd informed a DHCP server about it's address and obtained other +configuration details. +.It Dv BOUND +dhcpcd obtained a new lease from a DHCP server. +.It Dv RENEW +dhcpcd renewed it's lease. +.It Dv REBIND +dhcpcd has rebound to a new DHCP server. +.It Dv REBOOT +dhcpcd successfully requested a lease from a DHCP server. +.It Dv 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.enter-hook +and any scripts found in +.Pa @HOOKDIR@ +in a lexical order and then finally +.Pa @SYSCONFDIR@/dhcpcd.exit-hook +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples <roy@marples.name> +.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..1e5d5b3 --- /dev/null +++ b/dhcpcd-run-hooks.in @@ -0,0 +1,138 @@ +#!/bin/sh +# dhcpcd client configuration script + +# Handy variables and functions for our hooks to use +from="from" +signature_base="# Generated by dhcpcd" +signature="${signature_base} ${from} ${interface}" +signature_base_end="# End of dhcpcd" +signature_end="${signature_base_end} ${from} ${interface}" +state_dir="/var/run/dhcpcd" + +# Ensure that all arguments are unique +uniqify() +{ + local result= + + while [ -n "$1" ]; do + case " ${result} " in + *" $1 "*);; + *) result="${result}${result:+ }$1";; + esac + shift + done + echo "${result}" +} + +# List interface config files in a dir +# We may wish to control the order at some point rather than just lexical +list_interfaces() +{ + local x= interfaces= + for x in "$1"/*; do + [ -e "${x}" ] || continue + interfaces="${interfaces}${interfaces:+ }${x##*/}" + done + echo "${interfaces}" +} + +# We normally use sed to extract values using a key from a list of files +# but sed may not always be available at the time. +key_get_value() +{ + local key="$1" value= x= line= + + shift + if type sed >/dev/null 2>&1; then + sed -n "s/^${key}//p" $@ + else + for x; do + while read line; do + case "${line}" in + "${key}"*) echo "${line##${key}}";; + esac + done < "${x}" + done + fi +} + +# We normally use sed to remove markers from a configuration file +# but sed may not always be available at the time. +remove_markers() +{ + local m1="$1" m2="$2" x= line= in_marker=0 + + shift; shift + if type sed >/dev/null 2>&1; then + sed "/^${m1}/,/^${m2}/d" $@ + else + for x; do + while read line; do + case "${line}" in + "${m1}"*) in_marker=1;; + "${m2}"*) in_marker=0;; + *) [ ${in_marker} = 0 ] && echo "${line}";; + esac + done < "${x}" + done + fi +} + +# Compare two files +# It different, replace first with second otherwise remove second +change_file() +{ + if type cmp >/dev/null 2>&1; then + cmp -s "$1" "$2" + elif type diff >/dev/null 2>&1; then + diff -q "$1" "$2" >/dev/null + else + # Hopefully we're only working on small text files ... + [ "$(cat "$1")" = "$(cat "$2")" ] + fi + if [ $? -eq 0 ]; then + rm -f "$2" + return 1 + fi + mv -f "$2" "$1" + return 0 +} + +# Save a config file +save_conf() +{ + if [ -f "$1" ]; then + rm -f "$1"-pre."${interface}" + mv -f "$1" "$1"-pre."${interface}" + fi +} + +# Restore a config file +restore_conf() +{ + [ -f "$1"-pre."${interface}" ] || return 1 + rm -f "$1" + mv -f "$1"-pre."${interface}" "$1" +} + + +# We source each script into this one so that scripts run earlier can +# remove variables from the environment so later scripts don't see them. +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + @SYSCONFDIR@/dhcpcd.enter-hook \ + @HOOKDIR@/* \ + @SYSCONFDIR@/dhcpcd.exit-hook +do + for skip in ${skip_hooks}; do + case "${hook}" in + "${skip}") continue 2;; + */[0-9][0-9]"-${skip}") continue 2;; + */[0-9][0-9]"-${skip}.sh") continue 2;; + esac + done + if [ -f "${hook}" ]; then + . "${hook}" + fi +done diff --git a/dhcpcd.8 b/dhcpcd.8 new file mode 100644 index 0000000..ac6150c --- /dev/null +++ b/dhcpcd.8 @@ -0,0 +1,430 @@ +.\" 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 August 20, 2008 +.Dt DHCPCD 8 SMM +.Sh NAME +.Nm dhcpcd +.Nd an RFC 2131 compliant DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl bdknpqABDEGKLSTV +.Op Fl c , -script Ar script +.Op Fl f , -config Ar file +.Op Fl h , -hostname Ar hostname +.Op Fl i , -vendorclassid Ar vendorclassid +.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 +.Op Fl Q , -require Ar option +.Op Fl X , -blacklist Ar address +.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 +.Li RFC 2131 . +.Nm +gets the host information +.Po +IP address, routes, etc +.Pc +from a DHCP server and configures the network +.Ar interface +of the +machine on which it is running. +.Nm +then runs the configuration script which writes DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currenly blank, (null) or localhost then +.Nm +sets the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +Then it attempts to renew its lease and reconfigure if the new lease changes. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it probes for a valid IPv4LL address +.Po +aka ZeroConf, aka APIPA +.Pc . +Once obtained it restarts the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the +.Fl L , -noipv4ll +option. +.Ss Hooking into DHCP events +.Nm +runs +.Pa /system/etc/dhcpcd/dhcpcd-run-hooks , +or the script specified by the +.Fl c , -script +option. +This script runs each script found in +.Pa /system/etc/dhcpcd/dhcpcd-hooks +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 b , -background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.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 +sends 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 , -vendorclassid Ar vendorclassid +Override the +.Ar vendorclassid +field sent. The default is +dhcpcd <version>. +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. +.Nm +then waits until this process has exited. +.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 , -rebind +Notifies an existing +.Nm +process running on the +.Ar interface +to rebind it's lease. +.Nm +will not re-configure itself or use any other command line arguments. +.Nm +will timeout the rebind after 30 seconds at which point the lease will be +expired and +.Nm +will enter the discovery state to obtain a new lease. +Use the +.Fl t , -timeout +option to change this. +If +.Nm +is not running, then it starts up as normal. +This option used to be renew, but rebind is more accurate as we need to +broadcast the request instead of unicasting. +.It Fl p , -persistent +.Nm +normally deconfigures the +.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 DISCOVER to find servers to offer an address. +.Nm +then requests the address used. +You can use this option to skip the BROADCAST step and just request the +.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 DISCOVER 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 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. +You should also include the optional +.Ar cidr +network number in-case the address is not already configured on the interface. +.Nm +remains running and pretends it has an infinite lease. +.Nm +will not de-configure the interface when it exits. +If +.Nm +fails to contact a DHCP server then it returns a failure instead of falling +back on IPv4LL. +.It Fl t , -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 members of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , -vendor Ar code , Ns Ar value +Add an enscapulated vendor option. +.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 will signal an existing +.Nm +process running on the +.Ar interface +to deconfigure the +.Ar interface +and exit. +.Nm +then waits until this process has exited. +.It Fl D , -duid +Generate an +.Li RFC 4361 +compliant clientid. +This requires persistent storage and not all DHCP servers work with it so it's +not enabled by default. +.Nm +generates the DUID and stores in it +.Pa /system/etc/dhcpcd/dhcpcd.duid +This file 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 disable, none, ptr and both. +The current hostname or the hostname specified using the +.Fl h , -hostname +option must be a FQDN. +.Nm +itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Fl I , -clientid Ar clientid +Change the default clientid sent from the interface hardware address. +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 messages are still logged though. +.It Fl A , -noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl B , -nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl C , -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 G , -nogateway +Don't set any default routes. +.It Fl K , -nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. +.It Fl L , -noipv4ll +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). +.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 Q , -require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +.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 . +.It Fl X, -blacklist Ar address +Ignores all DHCP messages which have this +.Ar address +as the server ID. +This may be expanded in future releases to ignore all packets +matching either the IP or hardware +.Ar address . +.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. +.It Pa /var/run/dhcpcd\- Ns Ar interface Ns .pid +Stores the PID of +.Nm +running on the +.Ar interface . +.El +.Sh SEE ALSO +.Xr 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 <roy@marples.name> +.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..6c82d3f --- /dev/null +++ b/dhcpcd.8.in @@ -0,0 +1,430 @@ +.\" 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 August 20, 2008 +.Dt DHCPCD 8 SMM +.Sh NAME +.Nm dhcpcd +.Nd an RFC 2131 compliant DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl bdknpqABDEGKLSTV +.Op Fl c , -script Ar script +.Op Fl f , -config Ar file +.Op Fl h , -hostname Ar hostname +.Op Fl i , -vendorclassid Ar vendorclassid +.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 +.Op Fl Q , -require Ar option +.Op Fl X , -blacklist Ar address +.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 +.Li RFC 2131 . +.Nm +gets the host information +.Po +IP address, routes, etc +.Pc +from a DHCP server and configures the network +.Ar interface +of the +machine on which it is running. +.Nm +then runs the configuration script which writes DNS information to +.Xr resolvconf 8 , +if available, otherwise directly to +.Pa /etc/resolv.conf . +If the hostname is currenly blank, (null) or localhost then +.Nm +sets the hostname to the one supplied by the DHCP server. +.Nm +then daemonises and waits for the lease renewal time to lapse. +Then it attempts to renew its lease and reconfigure if the new lease changes. +.Ss Local Link configuration +If +.Nm +failed to obtain a lease, it probes for a valid IPv4LL address +.Po +aka ZeroConf, aka APIPA +.Pc . +Once obtained it restarts the process of looking for a DHCP server to get a +proper address. +.Pp +When using IPv4LL, +.Nm +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the +.Fl L , -noipv4ll +option. +.Ss Hooking into DHCP events +.Nm +runs +.Pa @SCRIPT@ , +or the script specified by the +.Fl c , -script +option. +This script runs each script found in +.Pa @HOOKDIR@ +in a lexical order. +The default installation supplies the scripts +.Pa 01-test , +.Pa 10-mtu , +.Pa 20-resolv.conf +and +.Pa 30-hostname . +You can disable each script by using the +.Fl C , -nohook +option. +See +.Xr dhcpcd-run-hooks 8 +for details on how these scripts work. +.Nm +currently ignores the exit code of the script. +.Ss Fine tuning +You can fine tune the behaviour of +.Nm +with the following options: +.Bl -tag -width indent +.It Fl b , -background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.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 +sends 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 , -vendorclassid Ar vendorclassid +Override the +.Ar vendorclassid +field sent. The default is +dhcpcd <version>. +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. +.Nm +then waits until this process has exited. +.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 , -rebind +Notifies an existing +.Nm +process running on the +.Ar interface +to rebind it's lease. +.Nm +will not re-configure itself or use any other command line arguments. +.Nm +will timeout the rebind after 30 seconds at which point the lease will be +expired and +.Nm +will enter the discovery state to obtain a new lease. +Use the +.Fl t , -timeout +option to change this. +If +.Nm +is not running, then it starts up as normal. +This option used to be renew, but rebind is more accurate as we need to +broadcast the request instead of unicasting. +.It Fl p , -persistent +.Nm +normally deconfigures the +.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 DISCOVER to find servers to offer an address. +.Nm +then requests the address used. +You can use this option to skip the BROADCAST step and just request the +.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 DISCOVER 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 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. +You should also include the optional +.Ar cidr +network number in-case the address is not already configured on the interface. +.Nm +remains running and pretends it has an infinite lease. +.Nm +will not de-configure the interface when it exits. +If +.Nm +fails to contact a DHCP server then it returns a failure instead of falling +back on IPv4LL. +.It Fl t , -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 members of the class DHCP options other than the +default, without having to know things like hardware address or hostname. +.It Fl v , -vendor Ar code , Ns Ar value +Add an enscapulated vendor option. +.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 will signal an existing +.Nm +process running on the +.Ar interface +to deconfigure the +.Ar interface +and exit. +.Nm +then waits until this process has exited. +.It Fl D , -duid +Generate an +.Li RFC 4361 +compliant clientid. +This requires persistent storage and not all DHCP servers work with it so it's +not enabled by default. +.Nm +generates the DUID and stores in it +.Pa @SYSCONFDIR@/dhcpcd.duid +This file 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 disable, none, ptr and both. +The current hostname or the hostname specified using the +.Fl h , -hostname +option must be a FQDN. +.Nm +itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Fl I , -clientid Ar clientid +Change the default clientid sent from the interface hardware address. +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 messages are still logged though. +.It Fl A , -noarp +Don't request or claim the address by ARP. +This also disables IPv4LL. +.It Fl B , -nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. +.It Fl C , -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 G , -nogateway +Don't set any default routes. +.It Fl K , -nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. +.It Fl L , -noipv4ll +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). +.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 Q , -require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +.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 . +.It Fl X, -blacklist Ar address +Ignores all DHCP messages which have this +.Ar address +as the server ID. +This may be expanded in future releases to ignore all packets +matching either the IP or hardware +.Ar address . +.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. +.It Pa /var/run/dhcpcd\- Ns Ar interface Ns .pid +Stores the PID of +.Nm +running on the +.Ar interface . +.El +.Sh SEE ALSO +.Xr 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 <roy@marples.name> +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd.c b/dhcpcd.c new file mode 100644 index 0000000..e674bd2 --- /dev/null +++ b/dhcpcd.c @@ -0,0 +1,1038 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; + +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +#include "config.h" +#include "client.h" +#include "dhcpcd.h" +#include "dhcp.h" +#include "net.h" +#include "logger.h" + +#ifdef ANDROID +#include <linux/capability.h> +#include <linux/prctl.h> +#include <cutils/properties.h> +#include <private/android_filesystem_config.h> +#endif + +/* Don't set any optional arguments here so we retain POSIX + * compatibility with getopt */ +#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:" + +static int doversion = 0; +static int dohelp = 0; +static const struct option longopts[] = { + {"background", no_argument, NULL, 'b'}, + {"script", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"config", required_argument, NULL, 'f'}, + {"hostname", optional_argument, NULL, 'h'}, + {"vendorclassid", optional_argument, NULL, 'i'}, + {"release", no_argument, NULL, 'k'}, + {"leasetime", required_argument, NULL, 'l'}, + {"metric", required_argument, NULL, 'm'}, + {"rebind", no_argument, NULL, 'n'}, + {"option", required_argument, NULL, 'o'}, + {"persistent", no_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"request", optional_argument, NULL, 'r'}, + {"inform", optional_argument, NULL, 's'}, + {"timeout", required_argument, NULL, 't'}, + {"userclass", required_argument, NULL, 'u'}, + {"vendor", required_argument, NULL, 'v'}, + {"exit", no_argument, NULL, 'x'}, + {"noarp", no_argument, NULL, 'A'}, + {"nobackground", no_argument, NULL, 'B'}, + {"nohook", required_argument, NULL, 'C'}, + {"duid", no_argument, NULL, 'D'}, + {"lastlease", no_argument, NULL, 'E'}, + {"fqdn", optional_argument, NULL, 'F'}, + {"nogateway", no_argument, NULL, 'G'}, + {"clientid", optional_argument, NULL, 'I'}, + {"nolink", no_argument, NULL, 'K'}, + {"noipv4ll", no_argument, NULL, 'L'}, + {"nooption", optional_argument, NULL, 'O'}, + {"require", required_argument, NULL, 'Q'}, + {"test", no_argument, NULL, 'T'}, + {"variables", no_argument, NULL, 'V'}, + {"blacklist", required_argument, NULL, 'X'}, + {"help", no_argument, &dohelp, 1}, + {"version", no_argument, &doversion, 1}, +#ifdef CMDLINE_COMPAT + {"classid", optional_argument, NULL, 'i'}, + {"renew", no_argument, NULL, 'n'}, + {"nohostname", no_argument, NULL, 'H'}, + {"nomtu", no_argument, NULL, 'M'}, + {"nontp", no_argument, NULL, 'N'}, + {"nodns", no_argument, NULL, 'R'}, + {"msscr", no_argument, NULL, 'S'}, + {"nonis", no_argument, NULL, 'Y'}, +#endif + {NULL, 0, NULL, '\0'} +}; + +#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) +{ + printf("usage: "PACKAGE" [-dknpqxADEGHKLOTV] [-c script] [-f file ] [-h hostname]\n" + " [-i classID ] [-l leasetime] [-m metric] [-o option] [-r ipaddr]\n" + " [-s ipaddr] [-t timeout] [-u userclass] [-F none|ptr|both]\n" + " [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] <interface>\n"); +} + +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]; +} + +#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; +} + +static int +parse_option(int opt, char *oarg, struct options *options) +{ + int i; + char *p; + ssize_t s; + struct in_addr addr; + + switch(opt) { + case 'b': + options->options |= DHCPCD_BACKGROUND; + break; + case 'c': + strlcpy(options->script, oarg, sizeof(options->script)); + break; + case 'h': + if (oarg) + s = parse_string(options->hostname + 1, + HOSTNAME_MAX_LEN, oarg); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "hostname: %s", strerror(errno)); + return -1; + } + if (s != 0 && options->hostname[1] == '.') { + logger(LOG_ERR, "hostname cannot begin with a ."); + return -1; + } + options->hostname[0] = (uint8_t)s; + break; + case 'i': + if (oarg) + s = parse_string((char *)options->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, oarg); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "vendorclassid: %s", strerror(errno)); + return -1; + } + *options->vendorclassid = (uint8_t)s; + break; + case 'l': + if (*oarg == '-') { + logger(LOG_ERR, + "leasetime must be a positive value"); + return -1; + } + errno = 0; + options->leasetime = (uint32_t)strtol(oarg, NULL, 0); + if (errno == EINVAL || errno == ERANGE) { + logger(LOG_ERR, "`%s' out of range", oarg); + return -1; + } + 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_option_mask(options->requestmask, &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': + s = USERCLASS_MAX_LEN - options->userclass[0] - 1; + s = parse_string((char *)options->userclass + options->userclass[0] + 2, + s, oarg); + if (s == -1) { + logger(LOG_ERR, "userclass: %s", strerror(errno)); + return -1; + } + if (s != 0) { + options->userclass[options->userclass[0] + 1] = s; + options->userclass[0] += s + 1; + } + break; + case 'v': + p = strchr(oarg, ','); + if (!p || !p[1]) { + logger(LOG_ERR, "invalid vendor format"); + return -1; + } + *p = '\0'; + i = atoint(oarg); + oarg = p + 1; + if (i < 1 || i > 254) { + logger(LOG_ERR, "vendor option should be between" + " 1 and 254 inclusive"); + return -1; + } + 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; + } + break; + case 'A': + options->options &= ~DHCPCD_ARP; + /* IPv4LL requires ARP */ + options->options &= ~DHCPCD_IPV4LL; + break; + case 'B': + options->options &= ~DHCPCD_DAEMONISE; + break; + case 'C': + /* Commas to spaces for shell */ + while ((p = strchr(oarg, ','))) + *p = ' '; + s = strlen("skip_hooks=") + strlen(oarg) + 1; + p = xmalloc(sizeof(char) * s); + snprintf(p, s, "skip_hooks=%s", oarg); + add_environ(options, p, 0); + free(p); + break; + case 'D': + options->options |= DHCPCD_DUID; + break; + case 'E': + options->options |= DHCPCD_LASTLEASE; + break; + case 'F': + if (!oarg) { + options->fqdn = FQDN_BOTH; + break; + } + if (strcmp(oarg, "none") == 0) + options->fqdn = FQDN_NONE; + else if (strcmp(oarg, "ptr") == 0) + options->fqdn = FQDN_PTR; + else if (strcmp(oarg, "both") == 0) + options->fqdn = FQDN_BOTH; + else if (strcmp(oarg, "disable") == 0) + options->fqdn = FQDN_DISABLE; + else { + logger(LOG_ERR, "invalid value `%s' for FQDN", + oarg); + return -1; + } + break; + case 'G': + options->options &= ~DHCPCD_GATEWAY; + break; + case 'I': + /* Strings have a type of 0 */; + options->clientid[1] = 0; + if (oarg) + s = parse_string_hwaddr((char *)options->clientid + 1, + CLIENTID_MAX_LEN, oarg, 1); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "clientid: %s", strerror(errno)); + return -1; + } + options->clientid[0] = (uint8_t)s; + if (s == 0) { + options->options &= ~DHCPCD_DUID; + options->options &= ~DHCPCD_CLIENTID; + } + break; + case 'K': + options->options &= ~DHCPCD_LINK; + break; + case 'L': + options->options &= ~DHCPCD_IPV4LL; + break; + case 'O': + if (make_option_mask(options->requestmask, &oarg, -1) != 0 || + make_option_mask(options->requiremask, &oarg, -1) != 0 || + make_option_mask(options->nomask, &oarg, 1) != 0) + { + logger(LOG_ERR, "unknown option `%s'", oarg); + return -1; + } + break; + case 'Q': + if (make_option_mask(options->requiremask, &oarg, 1) != 0 || + make_option_mask(options->requestmask, &oarg, 1) != 0) + { + logger(LOG_ERR, "unknown option `%s'", oarg); + return -1; + } + break; + case 'X': + if (!inet_aton(oarg, &addr)) { + logger(LOG_ERR, "`%s' is not a valid IP address", + oarg); + return -1; + } + options->blacklist = xrealloc(options->blacklist, + sizeof(in_addr_t) * (options->blacklist_len + 1)); + options->blacklist[options->blacklist_len] = addr.s_addr; + options->blacklist_len++; + break; + default: + return 0; + } + + return 1; +} + +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; + struct timespec ts; + +#ifdef ANDROID + switchUser(); +#endif + closefrom(3); + /* Saves calling fflush(stream) in the logger */ + setlinebuf(stdout); + openlog(PACKAGE, LOG_PID, LOG_LOCAL0); + setlogprefix(PACKAGE ": "); + + options = xzalloc(sizeof(*options)); + options->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE; + options->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK; + options->timeout = DEFAULT_TIMEOUT; + strlcpy(options->script, SCRIPT, sizeof(options->script)); + + options->vendorclassid[0] = snprintf((char *)options->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, + "%s %s", PACKAGE, VERSION); + +#ifdef CMDLINE_COMPAT + add_option_mask(options->requestmask, DHO_DNSSERVER); + add_option_mask(options->requestmask, DHO_DNSDOMAIN); + add_option_mask(options->requestmask, DHO_DNSSEARCH); + add_option_mask(options->requestmask, DHO_NISSERVER); + add_option_mask(options->requestmask, DHO_NISDOMAIN); + add_option_mask(options->requestmask, DHO_NTPSERVER); + + /* If the duid file exists, then enable duid by default + * This means we don't break existing clients that easily :) */ + if ((f = fopen(DUID, "r"))) { + options->options |= DHCPCD_DUID; + fclose(f); + } +#endif + + 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); + + 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); + + 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; + 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_option_mask(options->requestmask, DHO_MTU); + break; + case 'N': + del_option_mask(options->requestmask, DHO_NTPSERVER); + break; + case 'R': + del_option_mask(options->requestmask, DHO_DNSSERVER); + del_option_mask(options->requestmask, DHO_DNSDOMAIN); + del_option_mask(options->requestmask, DHO_DNSSEARCH); + break; + case 'S': + add_option_mask(options->requestmask, DHO_MSCSR); + break; + case 'Y': + del_option_mask(options->requestmask, DHO_NISSERVER); + del_option_mask(options->requestmask, DHO_NISDOMAIN); + break; +#endif + default: + i = parse_option(opt, optarg, options); + if (i == 1) + break; + if (i == 0) + usage(); + goto abort; + } + } + +#ifdef THERE_IS_NO_FORK + options->options &= ~DHCPCD_DAEMONISE; +#endif + +#ifndef ANDROID + /* android runs us as user "dhcp" */ + if (geteuid()) + logger(LOG_WARNING, PACKAGE " will not work correctly unless" + " run as root"); +#endif + + if (options->options & DHCPCD_TEST) { + if (options->options & DHCPCD_REQUEST || + options->options & DHCPCD_INFORM) { + logger(LOG_ERR, + "cannot test with --inform or --request"); + goto abort; + } + + if (options->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; + } + } + + prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3)); + snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface); + setlogprefix(prefix); + snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE, + options->interface); + free(prefix); + + if (options->request_address.s_addr == 0 && + (options->options & DHCPCD_INFORM || + options->options & DHCPCD_REQUEST)) + { + errno = 0; + if (get_address(options->interface, + &options->request_address, + &options->request_netmask) != 1) + { + if (errno) + logger(LOG_ERR, "get_address: %s", + strerror(errno)); + else + logger(LOG_ERR, "no existing address"); + goto abort; + } + } + if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) { + logger(LOG_ERR, + "you are not allowed to request a link local address"); + goto abort; + } + + chdir("/"); + umask(022); + + if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) { +#ifdef ANDROID + char pidpropname[PROPERTY_KEY_MAX]; + char pidpropval[PROPERTY_VALUE_MAX]; + + i = -1; + if (snprintf(pidpropname, + sizeof(pidpropname), + "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { + goto abort; + } + property_get(pidpropname, pidpropval, NULL); + if (strlen(pidpropval) == 0) { + goto abort; + } + pid = atoi(pidpropval); +#else + i = -1; + pid = read_pid(options->pidfile); +#endif + if (pid != 0) + logger(LOG_INFO, "sending signal %d to pid %d", + sig, pid); + + if (!pid || (i = kill(pid, sig))) { + if (sig != SIGALRM) + logger(LOG_ERR, ""PACKAGE" not running"); + unlink(options->pidfile); + } + if (i == 0) { + if (sig == SIGALRM) { + retval = EXIT_SUCCESS; + goto abort; + } + /* Spin until it exits */ + logger(LOG_INFO, "waiting for pid %d to exit", pid); + ts.tv_sec = 0; + ts.tv_nsec = 100000000; /* 10th of a second */ + for(i = 0; i < 100; i++) { + nanosleep(&ts, NULL); + if (read_pid(options->pidfile) == 0) { + retval = EXIT_SUCCESS; + break; + } + } + if (retval != EXIT_SUCCESS) + logger(LOG_ERR, "pid %d failed to exit", pid); + goto abort; + } + if (sig != SIGALRM) + goto abort; + } + + if (!(options->options & DHCPCD_TEST) && + !(options->options & DHCPCD_DAEMONISED)) + { +#ifdef ANDROID + char pidpropname[PROPERTY_KEY_MAX]; + char pidpropval[PROPERTY_VALUE_MAX]; +#endif +#ifndef ANDROID + if ((pid = read_pid(options->pidfile)) > 0 && + kill(pid, 0) == 0) + { + logger(LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, options->pidfile); + goto abort; + } +#endif + pid_fd = open(options->pidfile, + O_WRONLY | O_CREAT | O_NONBLOCK, 0664); + if (pid_fd == -1) { + logger(LOG_ERR, "open `%s': %s", + options->pidfile, strerror(errno)); + goto abort; + } + + /* 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; +#ifdef ANDROID + if (snprintf(pidpropname, + sizeof(pidpropname), + "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { + goto abort; + } + if (snprintf(pidpropval, sizeof(pidpropval), "%d", getpid()) >= PROPERTY_VALUE_MAX) { + goto abort; + } + property_set(pidpropname, pidpropval); +#else + writepid(pid_fd, getpid()); +#endif + logger(LOG_INFO, PACKAGE " " VERSION " starting"); + } + + /* Terminate the encapsulated options */ + if (options->vendor[0]) { + options->vendor[0]++; + options->vendor[options->vendor[0]] = DHO_END; + } + + 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->blacklist); + free(options); + 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..217ecba --- /dev/null +++ b/dhcpcd.conf.5 @@ -0,0 +1,155 @@ +.\" 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 August 18, 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 background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.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 +.Ic hostname +option must be a FQDN. +.Nm dhcpcd +itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic 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 nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic option Ar option +Requests the +.Ar option +from the server. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options seperated by commas, spaces or more option lines. +.It Ic require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options seperated by commas, spaces or more require 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 Ic 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" +.It Ic vendorclassid Ar string +Change the default vendorclassid sent from dhcpcd-version. +If not set then none is sent. +.El +.Sh SEE ALSO +.Xr dhcpcd-run-hooks 8 , +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples <roy@marples.name> +.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..899aea3 --- /dev/null +++ b/dhcpcd.conf.5.in @@ -0,0 +1,155 @@ +.\" 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 August 18, 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 background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. +.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 +.Ic hostname +option must be a FQDN. +.Nm dhcpcd +itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . +.It Ic interface Ar interface +Subsequent options are only parsed for this +.Ar interface . +.It Ic 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 nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic option Ar option +Requests the +.Ar option +from the server. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options seperated by commas, spaces or more option lines. +.It Ic require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options seperated by commas, spaces or more require 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 Ic 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" +.It Ic vendorclassid Ar string +Change the default vendorclassid sent from dhcpcd-version. +If not set then none is sent. +.El +.Sh SEE ALSO +.Xr dhcpcd-run-hooks 8 , +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples <roy@marples.name> +.Sh BUGS +Please report them to http://bugs.marples.name diff --git a/dhcpcd.h b/dhcpcd.h new file mode 100644 index 0000000..1cd2b5d --- /dev/null +++ b/dhcpcd.h @@ -0,0 +1,95 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCD_H +#define DHCPCD_H + +#include <sys/param.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> + +#include <limits.h> + +#include "common.h" + +#define DEFAULT_TIMEOUT 30 +#define DEFAULT_LEASETIME 3600 /* 1 hour */ + +#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#define VENDORCLASSID_MAX_LEN 48 +#define CLIENTID_MAX_LEN 48 +#define USERCLASS_MAX_LEN 255 +#define VENDOR_MAX_LEN 255 + +#define DHCPCD_ARP (1 << 0) +#define DHCPCD_DOMAIN (1 << 2) +#define DHCPCD_GATEWAY (1 << 3) +#define DHCPCD_LASTLEASE (1 << 7) +#define DHCPCD_INFORM (1 << 8) +#define DHCPCD_REQUEST (1 << 9) +#define DHCPCD_IPV4LL (1 << 10) +#define DHCPCD_DUID (1 << 11) +#define DHCPCD_PERSISTENT (1 << 12) +#define DHCPCD_DAEMONISE (1 << 14) +#define DHCPCD_DAEMONISED (1 << 15) +#define DHCPCD_TEST (1 << 16) +#define DHCPCD_FORKED (1 << 17) +#define DHCPCD_HOSTNAME (1 << 18) +#define DHCPCD_CLIENTID (1 << 19) +#define DHCPCD_LINK (1 << 20) +#define DHCPCD_BACKGROUND (1 << 21) + +struct options { + char interface[IF_NAMESIZE]; + int metric; + uint8_t requestmask[256 / 8]; + uint8_t requiremask[256 / 8]; + uint8_t nomask[256 / 8]; + uint32_t leasetime; + time_t timeout; + int options; + + struct in_addr request_address; + struct in_addr request_netmask; + + char **environ; + char script[PATH_MAX]; + char pidfile[PATH_MAX]; + + char hostname[HOSTNAME_MAX_LEN + 1]; + int fqdn; + uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 1]; + char clientid[CLIENTID_MAX_LEN + 1]; + uint8_t userclass[USERCLASS_MAX_LEN + 1]; + uint8_t vendor[VENDOR_MAX_LEN + 1]; + + size_t blacklist_len; + in_addr_t *blacklist; +}; +#endif diff --git a/if-bsd.c b/if-bsd.c new file mode 100644 index 0000000..bbf1a95 --- /dev/null +++ b/if-bsd.c @@ -0,0 +1,241 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/param.h> + +#include <arpa/inet.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> +#include <netinet/in.h> + +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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; +} + +int +open_link_socket(struct interface *iface) +{ + int fd; + + fd = socket(PF_ROUTE, SOCK_RAW, 0); + if (fd == -1) + return -1; + set_cloexec(fd); + if (iface->link_fd != -1) + close(iface->link_fd); + iface->link_fd = fd; + return 0; +} + +#define BUFFER_LEN 2048 +int +link_changed(struct interface *iface) +{ + char buffer[2048], *p; + ssize_t bytes; + struct rt_msghdr *rtm; + struct if_msghdr *ifm; + int i; + + if ((i = if_nametoindex(iface->name)) == -1) + return -1; + for (;;) { + bytes = recv(iface->link_fd, buffer, BUFFER_LEN, MSG_DONTWAIT); + if (bytes == -1) { + if (errno == EAGAIN) + return 0; + if (errno == EINTR) + continue; + return -1; + } + for (p = buffer; bytes > 0; + bytes -= ((struct rt_msghdr *)p)->rtm_msglen, + p += ((struct rt_msghdr *)p)->rtm_msglen) + { + rtm = (struct rt_msghdr *)p; + if (rtm->rtm_type != RTM_IFINFO) + continue; + ifm = (struct if_msghdr *)p; + if (ifm->ifm_index != i) + continue; + + /* Link changed */ + return 1; + } + } + return 0; +} diff --git a/if-linux.c b/if-linux.c new file mode 100644 index 0000000..1009270 --- /dev/null +++ b/if-linux.c @@ -0,0 +1,379 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <asm/types.h> /* Needed for 2.4 kernels */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/param.h> + +#include <arpa/inet.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <netinet/ether.h> +#include <netpacket/packet.h> + +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* Support older kernels */ +#ifndef IFLA_WIRELESS +# define IFLA_WIRELSSS (IFLFA_MASTER + 1) +#endif + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "net.h" + +#define BUFFERLEN 256 + +int +open_link_socket(struct interface *iface) +{ + int fd; + struct sockaddr_nl nl; + + if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + return -1; + memset(&nl, 0, sizeof(nl)); + nl.nl_family = AF_NETLINK; + nl.nl_groups = RTMGRP_LINK; + if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) + return -1; + set_cloexec(fd); + if (iface->link_fd != -1) + close(iface->link_fd); + iface->link_fd = fd; + return 0; +} + +static int +get_netlink(int fd, int flags, + int (*callback)(struct nlmsghdr *, const char *), + const char *ifname) +{ + char *buffer = NULL; + ssize_t bytes; + struct nlmsghdr *nlm; + int r = -1; + + buffer = xzalloc(sizeof(char) * BUFFERLEN); + for (;;) { + bytes = recv(fd, buffer, BUFFERLEN, flags); + if (bytes == -1) { + if (errno == EAGAIN) { + r = 0; + goto eexit; + } + if (errno == EINTR) + continue; + goto eexit; + } + for (nlm = (struct nlmsghdr *)buffer; + NLMSG_OK(nlm, (size_t)bytes); + nlm = NLMSG_NEXT(nlm, bytes)) + { + r = callback(nlm, ifname); + if (r != 0) + goto eexit; + } + } + +eexit: + free(buffer); + return r; +} + +static int +err_netlink(struct nlmsghdr *nlm, _unused const char *ifname) +{ + struct nlmsgerr *err; + int l; + + if (nlm->nlmsg_type != NLMSG_ERROR) + return 0; + l = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)l < sizeof(*err)) { + errno = EBADMSG; + return -1; + } + err = (struct nlmsgerr *)NLMSG_DATA(nlm); + if (err->error == 0) + return l; + errno = -err->error; + return -1; +} + +static int +link_netlink(struct nlmsghdr *nlm, const char *ifname) +{ + int len; + struct rtattr *rta; + struct ifinfomsg *ifi; + char ifn[IF_NAMESIZE + 1]; + + if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK) + return 0; + len = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)len < sizeof(*ifi)) { + errno = EBADMSG; + return -1; + } + ifi = NLMSG_DATA(nlm); + if (ifi->ifi_flags & IFF_LOOPBACK) + return 0; + rta = (struct rtattr *) ((char *)ifi + NLMSG_ALIGN(sizeof(*ifi))); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifi)); + *ifn = '\0'; + while (RTA_OK(rta, len)) { + switch (rta->rta_type) { + case IFLA_WIRELESS: + /* Ignore wireless messages */ + if (nlm->nlmsg_type == RTM_NEWLINK && + ifi->ifi_change == 0) + return 0; + break; + case IFLA_IFNAME: + strlcpy(ifn, RTA_DATA(rta), sizeof(ifn)); + break; + } + rta = RTA_NEXT(rta, len); + } + + if (strncmp(ifname, ifn, sizeof(ifn)) == 0) + return 1; + return 0; +} + +int +link_changed(struct interface *iface) +{ + return get_netlink(iface->link_fd, MSG_DONTWAIT, + &link_netlink, iface->name); +} + +static int +send_netlink(struct nlmsghdr *hdr) +{ + int fd, r; + struct sockaddr_nl nl; + struct iovec iov; + struct msghdr msg; + static unsigned int seq; + + if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + return -1; + memset(&nl, 0, sizeof(nl)); + nl.nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) { + close(fd); + return -1; + } + memset(&iov, 0, sizeof(iov)); + iov.iov_base = hdr; + iov.iov_len = hdr->nlmsg_len; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &nl; + msg.msg_namelen = sizeof(nl); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* Request a reply */ + hdr->nlmsg_flags |= NLM_F_ACK; + hdr->nlmsg_seq = ++seq; + + if (sendmsg(fd, &msg, 0) != -1) + r = get_netlink(fd, 0, &err_netlink, NULL); + else + r = -1; + close(fd); + return r; +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len))) + +static int +add_attr_l(struct nlmsghdr *n, unsigned int maxlen, int type, + const void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + errno = ENOBUFS; + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return 0; +} + +static int +add_attr_32(struct nlmsghdr *n, unsigned int maxlen, int type, uint32_t data) +{ + int len = RTA_LENGTH(sizeof(data)); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + errno = ENOBUFS; + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, sizeof(data)); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + + return 0; +} + +struct nlma +{ + struct nlmsghdr hdr; + struct ifaddrmsg ifa; + char buffer[64]; +}; + +struct nlmr +{ + struct nlmsghdr hdr; + struct rtmsg rt; + char buffer[256]; +}; + +int +if_address(const 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) + /* + * ers@google: + * commented out NLM_F_EXCL here and below. We + * sometimes keep one interface up while we are + * configuring the other one, and this flag + * causes route addition to fail. + */ + nlm->hdr.nlmsg_flags = NLM_F_CREATE /* | NLM_F_EXCL */; + 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..15c6cf7 --- /dev/null +++ b/logger.c @@ -0,0 +1,89 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "common.h" +#include "logger.h" + +static int loglevel = LOG_INFO; +static char logprefix[12] = {0}; + +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); + } + + 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..8ac76b6 --- /dev/null +++ b/logger.h @@ -0,0 +1,43 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef 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 <syslog.h> + +void setloglevel(int); +void setlogprefix(const char *); +void logger(int, const char *, ...) _PRINTF_LIKE (2, 3); + +#endif @@ -0,0 +1,168 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#ifdef __linux__ +# include <asm/types.h> /* needed for 2.4 kernels for the below header */ +# include <linux/filter.h> +# include <netpacket/packet.h> +# define bpf_insn sock_filter +# define BPF_SKIPTYPE +# define BPF_ETHCOOK -ETH_HLEN +# define BPF_WHOLEPACKET 0x0fffffff /* work around buggy LPF filters */ +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "net.h" +#include "bpf-filter.h" + +/* Broadcast address for IPoIB */ +static const uint8_t ipv4_bcast_addr[] = { + 0x00, 0xff, 0xff, 0xff, + 0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff +}; + +int +open_socket(struct interface *iface, int protocol) +{ + int s; + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su; + struct sock_fprog pf; + int *fd; + + if ((s = socket(PF_PACKET, SOCK_DGRAM, htons(protocol))) == -1) + return -1; + + memset(&su, 0, sizeof(su)); + su.sll.sll_family = PF_PACKET; + su.sll.sll_protocol = htons(protocol); + if (!(su.sll.sll_ifindex = if_nametoindex(iface->name))) { + errno = ENOENT; + goto eexit; + } + /* Install the DHCP filter */ + memset(&pf, 0, sizeof(pf)); + if (protocol == ETHERTYPE_ARP) { + pf.filter = UNCONST(arp_bpf_filter); + pf.len = arp_bpf_filter_len; + } else { + pf.filter = UNCONST(dhcp_bpf_filter); + pf.len = dhcp_bpf_filter_len; + } + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) != 0) + goto eexit; + if (set_cloexec(s) == -1) + goto eexit; + if (set_nonblock(s) == -1) + goto eexit; + if (bind(s, &su.sa, sizeof(su)) == -1) + goto eexit; + if (protocol == ETHERTYPE_ARP) + fd = &iface->arp_fd; + else + fd = &iface->raw_fd; + if (*fd != -1) + close(*fd); + *fd = s; + return s; + +eexit: + close(s); + return -1; +} + +ssize_t +send_raw_packet(const struct interface *iface, int protocol, + const void *data, ssize_t len) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_ll sll; + struct sockaddr_storage ss; + } su; + int fd; + + memset(&su, 0, sizeof(su)); + su.sll.sll_family = AF_PACKET; + su.sll.sll_protocol = htons(protocol); + if (!(su.sll.sll_ifindex = if_nametoindex(iface->name))) { + errno = ENOENT; + return -1; + } + su.sll.sll_hatype = htons(iface->family); + su.sll.sll_halen = iface->hwlen; + if (iface->family == ARPHRD_INFINIBAND) + memcpy(&su.sll.sll_addr, + &ipv4_bcast_addr, sizeof(ipv4_bcast_addr)); + else + memset(&su.sll.sll_addr, 0xff, iface->hwlen); + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + + return sendto(fd, data, len, 0, &su.sa, sizeof(su)); +} + +ssize_t +get_raw_packet(struct interface *iface, int protocol, void *data, ssize_t len) +{ + ssize_t bytes; + int fd = -1; + + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_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 <roy@marples.name> + +# Setup some good default CFLAGS +CFLAGS?= -Os + +# Default to using the C99 standard +CSTD?= c99 +_CSTD_SH= if test -n "${CSTD}"; then echo "-std=${CSTD}"; else echo ""; fi +_CSTD!= ${_CSTD_SH} +CFLAGS+= ${_CSTD}$(shell ${_CSTD_SH}) + +# Try and use some good cc flags if we're building from git +_CCFLAGS= -pedantic -Wall -Wunused -Wimplicit -Wshadow -Wformat=2 \ + -Wmissing-declarations -Wno-missing-prototypes -Wwrite-strings \ + -Wbad-function-cast -Wnested-externs -Wcomment -Winline \ + -Wchar-subscripts -Wcast-align -Wno-format-nonliteral \ + -Wdeclaration-after-statement -Wsequence-point -Wextra +_CC_FLAGS_SH= if ! test -d .git; then echo ""; else for f in ${_CCFLAGS}; do \ + if ${CC} $$f -S -o /dev/null -xc /dev/null >/dev/null 2>&1; \ + then printf "%s" "$$f "; fi \ + done; fi +_CC_FLAGS!= ${_CC_FLAGS_SH} +CFLAGS+= ${_CC_FLAGS}$(shell ${_CC_FLAGS_SH}) + +_GGDB_SH= if test "${DEBUG}" = "yes"; then echo "-ggdb -DDEBUG"; else echo ""; fi +_GGDB!= ${_GGDB_SH} +GGDB= ${_GGDB}$(shell ${_GGDB_SH}) +CFLAGS+= ${GGDB} diff --git a/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 <roy@marples.name> + +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 <roy@marples.name> + +GITREF?= HEAD +DISTPREFIX?= ${PROG}-${VERSION} +DISTFILE?= ${DISTPREFIX}.tar.bz2 + +CLEANFILES+= *.tar.bz2 + +_VERSION_SH= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' config.h +_VERSION!= ${_VERSION_SH} +VERSION= ${_VERSION}$(shell ${_VERSION_SH}) + +_SNAP_SH= date -u +%Y%m%d%H%M +_SNAP!= ${_SNAP_SH} +SNAP= ${_SNAP}$(shell ${_SNAP_SH}) +SNAPDIR= ${DISTPREFIX}-${SNAP} +SNAPFILE= ${SNAPDIR}.tar.bz2 + +dist: + git archive --prefix=${DISTPREFIX}/ ${GITREF} | bzip2 > ${DISTFILE} + +snapshot: + mkdir /tmp/${SNAPDIR} + cp -RPp * /tmp/${SNAPDIR} + (cd /tmp/${SNAPDIR}; make clean) + tar -cvjpf ${SNAPFILE} -C /tmp ${SNAPDIR} + rm -rf /tmp/${SNAPDIR} + ls -l ${SNAPFILE} + +snap: snapshot diff --git a/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 <roy@marples.name> + +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 <roy@marples.name> + +_MANPREFIX_SH= if [ -n "${PREFIX}" ]; then echo "${PREFIX}"; else echo "/usr/share"; fi +_MANPREFIX!= ${_MANPREFIX_SH} +MANPREFIX?= ${_MANPREFIX}$(shell ${_MANPREFIX_SH}) + +MANDIR?= ${MANPREFIX}/man/man +MANMODE?= 0444 + +_MAN5_SH= for man in ${MAN}; do case $$man in *.5) echo $$man;; esac; done +_MAN5!= ${_MAN5_SH} +MAN5= ${_MAN5}$(shell ${_MAN5_SH}) + +_MAN8_SH= for man in ${MAN}; do case $$man in *.8) echo $$man;; esac; done +_MAN8!= ${_MAN8_SH} +MAN8= ${_MAN8}$(shell ${_MAN8_SH}) + +_man: ${MAN} + +_maninstall: _man + ${INSTALL} -d ${DESTDIR}${MANDIR}5 + ${INSTALL} -m ${MANMODE} ${MAN5} ${DESTDIR}${MANDIR}5 + ${INSTALL} -d ${DESTDIR}${MANDIR}8 + ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}8 diff --git a/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 <roy@marples.name> + +SRC_PF= bpf.c +SRC_IF= if-bsd.c diff --git a/mk/os-Darwin.mk b/mk/os-Darwin.mk new file mode 100644 index 0000000..f2c3104 --- /dev/null +++ b/mk/os-Darwin.mk @@ -0,0 +1,5 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +include ${MK}/os-BSD.mk +LINK_RPATH= --rpath diff --git a/mk/os-Linux.mk b/mk/os-Linux.mk new file mode 100644 index 0000000..2d316b1 --- /dev/null +++ b/mk/os-Linux.mk @@ -0,0 +1,8 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +SRC_PF= lpf.c +SRC_IF= if-linux.c + +CPPFLAGS+= -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +LIBRT= -lrt diff --git a/mk/os.mk b/mk/os.mk new file mode 100644 index 0000000..f3426e4 --- /dev/null +++ b/mk/os.mk @@ -0,0 +1,7 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +_OS_SH= case `uname -s` in Linux) echo "Linux";; Darwin) echo "Darwin";; *) echo "BSD";; esac +_OS!= ${_OS_SH} +OS= ${_OS}$(shell ${_OS_SH}) +include ${MK}/os-${OS}.mk diff --git a/mk/prog.mk b/mk/prog.mk new file mode 100644 index 0000000..d970b2d --- /dev/null +++ b/mk/prog.mk @@ -0,0 +1,68 @@ +# rules to build a program +# based on FreeBSD's bsd.prog.mk + +# Copyright 2008 Roy Marples <roy@marples.name> + +include ${MK}/cc.mk + +OBJS+= ${SRCS:.c=.o} + +# If building for /, ensure we use the libc in / if different from +# the default one in /usr/lib +LINK_RPATH?= -Wl,-rpath +_RPATH_SH= if test "${PREFIX}" = "" -o "${PREIX}" = "/"; then \ + echo "${LINK_RPATH}=${PREFIX}/${LIBNAME}"; \ + else \ + echo ""; \ + fi +_RPATH!= ${_RPATH_SH} +LDFLAGS+= ${_RPATH}$(shell ${_RPATH_SH}) + +# If building for /, ensure we use the linker in /libexec if different from +# the default one in /usr/libexec +_DYNLINK_SH= if test "${PREFIX}" = "" -o "${PREFIX}" = "/" && test -e /libexec/ld.elf_so; then \ + echo "-Wl,-dynamic-linker=/libexec/ld.elf_so"; \ + else \ + echo ""; \ + fi +_DYNLINK!= ${_DYNLINK_SH} +LDFLAGS+= ${_DYNLINK}$(shell ${_DYNLINK_SH}) + +all: ${PROG} ${SCRIPTS} _man + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +${PROG}: ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} + +# We could save about 600 bytes by building it like this +# instead of the more traditional method above +small: ${SRCS} + echo "" > _${PROG}.c + for src in ${SRCS}; do echo "#include \"$$src\"" >> _${PROG}.c; done + ${CC} ${CFLAGS} ${CPPFLAGS} -c _${PROG}.c -o _${PROG}.o + ${CC} ${LDFLAGS} -o ${PROG} _${PROG}.o ${LDADD} + +_proginstall: ${PROG} + ${INSTALL} -d ${DESTDIR}${BINDIR} + ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${BINDIR} + ${INSTALL} -d ${DESTDIR}${DBDIR} + +include ${MK}/depend.mk +include ${MK}/files.mk +include ${MK}/scripts.mk +include ${MK}/man.mk +include ${MK}/dist.mk + +install: _proginstall _scriptsinstall _filesinstall _maninstall + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done + +clean: + rm -f ${OBJS} ${PROG} _${PROG}.c _${PROG}.o ${PROG}.core ${CLEANFILES} + +LINTFLAGS?= -hx +LINTFLAGS+= -X 159,247,352 + +lint: ${SRCS:.c=.c} + ${LINT} ${LINTFLAGS} ${CFLAGS:M-[DIU]*} $^ ${.ALLSRC} diff --git a/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 <roy@marples.name> + +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}) @@ -0,0 +1,707 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <arpa/inet.h> +#include <netinet/in_systm.h> +#ifdef __linux__ +#include <netinet/ether.h> +#include <netpacket/packet.h> +#endif +#include <netinet/in.h> +#include <netinet/ip.h> +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include <netinet/udp.h> +#undef __FAVOR_BSD +#ifdef SIOCGIFMEDIA +#include <net/if_media.h> +#endif +#include <arpa/inet.h> +#ifdef AF_LINK +# include <net/if_dl.h> +#endif + +#include <ctype.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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, found = 0; + int len = 10 * sizeof(struct ifreq); + int lastlen = 0; + char *p; + union { + char *buffer; + struct ifreq *ifr; + } ifreqs; + struct sockaddr_in address; + struct ifreq *ifr; + struct sockaddr_in netmask; +#ifdef AF_LINK + struct sockaddr_dl *sdl; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + /* 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; + + found = 1; + +#ifdef AF_LINK + if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) { + sdl = xmalloc(ifr->ifr_addr.sa_len); + memcpy(sdl, &ifr->ifr_addr, ifr->ifr_addr.sa_len); + *hwlen = sdl->sdl_alen; + memcpy(hwaddr, LLADDR(sdl), *hwlen); + free(sdl); + retval = 1; + break; + } +#endif + + 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; + } + } + } + + } + + if (!found) + errno = ENXIO; + close(s); + free(ifc.ifc_buf); + return retval; +} + +int +up_interface(const char *ifname) +{ + int s; + struct ifreq ifr; + int retval = -1; +#ifdef __linux__ + char *p; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only bring the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) { + if ((ifr.ifr_flags & IFF_UP)) + retval = 0; + else { + ifr.ifr_flags |= IFF_UP; + if (ioctl(s, SIOCSIFFLAGS, &ifr) == 0) + retval = 0; + } + } + close(s); + return retval; +} + +int +carrier_status(const char *ifname) +{ + int s; + struct ifreq ifr; + int retval = -1; +#ifdef SIOCGIFMEDIA + struct ifmediareq ifmr; +#endif +#ifdef __linux__ + char *p; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only test the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if ((retval = ioctl(s, SIOCGIFFLAGS, &ifr)) == 0) { + if (ifr.ifr_flags & IFF_UP && ifr.ifr_flags & IFF_RUNNING) + retval = 1; + else + retval = 0; + } + +#ifdef SIOCGIFMEDIA + if (retval == 1) { + memset(&ifmr, 0, sizeof(ifmr)); + strncpy(ifmr.ifm_name, ifr.ifr_name, sizeof(ifmr.ifm_name)); + if (ioctl(s, SIOCGIFMEDIA, &ifmr) != -1 && + ifmr.ifm_status & IFM_AVALID) + { + if (!(ifmr.ifm_status & IFM_ACTIVE)) + retval = 0; + } + } +#endif + close(s); + 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; + + 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; + } + + if (up_interface(ifname) != 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->raw_fd = -1; + iface->udp_fd = -1; + iface->arp_fd = -1; + iface->link_fd = -1; + +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; +#ifdef SO_BINDTODEVICE + struct ifreq ifr; +#endif + + if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto eexit; +#ifdef SO_BINDTODEVICE + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + goto eexit; +#endif + /* As we don't use this socket for receiving, set the + * receive buffer to 1 */ + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + goto eexit; + memset(&su, 0, sizeof(su)); + su.sin.sin_family = AF_INET; + su.sin.sin_port = htons(DHCP_CLIENT_PORT); + su.sin.sin_addr.s_addr = iface->addr.s_addr; + if (bind(s, &su.sa, sizeof(su)) == -1) + 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; +} + +int +send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip) +{ + struct arphdr *arp; + size_t arpsize; + uint8_t *p; + int retval; + + arpsize = sizeof(*arp) + 2 * iface->hwlen + 2 * sizeof(sip); + arp = xmalloc(arpsize); + arp->ar_hrd = htons(iface->family); + arp->ar_pro = htons(ETHERTYPE_IP); + arp->ar_hln = iface->hwlen; + arp->ar_pln = sizeof(sip); + arp->ar_op = htons(op); + p = (uint8_t *)arp; + p += sizeof(*arp); + memcpy(p, iface->hwaddr, iface->hwlen); + p += iface->hwlen; + memcpy(p, &sip, sizeof(sip)); + p += sizeof(sip); + /* ARP requests should ignore this */ + retval = iface->hwlen; + while (retval--) + *p++ = '\0'; + memcpy(p, &tip, sizeof(tip)); + p += sizeof(tip); + retval = send_raw_packet(iface, ETHERTYPE_ARP, arp, arpsize); + free(arp); + return retval; +} @@ -0,0 +1,176 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <limits.h> + +#include "config.h" + +#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 IN_CLASSB_NET +#define LINKLOCAL_BRDC (LINKLOCAL_ADDR | ~LINKLOCAL_MASK) + +#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 raw_fd; + int udp_fd; + int arp_fd; + int link_fd; + size_t buffer_size, buffer_len, buffer_pos; + unsigned char *buffer; + + struct in_addr addr; + struct in_addr net; + struct rt *routes; + + char leasefile[PATH_MAX]; + time_t start_uptime; + + unsigned char *clientid; +}; + +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 up_interface(const char *); +int do_interface(const char *, unsigned char *, size_t *, + struct in_addr *, struct in_addr *, int); +int if_address(const char *, const struct in_addr *, const struct in_addr *, + const struct in_addr *, int); +#define add_address(ifname, addr, net, brd) \ + if_address(ifname, addr, net, brd, 1) +#define del_address(ifname, addr, net) \ + if_address(ifname, addr, net, NULL, -1) +#define has_address(ifname, addr, net) \ + do_interface(ifname, NULL, NULL, addr, net, 0) +#define get_address(ifname, addr, net) \ + do_interface(ifname, NULL, NULL, addr, net, 1) + +int if_route(const 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 change_route(ifname, dest, mask, gate, metric) \ + if_route(ifname, dest, mask, gate, metric, 0) +#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); + +int send_arp(const struct interface *, int, in_addr_t, in_addr_t); + +int open_link_socket(struct interface *); +int link_changed(struct interface *); +int carrier_status(const char *); +#endif diff --git a/showlease.c b/showlease.c new file mode 100644 index 0000000..9bee4ab --- /dev/null +++ b/showlease.c @@ -0,0 +1,349 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include "dhcp.h" +#include "config.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) + +struct dhcp_opt { + uint8_t option; + int type; + const char *var; +}; + +static const struct dhcp_opt const dhcp_opts[] = { + { 1, IPV4 | REQUEST, "subnet_mask" }, + { 2, UINT32, "time_offset" }, + { 3, IPV4 | ARRAY | REQUEST, "routers" }, + { 4, IPV4 | ARRAY, "time_servers" }, + { 5, IPV4 | ARRAY, "ien116_name_servers" }, + { 6, IPV4 | ARRAY, "domain_name_servers" }, + { 7, IPV4 | ARRAY, "log_servers" }, + { 8, IPV4 | ARRAY, "cookie_servers" }, + { 9, IPV4 | ARRAY, "lpr_servers" }, + { 10, IPV4 | ARRAY, "impress_servers" }, + { 11, IPV4 | ARRAY, "resource_location_servers" }, + { 12, STRING, "host_name" }, + { 13, UINT16, "boot_size" }, + { 14, STRING, "merit_dump" }, + { 15, STRING, "domain_name" }, + { 16, IPV4, "swap_server" }, + { 17, STRING, "root_path" }, + { 18, STRING, "extensions_path" }, + { 19, UINT8, "ip_forwarding" }, + { 20, UINT8, "non_local_source_routing" }, + { 21, IPV4 | ARRAY, "policy_filter" }, + { 22, SINT16, "max_dgram_reassembly" }, + { 23, UINT16, "default_ip_ttl" }, + { 24, UINT32, "path_mtu_aging_timeout" }, + { 25, UINT16 | ARRAY, "path_mtu_plateau_table" }, + { 26, UINT16, "interface_mtu" }, + { 27, UINT8, "all_subnets_local" }, + { 28, IPV4 | REQUEST, "broadcast_address" }, + { 29, UINT8, "perform_mask_discovery" }, + { 30, UINT8, "mask_supplier" }, + { 31, UINT8, "router_discovery" }, + { 32, IPV4, "router_solicitation_address" }, + { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, + { 34, UINT8, "trailer_encapsulation" }, + { 35, UINT32, "arp_cache_timeout" }, + { 36, UINT16, "ieee802_3_encapsulation" }, + { 37, UINT8, "default_tcp_ttl" }, + { 38, UINT32, "tcp_keepalive_interval" }, + { 39, UINT8, "tcp_keepalive_garbage" }, + { 40, STRING, "nis_domain" }, + { 41, IPV4 | ARRAY, "nis_servers" }, + { 42, IPV4 | ARRAY, "ntp_servers" }, + { 43, STRING, "vendor_encapsulated_options" }, + { 44, IPV4 | ARRAY, "netbios_name_servers" }, + { 45, IPV4, "netbios_dd_server" }, + { 46, UINT8, "netbios_node_type" }, + { 47, STRING, "netbios_scope" }, + { 48, IPV4 | ARRAY, "font_servers" }, + { 49, IPV4 | ARRAY, "x_display_manager" }, + { 50, IPV4, "dhcp_requested_address" }, + { 51, UINT32 | REQUEST, "dhcp_lease_time" }, + { 52, UINT8, "dhcp_option_overload" }, + { 53, UINT8, "dhcp_message_type" }, + { 54, IPV4, "dhcp_server_identifier" }, + { 55, UINT8 | ARRAY, "dhcp_parameter_request_list" }, + { 56, STRING, "dhcp_message" }, + { 57, UINT16, "dhcp_max_message_size" }, + { 58, UINT32 | REQUEST, "dhcp_renewal_time" }, + { 59, UINT32 | REQUEST, "dhcp_rebinding_time" }, + { 64, STRING, "nisplus_domain" }, + { 65, IPV4 | ARRAY, "nisplus_servers" }, + { 66, STRING, "tftp_server_name" }, + { 67, STRING, "bootfile_name" }, + { 68, IPV4 | ARRAY, "mobile_ip_home_agent" }, + { 69, IPV4 | ARRAY, "smtp_server" }, + { 70, IPV4 | ARRAY, "pop_server" }, + { 71, IPV4 | ARRAY, "nntp_server" }, + { 72, IPV4 | ARRAY, "www_server" }, + { 73, IPV4 | ARRAY, "finger_server" }, + { 74, IPV4 | ARRAY, "irc_server" }, + { 75, IPV4 | ARRAY, "streettalk_server" }, + { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, + { 77, STRING, "user_class" }, + { 85, IPV4 | ARRAY, "nds_servers" }, + { 86, STRING, "nds_tree_name" }, + { 87, STRING, "nds_context" }, + { 88, STRING | RFC3397, "bcms_controller_names" }, + { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 91, UINT32, "client_last_transaction_time" }, + { 92, IPV4 | ARRAY, "associated_ip" }, + { 98, STRING, "uap_servers" }, + { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 113, STRING, "netinfo_server_tag" }, + { 114, STRING, "default_url" }, + { 118, IPV4, "subnet_selection" }, + { 119, STRING | RFC3397, "domain_search" }, + { 121, RFC3442 | REQUEST, "classless_static_routes" }, + { 249, RFC3442, "ms-classless_static_routes" }, + { 0, 0, NULL } +}; + +struct dhcp_message * +get_lease(const char *leasefile) +{ + int fd; + struct dhcp_message *dhcp; + ssize_t bytes; + + fd = open(leasefile, O_RDONLY); + if (fd == -1) + return NULL; + dhcp = malloc(sizeof(*dhcp)); + memset(dhcp, 0, sizeof(*dhcp)); + bytes = read(fd, dhcp, sizeof(*dhcp)); + close(fd); + if (bytes < 0) { + free(dhcp); + dhcp = NULL; + } + return dhcp; +} + +static uint8_t *dhcp_opt_buffer = NULL; + +static int +valid_length(uint8_t option, int dl, int *type) +{ + const struct dhcp_opt *opt; + ssize_t sz; + + if (dl == 0) + return -1; + + for (opt = dhcp_opts; opt->option; opt++) { + if (opt->option != option) + continue; + + if (type) + *type = opt->type; + + if (opt->type == 0 || opt->type & STRING || opt->type & RFC3442) + return 0; + + sz = 0; + if (opt->type & UINT32 || opt->type & IPV4) + sz = sizeof(uint32_t); + if (opt->type & UINT16) + sz = sizeof(uint16_t); + if (opt->type & UINT8) + sz = sizeof(uint8_t); + if (opt->type & IPV4 || opt->type & ARRAY) + return dl % sz; + return (dl == sz ? 0 : -1); + } + + /* unknown option, so let it pass */ + return 0; +} + +static void +free_option_buffer(void) +{ + free(dhcp_opt_buffer); +} + + +#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL) +static const uint8_t * +get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + int bl = 0; + + while (p < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!dhcp_opt_buffer) { + dhcp_opt_buffer = malloc(sizeof(struct dhcp_message)); + atexit(free_option_buffer); + } + if (!bp) + bp = dhcp_opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = *p; + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (valid_length(o, bl, type) == -1) { + errno = EINVAL; + return NULL; + } + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)&dhcp_opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_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; +} + +uint32_t +get_netmask(uint32_t addr) +{ + uint32_t dst; + + if (addr == 0) + return 0; + + dst = htonl(addr); + if (IN_CLASSA(dst)) + return ntohl(IN_CLASSA_NET); + if (IN_CLASSB (dst)) + return ntohl(IN_CLASSB_NET); + if (IN_CLASSC (dst)) + return ntohl(IN_CLASSC_NET); + + return 0; +} + +void showlease(struct dhcp_lease *lease) +{ + printf("addr: %s\n", inet_ntoa(lease->addr)); + printf("net: %s\n", inet_ntoa(lease->net)); + printf("leasetime: %d\n", lease->leasetime); + printf("renew: %d\n", lease->renewaltime); + printf("rebind: %d\n", lease->rebindtime); + printf("server: %s\n", inet_ntoa(lease->server)); +} +#define MAX_LEASETIME 2147460 + +int +main(int argc, char *argv[]) +{ + struct dhcp_message *dhcp; + struct dhcp_lease *lease; + char leasefile[PATH_MAX]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <interface>\n", argv[0]); + exit(1); + } + snprintf(leasefile, PATH_MAX, LEASEFILE, argv[1]); + if ((dhcp = get_lease(leasefile)) == NULL) { + fprintf(stderr, "Couldn't read lease file: %s\n", strerror(errno)); + exit(1); + } + lease = malloc(sizeof(*lease)); + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + + if (get_option_addr(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = get_netmask(dhcp->yiaddr); + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) != 0) + lease->leasetime = DEFAULT_LEASETIME; + get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID); + /* Dm: limit lease time value to avoid negative numbers when + converting to milliseconds */ + if ((lease->leasetime != ~0U) && (lease->leasetime > MAX_LEASETIME)) + lease->leasetime = MAX_LEASETIME; + if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) + lease->rebindtime = 0; + showlease(lease); + free(lease); + return 0; +} diff --git a/signals.c b/signals.c new file mode 100644 index 0000000..58679d6 --- /dev/null +++ b/signals.c @@ -0,0 +1,125 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#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]); +} + +/* Read a signal from the signal pipe. Returns 0 if there is + * no signal, -1 on error (and sets errno appropriately), and + * your signal on success */ +int +signal_read(void) +{ + int sig = -1; + char buf[16]; + size_t bytes; + + 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..f784a68 --- /dev/null +++ b/signals.h @@ -0,0 +1,37 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SIGNAL_H +#define SIGNAL_H + +int signal_init(void); +int signal_setup(void); +int signal_reset(void); +int signal_fd(void); +int signal_read(void); + +#endif |