From: Roy Marples Date: Tue, 2 Sep 2008 13:28:11 +0000 (+0000) Subject: Add an event loop. X-Git-Tag: v5.0.0~318 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd05b7dcfc61e1a8f1d4f0f7ecd27bb25a6cc7a8;p=thirdparty%2Fdhcpcd.git Add an event loop. Split client.c into smaller files and functions and recode around the event loop. Add multiple interface support using the new event loop. Document changes and outstanding bugs. --- diff --git a/Makefile b/Makefile index fdb566f0..58ca6ba0 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,9 @@ # Copyright 2008 Roy Marples PROG= dhcpcd -SRCS= common.c dhcp.c dhcpcd.c logger.c net.c signals.c -SRCS+= configure.c client.c +SRCS= arp.c bind.c common.c dhcp.c dhcpcd.c duid.c eloop.c +SRCS+= if-options.c ipv4ll.c logger.c net.c signals.c +SRCS+= configure.c SRCS+= ${SRC_IF} ${SRC_PF} LIBEXECDIR?= ${PREFIX}/libexec diff --git a/README b/README index 50294fe4..8c6dd6f2 100644 --- a/README +++ b/README @@ -43,21 +43,8 @@ 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. +dhcpcd-4.1 is only fully command line compatible with dhcpcd-4.0 +For compatibility with older versions, use dhcpcd-4.0 ChangeLog diff --git a/arp.c b/arp.c new file mode 100644 index 00000000..71431dc8 --- /dev/null +++ b/arp.c @@ -0,0 +1,204 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "arp.h" +#include "bind.h" +#include "common.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if-options.h" +#include "ipv4ll.h" +#include "logger.h" +#include "net.h" + +static void +handle_arp_failure(struct interface *iface) +{ + if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) { + handle_ipv4ll_failure(iface); + return; + } + send_decline(iface); + close_sockets(iface); + add_timeout_sec(DHCP_ARP_FAIL, start_interface, iface); +} + +void +handle_arp_packet(struct interface *iface) +{ + 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 if_state *state = iface->state; + + state->fail.s_addr = 0; + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_ARP, + arp_reply, sizeof(arp_reply)); + if (bytes == 0 || bytes == -1) + return; + /* 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, "%s: hardware address %s claims %s", + iface->name, + hwaddr_ntoa((unsigned char *)hw_s, + (size_t)reply.ar_hln), + inet_ntoa(state->fail)); + errno = EEXIST; + handle_arp_failure(iface); + return; + } + } +} + +void +send_arp_announce(struct interface *iface) +{ + struct if_state *state = iface->state; + struct timeval tv; + + if (iface->arp_fd == -1) { + open_socket(iface, ETHERTYPE_ARP); + add_event(iface->arp_fd, handle_arp_packet, iface); + } + if (++state->claims < ANNOUNCE_NUM) + logger(LOG_DEBUG, + "%s: sending ARP announce (%d of %d), " + "next in %d.00 seconds", + iface->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); + else + logger(LOG_DEBUG, + "%s: sending ARP announce (%d of %d)", + iface->name, state->claims, ANNOUNCE_NUM); + if (send_arp(iface, ARPOP_REQUEST, + state->new->yiaddr, state->new->yiaddr) == -1) + logger(LOG_ERR, "send_arp: %s", strerror(errno)); + if (state->claims < ANNOUNCE_NUM) { + add_timeout_sec(ANNOUNCE_WAIT, send_arp_announce, iface); + return; + } + if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { + /* We should pretend to be at the end + * of the DHCP negotation cycle */ + state->interval = 64; + state->probes = 0; + state->claims = 0; + tv.tv_sec = state->interval - DHCP_RAND_MIN; + tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + tv.tv_sec = 3; /* test easier */ + timernorm(&tv); + add_timeout_tv(&tv, start_discover, iface); + } else { + delete_event(iface->arp_fd); + close(iface->arp_fd); + iface->arp_fd = -1; + } +} + +void +send_arp_probe(struct interface *iface) +{ + struct if_state *state = iface->state; + struct in_addr addr; + struct timeval tv; + + if (iface->arp_fd == -1) { + open_socket(iface, ETHERTYPE_ARP); + add_event(iface->arp_fd, handle_arp_packet, iface); + } + if (state->probes == 0) { + addr.s_addr = state->offer->yiaddr; + logger(LOG_INFO, "%s: checking %s is available" + " on attached networks", + iface->name, inet_ntoa(addr)); + } + if (++state->probes < PROBE_NUM) { + tv.tv_sec = PROBE_MIN; + tv.tv_usec = arc4random() % (PROBE_MAX_U - PROBE_MIN_U); + timernorm(&tv); + add_timeout_tv(&tv, send_arp_probe, iface); + } else { + tv.tv_sec = ANNOUNCE_WAIT; + tv.tv_usec = 0; + if (IN_LINKLOCAL(htonl(state->offer->yiaddr))) + add_timeout_tv(&tv, bind_interface, iface); + else + add_timeout_tv(&tv, send_request, iface); + } + logger(LOG_DEBUG, + "%s: sending ARP probe (%d of %d), next in %0.2f seconds", + iface->name, state->probes, PROBE_NUM, timeval_to_double(&tv)); + if (send_arp(iface, ARPOP_REQUEST, 0, state->offer->yiaddr) == -1) + logger(LOG_ERR, "send_arp: %s", strerror(errno)); +} diff --git a/arp.h b/arp.h new file mode 100644 index 00000000..3cfe55a7 --- /dev/null +++ b/arp.h @@ -0,0 +1,49 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ARP_H +#define ARP_H + +#include "dhcpcd.h" + +/* These are for IPV4LL, RFC 3927. + * We put them here as we use the timings for all ARP foo. */ +#define PROBE_WAIT 1 +#define PROBE_NUM 3 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +#define ANNOUNCE_NUM 2 +#define ANNOUNCE_INTERVAL 2 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + +void send_arp_announce(struct interface *); +void send_arp_probe(struct interface *); +void handle_arp_packet(struct interface *); +#endif diff --git a/bind.c b/bind.c new file mode 100644 index 00000000..452738cd --- /dev/null +++ b/bind.c @@ -0,0 +1,189 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "arp.h" +#include "bind.h" +#include "common.h" +#include "configure.h" +#include "dhcpcd.h" +#include "dhcpf.h" +#include "eloop.h" +#include "if-options.h" +#include "logger.h" +#include "net.h" +#include "signals.h" + +static int daemonised = 0; +int can_daemonise = 1; + +#ifndef THERE_IS_NO_FORK +pid_t +daemonise(void) +{ + pid_t pid; + sigset_t full; + sigset_t old; + char buf = '\0'; + int sidpipe[2]; + + if (daemonised || !can_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_INFO, "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: + 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(pidfd, pid); + close(pidfd); + pidfd = -1; + exit(EXIT_SUCCESS); + } + sigprocmask(SIG_SETMASK, &old, NULL); + return pid; +} +#endif + +void bind_interface(struct interface *iface) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_lease *lease = &state->lease; + struct timeval tv; + const char *reason = NULL; + + delete_timeout(handle_exit_timeout, NULL); + if (clock_monotonic) + get_monotonic(&lease->boundtime); + state->state = DHS_BOUND; + state->xid = 0; + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + get_lease(lease, state->new); + if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { + logger(LOG_INFO, "%s: using IPv4LL address %s", + iface->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + reason = "IPV4LL"; + } else if (ifo->options & DHCPCD_INFORM) { + if (ifo->request_address.s_addr != 0) + lease->addr.s_addr = ifo->request_address.s_addr; + else + lease->addr.s_addr = iface->addr.s_addr; + logger(LOG_INFO, "%s: received approval for %s", iface->name, + inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + reason = "INFORM"; + } else { + if (gettimeofday(&tv, NULL) == 0) + lease->leasedfrom = tv.tv_sec; + if (lease->frominfo) + reason = "TIMEOUT"; + if (lease->leasetime == ~0U) { + lease->renewaltime = lease->rebindtime = lease->leasetime; + logger(LOG_INFO, "%s: leased %s for infinity", + iface->name, inet_ntoa(lease->addr)); + } else { + if (lease->rebindtime >= lease->leasetime) { + lease->rebindtime = lease->leasetime * T2; + logger(LOG_ERR, + "%s: rebind time greater than lease " + "time, forcing to %u seconds", + iface->name, lease->rebindtime); + } + if (lease->renewaltime > lease->rebindtime) { + lease->renewaltime = lease->leasetime * T1; + logger(LOG_ERR, + "%s: renewal time greater than rebind " + "time, forcing to %u seconds", + iface->name, lease->renewaltime); + } + if (!lease->renewaltime) + lease->renewaltime = lease->leasetime * T1; + if (!lease->rebindtime) + lease->rebindtime = lease->leasetime * T2; + logger(LOG_INFO, + "%s: leased %s for %u seconds", iface->name, + inet_ntoa(lease->addr), lease->leasetime); + } + } + 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 (lease->leasetime == ~0U) + lease->renewaltime = lease->rebindtime = lease->leasetime; + else { + add_timeout_sec(lease->renewaltime, start_renew, iface); + add_timeout_sec(lease->rebindtime, start_rebind, iface); + add_timeout_sec(lease->leasetime, start_expire, iface); + } + configure(iface, reason); + daemonise(); + if (ifo->options & DHCPCD_ARP) { + state->claims = 0; + send_arp_announce(iface); + } +} diff --git a/bind.h b/bind.h new file mode 100644 index 00000000..5aaabf70 --- /dev/null +++ b/bind.h @@ -0,0 +1,41 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef BIND_H +#define BIND_H + +#include "config.h" +#include "dhcpcd.h" +#ifdef THERE_IS_NO_FORK +#define daemonise() {} +#else +pid_t daemonise(void); +#endif + +extern int can_daemonise; +void bind_interface(struct interface *); +#endif diff --git a/client.c b/client.c deleted file mode 100644 index c34d3188..00000000 --- a/client.c +++ /dev/null @@ -1,1839 +0,0 @@ -/* - * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples - * All rights reserved - - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include -#include -#include -#include - -#ifdef __linux__ -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "common.h" -#include "client.h" -#include "configure.h" -#include "dhcp.h" -#include "dhcpcd.h" -#include "net.h" -#include "logger.h" -#include "signals.h" - -#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/common.c b/common.c index d90c7d2f..efcdd80a 100644 --- a/common.c +++ b/common.c @@ -284,7 +284,7 @@ xmalloc(size_t s) if (value) return value; - logger(LOG_ERR, "memory exhausted"); + logger(LOG_ERR, "memory exhausted (xalloc %zu bytes)", s); exit (EXIT_FAILURE); /* NOTREACHED */ } @@ -305,7 +305,7 @@ xrealloc(void *ptr, size_t s) if (value) return (value); - logger(LOG_ERR, "memory exhausted"); + logger(LOG_ERR, "memory exhausted (xrealloc %zu bytes)", s); exit(EXIT_FAILURE); /* NOTREACHED */ } @@ -321,7 +321,7 @@ xstrdup(const char *str) if ((value = strdup(str))) return value; - logger(LOG_ERR, "memory exhausted"); + logger(LOG_ERR, "memory exhausted (xstrdup)"); exit(EXIT_FAILURE); /* NOTREACHED */ } diff --git a/common.h b/common.h index 25226636..a9715d63 100644 --- a/common.h +++ b/common.h @@ -37,6 +37,15 @@ #define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#define timeval_to_double(tv) ((tv)->tv_sec * 1.0 + (tv)->tv_usec * 1.0e-6) +#define timernorm(tvp) \ + do { \ + while ((tvp)->tv_usec >= 1000000) { \ + (tvp)->tv_sec++; \ + (tvp)->tv_usec -= 1000000; \ + } \ + } while (0 /* CONSTCOND */); + #if __GNUC__ > 2 || defined(__INTEL_COMPILER) # define _unused __attribute__((__unused__)) #else diff --git a/config.h b/config.h index 92220394..801c89cd 100644 --- a/config.h +++ b/config.h @@ -28,7 +28,7 @@ #define CONFIG_H #define PACKAGE "dhcpcd" -#define VERSION "4.0.0" +#define VERSION "4.0.99" /* * By default we don't add a local link route if we got a routeable address. @@ -69,7 +69,7 @@ # define LEASEFILE DBDIR "/" PACKAGE "-%s.lease" #endif #ifndef PIDFILE -# define PIDFILE RUNDIR "/" PACKAGE "-%s.pid" +# define PIDFILE RUNDIR "/" PACKAGE "%s%s.pid" #endif #endif diff --git a/configure.c b/configure.c index dad3bce6..6baf4042 100644 --- a/configure.c +++ b/configure.c @@ -40,15 +40,14 @@ #include "config.h" #include "common.h" #include "configure.h" -#include "dhcp.h" -#include "dhcpcd.h" +#include "dhcpf.h" +#include "if-options.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) { @@ -80,18 +79,18 @@ exec_script(char *const *argv, char *const *env) } int -run_script(const struct options *options, const char *iface, - const char *reason, - const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) +run_script(const struct interface *iface, const char *reason) { - char *const argv[2] = { UNCONST(options->script), NULL }; + char *const argv[2] = { UNCONST(iface->state->options->script), NULL }; char **env = NULL, **ep; char *path; ssize_t e, elen; pid_t pid; int status = 0; + const struct if_options *ifo = iface->state->options; - logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason); + logger(LOG_DEBUG, "%s: executing `%s', reason %s", + iface->name, argv[0], reason); /* Make our env */ elen = 5; @@ -103,9 +102,9 @@ run_script(const struct options *options, const char *iface, snprintf(env[0], e, "PATH=%s", path); } else env[0] = xstrdup(DEFAULT_PATH); - e = strlen("interface") + strlen(iface) + 2; + e = strlen("interface") + strlen(iface->name) + 2; env[1] = xmalloc(e); - snprintf(env[1], e, "interface=%s", iface); + snprintf(env[1], e, "interface=%s", iface->name); e = strlen("reason") + strlen(reason) + 2; env[2] = xmalloc(e); snprintf(env[2], e, "reason=%s", reason); @@ -113,30 +112,32 @@ run_script(const struct options *options, const char *iface, 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); + snprintf(env[4], e, "metric=%d", iface->metric); + if (iface->state->old) { + e = configure_env(NULL, NULL, iface->state->old, ifo); if (e > 0) { env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += configure_env(env + elen, "old", dhcpo, options); + elen += configure_env(env + elen, "old", + iface->state->old, ifo); } } - if (dhcpn) { - e = configure_env(NULL, NULL, dhcpn, options); + if (iface->state->new) { + e = configure_env(NULL, NULL, iface->state->new, ifo); if (e > 0) { env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += configure_env(env + elen, "new", dhcpn, options); + elen += configure_env(env + elen, "new", + iface->state->new, ifo); } } /* Add our base environment */ - if (options->environ) { + if (ifo->environ) { e = 0; - while (options->environ[e++]) + while (ifo->environ[e++]) ; env = xrealloc(env, sizeof(char *) * (elen + e + 1)); e = 0; - while (options->environ[e]) { - env[elen + e] = xstrdup(options->environ[e]); + while (ifo->environ[e]) { + env[elen + e] = xstrdup(ifo->environ[e]); e++; } elen += e; @@ -181,13 +182,13 @@ reverse_routes(struct rt *routes) } static int -delete_route(const char *iface, struct rt *rt, int metric) +delete_route(const struct interface *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", + logger(LOG_DEBUG, "%s: deleting route %s/%d via %s", iface->name, addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); free(addr); retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric); @@ -206,7 +207,7 @@ delete_routes(struct interface *iface, int metric) rt = reverse_routes(iface->routes); while (rt) { rtn = rt->next; - retval += delete_route(iface->name, rt, metric); + retval += delete_route(iface, rt, metric); free(rt); rt = rtn; } @@ -229,9 +230,9 @@ in_routes(const struct rt *routes, const struct rt *rt) } static int -configure_routes(struct interface *iface, const struct dhcp_message *dhcp, - const struct options *options) +configure_routes(struct interface *iface, const struct dhcp_message *dhcp) { + const struct if_options *ifo = iface->state->options; struct rt *rt, *ort; struct rt *rtn = NULL, *nr = NULL; int remember; @@ -241,7 +242,7 @@ configure_routes(struct interface *iface, const struct dhcp_message *dhcp, ort = get_option_routes(dhcp); #ifdef IPV4LL_ALWAYSROUTE - if (options->options & DHCPCD_IPV4LL && + if (ifo->options & DHCPCD_IPV4LL && IN_PRIVATE(ntohl(dhcp->yiaddr))) { for (rt = ort; rt; rt = rt->next) { @@ -272,22 +273,22 @@ configure_routes(struct interface *iface, const struct dhcp_message *dhcp, 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); + delete_route(iface, rt, iface->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)) + !(ifo->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)); + logger(LOG_DEBUG, "%s: adding route to %s/%d via %s", + iface->name, addr, + inet_ntocidr(rt->net), inet_ntoa(rt->gate)); free(addr); - remember = add_route(iface->name, &rt->dest, - &rt->net, &rt->gate, - options->metric); + remember = add_route(iface, &rt->dest, + &rt->net, &rt->gate, iface->metric); retval += remember; /* If we failed to add the route, we may have already added it @@ -324,10 +325,11 @@ static int delete_address(struct interface *iface) { int retval; - logger(LOG_DEBUG, "deleting IP address %s/%d", + logger(LOG_DEBUG, "%s: deleting IP address %s/%d", + iface->name, inet_ntoa(iface->addr), inet_ntocidr(iface->net)); - retval = del_address(iface->name, &iface->addr, &iface->net); + retval = del_address(iface, &iface->addr, &iface->net); if (retval == -1 && errno != EADDRNOTAVAIL) logger(LOG_ERR, "del_address: %s", strerror(errno)); iface->addr.s_addr = 0; @@ -336,11 +338,9 @@ delete_address(struct interface *iface) } int -configure(struct interface *iface, const char *reason, - const struct dhcp_message *dhcp, const struct dhcp_message *old, - const struct dhcp_lease *lease, const struct options *options, - int up) +configure(struct interface *iface, const char *reason) { + struct dhcp_message *dhcp = iface->state->new; struct in_addr addr; struct in_addr net; struct in_addr brd; @@ -350,37 +350,32 @@ configure(struct interface *iface, const char *reason, #endif /* Grab our IP config */ - if (dhcp == NULL) - up = 0; - else { + if (dhcp) { addr.s_addr = dhcp->yiaddr; if (addr.s_addr == 0) - addr.s_addr = lease->addr.s_addr; + addr.s_addr = iface->state->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) { + } else { /* Only reset things if we had set them before */ if (iface->addr.s_addr != 0) { - delete_routes(iface, options->metric); + delete_routes(iface, iface->metric); delete_address(iface); } - run_script(options, iface->name, reason, NULL, old); + run_script(iface, reason); return 0; } /* This also changes netmask */ - if (!(options->options & DHCPCD_INFORM) || + if (!(iface->state->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 && + logger(LOG_DEBUG, "%s: adding IP address %s/%d", + iface->name, inet_ntoa(addr), inet_ntocidr(net)); + if (add_address(iface, &addr, &net, &brd) == -1 && errno != EEXIST) { logger(LOG_ERR, "add_address: %s", strerror(errno)); @@ -395,26 +390,25 @@ configure(struct interface *iface, const char *reason, #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) + if (iface->addr.s_addr != iface->state->lease.addr.s_addr && + iface->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); + add_route(iface, &dest, &net, &gate, iface->metric); + del_route(iface, &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); + configure_routes(iface, dhcp); iface->addr.s_addr = addr.s_addr; iface->net.s_addr = net.s_addr; - if (!lease->frominfo) + if (!iface->state->lease.frominfo) if (write_lease(iface, dhcp) == -1) logger(LOG_ERR, "write_lease: %s", strerror(errno)); - run_script(options, iface->name, reason, dhcp, old); + run_script(iface, reason); return 0; } diff --git a/configure.h b/configure.h index fe065db6..b5ffc7e0 100644 --- a/configure.h +++ b/configure.h @@ -28,14 +28,9 @@ #ifndef DHCPCONFIG_H #define DHCPCONFIG_H -#include "dhcpcd.h" -#include "dhcp.h" #include "net.h" -int run_script(const struct options *, const char *, const char *, - const struct dhcp_message *, const struct dhcp_message *); -int configure(struct interface *, const char *, - const struct dhcp_message *, const struct dhcp_message *, - const struct dhcp_lease *, const struct options *, int); +int run_script(const struct interface *, const char *); +int configure(struct interface *, const char *); #endif diff --git a/dhcp.c b/dhcp.c index a9ef632c..0fb1f63f 100644 --- a/dhcp.c +++ b/dhcp.c @@ -35,6 +35,7 @@ #include "config.h" #include "common.h" #include "dhcp.h" +#include "dhcpf.h" #define REQUEST (1 << 0) #define UINT8 (1 << 1) @@ -166,12 +167,13 @@ print_options(void) printf("%03d %s\n", opt->option, opt->var); } -int make_option_mask(uint8_t *mask, char **opts, int add) +int make_option_mask(uint8_t *mask, const char *opts, int add) { - char *token, *p = *opts, *t; + char *token, *o, *p, *t; const struct dhcp_opt *opt; int match, n; + o = p = xstrdup(opts); while ((token = strsep(&p, ", "))) { if (*token == '\0') continue; @@ -199,11 +201,12 @@ int make_option_mask(uint8_t *mask, char **opts, int add) } } if (!opt->option) { - *opts = token; + free(o); errno = ENOENT; return -1; } } + free(o); return 0; } @@ -745,8 +748,8 @@ encode_rfc1035(const char *src, uint8_t *dst, size_t len) ssize_t make_message(struct dhcp_message **message, - const struct interface *iface, const struct dhcp_lease *lease, - uint32_t xid, uint8_t type, const struct options *options) + const struct interface *iface, + uint8_t type) { struct dhcp_message *dhcp; uint8_t *m, *lp, *p; @@ -755,6 +758,8 @@ make_message(struct dhcp_message **message, uint32_t ul; uint16_t sz; const struct dhcp_opt *opt; + const struct if_options *ifo = iface->state->options; + const struct dhcp_lease *lease = &iface->state->lease; dhcp = xzalloc(sizeof (*dhcp)); m = (uint8_t *)dhcp; @@ -795,7 +800,7 @@ make_message(struct dhcp_message **message, dhcp->secs = htons((uint16_t)UINT16_MAX); else dhcp->secs = htons(up); - dhcp->xid = xid; + dhcp->xid = iface->state->xid; dhcp->cookie = htonl(MAGIC_COOKIE); *p++ = DHO_MESSAGETYPE; @@ -822,17 +827,17 @@ make_message(struct dhcp_message **message, } if (type != DHCP_DECLINE && type != DHCP_RELEASE) { - if (options->userclass[0]) { + if (ifo->userclass[0]) { *p++ = DHO_USERCLASS; - memcpy(p, options->userclass, options->userclass[0] + 1); - p += options->userclass[0] + 1; + memcpy(p, ifo->userclass, ifo->userclass[0] + 1); + p += ifo->userclass[0] + 1; } - if (options->vendorclassid[0]) { + if (ifo->vendorclassid[0]) { *p++ = DHO_VENDORCLASSID; - memcpy(p, options->vendorclassid, - options->vendorclassid[0] + 1); - p += options->vendorclassid[0] + 1; + memcpy(p, ifo->vendorclassid, + ifo->vendorclassid[0] + 1); + p += ifo->vendorclassid[0] + 1; } } @@ -854,10 +859,10 @@ make_message(struct dhcp_message **message, } #undef PUTADDR - if (options->leasetime != 0) { + if (ifo->leasetime != 0) { *p++ = DHO_LEASETIME; *p++ = 4; - ul = htonl(options->leasetime); + ul = htonl(ifo->leasetime); memcpy(p, &ul, 4); p += 4; } @@ -867,12 +872,12 @@ make_message(struct dhcp_message **message, type == DHCP_INFORM || type == DHCP_REQUEST) { - if (options->hostname[0]) { + if (ifo->hostname[0]) { *p++ = DHO_HOSTNAME; - memcpy(p, options->hostname, options->hostname[0] + 1); - p += options->hostname[0] + 1; + memcpy(p, ifo->hostname, ifo->hostname[0] + 1); + p += ifo->hostname[0] + 1; } - if (options->fqdn != FQDN_DISABLE) { + if (ifo->fqdn != FQDN_DISABLE) { /* IETF DHC-FQDN option (81), RFC4702 */ *p++ = DHO_FQDN; lp = p; @@ -887,20 +892,20 @@ make_message(struct dhcp_message **message, * N: 1 => Client requests Server to not * update DNS */ - *p++ = (options->fqdn & 0x09) | 0x04; + *p++ = (ifo->fqdn & 0x09) | 0x04; *p++ = 0; /* from server for PTR RR */ *p++ = 0; /* from server for A RR if S=1 */ - ul = encode_rfc1035(options->hostname + 1, p, - options->hostname[0]); + ul = encode_rfc1035(ifo->hostname + 1, p, + ifo->hostname[0]); *lp += ul; p += ul; } /* vendor is already encoded correctly, so just add it */ - if (options->vendor[0]) { + if (ifo->vendor[0]) { *p++ = DHO_VENDOR; - memcpy(p, options->vendor, options->vendor[0] + 1); - p += options->vendor[0] + 1; + memcpy(p, ifo->vendor, ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; } *p++ = DHO_PARAMETERREQUESTLIST; @@ -908,7 +913,7 @@ make_message(struct dhcp_message **message, *p++ = 0; for (opt = dhcp_opts; opt->option; opt++) { if (!(opt->type & REQUEST || - has_option_mask(options->requestmask, opt->option))) + has_option_mask(ifo->requestmask, opt->option))) continue; switch (opt->option) { case DHO_RENEWALTIME: /* FALLTHROUGH */ @@ -1155,7 +1160,7 @@ setvar(char ***e, const char *prefix, const char *var, const char *value) ssize_t configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, - const struct options *options) + const struct if_options *ifo) { unsigned int i; const uint8_t *p; @@ -1176,7 +1181,7 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (has_option_mask(options->nomask, opt->option)) + if (has_option_mask(ifo->nomask, opt->option)) continue; if (get_option_raw(dhcp, opt->option)) e++; @@ -1219,7 +1224,7 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (has_option_mask(options->nomask, opt->option)) + if (has_option_mask(ifo->nomask, opt->option)) continue; val = NULL; p = get_option(dhcp, opt->option, &pl, NULL); @@ -1242,3 +1247,25 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, return ep - env; } + +void +get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) +{ + 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) + 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; +} diff --git a/dhcp.h b/dhcp.h index e584452c..0f605344 100644 --- a/dhcp.h +++ b/dhcp.h @@ -29,13 +29,10 @@ #define DHCP_H #include +#include #include -#include "config.h" -#include "dhcpcd.h" -#include "net.h" - /* Max MTU - defines dhcp option length */ #define MTU_MAX 1500 #define MTU_MIN 576 @@ -61,6 +58,24 @@ #define DHCP_RELEASE 7 #define DHCP_INFORM 8 +/* Constants taken from RFC 2131. */ +#define T1 0.5 +#define T2 0.875 +#define DHCP_BASE 4 +#define DHCP_MAX 64 +#define DHCP_RAND_MIN -1 +#define DHCP_RAND_MAX 1 +#define DHCP_ARP_FAIL 10 + +/* number of usecs in a second. */ +#define USECS_SECOND 1000000 +/* As we use timevals, we should use the usec part for + * greater randomisation. */ +#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND +#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND +#define PROBE_MIN_U PROBE_MIN * USECS_SECOND +#define PROBE_MAX_U PROBE_MAX * USECS_SECOND + /* DHCP options */ enum DHO { @@ -154,25 +169,4 @@ struct dhcp_lease { 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.8.in b/dhcpcd.8.in index 6c82d3fb..68d7f950 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 20, 2008 +.Dd September 1, 2008 .Dt DHCPCD 8 SMM .Sh NAME .Nm dhcpcd @@ -48,13 +48,14 @@ .Op Fl O , -nooption Ar option .Op Fl Q , -require Ar option .Op Fl X , -blacklist Ar address -.Ar interface +.Op interface +.Op ... .Nm .Fl k , -release -.Ar interface +.Op interface .Nm .Fl x , -exit -.Ar interface +.Op interface .Sh DESCRIPTION .Nm is an implementation of the DHCP client specified in @@ -97,6 +98,14 @@ installed which always defeats IPv4LL probing. To disable this behaviour, you can use the .Fl L , -noipv4ll option. +.Ss Multiple interfaces +.Nm +can be run per interface or as a single instance to manage all interfaces. +If a list of interfaces are given on the command line, then +.Nm +only works with those interfaces. +If no interfaces are given then we detect all available interfaces and +attempt to configure all of them. .Ss Hooking into DHCP events .Nm runs @@ -427,4 +436,12 @@ RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702. .Sh AUTHORS .An Roy Marples .Sh BUGS +You cannot release a lease - this will be fixed before the first release. +.Pp +You cannot dynamically add or remove interfaces either manually or detected. +.Pp +One of the goals for one instance managing multiple interfaces is more +intelligent route and configuration management. +This has not yet been done. +.Pp Please report them to http://bugs.marples.name diff --git a/dhcpcd.c b/dhcpcd.c index 0219b02f..c450217e 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -36,6 +36,7 @@ const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; #include #include #include +#include #include #include #include @@ -44,97 +45,61 @@ const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; #include #include +#include "arp.h" +#include "bind.h" #include "config.h" -#include "client.h" +#include "common.h" +#include "configure.h" #include "dhcpcd.h" -#include "dhcp.h" -#include "net.h" +#include "dhcpf.h" +#include "duid.h" +#include "eloop.h" +#include "if-options.h" +#include "ipv4ll.h" #include "logger.h" +#include "net.h" +#include "signals.h" -/* 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'} -}; +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 -#ifdef CMDLINE_COMPAT -# define EXTRA_OPTS "HMNRSY" -#endif +int pidfd = -1; +static int linkfd = -1; +static char cffile[PATH_MAX]; +static char pidfile[PATH_MAX] = { '\0' }; +static struct interface *ifaces = NULL; -#ifndef EXTRA_OPTS -# define EXTRA_OPTS -#endif -static int -atoint(const char *s) -{ - char *t; - long n; +struct dhcp_op { + uint8_t value; + const char *name; +}; - 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; - } +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; - return (int)n; + for (d = dhcp_ops; d->name; d++) + if (d->value == type) + return d->name; + return NULL; } static pid_t -read_pid(const char *pidfile) +read_pid(void) { FILE *fp; pid_t pid = 0; @@ -159,721 +124,709 @@ usage(void) " [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] \n"); } -static char * -add_environ(struct options *options, const char *value, int uniq) +static void +cleanup(void) { - 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++; +#ifdef DEBUG_MEMORY + struct interface *iface; + + while (ifaces) { + iface = ifaces; + ifaces = iface->next; + free_interface(iface); } +#endif - newlist = xrealloc(lst, sizeof(char *) * (i + 2)); - newlist[i] = xstrdup(value); - newlist[i + 1] = NULL; - options->environ = newlist; - free(match); - return newlist[i]; + if (linkfd != -1) + close(linkfd); + if (pidfd > -1) { + close(pidfd); + unlink(pidfile); + } +} + +void +handle_exit_timeout(_unused struct interface *iface) +{ + logger(LOG_ERR, "timed out"); + exit(EXIT_FAILURE); +} + +void +drop_config(struct interface *iface, const char *reason) +{ + if (iface->state->new || strcmp(reason, "FAIL") == 0) { + free(iface->state->new); + iface->state->new = NULL; + configure(iface, reason); + free(iface->state->old); + iface->state->old = NULL; + } + iface->state->lease.addr.s_addr = 0; +} + +void +close_sockets(struct interface *iface) +{ + if (iface->arp_fd != -1) { + delete_event(iface->arp_fd); + close(iface->arp_fd); + iface->arp_fd = -1; + } + if (iface->raw_fd != -1) { + delete_event(iface->raw_fd); + close(iface->raw_fd); + iface->raw_fd = -1; + } + if (iface->udp_fd != -1) { + close(iface->udp_fd); + iface->udp_fd = -1; + } } -#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) +static void +send_message(struct interface *iface, int type, + void (*function)(struct interface *)) { - ssize_t l; - char *p; - int i; - char c[4]; - - /* If surrounded by quotes then it's a string */ - if (*str == '"') { - str++; - l = strlen(str); - p = str + l - 1; - if (*p == '"') - *p = '\0'; + struct if_state *state = iface->state; + struct dhcp_message *dhcp; + uint8_t *udp; + ssize_t len, r; + struct in_addr from, to; + in_addr_t a = 0; + struct timeval tv; + + if (!function) + logger(LOG_DEBUG, "%s: sending %s with xid 0x%x", + iface->name, get_dhcp_op(type), state->xid); + else { + if (state->interval == 0) + state->interval = 4; + else { + state->interval *= 2; + if (state->interval > 64) + state->interval = 64; + } + tv.tv_sec = state->interval + DHCP_RAND_MIN; + tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + logger(LOG_DEBUG, + "%s: sending %s with xid 0x%x, next in %0.2f seconds", + iface->name, get_dhcp_op(type), state->xid, + timeval_to_double(&tv)); + } + /* 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 (iface->udp_fd == -1) { + a = iface->addr.s_addr; + iface->addr.s_addr = 0; + } + len = make_message(&dhcp, iface, type); + if (iface->udp_fd == -1) + iface->addr.s_addr = a; + from.s_addr = dhcp->ciaddr; + if (from.s_addr) + to.s_addr = state->lease.server.s_addr; + else + to.s_addr = 0; + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { + r = send_packet(iface, to, (uint8_t *)dhcp, len); + if (r == -1) + logger(LOG_ERR, "%s: send_packet: %s", + iface->name, strerror(errno)); } else { - l = hwaddr_aton(NULL, str); - if (l > 1) { - if (l > slen) { - errno = ENOBUFS; - return -1; - } - hwaddr_aton((uint8_t *)sbuf, str); - return l; - } + len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); + r = send_raw_packet(iface, ETHERTYPE_IP, udp, len); + free(udp); + if (r == -1) + logger(LOG_ERR, "%s: send_raw_packet: %s", + iface->name, strerror(errno)); } + free(dhcp); + if (function) + add_timeout_tv(&tv, function, iface); +} + +static void +send_discover(struct interface *iface) +{ + send_message(iface, DHCP_DISCOVER, send_discover); +} + +void +send_request(struct interface *iface) +{ + send_message(iface, DHCP_REQUEST, send_request); +} - /* 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++; +static void +send_renew(struct interface *iface) +{ + send_message(iface, DHCP_REQUEST, send_renew); +} + +void +start_renew(struct interface *iface) +{ + logger(LOG_INFO, "%s: renewing lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->state = DHS_RENEWING; + iface->state->xid = arc4random(); + send_renew(iface); +} + +static void +send_rebind(struct interface *iface) +{ + send_message(iface, DHCP_REQUEST, send_rebind); +} + +void +start_rebind(struct interface *iface) +{ + logger(LOG_ERR, "%s: failed to renew, attmepting to rebind", + iface->name); + iface->state->state = DHS_REBINDING; + delete_timeout(send_renew, iface); + iface->state->lease.server.s_addr = 0; + send_rebind(iface); +} + +void +start_expire(struct interface *iface) +{ + int ll = IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr)); + + logger(LOG_ERR, "%s: lease expired", iface->name); + delete_timeout(NULL, iface); + drop_config(iface, "EXPIRE"); + iface->state->interval = 0; + if (iface->state->carrier != LINK_DOWN) { + if (ll) + start_interface(iface); + else + start_ipv4ll(iface); } - 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++; +} + +void +send_decline(struct interface *iface) +{ + send_message(iface, DHCP_DECLINE, NULL); +} + +static void +log_dhcp(int lvl, const char *msg, + const struct interface *iface, 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)); } - return l; + r = get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + logger(lvl, "%s: %s %s from %s `%s'", iface->name, msg, a, + inet_ntoa(addr), dhcp->servername); + else if (r == 0) + logger(lvl, "%s: %s %s from %s", + iface->name, msg, a, inet_ntoa(addr)); + else + logger(lvl, "%s: %s %s", iface->name, msg, a); + free(a); } -static int -parse_option(int opt, char *oarg, struct options *options) +static void +handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp) { - int i; - char *p; - ssize_t s; + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp = *dhcpp; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; struct in_addr addr; + size_t i; - 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; + /* reset the message counter */ + state->interval = 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", iface, dhcp); + return; + } + + /* 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 (ifo->blacklist_len != 0 && + get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID) == 0) + { + for (i = 0; i < ifo->blacklist_len; i++) { + if (ifo->blacklist[i] != addr.s_addr) + continue; + if (dhcp->servername[0]) + logger(LOG_WARNING, + "%s: ignoring blacklisted server %s `%s'", + iface->name, + inet_ntoa(addr), dhcp->servername); + else + logger(LOG_WARNING, + "%s: ignoring blacklisted server %s", + iface->name, inet_ntoa(addr)); + return; } - break; - case 'o': - if (make_option_mask(options->requestmask, &oarg, 1) != 0) { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; + } + + /* We should restart on a NAK */ + if (type == DHCP_NAK) { + log_dhcp(LOG_WARNING, "NAK:", iface, dhcp); + drop_config(iface, "EXPIRE"); + delete_event(iface->raw_fd); + close(iface->raw_fd); + iface->raw_fd = -1; + close(iface->udp_fd); + iface->udp_fd = -1; + /* If we constantly get NAKS then we should slowly back off */ + add_timeout_sec(state->nakoff, start_interface, iface); + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + return; + } + + /* 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(ifo->requiremask, i) && + get_option_uint8(&tmp, dhcp, i) != 0) + { + log_dhcp(LOG_WARNING, "reject", iface, dhcp); + return; } - 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; - } + } + + if (type == DHCP_OFFER && state->state == DHS_DISCOVERING) { + lease->addr.s_addr = dhcp->yiaddr; + get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID); + log_dhcp(LOG_INFO, "offered", iface, dhcp); + if (ifo->options & DHCPCD_TEST) { + run_script(iface, "TEST"); + exit(EXIT_SUCCESS); + } + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + delete_timeout(send_discover, iface); + if (ifo->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + addr.s_addr = state->offer->yiaddr; + if (!has_address(iface->name, &addr, NULL)) { + state->state = DHS_PROBING; + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + send_arp_probe(iface); + return; } } - /* 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; + state->state = DHS_REQUESTING; + send_request(iface); + return; + } + + if (type == DHCP_OFFER) { + log_dhcp(LOG_INFO, "ignoring offer of", iface, dhcp); + return; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", iface, dhcp); + return; + } + + if (!(ifo->options & DHCPCD_INFORM)) + log_dhcp(LOG_INFO, "acknowledged", iface, dhcp); + close_sockets(iface); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + /* Delete all timeouts for this interface. */ + delete_timeout(NULL, iface); + bind_interface(iface); +} + +static void +handle_dhcp_packet(struct interface *iface) +{ + uint8_t *packet; + struct dhcp_message *dhcp = NULL; + const uint8_t *pp; + uint8_t *p; + ssize_t bytes; + + /* We loop through until our buffer is empty. + * The benefit is that if we get >1 DHCP packet in our buffer and + * the first one fails for any reason, we can use the next. */ + packet = xmalloc(udp_dhcp_len); + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_IP, + packet, udp_dhcp_len); + if (bytes == 0 || bytes == -1) break; + if (valid_udp_packet(packet) == -1) + continue; + bytes = get_udp_data(&pp, packet); + if ((size_t)bytes > sizeof(*dhcp)) { + logger(LOG_ERR, "%s: packet greater than DHCP size", + iface->name); + continue; } - 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; + if (!dhcp) + dhcp = xmalloc(sizeof(*dhcp)); + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + logger(LOG_DEBUG, "%s: bogus cookie, ignoring", + iface->name); + continue; } - 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; + /* Ensure it's the right transaction */ + if (iface->state->xid != dhcp->xid) { + logger(LOG_DEBUG, + "%s: ignoring packet with xid 0x%x as" + " it's not ours (0x%x)", + iface->name, dhcp->xid, iface->state->xid); + continue; } - break; - case 'Q': - if (make_option_mask(options->requiremask, &oarg, 1) != 0 || - make_option_mask(options->requestmask, &oarg, 1) != 0) + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) { - 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; + logger(LOG_DEBUG, "%s: xid 0x%x is not for our hwaddr %s", + iface->name, dhcp->xid, + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + continue; } - 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; + /* 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; + } + handle_dhcp(iface, &dhcp); + if (iface->raw_fd == -1) + break; } - - return 1; + free(packet); + free(dhcp); } -static int -parse_config_line(const char *opt, char *line, struct options *options) +static void +open_sockets(struct interface *iface) { - unsigned int i; + if (iface->udp_fd != -1) + close(iface->udp_fd); + if (open_udp_socket(iface) == -1 && + (errno != EADDRINUSE || iface->addr.s_addr != 0)) + logger(LOG_ERR, "open_udp_socket: %s", strerror(errno)); + if (iface->raw_fd != -1) + delete_event(iface->raw_fd); + if (open_socket(iface, ETHERTYPE_IP) == -1) + logger(LOG_ERR, "open_socket: %s", strerror(errno)); + if (iface->raw_fd != -1) + add_event(iface->raw_fd, handle_dhcp_packet, iface); +} - for (i = 0; i < sizeof(longopts) / sizeof(longopts[0]); i++) { - if (!longopts[i].name || - strcmp(longopts[i].name, opt) != 0) - continue; +static void +handle_link(struct interface *iface) +{ + int retval; - if (longopts[i].has_arg == required_argument && !line) { - fprintf(stderr, - PACKAGE ": option requires an argument -- %s\n", - opt); - return -1; + retval = link_changed(linkfd, ifaces); + if (retval == -1) { + logger(LOG_ERR, "link_changed: %s", strerror(errno)); + return; + } + if (retval == 0) + return; + for (iface = ifaces; iface; iface = iface->next) { + if (iface->state->options->options & DHCPCD_LINK) { + switch (carrier_status(iface->name)) { + case -1: + logger(LOG_ERR, "carrier_status: %s", + strerror(errno)); + break; + case 0: + if (iface->state->carrier != LINK_DOWN) { + iface->state->carrier = LINK_DOWN; + logger(LOG_INFO, "%s: carrier lost", + iface->name); + close_sockets(iface); + delete_timeouts(iface, start_expire, NULL); + } + break; + default: + if (iface->state->carrier != LINK_UP) { + iface->state->carrier = LINK_UP; + logger(LOG_INFO, "%s: carrier acquired", + iface->name); + start_interface(iface); + } + break; + } } + } +} - return parse_option(longopts[i].val, line, options); +void +start_discover(struct interface *iface) +{ + struct if_options *ifo = iface->state->options; + + iface->state->state = DHS_DISCOVERING; + iface->state->xid = arc4random(); + open_sockets(iface); + delete_timeout(NULL, iface); + if (ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(iface->addr.s_addr))) + { + if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) + add_timeout_sec(RATE_LIMIT_INTERVAL, start_ipv4ll, iface); + else + add_timeout_sec(ifo->timeout, start_ipv4ll, iface); } + logger(LOG_INFO, "%s: broadcasting for a lease", iface->name); + send_discover(iface); +} + - fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt); - return -1; +void +start_reboot(struct interface *iface) +{ + struct if_options *ifo = iface->state->options; + + logger(LOG_INFO, "%s: rebinding lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->state = DHS_REBINDING; + iface->state->xid = arc4random(); + iface->state->lease.server.s_addr = 0; + delete_timeout(NULL, iface); + add_timeout_sec(ifo->timeout, start_expire, iface); + open_sockets(iface); + send_rebind(iface); } -int -main(int argc, char **argv) +void +start_interface(struct interface *iface) { - 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; + iface->start_uptime = uptime(); + if (!iface->state->lease.addr.s_addr) + start_discover(iface); + else if (IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr))) + start_ipv4ll(iface); + else + start_reboot(iface); +} - 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); +static void +init_state(struct interface *iface, int argc, char **argv) +{ + struct if_state *ifs; + struct if_options *ifo; + uint8_t *duid; + size_t len = 0, ifl; + + if (iface->state) { + ifs = iface->state; + free_options(ifs->options); + } else + ifs = iface->state = xzalloc(sizeof(*ifs)); + + ifs->state = DHS_INIT; + ifs->nakoff = 1; + ifo = ifs->options = read_config(cffile, iface->name); + add_options(ifo, argc, argv); + + if (ifo->metric) + iface->metric = ifo->metric; + + if (*ifo->clientid) { + iface->clientid = xmalloc(ifo->clientid[0] + 1); + memcpy(iface->clientid, ifo->clientid, ifo->clientid[0] + 1); + } else if (ifo->options & DHCPCD_CLIENTID) { + if (ifo->options & DHCPCD_DUID) { + duid = xmalloc(DUID_LEN); + if ((len = get_duid(duid, iface)) == 0) + logger(LOG_ERR, "get_duid: %s", strerror(errno)); + } + if (len > 0) { + iface->clientid = xmalloc(len + 6); + iface->clientid[0] = len + 5; + iface->clientid[1] = 255; /* RFC 4361 */ + ifl = strlen(iface->name); + if (ifl < 5) { + memcpy(iface->clientid + 2, iface->name, ifl); + if (ifl < 4) + memset(iface->clientid + 2 + ifl, + 0, 4 - ifl); + } else { + ifl = htonl(if_nametoindex(iface->name)); + memcpy(iface->clientid + 2, &ifl, 4); + } + } else if (len == 0) { + len = iface->hwlen + 1; + iface->clientid = xmalloc(len + 1); + iface->clientid[0] = len; + iface->clientid[1] = iface->family; + memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen); + } } -#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); + if (!(ifo->options & DHCPCD_TEST)) + run_script(iface, "PREINIT"); - while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, - longopts, &option_index)) != -1) - { - switch (opt) { + if (ifo->options & DHCPCD_LINK) { + switch (carrier_status(iface->name)) { 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; + ifs->carrier = LINK_DOWN; break; - case 'V': - print_options(); - goto abort; - case '?': - usage(); - goto abort; + case 1: + ifs->carrier = LINK_UP; + break; + default: + ifs->carrier = LINK_UNKNOWN; } } - if (doversion) - printf(""PACKAGE" "VERSION"\n%s\n", copyright); + if (ifs->carrier == LINK_DOWN) + logger(LOG_INFO, "%s: waiting for carrier", iface->name); + else + start_interface(iface); +} - if (dohelp) - usage(); +static void +handle_signal(struct interface *iface) +{ + int sig = signal_read(); - 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; - } + switch (sig) { + case SIGINT: + logger(LOG_INFO, "received SIGINT, stopping"); + break; + case SIGTERM: + logger(LOG_INFO, "received SIGTERM, stopping"); + break; + default: + logger (LOG_ERR, + "received signal %d, but don't know what to do with it", + sig); + return; + } - logger(LOG_ERR, "no interface specified"); - goto abort; + for (iface = ifaces; iface; iface = iface->next) { + if (!iface->state) + continue; + if (!(iface->state->options->options & DHCPCD_PERSISTENT)) + drop_config(iface, "STOP"); } + exit(EXIT_FAILURE); +} - /* 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) +int +main(int argc, char **argv) +{ + struct if_options *ifo; + struct interface *iface; + int opt, oi = 0, test = 0, signal_fd, sig = 0, i; + pid_t pid; + struct timespec ts; + + closefrom(3); + /* Saves calling fflush(stream) in the logger */ + setlinebuf(stdout); + openlog(PACKAGE, LOG_PID, LOG_LOCAL0); + strncpy(cffile, CONFIG, sizeof(cffile)); + + /* Test for --help and --version */ + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0) { usage(); - if (r != 1) - goto abort; - } else { - if (errno != ENOENT || cf) { - logger(LOG_ERR, "fopen `%s': %s", cf ? cf : CONFIG, - strerror(errno)); - goto abort; + exit(EXIT_SUCCESS); + } else if (strcmp(argv[1], "--version") == 0) { + printf(""PACKAGE" "VERSION"\n%s\n", copyright); + exit(EXIT_SUCCESS); } } - optind = 0; - while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, - longopts, &option_index)) != -1) + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { case 'd': - debug++; - switch (debug) { - case 1: - setloglevel(LOG_DEBUG); - break; - case 2: - options->options &= ~DHCPCD_DAEMONISE; - break; - } + setloglevel(LOG_DEBUG); break; case 'f': - break; - case 'k': - sig = SIGHUP; - break; - case 'n': - sig = SIGALRM; + strlcpy(cffile, optarg, sizeof(cffile)); 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); + test = 1; break; - case 'R': - del_option_mask(options->requestmask, DHO_DNSSERVER); - del_option_mask(options->requestmask, DHO_DNSDOMAIN); - del_option_mask(options->requestmask, DHO_DNSSEARCH); - 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; + case 'V': + print_options(); + exit(EXIT_SUCCESS); + case '?': + usage(); + exit(EXIT_FAILURE); } } + ifo = read_config(cffile, NULL); + opt = add_options(ifo, argc, argv); + if (opt != 1) { + if (opt == 0) + usage(); + exit(EXIT_FAILURE); + } + if (test) + ifo->options |= DHCPCD_TEST | DHCPCD_PERSISTENT; #ifdef THERE_IS_NO_FORK - options->options &= ~DHCPCD_DAEMONISE; + ifo->options &= ~DHCPCD_DAEMONISE; #endif + /* If we have any other args, we should run as a single dhcpcd instance + * for that interface. */ + if (optind == argc - 1) + snprintf(pidfile, sizeof(pidfile), PIDFILE, "-", argv[optind]); + else + snprintf(pidfile, sizeof(pidfile), PIDFILE, "", ""); + if (geteuid()) logger(LOG_WARNING, PACKAGE " will not work correctly unless" " run as root"); - 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); + atexit(cleanup); - if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) { + if (sig != 0) { i = -1; - pid = read_pid(options->pidfile); + pid = read_pid(); if (pid != 0) logger(LOG_INFO, "sending signal %d to pid %d", sig, pid); @@ -881,89 +834,84 @@ main(int argc, char **argv) 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; + unlink(pidfile); + exit(EXIT_FAILURE); + } + /* 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() == 0) + exit(EXIT_SUCCESS); + } + logger(LOG_ERR, "pid %d failed to exit", pid); + exit(EXIT_FAILURE); } - if (!(options->options & DHCPCD_TEST) && - !(options->options & DHCPCD_DAEMONISED)) - { - if ((pid = read_pid(options->pidfile)) > 0 && + if (!(ifo->options & DHCPCD_TEST)) { + if ((pid = read_pid()) > 0 && kill(pid, 0) == 0) { logger(LOG_ERR, ""PACKAGE " already running on pid %d (%s)", - pid, options->pidfile); - goto abort; + pid, pidfile); + exit(EXIT_FAILURE); } - pid_fd = open(options->pidfile, - O_WRONLY | O_CREAT | O_NONBLOCK, 0664); - if (pid_fd == -1) { + pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); + if (pidfd == -1) { logger(LOG_ERR, "open `%s': %s", - options->pidfile, strerror(errno)); - goto abort; + pidfile, strerror(errno)); + exit(EXIT_FAILURE); } - /* Lock the file so that only one instance of dhcpcd runs * on an interface */ - if (flock(pid_fd, LOCK_EX | LOCK_NB) == -1) { + if (flock(pidfd, LOCK_EX | LOCK_NB) == -1) { logger(LOG_ERR, "flock `%s': %s", - options->pidfile, strerror(errno)); - goto abort; + pidfile, strerror(errno)); + exit(EXIT_FAILURE); } - - if (set_cloexec(pid_fd) == -1) - goto abort; - writepid(pid_fd, getpid()); - logger(LOG_INFO, PACKAGE " " VERSION " starting"); + if (set_cloexec(pidfd) == -1) + exit(EXIT_FAILURE); + writepid(pidfd, getpid()); } - /* Terminate the encapsulated options */ - if (options->vendor[0]) { - options->vendor[0]++; - options->vendor[options->vendor[0]] = DHO_END; - } + logger(LOG_INFO, PACKAGE " " VERSION " starting"); - if (dhcp_run(options, &pid_fd) == 0) - retval = EXIT_SUCCESS; + if ((signal_fd =signal_init()) == -1) + exit(EXIT_FAILURE); + if (signal_setup() == -1) + exit(EXIT_FAILURE); + add_event(signal_fd, handle_signal, NULL); -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 (ifo->options & DHCPCD_LINK) { + linkfd = open_link_socket(); + if (linkfd == -1) + logger(LOG_ERR, "open_link_socket: %s", + strerror(errno)); + else + add_event(linkfd, handle_link, NULL); } - if (options->environ) { - len = 0; - while (options->environ[len]) - free(options->environ[len++]); - free(options->environ); + + if (!(ifo->options & DHCPCD_DAEMONISE)) + can_daemonise = 0; + if (can_daemonise && !(ifo->options & DHCPCD_BACKGROUND)) { + oi = ifo->timeout; + if (ifo->options & DHCPCD_IPV4LL) + oi += 10; + add_timeout_sec(oi, handle_exit_timeout, NULL); } - free(options->blacklist); - free(options); - exit(retval); + free_options(ifo); + + argc -= optind; + argv += optind; + ifaces = discover_interfaces(argc, argv); + argc += optind; + argv -= optind; + for (iface = ifaces; iface; iface = iface->next) + init_state(iface, argc, argv); + start_eloop(); /* NOTREACHED */ } diff --git a/dhcpcd.h b/dhcpcd.h index 1cd2b5d5..061a1d1b 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -28,68 +28,92 @@ #ifndef DHCPCD_H #define DHCPCD_H -#include -#include - #include -#include #include -#include "common.h" - -#define DEFAULT_TIMEOUT 30 -#define DEFAULT_LEASETIME 3600 /* 1 hour */ - -#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ -#define VENDORCLASSID_MAX_LEN 48 -#define CLIENTID_MAX_LEN 48 -#define USERCLASS_MAX_LEN 255 -#define VENDOR_MAX_LEN 255 - -#define DHCPCD_ARP (1 << 0) -#define DHCPCD_DOMAIN (1 << 2) -#define DHCPCD_GATEWAY (1 << 3) -#define DHCPCD_LASTLEASE (1 << 7) -#define DHCPCD_INFORM (1 << 8) -#define DHCPCD_REQUEST (1 << 9) -#define DHCPCD_IPV4LL (1 << 10) -#define DHCPCD_DUID (1 << 11) -#define DHCPCD_PERSISTENT (1 << 12) -#define DHCPCD_DAEMONISE (1 << 14) -#define DHCPCD_DAEMONISED (1 << 15) -#define DHCPCD_TEST (1 << 16) -#define DHCPCD_FORKED (1 << 17) -#define DHCPCD_HOSTNAME (1 << 18) -#define DHCPCD_CLIENTID (1 << 19) -#define DHCPCD_LINK (1 << 20) -#define DHCPCD_BACKGROUND (1 << 21) - -struct options { - char interface[IF_NAMESIZE]; +#include "dhcp.h" + +#define HWADDR_LEN 20 + +enum DHS { + DHS_INIT, + DHS_DISCOVERING, + DHS_REQUESTING, + DHS_BOUND, + DHS_RENEWING, + DHS_REBINDING, + DHS_REBOOT, + DHS_RENEW_REQUESTED, + DHS_INIT_IPV4LL, + DHS_PROBING, + DHS_ANNOUNCING +}; + +#define LINK_UP 1 +#define LINK_UNKNOWN 0 +#define LINK_DOWN -1 + +struct if_state { + enum DHS state; + struct if_options *options; + struct dhcp_message *sent; + struct dhcp_message *offer; + struct dhcp_message *new; + struct dhcp_message *old; + struct dhcp_lease lease; + time_t interval; + time_t nakoff; + uint32_t xid; + int socket; + int carrier; + int probes; + int claims; + int conflicts; + time_t defend; + struct in_addr fail; +}; + +struct interface +{ + char name[IF_NAMESIZE]; + struct if_state *state; + + sa_family_t family; + unsigned char hwaddr[HWADDR_LEN]; + size_t hwlen; int metric; - uint8_t requestmask[256 / 8]; - uint8_t requiremask[256 / 8]; - uint8_t nomask[256 / 8]; - uint32_t leasetime; - time_t timeout; - int options; - - struct in_addr request_address; - struct in_addr request_netmask; - - char **environ; - char script[PATH_MAX]; - char pidfile[PATH_MAX]; - - char hostname[HOSTNAME_MAX_LEN + 1]; - 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; + int arpable; + + int raw_fd; + int udp_fd; + int arp_fd; + size_t buffer_size, buffer_len, buffer_pos; + unsigned char *buffer; + + struct in_addr addr; + struct in_addr net; + struct rt *routes; + + char leasefile[PATH_MAX]; + time_t start_uptime; + + unsigned char *clientid; + + struct interface *next; }; + +extern int pidfd; +void handle_exit_timeout(struct interface *); +void send_request(struct interface *); +void start_interface(struct interface *); +void start_discover(struct interface *); +void start_renew(struct interface *); +void start_rebind(struct interface *); +void start_reboot(struct interface *); +void start_expire(struct interface *); +void send_decline(struct interface *); +void close_sockets(struct interface *); +void drop_config(struct interface *, const char *); + #endif diff --git a/dhcpf.h b/dhcpf.h new file mode 100644 index 00000000..eb09633a --- /dev/null +++ b/dhcpf.h @@ -0,0 +1,55 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPF_H +#define DHCPF_H + +#include "dhcp.h" +#include "if-options.h" +#include "net.h" + +#define add_option_mask(var, val) (var[val >> 3] |= 1 << (val & 7)) +#define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) +#define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7))) +int make_option_mask(uint8_t *, const char *, int); +void print_options(void); +char *get_option_string(const struct dhcp_message *, uint8_t); +int get_option_addr(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 if_options *); + +ssize_t make_message(struct dhcp_message **, const struct interface *, uint8_t); +int valid_dhcp_packet(unsigned char *); + +ssize_t write_lease(const struct interface *, const struct dhcp_message *); +struct dhcp_message *read_lease(const struct interface *); +void get_lease(struct dhcp_lease *, const struct dhcp_message *); +#endif diff --git a/duid.c b/duid.c new file mode 100644 index 00000000..8b19550a --- /dev/null +++ b/duid.c @@ -0,0 +1,106 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define THIRTY_YEARS_IN_SECONDS 946707779 + +#include +#include +#include +#include +#include + +#include "common.h" +#include "duid.h" +#include "net.h" + +size_t +get_duid(unsigned char *duid, const struct interface *iface) +{ + FILE *f; + uint16_t type = 0; + uint16_t hw = 0; + uint32_t ul; + time_t t; + int x = 0; + unsigned char *p = duid; + size_t len = 0, 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; +} diff --git a/client.h b/duid.h similarity index 92% rename from client.h rename to duid.h index 35a5e37c..cc2224e8 100644 --- a/client.h +++ b/duid.h @@ -25,11 +25,11 @@ * SUCH DAMAGE. */ -#ifndef CLIENT_H -#define CLIENT_H +#ifndef DUID_H +#define DUID_H -#include "dhcpcd.h" +#include "net.h" -int dhcp_run(const struct options *, int *); +size_t get_duid(unsigned char *duid, const struct interface *iface); #endif diff --git a/eloop.c b/eloop.c new file mode 100644 index 00000000..63914162 --- /dev/null +++ b/eloop.c @@ -0,0 +1,347 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "eloop.h" +#include "logger.h" +#include "net.h" + +static struct timeval now = {0, 0}; + +static struct event { + int fd; + void (*callback)(struct interface *); + struct interface *iface; + struct event *next; +} *events = NULL; +static struct event *free_events = NULL; + +static struct timeout { + struct timeval when; + void (*callback)(struct interface *); + struct interface *iface; + struct timeout *next; +} *timeouts = NULL; +static struct timeout *free_timeouts = NULL; + +static struct pollfd *fds = NULL; +static size_t fds_len = 0; + +#ifdef DEBUG_MEMORY +/* Define this to free all malloced memory. + * Normally we don't do this as the OS will do it for us at exit, + * but it's handy for debugging other leaks in valgrind. */ +static void +cleanup(void) +{ + struct event *e; + struct timeout *t; + + while (events) { + e = events->next; + free(events); + events = e; + } + while (free_events) { + e = free_events->next; + free(free_events); + free_events = e; + } + while (timeouts) { + t = timeouts->next; + free(timeouts); + timeouts = t; + } + while (free_timeouts) { + t = free_timeouts->next; + free(free_timeouts); + free_timeouts = t; + } + free(fds); +} +#endif + +void +add_event(int fd, void (*callback)(struct interface *), struct interface *iface) +{ + struct event *e, *last = NULL; + + /* We should only have one callback monitoring the fd */ + for (e = events; e; e = e->next) { + if (e->fd == fd) { + e->callback = callback; + e->iface = iface; + return; + } + last = e; + } + + /* Allocate a new event if no free ones already allocated */ + if (free_events) { + e = free_events; + free_events = e->next; + } else + e = xmalloc(sizeof(*e)); + e->fd = fd; + e->callback = callback; + e->iface = iface; + e->next = NULL; + if (last) + last->next = e; + else + events = e; +} + +void +delete_event(int fd) +{ + struct event *e, *last = NULL; + + for (e = events; e; e = e->next) { + if (e->fd == fd) { + if (last) + last->next = e->next; + else + events = e->next; + e->next = free_events; + free_events = e; + break; + } + last = e; + } +} + +void +add_timeout_tv(const struct timeval *when, + void (*callback)(struct interface *), struct interface *iface) +{ + struct timeval w; + struct timeout *t, *tt = NULL; + + get_monotonic(&now); + timeradd(&now, when, &w); + + /* Remove existing timeout if present */ + for (t = timeouts; t; t = t->next) { + if (t->callback == callback && t->iface == iface) { + if (tt) + tt->next = t->next; + else + timeouts = t->next; + break; + } + tt = t; + } + + if (!t) { + /* No existing, so allocate or grab one from the free pool */ + if (free_timeouts) { + t = free_timeouts; + free_timeouts = t->next; + } else + t = xmalloc(sizeof(*t)); + } + + t->when.tv_sec = w.tv_sec; + t->when.tv_usec = w.tv_usec; + t->callback = callback; + t->iface = iface; + + /* The timeout list should be in chronological order, + * soonest first. + * This is the easiest algorithm - check the head, then middle + * and finally the end. */ + if (!timeouts || timercmp(&t->when, &timeouts->when, <)) { + t->next = timeouts; + timeouts = t; + return; + } + for (tt = timeouts; tt->next; tt = tt->next) + if (timercmp(&t->when, &tt->next->when, <)) { + t->next = tt->next; + tt->next = t; + return; + } + tt->next = t; + t->next = NULL; +} + +void +add_timeout_sec(time_t when, + void (*callback)(struct interface *), struct interface *iface) +{ + struct timeval tv; + + tv.tv_sec = when; + tv.tv_usec = 0; + add_timeout_tv(&tv, callback, iface); +} + +/* This deletes all timeouts for the interface EXCEPT for ones with the + * callbacks given. Handy for deleting everything apart from the expire + * timeout. */ +void +delete_timeouts(struct interface *iface, + void (*callback)(struct interface *), ...) +{ + struct timeout *t, *tt, *last = NULL; + va_list va; + void (*f)(struct interface *); + + for (t = timeouts; t && (tt = t->next, 1); t = tt) { + if (t->iface == iface && t->callback != callback) { + va_start(va, callback); + while ((f = va_arg(va, void (*)(struct interface *)))) + if (f == t->callback) + break; + va_end(va); + if (!f) { + if (last) + last->next = t->next; + else + timeouts = t->next; + t->next = free_timeouts; + free_timeouts = t; + continue; + } + } + } +} + +void +delete_timeout(void (*callback)(struct interface *), struct interface *iface) +{ + struct timeout *t, *tt, *last = NULL; + + for (t = timeouts; t && (tt = t->next, 1); t = tt) { + if (t->iface == iface && + (!callback || t->callback == callback)) + { + if (last) + last->next = t->next; + else + timeouts = t->next; + t->next = free_timeouts; + free_timeouts = t; + continue; + } + last = t; + } +} + +void +start_eloop(void) +{ + int msecs, n; + nfds_t nfds, i; + struct event *e; + struct timeout *t; + struct timeval tv; + +#ifdef DEBUG_MEMORY + atexit(cleanup); +#endif + + for (;;) { + /* Run all timeouts first. + * When we have one that has not yet occured, + * calculate milliseconds until it does for use in poll. */ + if (timeouts) { + if (timercmp(&now, &timeouts->when, >)) { + t = timeouts; + timeouts = timeouts->next; + t->callback(t->iface); + t->next = free_timeouts; + free_timeouts = t; + continue; + } + timersub(&timeouts->when, &now, &tv); + if (tv.tv_sec > INT_MAX / 1000 || + (tv.tv_sec == INT_MAX / 1000 && + (tv.tv_usec + 999) / 1000 > INT_MAX % 1000)) + msecs = INT_MAX; + else + msecs = tv.tv_sec * 1000 + + (tv.tv_usec + 999) / 1000; + } else + /* No timeouts, so wait forever. */ + msecs = -1; + + /* Allocate memory for our pollfds as and when needed. + * We don't bother shrinking it. */ + nfds = 0; + for (e = events; e; e = e->next) + nfds++; + if (msecs == -1 && nfds == 0) { + logger(LOG_ERR, "nothing to do"); + exit(EXIT_FAILURE); + } + if (nfds > fds_len) { + free(fds); + fds = xmalloc(sizeof(*fds) * nfds); + fds_len = nfds; + } + nfds = 0; + for (e = events; e; e = e->next) { + fds[nfds].fd = e->fd; + fds[nfds].events = POLLIN; + fds[nfds].revents = 0; + nfds++; + } + n = poll(fds, nfds, msecs); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) { + get_monotonic(&now); + continue; + } + logger(LOG_ERR, "poll: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Get the now time and process any triggered events. */ + get_monotonic(&now); + if (n == 0) + continue; + for (i = 0; i < nfds; i++) { + if (!(fds[i].revents & (POLLIN | POLLHUP))) + continue; + for (e = events; e; e = e->next) { + if (e->fd == fds[i].fd) { + e->callback(e->iface); + break; + } + } + } + } +} diff --git a/eloop.h b/eloop.h new file mode 100644 index 00000000..18e64ee6 --- /dev/null +++ b/eloop.h @@ -0,0 +1,44 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ELOOP_H +#define ELOOP_H + +#include + +#include "dhcpcd.h" + +void add_event(int fd, void (*)(struct interface *), struct interface *); +void delete_event(int fd); +void add_timeout_sec(time_t, void (*)(struct interface *), struct interface *); +void add_timeout_tv(const struct timeval *, + void (*)(struct interface *), struct interface *); +void delete_timeout(void (*)(struct interface *), struct interface *); +void delete_timeouts(struct interface *, void (*)(struct interface *), ...); +void start_eloop(void); + +#endif diff --git a/if-bsd.c b/if-bsd.c index bbf1a95d..d44bf4e2 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -58,7 +58,7 @@ #endif int -if_address(const char *ifname, const struct in_addr *address, +if_address(const struct interface *iface, const struct in_addr *address, const struct in_addr *netmask, const struct in_addr *broadcast, int action) { @@ -74,7 +74,7 @@ if_address(const char *ifname, const struct in_addr *address, return -1; memset(&ifa, 0, sizeof(ifa)); - strlcpy(ifa.ifra_name, ifname, sizeof(ifa.ifra_name)); + strlcpy(ifa.ifra_name, iface->name, sizeof(ifa.ifra_name)); #define ADDADDR(_var, _addr) \ _s.sa = &_var; \ @@ -98,7 +98,7 @@ if_address(const char *ifname, const struct in_addr *address, } int -if_route(const char *ifname, const struct in_addr *destination, +if_route(const struct interface *iface, const struct in_addr *destination, const struct in_addr *netmask, const struct in_addr *gateway, _unused int metric, int action) { @@ -120,8 +120,6 @@ if_route(const char *ifname, const struct in_addr *destination, } 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) @@ -156,21 +154,18 @@ if_route(const char *ifname, const struct in_addr *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; + su.sdl.sdl_nlen = strlen(iface->name); + memcpy(&su.sdl.sdl_data, iface->name, (size_t)su.sdl.sdl_nlen); + su.sdl.sdl_alen = iface->hwlen; memcpy(((unsigned char *)&su.sdl.sdl_data) + su.sdl.sdl_nlen, - hwaddr, (size_t)su.sdl.sdl_alen); + iface->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); @@ -187,34 +182,28 @@ if_route(const char *ifname, const struct in_addr *destination, } int -open_link_socket(struct interface *iface) +open_link_socket(void) { int fd; fd = socket(PF_ROUTE, SOCK_RAW, 0); - if (fd == -1) - return -1; - set_cloexec(fd); - if (iface->link_fd != -1) - close(iface->link_fd); - iface->link_fd = fd; - return 0; + if (fd != -1) + set_cloexec(fd); + return fd; } #define BUFFER_LEN 2048 int -link_changed(struct interface *iface) +link_changed(int fd, const struct interface *ifaces) { char buffer[2048], *p; ssize_t bytes; struct rt_msghdr *rtm; struct if_msghdr *ifm; - int i; + const struct interface *iface; - if ((i = if_nametoindex(iface->name)) == -1) - return -1; for (;;) { - bytes = recv(iface->link_fd, buffer, BUFFER_LEN, MSG_DONTWAIT); + bytes = recv(fd, buffer, BUFFER_LEN, MSG_DONTWAIT); if (bytes == -1) { if (errno == EAGAIN) return 0; @@ -230,12 +219,19 @@ link_changed(struct interface *iface) if (rtm->rtm_type != RTM_IFINFO) continue; ifm = (struct if_msghdr *)p; - if (ifm->ifm_index != i) - continue; - - /* Link changed */ - return 1; + for (iface = ifaces; iface; iface = iface->next) + if (ifm->ifm_index == if_nametoindex(iface->name)) + return 1; } } return 0; } + +struct interface * +discover_interfaces(int argc, char * const *argv) +{ + struct interface *ifaces = NULL; + + do_interface(NULL, &ifaces, argc, argv, NULL, NULL, 2); + return ifaces; +} diff --git a/if-linux.c b/if-linux.c index afb0ea64..45b5b2b7 100644 --- a/if-linux.c +++ b/if-linux.c @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -59,7 +60,7 @@ #define BUFFERLEN 256 int -open_link_socket(struct interface *iface) +open_link_socket(void) { int fd; struct sockaddr_nl nl; @@ -72,16 +73,13 @@ open_link_socket(struct interface *iface) if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) return -1; set_cloexec(fd); - if (iface->link_fd != -1) - close(iface->link_fd); - iface->link_fd = fd; - return 0; + return fd; } static int get_netlink(int fd, int flags, - int (*callback)(struct nlmsghdr *, const char *), - const char *ifname) + int (*callback)(struct nlmsghdr *, const struct interface *), + const struct interface *iface) { char *buffer = NULL; ssize_t bytes; @@ -104,7 +102,7 @@ get_netlink(int fd, int flags, NLMSG_OK(nlm, (size_t)bytes); nlm = NLMSG_NEXT(nlm, bytes)) { - r = callback(nlm, ifname); + r = callback(nlm, iface); if (r != 0) goto eexit; } @@ -116,7 +114,7 @@ eexit: } static int -err_netlink(struct nlmsghdr *nlm, _unused const char *ifname) +err_netlink(struct nlmsghdr *nlm, _unused const struct interface *iface) { struct nlmsgerr *err; int l; @@ -136,12 +134,13 @@ err_netlink(struct nlmsghdr *nlm, _unused const char *ifname) } static int -link_netlink(struct nlmsghdr *nlm, const char *ifname) +link_netlink(struct nlmsghdr *nlm, const struct interface *ifaces) { int len; struct rtattr *rta; struct ifinfomsg *ifi; char ifn[IF_NAMESIZE + 1]; + const struct interface *iface; if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK) return 0; @@ -171,16 +170,16 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname) rta = RTA_NEXT(rta, len); } - if (strncmp(ifname, ifn, sizeof(ifn)) == 0) - return 1; + for (iface = ifaces; iface; iface = iface->next) + if (strcmp(iface->name, ifn) == 0) + return 1; return 0; } int -link_changed(struct interface *iface) +link_changed(int fd, const struct interface *iface) { - return get_netlink(iface->link_fd, MSG_DONTWAIT, - &link_netlink, iface->name); + return get_netlink(fd, MSG_DONTWAIT, &link_netlink, iface); } static int @@ -279,7 +278,7 @@ struct nlmr }; int -if_address(const char *ifname, +if_address(const struct interface *iface, const struct in_addr *address, const struct in_addr *netmask, const struct in_addr *broadcast, int action) { @@ -294,7 +293,7 @@ if_address(const char *ifname, nlm->hdr.nlmsg_type = RTM_NEWADDR; } else nlm->hdr.nlmsg_type = RTM_DELADDR; - if (!(nlm->ifa.ifa_index = if_nametoindex(ifname))) { + if (!(nlm->ifa.ifa_index = if_nametoindex(iface->name))) { free(nlm); errno = ENODEV; return -1; @@ -303,7 +302,7 @@ if_address(const char *ifname, nlm->ifa.ifa_prefixlen = inet_ntocidr(*netmask); /* This creates the aliased interface */ add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LABEL, - ifname, strlen(ifname) + 1); + iface->name, strlen(iface->name) + 1); add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL, &address->s_addr, sizeof(address->s_addr)); if (action >= 0) @@ -317,7 +316,7 @@ if_address(const char *ifname, } int -if_route(const char *ifname, +if_route(const struct interface *iface, const struct in_addr *destination, const struct in_addr *netmask, const struct in_addr *gateway, int metric, int action) { @@ -326,7 +325,7 @@ if_route(const char *ifname, int retval = 0; - if (!(ifindex = if_nametoindex(ifname))) { + if (!(ifindex = if_nametoindex(iface->name))) { errno = ENODEV; return -1; } @@ -370,3 +369,49 @@ if_route(const char *ifname, free(nlm); return retval; } + +struct interface * +discover_interfaces(int argc, char * const *argv) +{ + FILE *f; + char *buffer = NULL, *p; + size_t len = 0, ln = 0, n; + int i, s; + struct interface *ifaces = NULL, *ifp, *ifl; + + if ((f = fopen("/proc/net/dev", "r"))) { + while (get_line(&buffer, &len, f)) { + if (++ln < 2) + continue; + p = buffer; + while (isspace((unsigned int)*p)) + p++; + n = strcspn(p, ": \t"); + p[n]= '\0'; + ifl = NULL; + for (ifp = ifaces; ifp; ifp = ifp->next) { + if (strcmp(ifp->name, p) == 0) + break; + ifl = ifp; + } + if (ifp) + continue; + if (argc > 0) { + for (i = 0; i < argc; i++) + if (strcmp(argv[i], p) == 0) + break; + if (i == argc) + continue; + } + if (ifp = init_interface(p)) { + if (ifl) + ifl->next =ifp; + else + ifaces = ifp; + } + } + fclose(f); + free(buffer); + } + return ifaces; +} diff --git a/if-options.c b/if-options.c new file mode 100644 index 00000000..836bf8de --- /dev/null +++ b/if-options.c @@ -0,0 +1,653 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "config.h" +#include "dhcpf.h" +#include "if-options.h" +#include "logger.h" +#include "net.h" + +/* 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:" + +const struct option cf_options[] = { + {"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'}, + {NULL, 0, NULL, '\0'} +}; + +static int +atoint(const char *s) +{ + char *t; + long n; + + errno = 0; + n = strtol(s, &t, 0); + if ((errno != 0 && n == 0) || s == t || + (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN))) + { + logger(LOG_ERR, "`%s' out of range", s); + return -1; + } + + return (int)n; +} + +static char * +add_environ(struct if_options *ifo, const char *value, int uniq) +{ + char **newlist; + char **lst = ifo->environ; + size_t i = 0, l, lv; + char *match = NULL, *p; + + match = xstrdup(value); + p = strchr(match, '='); + if (p) + *p++ = '\0'; + l = strlen(match); + + while (lst && lst[i]) { + if (match && strncmp(lst[i], match, l) == 0) { + if (uniq) { + free(lst[i]); + lst[i] = xstrdup(value); + } else { + /* Append a space and the value to it */ + l = strlen(lst[i]); + lv = strlen(p); + lst[i] = xrealloc(lst[i], l + lv + 2); + lst[i][l] = ' '; + memcpy(lst[i] + l + 1, p, lv); + lst[i][l + lv + 1] = '\0'; + } + free(match); + return lst[i]; + } + i++; + } + + newlist = xrealloc(lst, sizeof(char *) * (i + 2)); + newlist[i] = xstrdup(value); + newlist[i + 1] = NULL; + ifo->environ = newlist; + free(match); + return newlist[i]; +} + +#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) +static ssize_t +parse_string_hwaddr(char *sbuf, ssize_t slen, const char *str, int clid) +{ + ssize_t l; + const char *p; + int i, punt_last = 0; + char c[4]; + + /* If surrounded by quotes then it's a string */ + if (*str == '"') { + str++; + l = strlen(str); + p = str + l - 1; + if (*p == '"') + punt_last = 1; + } else { + l = hwaddr_aton(NULL, str); + if (l > 1) { + if (l > slen) { + errno = ENOBUFS; + return -1; + } + hwaddr_aton((uint8_t *)sbuf, str); + return l; + } + } + + /* Process escapes */ + l = 0; + /* If processing a string on the clientid, first byte should be + * 0 to indicate a non hardware type */ + if (clid) { + *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++; + } + if (punt_last) + *--sbuf = '\0'; + return l; +} + +static int +parse_option(struct if_options *ifo, int opt, const char *arg) +{ + int i; + char *p; + ssize_t s; + struct in_addr addr; + + switch(opt) { + case 'b': + ifo->options |= DHCPCD_BACKGROUND; + break; + case 'c': + strlcpy(ifo->script, arg, sizeof(ifo->script)); + break; + case 'd': + break; + case 'h': + if (arg) + s = parse_string(ifo->hostname + 1, + HOSTNAME_MAX_LEN, arg); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "hostname: %s", strerror(errno)); + return -1; + } + if (s != 0 && ifo->hostname[1] == '.') { + logger(LOG_ERR, "hostname cannot begin with a ."); + return -1; + } + ifo->hostname[0] = (uint8_t)s; + break; + case 'i': + if (arg) + s = parse_string((char *)ifo->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, arg); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "vendorclassid: %s", strerror(errno)); + return -1; + } + *ifo->vendorclassid = (uint8_t)s; + break; + case 'l': + if (*arg == '-') { + logger(LOG_ERR, + "leasetime must be a positive value"); + return -1; + } + errno = 0; + ifo->leasetime = (uint32_t)strtol(arg, NULL, 0); + if (errno == EINVAL || errno == ERANGE) { + logger(LOG_ERR, "`%s' out of range", arg); + return -1; + } + break; + case 'm': + ifo->metric = atoint(arg); + if (ifo->metric < 0) { + logger(LOG_ERR, "metric must be a positive value"); + return -1; + } + break; + case 'o': + if (make_option_mask(ifo->requestmask, arg, 1) != 0) { + logger(LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'p': + ifo->options |= DHCPCD_PERSISTENT; + break; + case 'q': + setloglevel(LOG_WARNING); + break; + case 's': + ifo->options |= DHCPCD_INFORM; + ifo->options |= DHCPCD_PERSISTENT; + ifo->options &= ~DHCPCD_ARP; + if (!arg || *arg == '\0') { + ifo->request_address.s_addr = 0; + break; + } else { + if ((p = strchr(arg, '/'))) { + /* nullify the slash, so the -r option + * can read the address */ + *p++ = '\0'; + if (sscanf(p, "%d", &i) != 1 || + inet_cidrtoaddr(i, &ifo->request_netmask) != 0) + { + logger(LOG_ERR, + "`%s' is not a valid CIDR", + p); + return -1; + } + } + } + /* FALLTHROUGH */ + case 'r': + if (!(ifo->options & DHCPCD_INFORM)) + ifo->options |= DHCPCD_REQUEST; + if (arg && !inet_aton(arg, &ifo->request_address)) { + logger(LOG_ERR, "`%s' is not a valid IP address", + arg); + return -1; + } + break; + case 't': + ifo->timeout = atoint(arg); + if (ifo->timeout < 0) { + logger (LOG_ERR, "timeout must be a positive value"); + return -1; + } + break; + case 'u': + s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1; + s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2, + s, arg); + if (s == -1) { + logger(LOG_ERR, "userclass: %s", strerror(errno)); + return -1; + } + if (s != 0) { + ifo->userclass[ifo->userclass[0] + 1] = s; + ifo->userclass[0] += s + 1; + } + break; + case 'v': + p = strchr(arg, ','); + if (!p || !p[1]) { + logger(LOG_ERR, "invalid vendor format"); + return -1; + } + *p = '\0'; + i = atoint(arg); + arg = 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 - ifo->vendor[0] - 2; + if (inet_aton(arg, &addr) == 1) { + if (s < 6) { + s = -1; + errno = ENOBUFS; + } else + memcpy(ifo->vendor + ifo->vendor[0] + 3, + &addr.s_addr, sizeof(addr.s_addr)); + } else { + s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3, + s, arg); + } + if (s == -1) { + logger(LOG_ERR, "vendor: %s", strerror(errno)); + return -1; + } + if (s != 0) { + ifo->vendor[ifo->vendor[0] + 1] = i; + ifo->vendor[ifo->vendor[0] + 2] = s; + ifo->vendor[0] += s + 2; + } + break; + case 'x': + break; + case 'A': + ifo->options &= ~DHCPCD_ARP; + /* IPv4LL requires ARP */ + ifo->options &= ~DHCPCD_IPV4LL; + break; + case 'B': + ifo->options &= ~DHCPCD_DAEMONISE; + break; + case 'C': + /* Commas to spaces for shell */ + while ((p = strchr(arg, ','))) + *p = ' '; + s = strlen("skip_hooks=") + strlen(arg) + 1; + p = xmalloc(sizeof(char) * s); + snprintf(p, s, "skip_hooks=%s", arg); + add_environ(ifo, p, 0); + free(p); + break; + case 'D': + ifo->options |= DHCPCD_DUID; + break; + case 'E': + ifo->options |= DHCPCD_LASTLEASE; + break; + case 'F': + if (!arg) { + ifo->fqdn = FQDN_BOTH; + break; + } + if (strcmp(arg, "none") == 0) + ifo->fqdn = FQDN_NONE; + else if (strcmp(arg, "ptr") == 0) + ifo->fqdn = FQDN_PTR; + else if (strcmp(arg, "both") == 0) + ifo->fqdn = FQDN_BOTH; + else if (strcmp(arg, "disable") == 0) + ifo->fqdn = FQDN_DISABLE; + else { + logger(LOG_ERR, "invalid value `%s' for FQDN", arg); + return -1; + } + break; + case 'G': + ifo->options &= ~DHCPCD_GATEWAY; + break; + case 'I': + /* Strings have a type of 0 */; + ifo->clientid[1] = 0; + if (arg) + s = parse_string_hwaddr((char *)ifo->clientid + 1, + CLIENTID_MAX_LEN, arg, 1); + else + s = 0; + if (s == -1) { + logger(LOG_ERR, "clientid: %s", strerror(errno)); + return -1; + } + ifo->clientid[0] = (uint8_t)s; + if (s == 0) { + ifo->options &= ~DHCPCD_DUID; + ifo->options &= ~DHCPCD_CLIENTID; + } + break; + case 'K': + ifo->options &= ~DHCPCD_LINK; + break; + case 'L': + ifo->options &= ~DHCPCD_IPV4LL; + break; + case 'O': + if (make_option_mask(ifo->requestmask, arg, -1) != 0 || + make_option_mask(ifo->requiremask, arg, -1) != 0 || + make_option_mask(ifo->nomask, arg, 1) != 0) + { + logger(LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'Q': + if (make_option_mask(ifo->requiremask, arg, 1) != 0 || + make_option_mask(ifo->requestmask, arg, 1) != 0) + { + logger(LOG_ERR, "unknown option `%s'", arg); + return -1; + } + break; + case 'X': + if (!inet_aton(arg, &addr)) { + logger(LOG_ERR, "`%s' is not a valid IP address", + arg); + return -1; + } + ifo->blacklist = xrealloc(ifo->blacklist, + sizeof(in_addr_t) * (ifo->blacklist_len + 1)); + ifo->blacklist[ifo->blacklist_len] = addr.s_addr; + ifo->blacklist_len++; + break; + default: + return 0; + } + + return 1; +} + +static int +parse_config_line(struct if_options *ifo, const char *opt, char *line) +{ + unsigned int i; + + for (i = 0; i < sizeof(cf_options) / sizeof(cf_options[0]); i++) { + if (!cf_options[i].name || + strcmp(cf_options[i].name, opt) != 0) + continue; + + if (cf_options[i].has_arg == required_argument && !line) { + fprintf(stderr, + PACKAGE ": option requires an argument -- %s\n", + opt); + return -1; + } + + return parse_option(ifo, cf_options[i].val, line); + } + + fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt); + return -1; +} + +struct if_options * +read_config(const char *file, const char *ifname) +{ + struct if_options *ifo; + FILE *f; + size_t len = 0; + char *line, *option, *p, *buffer = NULL; + int skip = 0; + + /* Seed our default options */ + ifo = xzalloc(sizeof(*ifo)); + ifo->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE; + ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK; + ifo->timeout = DEFAULT_TIMEOUT; + gethostname(ifo->hostname + 1, sizeof(ifo->hostname)); + if (strcmp(ifo->hostname + 1, "(none)") == 0 || + strcmp(ifo->hostname + 1, "localhost") == 0) + ifo->hostname[1] = '\0'; + *ifo->hostname = strlen(ifo->hostname + 1); + strlcpy(ifo->script, SCRIPT, sizeof(ifo->script)); + ifo->vendorclassid[0] = snprintf((char *)ifo->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, + "%s %s", PACKAGE, VERSION); + + /* Parse our options file */ + f = fopen(file, "r"); + if (!f) + return ifo; + + 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'; + } + /* Start of an interface block, skip if not ours */ + if (strcmp(option, "interface") == 0) { + if (ifname && line && strcmp(line, ifname) == 0) + skip = 0; + else + skip = 1; + continue; + } + if (skip) + continue; + if (parse_config_line(ifo, option, line) != 1) { + break; + } + } + free(buffer); + fclose(f); + + /* Terminate the encapsulated options */ + if (ifo->vendor[0]) { + ifo->vendor[0]++; + ifo->vendor[ifo->vendor[0]] = DHO_END; + } + return ifo; +} + +int +add_options(struct if_options *ifo, int argc, char **argv) +{ + int oi, opt, r = 1; + + optind = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + r = parse_option(ifo, opt, optarg); + if (r != 1) + break; + } + /* Terminate the encapsulated options */ + if (r == 1 && ifo->vendor[0]) { + ifo->vendor[0]++; + ifo->vendor[ifo->vendor[0]] = DHO_END; + } + return r; +} + +void +free_options(struct if_options *ifo) +{ + size_t i; + + if (ifo->environ) { + i = 0; + while (ifo->environ[i]) + free(ifo->environ[i++]); + free(ifo->environ); + } + free(ifo->blacklist); + free(ifo); +} diff --git a/if-options.h b/if-options.h new file mode 100644 index 00000000..154aef2a --- /dev/null +++ b/if-options.h @@ -0,0 +1,100 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IF_OPTIONS_H +#define IF_OPTIONS_H + +#include +#include + +#include +#include + +/* Don't set any optional arguments here so we retain POSIX + * * compatibility with getopt */ +#define IF_OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:" + +#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) + +extern const struct option cf_options[]; + +struct if_options { + int metric; + uint8_t requestmask[256 / 8]; + uint8_t requiremask[256 / 8]; + uint8_t nomask[256 / 8]; + 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 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; +}; + +struct if_options *read_config(const char *, const char *); +int add_options(struct if_options *, int, char **); +void free_options(struct if_options *); + +#endif diff --git a/ipv4ll.c b/ipv4ll.c new file mode 100644 index 00000000..3e8786f0 --- /dev/null +++ b/ipv4ll.c @@ -0,0 +1,124 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "arp.h" +#include "common.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if-options.h" +#include "ipv4ll.h" +#include "logger.h" +#include "net.h" + +static struct dhcp_message* +make_ipv4ll_lease(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; +} + +void +start_ipv4ll(struct interface *iface) +{ + iface->state->probes = 0; + iface->state->claims = 0; + if (iface->addr.s_addr) { + iface->state->conflicts = 0; + if (IN_LINKLOCAL(htonl(iface->addr.s_addr))) { + send_arp_announce(iface); + return; + } + } + + logger(LOG_INFO, "%s: probing for an IPv4LL address", iface->name); + delete_timeout(NULL, iface); + iface->state->state = DHS_PROBING; + free(iface->state->offer); + iface->state->offer = make_ipv4ll_lease(0); + send_arp_probe(iface); +} + +void +handle_ipv4ll_failure(struct interface *iface) +{ + time_t up; + + if (iface->state->fail.s_addr == iface->state->lease.addr.s_addr) { + if (iface->state->state == DHS_PROBING) + drop_config(iface, "EXPIRE"); + else { + up = uptime(); + if (iface->state->defend + DEFEND_INTERVAL > up) { + drop_config(iface, "EXPIRE"); + iface->state->conflicts = -1; + } else { + iface->state->defend = up; + return; + } + } + } + + close_sockets(iface); + if (++iface->state->conflicts > MAX_CONFLICTS) { + logger(LOG_ERR, "%s: failed to acquire an IPv4LL address", + iface->name); + iface->state->interval = RATE_LIMIT_INTERVAL / 2; + start_discover(iface); + } else + add_timeout_sec(PROBE_WAIT, start_ipv4ll, iface); +} diff --git a/ipv4ll.h b/ipv4ll.h new file mode 100644 index 00000000..82f8d59f --- /dev/null +++ b/ipv4ll.h @@ -0,0 +1,35 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV4LL_H +#define IPV4LL_H + +#include "net.h" + +void start_ipv4ll(struct interface *); +void handle_ipv4ll_failure(struct interface *); +#endif diff --git a/logger.c b/logger.c index 15c6cf7b..3e138d7c 100644 --- a/logger.c +++ b/logger.c @@ -36,7 +36,6 @@ #include "logger.h" static int loglevel = LOG_INFO; -static char logprefix[12] = {0}; void setloglevel(int level) @@ -44,46 +43,20 @@ 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); - } - } - + if (level < LOG_DEBUG || level <= loglevel) + vsyslog(level, fmt, p2); va_end(p2); va_end(p); } diff --git a/logger.h b/logger.h index 8ac76b62..13f53728 100644 --- a/logger.h +++ b/logger.h @@ -37,7 +37,6 @@ #include void setloglevel(int); -void setlogprefix(const char *); void logger(int, const char *, ...) _PRINTF_LIKE (2, 3); #endif diff --git a/mk/prog.mk b/mk/prog.mk index d970b2d6..b6e3efc1 100644 --- a/mk/prog.mk +++ b/mk/prog.mk @@ -36,14 +36,6 @@ all: ${PROG} ${SCRIPTS} _man ${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} diff --git a/net.c b/net.c index fa5af103..10612961 100644 --- a/net.c +++ b/net.c @@ -35,6 +35,7 @@ #include #include #ifdef __linux__ +#include #include #include #endif @@ -63,6 +64,7 @@ #include "common.h" #include "dhcp.h" #include "logger.h" +#include "if-options.h" #include "net.h" #include "signals.h" @@ -176,10 +178,119 @@ hwaddr_aton(unsigned char *buffer, const char *addr) return len; } +struct interface * +init_interface(const char *ifname) +{ + int s, arpable; + struct ifreq ifr; + struct interface *iface = NULL; +#ifdef SIOCGIWNAME + struct iwreq iwr; +#endif + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return NULL; + + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) + goto eexit; + if (ifr.ifr_flags & IFF_LOOPBACK || ifr.ifr_flags & IFF_POINTOPOINT) + goto eexit; + arpable = !(ifr.ifr_flags & IFF_NOARP); + + iface = xzalloc(sizeof(*iface)); + strlcpy(iface->name, ifname, sizeof(iface->name)); + iface->metric = 100 + if_nametoindex(iface->name); + +#ifdef SIOCGIFHWADDR + 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: + iface->hwlen = ETHER_ADDR_LEN; + break; + case ARPHRD_IEEE1394: + iface->hwlen = EUI64_ADDR_LEN; + case ARPHRD_INFINIBAND: + iface->hwlen = INFINIBAND_ADDR_LEN; + break; + default: + logger(LOG_ERR, "%s: unsupported media family", iface->name); + goto eexit; + } + memcpy(iface->hwaddr, ifr.ifr_hwaddr.sa_data, iface->hwlen); + iface->family = ifr.ifr_hwaddr.sa_family; + +#else + iface->family = ARPHRD_ETHER; +#endif + +#ifdef SIOCGIWNAME + memset(&iwr, 0, sizeof(iwr)); + strlcpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name)); + /* Check for wireless */ + if (ioctl(s, SIOCGIWNAME, &iwr) != -1) + iface->metric += 100; +#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; + + snprintf(iface->leasefile, PATH_MAX, LEASEFILE, ifname); + iface->arpable = arpable; + + /* 0 is a valid fd, so init to -1 */ + iface->raw_fd = -1; + iface->udp_fd = -1; + iface->arp_fd = -1; + close(s); + return iface; + +eexit: + free(iface); + close(s); + return NULL; +} + +void +free_interface(struct interface *iface) +{ + if (!iface) + return; + if (iface->state) { + free_options(iface->state->options); + free(iface->state->old); + free(iface->state->new); + free(iface->state->offer); + free(iface->state); + } + free(iface->clientid); + free(iface); +} + int do_interface(const char *ifname, - _unused unsigned char *hwaddr, _unused size_t *hwlen, - struct in_addr *addr, struct in_addr *net, int get) + _unused struct interface **ifaces, + _unused int argc, _unused char * const *argv, + struct in_addr *addr, struct in_addr *net, int act) { int s; struct ifconf ifc; @@ -195,7 +306,9 @@ do_interface(const char *ifname, struct ifreq *ifr; struct sockaddr_in netmask; #ifdef AF_LINK + int n; struct sockaddr_dl *sdl; + struct interface *ifp, *ifl = NULL, *ifn = NULL; #endif if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) @@ -237,29 +350,49 @@ do_interface(const char *ifname, #endif p += sizeof(*ifr); - if (strcmp(ifname, ifr->ifr_name) != 0) + if (ifname && strcmp(ifname, ifr->ifr_name) != 0) continue; found = 1; #ifdef AF_LINK - if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) { + /* Interface discovery for BSD's */ + if (act == 2 && ifr->ifr_addr.sa_family == AF_LINK) { + for (ifp = *ifaces; ifp; ifp = ifp->next) { + ifl = ifp; + if (strcmp(ifp->name, ifr->ifr_name) == 0) + break; + } + if (ifp) + continue; + if (argc > 0) { + for (n = 0; n < argc; n++) + if (strcmp(ifr->ifr_name, argv[n]) == 0) + break; + if (n == argc) + continue; + } + ifn = init_interface(ifr->ifr_name); + if (!ifn) + continue; + if (ifl) + ifp = ifl->next = ifn; + else + ifp = *ifaces = ifn; 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); + ifp->hwlen = sdl->sdl_alen; + memcpy(ifp->hwaddr, LLADDR(sdl), sdl->sdl_alen); free(sdl); - retval = 1; - break; } #endif - if (ifr->ifr_addr.sa_family == AF_INET) { + if (ifr->ifr_addr.sa_family == AF_INET && addr) { memcpy(&address, &ifr->ifr_addr, sizeof(address)); if (ioctl(s, SIOCGIFNETMASK, ifr) == -1) continue; memcpy(&netmask, &ifr->ifr_addr, sizeof(netmask)); - if (get) { + if (act == 1) { addr->s_addr = address.sin_addr.s_addr; net->s_addr = netmask.sin_addr.s_addr; retval = 1; @@ -361,90 +494,6 @@ carrier_status(const char *ifname) 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) diff --git a/net.h b/net.h index 3ade0257..fd96f692 100644 --- a/net.h +++ b/net.h @@ -39,6 +39,8 @@ #include #include "config.h" +#include "dhcp.h" +#include "dhcpcd.h" #ifndef DUID_LEN # define DUID_LEN 128 + 2 @@ -57,7 +59,6 @@ # define ARPHRD_INFINIBAND 32 #endif -#define HWADDR_LEN 20 /* Work out if we have a private address or not * 10/8 @@ -93,36 +94,13 @@ struct rt { 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); +struct interface *init_interface(const char *); +struct interface *discover_interfaces(int, char * const *); +void free_interface(struct interface *); int do_mtu(const char *, short int); #define get_mtu(iface) do_mtu(iface, 0) #define set_mtu(iface, mtu) do_mtu(iface, mtu) @@ -131,27 +109,29 @@ 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 *, +int do_interface(const char *, struct interface **, + int argc, char * const *argv, struct in_addr *, struct in_addr *, int); -int if_address(const char *, const struct in_addr *, const struct in_addr *, +int if_address(const struct interface *, + 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) +#define add_address(iface, addr, net, brd) \ + if_address(iface, addr, net, brd, 1) +#define del_address(iface, addr, net) \ + if_address(iface, addr, net, NULL, -1) +#define has_address(iface, addr, net) \ + do_interface(iface, NULL, 0, NULL, addr, net, 0) +#define get_address(iface, addr, net) \ + do_interface(iface, NULL, 0, NULL, addr, net, 1) + +int if_route(const struct interface *, const struct in_addr *, + const struct in_addr *, const struct in_addr *, int, int); +#define add_route(iface, dest, mask, gate, metric) \ + if_route(iface, dest, mask, gate, metric, 1) +#define change_route(iface, dest, mask, gate, metric) \ + if_route(iface, dest, mask, gate, metric, 0) +#define del_route(iface, dest, mask, gate, metric) \ + if_route(iface, dest, mask, gate, metric, -1) void free_routes(struct rt *); int open_udp_socket(struct interface *); @@ -170,7 +150,7 @@ 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 open_link_socket(void); +int link_changed(int, const struct interface *); int carrier_status(const char *); #endif diff --git a/signals.c b/signals.c index 58679d63..d98a2dcc 100644 --- a/signals.c +++ b/signals.c @@ -55,12 +55,6 @@ signal_handler(int sig) 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 */ @@ -93,7 +87,7 @@ signal_init(void) return -1; if (set_cloexec(signal_pipe[1]) == -1) return -1; - return 0; + return signal_pipe[0]; } static int @@ -123,3 +117,4 @@ signal_reset(void) { return signal_handle(SIG_DFL); } + diff --git a/signals.h b/signals.h index f784a68a..76b13f52 100644 --- a/signals.h +++ b/signals.h @@ -31,7 +31,6 @@ int signal_init(void); int signal_setup(void); int signal_reset(void); -int signal_fd(void); int signal_read(void); #endif