From: Roy Marples Date: Sat, 2 Feb 2013 14:05:55 +0000 (+0000) Subject: Remove IPv4 and DHCP4 specific setup from dhcpcd.c and move into ipv4.c X-Git-Tag: v5.99.6~83 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=294eff4df4239e12befe13fcbeecfcb9c5f34e75;p=thirdparty%2Fdhcpcd.git Remove IPv4 and DHCP4 specific setup from dhcpcd.c and move into ipv4.c and dhcp.c Split configure.c into script.c and move the rest into dhcp.c This starts the goal of making the base of dhcpcd protocol agnostic and working towards building IPv4 and/or IPv6 code only to reduce size and allow growing any future new protocol easier. --- diff --git a/Makefile b/Makefile index 0a478804..b811d925 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ # dhcpcd Makefile PROG= dhcpcd -SRCS= arp.c bind.c common.c control.c dhcp.c dhcpcd.c duid.c eloop.c -SRCS+= configure.c if-options.c if-pref.c ipv4ll.c net.c signals.c +SRCS= arp.c common.c control.c dhcpcd.c duid.c eloop.c +SRCS+= if-options.c if-pref.c net.c script.c signals.c +SRCS+= dhcp-common.c +SRCS+= ipv4.c dhcp.c ipv4ll.c SRCS+= ipv6.c ipv6rs.c ipv6ns.c dhcp6.c CFLAGS?= -O2 diff --git a/arp.c b/arp.c index 6c35594a..652a2a7f 100644 --- a/arp.c +++ b/arp.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -33,8 +33,9 @@ #include #include "arp.h" -#include "bind.h" +#include "ipv4.h" #include "common.h" +#include "dhcp.h" #include "dhcpcd.h" #include "eloop.h" #include "if-options.h" @@ -45,7 +46,7 @@ (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) static int -send_arp(const struct interface *ifp, int op, in_addr_t sip, in_addr_t tip) +arp_send(const struct interface *ifp, int op, in_addr_t sip, in_addr_t tip) { uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; @@ -76,7 +77,7 @@ send_arp(const struct interface *ifp, int op, in_addr_t sip, in_addr_t tip) } static void -handle_arp_failure(struct interface *ifp) +arp_failure(struct interface *ifp) { /* If we failed without a magic cookie then we need to try @@ -92,8 +93,8 @@ handle_arp_failure(struct interface *ifp) unlink(ifp->leasefile); if (!ifp->state->lease.frominfo) - send_decline(ifp); - close_sockets(ifp); + dhcp_decline(ifp); + dhcp_close(ifp); eloop_timeout_delete(NULL, ifp); if (ifp->state->lease.frominfo) start_interface(ifp); @@ -102,7 +103,7 @@ handle_arp_failure(struct interface *ifp) } static void -handle_arp_packet(void *arg) +arp_packet(void *arg) { struct interface *ifp = arg; uint8_t arp_buffer[ARP_LEN]; @@ -166,7 +167,7 @@ handle_arp_packet(void *arg) if (select_profile(ifp, hwaddr) == -1 && errno == ENOENT) select_profile(ifp, inet_ntoa(ina)); - close_sockets(ifp); + dhcp_close(ifp); eloop_timeout_delete(NULL, ifp); start_interface(ifp); return; @@ -191,14 +192,14 @@ handle_arp_packet(void *arg) (size_t)ar.ar_hln), inet_ntoa(state->fail)); errno = EEXIST; - handle_arp_failure(ifp); + arp_failure(ifp); return; } } } void -send_arp_announce(void *arg) +arp_announce(void *arg) { struct interface *ifp = arg; struct if_state *state = ifp->state; @@ -208,7 +209,7 @@ send_arp_announce(void *arg) return; if (ifp->arp_fd == -1) { open_socket(ifp, ETHERTYPE_ARP); - eloop_event_add(ifp->arp_fd, handle_arp_packet, ifp); + eloop_event_add(ifp->arp_fd, arp_packet, ifp); } if (++state->claims < ANNOUNCE_NUM) syslog(LOG_DEBUG, @@ -219,11 +220,11 @@ send_arp_announce(void *arg) syslog(LOG_DEBUG, "%s: sending ARP announce (%d of %d)", ifp->name, state->claims, ANNOUNCE_NUM); - if (send_arp(ifp, ARPOP_REQUEST, + if (arp_send(ifp, ARPOP_REQUEST, state->new->yiaddr, state->new->yiaddr) == -1) syslog(LOG_ERR, "send_arp: %m"); if (state->claims < ANNOUNCE_NUM) { - eloop_timeout_add_sec(ANNOUNCE_WAIT, send_arp_announce, ifp); + eloop_timeout_add_sec(ANNOUNCE_WAIT, arp_announce, ifp); return; } if (state->new->cookie != htonl(MAGIC_COOKIE)) { @@ -236,7 +237,7 @@ send_arp_announce(void *arg) tv.tv_sec = state->interval - DHCP_RAND_MIN; tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); timernorm(&tv); - eloop_timeout_add_tv(&tv, start_discover, ifp); + eloop_timeout_add_tv(&tv, dhcp_discover, ifp); } else { eloop_event_delete(ifp->arp_fd); close(ifp->arp_fd); @@ -245,7 +246,7 @@ send_arp_announce(void *arg) } void -send_arp_probe(void *arg) +arp_probe(void *arg) { struct interface *ifp = arg; struct if_state *state = ifp->state; @@ -266,7 +267,7 @@ send_arp_probe(void *arg) if (ifp->arp_fd == -1) { open_socket(ifp, ETHERTYPE_ARP); - eloop_event_add(ifp->arp_fd, handle_arp_packet, ifp); + eloop_event_add(ifp->arp_fd, arp_packet, ifp); } if (state->probes == 0) { if (arping) @@ -280,32 +281,32 @@ send_arp_probe(void *arg) tv.tv_sec = PROBE_MIN; tv.tv_usec = arc4random() % (PROBE_MAX_U - PROBE_MIN_U); timernorm(&tv); - eloop_timeout_add_tv(&tv, send_arp_probe, ifp); + eloop_timeout_add_tv(&tv, arp_probe, ifp); } else { tv.tv_sec = ANNOUNCE_WAIT; tv.tv_usec = 0; if (arping) { state->probes = 0; if (++state->arping_index < state->options->arping_len) - eloop_timeout_add_tv(&tv, send_arp_probe, ifp); + eloop_timeout_add_tv(&tv, arp_probe, ifp); else eloop_timeout_add_tv(&tv, start_interface, ifp); } else - eloop_timeout_add_tv(&tv, bind_interface, ifp); + eloop_timeout_add_tv(&tv, dhcp_bind, ifp); } syslog(LOG_DEBUG, "%s: sending ARP probe (%d of %d), next in %0.2f seconds", ifp->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM, timeval_to_double(&tv)); - if (send_arp(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1) + if (arp_send(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1) syslog(LOG_ERR, "send_arp: %m"); } void -start_arping(struct interface *ifp) +arp_start(struct interface *ifp) { ifp->state->probes = 0; ifp->state->arping_index = 0; - send_arp_probe(ifp); + arp_probe(ifp); } diff --git a/arp.h b/arp.h index b97c38bd..3274bbb3 100644 --- a/arp.h +++ b/arp.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2008 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ #include "dhcpcd.h" -void send_arp_announce(void *); -void send_arp_probe(void *); -void start_arping(struct interface *); +void arp_announce(void *); +void arp_probe(void *); +void arp_start(struct interface *); #endif diff --git a/bind.c b/bind.c deleted file mode 100644 index f6228923..00000000 --- a/bind.c +++ /dev/null @@ -1,231 +0,0 @@ -/* - * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples - * All rights reserved - - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include -#include - -#include -#ifdef BSD -# include -#endif -#include -#include -#include -#include - -#include "arp.h" -#include "bind.h" -#include "common.h" -#include "configure.h" -#include "dhcpcd.h" -#include "eloop.h" -#include "if-options.h" -#include "net.h" -#include "signals.h" - -#ifndef _PATH_DEVNULL -# define _PATH_DEVNULL "/dev/null" -#endif - -/* We do things after aquiring the lease, so ensure we have enough time for them */ -#define DHCP_MIN_LEASE 20 - -#ifndef THERE_IS_NO_FORK -pid_t -daemonise(void) -{ - pid_t pid; - char buf = '\0'; - int sidpipe[2], fd; - - eloop_timeout_delete(handle_exit_timeout, NULL); - if (options & DHCPCD_DAEMONISED || !(options & DHCPCD_DAEMONISE)) - return 0; - /* Setup a signal pipe so parent knows when to exit. */ - if (pipe(sidpipe) == -1) { - syslog(LOG_ERR, "pipe: %m"); - return -1; - } - syslog(LOG_DEBUG, "forking to background"); - switch (pid = fork()) { - case -1: - syslog(LOG_ERR, "fork: %m"); - exit(EXIT_FAILURE); - /* NOTREACHED */ - case 0: - setsid(); - /* Notify parent it's safe to exit as we've detached. */ - close(sidpipe[0]); - if (write(sidpipe[1], &buf, 1) == -1) - syslog(LOG_ERR, "failed to notify parent: %m"); - close(sidpipe[1]); - if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - close(fd); - } - break; - default: - /* Wait for child to detach */ - close(sidpipe[1]); - if (read(sidpipe[0], &buf, 1) == -1) - syslog(LOG_ERR, "failed to read child: %m"); - close(sidpipe[0]); - break; - } - /* Done with the fd now */ - if (pid != 0) { - syslog(LOG_INFO, "forked to background, child pid %d",pid); - writepid(pidfd, pid); - close(pidfd); - pidfd = -1; - options |= DHCPCD_FORKED; - exit(EXIT_SUCCESS); - } - options |= DHCPCD_DAEMONISED; - return pid; -} -#endif - -void -bind_interface(void *arg) -{ - struct interface *iface = arg; - struct if_state *state = iface->state; - struct if_options *ifo = state->options; - struct dhcp_lease *lease = &state->lease; - struct timeval tv; - - /* We're binding an address now - ensure that sockets are closed */ - close_sockets(iface); - state->reason = NULL; - if (clock_monotonic) - get_monotonic(&lease->boundtime); - state->xid = 0; - free(state->old); - state->old = state->new; - state->new = state->offer; - state->offer = NULL; - get_lease(lease, state->new); - if (ifo->options & DHCPCD_STATIC) { - syslog(LOG_INFO, "%s: using static address %s", - iface->name, inet_ntoa(lease->addr)); - lease->leasetime = ~0U; - lease->net.s_addr = ifo->req_mask.s_addr; - state->reason = "STATIC"; - } else if (state->new->cookie != htonl(MAGIC_COOKIE)) { - syslog(LOG_INFO, "%s: using IPv4LL address %s", - iface->name, inet_ntoa(lease->addr)); - lease->leasetime = ~0U; - state->reason = "IPV4LL"; - } else if (ifo->options & DHCPCD_INFORM) { - if (ifo->req_addr.s_addr != 0) - lease->addr.s_addr = ifo->req_addr.s_addr; - else - lease->addr.s_addr = iface->addr.s_addr; - syslog(LOG_INFO, "%s: received approval for %s", iface->name, - inet_ntoa(lease->addr)); - lease->leasetime = ~0U; - state->reason = "INFORM"; - } else { - if (gettimeofday(&tv, NULL) == 0) - lease->leasedfrom = tv.tv_sec; - else if (lease->frominfo) - state->reason = "TIMEOUT"; - if (lease->leasetime == ~0U) { - lease->renewaltime = - lease->rebindtime = - lease->leasetime; - syslog(LOG_INFO, "%s: leased %s for infinity", - iface->name, inet_ntoa(lease->addr)); - } else { - if (lease->leasetime < DHCP_MIN_LEASE) { - syslog(LOG_WARNING, - "%s: minimum lease is %d seconds", - iface->name, DHCP_MIN_LEASE); - lease->leasetime = DHCP_MIN_LEASE; - } - if (lease->rebindtime == 0) - lease->rebindtime = lease->leasetime * T2; - else if (lease->rebindtime >= lease->leasetime) { - lease->rebindtime = lease->leasetime * T2; - syslog(LOG_ERR, - "%s: rebind time greater than lease " - "time, forcing to %u seconds", - iface->name, lease->rebindtime); - } - if (lease->renewaltime == 0) - lease->renewaltime = lease->leasetime * T1; - else if (lease->renewaltime > lease->rebindtime) { - lease->renewaltime = lease->leasetime * T1; - syslog(LOG_ERR, - "%s: renewal time greater than rebind " - "time, forcing to %u seconds", - iface->name, lease->renewaltime); - } - syslog(LOG_INFO, - "%s: leased %s for %u seconds", iface->name, - inet_ntoa(lease->addr), lease->leasetime); - } - } - if (options & DHCPCD_TEST) { - state->reason = "TEST"; - run_script(iface); - exit(EXIT_SUCCESS); - } - if (state->reason == NULL) { - if (state->old) { - if (state->old->yiaddr == state->new->yiaddr && - lease->server.s_addr) - state->reason = "RENEW"; - else - state->reason = "REBIND"; - } else if (state->state == DHS_REBOOT) - state->reason = "REBOOT"; - else - state->reason = "BOUND"; - } - if (lease->leasetime == ~0U) - lease->renewaltime = lease->rebindtime = lease->leasetime; - else { - eloop_timeout_add_sec(lease->renewaltime, start_renew, iface); - eloop_timeout_add_sec(lease->rebindtime, start_rebind, iface); - eloop_timeout_add_sec(lease->leasetime, start_expire, iface); - syslog(LOG_DEBUG, - "%s: renew in %u seconds, rebind in %u seconds", - iface->name, lease->renewaltime, lease->rebindtime); - } - ifo->options &= ~ DHCPCD_CSR_WARNED; - configure(iface); - daemonise(); - state->state = DHS_BOUND; - if (ifo->options & DHCPCD_ARP) { - state->claims = 0; - send_arp_announce(iface); - } -} diff --git a/dhcp-common.c b/dhcp-common.c new file mode 100644 index 00000000..be2d0fac --- /dev/null +++ b/dhcp-common.c @@ -0,0 +1,382 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp-common.h" +#include "dhcp.h" + +int make_option_mask(const struct dhcp_opt *dopts, + uint8_t *mask, const char *opts, int add) +{ + char *token, *o, *p, *t; + const struct dhcp_opt *opt; + int match, n; + + o = p = xstrdup(opts); + while ((token = strsep(&p, ", "))) { + if (*token == '\0') + continue; + for (opt = dopts; opt->option; opt++) { + if (!opt->var) + continue; + match = 0; + if (strcmp(opt->var, token) == 0) + match = 1; + else { + errno = 0; + n = strtol(token, &t, 0); + if (errno == 0 && !*t) + if (opt->option == n) + match = 1; + } + if (match) { + if (add == 2 && !(opt->type & ADDRIPV4)) { + free(o); + errno = EINVAL; + return -1; + } + if (add == 1 || add == 2) + add_option_mask(mask, + opt->option); + else + del_option_mask(mask, + opt->option); + break; + } + } + if (!opt->option) { + free(o); + errno = ENOENT; + return -1; + } + } + free(o); + return 0; +} + +/* Decode an RFC3397 DNS search order option into a space + * separated string. Returns length of string (including + * terminating zero) or zero on error. out may be NULL + * to just determine output length. */ +ssize_t +decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const char *start; + ssize_t start_len; + const uint8_t *r, *q = p; + int count = 0, l, hops; + uint8_t ltype; + + start = out; + start_len = len; + while (q - p < pl) { + r = NULL; + hops = 0; + /* We check we are inside our length again incase + * the data is NOT terminated correctly. */ + while ((l = *q++) && q - p < pl) { + ltype = l & 0xc0; + if (ltype == 0x80 || ltype == 0x40) + return 0; + else if (ltype == 0xc0) { /* pointer */ + l = (l & 0x3f) << 8; + l |= *q++; + /* save source of first jump. */ + if (!r) + r = q; + hops++; + if (hops > 255) + return 0; + q = p + l; + if (q - p >= pl) + return 0; + } else { + /* straightforward name segment, add with '.' */ + count += l + 1; + if (out) { + if ((ssize_t)l + 1 > len) { + errno = ENOBUFS; + return -1; + } + memcpy(out, q, l); + out += l; + *out++ = '.'; + len -= l; + len--; + } + q += l; + } + } + /* change last dot to space */ + if (out && out != start) + *(out - 1) = ' '; + if (r) + q = r; + } + + /* change last space to zero terminator */ + if (out) { + if (out != start) + *(out - 1) = '\0'; + else if (start_len > 0) + *out = '\0'; + } + + return count; +} + +ssize_t +print_string(char *s, ssize_t len, int dl, const uint8_t *data) +{ + uint8_t c; + const uint8_t *e, *p; + ssize_t bytes = 0; + ssize_t r; + + e = data + dl; + while (data < e) { + c = *data++; + if (c == '\0') { + /* If rest is all NULL, skip it. */ + for (p = data; p < e; p++) + if (*p != '\0') + break; + if (p == e) + break; + } + if (!isascii(c) || !isprint(c)) { + if (s) { + if (len < 5) { + errno = ENOBUFS; + return -1; + } + r = snprintf(s, len, "\\%03o", c); + len -= r; + bytes += r; + s += r; + } else + bytes += 4; + continue; + } + switch (c) { + case '"': /* FALLTHROUGH */ + case '\'': /* FALLTHROUGH */ + case '$': /* FALLTHROUGH */ + case '`': /* FALLTHROUGH */ + case '\\': /* FALLTHROUGH */ + case '|': /* FALLTHROUGH */ + case '&': + if (s) { + if (len < 3) { + errno = ENOBUFS; + return -1; + } + *s++ = '\\'; + len--; + } + bytes++; + break; + } + if (s) { + *s++ = c; + len--; + } + bytes++; + } + + /* NULL */ + if (s) + *s = '\0'; + bytes++; + return bytes; +} + +ssize_t +print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data, + const char *ifname) +{ + const uint8_t *e, *t; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + struct in_addr addr; + ssize_t bytes = 0; + ssize_t l; + char *tmp; + + if (type & RFC3397) { + l = decode_rfc3397(NULL, 0, dl, data); + if (l < 1) + return l; + tmp = xmalloc(l); + decode_rfc3397(tmp, l, dl, data); + l = print_string(s, len, l - 1, (uint8_t *)tmp); + free(tmp); + return l; + } + + if (type & RFC3361) { + if ((tmp = decode_rfc3361(dl, data)) == NULL) + return -1; + l = strlen(tmp); + l = print_string(s, len, l - 1, (uint8_t *)tmp); + free(tmp); + return l; + } + + if (type & RFC3442) + return decode_rfc3442(s, len, dl, data); + + if (type & RFC5969) + return decode_rfc5969(s, len, dl, data); + + if (type & STRING) { + /* Some DHCP servers return NULL strings */ + if (*data == '\0') + return 0; + return print_string(s, len, dl, data); + } + + /* DHCPv6 status code */ + if (type & SCODE && dl >= (int)sizeof(u16)) { + if (s) { + memcpy(&u16, data, sizeof(u16)); + u16 = ntohs(u16); + l = snprintf(s, len, "%d ", u16); + len -= l; + } else + l = 7; + data += sizeof(u16); + dl -= sizeof(u16); + if (dl) + l += print_option(s, len, STRING, dl, data, ifname); + return l; + } + + if (!s) { + if (type & UINT8) + l = 3; + else if (type & UINT16) { + l = 5; + dl /= 2; + } else if (type & SINT16) { + l = 6; + dl /= 2; + } else if (type & UINT32) { + l = 10; + dl /= 4; + } else if (type & SINT32) { + l = 11; + dl /= 4; + } else if (type & ADDRIPV4) { + l = 16; + dl /= 4; + } else if (type & ADDRIPV6) { + e = data + dl; + l = 0; + while (data < e) { + if (l) + l++; /* space */ + dl = ipv6_printaddr(NULL, 0, data, ifname); + if (dl != -1) + l += dl; + data += 16; + } + return l + 1; + } else if (type & BINHEX) { + l = 2; + } else { + errno = EINVAL; + return -1; + } + return (l + 1) * dl; + } + + t = data; + e = data + dl; + while (data < e) { + if (data != t && type != BINHEX) { + *s++ = ' '; + bytes++; + len--; + } + if (type & UINT8) { + l = snprintf(s, len, "%d", *data); + data++; + } else if (type & UINT16) { + memcpy(&u16, data, sizeof(u16)); + u16 = ntohs(u16); + l = snprintf(s, len, "%d", u16); + data += sizeof(u16); + } else if (type & SINT16) { + memcpy(&s16, data, sizeof(s16)); + s16 = ntohs(s16); + l = snprintf(s, len, "%d", s16); + data += sizeof(s16); + } else if (type & UINT32) { + memcpy(&u32, data, sizeof(u32)); + u32 = ntohl(u32); + l = snprintf(s, len, "%d", u32); + data += sizeof(u32); + } else if (type & SINT32) { + memcpy(&s32, data, sizeof(s32)); + s32 = ntohl(s32); + l = snprintf(s, len, "%d", s32); + data += sizeof(s32); + } else if (type & ADDRIPV4) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + l = snprintf(s, len, "%s", inet_ntoa(addr)); + data += sizeof(addr.s_addr); + } else if (type & ADDRIPV6) { + dl = ipv6_printaddr(s, len, data, ifname); + if (dl != -1) + l = dl; + else + l = 0; + data += 16; + } else if (type & BINHEX) { + l = snprintf(s, len, "%.2x", data[0]); + data++; + } else + l = 0; + len -= l; + bytes += l; + s += l; + } + + return bytes; +} diff --git a/dhcp-common.h b/dhcp-common.h new file mode 100644 index 00000000..48dcc145 --- /dev/null +++ b/dhcp-common.h @@ -0,0 +1,74 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef DHCPCOMMON_H +#define DHCPCOMMON_H + +#include +#include + +#include + +#include "common.h" + +/* Max MTU - defines dhcp option length */ +#define MTU_MAX 1500 +#define MTU_MIN 576 + +#define REQUEST (1 << 0) +#define UINT8 (1 << 1) +#define UINT16 (1 << 2) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define ADDRIPV4 (1 << 6) +#define STRING (1 << 7) +#define PAIR (1 << 8) +#define ARRAY (1 << 9) +#define RFC3361 (1 << 10) +#define RFC3397 (1 << 11) +#define RFC3442 (1 << 12) +#define RFC5969 (1 << 13) +#define ADDRIPV6 (1 << 14) +#define BINHEX (1 << 15) +#define SCODE (1 << 16) + +struct dhcp_opt { + uint16_t option; + int type; + const char *var; +}; + +#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(const struct dhcp_opt *,uint8_t *, const char *, int); +ssize_t decode_rfc3397(char *, ssize_t, int, const uint8_t *); +ssize_t print_string(char *, ssize_t, int, const uint8_t *); +ssize_t print_option(char *, ssize_t, int, int, const uint8_t *, const char *); + +#endif diff --git a/dhcp.c b/dhcp.c index 385bfb44..bd78caf2 100644 --- a/dhcp.c +++ b/dhcp.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,6 +25,15 @@ * SUCH DAMAGE. */ +#include +#include +#include + +#ifdef __linux__ +# include /* for systems with broken headers */ +# include +#endif + #include #include #include @@ -33,56 +42,93 @@ #include #include +#include "arp.h" #include "config.h" #include "common.h" #include "dhcp.h" +#include "dhcp-common.h" +#include "eloop.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "script.h" + +#define DAD "Duplicate address detected" +#define DHCP_MIN_LEASE 20 -#define DAD "Duplicate address detected" +static uint8_t *packet; /* Our aggregate option buffer. * We ONLY use this when options are split, which for most purposes is * practically never. See RFC3396 for details. */ static uint8_t *opt_buffer; +#define IPV4A ADDRIPV4 | ARRAY +#define IPV4R ADDRIPV4 | REQUEST + +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 + +/* Wait N nanoseconds between sending a RELEASE and dropping the address. + * This gives the kernel enough time to actually send it. */ +#define RELEASE_DELAY_S 0 +#define RELEASE_DELAY_NS 10000000 + +struct dhcp_op { + uint8_t value; + const char *name; +}; + +static const struct dhcp_op dhcp_ops[] = { + { DHCP_DISCOVER, "DISCOVER" }, + { DHCP_OFFER, "OFFER" }, + { DHCP_REQUEST, "REQUEST" }, + { DHCP_DECLINE, "DECLINE" }, + { DHCP_ACK, "ACK" }, + { DHCP_NAK, "NAK" }, + { DHCP_RELEASE, "RELEASE" }, + { DHCP_INFORM, "INFORM" }, + { 0, NULL } +}; + const struct dhcp_opt const dhcp_opts[] = { - { 1, IPV4 | REQUEST, "subnet_mask" }, + { 1, ADDRIPV4 | REQUEST, "subnet_mask" }, /* RFC 3442 states that the CSR has to come before all other * routes. For completeness, we also specify static routes, * then routers. */ { 121, RFC3442, "classless_static_routes" }, { 249, RFC3442, "ms_classless_static_routes" }, - { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, - { 3, IPV4 | ARRAY | REQUEST, "routers" }, + { 33, IPV4A | REQUEST, "static_routes" }, + { 3, IPV4A | REQUEST, "routers" }, { 2, UINT32, "time_offset" }, - { 4, IPV4 | ARRAY, "time_servers" }, - { 5, IPV4 | ARRAY, "ien116_name_servers" }, - { 6, IPV4 | ARRAY, "domain_name_servers" }, - { 7, IPV4 | ARRAY, "log_servers" }, - { 8, IPV4 | ARRAY, "cookie_servers" }, - { 9, IPV4 | ARRAY, "lpr_servers" }, - { 10, IPV4 | ARRAY, "impress_servers" }, - { 11, IPV4 | ARRAY, "resource_location_servers" }, + { 4, IPV4A, "time_servers" }, + { 5, IPV4A, "ien116_name_servers" }, + { 6, IPV4A, "domain_name_servers" }, + { 7, IPV4A, "log_servers" }, + { 8, IPV4A, "cookie_servers" }, + { 9, IPV4A, "lpr_servers" }, + { 10, IPV4A, "impress_servers" }, + { 11, IPV4A, "resource_location_servers" }, { 12, STRING, "host_name" }, { 13, UINT16, "boot_size" }, { 14, STRING, "merit_dump" }, { 15, STRING, "domain_name" }, - { 16, IPV4, "swap_server" }, + { 16, ADDRIPV4, "swap_server" }, { 17, STRING, "root_path" }, { 18, STRING, "extensions_path" }, { 19, UINT8, "ip_forwarding" }, { 20, UINT8, "non_local_source_routing" }, - { 21, IPV4 | ARRAY, "policy_filter" }, + { 21, IPV4A, "policy_filter" }, { 22, SINT16, "max_dgram_reassembly" }, { 23, UINT16, "default_ip_ttl" }, { 24, UINT32, "path_mtu_aging_timeout" }, { 25, UINT16 | ARRAY, "path_mtu_plateau_table" }, { 26, UINT16, "interface_mtu" }, { 27, UINT8, "all_subnets_local" }, - { 28, IPV4 | REQUEST, "broadcast_address" }, + { 28, ADDRIPV4 | REQUEST, "broadcast_address" }, { 29, UINT8, "perform_mask_discovery" }, { 30, UINT8, "mask_supplier" }, { 31, UINT8, "router_discovery" }, - { 32, IPV4, "router_solicitation_address" }, + { 32, ADDRIPV4, "router_solicitation_address" }, { 34, UINT8, "trailer_encapsulation" }, { 35, UINT32, "arp_cache_timeout" }, { 36, UINT16, "ieee802_3_encapsulation" }, @@ -90,52 +136,52 @@ const struct dhcp_opt const dhcp_opts[] = { { 38, UINT32, "tcp_keepalive_interval" }, { 39, UINT8, "tcp_keepalive_garbage" }, { 40, STRING, "nis_domain" }, - { 41, IPV4 | ARRAY, "nis_servers" }, - { 42, IPV4 | ARRAY, "ntp_servers" }, + { 41, IPV4A, "nis_servers" }, + { 42, IPV4A, "ntp_servers" }, { 43, STRING, "vendor_encapsulated_options" }, - { 44, IPV4 | ARRAY, "netbios_name_servers" }, - { 45, IPV4, "netbios_dd_server" }, + { 44, IPV4A, "netbios_name_servers" }, + { 45, ADDRIPV4, "netbios_dd_server" }, { 46, UINT8, "netbios_node_type" }, { 47, STRING, "netbios_scope" }, - { 48, IPV4 | ARRAY, "font_servers" }, - { 49, IPV4 | ARRAY, "x_display_manager" }, - { 50, IPV4, "dhcp_requested_address" }, + { 48, IPV4A, "font_servers" }, + { 49, IPV4A, "x_display_manager" }, + { 50, ADDRIPV4, "dhcp_requested_address" }, { 51, UINT32 | REQUEST, "dhcp_lease_time" }, { 52, UINT8, "dhcp_option_overload" }, { 53, UINT8, "dhcp_message_type" }, - { 54, IPV4, "dhcp_server_identifier" }, + { 54, ADDRIPV4, "dhcp_server_identifier" }, { 55, UINT8 | ARRAY, "dhcp_parameter_request_list" }, { 56, STRING, "dhcp_message" }, { 57, UINT16, "dhcp_max_message_size" }, { 58, UINT32 | REQUEST, "dhcp_renewal_time" }, { 59, UINT32 | REQUEST, "dhcp_rebinding_time" }, { 64, STRING, "nisplus_domain" }, - { 65, IPV4 | ARRAY, "nisplus_servers" }, + { 65, IPV4A, "nisplus_servers" }, { 66, STRING, "tftp_server_name" }, { 67, STRING, "bootfile_name" }, - { 68, IPV4 | ARRAY, "mobile_ip_home_agent" }, - { 69, IPV4 | ARRAY, "smtp_server" }, - { 70, IPV4 | ARRAY, "pop_server" }, - { 71, IPV4 | ARRAY, "nntp_server" }, - { 72, IPV4 | ARRAY, "www_server" }, - { 73, IPV4 | ARRAY, "finger_server" }, - { 74, IPV4 | ARRAY, "irc_server" }, - { 75, IPV4 | ARRAY, "streettalk_server" }, - { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, + { 68, IPV4A, "mobile_ip_home_agent" }, + { 69, IPV4A, "smtp_server" }, + { 70, IPV4A, "pop_server" }, + { 71, IPV4A, "nntp_server" }, + { 72, IPV4A, "www_server" }, + { 73, IPV4A, "finger_server" }, + { 74, IPV4A, "irc_server" }, + { 75, IPV4A, "streettalk_server" }, + { 76, IPV4A, "streettalk_directory_assistance_server" }, { 77, STRING, "user_class" }, { 81, STRING | RFC3397, "fqdn_name" }, - { 85, IPV4 | ARRAY, "nds_servers" }, + { 85, IPV4A, "nds_servers" }, { 86, STRING, "nds_tree_name" }, { 87, STRING, "nds_context" }, { 88, STRING | RFC3397, "bcms_controller_names" }, - { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 89, IPV4A, "bcms_controller_address" }, { 91, UINT32, "client_last_transaction_time" }, - { 92, IPV4 | ARRAY, "associated_ip" }, + { 92, IPV4A, "associated_ip" }, { 98, STRING, "uap_servers" }, - { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 112, IPV4A, "netinfo_server_address" }, { 113, STRING, "netinfo_server_tag" }, { 114, STRING, "default_url" }, - { 118, IPV4, "subnet_selection" }, + { 118, ADDRIPV4, "subnet_selection" }, { 119, STRING | RFC3397, "domain_search" }, { 120, STRING | RFC3361, "sip_server" }, { 212, RFC5969, "sixrd" }, @@ -151,6 +197,8 @@ static const char *dhcp_params[] = { NULL }; +static int dhcp_open(struct interface *); + void print_options(void) { @@ -165,55 +213,6 @@ print_options(void) printf("%03d %s\n", opt->option, opt->var); } -int make_option_mask(const struct dhcp_opt *dopts, - uint8_t *mask, const char *opts, int add) -{ - char *token, *o, *p, *t; - const struct dhcp_opt *opt; - int match, n; - - o = p = xstrdup(opts); - while ((token = strsep(&p, ", "))) { - if (*token == '\0') - continue; - for (opt = dopts; opt->option; opt++) { - if (!opt->var) - continue; - match = 0; - if (strcmp(opt->var, token) == 0) - match = 1; - else { - errno = 0; - n = strtol(token, &t, 0); - if (errno == 0 && !*t) - if (opt->option == n) - match = 1; - } - if (match) { - if (add == 2 && !(opt->type & IPV4)) { - free(o); - errno = EINVAL; - return -1; - } - if (add == 1 || add == 2) - add_option_mask(mask, - opt->option); - else - del_option_mask(mask, - opt->option); - break; - } - } - if (!opt->option) { - free(o); - errno = ENOENT; - return -1; - } - } - free(o); - return 0; -} - static int validate_length(uint8_t option, int dl, int *type) { @@ -234,14 +233,14 @@ validate_length(uint8_t option, int dl, int *type) opt->type & (STRING | RFC3442 | RFC5969)) return dl; - if (opt->type & IPV4 && opt->type & ARRAY) { + if (opt->type & ADDRIPV4 && opt->type & ARRAY) { if (dl < (int)sizeof(uint32_t)) return -1; return dl - (dl % sizeof(uint32_t)); } sz = 0; - if (opt->type & (UINT32 | IPV4)) + if (opt->type & (UINT32 | ADDRIPV4)) sz = sizeof(uint32_t); if (opt->type & UINT16) sz = sizeof(uint16_t); @@ -394,78 +393,7 @@ get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) return 0; } -/* Decode an RFC3397 DNS search order option into a space - * separated string. Returns length of string (including - * terminating zero) or zero on error. out may be NULL - * to just determine output length. */ ssize_t -decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p) -{ - const char *start; - ssize_t start_len; - const uint8_t *r, *q = p; - int count = 0, l, hops; - uint8_t ltype; - - start = out; - start_len = len; - while (q - p < pl) { - r = NULL; - hops = 0; - /* We check we are inside our length again incase - * the data is NOT terminated correctly. */ - while ((l = *q++) && q - p < pl) { - ltype = l & 0xc0; - if (ltype == 0x80 || ltype == 0x40) - return 0; - else if (ltype == 0xc0) { /* pointer */ - l = (l & 0x3f) << 8; - l |= *q++; - /* save source of first jump. */ - if (!r) - r = q; - hops++; - if (hops > 255) - return 0; - q = p + l; - if (q - p >= pl) - return 0; - } else { - /* straightforward name segment, add with '.' */ - count += l + 1; - if (out) { - if ((ssize_t)l + 1 > len) { - errno = ENOBUFS; - return -1; - } - memcpy(out, q, l); - out += l; - *out++ = '.'; - len -= l; - len--; - } - q += l; - } - } - /* change last dot to space */ - if (out && out != start) - *(out - 1) = ' '; - if (r) - q = r; - } - - /* change last space to zero terminator */ - if (out) { - if (out != start) - *(out - 1) = '\0'; - else if (start_len > 0) - *out = '\0'; - } - - return count; -} - -static ssize_t decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) { const uint8_t *e; @@ -571,7 +499,7 @@ decode_rfc3442_rt(int dl, const uint8_t *data) return routes; } -static char * +char * decode_rfc3361(int dl, const uint8_t *data) { uint8_t enc; @@ -621,7 +549,7 @@ decode_rfc3361(int dl, const uint8_t *data) /* Decode an RFC5969 6rd order option into a space * separated string. Returns length of string (including * terminating zero) or zero on error. */ -static ssize_t +ssize_t decode_rfc5969(char *out, ssize_t len, int pl, const uint8_t *p) { uint8_t ipv4masklen, ipv6prefixlen; @@ -1164,232 +1092,6 @@ read_lease(const struct interface *iface) return dhcp; } -ssize_t -print_string(char *s, ssize_t len, int dl, const uint8_t *data) -{ - uint8_t c; - const uint8_t *e, *p; - ssize_t bytes = 0; - ssize_t r; - - e = data + dl; - while (data < e) { - c = *data++; - if (c == '\0') { - /* If rest is all NULL, skip it. */ - for (p = data; p < e; p++) - if (*p != '\0') - break; - if (p == e) - break; - } - if (!isascii(c) || !isprint(c)) { - if (s) { - if (len < 5) { - errno = ENOBUFS; - return -1; - } - r = snprintf(s, len, "\\%03o", c); - len -= r; - bytes += r; - s += r; - } else - bytes += 4; - continue; - } - switch (c) { - case '"': /* FALLTHROUGH */ - case '\'': /* FALLTHROUGH */ - case '$': /* FALLTHROUGH */ - case '`': /* FALLTHROUGH */ - case '\\': /* FALLTHROUGH */ - case '|': /* FALLTHROUGH */ - case '&': - if (s) { - if (len < 3) { - errno = ENOBUFS; - return -1; - } - *s++ = '\\'; - len--; - } - bytes++; - break; - } - if (s) { - *s++ = c; - len--; - } - bytes++; - } - - /* NULL */ - if (s) - *s = '\0'; - bytes++; - return bytes; -} - -ssize_t -print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data, - const char *ifname) -{ - const uint8_t *e, *t; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - struct in_addr addr; - ssize_t bytes = 0; - ssize_t l; - char *tmp; - - if (type & RFC3397) { - l = decode_rfc3397(NULL, 0, dl, data); - if (l < 1) - return l; - tmp = xmalloc(l); - decode_rfc3397(tmp, l, dl, data); - l = print_string(s, len, l - 1, (uint8_t *)tmp); - free(tmp); - return l; - } - - if (type & RFC3361) { - if ((tmp = decode_rfc3361(dl, data)) == NULL) - return -1; - l = strlen(tmp); - l = print_string(s, len, l - 1, (uint8_t *)tmp); - free(tmp); - return l; - } - - if (type & RFC3442) - return decode_rfc3442(s, len, dl, data); - - if (type & RFC5969) - return decode_rfc5969(s, len, dl, data); - - if (type & STRING) { - /* Some DHCP servers return NULL strings */ - if (*data == '\0') - return 0; - return print_string(s, len, dl, data); - } - - /* DHCPv6 status code */ - if (type & SCODE && dl >= (int)sizeof(u16)) { - if (s) { - memcpy(&u16, data, sizeof(u16)); - u16 = ntohs(u16); - l = snprintf(s, len, "%d ", u16); - len -= l; - } else - l = 7; - data += sizeof(u16); - dl -= sizeof(u16); - if (dl) - l += print_option(s, len, STRING, dl, data, ifname); - return l; - } - - if (type & IPV6) { - } - - if (!s) { - if (type & UINT8) - l = 3; - else if (type & UINT16) { - l = 5; - dl /= 2; - } else if (type & SINT16) { - l = 6; - dl /= 2; - } else if (type & UINT32) { - l = 10; - dl /= 4; - } else if (type & SINT32) { - l = 11; - dl /= 4; - } else if (type & IPV4) { - l = 16; - dl /= 4; - } else if (type & IPV6) { - e = data + dl; - l = 0; - while (data < e) { - if (l) - l++; /* space */ - dl = ipv6_printaddr(NULL, 0, data, ifname); - if (dl != -1) - l += dl; - data += 16; - } - return l + 1; - } else if (type & BINHEX) { - l = 2; - } else { - errno = EINVAL; - return -1; - } - return (l + 1) * dl; - } - - t = data; - e = data + dl; - while (data < e) { - if (data != t && type != BINHEX) { - *s++ = ' '; - bytes++; - len--; - } - if (type & UINT8) { - l = snprintf(s, len, "%d", *data); - data++; - } else if (type & UINT16) { - memcpy(&u16, data, sizeof(u16)); - u16 = ntohs(u16); - l = snprintf(s, len, "%d", u16); - data += sizeof(u16); - } else if (type & SINT16) { - memcpy(&s16, data, sizeof(s16)); - s16 = ntohs(s16); - l = snprintf(s, len, "%d", s16); - data += sizeof(s16); - } else if (type & UINT32) { - memcpy(&u32, data, sizeof(u32)); - u32 = ntohl(u32); - l = snprintf(s, len, "%d", u32); - data += sizeof(u32); - } else if (type & SINT32) { - memcpy(&s32, data, sizeof(s32)); - s32 = ntohl(s32); - l = snprintf(s, len, "%d", s32); - data += sizeof(s32); - } else if (type & IPV4) { - memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); - l = snprintf(s, len, "%s", inet_ntoa(addr)); - data += sizeof(addr.s_addr); - } else if (type & IPV6) { - dl = ipv6_printaddr(s, len, data, ifname); - if (dl != -1) - l = dl; - else - l = 0; - data += 16; - } else if (type & BINHEX) { - l = snprintf(s, len, "%.2x", data[0]); - data++; - } else - l = 0; - len -= l; - bytes += l; - s += l; - } - - return bytes; -} - ssize_t configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, const struct interface *ifp) @@ -1509,3 +1211,1000 @@ get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) if (get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) lease->server.s_addr = INADDR_ANY; } + +static const char * +get_dhcp_op(uint8_t type) +{ + const struct dhcp_op *d; + + for (d = dhcp_ops; d->name; d++) + if (d->value == type) + return d->name; + return NULL; +} + +static void +dhcp_fallback(void *arg) +{ + struct interface *iface; + + iface = (struct interface *)arg; + select_profile(iface, iface->state->options->fallback); + start_interface(iface); +} + +uint32_t +dhcp_xid(const struct interface *ifp) +{ + uint32_t xid; + + if (ifp->state->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid), + sizeof(xid)); + else + xid = arc4random(); + + return xid; +} + +void +dhcp_close(struct interface *iface) +{ + + if (iface->arp_fd != -1) { + eloop_event_delete(iface->arp_fd); + close(iface->arp_fd); + iface->arp_fd = -1; + } + if (iface->raw_fd != -1) { + eloop_event_delete(iface->raw_fd); + close(iface->raw_fd); + iface->raw_fd = -1; + } + if (iface->udp_fd != -1) { + /* we don't listen to events on the udp */ + close(iface->udp_fd); + iface->udp_fd = -1; + } +} + +static void +send_message(struct interface *iface, int type, + void (*callback)(void *)) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp; + uint8_t *udp; + ssize_t len, r; + struct in_addr from, to; + in_addr_t a = 0; + struct timeval tv; + + if (!callback) + syslog(LOG_DEBUG, "%s: sending %s with xid 0x%x", + iface->name, get_dhcp_op(type), state->xid); + else { + if (state->interval == 0) + state->interval = 4; + else { + state->interval *= 2; + if (state->interval > 64) + state->interval = 64; + } + tv.tv_sec = state->interval + DHCP_RAND_MIN; + tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&tv); + syslog(LOG_DEBUG, + "%s: sending %s (xid 0x%x), next in %0.2f seconds", + iface->name, get_dhcp_op(type), state->xid, + timeval_to_double(&tv)); + } + + /* Ensure sockets are open. */ + if (dhcp_open(iface) == -1) { + if (!(options & DHCPCD_TEST)) + dhcp_drop(iface, "FAIL"); + return; + } + + /* If we couldn't open a UDP port for our IP address + * then we cannot renew. + * This could happen if our IP was pulled out from underneath us. + * Also, we should not unicast from a BOOTP lease. */ + if (iface->udp_fd == -1 || + (!(ifo->options & DHCPCD_INFORM) && is_bootp(iface->state->new))) + { + a = iface->addr.s_addr; + iface->addr.s_addr = 0; + } + len = make_message(&dhcp, iface, type); + if (a) + iface->addr.s_addr = a; + from.s_addr = dhcp->ciaddr; + if (from.s_addr) + to.s_addr = state->lease.server.s_addr; + else + to.s_addr = 0; + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { + r = send_packet(iface, to, (uint8_t *)dhcp, len); + if (r == -1) { + syslog(LOG_ERR, "%s: send_packet: %m", iface->name); + dhcp_close(iface); + } + } else { + len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); + r = send_raw_packet(iface, ETHERTYPE_IP, udp, len); + free(udp); + /* If we failed to send a raw packet this normally means + * we don't have the ability to work beneath the IP layer + * for this interface. + * As such we remove it from consideration without actually + * stopping the interface. */ + if (r == -1) { + syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name); + if (!(options & DHCPCD_TEST)) + dhcp_drop(iface, "FAIL"); + dhcp_close(iface); + eloop_timeout_delete(NULL, iface); + callback = NULL; + } + } + free(dhcp); + + /* Even if we fail to send a packet we should continue as we are + * as our failure timeouts will change out codepath when needed. */ + if (callback) + eloop_timeout_add_tv(&tv, callback, iface); +} + +static void +send_inform(void *arg) +{ + + send_message((struct interface *)arg, DHCP_INFORM, send_inform); +} + +static void +send_discover(void *arg) +{ + + send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); +} + +static void +send_request(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_request); +} + +static void +send_renew(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_renew); +} + +static void +send_rebind(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_rebind); +} + +void +dhcp_discover(void *arg) +{ + struct interface *iface = arg; + struct if_options *ifo = iface->state->options; + int timeout = ifo->timeout; + + /* If we're rebooting and we're not daemonised then we need + * to shorten the normal timeout to ensure we try correctly + * for a fallback or IPv4LL address. */ + if (iface->state->state == DHS_REBOOT && + !(options & DHCPCD_DAEMONISED)) + { + timeout -= ifo->reboot; + if (timeout <= 0) + timeout = 2; + } + + iface->state->state = DHS_DISCOVER; + iface->state->xid = dhcp_xid(iface); + eloop_timeout_delete(NULL, iface); + if (ifo->fallback) + eloop_timeout_add_sec(timeout, dhcp_fallback, iface); + else if (ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(iface->addr.s_addr))) + { + if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) + eloop_timeout_add_sec(RATE_LIMIT_INTERVAL, + ipv4ll_start, iface); + else + eloop_timeout_add_sec(timeout, ipv4ll_start, iface); + } + if (ifo->options & DHCPCD_REQUEST) + syslog(LOG_INFO, "%s: broadcasting for a lease (requesting %s)", + iface->name, inet_ntoa(ifo->req_addr)); + else + syslog(LOG_INFO, "%s: broadcasting for a lease", iface->name); + send_discover(iface); +} + +static void +dhcp_request(void *arg) +{ + struct interface *iface = arg; + + iface->state->state = DHS_REQUEST; + send_request(iface); +} + +static void +dhcp_expire(void *arg) +{ + struct interface *iface = arg; + + iface->state->interval = 0; + if (iface->addr.s_addr == 0) { + /* We failed to reboot, so enter discovery. */ + iface->state->lease.addr.s_addr = 0; + dhcp_discover(iface); + return; + } + + syslog(LOG_ERR, "%s: lease expired", iface->name); + eloop_timeout_delete(NULL, iface); + dhcp_drop(iface, "EXPIRE"); + unlink(iface->leasefile); + if (iface->carrier != LINK_DOWN) + start_interface(iface); +} + +void +dhcp_release(struct interface *iface) +{ + struct timespec ts; + + if (iface->state->new != NULL && + iface->state->new->cookie == htonl(MAGIC_COOKIE)) + { + syslog(LOG_INFO, "%s: releasing lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->xid = dhcp_xid(iface); + send_message(iface, DHCP_RELEASE, NULL); + /* Give the packet a chance to go before dropping the ip */ + ts.tv_sec = RELEASE_DELAY_S; + ts.tv_nsec = RELEASE_DELAY_NS; + nanosleep(&ts, NULL); + dhcp_drop(iface, "RELEASE"); + } + unlink(iface->leasefile); +} + +void +dhcp_decline(struct interface *ifp) +{ + + send_message(ifp, DHCP_DECLINE, NULL); +} + +static void +dhcp_renew(void *arg) +{ + struct interface *iface = arg; + struct dhcp_lease *lease = &iface->state->lease; + + syslog(LOG_INFO, "%s: renewing lease of %s", + iface->name, inet_ntoa(lease->addr)); + syslog(LOG_DEBUG, "%s: rebind in %u seconds, expire in %u seconds", + iface->name, lease->rebindtime - lease->renewaltime, + lease->leasetime - lease->renewaltime); + iface->state->state = DHS_RENEW; + iface->state->xid = dhcp_xid(iface); + send_renew(iface); +} + +static void +dhcp_rebind(void *arg) +{ + struct interface *iface = arg; + struct dhcp_lease *lease = &iface->state->lease; + + syslog(LOG_ERR, "%s: failed to renew, attempting to rebind", + iface->name); + syslog(LOG_DEBUG, "%s: expire in %u seconds", + iface->name, lease->leasetime - lease->rebindtime); + iface->state->state = DHS_REBIND; + eloop_timeout_delete(send_renew, iface); + iface->state->lease.server.s_addr = 0; + send_rebind(iface); +} + +void +dhcp_bind(void *arg) +{ + struct interface *iface = arg; + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_lease *lease = &state->lease; + struct timeval tv; + + /* We're binding an address now - ensure that sockets are closed */ + dhcp_close(iface); + state->reason = NULL; + if (clock_monotonic) + get_monotonic(&lease->boundtime); + state->xid = 0; + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + get_lease(lease, state->new); + if (ifo->options & DHCPCD_STATIC) { + syslog(LOG_INFO, "%s: using static address %s", + iface->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + lease->net.s_addr = ifo->req_mask.s_addr; + state->reason = "STATIC"; + } else if (state->new->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_INFO, "%s: using IPv4LL address %s", + iface->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "IPV4LL"; + } else if (ifo->options & DHCPCD_INFORM) { + if (ifo->req_addr.s_addr != 0) + lease->addr.s_addr = ifo->req_addr.s_addr; + else + lease->addr.s_addr = iface->addr.s_addr; + syslog(LOG_INFO, "%s: received approval for %s", iface->name, + inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "INFORM"; + } else { + if (gettimeofday(&tv, NULL) == 0) + lease->leasedfrom = tv.tv_sec; + else if (lease->frominfo) + state->reason = "TIMEOUT"; + if (lease->leasetime == ~0U) { + lease->renewaltime = + lease->rebindtime = + lease->leasetime; + syslog(LOG_INFO, "%s: leased %s for infinity", + iface->name, inet_ntoa(lease->addr)); + } else { + if (lease->leasetime < DHCP_MIN_LEASE) { + syslog(LOG_WARNING, + "%s: minimum lease is %d seconds", + iface->name, DHCP_MIN_LEASE); + lease->leasetime = DHCP_MIN_LEASE; + } + if (lease->rebindtime == 0) + lease->rebindtime = lease->leasetime * T2; + else if (lease->rebindtime >= lease->leasetime) { + lease->rebindtime = lease->leasetime * T2; + syslog(LOG_ERR, + "%s: rebind time greater than lease " + "time, forcing to %u seconds", + iface->name, lease->rebindtime); + } + if (lease->renewaltime == 0) + lease->renewaltime = lease->leasetime * T1; + else if (lease->renewaltime > lease->rebindtime) { + lease->renewaltime = lease->leasetime * T1; + syslog(LOG_ERR, + "%s: renewal time greater than rebind " + "time, forcing to %u seconds", + iface->name, lease->renewaltime); + } + syslog(LOG_INFO, + "%s: leased %s for %u seconds", iface->name, + inet_ntoa(lease->addr), lease->leasetime); + } + } + if (options & DHCPCD_TEST) { + state->reason = "TEST"; + script_run(iface); + exit(EXIT_SUCCESS); + } + if (state->reason == NULL) { + if (state->old) { + if (state->old->yiaddr == state->new->yiaddr && + lease->server.s_addr) + state->reason = "RENEW"; + else + state->reason = "REBIND"; + } else if (state->state == DHS_REBOOT) + state->reason = "REBOOT"; + else + state->reason = "BOUND"; + } + if (lease->leasetime == ~0U) + lease->renewaltime = lease->rebindtime = lease->leasetime; + else { + eloop_timeout_add_sec(lease->renewaltime, dhcp_renew, iface); + eloop_timeout_add_sec(lease->rebindtime, dhcp_rebind, iface); + eloop_timeout_add_sec(lease->leasetime, dhcp_expire, iface); + syslog(LOG_DEBUG, + "%s: renew in %u seconds, rebind in %u seconds", + iface->name, lease->renewaltime, lease->rebindtime); + } + ifo->options &= ~ DHCPCD_CSR_WARNED; + ipv4_applyaddr(iface); + daemonise(); + state->state = DHS_BOUND; + if (ifo->options & DHCPCD_ARP) { + state->claims = 0; + arp_announce(iface); + } +} + + +static void +dhcp_timeout(void *arg) +{ + struct interface *iface = arg; + + dhcp_bind(iface); + iface->state->interval = 0; + dhcp_discover(iface); +} + +struct dhcp_message * +dhcp_message_new(struct in_addr *addr, struct in_addr *mask) +{ + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = xzalloc(sizeof(*dhcp)); + dhcp->yiaddr = addr->s_addr; + p = dhcp->options; + if (mask && mask->s_addr != INADDR_ANY) { + *p++ = DHO_SUBNETMASK; + *p++ = sizeof(mask->s_addr); + memcpy(p, &mask->s_addr, sizeof(mask->s_addr)); + p+= sizeof(mask->s_addr); + } + *p++ = DHO_END; + return dhcp; +} + +static int +handle_3rdparty(struct interface *iface) +{ + struct if_options *ifo; + struct in_addr addr, net, dst; + + ifo = iface->state->options; + if (ifo->req_addr.s_addr != INADDR_ANY) + return 0; + + if (get_address(iface->name, &addr, &net, &dst) == 1) + ipv4_handleifa(RTM_NEWADDR, iface->name, &addr, &net, &dst); + else { + syslog(LOG_INFO, + "%s: waiting for 3rd party to configure IP address", + iface->name); + iface->state->reason = "3RDPARTY"; + script_run(iface); + } + return 1; +} + +static void +dhcp_static(struct interface *iface) +{ + struct if_options *ifo; + + if (handle_3rdparty(iface)) + return; + ifo = iface->state->options; + iface->state->offer = + dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + eloop_timeout_delete(NULL, iface); + dhcp_bind(iface); +} + +void +dhcp_inform(struct interface *iface) +{ + + if (handle_3rdparty(iface)) + return; + + if (options & DHCPCD_TEST) { + iface->addr.s_addr = iface->state->options->req_addr.s_addr; + iface->net.s_addr = iface->state->options->req_mask.s_addr; + } else { + iface->state->options->options |= DHCPCD_STATIC; + dhcp_static(iface); + } + + iface->state->state = DHS_INFORM; + iface->state->xid = dhcp_xid(iface); + send_inform(iface); +} + +static void +dhcp_reboot(struct interface *iface) +{ + struct if_options *ifo = iface->state->options; + + if (ifo->options & DHCPCD_LINK && iface->carrier == LINK_DOWN) { + syslog(LOG_INFO, "%s: waiting for carrier", iface->name); + return; + } + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(iface); + return; + } + if (ifo->reboot == 0 || iface->state->offer == NULL) { + dhcp_discover(iface); + return; + } + if (ifo->options & DHCPCD_INFORM) { + syslog(LOG_INFO, "%s: informing address of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + } else if (iface->state->offer->cookie == 0) { + if (ifo->options & DHCPCD_IPV4LL) { + iface->state->claims = 0; + arp_announce(iface); + } else + dhcp_discover(iface); + return; + } else { + syslog(LOG_INFO, "%s: rebinding lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + } + iface->state->state = DHS_REBOOT; + iface->state->xid = dhcp_xid(iface); + iface->state->lease.server.s_addr = 0; + eloop_timeout_delete(NULL, iface); + if (ifo->fallback) + eloop_timeout_add_sec(ifo->reboot, dhcp_fallback, iface); + else if (ifo->options & DHCPCD_LASTLEASE && + iface->state->lease.frominfo) + eloop_timeout_add_sec(ifo->reboot, dhcp_timeout, iface); + else if (!(ifo->options & DHCPCD_INFORM && + options & (DHCPCD_MASTER | DHCPCD_DAEMONISED))) + eloop_timeout_add_sec(ifo->reboot, dhcp_expire, iface); + /* Don't bother ARP checking as the server could NAK us first. */ + if (ifo->options & DHCPCD_INFORM) + dhcp_inform(iface); + else + dhcp_request(iface); +} + + +void +dhcp_drop(struct interface *iface, const char *reason) +{ + + eloop_timeouts_delete(iface, dhcp_expire, NULL); + free(iface->state->old); + iface->state->old = iface->state->new; + iface->state->new = NULL; + iface->state->reason = reason; + ipv4_applyaddr(iface); + free(iface->state->old); + iface->state->old = NULL; + iface->state->lease.addr.s_addr = 0; +} + +static void +log_dhcp(int lvl, const char *msg, + const struct interface *iface, const struct dhcp_message *dhcp, + const struct in_addr *from) +{ + const char *tfrom; + char *a; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) + a = get_option_string(dhcp, DHO_MESSAGE); + else if (dhcp->yiaddr != 0) { + addr.s_addr = dhcp->yiaddr; + a = xstrdup(inet_ntoa(addr)); + } else + a = NULL; + + tfrom = "from"; + r = get_option_addr(&addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + syslog(lvl, "%s: %s %s %s %s `%s'", iface->name, msg, a, + tfrom, inet_ntoa(addr), dhcp->servername); + else { + if (r != 0) { + tfrom = "via"; + addr = *from; + } + if (a == NULL) + syslog(lvl, "%s: %s %s %s", + iface->name, msg, tfrom, inet_ntoa(addr)); + else + syslog(lvl, "%s: %s %s %s %s", + iface->name, msg, a, tfrom, inet_ntoa(addr)); + } + free(a); +} + +static int +blacklisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + for (i = 0; i < ifo->blacklist_len; i += 2) + if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1])) + return 1; + return 0; +} + +static int +whitelisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + if (ifo->whitelist_len == 0) + return -1; + for (i = 0; i < ifo->whitelist_len; i += 2) + if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) + return 1; + return 0; +} + +static void +dhcp_handle(struct interface *iface, struct dhcp_message **dhcpp, + const struct in_addr *from) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp = *dhcpp; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; + struct in_addr addr; + size_t i; + + /* reset the message counter */ + state->interval = 0; + + /* We may have found a BOOTP server */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + + if (type == DHCP_NAK) { + /* For NAK, only check if we require the ServerID */ + if (has_option_mask(ifo->requiremask, DHO_SERVERID) && + get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) + { + log_dhcp(LOG_WARNING, "reject NAK", iface, dhcp, from); + return; + } + /* We should restart on a NAK */ + log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from); + if (!(options & DHCPCD_TEST)) { + dhcp_drop(iface, "NAK"); + unlink(iface->leasefile); + } + dhcp_close(iface); + /* If we constantly get NAKS then we should slowly back off */ + eloop_timeout_add_sec(state->nakoff, start_interface, iface); + if (state->nakoff == 0) + state->nakoff = 1; + else { + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + } + return; + } + + /* Ensure that all required options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(ifo->requiremask, i) && + get_option_uint8(&tmp, dhcp, i) != 0) + { + /* If we are bootp, then ignore the need for serverid. + * To ignore bootp, require dhcp_message_type instead. */ + if (type == 0 && i == DHO_SERVERID) + continue; + log_dhcp(LOG_WARNING, "reject DHCP", iface, dhcp, from); + return; + } + } + + /* Ensure that the address offered is valid */ + if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && + (dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) && + (dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST)) + { + log_dhcp(LOG_WARNING, "reject invalid address", + iface, dhcp, from); + return; + } + + /* No NAK, so reset the backoff */ + state->nakoff = 0; + + if ((type == 0 || type == DHCP_OFFER) && + state->state == DHS_DISCOVER) + { + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + lease->cookie = dhcp->cookie; + if (type == 0 || + get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; + log_dhcp(LOG_INFO, "offered", iface, dhcp, from); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + if (options & DHCPCD_TEST) { + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + state->reason = "TEST"; + script_run(iface); + exit(EXIT_SUCCESS); + } + eloop_timeout_delete(send_discover, iface); + /* We don't request BOOTP addresses */ + if (type) { + /* We used to ARP check here, but that seems to be in + * violation of RFC2131 where it only describes + * DECLINE after REQUEST. + * It also seems that some MS DHCP servers actually + * ignore DECLINE if no REQUEST, ie we decline a + * DISCOVER. */ + dhcp_request(iface); + return; + } + } + + if (type) { + if (type == DHCP_OFFER) { + log_dhcp(LOG_INFO, "ignoring offer of", + iface, dhcp, from); + return; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", + iface, dhcp, from); + return; + } + + if (!(ifo->options & DHCPCD_INFORM)) + log_dhcp(LOG_INFO, "acknowledged", iface, dhcp, from); + } + + /* BOOTP could have already assigned this above, so check we still + * have a pointer. */ + if (*dhcpp) { + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + } + + lease->frominfo = 0; + eloop_timeout_delete(NULL, iface); + + /* We now have an offer, so close the DHCP sockets. + * This allows us to safely ARP when broken DHCP servers send an ACK + * follows by an invalid NAK. */ + dhcp_close(iface); + + if (ifo->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + addr.s_addr = state->offer->yiaddr; + if (has_address(iface->name, &addr, NULL) != 1) { + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + state->state = DHS_PROBE; + arp_probe(iface); + return; + } + } + + dhcp_bind(iface); +} + +static void +dhcp_handlepacket(void *arg) +{ + struct interface *iface = arg; + struct dhcp_message *dhcp = NULL; + const uint8_t *pp; + ssize_t bytes; + struct in_addr from; + int i, partialcsum = 0; + + /* We loop through until our buffer is empty. + * The benefit is that if we get >1 DHCP packet in our buffer and + * the first one fails for any reason, we can use the next. */ + if (packet == NULL) + packet = xmalloc(udp_dhcp_len); + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_IP, + packet, udp_dhcp_len, &partialcsum); + if (bytes == 0 || bytes == -1) + break; + if (valid_udp_packet(packet, bytes, &from, partialcsum) == -1) { + syslog(LOG_ERR, "%s: invalid UDP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } + i = whitelisted_ip(iface->state->options, from.s_addr); + if (i == 0) { + syslog(LOG_WARNING, + "%s: non whitelisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } else if (i != 1 && + blacklisted_ip(iface->state->options, from.s_addr) == 1) + { + syslog(LOG_WARNING, + "%s: blacklisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } + if (iface->flags & IFF_POINTOPOINT && + iface->dst.s_addr != from.s_addr) + { + syslog(LOG_WARNING, + "%s: server %s is not destination", + iface->name, inet_ntoa(from)); + } + bytes = get_udp_data(&pp, packet); + if ((size_t)bytes > sizeof(*dhcp)) { + syslog(LOG_ERR, + "%s: packet greater than DHCP size from %s", + iface->name, inet_ntoa(from)); + continue; + } + if (!dhcp) + dhcp = xzalloc(sizeof(*dhcp)); + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_DEBUG, "%s: bogus cookie from %s", + iface->name, inet_ntoa(from)); + continue; + } + /* Ensure it's the right transaction */ + if (iface->state->xid != ntohl(dhcp->xid)) { + syslog(LOG_DEBUG, + "%s: wrong xid 0x%x (expecting 0x%x) from %s", + iface->name, ntohl(dhcp->xid), iface->state->xid, + inet_ntoa(from)); + continue; + } + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) + { + syslog(LOG_DEBUG, "%s: xid 0x%x is not for hwaddr %s", + iface->name, ntohl(dhcp->xid), + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + continue; + } + dhcp_handle(iface, &dhcp, &from); + if (iface->raw_fd == -1) + break; + } + free(packet); + packet = NULL; + free(dhcp); +} + +static int +dhcp_open(struct interface *ifp) +{ + int r = 0; + + if (ifp->raw_fd == -1) { + if ((r = open_socket(ifp, ETHERTYPE_IP)) == -1) + syslog(LOG_ERR, "%s: %s: %m", __func__, ifp->name); + else + eloop_event_add(ifp->raw_fd, dhcp_handlepacket, ifp); + } + if (ifp->udp_fd == -1 && + ifp->addr.s_addr != 0 && + ifp->state->new != NULL && + (ifp->state->new->cookie == htonl(MAGIC_COOKIE) || + ifp->state->options->options & DHCPCD_INFORM)) + { + if (open_udp_socket(ifp) == -1 && errno != EADDRINUSE) { + syslog(LOG_ERR, "%s: open_udp_socket: %m", ifp->name); + r = -1; + } + } + return r; +} + +void +dhcp_start(struct interface *ifp) +{ + struct if_options *ifo = ifp->state->options; + struct stat st; + struct timeval now; + uint32_t l; + int nolease; + + if (!(ifo->options & DHCPCD_IPV4)) + return; + + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + + if (!dhcp_open(ifp)) + return; + + if (ifo->options & DHCPCD_INFORM) { + dhcp_inform(ifp); + return; + } + if (ifp->hwlen == 0 && ifo->clientid[0] == '\0') { + syslog(LOG_WARNING, "%s: needs a clientid to configure", + ifp->name); + dhcp_drop(ifp, "FAIL"); + dhcp_close(ifp); + eloop_timeout_delete(NULL, ifp); + return; + } + /* We don't want to read the old lease if we NAK an old test */ + nolease = ifp->state->offer && options & DHCPCD_TEST; + if (!nolease) + ifp->state->offer = read_lease(ifp); + if (ifp->state->offer) { + get_lease(&ifp->state->lease, ifp->state->offer); + ifp->state->lease.frominfo = 1; + if (ifp->state->offer->cookie == 0) { + if (ifp->state->offer->yiaddr == + ifp->addr.s_addr) + { + free(ifp->state->offer); + ifp->state->offer = NULL; + } + } else if (ifp->state->lease.leasetime != ~0U && + stat(ifp->leasefile, &st) == 0) + { + /* Offset lease times and check expiry */ + gettimeofday(&now, NULL); + if ((time_t)ifp->state->lease.leasetime < + now.tv_sec - st.st_mtime) + { + syslog(LOG_DEBUG, + "%s: discarding expired lease", + ifp->name); + free(ifp->state->offer); + ifp->state->offer = NULL; + ifp->state->lease.addr.s_addr = 0; + } else { + l = now.tv_sec - st.st_mtime; + ifp->state->lease.leasetime -= l; + ifp->state->lease.renewaltime -= l; + ifp->state->lease.rebindtime -= l; + } + } + } + if (ifp->state->offer == NULL) + dhcp_discover(ifp); + else if (ifp->state->offer->cookie == 0 && + ifp->state->options->options & DHCPCD_IPV4LL) + ipv4ll_start(ifp); + else + dhcp_reboot(ifp); +} + diff --git a/dhcp.h b/dhcp.h index c0ddaa92..f83722bb 100644 --- a/dhcp.h +++ b/dhcp.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -34,10 +34,7 @@ #include #include "common.h" - -/* Max MTU - defines dhcp option length */ -#define MTU_MAX 1500 -#define MTU_MIN 576 +#include "dhcp-common.h" /* UDP port numbers for DHCP */ #define DHCP_SERVER_PORT 67 @@ -114,26 +111,6 @@ enum DHO { DHO_END = 255 }; -#define REQUEST (1 << 0) -#define UINT8 (1 << 1) -#define UINT16 (1 << 2) -#define SINT16 (1 << 3) -#define UINT32 (1 << 4) -#define SINT32 (1 << 5) -#define IPV4 (1 << 6) -#define STRING (1 << 7) -#define PAIR (1 << 8) -#define ARRAY (1 << 9) -#define RFC3361 (1 << 10) -#define RFC3397 (1 << 11) -#define RFC3442 (1 << 12) -#define RFC5969 (1 << 13) -#define IPV6 (1 << 14) -#define BINHEX (1 << 15) -#define SCODE (1 << 16) - -#define IPV4R IPV4 | REQUEST - /* FQDN values - lsnybble used in flags * hsnybble to create order * and to allow 0x00 to mean disable @@ -193,18 +170,12 @@ struct dhcp_lease { #include "if-options.h" #include "net.h" -struct dhcp_opt { - uint16_t option; - int type; - const char *var; -}; - extern const struct dhcp_opt const dhcp_opts[]; -#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(const struct dhcp_opt *,uint8_t *, const char *, int); +char *decode_rfc3361(int dl, const uint8_t *data); +ssize_t decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p); +ssize_t decode_rfc5969(char *out, ssize_t len, int pl, const uint8_t *p); + void print_options(void); char *get_option_string(const struct dhcp_message *, uint8_t); int get_option_addr(struct in_addr *, const struct dhcp_message *, uint8_t); @@ -215,12 +186,12 @@ int get_option_uint8(uint8_t *, const struct dhcp_message *, uint8_t); !IN_LINKLOCAL(htonl((m)->yiaddr)) && \ get_option_uint8(NULL, m, DHO_MESSAGETYPE) == -1) struct rt *get_option_routes(struct interface *, const struct dhcp_message *); -ssize_t decode_rfc3397(char *, ssize_t, int, const uint8_t *); -ssize_t print_string(char *, ssize_t, int, const uint8_t *); -ssize_t print_option(char *, ssize_t, int, int, const uint8_t *, const char *); ssize_t configure_env(char **, const char *, const struct dhcp_message *, const struct interface *); +uint32_t dhcp_xid(const struct interface *); +struct dhcp_message *dhcp_message_new(struct in_addr *addr, + struct in_addr *mask); int dhcp_message_add_addr(struct dhcp_message *, uint8_t, struct in_addr); ssize_t make_message(struct dhcp_message **, const struct interface *, uint8_t); @@ -230,4 +201,14 @@ 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 *); +void dhcp_drop(struct interface *, const char *); +void dhcp_start(struct interface *); +void dhcp_stop(struct interface *); +void dhcp_decline(struct interface *); +void dhcp_discover(void *); +void dhcp_inform(struct interface *); +void dhcp_release(struct interface *); +void dhcp_bind(void *); +void dhcp_close(struct interface *); + #endif diff --git a/dhcp6.c b/dhcp6.c index 100cfe49..26760550 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -44,16 +44,15 @@ #define ELOOP_QUEUE 2 -#include "bind.h" #include "config.h" #include "common.h" -#include "configure.h" #include "dhcp.h" #include "dhcp6.h" #include "duid.h" #include "eloop.h" #include "ipv6rs.h" #include "platform.h" +#include "script.h" #ifndef __UNCONST #define __UNCONST(a) ((void *)(unsigned long)(const void *)(a)) @@ -95,26 +94,27 @@ static const struct dhcp6_op dhcp6_ops[] = { { 0, NULL } }; +#define IPV6A ADDRIPV6 | ARRAY const struct dhcp_opt const dhcp6_opts[] = { { D6_OPTION_CLIENTID, BINHEX, "client_id" }, { D6_OPTION_SERVERID, BINHEX, "server_id" }, - { D6_OPTION_IA_ADDR, IPV6 | ARRAY, "ia_addr" }, + { D6_OPTION_IA_ADDR, IPV6A, "ia_addr" }, { D6_OPTION_PREFERENCE, UINT8, "preference" }, { D6_OPTION_RAPID_COMMIT, 0, "rapid_commit" }, - { D6_OPTION_UNICAST, IPV6, "unicast" }, + { D6_OPTION_UNICAST, ADDRIPV6, "unicast" }, { D6_OPTION_STATUS_CODE, SCODE, "status_code" }, { D6_OPTION_SIP_SERVERS_NAME, RFC3397, "sip_servers_names" }, - { D6_OPTION_SIP_SERVERS_ADDRESS,IPV6 | ARRAY, "sip_servers_addresses" }, - { D6_OPTION_DNS_SERVERS, IPV6 | ARRAY, "name_servers" }, + { D6_OPTION_SIP_SERVERS_ADDRESS,IPV6A, "sip_servers_addresses" }, + { D6_OPTION_DNS_SERVERS, IPV6A, "name_servers" }, { D6_OPTION_DOMAIN_LIST, RFC3397, "domain_search" }, - { D6_OPTION_NIS_SERVERS, IPV6 | ARRAY, "nis_servers" }, - { D6_OPTION_NISP_SERVERS, IPV6 | ARRAY, "nisp_servers" }, + { D6_OPTION_NIS_SERVERS, IPV6A, "nis_servers" }, + { D6_OPTION_NISP_SERVERS, IPV6A, "nisp_servers" }, { D6_OPTION_NIS_DOMAIN_NAME, RFC3397, "nis_domain_name" }, { D6_OPTION_NISP_DOMAIN_NAME, RFC3397, "nisp_domain_name" }, - { D6_OPTION_SNTP_SERVERS, IPV6 | ARRAY, "sntp_servers" }, + { D6_OPTION_SNTP_SERVERS, IPV6A, "sntp_servers" }, { D6_OPTION_INFO_REFRESH_TIME, UINT32, "info_refresh_time" }, { D6_OPTION_BCMS_SERVER_D, RFC3397, "bcms_server_d" }, - { D6_OPTION_BCMS_SERVER_A, IPV6 | ARRAY, "bcms_server_a" }, + { D6_OPTION_BCMS_SERVER_A, IPV6A, "bcms_server_a" }, { 0, 0, NULL } }; @@ -875,7 +875,7 @@ dhcp6_startexpire(void *arg) syslog(LOG_ERR, "%s: DHCPv6 lease expired", ifp->name); dhcp6_freedrop_addrs(ifp, 1); - run_script_reason(ifp, "EXPIRE6"); + script_runreason(ifp, "EXPIRE6"); state = D6_CSTATE(ifp); unlink(state->leasefile); dhcp6_startdiscover(ifp); @@ -1390,7 +1390,7 @@ recv: dhcp6_writelease(ifp); } - run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : reason); + script_runreason(ifp, options & DHCPCD_TEST ? "TEST" : reason); if (options & DHCPCD_TEST || (ifp->state->options->options & DHCPCD_INFORM && !(options & DHCPCD_MASTER))) @@ -1527,7 +1527,7 @@ dhcp6_freedrop(struct interface *ifp, int drop, const char *reason) if (drop && state->new) { if (reason == NULL) reason = "STOP6"; - run_script_reason(ifp, reason); + script_runreason(ifp, reason); } free(state->send); free(state->recv); diff --git a/dhcpcd.c b/dhcpcd.c index 27665e3a..ebbb3007 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,7 +25,7 @@ * SUCH DAMAGE. */ -const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; +const char copyright[] = "Copyright (c) 2006-2013 Roy Marples"; #include #include @@ -35,14 +35,6 @@ const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; #include #include -#include -#include - -#ifdef __linux__ -# include /* for systems with broken headers */ -# include -#endif - #include #include #include @@ -57,10 +49,8 @@ const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; #include #include "arp.h" -#include "bind.h" #include "config.h" #include "common.h" -#include "configure.h" #include "control.h" #include "dhcpcd.h" #include "dhcp6.h" @@ -68,12 +58,14 @@ const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; #include "eloop.h" #include "if-options.h" #include "if-pref.h" +#include "ipv4.h" #include "ipv4ll.h" #include "ipv6.h" #include "ipv6ns.h" #include "ipv6rs.h" #include "net.h" #include "platform.h" +#include "script.h" #include "signals.h" /* We should define a maximum for the NAK exponential backoff */ @@ -98,42 +90,11 @@ static char *cffile; static struct if_options *if_options; static char *pidfile; static int linkfd = -1, ipv6rsfd = -1, ipv6nsfd = -1; -static uint8_t *packet; static char **ifv; static int ifc; static char **margv; static int margc; -struct dhcp_op { - uint8_t value; - const char *name; -}; - -static const struct dhcp_op dhcp_ops[] = { - { DHCP_DISCOVER, "DISCOVER" }, - { DHCP_OFFER, "OFFER" }, - { DHCP_REQUEST, "REQUEST" }, - { DHCP_DECLINE, "DECLINE" }, - { DHCP_ACK, "ACK" }, - { DHCP_NAK, "NAK" }, - { DHCP_RELEASE, "RELEASE" }, - { DHCP_INFORM, "INFORM" }, - { 0, NULL } -}; - -static void send_release(struct interface *); - -static const char * -get_dhcp_op(uint8_t type) -{ - const struct dhcp_op *d; - - for (d = dhcp_ops; d->name; d++) - if (d->value == type) - return d->name; - return NULL; -} - static pid_t read_pid(void) { @@ -216,7 +177,7 @@ cleanup(void) } /* ARGSUSED */ -void +static void handle_exit_timeout(_unused void *arg) { int timeout; @@ -235,17 +196,64 @@ handle_exit_timeout(_unused void *arg) eloop_timeout_add_sec(timeout, handle_exit_timeout, NULL); } -void -drop_dhcp(struct interface *iface, const char *reason) +pid_t +daemonise(void) { - free(iface->state->old); - iface->state->old = iface->state->new; - iface->state->new = NULL; - iface->state->reason = reason; - configure(iface); - free(iface->state->old); - iface->state->old = NULL; - iface->state->lease.addr.s_addr = 0; +#ifdef THERE_IS_NO_FORK + return -1; +#else + pid_t pid; + char buf = '\0'; + int sidpipe[2], fd; + + eloop_timeout_delete(handle_exit_timeout, NULL); + if (options & DHCPCD_DAEMONISED || !(options & DHCPCD_DAEMONISE)) + return 0; + /* Setup a signal pipe so parent knows when to exit. */ + if (pipe(sidpipe) == -1) { + syslog(LOG_ERR, "pipe: %m"); + return -1; + } + syslog(LOG_DEBUG, "forking to background"); + switch (pid = fork()) { + case -1: + syslog(LOG_ERR, "fork: %m"); + exit(EXIT_FAILURE); + /* NOTREACHED */ + case 0: + setsid(); + /* Notify parent it's safe to exit as we've detached. */ + close(sidpipe[0]); + if (write(sidpipe[1], &buf, 1) == -1) + syslog(LOG_ERR, "failed to notify parent: %m"); + close(sidpipe[1]); + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + break; + default: + /* Wait for child to detach */ + close(sidpipe[1]); + if (read(sidpipe[0], &buf, 1) == -1) + syslog(LOG_ERR, "failed to read child: %m"); + close(sidpipe[0]); + break; + } + /* Done with the fd now */ + if (pid != 0) { + syslog(LOG_INFO, "forked to background, child pid %d",pid); + writepid(pidfd, pid); + close(pidfd); + pidfd = -1; + options |= DHCPCD_FORKED; + exit(EXIT_SUCCESS); + } + options |= DHCPCD_DAEMONISED; + return pid; +#endif } struct interface * @@ -279,509 +287,15 @@ stop_interface(struct interface *iface) dhcp6_drop(iface, NULL); ipv6rs_drop(iface); - if (strcmp(iface->state->reason, "RELEASE") != 0) - drop_dhcp(iface, "STOP"); - close_sockets(iface); +// if (strcmp(iface->state->reason, "RELEASE") != 0) + dhcp_drop(iface, "STOP"); + dhcp_close(iface); eloop_timeout_delete(NULL, iface); free_interface(ifp); if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) exit(EXIT_FAILURE); } -static uint32_t -dhcp_xid(struct interface *iface) -{ - uint32_t xid; - - if (iface->state->options->options & DHCPCD_XID_HWADDR && - iface->hwlen >= sizeof(xid)) - /* The lower bits are probably more unique on the network */ - memcpy(&xid, (iface->hwaddr + iface->hwlen) - sizeof(xid), - sizeof(xid)); - else - xid = arc4random(); - - return xid; -} - -static void -send_message(struct interface *iface, int type, - void (*callback)(void *)) -{ - struct if_state *state = iface->state; - struct if_options *ifo = state->options; - struct dhcp_message *dhcp; - uint8_t *udp; - ssize_t len, r; - struct in_addr from, to; - in_addr_t a = 0; - struct timeval tv; - - if (!callback) - syslog(LOG_DEBUG, "%s: sending %s with xid 0x%x", - iface->name, get_dhcp_op(type), state->xid); - else { - if (state->interval == 0) - state->interval = 4; - else { - state->interval *= 2; - if (state->interval > 64) - state->interval = 64; - } - tv.tv_sec = state->interval + DHCP_RAND_MIN; - tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); - timernorm(&tv); - syslog(LOG_DEBUG, - "%s: sending %s (xid 0x%x), next in %0.2f seconds", - iface->name, get_dhcp_op(type), state->xid, - timeval_to_double(&tv)); - } - - /* Ensure sockets are open. */ - if (open_sockets(iface) == -1) { - if (!(options & DHCPCD_TEST)) - drop_dhcp(iface, "FAIL"); - return; - } - - /* If we couldn't open a UDP port for our IP address - * then we cannot renew. - * This could happen if our IP was pulled out from underneath us. - * Also, we should not unicast from a BOOTP lease. */ - if (iface->udp_fd == -1 || - (!(ifo->options & DHCPCD_INFORM) && is_bootp(iface->state->new))) - { - a = iface->addr.s_addr; - iface->addr.s_addr = 0; - } - len = make_message(&dhcp, iface, type); - if (a) - iface->addr.s_addr = a; - from.s_addr = dhcp->ciaddr; - if (from.s_addr) - to.s_addr = state->lease.server.s_addr; - else - to.s_addr = 0; - if (to.s_addr && to.s_addr != INADDR_BROADCAST) { - r = send_packet(iface, to, (uint8_t *)dhcp, len); - if (r == -1) { - syslog(LOG_ERR, "%s: send_packet: %m", iface->name); - close_sockets(iface); - } - } else { - len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); - r = send_raw_packet(iface, ETHERTYPE_IP, udp, len); - free(udp); - /* If we failed to send a raw packet this normally means - * we don't have the ability to work beneath the IP layer - * for this interface. - * As such we remove it from consideration without actually - * stopping the interface. */ - if (r == -1) { - syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name); - if (!(options & DHCPCD_TEST)) - drop_dhcp(iface, "FAIL"); - close_sockets(iface); - eloop_timeout_delete(NULL, iface); - callback = NULL; - } - } - free(dhcp); - - /* Even if we fail to send a packet we should continue as we are - * as our failure timeouts will change out codepath when needed. */ - if (callback) - eloop_timeout_add_tv(&tv, callback, iface); -} - -static void -send_inform(void *arg) -{ - send_message((struct interface *)arg, DHCP_INFORM, send_inform); -} - -static void -send_discover(void *arg) -{ - send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); -} - -static void -send_request(void *arg) -{ - send_message((struct interface *)arg, DHCP_REQUEST, send_request); -} - -static void -send_renew(void *arg) -{ - send_message((struct interface *)arg, DHCP_REQUEST, send_renew); -} - -static void -send_rebind(void *arg) -{ - send_message((struct interface *)arg, DHCP_REQUEST, send_rebind); -} - -void -start_expire(void *arg) -{ - struct interface *iface = arg; - - iface->state->interval = 0; - if (iface->addr.s_addr == 0) { - /* We failed to reboot, so enter discovery. */ - iface->state->lease.addr.s_addr = 0; - start_discover(iface); - return; - } - - syslog(LOG_ERR, "%s: lease expired", iface->name); - eloop_timeout_delete(NULL, iface); - drop_dhcp(iface, "EXPIRE"); - unlink(iface->leasefile); - if (iface->carrier != LINK_DOWN) - start_interface(iface); -} - -static void -log_dhcp(int lvl, const char *msg, - const struct interface *iface, const struct dhcp_message *dhcp, - const struct in_addr *from) -{ - const char *tfrom; - char *a; - struct in_addr addr; - int r; - - if (strcmp(msg, "NAK:") == 0) - a = get_option_string(dhcp, DHO_MESSAGE); - else if (dhcp->yiaddr != 0) { - addr.s_addr = dhcp->yiaddr; - a = xstrdup(inet_ntoa(addr)); - } else - a = NULL; - - tfrom = "from"; - r = get_option_addr(&addr, dhcp, DHO_SERVERID); - if (dhcp->servername[0] && r == 0) - syslog(lvl, "%s: %s %s %s %s `%s'", iface->name, msg, a, - tfrom, inet_ntoa(addr), dhcp->servername); - else { - if (r != 0) { - tfrom = "via"; - addr = *from; - } - if (a == NULL) - syslog(lvl, "%s: %s %s %s", - iface->name, msg, tfrom, inet_ntoa(addr)); - else - syslog(lvl, "%s: %s %s %s %s", - iface->name, msg, a, tfrom, inet_ntoa(addr)); - } - free(a); -} - -static int -blacklisted_ip(const struct if_options *ifo, in_addr_t addr) -{ - size_t i; - - for (i = 0; i < ifo->blacklist_len; i += 2) - if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1])) - return 1; - return 0; -} - -static int -whitelisted_ip(const struct if_options *ifo, in_addr_t addr) -{ - size_t i; - - if (ifo->whitelist_len == 0) - return -1; - for (i = 0; i < ifo->whitelist_len; i += 2) - if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) - return 1; - return 0; -} - -static void -handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp, const struct in_addr *from) -{ - struct if_state *state = iface->state; - struct if_options *ifo = state->options; - struct dhcp_message *dhcp = *dhcpp; - struct dhcp_lease *lease = &state->lease; - uint8_t type, tmp; - struct in_addr addr; - size_t i; - - /* reset the message counter */ - state->interval = 0; - - /* We may have found a BOOTP server */ - if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) - type = 0; - - if (type == DHCP_NAK) { - /* For NAK, only check if we require the ServerID */ - if (has_option_mask(ifo->requiremask, DHO_SERVERID) && - get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) - { - log_dhcp(LOG_WARNING, "reject NAK", iface, dhcp, from); - return; - } - /* We should restart on a NAK */ - log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from); - if (!(options & DHCPCD_TEST)) { - drop_dhcp(iface, "NAK"); - unlink(iface->leasefile); - } - close_sockets(iface); - /* If we constantly get NAKS then we should slowly back off */ - eloop_timeout_add_sec(state->nakoff, start_interface, iface); - if (state->nakoff == 0) - state->nakoff = 1; - else { - state->nakoff *= 2; - if (state->nakoff > NAKOFF_MAX) - state->nakoff = NAKOFF_MAX; - } - return; - } - - /* Ensure that all required options are present */ - for (i = 1; i < 255; i++) { - if (has_option_mask(ifo->requiremask, i) && - get_option_uint8(&tmp, dhcp, i) != 0) - { - /* If we are bootp, then ignore the need for serverid. - * To ignore bootp, require dhcp_message_type instead. */ - if (type == 0 && i == DHO_SERVERID) - continue; - log_dhcp(LOG_WARNING, "reject DHCP", iface, dhcp, from); - return; - } - } - - /* Ensure that the address offered is valid */ - if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && - (dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) && - (dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST)) - { - log_dhcp(LOG_WARNING, "reject invalid address", - iface, dhcp, from); - return; - } - - /* No NAK, so reset the backoff */ - state->nakoff = 0; - - if ((type == 0 || type == DHCP_OFFER) && - state->state == DHS_DISCOVER) - { - lease->frominfo = 0; - lease->addr.s_addr = dhcp->yiaddr; - lease->cookie = dhcp->cookie; - if (type == 0 || - get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) - lease->server.s_addr = INADDR_ANY; - log_dhcp(LOG_INFO, "offered", iface, dhcp, from); - free(state->offer); - state->offer = dhcp; - *dhcpp = NULL; - if (options & DHCPCD_TEST) { - free(state->old); - state->old = state->new; - state->new = state->offer; - state->offer = NULL; - state->reason = "TEST"; - run_script(iface); - exit(EXIT_SUCCESS); - } - eloop_timeout_delete(send_discover, iface); - /* We don't request BOOTP addresses */ - if (type) { - /* We used to ARP check here, but that seems to be in - * violation of RFC2131 where it only describes - * DECLINE after REQUEST. - * It also seems that some MS DHCP servers actually - * ignore DECLINE if no REQUEST, ie we decline a - * DISCOVER. */ - start_request(iface); - return; - } - } - - if (type) { - if (type == DHCP_OFFER) { - log_dhcp(LOG_INFO, "ignoring offer of", - iface, dhcp, from); - return; - } - - /* We should only be dealing with acks */ - if (type != DHCP_ACK) { - log_dhcp(LOG_ERR, "not ACK or OFFER", - iface, dhcp, from); - return; - } - - if (!(ifo->options & DHCPCD_INFORM)) - log_dhcp(LOG_INFO, "acknowledged", iface, dhcp, from); - } - - /* BOOTP could have already assigned this above, so check we still - * have a pointer. */ - if (*dhcpp) { - free(state->offer); - state->offer = dhcp; - *dhcpp = NULL; - } - - lease->frominfo = 0; - eloop_timeout_delete(NULL, iface); - - /* We now have an offer, so close the DHCP sockets. - * This allows us to safely ARP when broken DHCP servers send an ACK - * follows by an invalid NAK. */ - close_sockets(iface); - - if (ifo->options & DHCPCD_ARP && - iface->addr.s_addr != state->offer->yiaddr) - { - /* If the interface already has the address configured - * then we can't ARP for duplicate detection. */ - addr.s_addr = state->offer->yiaddr; - if (has_address(iface->name, &addr, NULL) != 1) { - state->claims = 0; - state->probes = 0; - state->conflicts = 0; - state->state = DHS_PROBE; - send_arp_probe(iface); - return; - } - } - - bind_interface(iface); -} - -static void -handle_dhcp_packet(void *arg) -{ - struct interface *iface = arg; - struct dhcp_message *dhcp = NULL; - const uint8_t *pp; - ssize_t bytes; - struct in_addr from; - int i, partialcsum = 0; - - /* We loop through until our buffer is empty. - * The benefit is that if we get >1 DHCP packet in our buffer and - * the first one fails for any reason, we can use the next. */ - if (packet == NULL) - packet = xmalloc(udp_dhcp_len); - for(;;) { - bytes = get_raw_packet(iface, ETHERTYPE_IP, - packet, udp_dhcp_len, &partialcsum); - if (bytes == 0 || bytes == -1) - break; - if (valid_udp_packet(packet, bytes, &from, partialcsum) == -1) { - syslog(LOG_ERR, "%s: invalid UDP packet from %s", - iface->name, inet_ntoa(from)); - continue; - } - i = whitelisted_ip(iface->state->options, from.s_addr); - if (i == 0) { - syslog(LOG_WARNING, - "%s: non whitelisted DHCP packet from %s", - iface->name, inet_ntoa(from)); - continue; - } else if (i != 1 && - blacklisted_ip(iface->state->options, from.s_addr) == 1) - { - syslog(LOG_WARNING, - "%s: blacklisted DHCP packet from %s", - iface->name, inet_ntoa(from)); - continue; - } - if (iface->flags & IFF_POINTOPOINT && - iface->dst.s_addr != from.s_addr) - { - syslog(LOG_WARNING, - "%s: server %s is not destination", - iface->name, inet_ntoa(from)); - } - bytes = get_udp_data(&pp, packet); - if ((size_t)bytes > sizeof(*dhcp)) { - syslog(LOG_ERR, - "%s: packet greater than DHCP size from %s", - iface->name, inet_ntoa(from)); - continue; - } - if (!dhcp) - dhcp = xzalloc(sizeof(*dhcp)); - memcpy(dhcp, pp, bytes); - if (dhcp->cookie != htonl(MAGIC_COOKIE)) { - syslog(LOG_DEBUG, "%s: bogus cookie from %s", - iface->name, inet_ntoa(from)); - continue; - } - /* Ensure it's the right transaction */ - if (iface->state->xid != ntohl(dhcp->xid)) { - syslog(LOG_DEBUG, - "%s: wrong xid 0x%x (expecting 0x%x) from %s", - iface->name, ntohl(dhcp->xid), iface->state->xid, - inet_ntoa(from)); - continue; - } - /* Ensure packet is for us */ - if (iface->hwlen <= sizeof(dhcp->chaddr) && - memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) - { - syslog(LOG_DEBUG, "%s: xid 0x%x is not for hwaddr %s", - iface->name, ntohl(dhcp->xid), - hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); - continue; - } - handle_dhcp(iface, &dhcp, &from); - if (iface->raw_fd == -1) - break; - } - free(packet); - packet = NULL; - free(dhcp); -} - -static void -send_release(struct interface *iface) -{ - struct timespec ts; - - if (iface->state->new != NULL && - iface->state->new->cookie == htonl(MAGIC_COOKIE)) - { - syslog(LOG_INFO, "%s: releasing lease of %s", - iface->name, inet_ntoa(iface->state->lease.addr)); - iface->state->xid = dhcp_xid(iface); - send_message(iface, DHCP_RELEASE, NULL); - /* Give the packet a chance to go before dropping the ip */ - ts.tv_sec = RELEASE_DELAY_S; - ts.tv_nsec = RELEASE_DELAY_NS; - nanosleep(&ts, NULL); - drop_dhcp(iface, "RELEASE"); - } - unlink(iface->leasefile); -} - -void -send_decline(struct interface *iface) -{ - send_message(iface, DHCP_DECLINE, NULL); -} - static void configure_interface1(struct interface *iface) { @@ -905,16 +419,6 @@ exit: return ret; } -static void -start_fallback(void *arg) -{ - struct interface *iface; - - iface = (struct interface *)arg; - select_profile(iface, iface->state->options->fallback); - start_interface(iface); -} - static void configure_interface(struct interface *iface, int argc, char **argv) { @@ -954,11 +458,10 @@ handle_carrier(int action, int flags, const char *ifname) if (iface->carrier != LINK_DOWN) { iface->carrier = LINK_DOWN; syslog(LOG_INFO, "%s: carrier lost", iface->name); - close_sockets(iface); - eloop_timeouts_delete(iface, start_expire, NULL); + dhcp_close(iface); dhcp6_drop(iface, "EXPIRE6"); ipv6rs_drop(iface); - drop_dhcp(iface, "NOCARRIER"); + dhcp_drop(iface, "NOCARRIER"); } } else if (carrier == 1 && !(~iface->flags & IFF_UP)) { if (iface->carrier != LINK_UP) { @@ -969,235 +472,17 @@ handle_carrier(int action, int flags, const char *ifname) configure_interface(iface, margc, margv); iface->state->interval = 0; iface->state->reason = "CARRIER"; - run_script(iface); + script_run(iface); start_interface(iface); } } } -void -start_discover(void *arg) -{ - struct interface *iface = arg; - struct if_options *ifo = iface->state->options; - int timeout = ifo->timeout; - - /* If we're rebooting and we're not daemonised then we need - * to shorten the normal timeout to ensure we try correctly - * for a fallback or IPv4LL address. */ - if (iface->state->state == DHS_REBOOT && - !(options & DHCPCD_DAEMONISED)) - { - timeout -= ifo->reboot; - if (timeout <= 0) - timeout = 2; - } - - iface->state->state = DHS_DISCOVER; - iface->state->xid = dhcp_xid(iface); - eloop_timeout_delete(NULL, iface); - if (ifo->fallback) - eloop_timeout_add_sec(timeout, start_fallback, iface); - else if (ifo->options & DHCPCD_IPV4LL && - !IN_LINKLOCAL(htonl(iface->addr.s_addr))) - { - if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) - eloop_timeout_add_sec(RATE_LIMIT_INTERVAL, - ipv4ll_start, iface); - else - eloop_timeout_add_sec(timeout, ipv4ll_start, iface); - } - if (ifo->options & DHCPCD_REQUEST) - syslog(LOG_INFO, "%s: broadcasting for a lease (requesting %s)", - iface->name, inet_ntoa(ifo->req_addr)); - else - syslog(LOG_INFO, "%s: broadcasting for a lease", iface->name); - send_discover(iface); -} - -void -start_request(void *arg) -{ - struct interface *iface = arg; - - iface->state->state = DHS_REQUEST; - send_request(iface); -} - -void -start_renew(void *arg) -{ - struct interface *iface = arg; - struct dhcp_lease *lease = &iface->state->lease; - - syslog(LOG_INFO, "%s: renewing lease of %s", - iface->name, inet_ntoa(lease->addr)); - syslog(LOG_DEBUG, "%s: rebind in %u seconds, expire in %u seconds", - iface->name, lease->rebindtime - lease->renewaltime, - lease->leasetime - lease->renewaltime); - iface->state->state = DHS_RENEW; - iface->state->xid = dhcp_xid(iface); - send_renew(iface); -} - -void -start_rebind(void *arg) -{ - struct interface *iface = arg; - struct dhcp_lease *lease = &iface->state->lease; - - syslog(LOG_ERR, "%s: failed to renew, attempting to rebind", - iface->name); - syslog(LOG_DEBUG, "%s: expire in %u seconds", - iface->name, lease->leasetime - lease->rebindtime); - iface->state->state = DHS_REBIND; - eloop_timeout_delete(send_renew, iface); - iface->state->lease.server.s_addr = 0; - send_rebind(iface); -} - -static void -start_timeout(void *arg) -{ - struct interface *iface = arg; - - bind_interface(iface); - iface->state->interval = 0; - start_discover(iface); -} - -static struct dhcp_message * -dhcp_message_new(struct in_addr *addr, struct in_addr *mask) -{ - struct dhcp_message *dhcp; - uint8_t *p; - - dhcp = xzalloc(sizeof(*dhcp)); - dhcp->yiaddr = addr->s_addr; - p = dhcp->options; - if (mask && mask->s_addr != INADDR_ANY) { - *p++ = DHO_SUBNETMASK; - *p++ = sizeof(mask->s_addr); - memcpy(p, &mask->s_addr, sizeof(mask->s_addr)); - p+= sizeof(mask->s_addr); - } - *p++ = DHO_END; - return dhcp; -} - -static int -handle_3rdparty(struct interface *iface) -{ - struct if_options *ifo; - struct in_addr addr, net, dst; - - ifo = iface->state->options; - if (ifo->req_addr.s_addr != INADDR_ANY) - return 0; - - if (get_address(iface->name, &addr, &net, &dst) == 1) - handle_ifa(RTM_NEWADDR, iface->name, &addr, &net, &dst); - else { - syslog(LOG_INFO, - "%s: waiting for 3rd party to configure IP address", - iface->name); - iface->state->reason = "3RDPARTY"; - run_script(iface); - } - return 1; -} - -static void -start_static(struct interface *iface) -{ - struct if_options *ifo; - - if (handle_3rdparty(iface)) - return; - ifo = iface->state->options; - iface->state->offer = - dhcp_message_new(&ifo->req_addr, &ifo->req_mask); - eloop_timeout_delete(NULL, iface); - bind_interface(iface); -} - -static void -start_inform(struct interface *iface) -{ - if (handle_3rdparty(iface)) - return; - - if (options & DHCPCD_TEST) { - iface->addr.s_addr = iface->state->options->req_addr.s_addr; - iface->net.s_addr = iface->state->options->req_mask.s_addr; - } else { - iface->state->options->options |= DHCPCD_STATIC; - start_static(iface); - } - - iface->state->state = DHS_INFORM; - iface->state->xid = dhcp_xid(iface); - send_inform(iface); -} - -void -start_reboot(struct interface *iface) -{ - struct if_options *ifo = iface->state->options; - - if (ifo->options & DHCPCD_LINK && iface->carrier == LINK_DOWN) { - syslog(LOG_INFO, "%s: waiting for carrier", iface->name); - return; - } - if (ifo->options & DHCPCD_STATIC) { - start_static(iface); - return; - } - if (ifo->reboot == 0 || iface->state->offer == NULL) { - start_discover(iface); - return; - } - if (ifo->options & DHCPCD_INFORM) { - syslog(LOG_INFO, "%s: informing address of %s", - iface->name, inet_ntoa(iface->state->lease.addr)); - } else if (iface->state->offer->cookie == 0) { - if (ifo->options & DHCPCD_IPV4LL) { - iface->state->claims = 0; - send_arp_announce(iface); - } else - start_discover(iface); - return; - } else { - syslog(LOG_INFO, "%s: rebinding lease of %s", - iface->name, inet_ntoa(iface->state->lease.addr)); - } - iface->state->state = DHS_REBOOT; - iface->state->xid = dhcp_xid(iface); - iface->state->lease.server.s_addr = 0; - eloop_timeout_delete(NULL, iface); - if (ifo->fallback) - eloop_timeout_add_sec(ifo->reboot, start_fallback, iface); - else if (ifo->options & DHCPCD_LASTLEASE && - iface->state->lease.frominfo) - eloop_timeout_add_sec(ifo->reboot, start_timeout, iface); - else if (!(ifo->options & DHCPCD_INFORM && - options & (DHCPCD_MASTER | DHCPCD_DAEMONISED))) - eloop_timeout_add_sec(ifo->reboot, start_expire, iface); - /* Don't bother ARP checking as the server could NAK us first. */ - if (ifo->options & DHCPCD_INFORM) - send_inform(iface); - else - send_request(iface); -} - void start_interface(void *arg) { struct interface *iface = arg; struct if_options *ifo = iface->state->options; - struct stat st; - struct timeval now; - uint32_t l; int nolease; handle_carrier(0, 0, iface->name); @@ -1215,11 +500,7 @@ start_interface(void *arg) ipv6rs_start(iface); if (iface->state->arping_index < ifo->arping_len) { - start_arping(iface); - return; - } - if (ifo->options & DHCPCD_STATIC) { - start_static(iface); + arp_start(iface); return; } @@ -1234,64 +515,8 @@ start_interface(void *arg) syslog(LOG_ERR, "%s: dhcp6_start: %m", iface->name); } - if (!(ifo->options & DHCPCD_IPV4)) - return; - - if (ifo->options & DHCPCD_INFORM) { - start_inform(iface); - return; - } - if (iface->hwlen == 0 && ifo->clientid[0] == '\0') { - syslog(LOG_WARNING, "%s: needs a clientid to configure", - iface->name); - drop_dhcp(iface, "FAIL"); - close_sockets(iface); - eloop_timeout_delete(NULL, iface); - return; - } - /* We don't want to read the old lease if we NAK an old test */ - nolease = iface->state->offer && options & DHCPCD_TEST; - if (!nolease) - iface->state->offer = read_lease(iface); - if (iface->state->offer) { - get_lease(&iface->state->lease, iface->state->offer); - iface->state->lease.frominfo = 1; - if (iface->state->offer->cookie == 0) { - if (iface->state->offer->yiaddr == - iface->addr.s_addr) - { - free(iface->state->offer); - iface->state->offer = NULL; - } - } else if (iface->state->lease.leasetime != ~0U && - stat(iface->leasefile, &st) == 0) - { - /* Offset lease times and check expiry */ - gettimeofday(&now, NULL); - if ((time_t)iface->state->lease.leasetime < - now.tv_sec - st.st_mtime) - { - syslog(LOG_DEBUG, - "%s: discarding expired lease", - iface->name); - free(iface->state->offer); - iface->state->offer = NULL; - iface->state->lease.addr.s_addr = 0; - } else { - l = now.tv_sec - st.st_mtime; - iface->state->lease.leasetime -= l; - iface->state->lease.renewaltime -= l; - iface->state->lease.rebindtime -= l; - } - } - } - if (iface->state->offer == NULL) - start_discover(iface); - else if (iface->state->offer->cookie == 0 && - iface->state->options->options & DHCPCD_IPV4LL) - ipv4ll_start(iface); - else - start_reboot(iface); + if (ifo->options & DHCPCD_IPV4) + dhcp_start(iface); } static void @@ -1309,7 +534,7 @@ init_state(struct interface *iface, int argc, char **argv) ifs->nakoff = 0; configure_interface(iface, argc, argv); if (!(options & DHCPCD_TEST)) - run_script(iface); + script_run(iface); /* We need to drop the leasefile so that start_interface * doesn't load it. */ if (ifs->options->options & DHCPCD_REQUEST) @@ -1330,7 +555,7 @@ init_state(struct interface *iface, int argc, char **argv) return; } if (!(options & DHCPCD_TEST)) - run_script(iface); + script_run(iface); } else iface->carrier = LINK_UNKNOWN; } @@ -1422,66 +647,11 @@ handle_hwaddr(const char *ifname, unsigned char *hwaddr, size_t hwlen) } #endif -void -handle_ifa(int type, const char *ifname, - struct in_addr *addr, struct in_addr *net, struct in_addr *dst) -{ - struct interface *ifp; - struct if_options *ifo; - int i; - - if (addr->s_addr == INADDR_ANY) - return; - for (ifp = ifaces; ifp; ifp = ifp->next) - if (strcmp(ifp->name, ifname) == 0) - break; - if (ifp == NULL) - return; - - if (type == RTM_DELADDR) { - if (ifp->state->new && - ifp->state->new->yiaddr == addr->s_addr) - syslog(LOG_INFO, "%s: removing IP address %s/%d", - ifp->name, inet_ntoa(ifp->state->lease.addr), - inet_ntocidr(ifp->state->lease.net)); - return; - } - - if (type != RTM_NEWADDR) - return; - - ifo = ifp->state->options; - if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) == 0 || - ifo->req_addr.s_addr != INADDR_ANY) - return; - - free(ifp->state->old); - ifp->state->old = ifp->state->new; - ifp->state->new = dhcp_message_new(addr, net); - ifp->dst.s_addr = dst ? dst->s_addr : INADDR_ANY; - if (dst) { - for (i = 1; i < 255; i++) - if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i)) - dhcp_message_add_addr(ifp->state->new, i, *dst); - } - ifp->state->reason = "STATIC"; - build_routes(); - run_script(ifp); - if (ifo->options & DHCPCD_INFORM) { - ifp->state->state = DHS_INFORM; - ifp->state->xid = dhcp_xid(ifp); - ifp->state->lease.server.s_addr = - dst ? dst->s_addr : INADDR_ANY; - ifp->addr = *addr; - ifp->net = *net; - send_inform(ifp); - } -} - /* ARGSUSED */ static void handle_link(_unused void *arg) { + if (manage_link(linkfd) == -1) syslog(LOG_ERR, "manage_link: %m"); } @@ -1502,7 +672,7 @@ if_reboot(struct interface *iface, int argc, char **argv) (opt & (DHCPCD_INFORM | DHCPCD_STATIC) && !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) { - drop_dhcp(iface, "EXPIRE"); + dhcp_drop(iface, "EXPIRE"); } else { free(iface->state->offer); iface->state->offer = NULL; @@ -1530,7 +700,7 @@ reconf_reboot(int action, int argc, char **argv, int oi) if (action) if_reboot(ifn, argc, argv); else if (ifn->state->new) - configure(ifn); + ipv4_applyaddr(ifn); free_interface(ifp); } else { ifp->next = NULL; @@ -1592,7 +762,7 @@ handle_signal(int sig) syslog(LOG_INFO, "received SIGUSR, reconfiguring"); for (ifp = ifaces; ifp; ifp = ifp->next) if (ifp->state->new) - configure(ifp); + ipv4_applyaddr(ifp); return; case SIGPIPE: syslog(LOG_WARNING, "received SIGPIPE"); @@ -1619,7 +789,7 @@ handle_signal(int sig) if (ifp->carrier != LINK_DOWN && (do_release || ifp->state->options->options & DHCPCD_RELEASE)) - send_release(ifp); + dhcp_release(ifp); stop_interface(ifp); } exit(EXIT_FAILURE); @@ -1755,7 +925,7 @@ handle_args(struct fd_list *fd, int argc, char **argv) ifp->state->options->options |= DHCPCD_RELEASE; if (ifp->state->options->options & DHCPCD_RELEASE && ifp->carrier != LINK_DOWN) - send_release(ifp); + dhcp_release(ifp); stop_interface(ifp); } return 0; @@ -1765,51 +935,6 @@ handle_args(struct fd_list *fd, int argc, char **argv) return 0; } -int -open_sockets(struct interface *iface) -{ - int r = 0; - - if (iface->raw_fd == -1) { - if ((r = open_socket(iface, ETHERTYPE_IP)) == -1) - syslog(LOG_ERR, "%s: open_socket: %m", iface->name); - else - eloop_event_add(iface->raw_fd, handle_dhcp_packet, iface); - } - if (iface->udp_fd == -1 && - iface->addr.s_addr != 0 && - iface->state->new != NULL && - (iface->state->new->cookie == htonl(MAGIC_COOKIE) || - iface->state->options->options & DHCPCD_INFORM)) - { - if (open_udp_socket(iface) == -1 && errno != EADDRINUSE) { - syslog(LOG_ERR, "%s: open_udp_socket: %m", iface->name); - r = -1; - } - } - return r; -} - -void -close_sockets(struct interface *iface) -{ - if (iface->arp_fd != -1) { - eloop_event_delete(iface->arp_fd); - close(iface->arp_fd); - iface->arp_fd = -1; - } - if (iface->raw_fd != -1) { - eloop_event_delete(iface->raw_fd); - close(iface->raw_fd); - iface->raw_fd = -1; - } - if (iface->udp_fd != -1) { - /* we don't listen to events on the udp */ - close(iface->udp_fd); - iface->udp_fd = -1; - } -} - int main(int argc, char **argv) { @@ -1967,7 +1092,7 @@ main(int argc, char **argv) exit(EXIT_FAILURE); } iface->state->reason = "DUMP"; - run_script(iface); + script_run(iface); exit(EXIT_SUCCESS); } diff --git a/dhcpcd.h b/dhcpcd.h index bfcba8c4..2e389946 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -128,27 +128,16 @@ extern int ifdc; extern char **ifdv; extern struct interface *ifaces; +pid_t daemonise(void); struct interface *find_interface(const char *); int handle_args(struct fd_list *, int, char **); void handle_carrier(int, int, const char *); void handle_interface(int, const char *); void handle_hwaddr(const char *, unsigned char *, size_t); -void handle_ifa(int, const char *, - struct in_addr *, struct in_addr *, struct in_addr *); -void handle_exit_timeout(void *); void handle_signal(int); -void start_interface(void *); -void start_discover(void *); -void start_request(void *); -void start_renew(void *); -void start_rebind(void *); -void start_reboot(struct interface *); -void start_expire(void *); -void send_decline(struct interface *); -int open_sockets(struct interface *); -void close_sockets(struct interface *); -void drop_dhcp(struct interface *, const char *); void drop_interface(struct interface *, const char *); int select_profile(struct interface *, const char *); +void start_interface(void *); + #endif diff --git a/if-linux.c b/if-linux.c index f39507a4..4b21ff34 100644 --- a/if-linux.c +++ b/if-linux.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2011 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -57,8 +57,8 @@ #include "config.h" #include "common.h" -#include "configure.h" #include "dhcp.h" +#include "ipv4.h" #include "ipv6.h" #include "net.h" @@ -294,7 +294,7 @@ link_route(struct nlmsghdr *nlm) if (rt.iface != NULL) { if (metric == rt.iface->metric) { inet_cidrtoaddr(rtm->rtm_dst_len, &rt.net); - route_deleted(&rt); + ipv4_routedeleted(&rt); } } return 1; @@ -346,7 +346,7 @@ link_addr(struct nlmsghdr *nlm) } rta = RTA_NEXT(rta, len); } - handle_ifa(nlm->nlmsg_type, ifn, &addr, &net, &dest); + ipv4_handleifa(nlm->nlmsg_type, ifn, &addr, &net, &dest); return 1; } diff --git a/configure.c b/ipv4.c similarity index 53% rename from configure.c rename to ipv4.c index 01b0d861..45b4e5d7 100644 --- a/configure.c +++ b/ipv4.c @@ -25,18 +25,16 @@ * SUCH DAMAGE. */ -#include -#include -#include - #include #include +#ifdef __linux__ +# include /* for systems with broken headers */ +# include +#endif + #include #include -#include -/* We can't include spawn.h here because it may not exist. - * config.h will pull it in, or our compat one. */ #include #include #include @@ -44,433 +42,15 @@ #include "config.h" #include "common.h" -#include "configure.h" #include "dhcp.h" -#include "dhcp6.h" #include "if-options.h" #include "if-pref.h" -#include "ipv6rs.h" +#include "ipv4.h" #include "net.h" -#include "signals.h" - -#define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" +#include "script.h" static struct rt *routes; -static const char *if_params[] = { - "interface", - "reason", - "pid", - "ifmetric", - "ifwireless", - "ifflags", - "ssid", - "profile", - "interface_order", - NULL -}; - -void -if_printoptions(void) -{ - const char **p; - - for (p = if_params; *p; p++) - printf(" - %s\n", *p); -} - -static int -exec_script(char *const *argv, char *const *env) -{ - pid_t pid; - posix_spawnattr_t attr; - short flags; - sigset_t defsigs; - int i; - - /* posix_spawn is a safe way of executing another image - * and changing signals back to how they should be. */ - if (posix_spawnattr_init(&attr) == -1) - return -1; - flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; - posix_spawnattr_setflags(&attr, flags); - sigemptyset(&defsigs); - for (i = 0; i < handle_sigs[i]; i++) - sigaddset(&defsigs, handle_sigs[i]); - posix_spawnattr_setsigdefault(&attr, &defsigs); - posix_spawnattr_setsigmask(&attr, &dhcpcd_sigset); - errno = 0; - i = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); - if (i) { - errno = i; - return -1; - } - return pid; -} - -static char * -make_var(const char *prefix, const char *var) -{ - size_t len; - char *v; - - len = strlen(prefix) + strlen(var) + 2; - v = xmalloc(len); - snprintf(v, len, "%s_%s", prefix, var); - return v; -} - - -static void -append_config(char ***env, ssize_t *len, - const char *prefix, const char *const *config) -{ - ssize_t i, j, e1; - char **ne, *eq; - - if (config == NULL) - return; - - ne = *env; - for (i = 0; config[i] != NULL; i++) { - eq = strchr(config[i], '='); - e1 = eq - config[i] + 1; - for (j = 0; j < *len; j++) { - if (strncmp(ne[j] + strlen(prefix) + 1, - config[i], e1) == 0) - { - free(ne[j]); - ne[j] = make_var(prefix, config[i]); - break; - } - } - if (j == *len) { - j++; - ne = xrealloc(ne, sizeof(char *) * (j + 1)); - ne[j - 1] = make_var(prefix, config[i]); - *len = j; - } - } - *env = ne; -} - -static size_t -arraytostr(const char *const *argv, char **s) -{ - const char *const *ap; - char *p; - size_t len, l; - - len = 0; - ap = argv; - while (*ap) - len += strlen(*ap++) + 1; - *s = p = xmalloc(len); - ap = argv; - while (*ap) { - l = strlen(*ap) + 1; - memcpy(p, *ap, l); - p += l; - ap++; - } - return len; -} - -static ssize_t -make_env(const struct interface *iface, const char *reason, char ***argv) -{ - char **env, *p; - ssize_t e, elen, l; - const struct if_options *ifo = iface->state->options; - const struct interface *ifp; - int dhcp, dhcp6, ra; - const struct dhcp6_state *d6_state; - - dhcp = dhcp6 = ra = 0; - d6_state = D6_STATE(iface); - if (strcmp(reason, "TEST") == 0) { - if (d6_state && d6_state->new) - dhcp6 = 1; - else if (ipv6rs_has_ra(iface)) - ra = 1; - else - dhcp = 1; - } else if (reason[strlen(reason) - 1] == '6') - dhcp6 = 1; - else if (strcmp(reason, "ROUTERADVERT") == 0) - ra = 1; - else - dhcp = 1; - - /* When dumping the lease, we only want to report interface and - reason - the other interface variables are meaningless */ - if (options & DHCPCD_DUMPLEASE) - elen = 2; - else - elen = 10; - - /* Make our env */ - env = xmalloc(sizeof(char *) * (elen + 1)); - e = strlen("interface") + strlen(iface->name) + 2; - env[0] = xmalloc(e); - snprintf(env[0], e, "interface=%s", iface->name); - e = strlen("reason") + strlen(reason) + 2; - env[1] = xmalloc(e); - snprintf(env[1], e, "reason=%s", reason); - if (options & DHCPCD_DUMPLEASE) - goto dumplease; - - e = 20; - env[2] = xmalloc(e); - snprintf(env[2], e, "pid=%d", getpid()); - env[3] = xmalloc(e); - snprintf(env[3], e, "ifmetric=%d", iface->metric); - env[4] = xmalloc(e); - snprintf(env[4], e, "ifwireless=%d", iface->wireless); - env[5] = xmalloc(e); - snprintf(env[5], e, "ifflags=%u", iface->flags); - env[6] = xmalloc(e); - snprintf(env[6], e, "ifmtu=%d", get_mtu(iface->name)); - l = e = strlen("interface_order="); - for (ifp = ifaces; ifp; ifp = ifp->next) - e += strlen(ifp->name) + 1; - p = env[7] = xmalloc(e); - strlcpy(p, "interface_order=", e); - e -= l; - p += l; - for (ifp = ifaces; ifp; ifp = ifp->next) { - l = strlcpy(p, ifp->name, e); - p += l; - e -= l; - *p++ = ' '; - e--; - } - *--p = '\0'; - if (strcmp(reason, "TEST") == 0) { - env[8] = strdup("if_up=false"); - env[9] = strdup("if_down=false"); - } else if ((dhcp && iface->state->new) || - (dhcp6 && d6_state->new) || - (ra && ipv6rs_has_ra(iface))) - { - env[8] = strdup("if_up=true"); - env[9] = strdup("if_down=false"); - } else { - env[8] = strdup("if_up=false"); - env[9] = strdup("if_down=true"); - } - if (*iface->state->profile) { - e = strlen("profile=") + strlen(iface->state->profile) + 2; - env[elen] = xmalloc(e); - snprintf(env[elen++], e, "profile=%s", iface->state->profile); - } - if (iface->wireless) { - e = strlen("new_ssid=") + strlen(iface->ssid) + 2; - if (iface->state->new != NULL || - strcmp(iface->state->reason, "CARRIER") == 0) - { - env = xrealloc(env, sizeof(char *) * (elen + 2)); - env[elen] = xmalloc(e); - snprintf(env[elen++], e, "new_ssid=%s", iface->ssid); - } - if (iface->state->old != NULL || - strcmp(iface->state->reason, "NOCARRIER") == 0) - { - env = xrealloc(env, sizeof(char *) * (elen + 2)); - env[elen] = xmalloc(e); - snprintf(env[elen++], e, "old_ssid=%s", iface->ssid); - } - } - if (dhcp && iface->state->old) { - e = configure_env(NULL, NULL, iface->state->old, iface); - if (e > 0) { - env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += configure_env(env + elen, "old", - iface->state->old, iface); - } - append_config(&env, &elen, "old", - (const char *const *)ifo->config); - } - if (dhcp6 && d6_state->old) { - e = dhcp6_env(NULL, NULL, iface, - d6_state->old, d6_state->old_len); - if (e > 0) { - env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += dhcp6_env(env + elen, "old", iface, - d6_state->old, d6_state->old_len); - } - } - -dumplease: - if (dhcp && iface->state->new) { - e = configure_env(NULL, NULL, iface->state->new, iface); - if (e > 0) { - env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += configure_env(env + elen, "new", - iface->state->new, iface); - } - append_config(&env, &elen, "new", - (const char *const *)ifo->config); - } - if (dhcp6 && d6_state->new) { - e = dhcp6_env(NULL, NULL, iface, - d6_state->new, d6_state->new_len); - if (e > 0) { - env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += dhcp6_env(env + elen, "new", iface, - d6_state->new, d6_state->new_len); - } - } - if (ra) { - e = ipv6rs_env(NULL, NULL, iface); - if (e > 0) { - env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - elen += ipv6rs_env(env + elen, NULL, iface); - } - } - - /* Add our base environment */ - if (ifo->environ) { - e = 0; - while (ifo->environ[e++]) - ; - env = xrealloc(env, sizeof(char *) * (elen + e + 1)); - e = 0; - while (ifo->environ[e]) { - env[elen + e] = xstrdup(ifo->environ[e]); - e++; - } - elen += e; - } - env[elen] = '\0'; - - *argv = env; - return elen; -} - -static int -send_interface1(int fd, const struct interface *iface, const char *reason) -{ - char **env, **ep, *s; - ssize_t elen; - struct iovec iov[2]; - int retval; - - make_env(iface, reason, &env); - elen = arraytostr((const char *const *)env, &s); - iov[0].iov_base = &elen; - iov[0].iov_len = sizeof(ssize_t); - iov[1].iov_base = s; - iov[1].iov_len = elen; - retval = writev(fd, iov, 2); - ep = env; - while (*ep) - free(*ep++); - free(env); - free(s); - return retval; -} - -int -send_interface(int fd, const struct interface *iface) -{ - int retval = 0; - if (send_interface1(fd, iface, iface->state->reason) == -1) - retval = -1; - if (ipv6rs_has_ra(iface)) { - if (send_interface1(fd, iface, "ROUTERADVERT") == -1) - retval = -1; - } - if (D6_STATE_RUNNING(iface)) { - if (send_interface1(fd, iface, "INFORM6") == -1) - retval = -1; - } - return retval; -} - -int -run_script_reason(const struct interface *iface, const char *reason) -{ - char *const argv[2] = { UNCONST(iface->state->options->script), NULL }; - char **env = NULL, **ep; - char *path, *bigenv; - ssize_t e, elen = 0; - pid_t pid; - int status = 0; - const struct fd_list *fd; - struct iovec iov[2]; - - if (iface->state->options->script == NULL || - iface->state->options->script[0] == '\0' || - strcmp(iface->state->options->script, "/dev/null") == 0) - return 0; - - if (reason == NULL) - reason = iface->state->reason; - syslog(LOG_DEBUG, "%s: executing `%s', reason %s", - iface->name, argv[0], reason); - - /* Make our env */ - elen = make_env(iface, reason, &env); - env = xrealloc(env, sizeof(char *) * (elen + 2)); - /* Add path to it */ - path = getenv("PATH"); - if (path) { - e = strlen("PATH") + strlen(path) + 2; - env[elen] = xmalloc(e); - snprintf(env[elen], e, "PATH=%s", path); - } else - env[elen] = xstrdup(DEFAULT_PATH); - env[++elen] = '\0'; - - pid = exec_script(argv, env); - if (pid == -1) - syslog(LOG_ERR, "exec_script: %s: %m", argv[0]); - else if (pid != 0) { - /* Wait for the script to finish */ - while (waitpid(pid, &status, 0) == -1) { - if (errno != EINTR) { - syslog(LOG_ERR, "waitpid: %m"); - status = 0; - break; - } - } - if (WIFEXITED(status)) { - if (WEXITSTATUS(status)) - syslog(LOG_ERR, - "exec_script: %s: WEXITSTATUS %d", - argv[0], WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) - syslog(LOG_ERR, "exec_sript: %s: %s", - argv[0], strsignal(WTERMSIG(status))); - } - - /* Send to our listeners */ - bigenv = NULL; - for (fd = control_fds; fd != NULL; fd = fd->next) { - if (fd->listener) { - if (bigenv == NULL) { - elen = arraytostr((const char *const *)env, - &bigenv); - iov[0].iov_base = &elen; - iov[0].iov_len = sizeof(ssize_t); - iov[1].iov_base = bigenv; - iov[1].iov_len = elen; - } - if (writev(fd->fd, iov, 2) == -1) - syslog(LOG_ERR, "writev: %m"); - } - } - free(bigenv); - - /* Cleanup */ - ep = env; - while (*ep) - free(*ep++); - free(env); - return WEXITSTATUS(status); -} - static struct rt * find_route(struct rt *rts, const struct rt *r, struct rt **lrt, const struct rt *srt) @@ -519,7 +99,7 @@ desc_route(const char *cmd, const struct rt *rt) /* If something other than dhcpcd removes a route, * we need to remove it from our internal table. */ int -route_deleted(const struct rt *rt) +ipv4_routedeleted(const struct rt *rt) { struct rt *f, *l; @@ -636,12 +216,12 @@ add_subnet_route(struct rt *rt, const struct interface *iface) } static struct rt * -get_routes(struct interface *iface) +get_routes(struct interface *ifp) { struct rt *rt, *nrt = NULL, *r = NULL; - if (iface->state->options->routes != NULL) { - for (rt = iface->state->options->routes; + if (ifp->state->options->routes != NULL) { + for (rt = ifp->state->options->routes; rt != NULL; rt = rt->next) { @@ -659,7 +239,7 @@ get_routes(struct interface *iface) return nrt; } - return get_option_routes(iface, iface->state->new); + return get_option_routes(ifp, ifp->state->new); } /* Some DHCP servers add set host routes by setting the gateway @@ -746,7 +326,7 @@ add_router_host_route(struct rt *rt, const struct interface *ifp) } void -build_routes(void) +ipv4_buildroutes(void) { struct rt *nrs = NULL, *dnr, *or, *rt, *rtn, *rtl, *lrt = NULL; struct interface *ifp; @@ -830,9 +410,10 @@ delete_address(struct interface *iface) return retval; } -int -configure(struct interface *iface) +void +ipv4_applyaddr(void *arg) { + struct interface *iface = arg; struct dhcp_message *dhcp = iface->state->new; struct dhcp_lease *lease = &iface->state->lease; struct if_options *ifo = iface->state->options; @@ -844,12 +425,12 @@ configure(struct interface *iface) if (dhcp == NULL) { if (!(ifo->options & DHCPCD_PERSISTENT)) { - build_routes(); + ipv4_buildroutes(); if (iface->addr.s_addr != 0) delete_address(iface); - run_script(iface); + script_run(iface); } - return 0; + return; } /* This also changes netmask */ @@ -863,8 +444,8 @@ configure(struct interface *iface) &lease->addr, &lease->net, &lease->brd) == -1 && errno != EEXIST) { - syslog(LOG_ERR, "add_address: %m"); - return -1; + syslog(LOG_ERR, "%s: add_address: %m", __func__); + return; } } @@ -887,11 +468,66 @@ configure(struct interface *iface) free(rt); } - build_routes(); + ipv4_buildroutes(); if (!iface->state->lease.frominfo && !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) if (write_lease(iface, dhcp) == -1) - syslog(LOG_ERR, "write_lease: %m"); - run_script(iface); - return 0; + syslog(LOG_ERR, "%s: write_lease: %m", __func__); + script_run(iface); +} + +void +ipv4_handleifa(int type, const char *ifname, + struct in_addr *addr, struct in_addr *net, struct in_addr *dst) +{ + struct interface *ifp; + struct if_options *ifo; + int i; + + if (addr->s_addr == INADDR_ANY) + return; + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0) + break; + if (ifp == NULL) + return; + + if (type == RTM_DELADDR) { + if (ifp->state->new && + ifp->state->new->yiaddr == addr->s_addr) + syslog(LOG_INFO, "%s: removing IP address %s/%d", + ifp->name, inet_ntoa(ifp->state->lease.addr), + inet_ntocidr(ifp->state->lease.net)); + return; + } + + if (type != RTM_NEWADDR) + return; + + ifo = ifp->state->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) == 0 || + ifo->req_addr.s_addr != INADDR_ANY) + return; + + free(ifp->state->old); + ifp->state->old = ifp->state->new; + ifp->state->new = dhcp_message_new(addr, net); + ifp->dst.s_addr = dst ? dst->s_addr : INADDR_ANY; + if (dst) { + for (i = 1; i < 255; i++) + if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i)) + dhcp_message_add_addr(ifp->state->new, i, *dst); + } + ifp->state->reason = "STATIC"; + ipv4_buildroutes(); + script_run(ifp); + if (ifo->options & DHCPCD_INFORM) { + ifp->state->state = DHS_INFORM; + ifp->state->xid = dhcp_xid(ifp); + ifp->state->lease.server.s_addr = + dst ? dst->s_addr : INADDR_ANY; + ifp->addr = *addr; + ifp->net = *net; + dhcp_inform(ifp); + } } diff --git a/bind.h b/ipv4.h similarity index 81% rename from bind.h rename to ipv4.h index 375a0f3c..a13f4478 100644 --- a/bind.h +++ b/ipv4.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2008 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,15 +25,15 @@ * SUCH DAMAGE. */ -#ifndef BIND_H -#define BIND_H +#ifndef IPV4_H +#define IPV4_H -#include "config.h" -#ifdef THERE_IS_NO_FORK -# define daemonise() {} -#else -pid_t daemonise(void); -#endif +#include "net.h" + +void ipv4_buildroutes(void); +void ipv4_applyaddr(void *); +int ipv4_routedeleted(const struct rt *); -void bind_interface(void *); +void ipv4_handleifa(int, const char *, + struct in_addr *, struct in_addr *, struct in_addr *); #endif diff --git a/ipv4ll.c b/ipv4ll.c index 402e158c..f1d4b122 100644 --- a/ipv4ll.c +++ b/ipv4ll.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -94,7 +94,7 @@ ipv4ll_start(void *arg) if (ifp->addr.s_addr) { ifp->state->conflicts = 0; if (IN_LINKLOCAL(htonl(ifp->addr.s_addr))) { - send_arp_announce(ifp); + arp_announce(ifp); return; } } @@ -116,7 +116,7 @@ ipv4ll_start(void *arg) else ifp->state->offer = ipv4ll_make_lease(addr); ifp->state->lease.frominfo = 0; - send_arp_probe(ifp); + arp_probe(ifp); } void @@ -131,7 +131,7 @@ ipv4ll_handle_failure(void *arg) syslog(LOG_DEBUG, "%s: IPv4LL %d second defence failed", ifp->name, DEFEND_INTERVAL); - drop_dhcp(ifp, "EXPIRE"); + dhcp_drop(ifp, "EXPIRE"); ifp->state->conflicts = -1; } else { syslog(LOG_DEBUG, "%s: defended IPv4LL address", @@ -141,7 +141,7 @@ ipv4ll_handle_failure(void *arg) } } - close_sockets(ifp); + dhcp_close(ifp); free(ifp->state->offer); ifp->state->offer = NULL; eloop_timeout_delete(NULL, ifp); @@ -149,7 +149,7 @@ ipv4ll_handle_failure(void *arg) syslog(LOG_ERR, "%s: failed to acquire an IPv4LL address", ifp->name); ifp->state->interval = RATE_LIMIT_INTERVAL / 2; - start_discover(ifp); + dhcp_discover(ifp); } else { eloop_timeout_add_sec(PROBE_WAIT, ipv4ll_start, ifp); } diff --git a/ipv6.c b/ipv6.c index 93d2e1cf..ae6c5df3 100644 --- a/ipv6.c +++ b/ipv6.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ #include #include "common.h" -#include "configure.h" #include "dhcpcd.h" #include "dhcp6.h" #include "ipv6.h" diff --git a/ipv6ns.c b/ipv6ns.c index 02e7f82e..120ec2a1 100644 --- a/ipv6ns.c +++ b/ipv6ns.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -47,11 +47,11 @@ #define ELOOP_QUEUE 1 #include "common.h" -#include "configure.h" #include "dhcpcd.h" #include "eloop.h" #include "ipv6.h" #include "ipv6ns.h" +#include "script.h" #define MIN_RANDOM_FACTOR (500 * 1000) /* milliseconds in usecs */ #define MAX_RANDOM_FACTOR (1500 * 1000) /* milliseconds in usecs */ @@ -169,7 +169,7 @@ ipv6ns_unreachable(void *arg) rap->iface->name, rap->sfrom); rap->expired = 1; ipv6_buildroutes(); - run_script_reason(rap->iface, "ROUTERADVERT"); /* XXX not RA */ + script_runreason(rap->iface, "ROUTERADVERT"); /* XXX not RA */ } void @@ -342,7 +342,7 @@ ipv6ns_handledata(_unused void *arg) ifp->name, sfrom); rap->expired = 1; ipv6_buildroutes(); - run_script_reason(ifp, "ROUTERADVERT"); + script_runreason(ifp, "ROUTERADVERT"); return; } diff --git a/ipv6rs.c b/ipv6rs.c index 3b18750a..2d64ba98 100644 --- a/ipv6rs.c +++ b/ipv6rs.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -45,15 +45,14 @@ #include #define ELOOP_QUEUE 1 -#include "bind.h" #include "common.h" -#include "configure.h" #include "dhcpcd.h" #include "dhcp6.h" #include "eloop.h" #include "ipv6.h" #include "ipv6ns.h" #include "ipv6rs.h" +#include "script.h" /* Debugging Router Solicitations is a lot of spam, so disable it */ //#define DEBUG_RS @@ -756,13 +755,13 @@ ipv6rs_handledata(_unused void *arg) if (options & DHCPCD_IPV6RA_OWN && !(options & DHCPCD_TEST)) ipv6_addaddrs(ifp, &rap->addrs); if (options & DHCPCD_TEST) { - run_script_reason(ifp, "TEST"); + script_runreason(ifp, "TEST"); goto handle_flag; } ipv6_buildroutes(); /* We will get run by the expire function */ if (rap->lifetime) - run_script_reason(ifp, "ROUTERADVERT"); + script_runreason(ifp, "ROUTERADVERT"); /* If we don't require RDNSS then set has_dns = 1 so we fork */ if (!(ifp->state->options->options & DHCPCD_IPV6RA_REQRDNSS)) @@ -1070,7 +1069,7 @@ ipv6rs_expire(void *arg) eloop_timeout_add_tv(&next, ipv6rs_expire, ifp); if (expired) { ipv6_buildroutes(); - run_script_reason(ifp, "ROUTERADVERT"); + script_runreason(ifp, "ROUTERADVERT"); } } @@ -1125,6 +1124,6 @@ ipv6rs_drop(struct interface *ifp) ipv6rs_drop_ra(rap); } } - run_script_reason(ifp, "ROUTERADVERT"); + script_runreason(ifp, "ROUTERADVERT"); } } diff --git a/script.c b/script.c new file mode 100644 index 00000000..870d9410 --- /dev/null +++ b/script.c @@ -0,0 +1,470 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2013 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 +/* We can't include spawn.h here because it may not exist. + * config.h will pull it in, or our compat one. */ +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "if-options.h" +#include "if-pref.h" +#include "ipv6rs.h" +#include "net.h" +#include "script.h" +#include "signals.h" + +#define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" + +static const char *if_params[] = { + "interface", + "reason", + "pid", + "ifmetric", + "ifwireless", + "ifflags", + "ssid", + "profile", + "interface_order", + NULL +}; + +void +if_printoptions(void) +{ + const char **p; + + for (p = if_params; *p; p++) + printf(" - %s\n", *p); +} + +static int +exec_script(char *const *argv, char *const *env) +{ + pid_t pid; + posix_spawnattr_t attr; + short flags; + sigset_t defsigs; + int i; + + /* posix_spawn is a safe way of executing another image + * and changing signals back to how they should be. */ + if (posix_spawnattr_init(&attr) == -1) + return -1; + flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; + posix_spawnattr_setflags(&attr, flags); + sigemptyset(&defsigs); + for (i = 0; i < handle_sigs[i]; i++) + sigaddset(&defsigs, handle_sigs[i]); + posix_spawnattr_setsigdefault(&attr, &defsigs); + posix_spawnattr_setsigmask(&attr, &dhcpcd_sigset); + errno = 0; + i = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); + if (i) { + errno = i; + return -1; + } + return pid; +} + +static char * +make_var(const char *prefix, const char *var) +{ + size_t len; + char *v; + + len = strlen(prefix) + strlen(var) + 2; + v = xmalloc(len); + snprintf(v, len, "%s_%s", prefix, var); + return v; +} + + +static void +append_config(char ***env, ssize_t *len, + const char *prefix, const char *const *config) +{ + ssize_t i, j, e1; + char **ne, *eq; + + if (config == NULL) + return; + + ne = *env; + for (i = 0; config[i] != NULL; i++) { + eq = strchr(config[i], '='); + e1 = eq - config[i] + 1; + for (j = 0; j < *len; j++) { + if (strncmp(ne[j] + strlen(prefix) + 1, + config[i], e1) == 0) + { + free(ne[j]); + ne[j] = make_var(prefix, config[i]); + break; + } + } + if (j == *len) { + j++; + ne = xrealloc(ne, sizeof(char *) * (j + 1)); + ne[j - 1] = make_var(prefix, config[i]); + *len = j; + } + } + *env = ne; +} + +static size_t +arraytostr(const char *const *argv, char **s) +{ + const char *const *ap; + char *p; + size_t len, l; + + len = 0; + ap = argv; + while (*ap) + len += strlen(*ap++) + 1; + *s = p = xmalloc(len); + ap = argv; + while (*ap) { + l = strlen(*ap) + 1; + memcpy(p, *ap, l); + p += l; + ap++; + } + return len; +} + +static ssize_t +make_env(const struct interface *iface, const char *reason, char ***argv) +{ + char **env, *p; + ssize_t e, elen, l; + const struct if_options *ifo = iface->state->options; + const struct interface *ifp; + int dhcp, dhcp6, ra; + const struct dhcp6_state *d6_state; + + dhcp = dhcp6 = ra = 0; + d6_state = D6_STATE(iface); + if (strcmp(reason, "TEST") == 0) { + if (d6_state && d6_state->new) + dhcp6 = 1; + else if (ipv6rs_has_ra(iface)) + ra = 1; + else + dhcp = 1; + } else if (reason[strlen(reason) - 1] == '6') + dhcp6 = 1; + else if (strcmp(reason, "ROUTERADVERT") == 0) + ra = 1; + else + dhcp = 1; + + /* When dumping the lease, we only want to report interface and + reason - the other interface variables are meaningless */ + if (options & DHCPCD_DUMPLEASE) + elen = 2; + else + elen = 10; + + /* Make our env */ + env = xmalloc(sizeof(char *) * (elen + 1)); + e = strlen("interface") + strlen(iface->name) + 2; + env[0] = xmalloc(e); + snprintf(env[0], e, "interface=%s", iface->name); + e = strlen("reason") + strlen(reason) + 2; + env[1] = xmalloc(e); + snprintf(env[1], e, "reason=%s", reason); + if (options & DHCPCD_DUMPLEASE) + goto dumplease; + + e = 20; + env[2] = xmalloc(e); + snprintf(env[2], e, "pid=%d", getpid()); + env[3] = xmalloc(e); + snprintf(env[3], e, "ifmetric=%d", iface->metric); + env[4] = xmalloc(e); + snprintf(env[4], e, "ifwireless=%d", iface->wireless); + env[5] = xmalloc(e); + snprintf(env[5], e, "ifflags=%u", iface->flags); + env[6] = xmalloc(e); + snprintf(env[6], e, "ifmtu=%d", get_mtu(iface->name)); + l = e = strlen("interface_order="); + for (ifp = ifaces; ifp; ifp = ifp->next) + e += strlen(ifp->name) + 1; + p = env[7] = xmalloc(e); + strlcpy(p, "interface_order=", e); + e -= l; + p += l; + for (ifp = ifaces; ifp; ifp = ifp->next) { + l = strlcpy(p, ifp->name, e); + p += l; + e -= l; + *p++ = ' '; + e--; + } + *--p = '\0'; + if (strcmp(reason, "TEST") == 0) { + env[8] = strdup("if_up=false"); + env[9] = strdup("if_down=false"); + } else if ((dhcp && iface->state->new) || + (dhcp6 && d6_state->new) || + (ra && ipv6rs_has_ra(iface))) + { + env[8] = strdup("if_up=true"); + env[9] = strdup("if_down=false"); + } else { + env[8] = strdup("if_up=false"); + env[9] = strdup("if_down=true"); + } + if (*iface->state->profile) { + e = strlen("profile=") + strlen(iface->state->profile) + 2; + env[elen] = xmalloc(e); + snprintf(env[elen++], e, "profile=%s", iface->state->profile); + } + if (iface->wireless) { + e = strlen("new_ssid=") + strlen(iface->ssid) + 2; + if (iface->state->new != NULL || + strcmp(iface->state->reason, "CARRIER") == 0) + { + env = xrealloc(env, sizeof(char *) * (elen + 2)); + env[elen] = xmalloc(e); + snprintf(env[elen++], e, "new_ssid=%s", iface->ssid); + } + if (iface->state->old != NULL || + strcmp(iface->state->reason, "NOCARRIER") == 0) + { + env = xrealloc(env, sizeof(char *) * (elen + 2)); + env[elen] = xmalloc(e); + snprintf(env[elen++], e, "old_ssid=%s", iface->ssid); + } + } + if (dhcp && iface->state->old) { + e = configure_env(NULL, NULL, iface->state->old, iface); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += configure_env(env + elen, "old", + iface->state->old, iface); + } + append_config(&env, &elen, "old", + (const char *const *)ifo->config); + } + if (dhcp6 && d6_state->old) { + e = dhcp6_env(NULL, NULL, iface, + d6_state->old, d6_state->old_len); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += dhcp6_env(env + elen, "old", iface, + d6_state->old, d6_state->old_len); + } + } + +dumplease: + if (dhcp && iface->state->new) { + e = configure_env(NULL, NULL, iface->state->new, iface); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += configure_env(env + elen, "new", + iface->state->new, iface); + } + append_config(&env, &elen, "new", + (const char *const *)ifo->config); + } + if (dhcp6 && d6_state->new) { + e = dhcp6_env(NULL, NULL, iface, + d6_state->new, d6_state->new_len); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += dhcp6_env(env + elen, "new", iface, + d6_state->new, d6_state->new_len); + } + } + if (ra) { + e = ipv6rs_env(NULL, NULL, iface); + if (e > 0) { + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + elen += ipv6rs_env(env + elen, NULL, iface); + } + } + + /* Add our base environment */ + if (ifo->environ) { + e = 0; + while (ifo->environ[e++]) + ; + env = xrealloc(env, sizeof(char *) * (elen + e + 1)); + e = 0; + while (ifo->environ[e]) { + env[elen + e] = xstrdup(ifo->environ[e]); + e++; + } + elen += e; + } + env[elen] = '\0'; + + *argv = env; + return elen; +} + +static int +send_interface1(int fd, const struct interface *iface, const char *reason) +{ + char **env, **ep, *s; + ssize_t elen; + struct iovec iov[2]; + int retval; + + make_env(iface, reason, &env); + elen = arraytostr((const char *const *)env, &s); + iov[0].iov_base = &elen; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = s; + iov[1].iov_len = elen; + retval = writev(fd, iov, 2); + ep = env; + while (*ep) + free(*ep++); + free(env); + free(s); + return retval; +} + +int +send_interface(int fd, const struct interface *iface) +{ + int retval = 0; + if (send_interface1(fd, iface, iface->state->reason) == -1) + retval = -1; + if (ipv6rs_has_ra(iface)) { + if (send_interface1(fd, iface, "ROUTERADVERT") == -1) + retval = -1; + } + if (D6_STATE_RUNNING(iface)) { + if (send_interface1(fd, iface, "INFORM6") == -1) + retval = -1; + } + return retval; +} + +int +script_runreason(const struct interface *iface, const char *reason) +{ + char *const argv[2] = { UNCONST(iface->state->options->script), NULL }; + char **env = NULL, **ep; + char *path, *bigenv; + ssize_t e, elen = 0; + pid_t pid; + int status = 0; + const struct fd_list *fd; + struct iovec iov[2]; + + if (iface->state->options->script == NULL || + iface->state->options->script[0] == '\0' || + strcmp(iface->state->options->script, "/dev/null") == 0) + return 0; + + if (reason == NULL) + reason = iface->state->reason; + syslog(LOG_DEBUG, "%s: executing `%s', reason %s", + iface->name, argv[0], reason); + + /* Make our env */ + elen = make_env(iface, reason, &env); + env = xrealloc(env, sizeof(char *) * (elen + 2)); + /* Add path to it */ + path = getenv("PATH"); + if (path) { + e = strlen("PATH") + strlen(path) + 2; + env[elen] = xmalloc(e); + snprintf(env[elen], e, "PATH=%s", path); + } else + env[elen] = xstrdup(DEFAULT_PATH); + env[++elen] = '\0'; + + pid = exec_script(argv, env); + if (pid == -1) + syslog(LOG_ERR, "%s: %s: %m", __func__, argv[0]); + else if (pid != 0) { + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + syslog(LOG_ERR, "waitpid: %m"); + status = 0; + break; + } + } + if (WIFEXITED(status)) { + if (WEXITSTATUS(status)) + syslog(LOG_ERR, + "%s: %s: WEXITSTATUS %d", + __func__, argv[0], WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) + syslog(LOG_ERR, "%s: %s: %s", + __func__, argv[0], strsignal(WTERMSIG(status))); + } + + /* Send to our listeners */ + bigenv = NULL; + for (fd = control_fds; fd != NULL; fd = fd->next) { + if (fd->listener) { + if (bigenv == NULL) { + elen = arraytostr((const char *const *)env, + &bigenv); + iov[0].iov_base = &elen; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = bigenv; + iov[1].iov_len = elen; + } + if (writev(fd->fd, iov, 2) == -1) + syslog(LOG_ERR, "%s: writev: %m", __func__); + } + } + free(bigenv); + + /* Cleanup */ + ep = env; + while (*ep) + free(*ep++); + free(env); + return WEXITSTATUS(status); +} diff --git a/configure.h b/script.h similarity index 81% rename from configure.h rename to script.h index 5e5090fa..50e142cc 100644 --- a/configure.h +++ b/script.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2012 Roy Marples + * Copyright (c) 2006-2013 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,17 +25,14 @@ * SUCH DAMAGE. */ -#ifndef DHCPCONFIG_H -#define DHCPCONFIG_H +#ifndef SCRIPT_H +#define SCRIPT_H #include "net.h" void if_printoptions(void); int send_interface(int, const struct interface *); -int run_script_reason(const struct interface *, const char *); -void build_routes(void); -int configure(struct interface *); -int route_deleted(const struct rt *); +int script_runreason(const struct interface *, const char *); -#define run_script(ifp) run_script_reason(ifp, (ifp)->state->reason); +#define script_run(ifp) script_runreason(ifp, (ifp)->state->reason); #endif