# Copyright 2008 Roy Marples <roy@marples.name>
PROG= dhcpcd
-SRCS= common.c dhcp.c dhcpcd.c logger.c net.c signals.c
-SRCS+= configure.c client.c
+SRCS= arp.c bind.c common.c dhcp.c dhcpcd.c duid.c eloop.c
+SRCS+= if-options.c ipv4ll.c logger.c net.c signals.c
+SRCS+= configure.c
SRCS+= ${SRC_IF} ${SRC_PF}
LIBEXECDIR?= ${PREFIX}/libexec
Compatibility
-------------
-If you require compatibility with dhcpcd-3 and older style variables,
-you can install 50-dhcpcd-compat into the directory $LIBEXECDIR/dhcpcd-hooks
-We don't install this by default.
-You should also add -DCMDLINE_COMPAT to your CPPFLAGS if you need to be fully
-commandline compatible with prior versions.
-
-dhcpcd-3 enabled DUID support by default - this has changed in dhcpcd-4.
-You can enable it via the --duid, -D command line option or by using the
-duid directive in dhcpcd.conf.
-If CMDLINE_COMPAT is defined the we renable DUID support by default IF
-the dhcpcd.duid file exits. This keeps the clients working as they were,
-which is good.
-
-dhcpcd-4 is NOT fully commandline compatible with dhcpcd-2 and older and
-changes the meaning of some options.
+dhcpcd-4.1 is only fully command line compatible with dhcpcd-4.0
+For compatibility with older versions, use dhcpcd-4.0
ChangeLog
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "bind.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+#include "logger.h"
+#include "net.h"
+
+static void
+handle_arp_failure(struct interface *iface)
+{
+ if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) {
+ handle_ipv4ll_failure(iface);
+ return;
+ }
+ send_decline(iface);
+ close_sockets(iface);
+ add_timeout_sec(DHCP_ARP_FAIL, start_interface, iface);
+}
+
+void
+handle_arp_packet(struct interface *iface)
+{
+ struct arphdr reply;
+ uint32_t reply_s;
+ uint32_t reply_t;
+ uint8_t arp_reply[sizeof(reply) + 2 * sizeof(reply_s) + 2 * HWADDR_LEN];
+ uint8_t *hw_s, *hw_t;
+ ssize_t bytes;
+ struct if_state *state = iface->state;
+
+ state->fail.s_addr = 0;
+ for(;;) {
+ bytes = get_raw_packet(iface, ETHERTYPE_ARP,
+ arp_reply, sizeof(arp_reply));
+ if (bytes == 0 || bytes == -1)
+ return;
+ /* We must have a full ARP header */
+ if ((size_t)bytes < sizeof(reply))
+ continue;
+ memcpy(&reply, arp_reply, sizeof(reply));
+ /* Protocol must be IP. */
+ if (reply.ar_pro != htons(ETHERTYPE_IP))
+ continue;
+ if (reply.ar_pln != sizeof(reply_s))
+ continue;
+ /* Only these types are recognised */
+ if (reply.ar_op != htons(ARPOP_REPLY) &&
+ reply.ar_op != htons(ARPOP_REQUEST))
+ continue;
+
+ /* Get pointers to the hardware addreses */
+ hw_s = arp_reply + sizeof(reply);
+ hw_t = hw_s + reply.ar_hln + reply.ar_pln;
+ /* Ensure we got all the data */
+ if ((hw_t + reply.ar_hln + reply.ar_pln) - arp_reply > bytes)
+ continue;
+ /* Ignore messages from ourself */
+ if (reply.ar_hln == iface->hwlen &&
+ memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0)
+ continue;
+ /* Copy out the IP addresses */
+ memcpy(&reply_s, hw_s + reply.ar_hln, reply.ar_pln);
+ memcpy(&reply_t, hw_t + reply.ar_hln, reply.ar_pln);
+
+ /* Check for conflict */
+ if (state->offer &&
+ (reply_s == state->offer->yiaddr ||
+ (reply_s == 0 && reply_t == state->offer->yiaddr)))
+ state->fail.s_addr = state->offer->yiaddr;
+
+ /* Handle IPv4LL conflicts */
+ if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) &&
+ (reply_s == iface->addr.s_addr ||
+ (reply_s == 0 && reply_t == iface->addr.s_addr)))
+ state->fail.s_addr = iface->addr.s_addr;
+
+ if (state->fail.s_addr) {
+ logger(LOG_ERR, "%s: hardware address %s claims %s",
+ iface->name,
+ hwaddr_ntoa((unsigned char *)hw_s,
+ (size_t)reply.ar_hln),
+ inet_ntoa(state->fail));
+ errno = EEXIST;
+ handle_arp_failure(iface);
+ return;
+ }
+ }
+}
+
+void
+send_arp_announce(struct interface *iface)
+{
+ struct if_state *state = iface->state;
+ struct timeval tv;
+
+ if (iface->arp_fd == -1) {
+ open_socket(iface, ETHERTYPE_ARP);
+ add_event(iface->arp_fd, handle_arp_packet, iface);
+ }
+ if (++state->claims < ANNOUNCE_NUM)
+ logger(LOG_DEBUG,
+ "%s: sending ARP announce (%d of %d), "
+ "next in %d.00 seconds",
+ iface->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
+ else
+ logger(LOG_DEBUG,
+ "%s: sending ARP announce (%d of %d)",
+ iface->name, state->claims, ANNOUNCE_NUM);
+ if (send_arp(iface, ARPOP_REQUEST,
+ state->new->yiaddr, state->new->yiaddr) == -1)
+ logger(LOG_ERR, "send_arp: %s", strerror(errno));
+ if (state->claims < ANNOUNCE_NUM) {
+ add_timeout_sec(ANNOUNCE_WAIT, send_arp_announce, iface);
+ return;
+ }
+ if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
+ /* We should pretend to be at the end
+ * of the DHCP negotation cycle */
+ state->interval = 64;
+ state->probes = 0;
+ state->claims = 0;
+ tv.tv_sec = state->interval - DHCP_RAND_MIN;
+ tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
+ tv.tv_sec = 3; /* test easier */
+ timernorm(&tv);
+ add_timeout_tv(&tv, start_discover, iface);
+ } else {
+ delete_event(iface->arp_fd);
+ close(iface->arp_fd);
+ iface->arp_fd = -1;
+ }
+}
+
+void
+send_arp_probe(struct interface *iface)
+{
+ struct if_state *state = iface->state;
+ struct in_addr addr;
+ struct timeval tv;
+
+ if (iface->arp_fd == -1) {
+ open_socket(iface, ETHERTYPE_ARP);
+ add_event(iface->arp_fd, handle_arp_packet, iface);
+ }
+ if (state->probes == 0) {
+ addr.s_addr = state->offer->yiaddr;
+ logger(LOG_INFO, "%s: checking %s is available"
+ " on attached networks",
+ iface->name, inet_ntoa(addr));
+ }
+ if (++state->probes < PROBE_NUM) {
+ tv.tv_sec = PROBE_MIN;
+ tv.tv_usec = arc4random() % (PROBE_MAX_U - PROBE_MIN_U);
+ timernorm(&tv);
+ add_timeout_tv(&tv, send_arp_probe, iface);
+ } else {
+ tv.tv_sec = ANNOUNCE_WAIT;
+ tv.tv_usec = 0;
+ if (IN_LINKLOCAL(htonl(state->offer->yiaddr)))
+ add_timeout_tv(&tv, bind_interface, iface);
+ else
+ add_timeout_tv(&tv, send_request, iface);
+ }
+ logger(LOG_DEBUG,
+ "%s: sending ARP probe (%d of %d), next in %0.2f seconds",
+ iface->name, state->probes, PROBE_NUM, timeval_to_double(&tv));
+ if (send_arp(iface, ARPOP_REQUEST, 0, state->offer->yiaddr) == -1)
+ logger(LOG_ERR, "send_arp: %s", strerror(errno));
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef ARP_H
+#define ARP_H
+
+#include "dhcpcd.h"
+
+/* These are for IPV4LL, RFC 3927.
+ * We put them here as we use the timings for all ARP foo. */
+#define PROBE_WAIT 1
+#define PROBE_NUM 3
+#define PROBE_MIN 1
+#define PROBE_MAX 2
+#define ANNOUNCE_WAIT 2
+#define ANNOUNCE_NUM 2
+#define ANNOUNCE_INTERVAL 2
+#define MAX_CONFLICTS 10
+#define RATE_LIMIT_INTERVAL 60
+#define DEFEND_INTERVAL 10
+
+void send_arp_announce(struct interface *);
+void send_arp_probe(struct interface *);
+void handle_arp_packet(struct interface *);
+#endif
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "bind.h"
+#include "common.h"
+#include "configure.h"
+#include "dhcpcd.h"
+#include "dhcpf.h"
+#include "eloop.h"
+#include "if-options.h"
+#include "logger.h"
+#include "net.h"
+#include "signals.h"
+
+static int daemonised = 0;
+int can_daemonise = 1;
+
+#ifndef THERE_IS_NO_FORK
+pid_t
+daemonise(void)
+{
+ pid_t pid;
+ sigset_t full;
+ sigset_t old;
+ char buf = '\0';
+ int sidpipe[2];
+
+ if (daemonised || !can_daemonise)
+ return 0;
+ sigfillset(&full);
+ sigprocmask(SIG_SETMASK, &full, &old);
+ /* Setup a signal pipe so parent knows when to exit. */
+ if (pipe(sidpipe) == -1) {
+ logger(LOG_ERR, "pipe: %s", strerror(errno));
+ return -1;
+ }
+ logger(LOG_INFO, "forking to background");
+ switch (pid = fork()) {
+ case -1:
+ logger(LOG_ERR, "fork: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+ case 0:
+ setsid();
+ /* Notify parent it's safe to exit as we've detached. */
+ close(sidpipe[0]);
+ write(sidpipe[1], &buf, 1);
+ close(sidpipe[1]);
+ close_fds();
+ break;
+ default:
+ signal_reset();
+ /* Wait for child to detach */
+ close(sidpipe[1]);
+ read(sidpipe[0], &buf, 1);
+ close(sidpipe[0]);
+ break;
+ }
+ /* Done with the fd now */
+ if (pid != 0) {
+ writepid(pidfd, pid);
+ close(pidfd);
+ pidfd = -1;
+ exit(EXIT_SUCCESS);
+ }
+ sigprocmask(SIG_SETMASK, &old, NULL);
+ return pid;
+}
+#endif
+
+void bind_interface(struct interface *iface)
+{
+ struct if_state *state = iface->state;
+ struct if_options *ifo = state->options;
+ struct dhcp_lease *lease = &state->lease;
+ struct timeval tv;
+ const char *reason = NULL;
+
+ delete_timeout(handle_exit_timeout, NULL);
+ if (clock_monotonic)
+ get_monotonic(&lease->boundtime);
+ state->state = DHS_BOUND;
+ state->xid = 0;
+ free(state->old);
+ state->old = state->new;
+ state->new = state->offer;
+ state->offer = NULL;
+ get_lease(lease, state->new);
+ if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
+ logger(LOG_INFO, "%s: using IPv4LL address %s",
+ iface->name, inet_ntoa(lease->addr));
+ lease->leasetime = ~0U;
+ reason = "IPV4LL";
+ } else if (ifo->options & DHCPCD_INFORM) {
+ if (ifo->request_address.s_addr != 0)
+ lease->addr.s_addr = ifo->request_address.s_addr;
+ else
+ lease->addr.s_addr = iface->addr.s_addr;
+ logger(LOG_INFO, "%s: received approval for %s", iface->name,
+ inet_ntoa(lease->addr));
+ lease->leasetime = ~0U;
+ reason = "INFORM";
+ } else {
+ if (gettimeofday(&tv, NULL) == 0)
+ lease->leasedfrom = tv.tv_sec;
+ if (lease->frominfo)
+ reason = "TIMEOUT";
+ if (lease->leasetime == ~0U) {
+ lease->renewaltime = lease->rebindtime = lease->leasetime;
+ logger(LOG_INFO, "%s: leased %s for infinity",
+ iface->name, inet_ntoa(lease->addr));
+ } else {
+ if (lease->rebindtime >= lease->leasetime) {
+ lease->rebindtime = lease->leasetime * T2;
+ logger(LOG_ERR,
+ "%s: rebind time greater than lease "
+ "time, forcing to %u seconds",
+ iface->name, lease->rebindtime);
+ }
+ if (lease->renewaltime > lease->rebindtime) {
+ lease->renewaltime = lease->leasetime * T1;
+ logger(LOG_ERR,
+ "%s: renewal time greater than rebind "
+ "time, forcing to %u seconds",
+ iface->name, lease->renewaltime);
+ }
+ if (!lease->renewaltime)
+ lease->renewaltime = lease->leasetime * T1;
+ if (!lease->rebindtime)
+ lease->rebindtime = lease->leasetime * T2;
+ logger(LOG_INFO,
+ "%s: leased %s for %u seconds", iface->name,
+ inet_ntoa(lease->addr), lease->leasetime);
+ }
+ }
+ if (!reason) {
+ if (state->old) {
+ if (state->old->yiaddr == state->new->yiaddr &&
+ lease->server.s_addr)
+ reason = "RENEW";
+ else
+ reason = "REBIND";
+ } else
+ reason = "BOUND";
+ }
+ if (lease->leasetime == ~0U)
+ lease->renewaltime = lease->rebindtime = lease->leasetime;
+ else {
+ add_timeout_sec(lease->renewaltime, start_renew, iface);
+ add_timeout_sec(lease->rebindtime, start_rebind, iface);
+ add_timeout_sec(lease->leasetime, start_expire, iface);
+ }
+ configure(iface, reason);
+ daemonise();
+ if (ifo->options & DHCPCD_ARP) {
+ state->claims = 0;
+ send_arp_announce(iface);
+ }
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef BIND_H
+#define BIND_H
+
+#include "config.h"
+#include "dhcpcd.h"
+#ifdef THERE_IS_NO_FORK
+#define daemonise() {}
+#else
+pid_t daemonise(void);
+#endif
+
+extern int can_daemonise;
+void bind_interface(struct interface *);
+#endif
+++ /dev/null
-/*
- * dhcpcd - DHCP client daemon
- * Copyright 2006-2008 Roy Marples <roy@marples.name>
- * All rights reserved
-
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-
-#ifdef __linux__
-# include <netinet/ether.h>
-#endif
-
-#include <errno.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "config.h"
-#include "common.h"
-#include "client.h"
-#include "configure.h"
-#include "dhcp.h"
-#include "dhcpcd.h"
-#include "net.h"
-#include "logger.h"
-#include "signals.h"
-
-#define IPV4LL_LEASETIME 2
-
-/* Some platforms don't define INFTIM */
-#ifndef INFTIM
-# define INFTIM -1
-#endif
-
-#define STATE_INIT 0
-#define STATE_DISCOVERING 1
-#define STATE_REQUESTING 2
-#define STATE_BOUND 3
-#define STATE_RENEWING 4
-#define STATE_REBINDING 5
-#define STATE_REBOOT 6
-#define STATE_RENEW_REQUESTED 7
-#define STATE_INIT_IPV4LL 8
-#define STATE_PROBING 9
-#define STATE_ANNOUNCING 10
-
-/* Constants taken from RFC 2131. */
-#define T1 0.5
-#define T2 0.875
-#define DHCP_BASE 4
-#define DHCP_MAX 64
-#define DHCP_RAND_MIN -1
-#define DHCP_RAND_MAX 1
-#define DHCP_ARP_FAIL 10
-
-/* We should define a maximum for the NAK exponential backoff */
-#define NAKOFF_MAX 60
-
-#define SOCKET_CLOSED 0
-#define SOCKET_OPEN 1
-
-/* These are for IPV4LL, RFC 3927. */
-#define PROBE_WAIT 1
-#define PROBE_NUM 3
-#define PROBE_MIN 1
-#define PROBE_MAX 2
-#define ANNOUNCE_WAIT 2
-/* BSD systems always do a grauitous ARP when assigning an address,
- * so we can do one less announce. */
-#ifdef BSD
-# define ANNOUNCE_NUM 1
-#else
-# define ANNOUNCE_NUM 2
-#endif
-#define ANNOUNCE_INTERVAL 2
-#define MAX_CONFLICTS 10
-#define RATE_LIMIT_INTERVAL 60
-#define DEFEND_INTERVAL 10
-
-
-/* number of usecs in a second. */
-#define USECS_SECOND 1000000
-/* As we use timevals, we should use the usec part for
- * greater randomisation. */
-#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND
-#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND
-#define PROBE_MIN_U PROBE_MIN * USECS_SECOND
-#define PROBE_MAX_U PROBE_MAX * USECS_SECOND
-
-#define timernorm(tvp) \
- do { \
- while ((tvp)->tv_usec >= 1000000) { \
- (tvp)->tv_sec++; \
- (tvp)->tv_usec -= 1000000; \
- } \
- } while (0 /* CONSTCOND */);
-
-#define timerneg(tvp) ((tvp)->tv_sec < 0 || (tvp)->tv_usec < 0)
-
-struct if_state {
- int options;
- struct interface *interface;
- struct dhcp_message *offer;
- struct dhcp_message *new;
- struct dhcp_message *old;
- struct dhcp_lease lease;
- struct timeval timeout;
- struct timeval stop;
- struct timeval exit;
- int state;
- int messages;
- time_t nakoff;
- uint32_t xid;
- int socket;
- int *pid_fd;
- int signal_fd;
- int carrier;
- int probes;
- int claims;
- int conflicts;
- time_t defend;
- struct in_addr fail;
-};
-
-#define LINK_UP 1
-#define LINK_UNKNOWN 0
-#define LINK_DOWN -1
-
-struct dhcp_op {
- uint8_t value;
- const char *name;
-};
-
-static const struct dhcp_op const dhcp_ops[] = {
- { DHCP_DISCOVER, "DHCP_DISCOVER" },
- { DHCP_OFFER, "DHCP_OFFER" },
- { DHCP_REQUEST, "DHCP_REQUEST" },
- { DHCP_DECLINE, "DHCP_DECLINE" },
- { DHCP_ACK, "DHCP_ACK" },
- { DHCP_NAK, "DHCP_NAK" },
- { DHCP_RELEASE, "DHCP_RELEASE" },
- { DHCP_INFORM, "DHCP_INFORM" },
- { 0, NULL }
-};
-
-static const char *
-get_dhcp_op(uint8_t type)
-{
- const struct dhcp_op *d;
-
- for (d = dhcp_ops; d->name; d++)
- if (d->value == type)
- return d->name;
- return NULL;
-}
-
-#ifdef THERE_IS_NO_FORK
-#define daemonise(a,b) 0
-#else
-static int
-daemonise(struct if_state *state, const struct options *options)
-{
- pid_t pid;
- sigset_t full;
- sigset_t old;
- char buf = '\0';
- int sidpipe[2];
-
- if (state->options & DHCPCD_DAEMONISED ||
- !(options->options & DHCPCD_DAEMONISE))
- return 0;
-
- sigfillset(&full);
- sigprocmask(SIG_SETMASK, &full, &old);
-
- /* Setup a signal pipe so parent knows when to exit. */
- if (pipe(sidpipe) == -1) {
- logger(LOG_ERR,"pipe: %s", strerror(errno));
- return -1;
- }
-
- logger(LOG_DEBUG, "forking to background");
- switch (pid = fork()) {
- case -1:
- logger(LOG_ERR, "fork: %s", strerror(errno));
- exit(EXIT_FAILURE);
- /* NOTREACHED */
- case 0:
- setsid();
- /* Notify parent it's safe to exit as we've detached. */
- close(sidpipe[0]);
- write(sidpipe[1], &buf, 1);
- close(sidpipe[1]);
- close_fds();
- break;
- default:
- /* Reset signals as we're the parent about to exit. */
- signal_reset();
- /* Wait for child to detach */
- close(sidpipe[1]);
- read(sidpipe[0], &buf, 1);
- close(sidpipe[0]);
- break;
- }
-
- /* Done with the fd now */
- if (pid != 0) {
- writepid(*state->pid_fd, pid);
- close(*state->pid_fd);
- *state->pid_fd = -1;
- }
-
- sigprocmask(SIG_SETMASK, &old, NULL);
- if (pid == 0) {
- state->options |= DHCPCD_DAEMONISED;
- timerclear(&state->exit);
- return 0;
- }
- state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED;
- return -1;
-}
-#endif
-
-#define THIRTY_YEARS_IN_SECONDS 946707779
-static size_t
-get_duid(unsigned char *duid, const struct interface *iface)
-{
- FILE *f;
- uint16_t type = 0;
- uint16_t hw = 0;
- uint32_t ul;
- time_t t;
- int x = 0;
- unsigned char *p = duid;
- size_t len = 0, l = 0;
- char *buffer = NULL, *line, *option;
-
- /* If we already have a DUID then use it as it's never supposed
- * to change once we have one even if the interfaces do */
- if ((f = fopen(DUID, "r"))) {
- while ((get_line(&buffer, &len, f))) {
- line = buffer;
- while ((option = strsep(&line, " \t")))
- if (*option != '\0')
- break;
- if (!option || *option == '\0' || *option == '#')
- continue;
- l = hwaddr_aton(NULL, option);
- if (l && l <= DUID_LEN) {
- hwaddr_aton(duid, option);
- break;
- }
- l = 0;
- }
- fclose(f);
- free(buffer);
- if (l)
- return l;
- } else {
- if (errno != ENOENT)
- return 0;
- }
-
- /* No file? OK, lets make one based on our interface */
- if (!(f = fopen(DUID, "w")))
- return 0;
- type = htons(1); /* DUI-D-LLT */
- memcpy(p, &type, 2);
- p += 2;
- hw = htons(iface->family);
- memcpy(p, &hw, 2);
- p += 2;
- /* time returns seconds from jan 1 1970, but DUID-LLT is
- * seconds from jan 1 2000 modulo 2^32 */
- t = time(NULL) - THIRTY_YEARS_IN_SECONDS;
- ul = htonl(t & 0xffffffff);
- memcpy(p, &ul, 4);
- p += 4;
- /* Finally, add the MAC address of the interface */
- memcpy(p, iface->hwaddr, iface->hwlen);
- p += iface->hwlen;
- len = p - duid;
- x = fprintf(f, "%s\n", hwaddr_ntoa(duid, len));
- fclose(f);
- /* Failed to write the duid? scrub it, we cannot use it */
- if (x < 1) {
- len = 0;
- unlink(DUID);
- }
- return len;
-}
-
-static struct dhcp_message*
-ipv4ll_get_dhcp(uint32_t old_addr)
-{
- uint32_t u32;
- struct dhcp_message *dhcp;
- uint8_t *p;
-
- dhcp = xzalloc(sizeof(*dhcp));
- /* Put some LL options in */
- p = dhcp->options;
- *p++ = DHO_SUBNETMASK;
- *p++ = sizeof(u32);
- u32 = htonl(LINKLOCAL_MASK);
- memcpy(p, &u32, sizeof(u32));
- p += sizeof(u32);
- *p++ = DHO_BROADCAST;
- *p++ = sizeof(u32);
- u32 = htonl(LINKLOCAL_BRDC);
- memcpy(p, &u32, sizeof(u32));
- p += sizeof(u32);
- *p++ = DHO_END;
-
- for (;;) {
- dhcp->yiaddr = htonl(LINKLOCAL_ADDR |
- (((uint32_t)abs((int)arc4random())
- % 0xFD00) + 0x0100));
- if (dhcp->yiaddr != old_addr &&
- IN_LINKLOCAL(ntohl(dhcp->yiaddr)))
- break;
- }
- return dhcp;
-}
-
-static double
-timeval_to_double(struct timeval *tv)
-{
- return tv->tv_sec * 1.0 + tv->tv_usec * 1.0e-6;
-}
-
-static void
-get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp)
-{
- time_t t;
-
- lease->frominfo = 0;
- lease->addr.s_addr = dhcp->yiaddr;
-
- if (get_option_addr(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1)
- lease->net.s_addr = get_netmask(dhcp->yiaddr);
- if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) {
- /* Ensure that we can use the lease */
- t = 0;
- if (t + (time_t)lease->leasetime < t) {
- logger(LOG_WARNING, "lease of %u would overflow, "
- "treating as infinite", lease->leasetime);
- lease->leasetime = ~0U; /* Infinite lease */
- }
- } else
- lease->leasetime = DEFAULT_LEASETIME;
- if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0)
- lease->renewaltime = 0;
- if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0)
- lease->rebindtime = 0;
-}
-
-static int
-get_old_lease(struct if_state *state)
-{
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- struct dhcp_message *dhcp = NULL;
- struct timeval tv;
- unsigned int offset = 0;
- struct stat sb;
-
- if (stat(iface->leasefile, &sb) == -1) {
- if (errno != ENOENT)
- logger(LOG_ERR, "stat: %s", strerror(errno));
- goto eexit;
- }
- if (!IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
- logger(LOG_INFO, "trying to use old lease in `%s'",
- iface->leasefile);
- if ((dhcp = read_lease(iface)) == NULL) {
- logger(LOG_INFO, "read_lease: %s", strerror(errno));
- goto eexit;
- }
- get_lease(&state->lease, dhcp);
- lease->frominfo = 1;
- lease->leasedfrom = sb.st_mtime;
-
- /* Vitaly important we remove the server information here */
- state->lease.server.s_addr = 0;
- dhcp->servername[0] = '\0';
-
- if (!IN_LINKLOCAL(ntohl(dhcp->yiaddr))) {
- if (!(state->options & DHCPCD_LASTLEASE))
- goto eexit;
-
- /* Ensure that we can still use the lease */
- if (gettimeofday(&tv, NULL) == -1) {
- logger(LOG_ERR, "gettimeofday: %s", strerror(errno));
- goto eexit;
- }
-
- offset = tv.tv_sec - lease->leasedfrom;
- if (lease->leasedfrom &&
- tv.tv_sec - lease->leasedfrom > (time_t)lease->leasetime)
- {
- logger(LOG_ERR, "lease expired %u seconds ago",
- offset + lease->leasetime);
- /* Persistent interfaces should still try and use the
- * lease if we can't contact a DHCP server.
- * We just set the timeout to 1 second. */
- if (state->options & DHCPCD_PERSISTENT)
- offset = lease->renewaltime - 1;
- else
- goto eexit;
- }
- }
-
- if (lease->leasedfrom == 0)
- offset = 0;
- iface->start_uptime = uptime();
- state->timeout.tv_sec = lease->renewaltime - offset;
- free(state->old);
- state->old = state->new;
- state->new = NULL;
- state->offer = dhcp;
- return 0;
-
-eexit:
- lease->addr.s_addr = 0;
- free(dhcp);
- return -1;
-}
-
-static int
-client_setup(struct if_state *state, const struct options *options)
-{
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- struct in_addr addr;
- struct timeval tv;
- size_t len = 0;
- unsigned char *duid = NULL;
- uint32_t ul;
-
- state->state = STATE_INIT;
- state->nakoff = 1;
- state->options = options->options;
- timerclear(&tv);
-
- if (options->request_address.s_addr == 0 &&
- (options->options & DHCPCD_INFORM ||
- options->options & DHCPCD_REQUEST ||
- (options->options & DHCPCD_DAEMONISED &&
- !(options->options & DHCPCD_BACKGROUND))))
- {
- if (get_old_lease(state) != 0)
- return -1;
- timerclear(&state->timeout);
-
- if (!(options->options & DHCPCD_DAEMONISED) &&
- IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
- {
- logger(LOG_ERR, "cannot request a link local address");
- return -1;
- }
- } else {
- lease->addr.s_addr = options->request_address.s_addr;
- lease->net.s_addr = options->request_netmask.s_addr;
- }
-
- if (options->options & DHCPCD_REQUEST &&
- state->options & DHCPCD_ARP &&
- !state->offer)
- {
- state->offer = xzalloc(sizeof(*state->offer));
- state->offer->yiaddr = options->request_address.s_addr;
- state->state = STATE_PROBING;
- state->xid = arc4random();
- }
-
- /* If INFORMing, ensure the interface has the address */
- if (state->options & DHCPCD_INFORM &&
- has_address(iface->name, &lease->addr, &lease->net) < 1)
- {
- addr.s_addr = lease->addr.s_addr | ~lease->net.s_addr;
- logger(LOG_DEBUG, "adding IP address %s/%d",
- inet_ntoa(lease->addr), inet_ntocidr(lease->net));
- if (add_address(iface->name, &lease->addr,
- &lease->net, &addr) == -1)
- {
- logger(LOG_ERR, "add_address: %s", strerror(errno));
- return -1;
- }
- iface->addr.s_addr = lease->addr.s_addr;
- iface->net.s_addr = lease->net.s_addr;
- }
-
- if (*options->clientid) {
- iface->clientid = xmalloc(options->clientid[0] + 1);
- memcpy(iface->clientid,
- options->clientid, options->clientid[0] + 1);
- } else if (options->options & DHCPCD_CLIENTID) {
- if (options->options & DHCPCD_DUID) {
- duid = xmalloc(DUID_LEN);
- if ((len = get_duid(duid, iface)) == 0)
- logger(LOG_ERR, "get_duid: %s",
- strerror(errno));
- }
-
- if (len > 0) {
- logger(LOG_DEBUG, "DUID = %s",
- hwaddr_ntoa(duid, len));
-
- iface->clientid = xmalloc(len + 6);
- iface->clientid[0] = len + 5;
- iface->clientid[1] = 255; /* RFC 4361 */
-
- /* IAID is 4 bytes, so if the iface name is 4 bytes
- * or less, use it */
- ul = strlen(iface->name);
- if (ul < 5) {
- memcpy(iface->clientid + 2, iface->name, ul);
- if (ul < 4)
- memset(iface->clientid + 2 + ul,
- 0, 4 - ul);
- } else {
- /* Name isn't 4 bytes, so use the index */
- ul = htonl(if_nametoindex(iface->name));
- memcpy(iface->clientid + 2, &ul, 4);
- }
-
- memcpy(iface->clientid + 6, duid, len);
- free(duid);
- }
- if (len == 0) {
- len = iface->hwlen + 1;
- iface->clientid = xmalloc(len + 1);
- iface->clientid[0] = len;
- iface->clientid[1] = iface->family;
- memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen);
- }
- }
-
- if (state->options & DHCPCD_LINK) {
- open_link_socket(iface);
- switch (carrier_status(iface->name)) {
- case 0:
- state->carrier = LINK_DOWN;
- break;
- case 1:
- state->carrier = LINK_UP;
- break;
- default:
- state->carrier = LINK_UNKNOWN;
- }
- }
-
- if (options->timeout > 0 &&
- !(state->options & DHCPCD_DAEMONISED))
- {
- if (state->options & DHCPCD_IPV4LL) {
- state->stop.tv_sec = options->timeout;
- if (!(state->options & DHCPCD_BACKGROUND))
- state->exit.tv_sec = state->stop.tv_sec + 10;
- } else if (!(state->options & DHCPCD_BACKGROUND))
- state->exit.tv_sec = options->timeout;
- }
- return 0;
-}
-
-static int
-do_socket(struct if_state *state, int mode)
-{
- if (state->interface->raw_fd != -1) {
- close(state->interface->raw_fd);
- state->interface->raw_fd = -1;
- }
- if (mode == SOCKET_CLOSED) {
- if (state->interface->udp_fd != -1) {
- close(state->interface->udp_fd);
- state->interface->udp_fd = -1;
- }
- if (state->interface->arp_fd != -1) {
- close(state->interface->arp_fd);
- state->interface->arp_fd = -1;
- }
- }
-
- /* Always have the UDP socket open to avoid the kernel sending
- * ICMP unreachable messages. */
- /* For systems without SO_BINDTODEVICE, (ie BSD ones) we may get an
- * error or EADDRINUSE when binding to INADDR_ANY as another dhcpcd
- * instance could be running.
- * Oddly enough, we don't care about this as the socket is there
- * just to please the kernel - we don't care for reading from it. */
- if (mode == SOCKET_OPEN &&
- state->interface->udp_fd == -1 &&
- open_udp_socket(state->interface) == -1 &&
- (errno != EADDRINUSE || state->interface->addr.s_addr != 0))
- logger(LOG_ERR, "open_udp_socket: %s", strerror(errno));
-
- if (mode == SOCKET_OPEN)
- if (open_socket(state->interface, ETHERTYPE_IP) == -1) {
- logger(LOG_ERR, "open_socket: %s", strerror(errno));
- return -1;
- }
- state->socket = mode;
- return 0;
-}
-
-static ssize_t
-send_message(struct if_state *state, int type, const struct options *options)
-{
- struct dhcp_message *dhcp;
- uint8_t *udp;
- ssize_t len, r;
- struct in_addr from, to;
- in_addr_t a = 0;
-
- if (state->carrier == LINK_DOWN)
- return 0;
- if (type == DHCP_RELEASE)
- logger(LOG_DEBUG, "sending %s with xid 0x%x",
- get_dhcp_op(type), state->xid);
- else
- logger(LOG_DEBUG,
- "sending %s with xid 0x%x, next in %0.2f seconds",
- get_dhcp_op(type), state->xid,
- timeval_to_double(&state->timeout));
- state->messages++;
- if (state->messages < 0)
- state->messages = INT_MAX;
- /* If we couldn't open a UDP port for our IP address
- * then we cannot renew.
- * This could happen if our IP was pulled out from underneath us. */
- if (state->interface->udp_fd == -1) {
- a = state->interface->addr.s_addr;
- state->interface->addr.s_addr = 0;
- }
- len = make_message(&dhcp, state->interface, &state->lease, state->xid,
- type, options);
- if (state->interface->udp_fd == -1)
- state->interface->addr.s_addr = a;
- from.s_addr = dhcp->ciaddr;
- if (from.s_addr)
- to.s_addr = state->lease.server.s_addr;
- else
- to.s_addr = 0;
- if (to.s_addr && to.s_addr != INADDR_BROADCAST) {
- r = send_packet(state->interface, to, (uint8_t *)dhcp, len);
- if (r == -1)
- logger(LOG_ERR, "send_packet: %s", strerror(errno));
- } else {
- len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to);
- r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len);
- free(udp);
- if (r == -1)
- logger(LOG_ERR, "send_raw_packet: %s", strerror(errno));
- }
- free(dhcp);
- /* Failed to send the packet? Return to the init state */
- if (r == -1) {
- state->state = STATE_INIT;
- timerclear(&state->timeout);
- timerclear(&state->stop);
- do_socket(state, SOCKET_CLOSED);
- }
- return r;
-}
-
-static void
-drop_config(struct if_state *state, const char *reason,
- const struct options *options)
-{
- if (state->new || strcmp(reason, "FAIL") == 0) {
- configure(state->interface, reason, NULL, state->new,
- &state->lease, options, 0);
- free(state->old);
- state->old = NULL;
- free(state->new);
- state->new = NULL;
- }
- state->lease.addr.s_addr = 0;
-}
-
-static void
-reduce_timers(struct if_state *state, const struct timeval *tv)
-{
- if (timerisset(&state->exit)) {
- timersub(&state->exit, tv, &state->exit);
- if (!timerisset(&state->exit))
- state->exit.tv_sec = -1;
- }
- if (timerisset(&state->stop)) {
- timersub(&state->stop, tv, &state->stop);
- if (!timerisset(&state->stop))
- state->stop.tv_sec = -1;
- }
- if (timerisset(&state->timeout)) {
- timersub(&state->timeout, tv, &state->timeout);
- if (!timerisset(&state->timeout))
- state->timeout.tv_sec = -1;
- }
-}
-
-static struct timeval *
-get_lowest_timer(struct if_state *state)
-{
- struct timeval *ref = NULL;
-
- if (timerisset(&state->exit))
- ref = &state->exit;
- if (timerisset(&state->stop)) {
- if (!ref || timercmp(&state->stop, ref, <))
- ref = &state->stop;
- }
- if (timerisset(&state->timeout)) {
- if (!ref || timercmp(&state->timeout, ref, <))
- ref = &state->timeout;
- }
- return ref;
-}
-
-static int
-wait_for_fd(struct if_state *state, int *fd)
-{
- struct pollfd fds[4]; /* signal, link, raw, arp */
- struct interface *iface = state->interface;
- int i, r, nfds = 0, msecs = -1;
- struct timeval start, stop, diff, *ref;
- static int lastinf = 0;
-
- /* Ensure that we haven't already timed out */
- ref = get_lowest_timer(state);
- if (ref && timerneg(ref))
- return 0;
-
- /* We always listen to signals */
- fds[nfds].fd = state->signal_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- /* And links */
- if (iface->link_fd != -1) {
- fds[nfds].fd = iface->link_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- }
-
- if (state->lease.leasetime == ~0U &&
- state->state == STATE_BOUND)
- {
- if (!lastinf) {
- logger(LOG_DEBUG, "waiting for infinity");
- lastinf = 1;
- }
- ref = NULL;
- } else if (state->carrier == LINK_DOWN && !ref) {
- if (!lastinf) {
- logger(LOG_DEBUG, "waiting for carrier");
- lastinf = 1;
- }
- if (timerisset(&state->exit))
- ref = &state->exit;
- else
- ref = NULL;
- } else {
- if (iface->raw_fd != -1) {
- fds[nfds].fd = iface->raw_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- }
- if (iface->arp_fd != -1) {
- fds[nfds].fd = iface->arp_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- }
- }
-
- /* Wait and then reduce the timers.
- * If we reduce a timer to zero, set it negative to indicate timeout.
- * We cannot reliably use select as there is no guarantee we will
- * actually wait the whole time if greater than 31 days according
- * to POSIX. So we loop on poll if needed as it's limitation of
- * INT_MAX milliseconds is known. */
- for (;;) {
- get_monotonic(&start);
- if (ref) {
- lastinf = 0;
- if (ref->tv_sec > INT_MAX / 1000 ||
- (ref->tv_sec == INT_MAX / 1000 &&
- (ref->tv_usec + 999) / 1000 > INT_MAX % 1000))
- msecs = INT_MAX;
- else
- msecs = ref->tv_sec * 1000 +
- (ref->tv_usec + 999) / 1000;
- } else
- msecs = -1;
- r = poll(fds, nfds, msecs);
- get_monotonic(&stop);
- timersub(&stop, &start, &diff);
- reduce_timers(state, &diff);
- if (r == -1) {
- if (errno != EINTR)
- logger(LOG_ERR, "poll: %s", strerror(errno));
- return -1;
- }
- if (r)
- break;
- /* We should not have an infinite timeout if we get here */
- if (timerneg(ref))
- return 0;
- }
-
- /* We configured our array in the order we should deal with them */
- for (i = 0; i < nfds; i++)
- if (fds[i].revents & POLLIN) {
- *fd = fds[i].fd;
- return r;
- }
- return r;
-}
-
-static int
-handle_signal(int sig, struct if_state *state, const struct options *options)
-{
- struct dhcp_lease *lease = &state->lease;
-
- switch (sig) {
- case SIGINT:
- logger(LOG_INFO, "received SIGINT, stopping");
- if (!(state->options & DHCPCD_PERSISTENT))
- drop_config(state, "STOP", options);
- return -1;
- case SIGTERM:
- logger(LOG_INFO, "received SIGTERM, stopping");
- if (!(state->options & DHCPCD_PERSISTENT))
- drop_config(state, "STOP", options);
- return -1;
- case SIGALRM:
- logger(LOG_INFO, "received SIGALRM, renewing lease");
- do_socket(state, SOCKET_CLOSED);
- state->state = STATE_RENEW_REQUESTED;
- timerclear(&state->timeout);
- timerclear(&state->stop);
- return 1;
- case SIGHUP:
- logger(LOG_INFO, "received SIGHUP, releasing lease");
- if (lease->addr.s_addr &&
- !IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
- {
- do_socket(state, SOCKET_OPEN);
- state->xid = arc4random();
- send_message(state, DHCP_RELEASE, options);
- do_socket(state, SOCKET_CLOSED);
- }
- drop_config(state, "RELEASE", options);
- return -1;
- default:
- logger (LOG_ERR,
- "received signal %d, but don't know what to do with it",
- sig);
- }
-
- return 0;
-}
-
-static int bind_dhcp(struct if_state *state, const struct options *options)
-{
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- const char *reason = NULL;
- struct timeval start, stop, diff;
- int retval;
-
- free(state->old);
- state->old = state->new;
- state->new = state->offer;
- state->offer = NULL;
- state->messages = 0;
- state->conflicts = 0;
- state->defend = 0;
- timerclear(&state->exit);
- if (clock_monotonic)
- get_monotonic(&lease->boundtime);
-
- if (options->options & DHCPCD_INFORM) {
- if (options->request_address.s_addr != 0)
- lease->addr.s_addr = options->request_address.s_addr;
- else
- lease->addr.s_addr = iface->addr.s_addr;
- logger(LOG_INFO, "received approval for %s",
- inet_ntoa(lease->addr));
- state->state = STATE_BOUND;
- state->lease.leasetime = ~0U;
- timerclear(&state->stop);
- reason = "INFORM";
- } else if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
- get_lease(lease, state->new);
- logger(LOG_INFO, "using IPv4LL address %s",
- inet_ntoa(lease->addr));
- state->state = STATE_INIT;
- timerclear(&state->timeout);
- reason = "IPV4LL";
- } else {
- if (gettimeofday(&start, NULL) == 0)
- lease->leasedfrom = start.tv_sec;
-
- get_lease(lease, state->new);
- if (lease->frominfo)
- reason = "TIMEOUT";
-
- if (lease->leasetime == ~0U) {
- lease->renewaltime = lease->rebindtime = lease->leasetime;
- logger(LOG_INFO, "leased %s for infinity",
- inet_ntoa(lease->addr));
- state->state = STATE_BOUND;
- timerclear(&state->stop);
- } else {
- if (lease->rebindtime >= lease->leasetime) {
- lease->rebindtime = lease->leasetime * T2;
- logger(LOG_ERR,
- "rebind time greater than lease "
- "time, forcing to %u seconds",
- lease->rebindtime);
- }
- if (lease->renewaltime > lease->rebindtime) {
- lease->renewaltime = lease->leasetime * T1;
- logger(LOG_ERR,
- "renewal time greater than rebind time, "
- "forcing to %u seconds",
- lease->renewaltime);
- }
- if (!lease->renewaltime)
- lease->renewaltime = lease->leasetime * T1;
- if (!lease->rebindtime)
- lease->rebindtime = lease->leasetime * T2;
- logger(LOG_INFO,
- "leased %s for %u seconds",
- inet_ntoa(lease->addr), lease->leasetime);
- state->stop.tv_sec = lease->renewaltime;
- state->stop.tv_usec = 0;
- }
- state->state = STATE_BOUND;
- }
-
- state->xid = 0;
- timerclear(&state->timeout);
- if (!reason) {
- if (state->old) {
- if (state->old->yiaddr == state->new->yiaddr &&
- lease->server.s_addr)
- reason = "RENEW";
- else
- reason = "REBIND";
- } else
- reason = "BOUND";
- }
- /* If we have a monotonic clock we can safely substract the
- * script execution time from our timers.
- * Otherwise we can't as the script may update the real time. */
- if (clock_monotonic)
- get_monotonic(&start);
- retval = configure(iface, reason, state->new, state->old,
- &state->lease, options, 1);
- if (clock_monotonic) {
- get_monotonic(&stop);
- timersub(&stop, &start, &diff);
- reduce_timers(state, &diff);
- }
- if (retval != 0)
- return -1;
- return daemonise(state, options);
-}
-
-static int
-handle_timeout_fail(struct if_state *state, const struct options *options)
-{
- struct dhcp_lease *lease = &state->lease;
- struct interface *iface = state->interface;
- int gotlease = -1;
- const char *reason = NULL;
-
- timerclear(&state->stop);
- timerclear(&state->exit);
- if (state->state != STATE_DISCOVERING)
- state->messages = 0;
-
- switch (state->state) {
- case STATE_INIT: /* FALLTHROUGH */
- case STATE_DISCOVERING: /* FALLTHROUGH */
- case STATE_REQUESTING:
- if (IN_LINKLOCAL(ntohl(iface->addr.s_addr))) {
- if (!(state->options & DHCPCD_DAEMONISED))
- logger(LOG_ERR, "timed out");
- } else {
- if (iface->addr.s_addr != 0 &&
- !(state->options & DHCPCD_INFORM))
- logger(LOG_ERR, "lost lease");
- else if (state->carrier != LINK_DOWN ||
- !(state->options & DHCPCD_DAEMONISED))
- logger(LOG_ERR, "timed out");
- }
- do_socket(state, SOCKET_CLOSED);
- if (state->options & DHCPCD_INFORM ||
- state->options & DHCPCD_TEST)
- return -1;
-
- if (state->carrier != LINK_DOWN &&
- (state->options & DHCPCD_IPV4LL ||
- state->options & DHCPCD_LASTLEASE))
- gotlease = get_old_lease(state);
-
- if (state->carrier != LINK_DOWN &&
- state->options & DHCPCD_IPV4LL &&
- gotlease != 0)
- {
- logger(LOG_INFO, "probing for an IPV4LL address");
- free(state->offer);
- state->offer = ipv4ll_get_dhcp(0);
- gotlease = 0;
- }
-
- if (gotlease == 0 &&
- state->offer->yiaddr != iface->addr.s_addr)
- {
- state->state = STATE_PROBING;
- state->claims = 0;
- state->probes = 0;
- if (iface->addr.s_addr)
- state->conflicts = 0;
- return 1;
- }
-
- if (gotlease == 0)
- return bind_dhcp(state, options);
-
- if (iface->addr.s_addr)
- reason = "EXPIRE";
- else
- reason = "FAIL";
- drop_config(state, reason, options);
- if (!(state->options & DHCPCD_DAEMONISED) &&
- (state->options & DHCPCD_DAEMONISE))
- return -1;
- state->state = STATE_RENEW_REQUESTED;
- return 1;
- case STATE_BOUND:
- logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr));
- if (state->carrier != LINK_DOWN)
- do_socket(state, SOCKET_OPEN);
- state->xid = arc4random();
- state->state = STATE_RENEWING;
- state->stop.tv_sec = lease->rebindtime - lease->renewaltime;
- break;
- case STATE_RENEWING:
- logger(LOG_ERR, "failed to renew, attempting to rebind");
- state->state = STATE_REBINDING;
- if (lease->server.s_addr == 0)
- state->stop.tv_sec = options->timeout;
- else
- state->stop.tv_sec = lease->rebindtime - \
- lease->renewaltime;
- lease->server.s_addr = 0;
- break;
- case STATE_REBINDING:
- logger(LOG_ERR, "failed to rebind");
- reason = "EXPIRE";
- drop_config(state, reason, options);
- state->state = STATE_INIT;
- break;
- case STATE_PROBING: /* FALLTHROUGH */
- case STATE_ANNOUNCING:
- /* We should have lost carrier here and exit timer went */
- logger(LOG_ERR, "timed out");
- return -1;
- default:
- logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d",
- state->state);
- }
-
- /* This effectively falls through into the handle_timeout funtion */
- return 1;
-}
-
-static int
-handle_timeout(struct if_state *state, const struct options *options)
-{
- struct dhcp_lease *lease = &state->lease;
- struct interface *iface = state->interface;
- int i = 0;
- struct in_addr addr;
- struct timeval tv;
-
- timerclear(&state->timeout);
- if (timerneg(&state->exit))
- return handle_timeout_fail(state, options);
-
- if (state->state == STATE_RENEW_REQUESTED &&
- IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
- {
- state->state = STATE_PROBING;
- free(state->offer);
- state->offer = read_lease(state->interface);
- state->probes = 0;
- state->claims = 0;
- }
- switch (state->state) {
- case STATE_INIT_IPV4LL:
- state->state = STATE_PROBING;
- free(state->offer);
- state->offer = ipv4ll_get_dhcp(0);
- state->probes = 0;
- state->claims = 0;
- /* FALLTHROUGH */
- case STATE_PROBING:
- if (iface->arp_fd == -1)
- open_socket(iface, ETHERTYPE_ARP);
- if (state->probes < PROBE_NUM) {
- if (state->probes == 0) {
- addr.s_addr = state->offer->yiaddr;
- logger(LOG_INFO, "checking %s is available"
- " on attached networks",
- inet_ntoa(addr));
- }
- state->probes++;
- if (state->probes < PROBE_NUM) {
- state->timeout.tv_sec = PROBE_MIN;
- state->timeout.tv_usec = arc4random() %
- (PROBE_MAX_U - PROBE_MIN_U);
- timernorm(&state->timeout);
- } else {
- state->timeout.tv_sec = ANNOUNCE_WAIT;
- state->timeout.tv_usec = 0;
- }
- logger(LOG_DEBUG,
- "sending ARP probe (%d of %d), next in %0.2f seconds",
- state->probes, PROBE_NUM,
- timeval_to_double(&state->timeout));
- if (send_arp(iface, ARPOP_REQUEST, 0,
- state->offer->yiaddr) == -1)
- {
- logger(LOG_ERR, "send_arp: %s", strerror(errno));
- return -1;
- }
- return 0;
- } else {
- /* We've waited for ANNOUNCE_WAIT after the final probe
- * so the address is now ours */
- if (IN_LINKLOCAL(htonl(state->offer->yiaddr))) {
- i = bind_dhcp(state, options);
- state->state = STATE_ANNOUNCING;
- state->timeout.tv_sec = ANNOUNCE_INTERVAL;
- state->timeout.tv_usec = 0;
- return i;
- }
- state->state = STATE_REQUESTING;
- }
- break;
- case STATE_ANNOUNCING:
- if (iface->arp_fd == -1)
- open_socket(iface, ETHERTYPE_ARP);
- if (state->claims < ANNOUNCE_NUM) {
- state->claims++;
- if (state->claims < ANNOUNCE_NUM) {
- state->timeout.tv_sec = ANNOUNCE_INTERVAL;
- state->timeout.tv_usec = 0;
- logger(LOG_DEBUG,
- "sending ARP announce (%d of %d),"
- " next in %0.2f seconds",
- state->claims, ANNOUNCE_NUM,
- timeval_to_double(&state->timeout));
- } else
- logger(LOG_DEBUG,
- "sending ARP announce (%d of %d)",
- state->claims, ANNOUNCE_NUM);
- i = send_arp(iface, ARPOP_REQUEST,
- state->new->yiaddr, state->new->yiaddr);
- if (i == -1) {
- logger(LOG_ERR, "send_arp: %s", strerror(errno));
- return -1;
- }
- }
- if (state->claims < ANNOUNCE_NUM)
- return 0;
- if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
- /* We should pretend to be at the end
- * of the DHCP negotation cycle */
- state->state = STATE_INIT;
- state->messages = DHCP_MAX / DHCP_BASE;
- state->probes = 0;
- state->claims = 0;
- timerclear(&state->stop);
- goto dhcp_timeout;
- } else {
- state->state = STATE_BOUND;
- close(iface->arp_fd);
- iface->arp_fd = -1;
- if (lease->leasetime != ~0U) {
- state->stop.tv_sec = lease->renewaltime;
- state->stop.tv_usec = 0;
- if (clock_monotonic) {
- get_monotonic(&tv);
- timersub(&tv, &lease->boundtime, &tv);
- timersub(&state->stop, &tv, &state->stop);
- } else {
- state->stop.tv_sec -=
- (ANNOUNCE_INTERVAL * ANNOUNCE_NUM);
- }
- logger(LOG_DEBUG, "renew in %ld seconds",
- (long int)state->stop.tv_sec);
- }
- }
- return 0;
- }
-
- if (timerneg(&state->stop))
- return handle_timeout_fail(state, options);
-
- switch (state->state) {
- case STATE_BOUND: /* FALLTHROUGH */
- case STATE_RENEW_REQUESTED:
- timerclear(&state->stop);
- /* FALLTHROUGH */
- case STATE_INIT:
- do_socket(state, SOCKET_OPEN);
- state->xid = arc4random();
- iface->start_uptime = uptime();
- break;
- }
-
- switch(state->state) {
- case STATE_RENEW_REQUESTED:
- /* If a renew was requested (ie, didn't timeout) we actually
- * enter the REBIND state so that we broadcast to all servers.
- * We need to do this for when we change networks. */
- lease->server.s_addr = 0;
- state->messages = 0;
- if (lease->addr.s_addr && !(state->options & DHCPCD_INFORM)) {
- logger(LOG_INFO, "rebinding lease of %s",
- inet_ntoa(lease->addr));
- state->state = STATE_REBINDING;
- state->stop.tv_sec = options->timeout;
- state->stop.tv_usec = 0;
- break;
- }
- /* FALLTHROUGH */
- case STATE_INIT:
- if (state->carrier == LINK_DOWN)
- return 0;
- if (lease->addr.s_addr == 0 ||
- IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
- {
- logger(LOG_INFO, "broadcasting for a lease");
- state->state = STATE_DISCOVERING;
- } else if (state->options & DHCPCD_INFORM) {
- logger(LOG_INFO, "broadcasting inform for %s",
- inet_ntoa(lease->addr));
- state->state = STATE_REQUESTING;
- } else {
- logger(LOG_INFO, "broadcasting for a lease of %s",
- inet_ntoa(lease->addr));
- state->state = STATE_REQUESTING;
- }
- if (!lease->addr.s_addr && !timerisset(&state->stop)) {
- state->stop.tv_sec = DHCP_MAX + DHCP_RAND_MIN;
- state->stop.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
- timernorm(&state->stop);
- }
- break;
- }
-
-dhcp_timeout:
- if (state->carrier == LINK_DOWN) {
- timerclear(&state->timeout);
- return 0;
- }
- state->timeout.tv_sec = DHCP_BASE;
- for (i = 0; i < state->messages; i++) {
- state->timeout.tv_sec *= 2;
- if (state->timeout.tv_sec > DHCP_MAX) {
- state->timeout.tv_sec = DHCP_MAX;
- break;
- }
- }
- state->timeout.tv_sec += DHCP_RAND_MIN;
- state->timeout.tv_usec = arc4random() %
- (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
- timernorm(&state->timeout);
-
- /* We send the message here so that the timeout is reported */
- switch (state->state) {
- case STATE_DISCOVERING:
- send_message(state, DHCP_DISCOVER, options);
- break;
- case STATE_REQUESTING:
- if (state->options & DHCPCD_INFORM) {
- send_message(state, DHCP_INFORM, options);
- break;
- }
- /* FALLTHROUGH */
- case STATE_RENEWING: /* FALLTHROUGH */
- case STATE_REBINDING:
- if (iface->raw_fd == -1)
- do_socket(state, SOCKET_OPEN);
- send_message(state, DHCP_REQUEST, options);
- break;
- }
-
- return 0;
-}
-
-static void
-log_dhcp(int lvl, const char *msg, const struct dhcp_message *dhcp)
-{
- char *a;
- struct in_addr addr;
- int r;
-
- if (strcmp(msg, "NAK:") == 0)
- a = get_option_string(dhcp, DHO_MESSAGE);
- else {
- addr.s_addr = dhcp->yiaddr;
- a = xstrdup(inet_ntoa(addr));
- }
- r = get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID);
- if (dhcp->servername[0] && r == 0)
- logger(lvl, "%s %s from %s `%s'", msg, a,
- inet_ntoa(addr), dhcp->servername);
- else if (r == 0)
- logger(lvl, "%s %s from %s", msg, a, inet_ntoa(addr));
- else
- logger(lvl, "%s %s", msg, a);
- free(a);
-}
-
-static int
-handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp,
- const struct options *options)
-{
- struct dhcp_message *dhcp = *dhcpp;
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- uint8_t type, tmp;
- struct in_addr addr;
- size_t i;
- int r;
-
- /* reset the message counter */
- state->messages = 0;
-
- /* We have to have DHCP type to work */
- if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) {
- log_dhcp(LOG_ERR, "no DHCP type in", dhcp);
- return 0;
- }
-
- /* Ensure that it's not from a blacklisted server.
- * We should expand this to check IP and/or hardware address
- * at the packet level. */
- if (options->blacklist_len != 0 &&
- get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID) == 0)
- {
- for (i = 0; i < options->blacklist_len; i++) {
- if (options->blacklist[i] != addr.s_addr)
- continue;
- if (dhcp->servername[0])
- logger(LOG_WARNING,
- "ignoring blacklisted server %s `%s'",
- inet_ntoa(addr), dhcp->servername);
- else
- logger(LOG_WARNING,
- "ignoring blacklisted server %s",
- inet_ntoa(addr));
- return 0;
- }
- }
-
- /* We should restart on a NAK */
- if (type == DHCP_NAK) {
- log_dhcp(LOG_WARNING, "NAK:", dhcp);
- drop_config(state, "EXPIRE", options);
- do_socket(state, SOCKET_CLOSED);
- state->state = STATE_INIT;
- /* If we constantly get NAKS then we should slowly back off */
- if (state->nakoff == 0) {
- state->nakoff = 1;
- timerclear(&state->timeout);
- } else {
- state->timeout.tv_sec = state->nakoff;
- state->timeout.tv_usec = 0;
- state->nakoff *= 2;
- if (state->nakoff > NAKOFF_MAX)
- state->nakoff = NAKOFF_MAX;
- }
- return 0;
- }
-
- /* No NAK, so reset the backoff */
- state->nakoff = 1;
-
- /* Ensure that all required options are present */
- for (i = 1; i < 255; i++) {
- if (has_option_mask(options->requiremask, i) &&
- get_option_uint8(&tmp, dhcp, i) != 0)
- {
- log_dhcp(LOG_WARNING, "reject", dhcp);
- return 0;
- }
- }
-
- if (type == DHCP_OFFER && state->state == STATE_DISCOVERING) {
- lease->addr.s_addr = dhcp->yiaddr;
- get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID);
- log_dhcp(LOG_INFO, "offered", dhcp);
- if (state->options & DHCPCD_TEST) {
- run_script(options, iface->name, "TEST", dhcp, NULL);
- /* Fake the fact we forked so we return 0 to userland */
- state->options |= DHCPCD_FORKED;
- return -1;
- }
- free(state->offer);
- state->offer = dhcp;
- *dhcpp = NULL;
- timerclear(&state->timeout);
- if (state->options & DHCPCD_ARP &&
- iface->addr.s_addr != state->offer->yiaddr)
- {
- /* If the interface already has the address configured
- * then we can't ARP for duplicate detection. */
- addr.s_addr = state->offer->yiaddr;
- if (!has_address(iface->name, &addr, NULL)) {
- state->state = STATE_PROBING;
- state->claims = 0;
- state->probes = 0;
- state->conflicts = 0;
- timerclear(&state->stop);
- return 1;
- }
- }
- state->state = STATE_REQUESTING;
- return 1;
- }
-
- if (type == DHCP_OFFER) {
- log_dhcp(LOG_INFO, "ignoring offer of", dhcp);
- return 0;
- }
-
- /* We should only be dealing with acks */
- if (type != DHCP_ACK) {
- log_dhcp(LOG_ERR, "not ACK or OFFER", dhcp);
- return 0;
- }
-
- switch (state->state) {
- case STATE_RENEW_REQUESTED:
- case STATE_REQUESTING:
- case STATE_RENEWING:
- case STATE_REBINDING:
- if (!(state->options & DHCPCD_INFORM)) {
- get_option_addr(&lease->server.s_addr,
- dhcp, DHO_SERVERID);
- log_dhcp(LOG_INFO, "acknowledged", dhcp);
- }
- free(state->offer);
- state->offer = dhcp;
- *dhcpp = NULL;
- break;
- default:
- logger(LOG_ERR, "wrong state %d", state->state);
- }
-
- do_socket(state, SOCKET_CLOSED);
- r = bind_dhcp(state, options);
- if (!(state->options & DHCPCD_ARP)) {
- if (!(state->options & DHCPCD_INFORM))
- logger(LOG_DEBUG, "renew in %ld seconds",
- (long int)state->stop.tv_sec);
- return r;
- }
- state->state = STATE_ANNOUNCING;
- if (state->options & DHCPCD_FORKED)
- return r;
- return 1;
-}
-
-static int
-handle_dhcp_packet(struct if_state *state, const struct options *options)
-{
- uint8_t *packet;
- struct interface *iface = state->interface;
- struct dhcp_message *dhcp = NULL;
- const uint8_t *pp;
- uint8_t *p;
- ssize_t bytes;
- int retval = -1;
-
- /* We loop through until our buffer is empty.
- * The benefit is that if we get >1 DHCP packet in our buffer and
- * the first one fails for any reason, we can use the next. */
- packet = xmalloc(udp_dhcp_len);
- for(;;) {
- bytes = get_raw_packet(iface, ETHERTYPE_IP,
- packet, udp_dhcp_len);
- if (bytes == 0) {
- retval = 0;
- break;
- }
- if (bytes == -1)
- break;
- if (valid_udp_packet(packet) == -1)
- continue;
- bytes = get_udp_data(&pp, packet);
- if ((size_t)bytes > sizeof(*dhcp)) {
- logger(LOG_ERR, "packet greater than DHCP size");
- continue;
- }
- if (!dhcp)
- dhcp = xmalloc(sizeof(*dhcp));
- memcpy(dhcp, pp, bytes);
- if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
- logger(LOG_DEBUG, "bogus cookie, ignoring");
- continue;
- }
- /* Ensure it's the right transaction */
- if (state->xid != dhcp->xid) {
- logger(LOG_DEBUG,
- "ignoring packet with xid 0x%x as"
- " it's not ours (0x%x)",
- dhcp->xid, state->xid);
- continue;
- }
- /* Ensure packet is for us */
- if (iface->hwlen <= sizeof(dhcp->chaddr) &&
- memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen))
- {
- logger(LOG_DEBUG, "xid 0x%x is not for our hwaddr %s",
- dhcp->xid,
- hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr)));
- continue;
- }
- /* We should ensure that the packet is terminated correctly
- * if we have space for the terminator */
- if ((size_t)bytes < sizeof(struct dhcp_message)) {
- p = (uint8_t *)dhcp + bytes - 1;
- while (p > dhcp->options && *p == DHO_PAD)
- p--;
- if (*p != DHO_END)
- *++p = DHO_END;
- }
- retval = handle_dhcp(state, &dhcp, options);
- if (retval == 0 && state->options & DHCPCD_TEST)
- state->options |= DHCPCD_FORKED;
- break;
- }
-
- free(packet);
- free(dhcp);
- return retval;
-}
-
-static int
-handle_arp_packet(struct if_state *state)
-{
- struct arphdr reply;
- uint32_t reply_s;
- uint32_t reply_t;
- uint8_t arp_reply[sizeof(reply) + 2 * sizeof(reply_s) + 2 * HWADDR_LEN];
- uint8_t *hw_s, *hw_t;
- ssize_t bytes;
- struct interface *iface = state->interface;
-
- state->fail.s_addr = 0;
- for(;;) {
- bytes = get_raw_packet(iface, ETHERTYPE_ARP,
- arp_reply, sizeof(arp_reply));
- if (bytes == 0 || bytes == -1)
- return (int)bytes;
- /* We must have a full ARP header */
- if ((size_t)bytes < sizeof(reply))
- continue;
- memcpy(&reply, arp_reply, sizeof(reply));
- /* Protocol must be IP. */
- if (reply.ar_pro != htons(ETHERTYPE_IP))
- continue;
- if (reply.ar_pln != sizeof(reply_s))
- continue;
- /* Only these types are recognised */
- if (reply.ar_op != htons(ARPOP_REPLY) &&
- reply.ar_op != htons(ARPOP_REQUEST))
- continue;
-
- /* Get pointers to the hardware addreses */
- hw_s = arp_reply + sizeof(reply);
- hw_t = hw_s + reply.ar_hln + reply.ar_pln;
- /* Ensure we got all the data */
- if ((hw_t + reply.ar_hln + reply.ar_pln) - arp_reply > bytes)
- continue;
- /* Ignore messages from ourself */
- if (reply.ar_hln == iface->hwlen &&
- memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0)
- continue;
- /* Copy out the IP addresses */
- memcpy(&reply_s, hw_s + reply.ar_hln, reply.ar_pln);
- memcpy(&reply_t, hw_t + reply.ar_hln, reply.ar_pln);
-
- /* Check for conflict */
- if (state->offer &&
- (reply_s == state->offer->yiaddr ||
- (reply_s == 0 && reply_t == state->offer->yiaddr)))
- state->fail.s_addr = state->offer->yiaddr;
-
- /* Handle IPv4LL conflicts */
- if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) &&
- (reply_s == iface->addr.s_addr ||
- (reply_s == 0 && reply_t == iface->addr.s_addr)))
- state->fail.s_addr = iface->addr.s_addr;
-
- if (state->fail.s_addr) {
- logger(LOG_ERR, "hardware address %s claims %s",
- hwaddr_ntoa((unsigned char *)hw_s,
- (size_t)reply.ar_hln),
- inet_ntoa(state->fail));
- errno = EEXIST;
- return -1;
- }
- }
-}
-
-static int
-handle_arp_fail(struct if_state *state, const struct options *options)
-{
- time_t up;
- int cookie = state->offer->cookie;
-
- if (!IN_LINKLOCAL(htonl(state->fail.s_addr))) {
- state->state = STATE_INIT;
- free(state->offer);
- state->offer = NULL;
- state->lease.addr.s_addr = 0;
- if (!cookie)
- return 1;
- state->timeout.tv_sec = DHCP_ARP_FAIL;
- state->timeout.tv_usec = 0;
- do_socket(state, SOCKET_OPEN);
- send_message(state, DHCP_DECLINE, options);
- do_socket(state, SOCKET_CLOSED);
- return 0;
- }
-
- if (state->fail.s_addr == state->interface->addr.s_addr) {
- if (state->state == STATE_PROBING)
- /* This should only happen when SIGALRM or
- * link when down/up and we have a conflict. */
- drop_config(state, "EXPIRE", options);
- else {
- up = uptime();
- if (state->defend + DEFEND_INTERVAL > up) {
- drop_config(state, "EXPIRE", options);
- state->conflicts = -1;
- /* drop through to set conflicts to 0 */
- } else {
- state->defend = up;
- return 0;
- }
- }
- }
- do_socket(state, SOCKET_CLOSED);
- state->conflicts++;
- timerclear(&state->stop);
- if (state->conflicts > MAX_CONFLICTS) {
- logger(LOG_ERR, "failed to obtain an IPv4LL address");
- state->state = STATE_INIT;
- timerclear(&state->timeout);
- if (!(state->options & DHCPCD_DAEMONISED) &&
- (state->options & DHCPCD_DAEMONISE))
- return -1;
- return 1;
- }
- state->state = STATE_INIT_IPV4LL;
- state->timeout.tv_sec = PROBE_WAIT;
- state->timeout.tv_usec = 0;
- return 0;
-}
-
-static int
-handle_link(struct if_state *state)
-{
- int retval;
-
- retval = link_changed(state->interface);
- if (retval == -1) {
- logger(LOG_ERR, "link_changed: %s", strerror(errno));
- return -1;
- }
- if (retval == 0)
- return 0;
-
- timerclear(&state->timeout);
- switch (carrier_status(state->interface->name)) {
- case -1:
- logger(LOG_ERR, "carrier_status: %s", strerror(errno));
- return -1;
- case 0:
- if (state->carrier != LINK_DOWN) {
- logger(LOG_INFO, "carrier lost");
- state->carrier = LINK_DOWN;
- do_socket(state, SOCKET_CLOSED);
- if (state->state != STATE_BOUND)
- timerclear(&state->stop);
- }
- break;
- default:
- if (state->carrier != LINK_UP) {
- logger(LOG_INFO, "carrier acquired");
- state->state = STATE_RENEW_REQUESTED;
- state->carrier = LINK_UP;
- timerclear(&state->stop);
- return 1;
- }
- break;
- }
- return 0;
-}
-
-int
-dhcp_run(const struct options *options, int *pid_fd)
-{
- struct interface *iface;
- struct if_state *state = NULL;
- int fd = -1, r = 0, sig;
-
- iface = read_interface(options->interface, options->metric);
- if (!iface) {
- logger(LOG_ERR, "read_interface: %s", strerror(errno));
- goto eexit;
- }
- logger(LOG_DEBUG, "hardware address = %s",
- hwaddr_ntoa(iface->hwaddr, iface->hwlen));
- state = xzalloc(sizeof(*state));
- state->pid_fd = pid_fd;
- state->interface = iface;
- if (!(options->options & DHCPCD_TEST))
- run_script(options, iface->name, "PREINIT", NULL, NULL);
-
- if (client_setup(state, options) == -1)
- goto eexit;
- if (signal_init() == -1)
- goto eexit;
- if (signal_setup() == -1)
- goto eexit;
- state->signal_fd = signal_fd();
-
- if (state->options & DHCPCD_BACKGROUND &&
- !(state->options & DHCPCD_DAEMONISED))
- if (daemonise(state, options) == -1)
- goto eexit;
-
- if (state->carrier == LINK_DOWN)
- logger(LOG_INFO, "waiting for carrier");
-
- for (;;) {
- if (r == 0)
- r = handle_timeout(state, options);
- else if (r > 0) {
- if (fd == state->signal_fd) {
- if ((sig = signal_read()) != -1)
- r = handle_signal(sig, state, options);
- } else if (fd == iface->link_fd)
- r = handle_link(state);
- else if (fd == iface->raw_fd)
- r = handle_dhcp_packet(state, options);
- else if (fd == iface->arp_fd) {
- if ((r = handle_arp_packet(state)) == -1)
- r = handle_arp_fail(state, options);
- } else
- r = 0;
- }
- if (r == -1)
- break;
- if (r == 0) {
- fd = -1;
- r = wait_for_fd(state, &fd);
- if (r == -1 && errno == EINTR) {
- r = 1;
- fd = state->signal_fd;
- }
- } else
- r = 0;
- }
-
-eexit:
- if (iface) {
- do_socket(state, SOCKET_CLOSED);
- if (iface->link_fd != -1)
- close(iface->link_fd);
- free_routes(iface->routes);
- free(iface->clientid);
- free(iface->buffer);
- free(iface);
- }
-
- if (state) {
- if (state->options & DHCPCD_FORKED)
- r = 0;
- if (state->options & DHCPCD_DAEMONISED)
- unlink(options->pidfile);
- free(state->offer);
- free(state->new);
- free(state->old);
- free(state);
- }
-
- return r;
-}
if (value)
return value;
- logger(LOG_ERR, "memory exhausted");
+ logger(LOG_ERR, "memory exhausted (xalloc %zu bytes)", s);
exit (EXIT_FAILURE);
/* NOTREACHED */
}
if (value)
return (value);
- logger(LOG_ERR, "memory exhausted");
+ logger(LOG_ERR, "memory exhausted (xrealloc %zu bytes)", s);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
if ((value = strdup(str)))
return value;
- logger(LOG_ERR, "memory exhausted");
+ logger(LOG_ERR, "memory exhausted (xstrdup)");
exit(EXIT_FAILURE);
/* NOTREACHED */
}
#define UNCONST(a) ((void *)(unsigned long)(const void *)(a))
+#define timeval_to_double(tv) ((tv)->tv_sec * 1.0 + (tv)->tv_usec * 1.0e-6)
+#define timernorm(tvp) \
+ do { \
+ while ((tvp)->tv_usec >= 1000000) { \
+ (tvp)->tv_sec++; \
+ (tvp)->tv_usec -= 1000000; \
+ } \
+ } while (0 /* CONSTCOND */);
+
#if __GNUC__ > 2 || defined(__INTEL_COMPILER)
# define _unused __attribute__((__unused__))
#else
#define CONFIG_H
#define PACKAGE "dhcpcd"
-#define VERSION "4.0.0"
+#define VERSION "4.0.99"
/*
* By default we don't add a local link route if we got a routeable address.
# define LEASEFILE DBDIR "/" PACKAGE "-%s.lease"
#endif
#ifndef PIDFILE
-# define PIDFILE RUNDIR "/" PACKAGE "-%s.pid"
+# define PIDFILE RUNDIR "/" PACKAGE "%s%s.pid"
#endif
#endif
#include "config.h"
#include "common.h"
#include "configure.h"
-#include "dhcp.h"
-#include "dhcpcd.h"
+#include "dhcpf.h"
+#include "if-options.h"
#include "logger.h"
#include "net.h"
#include "signals.h"
#define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin"
-
static int
exec_script(char *const *argv, char *const *env)
{
}
int
-run_script(const struct options *options, const char *iface,
- const char *reason,
- const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo)
+run_script(const struct interface *iface, const char *reason)
{
- char *const argv[2] = { UNCONST(options->script), NULL };
+ char *const argv[2] = { UNCONST(iface->state->options->script), NULL };
char **env = NULL, **ep;
char *path;
ssize_t e, elen;
pid_t pid;
int status = 0;
+ const struct if_options *ifo = iface->state->options;
- logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason);
+ logger(LOG_DEBUG, "%s: executing `%s', reason %s",
+ iface->name, argv[0], reason);
/* Make our env */
elen = 5;
snprintf(env[0], e, "PATH=%s", path);
} else
env[0] = xstrdup(DEFAULT_PATH);
- e = strlen("interface") + strlen(iface) + 2;
+ e = strlen("interface") + strlen(iface->name) + 2;
env[1] = xmalloc(e);
- snprintf(env[1], e, "interface=%s", iface);
+ snprintf(env[1], e, "interface=%s", iface->name);
e = strlen("reason") + strlen(reason) + 2;
env[2] = xmalloc(e);
snprintf(env[2], e, "reason=%s", reason);
env[3] = xmalloc(e);
snprintf(env[3], e, "pid=%d", getpid());
env[4] = xmalloc(e);
- snprintf(env[4], e, "metric=%d", options->metric);
- if (dhcpo) {
- e = configure_env(NULL, NULL, dhcpo, options);
+ snprintf(env[4], e, "metric=%d", iface->metric);
+ if (iface->state->old) {
+ e = configure_env(NULL, NULL, iface->state->old, ifo);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
- elen += configure_env(env + elen, "old", dhcpo, options);
+ elen += configure_env(env + elen, "old",
+ iface->state->old, ifo);
}
}
- if (dhcpn) {
- e = configure_env(NULL, NULL, dhcpn, options);
+ if (iface->state->new) {
+ e = configure_env(NULL, NULL, iface->state->new, ifo);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
- elen += configure_env(env + elen, "new", dhcpn, options);
+ elen += configure_env(env + elen, "new",
+ iface->state->new, ifo);
}
}
/* Add our base environment */
- if (options->environ) {
+ if (ifo->environ) {
e = 0;
- while (options->environ[e++])
+ while (ifo->environ[e++])
;
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
e = 0;
- while (options->environ[e]) {
- env[elen + e] = xstrdup(options->environ[e]);
+ while (ifo->environ[e]) {
+ env[elen + e] = xstrdup(ifo->environ[e]);
e++;
}
elen += e;
}
static int
-delete_route(const char *iface, struct rt *rt, int metric)
+delete_route(const struct interface *iface, struct rt *rt, int metric)
{
char *addr;
int retval;
addr = xstrdup(inet_ntoa(rt->dest));
- logger(LOG_DEBUG, "deleting route %s/%d via %s",
+ logger(LOG_DEBUG, "%s: deleting route %s/%d via %s", iface->name,
addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate));
free(addr);
retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric);
rt = reverse_routes(iface->routes);
while (rt) {
rtn = rt->next;
- retval += delete_route(iface->name, rt, metric);
+ retval += delete_route(iface, rt, metric);
free(rt);
rt = rtn;
}
}
static int
-configure_routes(struct interface *iface, const struct dhcp_message *dhcp,
- const struct options *options)
+configure_routes(struct interface *iface, const struct dhcp_message *dhcp)
{
+ const struct if_options *ifo = iface->state->options;
struct rt *rt, *ort;
struct rt *rtn = NULL, *nr = NULL;
int remember;
ort = get_option_routes(dhcp);
#ifdef IPV4LL_ALWAYSROUTE
- if (options->options & DHCPCD_IPV4LL &&
+ if (ifo->options & DHCPCD_IPV4LL &&
IN_PRIVATE(ntohl(dhcp->yiaddr)))
{
for (rt = ort; rt; rt = rt->next) {
iface->routes = reverse_routes(iface->routes);
for (rt = iface->routes; rt; rt = rt->next)
if (in_routes(ort, rt) != 0)
- delete_route(iface->name, rt, options->metric);
+ delete_route(iface, rt, iface->metric);
for (rt = ort; rt; rt = rt->next) {
/* Don't set default routes if not asked to */
if (rt->dest.s_addr == 0 &&
rt->net.s_addr == 0 &&
- !(options->options & DHCPCD_GATEWAY))
+ !(ifo->options & DHCPCD_GATEWAY))
continue;
addr = xstrdup(inet_ntoa(rt->dest));
- logger(LOG_DEBUG, "adding route to %s/%d via %s",
- addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate));
+ logger(LOG_DEBUG, "%s: adding route to %s/%d via %s",
+ iface->name, addr,
+ inet_ntocidr(rt->net), inet_ntoa(rt->gate));
free(addr);
- remember = add_route(iface->name, &rt->dest,
- &rt->net, &rt->gate,
- options->metric);
+ remember = add_route(iface, &rt->dest,
+ &rt->net, &rt->gate, iface->metric);
retval += remember;
/* If we failed to add the route, we may have already added it
delete_address(struct interface *iface)
{
int retval;
- logger(LOG_DEBUG, "deleting IP address %s/%d",
+ logger(LOG_DEBUG, "%s: deleting IP address %s/%d",
+ iface->name,
inet_ntoa(iface->addr),
inet_ntocidr(iface->net));
- retval = del_address(iface->name, &iface->addr, &iface->net);
+ retval = del_address(iface, &iface->addr, &iface->net);
if (retval == -1 && errno != EADDRNOTAVAIL)
logger(LOG_ERR, "del_address: %s", strerror(errno));
iface->addr.s_addr = 0;
}
int
-configure(struct interface *iface, const char *reason,
- const struct dhcp_message *dhcp, const struct dhcp_message *old,
- const struct dhcp_lease *lease, const struct options *options,
- int up)
+configure(struct interface *iface, const char *reason)
{
+ struct dhcp_message *dhcp = iface->state->new;
struct in_addr addr;
struct in_addr net;
struct in_addr brd;
#endif
/* Grab our IP config */
- if (dhcp == NULL)
- up = 0;
- else {
+ if (dhcp) {
addr.s_addr = dhcp->yiaddr;
if (addr.s_addr == 0)
- addr.s_addr = lease->addr.s_addr;
+ addr.s_addr = iface->state->lease.addr.s_addr;
/* Ensure we have all the needed values */
if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1)
net.s_addr = get_netmask(addr.s_addr);
if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1)
brd.s_addr = addr.s_addr | ~net.s_addr;
- }
-
- /* If we aren't up, then reset the interface as much as we can */
- if (!up) {
+ } else {
/* Only reset things if we had set them before */
if (iface->addr.s_addr != 0) {
- delete_routes(iface, options->metric);
+ delete_routes(iface, iface->metric);
delete_address(iface);
}
- run_script(options, iface->name, reason, NULL, old);
+ run_script(iface, reason);
return 0;
}
/* This also changes netmask */
- if (!(options->options & DHCPCD_INFORM) ||
+ if (!(iface->state->options->options & DHCPCD_INFORM) ||
!has_address(iface->name, &addr, &net)) {
- logger(LOG_DEBUG, "adding IP address %s/%d",
- inet_ntoa(addr), inet_ntocidr(net));
- if (add_address(iface->name, &addr, &net, &brd) == -1 &&
+ logger(LOG_DEBUG, "%s: adding IP address %s/%d",
+ iface->name, inet_ntoa(addr), inet_ntocidr(net));
+ if (add_address(iface, &addr, &net, &brd) == -1 &&
errno != EEXIST)
{
logger(LOG_ERR, "add_address: %s", strerror(errno));
#ifdef __linux__
/* On linux, we need to change the subnet route to have our metric. */
- if (iface->addr.s_addr != lease->addr.s_addr &&
- options->metric > 0 && net.s_addr != INADDR_BROADCAST)
+ if (iface->addr.s_addr != iface->state->lease.addr.s_addr &&
+ iface->metric > 0 &&
+ net.s_addr != INADDR_BROADCAST)
{
dest.s_addr = addr.s_addr & net.s_addr;
gate.s_addr = 0;
- add_route(iface->name, &dest, &net, &gate, options->metric);
- del_route(iface->name, &dest, &net, &gate, 0);
+ add_route(iface, &dest, &net, &gate, iface->metric);
+ del_route(iface, &dest, &net, &gate, 0);
}
#endif
- configure_routes(iface, dhcp, options);
- up = (iface->addr.s_addr != addr.s_addr ||
- iface->net.s_addr != net.s_addr);
+ configure_routes(iface, dhcp);
iface->addr.s_addr = addr.s_addr;
iface->net.s_addr = net.s_addr;
- if (!lease->frominfo)
+ if (!iface->state->lease.frominfo)
if (write_lease(iface, dhcp) == -1)
logger(LOG_ERR, "write_lease: %s", strerror(errno));
- run_script(options, iface->name, reason, dhcp, old);
+ run_script(iface, reason);
return 0;
}
#ifndef DHCPCONFIG_H
#define DHCPCONFIG_H
-#include "dhcpcd.h"
-#include "dhcp.h"
#include "net.h"
-int run_script(const struct options *, const char *, const char *,
- const struct dhcp_message *, const struct dhcp_message *);
-int configure(struct interface *, const char *,
- const struct dhcp_message *, const struct dhcp_message *,
- const struct dhcp_lease *, const struct options *, int);
+int run_script(const struct interface *, const char *);
+int configure(struct interface *, const char *);
#endif
#include "config.h"
#include "common.h"
#include "dhcp.h"
+#include "dhcpf.h"
#define REQUEST (1 << 0)
#define UINT8 (1 << 1)
printf("%03d %s\n", opt->option, opt->var);
}
-int make_option_mask(uint8_t *mask, char **opts, int add)
+int make_option_mask(uint8_t *mask, const char *opts, int add)
{
- char *token, *p = *opts, *t;
+ char *token, *o, *p, *t;
const struct dhcp_opt *opt;
int match, n;
+ o = p = xstrdup(opts);
while ((token = strsep(&p, ", "))) {
if (*token == '\0')
continue;
}
}
if (!opt->option) {
- *opts = token;
+ free(o);
errno = ENOENT;
return -1;
}
}
+ free(o);
return 0;
}
ssize_t
make_message(struct dhcp_message **message,
- const struct interface *iface, const struct dhcp_lease *lease,
- uint32_t xid, uint8_t type, const struct options *options)
+ const struct interface *iface,
+ uint8_t type)
{
struct dhcp_message *dhcp;
uint8_t *m, *lp, *p;
uint32_t ul;
uint16_t sz;
const struct dhcp_opt *opt;
+ const struct if_options *ifo = iface->state->options;
+ const struct dhcp_lease *lease = &iface->state->lease;
dhcp = xzalloc(sizeof (*dhcp));
m = (uint8_t *)dhcp;
dhcp->secs = htons((uint16_t)UINT16_MAX);
else
dhcp->secs = htons(up);
- dhcp->xid = xid;
+ dhcp->xid = iface->state->xid;
dhcp->cookie = htonl(MAGIC_COOKIE);
*p++ = DHO_MESSAGETYPE;
}
if (type != DHCP_DECLINE && type != DHCP_RELEASE) {
- if (options->userclass[0]) {
+ if (ifo->userclass[0]) {
*p++ = DHO_USERCLASS;
- memcpy(p, options->userclass, options->userclass[0] + 1);
- p += options->userclass[0] + 1;
+ memcpy(p, ifo->userclass, ifo->userclass[0] + 1);
+ p += ifo->userclass[0] + 1;
}
- if (options->vendorclassid[0]) {
+ if (ifo->vendorclassid[0]) {
*p++ = DHO_VENDORCLASSID;
- memcpy(p, options->vendorclassid,
- options->vendorclassid[0] + 1);
- p += options->vendorclassid[0] + 1;
+ memcpy(p, ifo->vendorclassid,
+ ifo->vendorclassid[0] + 1);
+ p += ifo->vendorclassid[0] + 1;
}
}
}
#undef PUTADDR
- if (options->leasetime != 0) {
+ if (ifo->leasetime != 0) {
*p++ = DHO_LEASETIME;
*p++ = 4;
- ul = htonl(options->leasetime);
+ ul = htonl(ifo->leasetime);
memcpy(p, &ul, 4);
p += 4;
}
type == DHCP_INFORM ||
type == DHCP_REQUEST)
{
- if (options->hostname[0]) {
+ if (ifo->hostname[0]) {
*p++ = DHO_HOSTNAME;
- memcpy(p, options->hostname, options->hostname[0] + 1);
- p += options->hostname[0] + 1;
+ memcpy(p, ifo->hostname, ifo->hostname[0] + 1);
+ p += ifo->hostname[0] + 1;
}
- if (options->fqdn != FQDN_DISABLE) {
+ if (ifo->fqdn != FQDN_DISABLE) {
/* IETF DHC-FQDN option (81), RFC4702 */
*p++ = DHO_FQDN;
lp = p;
* N: 1 => Client requests Server to not
* update DNS
*/
- *p++ = (options->fqdn & 0x09) | 0x04;
+ *p++ = (ifo->fqdn & 0x09) | 0x04;
*p++ = 0; /* from server for PTR RR */
*p++ = 0; /* from server for A RR if S=1 */
- ul = encode_rfc1035(options->hostname + 1, p,
- options->hostname[0]);
+ ul = encode_rfc1035(ifo->hostname + 1, p,
+ ifo->hostname[0]);
*lp += ul;
p += ul;
}
/* vendor is already encoded correctly, so just add it */
- if (options->vendor[0]) {
+ if (ifo->vendor[0]) {
*p++ = DHO_VENDOR;
- memcpy(p, options->vendor, options->vendor[0] + 1);
- p += options->vendor[0] + 1;
+ memcpy(p, ifo->vendor, ifo->vendor[0] + 1);
+ p += ifo->vendor[0] + 1;
}
*p++ = DHO_PARAMETERREQUESTLIST;
*p++ = 0;
for (opt = dhcp_opts; opt->option; opt++) {
if (!(opt->type & REQUEST ||
- has_option_mask(options->requestmask, opt->option)))
+ has_option_mask(ifo->requestmask, opt->option)))
continue;
switch (opt->option) {
case DHO_RENEWALTIME: /* FALLTHROUGH */
ssize_t
configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp,
- const struct options *options)
+ const struct if_options *ifo)
{
unsigned int i;
const uint8_t *p;
for (opt = dhcp_opts; opt->option; opt++) {
if (!opt->var)
continue;
- if (has_option_mask(options->nomask, opt->option))
+ if (has_option_mask(ifo->nomask, opt->option))
continue;
if (get_option_raw(dhcp, opt->option))
e++;
for (opt = dhcp_opts; opt->option; opt++) {
if (!opt->var)
continue;
- if (has_option_mask(options->nomask, opt->option))
+ if (has_option_mask(ifo->nomask, opt->option))
continue;
val = NULL;
p = get_option(dhcp, opt->option, &pl, NULL);
return ep - env;
}
+
+void
+get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp)
+{
+ time_t t;
+
+ lease->frominfo = 0;
+ lease->addr.s_addr = dhcp->yiaddr;
+ if (get_option_addr(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1)
+ lease->net.s_addr = get_netmask(dhcp->yiaddr);
+ if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) {
+ /* Ensure that we can use the lease */
+ t = 0;
+ if (t + (time_t)lease->leasetime < t)
+ lease->leasetime = ~0U; /* Infinite lease */
+ } else
+ lease->leasetime = DEFAULT_LEASETIME;
+ if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0)
+ lease->renewaltime = 0;
+ if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0)
+ lease->rebindtime = 0;
+}
#define DHCP_H
#include <arpa/inet.h>
+#include <netinet/in.h>
#include <stdint.h>
-#include "config.h"
-#include "dhcpcd.h"
-#include "net.h"
-
/* Max MTU - defines dhcp option length */
#define MTU_MAX 1500
#define MTU_MIN 576
#define DHCP_RELEASE 7
#define DHCP_INFORM 8
+/* Constants taken from RFC 2131. */
+#define T1 0.5
+#define T2 0.875
+#define DHCP_BASE 4
+#define DHCP_MAX 64
+#define DHCP_RAND_MIN -1
+#define DHCP_RAND_MAX 1
+#define DHCP_ARP_FAIL 10
+
+/* number of usecs in a second. */
+#define USECS_SECOND 1000000
+/* As we use timevals, we should use the usec part for
+ * greater randomisation. */
+#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND
+#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND
+#define PROBE_MIN_U PROBE_MIN * USECS_SECOND
+#define PROBE_MAX_U PROBE_MAX * USECS_SECOND
+
/* DHCP options */
enum DHO
{
uint8_t frominfo;
};
-#define add_option_mask(var, val) (var[val >> 3] |= 1 << (val & 7))
-#define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7)))
-#define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7)))
-int make_option_mask(uint8_t *, char **, int);
-void print_options(void);
-char *get_option_string(const struct dhcp_message *, uint8_t);
-int get_option_addr(uint32_t *, const struct dhcp_message *, uint8_t);
-int get_option_uint32(uint32_t *, const struct dhcp_message *, uint8_t);
-int get_option_uint16(uint16_t *, const struct dhcp_message *, uint8_t);
-int get_option_uint8(uint8_t *, const struct dhcp_message *, uint8_t);
-struct rt *get_option_routes(const struct dhcp_message *);
-ssize_t configure_env(char **, const char *, const struct dhcp_message *,
- const struct options *);
-
-ssize_t make_message(struct dhcp_message **,
- const struct interface *, const struct dhcp_lease *,
- uint32_t, uint8_t, const struct options *);
-int valid_dhcp_packet(unsigned char *);
-
-ssize_t write_lease(const struct interface *, const struct dhcp_message *);
-struct dhcp_message *read_lease(const struct interface *iface);
#endif
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 20, 2008
+.Dd September 1, 2008
.Dt DHCPCD 8 SMM
.Sh NAME
.Nm dhcpcd
.Op Fl O , -nooption Ar option
.Op Fl Q , -require Ar option
.Op Fl X , -blacklist Ar address
-.Ar interface
+.Op interface
+.Op ...
.Nm
.Fl k , -release
-.Ar interface
+.Op interface
.Nm
.Fl x , -exit
-.Ar interface
+.Op interface
.Sh DESCRIPTION
.Nm
is an implementation of the DHCP client specified in
To disable this behaviour, you can use the
.Fl L , -noipv4ll
option.
+.Ss Multiple interfaces
+.Nm
+can be run per interface or as a single instance to manage all interfaces.
+If a list of interfaces are given on the command line, then
+.Nm
+only works with those interfaces.
+If no interfaces are given then we detect all available interfaces and
+attempt to configure all of them.
.Ss Hooking into DHCP events
.Nm
runs
.Sh AUTHORS
.An Roy Marples <roy@marples.name>
.Sh BUGS
+You cannot release a lease - this will be fixed before the first release.
+.Pp
+You cannot dynamically add or remove interfaces either manually or detected.
+.Pp
+One of the goals for one instance managing multiple interfaces is more
+intelligent route and configuration management.
+This has not yet been done.
+.Pp
Please report them to http://bugs.marples.name
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
+#include <limits.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
+#include "arp.h"
+#include "bind.h"
#include "config.h"
-#include "client.h"
+#include "common.h"
+#include "configure.h"
#include "dhcpcd.h"
-#include "dhcp.h"
-#include "net.h"
+#include "dhcpf.h"
+#include "duid.h"
+#include "eloop.h"
+#include "if-options.h"
+#include "ipv4ll.h"
#include "logger.h"
+#include "net.h"
+#include "signals.h"
-/* Don't set any optional arguments here so we retain POSIX
- * compatibility with getopt */
-#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:"
-
-static int doversion = 0;
-static int dohelp = 0;
-static const struct option longopts[] = {
- {"background", no_argument, NULL, 'b'},
- {"script", required_argument, NULL, 'c'},
- {"debug", no_argument, NULL, 'd'},
- {"config", required_argument, NULL, 'f'},
- {"hostname", optional_argument, NULL, 'h'},
- {"vendorclassid", optional_argument, NULL, 'i'},
- {"release", no_argument, NULL, 'k'},
- {"leasetime", required_argument, NULL, 'l'},
- {"metric", required_argument, NULL, 'm'},
- {"rebind", no_argument, NULL, 'n'},
- {"option", required_argument, NULL, 'o'},
- {"persistent", no_argument, NULL, 'p'},
- {"quiet", no_argument, NULL, 'q'},
- {"request", optional_argument, NULL, 'r'},
- {"inform", optional_argument, NULL, 's'},
- {"timeout", required_argument, NULL, 't'},
- {"userclass", required_argument, NULL, 'u'},
- {"vendor", required_argument, NULL, 'v'},
- {"exit", no_argument, NULL, 'x'},
- {"noarp", no_argument, NULL, 'A'},
- {"nobackground", no_argument, NULL, 'B'},
- {"nohook", required_argument, NULL, 'C'},
- {"duid", no_argument, NULL, 'D'},
- {"lastlease", no_argument, NULL, 'E'},
- {"fqdn", optional_argument, NULL, 'F'},
- {"nogateway", no_argument, NULL, 'G'},
- {"clientid", optional_argument, NULL, 'I'},
- {"nolink", no_argument, NULL, 'K'},
- {"noipv4ll", no_argument, NULL, 'L'},
- {"nooption", optional_argument, NULL, 'O'},
- {"require", required_argument, NULL, 'Q'},
- {"test", no_argument, NULL, 'T'},
- {"variables", no_argument, NULL, 'V'},
- {"blacklist", required_argument, NULL, 'X'},
- {"help", no_argument, &dohelp, 1},
- {"version", no_argument, &doversion, 1},
-#ifdef CMDLINE_COMPAT
- {"classid", optional_argument, NULL, 'i'},
- {"renew", no_argument, NULL, 'n'},
- {"nohostname", no_argument, NULL, 'H'},
- {"nomtu", no_argument, NULL, 'M'},
- {"nontp", no_argument, NULL, 'N'},
- {"nodns", no_argument, NULL, 'R'},
- {"msscr", no_argument, NULL, 'S'},
- {"nonis", no_argument, NULL, 'Y'},
-#endif
- {NULL, 0, NULL, '\0'}
-};
+/* We should define a maximum for the NAK exponential backoff */
+#define NAKOFF_MAX 60
-#ifdef CMDLINE_COMPAT
-# define EXTRA_OPTS "HMNRSY"
-#endif
+int pidfd = -1;
+static int linkfd = -1;
+static char cffile[PATH_MAX];
+static char pidfile[PATH_MAX] = { '\0' };
+static struct interface *ifaces = NULL;
-#ifndef EXTRA_OPTS
-# define EXTRA_OPTS
-#endif
-static int
-atoint(const char *s)
-{
- char *t;
- long n;
+struct dhcp_op {
+ uint8_t value;
+ const char *name;
+};
- errno = 0;
- n = strtol(s, &t, 0);
- if ((errno != 0 && n == 0) || s == t ||
- (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)))
- {
- logger(LOG_ERR, "`%s' out of range", s);
- return -1;
- }
+static const struct dhcp_op const dhcp_ops[] = {
+ { DHCP_DISCOVER, "DHCP_DISCOVER" },
+ { DHCP_OFFER, "DHCP_OFFER" },
+ { DHCP_REQUEST, "DHCP_REQUEST" },
+ { DHCP_DECLINE, "DHCP_DECLINE" },
+ { DHCP_ACK, "DHCP_ACK" },
+ { DHCP_NAK, "DHCP_NAK" },
+ { DHCP_RELEASE, "DHCP_RELEASE" },
+ { DHCP_INFORM, "DHCP_INFORM" },
+ { 0, NULL }
+};
+
+static const char *
+get_dhcp_op(uint8_t type)
+{
+ const struct dhcp_op *d;
- return (int)n;
+ for (d = dhcp_ops; d->name; d++)
+ if (d->value == type)
+ return d->name;
+ return NULL;
}
static pid_t
-read_pid(const char *pidfile)
+read_pid(void)
{
FILE *fp;
pid_t pid = 0;
" [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] <interface>\n");
}
-static char *
-add_environ(struct options *options, const char *value, int uniq)
+static void
+cleanup(void)
{
- char **newlist;
- char **lst = options->environ;
- size_t i = 0, l, lv;
- char *match = NULL, *p;
-
- match = xstrdup(value);
- p = strchr(match, '=');
- if (p)
- *p++ = '\0';
- l = strlen(match);
-
- while (lst && lst[i]) {
- if (match && strncmp(lst[i], match, l) == 0) {
- if (uniq) {
- free(lst[i]);
- lst[i] = xstrdup(value);
- } else {
- /* Append a space and the value to it */
- l = strlen(lst[i]);
- lv = strlen(p);
- lst[i] = xrealloc(lst[i], l + lv + 2);
- lst[i][l] = ' ';
- memcpy(lst[i] + l + 1, p, lv);
- lst[i][l + lv + 1] = '\0';
- }
- free(match);
- return lst[i];
- }
- i++;
+#ifdef DEBUG_MEMORY
+ struct interface *iface;
+
+ while (ifaces) {
+ iface = ifaces;
+ ifaces = iface->next;
+ free_interface(iface);
}
+#endif
- newlist = xrealloc(lst, sizeof(char *) * (i + 2));
- newlist[i] = xstrdup(value);
- newlist[i + 1] = NULL;
- options->environ = newlist;
- free(match);
- return newlist[i];
+ if (linkfd != -1)
+ close(linkfd);
+ if (pidfd > -1) {
+ close(pidfd);
+ unlink(pidfile);
+ }
+}
+
+void
+handle_exit_timeout(_unused struct interface *iface)
+{
+ logger(LOG_ERR, "timed out");
+ exit(EXIT_FAILURE);
+}
+
+void
+drop_config(struct interface *iface, const char *reason)
+{
+ if (iface->state->new || strcmp(reason, "FAIL") == 0) {
+ free(iface->state->new);
+ iface->state->new = NULL;
+ configure(iface, reason);
+ free(iface->state->old);
+ iface->state->old = NULL;
+ }
+ iface->state->lease.addr.s_addr = 0;
+}
+
+void
+close_sockets(struct interface *iface)
+{
+ if (iface->arp_fd != -1) {
+ delete_event(iface->arp_fd);
+ close(iface->arp_fd);
+ iface->arp_fd = -1;
+ }
+ if (iface->raw_fd != -1) {
+ delete_event(iface->raw_fd);
+ close(iface->raw_fd);
+ iface->raw_fd = -1;
+ }
+ if (iface->udp_fd != -1) {
+ close(iface->udp_fd);
+ iface->udp_fd = -1;
+ }
}
-#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0)
-static ssize_t
-parse_string_hwaddr(char *sbuf, ssize_t slen, char *str, int clid)
+static void
+send_message(struct interface *iface, int type,
+ void (*function)(struct interface *))
{
- ssize_t l;
- char *p;
- int i;
- char c[4];
-
- /* If surrounded by quotes then it's a string */
- if (*str == '"') {
- str++;
- l = strlen(str);
- p = str + l - 1;
- if (*p == '"')
- *p = '\0';
+ struct if_state *state = iface->state;
+ struct dhcp_message *dhcp;
+ uint8_t *udp;
+ ssize_t len, r;
+ struct in_addr from, to;
+ in_addr_t a = 0;
+ struct timeval tv;
+
+ if (!function)
+ logger(LOG_DEBUG, "%s: sending %s with xid 0x%x",
+ iface->name, get_dhcp_op(type), state->xid);
+ else {
+ if (state->interval == 0)
+ state->interval = 4;
+ else {
+ state->interval *= 2;
+ if (state->interval > 64)
+ state->interval = 64;
+ }
+ tv.tv_sec = state->interval + DHCP_RAND_MIN;
+ tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
+ logger(LOG_DEBUG,
+ "%s: sending %s with xid 0x%x, next in %0.2f seconds",
+ iface->name, get_dhcp_op(type), state->xid,
+ timeval_to_double(&tv));
+ }
+ /* If we couldn't open a UDP port for our IP address
+ * then we cannot renew.
+ * This could happen if our IP was pulled out from underneath us. */
+ if (iface->udp_fd == -1) {
+ a = iface->addr.s_addr;
+ iface->addr.s_addr = 0;
+ }
+ len = make_message(&dhcp, iface, type);
+ if (iface->udp_fd == -1)
+ iface->addr.s_addr = a;
+ from.s_addr = dhcp->ciaddr;
+ if (from.s_addr)
+ to.s_addr = state->lease.server.s_addr;
+ else
+ to.s_addr = 0;
+ if (to.s_addr && to.s_addr != INADDR_BROADCAST) {
+ r = send_packet(iface, to, (uint8_t *)dhcp, len);
+ if (r == -1)
+ logger(LOG_ERR, "%s: send_packet: %s",
+ iface->name, strerror(errno));
} else {
- l = hwaddr_aton(NULL, str);
- if (l > 1) {
- if (l > slen) {
- errno = ENOBUFS;
- return -1;
- }
- hwaddr_aton((uint8_t *)sbuf, str);
- return l;
- }
+ len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to);
+ r = send_raw_packet(iface, ETHERTYPE_IP, udp, len);
+ free(udp);
+ if (r == -1)
+ logger(LOG_ERR, "%s: send_raw_packet: %s",
+ iface->name, strerror(errno));
}
+ free(dhcp);
+ if (function)
+ add_timeout_tv(&tv, function, iface);
+}
+
+static void
+send_discover(struct interface *iface)
+{
+ send_message(iface, DHCP_DISCOVER, send_discover);
+}
+
+void
+send_request(struct interface *iface)
+{
+ send_message(iface, DHCP_REQUEST, send_request);
+}
- /* Process escapes */
- l = 0;
- /* If processing a string on the clientid, first byte should be
- * 0 to indicate a non hardware type */
- if (clid) {
- *sbuf++ = 0;
- l++;
+static void
+send_renew(struct interface *iface)
+{
+ send_message(iface, DHCP_REQUEST, send_renew);
+}
+
+void
+start_renew(struct interface *iface)
+{
+ logger(LOG_INFO, "%s: renewing lease of %s",
+ iface->name, inet_ntoa(iface->state->lease.addr));
+ iface->state->state = DHS_RENEWING;
+ iface->state->xid = arc4random();
+ send_renew(iface);
+}
+
+static void
+send_rebind(struct interface *iface)
+{
+ send_message(iface, DHCP_REQUEST, send_rebind);
+}
+
+void
+start_rebind(struct interface *iface)
+{
+ logger(LOG_ERR, "%s: failed to renew, attmepting to rebind",
+ iface->name);
+ iface->state->state = DHS_REBINDING;
+ delete_timeout(send_renew, iface);
+ iface->state->lease.server.s_addr = 0;
+ send_rebind(iface);
+}
+
+void
+start_expire(struct interface *iface)
+{
+ int ll = IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr));
+
+ logger(LOG_ERR, "%s: lease expired", iface->name);
+ delete_timeout(NULL, iface);
+ drop_config(iface, "EXPIRE");
+ iface->state->interval = 0;
+ if (iface->state->carrier != LINK_DOWN) {
+ if (ll)
+ start_interface(iface);
+ else
+ start_ipv4ll(iface);
}
- c[3] = '\0';
- while (*str) {
- if (++l > slen) {
- errno = ENOBUFS;
- return -1;
- }
- if (*str == '\\') {
- str++;
- switch(*str++) {
- case '\0':
- break;
- case 'b':
- *sbuf++ = '\b';
- break;
- case 'n':
- *sbuf++ = '\n';
- break;
- case 'r':
- *sbuf++ = '\r';
- break;
- case 't':
- *sbuf++ = '\t';
- break;
- case 'x':
- /* Grab a hex code */
- c[1] = '\0';
- for (i = 0; i < 2; i++) {
- if (isxdigit((unsigned char)*str) == 0)
- break;
- c[i] = *str++;
- }
- if (c[1] != '\0') {
- c[2] = '\0';
- *sbuf++ = strtol(c, NULL, 16);
- } else
- l--;
- break;
- case '0':
- /* Grab an octal code */
- c[2] = '\0';
- for (i = 0; i < 3; i++) {
- if (*str < '0' || *str > '7')
- break;
- c[i] = *str++;
- }
- if (c[2] != '\0') {
- i = strtol(c, NULL, 8);
- if (i > 255)
- i = 255;
- *sbuf ++= i;
- } else
- l--;
- break;
- default:
- *sbuf++ = *str++;
- }
- } else
- *sbuf++ = *str++;
+}
+
+void
+send_decline(struct interface *iface)
+{
+ send_message(iface, DHCP_DECLINE, NULL);
+}
+
+static void
+log_dhcp(int lvl, const char *msg,
+ const struct interface *iface, const struct dhcp_message *dhcp)
+{
+ char *a;
+ struct in_addr addr;
+ int r;
+
+ if (strcmp(msg, "NAK:") == 0)
+ a = get_option_string(dhcp, DHO_MESSAGE);
+ else {
+ addr.s_addr = dhcp->yiaddr;
+ a = xstrdup(inet_ntoa(addr));
}
- return l;
+ r = get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID);
+ if (dhcp->servername[0] && r == 0)
+ logger(lvl, "%s: %s %s from %s `%s'", iface->name, msg, a,
+ inet_ntoa(addr), dhcp->servername);
+ else if (r == 0)
+ logger(lvl, "%s: %s %s from %s",
+ iface->name, msg, a, inet_ntoa(addr));
+ else
+ logger(lvl, "%s: %s %s", iface->name, msg, a);
+ free(a);
}
-static int
-parse_option(int opt, char *oarg, struct options *options)
+static void
+handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp)
{
- int i;
- char *p;
- ssize_t s;
+ struct if_state *state = iface->state;
+ struct if_options *ifo = state->options;
+ struct dhcp_message *dhcp = *dhcpp;
+ struct dhcp_lease *lease = &state->lease;
+ uint8_t type, tmp;
struct in_addr addr;
+ size_t i;
- switch(opt) {
- case 'b':
- options->options |= DHCPCD_BACKGROUND;
- break;
- case 'c':
- strlcpy(options->script, oarg, sizeof(options->script));
- break;
- case 'h':
- if (oarg)
- s = parse_string(options->hostname + 1,
- HOSTNAME_MAX_LEN, oarg);
- else
- s = 0;
- if (s == -1) {
- logger(LOG_ERR, "hostname: %s", strerror(errno));
- return -1;
- }
- if (s != 0 && options->hostname[1] == '.') {
- logger(LOG_ERR, "hostname cannot begin with a .");
- return -1;
- }
- options->hostname[0] = (uint8_t)s;
- break;
- case 'i':
- if (oarg)
- s = parse_string((char *)options->vendorclassid + 1,
- VENDORCLASSID_MAX_LEN, oarg);
- else
- s = 0;
- if (s == -1) {
- logger(LOG_ERR, "vendorclassid: %s", strerror(errno));
- return -1;
- }
- *options->vendorclassid = (uint8_t)s;
- break;
- case 'l':
- if (*oarg == '-') {
- logger(LOG_ERR,
- "leasetime must be a positive value");
- return -1;
- }
- errno = 0;
- options->leasetime = (uint32_t)strtol(oarg, NULL, 0);
- if (errno == EINVAL || errno == ERANGE) {
- logger(LOG_ERR, "`%s' out of range", oarg);
- return -1;
- }
- break;
- case 'm':
- options->metric = atoint(oarg);
- if (options->metric < 0) {
- logger(LOG_ERR, "metric must be a positive value");
- return -1;
+ /* reset the message counter */
+ state->interval = 0;
+
+ /* We have to have DHCP type to work */
+ if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) {
+ log_dhcp(LOG_ERR, "no DHCP type in", iface, dhcp);
+ return;
+ }
+
+ /* Ensure that it's not from a blacklisted server.
+ * We should expand this to check IP and/or hardware address
+ * at the packet level. */
+ if (ifo->blacklist_len != 0 &&
+ get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID) == 0)
+ {
+ for (i = 0; i < ifo->blacklist_len; i++) {
+ if (ifo->blacklist[i] != addr.s_addr)
+ continue;
+ if (dhcp->servername[0])
+ logger(LOG_WARNING,
+ "%s: ignoring blacklisted server %s `%s'",
+ iface->name,
+ inet_ntoa(addr), dhcp->servername);
+ else
+ logger(LOG_WARNING,
+ "%s: ignoring blacklisted server %s",
+ iface->name, inet_ntoa(addr));
+ return;
}
- break;
- case 'o':
- if (make_option_mask(options->requestmask, &oarg, 1) != 0) {
- logger(LOG_ERR, "unknown option `%s'", oarg);
- return -1;
+ }
+
+ /* We should restart on a NAK */
+ if (type == DHCP_NAK) {
+ log_dhcp(LOG_WARNING, "NAK:", iface, dhcp);
+ drop_config(iface, "EXPIRE");
+ delete_event(iface->raw_fd);
+ close(iface->raw_fd);
+ iface->raw_fd = -1;
+ close(iface->udp_fd);
+ iface->udp_fd = -1;
+ /* If we constantly get NAKS then we should slowly back off */
+ add_timeout_sec(state->nakoff, start_interface, iface);
+ state->nakoff *= 2;
+ if (state->nakoff > NAKOFF_MAX)
+ state->nakoff = NAKOFF_MAX;
+ return;
+ }
+
+ /* No NAK, so reset the backoff */
+ state->nakoff = 1;
+
+ /* Ensure that all required options are present */
+ for (i = 1; i < 255; i++) {
+ if (has_option_mask(ifo->requiremask, i) &&
+ get_option_uint8(&tmp, dhcp, i) != 0)
+ {
+ log_dhcp(LOG_WARNING, "reject", iface, dhcp);
+ return;
}
- break;
- case 'p':
- options->options |= DHCPCD_PERSISTENT;
- break;
- case 'q':
- setloglevel(LOG_WARNING);
- break;
- case 's':
- options->options |= DHCPCD_INFORM;
- options->options |= DHCPCD_PERSISTENT;
- options->options &= ~DHCPCD_ARP;
- if (!oarg || *oarg == '\0') {
- options->request_address.s_addr = 0;
- break;
- } else {
- if ((p = strchr(oarg, '/'))) {
- /* nullify the slash, so the -r option
- * can read the address */
- *p++ = '\0';
- if (sscanf(p, "%d", &i) != 1 ||
- inet_cidrtoaddr(i, &options->request_netmask) != 0)
- {
- logger(LOG_ERR,
- "`%s' is not a valid CIDR",
- p);
- return -1;
- }
+ }
+
+ if (type == DHCP_OFFER && state->state == DHS_DISCOVERING) {
+ lease->addr.s_addr = dhcp->yiaddr;
+ get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID);
+ log_dhcp(LOG_INFO, "offered", iface, dhcp);
+ if (ifo->options & DHCPCD_TEST) {
+ run_script(iface, "TEST");
+ exit(EXIT_SUCCESS);
+ }
+ free(state->offer);
+ state->offer = dhcp;
+ *dhcpp = NULL;
+ delete_timeout(send_discover, iface);
+ if (ifo->options & DHCPCD_ARP &&
+ iface->addr.s_addr != state->offer->yiaddr)
+ {
+ /* If the interface already has the address configured
+ * then we can't ARP for duplicate detection. */
+ addr.s_addr = state->offer->yiaddr;
+ if (!has_address(iface->name, &addr, NULL)) {
+ state->state = DHS_PROBING;
+ state->claims = 0;
+ state->probes = 0;
+ state->conflicts = 0;
+ send_arp_probe(iface);
+ return;
}
}
- /* FALLTHROUGH */
- case 'r':
- if (!(options->options & DHCPCD_INFORM))
- options->options |= DHCPCD_REQUEST;
- if (oarg && !inet_aton(oarg, &options->request_address)) {
- logger(LOG_ERR, "`%s' is not a valid IP address",
- oarg);
- return -1;
- }
- break;
- case 't':
- options->timeout = atoint(oarg);
- if (options->timeout < 0) {
- logger (LOG_ERR, "timeout must be a positive value");
- return -1;
- }
- break;
- case 'u':
- s = USERCLASS_MAX_LEN - options->userclass[0] - 1;
- s = parse_string((char *)options->userclass + options->userclass[0] + 2,
- s, oarg);
- if (s == -1) {
- logger(LOG_ERR, "userclass: %s", strerror(errno));
- return -1;
- }
- if (s != 0) {
- options->userclass[options->userclass[0] + 1] = s;
- options->userclass[0] += s + 1;
- }
- break;
- case 'v':
- p = strchr(oarg, ',');
- if (!p || !p[1]) {
- logger(LOG_ERR, "invalid vendor format");
- return -1;
- }
- *p = '\0';
- i = atoint(oarg);
- oarg = p + 1;
- if (i < 1 || i > 254) {
- logger(LOG_ERR, "vendor option should be between"
- " 1 and 254 inclusive");
- return -1;
- }
- s = VENDOR_MAX_LEN - options->vendor[0] - 2;
- if (inet_aton(oarg, &addr) == 1) {
- if (s < 6) {
- s = -1;
- errno = ENOBUFS;
- } else
- memcpy(options->vendor + options->vendor[0] + 3,
- &addr.s_addr, sizeof(addr.s_addr));
- } else {
- s = parse_string((char *)options->vendor + options->vendor[0] + 3,
- s, oarg);
- }
- if (s == -1) {
- logger(LOG_ERR, "vendor: %s", strerror(errno));
- return -1;
- }
- if (s != 0) {
- options->vendor[options->vendor[0] + 1] = i;
- options->vendor[options->vendor[0] + 2] = s;
- options->vendor[0] += s + 2;
- }
- break;
- case 'A':
- options->options &= ~DHCPCD_ARP;
- /* IPv4LL requires ARP */
- options->options &= ~DHCPCD_IPV4LL;
- break;
- case 'B':
- options->options &= ~DHCPCD_DAEMONISE;
- break;
- case 'C':
- /* Commas to spaces for shell */
- while ((p = strchr(oarg, ',')))
- *p = ' ';
- s = strlen("skip_hooks=") + strlen(oarg) + 1;
- p = xmalloc(sizeof(char) * s);
- snprintf(p, s, "skip_hooks=%s", oarg);
- add_environ(options, p, 0);
- free(p);
- break;
- case 'D':
- options->options |= DHCPCD_DUID;
- break;
- case 'E':
- options->options |= DHCPCD_LASTLEASE;
- break;
- case 'F':
- if (!oarg) {
- options->fqdn = FQDN_BOTH;
+ state->state = DHS_REQUESTING;
+ send_request(iface);
+ return;
+ }
+
+ if (type == DHCP_OFFER) {
+ log_dhcp(LOG_INFO, "ignoring offer of", iface, dhcp);
+ return;
+ }
+
+ /* We should only be dealing with acks */
+ if (type != DHCP_ACK) {
+ log_dhcp(LOG_ERR, "not ACK or OFFER", iface, dhcp);
+ return;
+ }
+
+ if (!(ifo->options & DHCPCD_INFORM))
+ log_dhcp(LOG_INFO, "acknowledged", iface, dhcp);
+ close_sockets(iface);
+ free(state->offer);
+ state->offer = dhcp;
+ *dhcpp = NULL;
+ /* Delete all timeouts for this interface. */
+ delete_timeout(NULL, iface);
+ bind_interface(iface);
+}
+
+static void
+handle_dhcp_packet(struct interface *iface)
+{
+ uint8_t *packet;
+ struct dhcp_message *dhcp = NULL;
+ const uint8_t *pp;
+ uint8_t *p;
+ ssize_t bytes;
+
+ /* We loop through until our buffer is empty.
+ * The benefit is that if we get >1 DHCP packet in our buffer and
+ * the first one fails for any reason, we can use the next. */
+ packet = xmalloc(udp_dhcp_len);
+ for(;;) {
+ bytes = get_raw_packet(iface, ETHERTYPE_IP,
+ packet, udp_dhcp_len);
+ if (bytes == 0 || bytes == -1)
break;
+ if (valid_udp_packet(packet) == -1)
+ continue;
+ bytes = get_udp_data(&pp, packet);
+ if ((size_t)bytes > sizeof(*dhcp)) {
+ logger(LOG_ERR, "%s: packet greater than DHCP size",
+ iface->name);
+ continue;
}
- if (strcmp(oarg, "none") == 0)
- options->fqdn = FQDN_NONE;
- else if (strcmp(oarg, "ptr") == 0)
- options->fqdn = FQDN_PTR;
- else if (strcmp(oarg, "both") == 0)
- options->fqdn = FQDN_BOTH;
- else if (strcmp(oarg, "disable") == 0)
- options->fqdn = FQDN_DISABLE;
- else {
- logger(LOG_ERR, "invalid value `%s' for FQDN",
- oarg);
- return -1;
- }
- break;
- case 'G':
- options->options &= ~DHCPCD_GATEWAY;
- break;
- case 'I':
- /* Strings have a type of 0 */;
- options->clientid[1] = 0;
- if (oarg)
- s = parse_string_hwaddr((char *)options->clientid + 1,
- CLIENTID_MAX_LEN, oarg, 1);
- else
- s = 0;
- if (s == -1) {
- logger(LOG_ERR, "clientid: %s", strerror(errno));
- return -1;
- }
- options->clientid[0] = (uint8_t)s;
- if (s == 0) {
- options->options &= ~DHCPCD_DUID;
- options->options &= ~DHCPCD_CLIENTID;
+ if (!dhcp)
+ dhcp = xmalloc(sizeof(*dhcp));
+ memcpy(dhcp, pp, bytes);
+ if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
+ logger(LOG_DEBUG, "%s: bogus cookie, ignoring",
+ iface->name);
+ continue;
}
- break;
- case 'K':
- options->options &= ~DHCPCD_LINK;
- break;
- case 'L':
- options->options &= ~DHCPCD_IPV4LL;
- break;
- case 'O':
- if (make_option_mask(options->requestmask, &oarg, -1) != 0 ||
- make_option_mask(options->requiremask, &oarg, -1) != 0 ||
- make_option_mask(options->nomask, &oarg, 1) != 0)
- {
- logger(LOG_ERR, "unknown option `%s'", oarg);
- return -1;
+ /* Ensure it's the right transaction */
+ if (iface->state->xid != dhcp->xid) {
+ logger(LOG_DEBUG,
+ "%s: ignoring packet with xid 0x%x as"
+ " it's not ours (0x%x)",
+ iface->name, dhcp->xid, iface->state->xid);
+ continue;
}
- break;
- case 'Q':
- if (make_option_mask(options->requiremask, &oarg, 1) != 0 ||
- make_option_mask(options->requestmask, &oarg, 1) != 0)
+ /* Ensure packet is for us */
+ if (iface->hwlen <= sizeof(dhcp->chaddr) &&
+ memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen))
{
- logger(LOG_ERR, "unknown option `%s'", oarg);
- return -1;
- }
- break;
- case 'X':
- if (!inet_aton(oarg, &addr)) {
- logger(LOG_ERR, "`%s' is not a valid IP address",
- oarg);
- return -1;
+ logger(LOG_DEBUG, "%s: xid 0x%x is not for our hwaddr %s",
+ iface->name, dhcp->xid,
+ hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr)));
+ continue;
}
- options->blacklist = xrealloc(options->blacklist,
- sizeof(in_addr_t) * (options->blacklist_len + 1));
- options->blacklist[options->blacklist_len] = addr.s_addr;
- options->blacklist_len++;
- break;
- default:
- return 0;
+ /* We should ensure that the packet is terminated correctly
+ * if we have space for the terminator */
+ if ((size_t)bytes < sizeof(struct dhcp_message)) {
+ p = (uint8_t *)dhcp + bytes - 1;
+ while (p > dhcp->options && *p == DHO_PAD)
+ p--;
+ if (*p != DHO_END)
+ *++p = DHO_END;
+ }
+ handle_dhcp(iface, &dhcp);
+ if (iface->raw_fd == -1)
+ break;
}
-
- return 1;
+ free(packet);
+ free(dhcp);
}
-static int
-parse_config_line(const char *opt, char *line, struct options *options)
+static void
+open_sockets(struct interface *iface)
{
- unsigned int i;
+ if (iface->udp_fd != -1)
+ close(iface->udp_fd);
+ if (open_udp_socket(iface) == -1 &&
+ (errno != EADDRINUSE || iface->addr.s_addr != 0))
+ logger(LOG_ERR, "open_udp_socket: %s", strerror(errno));
+ if (iface->raw_fd != -1)
+ delete_event(iface->raw_fd);
+ if (open_socket(iface, ETHERTYPE_IP) == -1)
+ logger(LOG_ERR, "open_socket: %s", strerror(errno));
+ if (iface->raw_fd != -1)
+ add_event(iface->raw_fd, handle_dhcp_packet, iface);
+}
- for (i = 0; i < sizeof(longopts) / sizeof(longopts[0]); i++) {
- if (!longopts[i].name ||
- strcmp(longopts[i].name, opt) != 0)
- continue;
+static void
+handle_link(struct interface *iface)
+{
+ int retval;
- if (longopts[i].has_arg == required_argument && !line) {
- fprintf(stderr,
- PACKAGE ": option requires an argument -- %s\n",
- opt);
- return -1;
+ retval = link_changed(linkfd, ifaces);
+ if (retval == -1) {
+ logger(LOG_ERR, "link_changed: %s", strerror(errno));
+ return;
+ }
+ if (retval == 0)
+ return;
+ for (iface = ifaces; iface; iface = iface->next) {
+ if (iface->state->options->options & DHCPCD_LINK) {
+ switch (carrier_status(iface->name)) {
+ case -1:
+ logger(LOG_ERR, "carrier_status: %s",
+ strerror(errno));
+ break;
+ case 0:
+ if (iface->state->carrier != LINK_DOWN) {
+ iface->state->carrier = LINK_DOWN;
+ logger(LOG_INFO, "%s: carrier lost",
+ iface->name);
+ close_sockets(iface);
+ delete_timeouts(iface, start_expire, NULL);
+ }
+ break;
+ default:
+ if (iface->state->carrier != LINK_UP) {
+ iface->state->carrier = LINK_UP;
+ logger(LOG_INFO, "%s: carrier acquired",
+ iface->name);
+ start_interface(iface);
+ }
+ break;
+ }
}
+ }
+}
- return parse_option(longopts[i].val, line, options);
+void
+start_discover(struct interface *iface)
+{
+ struct if_options *ifo = iface->state->options;
+
+ iface->state->state = DHS_DISCOVERING;
+ iface->state->xid = arc4random();
+ open_sockets(iface);
+ delete_timeout(NULL, iface);
+ if (ifo->options & DHCPCD_IPV4LL &&
+ !IN_LINKLOCAL(htonl(iface->addr.s_addr)))
+ {
+ if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr)))
+ add_timeout_sec(RATE_LIMIT_INTERVAL, start_ipv4ll, iface);
+ else
+ add_timeout_sec(ifo->timeout, start_ipv4ll, iface);
}
+ logger(LOG_INFO, "%s: broadcasting for a lease", iface->name);
+ send_discover(iface);
+}
+
- fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt);
- return -1;
+void
+start_reboot(struct interface *iface)
+{
+ struct if_options *ifo = iface->state->options;
+
+ logger(LOG_INFO, "%s: rebinding lease of %s",
+ iface->name, inet_ntoa(iface->state->lease.addr));
+ iface->state->state = DHS_REBINDING;
+ iface->state->xid = arc4random();
+ iface->state->lease.server.s_addr = 0;
+ delete_timeout(NULL, iface);
+ add_timeout_sec(ifo->timeout, start_expire, iface);
+ open_sockets(iface);
+ send_rebind(iface);
}
-int
-main(int argc, char **argv)
+void
+start_interface(struct interface *iface)
{
- struct options *options;
- int opt;
- int option_index = 0;
- char *prefix;
- pid_t pid;
- int debug = 0;
- int i, r;
- int pid_fd = -1;
- int sig = 0;
- int retval = EXIT_FAILURE;
- char *line, *option, *p, *buffer = NULL;
- size_t len = 0;
- FILE *f;
- char *cf = NULL;
- char *intf = NULL;
- struct timespec ts;
+ iface->start_uptime = uptime();
+ if (!iface->state->lease.addr.s_addr)
+ start_discover(iface);
+ else if (IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr)))
+ start_ipv4ll(iface);
+ else
+ start_reboot(iface);
+}
- closefrom(3);
- /* Saves calling fflush(stream) in the logger */
- setlinebuf(stdout);
- openlog(PACKAGE, LOG_PID, LOG_LOCAL0);
- setlogprefix(PACKAGE ": ");
-
- options = xzalloc(sizeof(*options));
- options->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE;
- options->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK;
- options->timeout = DEFAULT_TIMEOUT;
- strlcpy(options->script, SCRIPT, sizeof(options->script));
-
- options->vendorclassid[0] = snprintf((char *)options->vendorclassid + 1,
- VENDORCLASSID_MAX_LEN,
- "%s %s", PACKAGE, VERSION);
-
-#ifdef CMDLINE_COMPAT
- add_option_mask(options->requestmask, DHO_DNSSERVER);
- add_option_mask(options->requestmask, DHO_DNSDOMAIN);
- add_option_mask(options->requestmask, DHO_DNSSEARCH);
- add_option_mask(options->requestmask, DHO_NISSERVER);
- add_option_mask(options->requestmask, DHO_NISDOMAIN);
- add_option_mask(options->requestmask, DHO_NTPSERVER);
-
- /* If the duid file exists, then enable duid by default
- * This means we don't break existing clients that easily :) */
- if ((f = fopen(DUID, "r"))) {
- options->options |= DHCPCD_DUID;
- fclose(f);
+static void
+init_state(struct interface *iface, int argc, char **argv)
+{
+ struct if_state *ifs;
+ struct if_options *ifo;
+ uint8_t *duid;
+ size_t len = 0, ifl;
+
+ if (iface->state) {
+ ifs = iface->state;
+ free_options(ifs->options);
+ } else
+ ifs = iface->state = xzalloc(sizeof(*ifs));
+
+ ifs->state = DHS_INIT;
+ ifs->nakoff = 1;
+ ifo = ifs->options = read_config(cffile, iface->name);
+ add_options(ifo, argc, argv);
+
+ if (ifo->metric)
+ iface->metric = ifo->metric;
+
+ if (*ifo->clientid) {
+ iface->clientid = xmalloc(ifo->clientid[0] + 1);
+ memcpy(iface->clientid, ifo->clientid, ifo->clientid[0] + 1);
+ } else if (ifo->options & DHCPCD_CLIENTID) {
+ if (ifo->options & DHCPCD_DUID) {
+ duid = xmalloc(DUID_LEN);
+ if ((len = get_duid(duid, iface)) == 0)
+ logger(LOG_ERR, "get_duid: %s", strerror(errno));
+ }
+ if (len > 0) {
+ iface->clientid = xmalloc(len + 6);
+ iface->clientid[0] = len + 5;
+ iface->clientid[1] = 255; /* RFC 4361 */
+ ifl = strlen(iface->name);
+ if (ifl < 5) {
+ memcpy(iface->clientid + 2, iface->name, ifl);
+ if (ifl < 4)
+ memset(iface->clientid + 2 + ifl,
+ 0, 4 - ifl);
+ } else {
+ ifl = htonl(if_nametoindex(iface->name));
+ memcpy(iface->clientid + 2, &ifl, 4);
+ }
+ } else if (len == 0) {
+ len = iface->hwlen + 1;
+ iface->clientid = xmalloc(len + 1);
+ iface->clientid[0] = len;
+ iface->clientid[1] = iface->family;
+ memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen);
+ }
}
-#endif
- gethostname(options->hostname + 1, sizeof(options->hostname));
- if (strcmp(options->hostname + 1, "(none)") == 0 ||
- strcmp(options->hostname + 1, "localhost") == 0)
- options->hostname[1] = '\0';
- *options->hostname = strlen(options->hostname + 1);
+ if (!(ifo->options & DHCPCD_TEST))
+ run_script(iface, "PREINIT");
- while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS,
- longopts, &option_index)) != -1)
- {
- switch (opt) {
+ if (ifo->options & DHCPCD_LINK) {
+ switch (carrier_status(iface->name)) {
case 0:
- if (longopts[option_index].flag)
- break;
- logger(LOG_ERR, "option `%s' should set a flag",
- longopts[option_index].name);
- goto abort;
- case 'f':
- cf = optarg;
+ ifs->carrier = LINK_DOWN;
break;
- case 'V':
- print_options();
- goto abort;
- case '?':
- usage();
- goto abort;
+ case 1:
+ ifs->carrier = LINK_UP;
+ break;
+ default:
+ ifs->carrier = LINK_UNKNOWN;
}
}
- if (doversion)
- printf(""PACKAGE" "VERSION"\n%s\n", copyright);
+ if (ifs->carrier == LINK_DOWN)
+ logger(LOG_INFO, "%s: waiting for carrier", iface->name);
+ else
+ start_interface(iface);
+}
- if (dohelp)
- usage();
+static void
+handle_signal(struct interface *iface)
+{
+ int sig = signal_read();
- if (optind < argc) {
- if (strlen(argv[optind]) >= IF_NAMESIZE) {
- logger(LOG_ERR,
- "`%s' too long for an interface name (max=%d)",
- argv[optind], IF_NAMESIZE);
- goto abort;
- }
- strlcpy(options->interface, argv[optind],
- sizeof(options->interface));
- } else {
- /* If only version was requested then exit now */
- if (doversion || dohelp) {
- retval = 0;
- goto abort;
- }
+ switch (sig) {
+ case SIGINT:
+ logger(LOG_INFO, "received SIGINT, stopping");
+ break;
+ case SIGTERM:
+ logger(LOG_INFO, "received SIGTERM, stopping");
+ break;
+ default:
+ logger (LOG_ERR,
+ "received signal %d, but don't know what to do with it",
+ sig);
+ return;
+ }
- logger(LOG_ERR, "no interface specified");
- goto abort;
+ for (iface = ifaces; iface; iface = iface->next) {
+ if (!iface->state)
+ continue;
+ if (!(iface->state->options->options & DHCPCD_PERSISTENT))
+ drop_config(iface, "STOP");
}
+ exit(EXIT_FAILURE);
+}
- /* Parse our options file */
- f = fopen(cf ? cf : CONFIG, "r");
- if (f) {
- r = 1;
- while ((get_line(&buffer, &len, f))) {
- line = buffer;
- while ((option = strsep(&line, " \t")))
- if (*option != '\0')
- break;
- if (!option || *option == '\0' || *option == '#')
- continue;
- /* Trim leading whitespace */
- if (line) {
- while (*line != '\0' && (*line == ' ' || *line == '\t'))
- line++;
- }
- /* Trim trailing whitespace */
- if (line && *line) {
- p = line + strlen(line) - 1;
- while (p != line &&
- (*p == ' ' || *p == '\t') &&
- *(p - 1) != '\\')
- *p-- = '\0';
- }
- if (strcmp(option, "interface") == 0) {
- free(intf);
- intf = xstrdup(line);
- continue;
- }
- /* If we're in an interface block don't use these
- * options unless it's for us */
- if (intf && strcmp(intf, options->interface) != 0)
- continue;
- r = parse_config_line(option, line, options);
- if (r != 1)
- break;
- }
- free(buffer);
- free(intf);
- fclose(f);
- if (r == 0)
+int
+main(int argc, char **argv)
+{
+ struct if_options *ifo;
+ struct interface *iface;
+ int opt, oi = 0, test = 0, signal_fd, sig = 0, i;
+ pid_t pid;
+ struct timespec ts;
+
+ closefrom(3);
+ /* Saves calling fflush(stream) in the logger */
+ setlinebuf(stdout);
+ openlog(PACKAGE, LOG_PID, LOG_LOCAL0);
+ strncpy(cffile, CONFIG, sizeof(cffile));
+
+ /* Test for --help and --version */
+ if (argc > 1) {
+ if (strcmp(argv[1], "--help") == 0) {
usage();
- if (r != 1)
- goto abort;
- } else {
- if (errno != ENOENT || cf) {
- logger(LOG_ERR, "fopen `%s': %s", cf ? cf : CONFIG,
- strerror(errno));
- goto abort;
+ exit(EXIT_SUCCESS);
+ } else if (strcmp(argv[1], "--version") == 0) {
+ printf(""PACKAGE" "VERSION"\n%s\n", copyright);
+ exit(EXIT_SUCCESS);
}
}
- optind = 0;
- while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS,
- longopts, &option_index)) != -1)
+ while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
{
switch (opt) {
case 'd':
- debug++;
- switch (debug) {
- case 1:
- setloglevel(LOG_DEBUG);
- break;
- case 2:
- options->options &= ~DHCPCD_DAEMONISE;
- break;
- }
+ setloglevel(LOG_DEBUG);
break;
case 'f':
- break;
- case 'k':
- sig = SIGHUP;
- break;
- case 'n':
- sig = SIGALRM;
+ strlcpy(cffile, optarg, sizeof(cffile));
break;
case 'x':
sig = SIGTERM;
break;
case 'T':
- options->options |= DHCPCD_TEST | DHCPCD_PERSISTENT;
- break;
-#ifdef CMDLINE_COMPAT
- case 'H': /* FALLTHROUGH */
- case 'M':
- del_option_mask(options->requestmask, DHO_MTU);
- break;
- case 'N':
- del_option_mask(options->requestmask, DHO_NTPSERVER);
+ test = 1;
break;
- case 'R':
- del_option_mask(options->requestmask, DHO_DNSSERVER);
- del_option_mask(options->requestmask, DHO_DNSDOMAIN);
- del_option_mask(options->requestmask, DHO_DNSSEARCH);
- break;
- case 'S':
- add_option_mask(options->requestmask, DHO_MSCSR);
- break;
- case 'Y':
- del_option_mask(options->requestmask, DHO_NISSERVER);
- del_option_mask(options->requestmask, DHO_NISDOMAIN);
- break;
-#endif
- default:
- i = parse_option(opt, optarg, options);
- if (i == 1)
- break;
- if (i == 0)
- usage();
- goto abort;
+ case 'V':
+ print_options();
+ exit(EXIT_SUCCESS);
+ case '?':
+ usage();
+ exit(EXIT_FAILURE);
}
}
+ ifo = read_config(cffile, NULL);
+ opt = add_options(ifo, argc, argv);
+ if (opt != 1) {
+ if (opt == 0)
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ if (test)
+ ifo->options |= DHCPCD_TEST | DHCPCD_PERSISTENT;
#ifdef THERE_IS_NO_FORK
- options->options &= ~DHCPCD_DAEMONISE;
+ ifo->options &= ~DHCPCD_DAEMONISE;
#endif
+ /* If we have any other args, we should run as a single dhcpcd instance
+ * for that interface. */
+ if (optind == argc - 1)
+ snprintf(pidfile, sizeof(pidfile), PIDFILE, "-", argv[optind]);
+ else
+ snprintf(pidfile, sizeof(pidfile), PIDFILE, "", "");
+
if (geteuid())
logger(LOG_WARNING, PACKAGE " will not work correctly unless"
" run as root");
- if (options->options & DHCPCD_TEST) {
- if (options->options & DHCPCD_REQUEST ||
- options->options & DHCPCD_INFORM) {
- logger(LOG_ERR,
- "cannot test with --inform or --request");
- goto abort;
- }
-
- if (options->options & DHCPCD_LASTLEASE) {
- logger(LOG_ERR, "cannot test with --lastlease");
- goto abort;
- }
-
- if (sig != 0) {
- logger(LOG_ERR,
- "cannot test with --release or --renew");
- goto abort;
- }
- }
-
- prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3));
- snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface);
- setlogprefix(prefix);
- snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE,
- options->interface);
- free(prefix);
-
- if (options->request_address.s_addr == 0 &&
- (options->options & DHCPCD_INFORM ||
- options->options & DHCPCD_REQUEST))
- {
- errno = 0;
- if (get_address(options->interface,
- &options->request_address,
- &options->request_netmask) != 1)
- {
- if (errno)
- logger(LOG_ERR, "get_address: %s",
- strerror(errno));
- else
- logger(LOG_ERR, "no existing address");
- goto abort;
- }
- }
- if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) {
- logger(LOG_ERR,
- "you are not allowed to request a link local address");
- goto abort;
- }
-
chdir("/");
umask(022);
+ atexit(cleanup);
- if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) {
+ if (sig != 0) {
i = -1;
- pid = read_pid(options->pidfile);
+ pid = read_pid();
if (pid != 0)
logger(LOG_INFO, "sending signal %d to pid %d",
sig, pid);
if (!pid || (i = kill(pid, sig))) {
if (sig != SIGALRM)
logger(LOG_ERR, ""PACKAGE" not running");
- unlink(options->pidfile);
- }
- if (i == 0) {
- if (sig == SIGALRM) {
- retval = EXIT_SUCCESS;
- goto abort;
- }
- /* Spin until it exits */
- logger(LOG_INFO, "waiting for pid %d to exit", pid);
- ts.tv_sec = 0;
- ts.tv_nsec = 100000000; /* 10th of a second */
- for(i = 0; i < 100; i++) {
- nanosleep(&ts, NULL);
- if (read_pid(options->pidfile) == 0) {
- retval = EXIT_SUCCESS;
- break;
- }
- }
- if (retval != EXIT_SUCCESS)
- logger(LOG_ERR, "pid %d failed to exit", pid);
- goto abort;
- }
- if (sig != SIGALRM)
- goto abort;
+ unlink(pidfile);
+ exit(EXIT_FAILURE);
+ }
+ /* Spin until it exits */
+ logger(LOG_INFO, "waiting for pid %d to exit", pid);
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100000000; /* 10th of a second */
+ for(i = 0; i < 100; i++) {
+ nanosleep(&ts, NULL);
+ if (read_pid() == 0)
+ exit(EXIT_SUCCESS);
+ }
+ logger(LOG_ERR, "pid %d failed to exit", pid);
+ exit(EXIT_FAILURE);
}
- if (!(options->options & DHCPCD_TEST) &&
- !(options->options & DHCPCD_DAEMONISED))
- {
- if ((pid = read_pid(options->pidfile)) > 0 &&
+ if (!(ifo->options & DHCPCD_TEST)) {
+ if ((pid = read_pid()) > 0 &&
kill(pid, 0) == 0)
{
logger(LOG_ERR, ""PACKAGE
" already running on pid %d (%s)",
- pid, options->pidfile);
- goto abort;
+ pid, pidfile);
+ exit(EXIT_FAILURE);
}
- pid_fd = open(options->pidfile,
- O_WRONLY | O_CREAT | O_NONBLOCK, 0664);
- if (pid_fd == -1) {
+ pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664);
+ if (pidfd == -1) {
logger(LOG_ERR, "open `%s': %s",
- options->pidfile, strerror(errno));
- goto abort;
+ pidfile, strerror(errno));
+ exit(EXIT_FAILURE);
}
-
/* Lock the file so that only one instance of dhcpcd runs
* on an interface */
- if (flock(pid_fd, LOCK_EX | LOCK_NB) == -1) {
+ if (flock(pidfd, LOCK_EX | LOCK_NB) == -1) {
logger(LOG_ERR, "flock `%s': %s",
- options->pidfile, strerror(errno));
- goto abort;
+ pidfile, strerror(errno));
+ exit(EXIT_FAILURE);
}
-
- if (set_cloexec(pid_fd) == -1)
- goto abort;
- writepid(pid_fd, getpid());
- logger(LOG_INFO, PACKAGE " " VERSION " starting");
+ if (set_cloexec(pidfd) == -1)
+ exit(EXIT_FAILURE);
+ writepid(pidfd, getpid());
}
- /* Terminate the encapsulated options */
- if (options->vendor[0]) {
- options->vendor[0]++;
- options->vendor[options->vendor[0]] = DHO_END;
- }
+ logger(LOG_INFO, PACKAGE " " VERSION " starting");
- if (dhcp_run(options, &pid_fd) == 0)
- retval = EXIT_SUCCESS;
+ if ((signal_fd =signal_init()) == -1)
+ exit(EXIT_FAILURE);
+ if (signal_setup() == -1)
+ exit(EXIT_FAILURE);
+ add_event(signal_fd, handle_signal, NULL);
-abort:
- /* If we didn't daemonise then we need to punt the pidfile now */
- if (pid_fd > -1) {
- close(pid_fd);
- unlink(options->pidfile);
+ if (ifo->options & DHCPCD_LINK) {
+ linkfd = open_link_socket();
+ if (linkfd == -1)
+ logger(LOG_ERR, "open_link_socket: %s",
+ strerror(errno));
+ else
+ add_event(linkfd, handle_link, NULL);
}
- if (options->environ) {
- len = 0;
- while (options->environ[len])
- free(options->environ[len++]);
- free(options->environ);
+
+ if (!(ifo->options & DHCPCD_DAEMONISE))
+ can_daemonise = 0;
+ if (can_daemonise && !(ifo->options & DHCPCD_BACKGROUND)) {
+ oi = ifo->timeout;
+ if (ifo->options & DHCPCD_IPV4LL)
+ oi += 10;
+ add_timeout_sec(oi, handle_exit_timeout, NULL);
}
- free(options->blacklist);
- free(options);
- exit(retval);
+ free_options(ifo);
+
+ argc -= optind;
+ argv += optind;
+ ifaces = discover_interfaces(argc, argv);
+ argc += optind;
+ argv -= optind;
+ for (iface = ifaces; iface; iface = iface->next)
+ init_state(iface, argc, argv);
+ start_eloop();
/* NOTREACHED */
}
#ifndef DHCPCD_H
#define DHCPCD_H
-#include <sys/param.h>
-#include <sys/socket.h>
-
#include <net/if.h>
-#include <netinet/in.h>
#include <limits.h>
-#include "common.h"
-
-#define DEFAULT_TIMEOUT 30
-#define DEFAULT_LEASETIME 3600 /* 1 hour */
-
-#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */
-#define VENDORCLASSID_MAX_LEN 48
-#define CLIENTID_MAX_LEN 48
-#define USERCLASS_MAX_LEN 255
-#define VENDOR_MAX_LEN 255
-
-#define DHCPCD_ARP (1 << 0)
-#define DHCPCD_DOMAIN (1 << 2)
-#define DHCPCD_GATEWAY (1 << 3)
-#define DHCPCD_LASTLEASE (1 << 7)
-#define DHCPCD_INFORM (1 << 8)
-#define DHCPCD_REQUEST (1 << 9)
-#define DHCPCD_IPV4LL (1 << 10)
-#define DHCPCD_DUID (1 << 11)
-#define DHCPCD_PERSISTENT (1 << 12)
-#define DHCPCD_DAEMONISE (1 << 14)
-#define DHCPCD_DAEMONISED (1 << 15)
-#define DHCPCD_TEST (1 << 16)
-#define DHCPCD_FORKED (1 << 17)
-#define DHCPCD_HOSTNAME (1 << 18)
-#define DHCPCD_CLIENTID (1 << 19)
-#define DHCPCD_LINK (1 << 20)
-#define DHCPCD_BACKGROUND (1 << 21)
-
-struct options {
- char interface[IF_NAMESIZE];
+#include "dhcp.h"
+
+#define HWADDR_LEN 20
+
+enum DHS {
+ DHS_INIT,
+ DHS_DISCOVERING,
+ DHS_REQUESTING,
+ DHS_BOUND,
+ DHS_RENEWING,
+ DHS_REBINDING,
+ DHS_REBOOT,
+ DHS_RENEW_REQUESTED,
+ DHS_INIT_IPV4LL,
+ DHS_PROBING,
+ DHS_ANNOUNCING
+};
+
+#define LINK_UP 1
+#define LINK_UNKNOWN 0
+#define LINK_DOWN -1
+
+struct if_state {
+ enum DHS state;
+ struct if_options *options;
+ struct dhcp_message *sent;
+ struct dhcp_message *offer;
+ struct dhcp_message *new;
+ struct dhcp_message *old;
+ struct dhcp_lease lease;
+ time_t interval;
+ time_t nakoff;
+ uint32_t xid;
+ int socket;
+ int carrier;
+ int probes;
+ int claims;
+ int conflicts;
+ time_t defend;
+ struct in_addr fail;
+};
+
+struct interface
+{
+ char name[IF_NAMESIZE];
+ struct if_state *state;
+
+ sa_family_t family;
+ unsigned char hwaddr[HWADDR_LEN];
+ size_t hwlen;
int metric;
- uint8_t requestmask[256 / 8];
- uint8_t requiremask[256 / 8];
- uint8_t nomask[256 / 8];
- uint32_t leasetime;
- time_t timeout;
- int options;
-
- struct in_addr request_address;
- struct in_addr request_netmask;
-
- char **environ;
- char script[PATH_MAX];
- char pidfile[PATH_MAX];
-
- char hostname[HOSTNAME_MAX_LEN + 1];
- int fqdn;
- uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 1];
- char clientid[CLIENTID_MAX_LEN + 1];
- uint8_t userclass[USERCLASS_MAX_LEN + 1];
- uint8_t vendor[VENDOR_MAX_LEN + 1];
-
- size_t blacklist_len;
- in_addr_t *blacklist;
+ int arpable;
+
+ int raw_fd;
+ int udp_fd;
+ int arp_fd;
+ size_t buffer_size, buffer_len, buffer_pos;
+ unsigned char *buffer;
+
+ struct in_addr addr;
+ struct in_addr net;
+ struct rt *routes;
+
+ char leasefile[PATH_MAX];
+ time_t start_uptime;
+
+ unsigned char *clientid;
+
+ struct interface *next;
};
+
+extern int pidfd;
+void handle_exit_timeout(struct interface *);
+void send_request(struct interface *);
+void start_interface(struct interface *);
+void start_discover(struct interface *);
+void start_renew(struct interface *);
+void start_rebind(struct interface *);
+void start_reboot(struct interface *);
+void start_expire(struct interface *);
+void send_decline(struct interface *);
+void close_sockets(struct interface *);
+void drop_config(struct interface *, const char *);
+
#endif
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef DHCPF_H
+#define DHCPF_H
+
+#include "dhcp.h"
+#include "if-options.h"
+#include "net.h"
+
+#define add_option_mask(var, val) (var[val >> 3] |= 1 << (val & 7))
+#define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7)))
+#define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7)))
+int make_option_mask(uint8_t *, const char *, int);
+void print_options(void);
+char *get_option_string(const struct dhcp_message *, uint8_t);
+int get_option_addr(uint32_t *, const struct dhcp_message *, uint8_t);
+int get_option_uint32(uint32_t *, const struct dhcp_message *, uint8_t);
+int get_option_uint16(uint16_t *, const struct dhcp_message *, uint8_t);
+int get_option_uint8(uint8_t *, const struct dhcp_message *, uint8_t);
+struct rt *get_option_routes(const struct dhcp_message *);
+ssize_t configure_env(char **, const char *, const struct dhcp_message *,
+ const struct if_options *);
+
+ssize_t make_message(struct dhcp_message **, const struct interface *, uint8_t);
+int valid_dhcp_packet(unsigned char *);
+
+ssize_t write_lease(const struct interface *, const struct dhcp_message *);
+struct dhcp_message *read_lease(const struct interface *);
+void get_lease(struct dhcp_lease *, const struct dhcp_message *);
+#endif
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define THIRTY_YEARS_IN_SECONDS 946707779
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "duid.h"
+#include "net.h"
+
+size_t
+get_duid(unsigned char *duid, const struct interface *iface)
+{
+ FILE *f;
+ uint16_t type = 0;
+ uint16_t hw = 0;
+ uint32_t ul;
+ time_t t;
+ int x = 0;
+ unsigned char *p = duid;
+ size_t len = 0, l = 0;
+ char *buffer = NULL, *line, *option;
+
+ /* If we already have a DUID then use it as it's never supposed
+ * to change once we have one even if the interfaces do */
+ if ((f = fopen(DUID, "r"))) {
+ while ((get_line(&buffer, &len, f))) {
+ line = buffer;
+ while ((option = strsep(&line, " \t")))
+ if (*option != '\0')
+ break;
+ if (!option || *option == '\0' || *option == '#')
+ continue;
+ l = hwaddr_aton(NULL, option);
+ if (l && l <= DUID_LEN) {
+ hwaddr_aton(duid, option);
+ break;
+ }
+ l = 0;
+ }
+ fclose(f);
+ free(buffer);
+ if (l)
+ return l;
+ } else {
+ if (errno != ENOENT)
+ return 0;
+ }
+
+ /* No file? OK, lets make one based on our interface */
+ if (!(f = fopen(DUID, "w")))
+ return 0;
+ type = htons(1); /* DUI-D-LLT */
+ memcpy(p, &type, 2);
+ p += 2;
+ hw = htons(iface->family);
+ memcpy(p, &hw, 2);
+ p += 2;
+ /* time returns seconds from jan 1 1970, but DUID-LLT is
+ * seconds from jan 1 2000 modulo 2^32 */
+ t = time(NULL) - THIRTY_YEARS_IN_SECONDS;
+ ul = htonl(t & 0xffffffff);
+ memcpy(p, &ul, 4);
+ p += 4;
+ /* Finally, add the MAC address of the interface */
+ memcpy(p, iface->hwaddr, iface->hwlen);
+ p += iface->hwlen;
+ len = p - duid;
+ x = fprintf(f, "%s\n", hwaddr_ntoa(duid, len));
+ fclose(f);
+ /* Failed to write the duid? scrub it, we cannot use it */
+ if (x < 1) {
+ len = 0;
+ unlink(DUID);
+ }
+ return len;
+}
* SUCH DAMAGE.
*/
-#ifndef CLIENT_H
-#define CLIENT_H
+#ifndef DUID_H
+#define DUID_H
-#include "dhcpcd.h"
+#include "net.h"
-int dhcp_run(const struct options *, int *);
+size_t get_duid(unsigned char *duid, const struct interface *iface);
#endif
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/time.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "eloop.h"
+#include "logger.h"
+#include "net.h"
+
+static struct timeval now = {0, 0};
+
+static struct event {
+ int fd;
+ void (*callback)(struct interface *);
+ struct interface *iface;
+ struct event *next;
+} *events = NULL;
+static struct event *free_events = NULL;
+
+static struct timeout {
+ struct timeval when;
+ void (*callback)(struct interface *);
+ struct interface *iface;
+ struct timeout *next;
+} *timeouts = NULL;
+static struct timeout *free_timeouts = NULL;
+
+static struct pollfd *fds = NULL;
+static size_t fds_len = 0;
+
+#ifdef DEBUG_MEMORY
+/* Define this to free all malloced memory.
+ * Normally we don't do this as the OS will do it for us at exit,
+ * but it's handy for debugging other leaks in valgrind. */
+static void
+cleanup(void)
+{
+ struct event *e;
+ struct timeout *t;
+
+ while (events) {
+ e = events->next;
+ free(events);
+ events = e;
+ }
+ while (free_events) {
+ e = free_events->next;
+ free(free_events);
+ free_events = e;
+ }
+ while (timeouts) {
+ t = timeouts->next;
+ free(timeouts);
+ timeouts = t;
+ }
+ while (free_timeouts) {
+ t = free_timeouts->next;
+ free(free_timeouts);
+ free_timeouts = t;
+ }
+ free(fds);
+}
+#endif
+
+void
+add_event(int fd, void (*callback)(struct interface *), struct interface *iface)
+{
+ struct event *e, *last = NULL;
+
+ /* We should only have one callback monitoring the fd */
+ for (e = events; e; e = e->next) {
+ if (e->fd == fd) {
+ e->callback = callback;
+ e->iface = iface;
+ return;
+ }
+ last = e;
+ }
+
+ /* Allocate a new event if no free ones already allocated */
+ if (free_events) {
+ e = free_events;
+ free_events = e->next;
+ } else
+ e = xmalloc(sizeof(*e));
+ e->fd = fd;
+ e->callback = callback;
+ e->iface = iface;
+ e->next = NULL;
+ if (last)
+ last->next = e;
+ else
+ events = e;
+}
+
+void
+delete_event(int fd)
+{
+ struct event *e, *last = NULL;
+
+ for (e = events; e; e = e->next) {
+ if (e->fd == fd) {
+ if (last)
+ last->next = e->next;
+ else
+ events = e->next;
+ e->next = free_events;
+ free_events = e;
+ break;
+ }
+ last = e;
+ }
+}
+
+void
+add_timeout_tv(const struct timeval *when,
+ void (*callback)(struct interface *), struct interface *iface)
+{
+ struct timeval w;
+ struct timeout *t, *tt = NULL;
+
+ get_monotonic(&now);
+ timeradd(&now, when, &w);
+
+ /* Remove existing timeout if present */
+ for (t = timeouts; t; t = t->next) {
+ if (t->callback == callback && t->iface == iface) {
+ if (tt)
+ tt->next = t->next;
+ else
+ timeouts = t->next;
+ break;
+ }
+ tt = t;
+ }
+
+ if (!t) {
+ /* No existing, so allocate or grab one from the free pool */
+ if (free_timeouts) {
+ t = free_timeouts;
+ free_timeouts = t->next;
+ } else
+ t = xmalloc(sizeof(*t));
+ }
+
+ t->when.tv_sec = w.tv_sec;
+ t->when.tv_usec = w.tv_usec;
+ t->callback = callback;
+ t->iface = iface;
+
+ /* The timeout list should be in chronological order,
+ * soonest first.
+ * This is the easiest algorithm - check the head, then middle
+ * and finally the end. */
+ if (!timeouts || timercmp(&t->when, &timeouts->when, <)) {
+ t->next = timeouts;
+ timeouts = t;
+ return;
+ }
+ for (tt = timeouts; tt->next; tt = tt->next)
+ if (timercmp(&t->when, &tt->next->when, <)) {
+ t->next = tt->next;
+ tt->next = t;
+ return;
+ }
+ tt->next = t;
+ t->next = NULL;
+}
+
+void
+add_timeout_sec(time_t when,
+ void (*callback)(struct interface *), struct interface *iface)
+{
+ struct timeval tv;
+
+ tv.tv_sec = when;
+ tv.tv_usec = 0;
+ add_timeout_tv(&tv, callback, iface);
+}
+
+/* This deletes all timeouts for the interface EXCEPT for ones with the
+ * callbacks given. Handy for deleting everything apart from the expire
+ * timeout. */
+void
+delete_timeouts(struct interface *iface,
+ void (*callback)(struct interface *), ...)
+{
+ struct timeout *t, *tt, *last = NULL;
+ va_list va;
+ void (*f)(struct interface *);
+
+ for (t = timeouts; t && (tt = t->next, 1); t = tt) {
+ if (t->iface == iface && t->callback != callback) {
+ va_start(va, callback);
+ while ((f = va_arg(va, void (*)(struct interface *))))
+ if (f == t->callback)
+ break;
+ va_end(va);
+ if (!f) {
+ if (last)
+ last->next = t->next;
+ else
+ timeouts = t->next;
+ t->next = free_timeouts;
+ free_timeouts = t;
+ continue;
+ }
+ }
+ }
+}
+
+void
+delete_timeout(void (*callback)(struct interface *), struct interface *iface)
+{
+ struct timeout *t, *tt, *last = NULL;
+
+ for (t = timeouts; t && (tt = t->next, 1); t = tt) {
+ if (t->iface == iface &&
+ (!callback || t->callback == callback))
+ {
+ if (last)
+ last->next = t->next;
+ else
+ timeouts = t->next;
+ t->next = free_timeouts;
+ free_timeouts = t;
+ continue;
+ }
+ last = t;
+ }
+}
+
+void
+start_eloop(void)
+{
+ int msecs, n;
+ nfds_t nfds, i;
+ struct event *e;
+ struct timeout *t;
+ struct timeval tv;
+
+#ifdef DEBUG_MEMORY
+ atexit(cleanup);
+#endif
+
+ for (;;) {
+ /* Run all timeouts first.
+ * When we have one that has not yet occured,
+ * calculate milliseconds until it does for use in poll. */
+ if (timeouts) {
+ if (timercmp(&now, &timeouts->when, >)) {
+ t = timeouts;
+ timeouts = timeouts->next;
+ t->callback(t->iface);
+ t->next = free_timeouts;
+ free_timeouts = t;
+ continue;
+ }
+ timersub(&timeouts->when, &now, &tv);
+ if (tv.tv_sec > INT_MAX / 1000 ||
+ (tv.tv_sec == INT_MAX / 1000 &&
+ (tv.tv_usec + 999) / 1000 > INT_MAX % 1000))
+ msecs = INT_MAX;
+ else
+ msecs = tv.tv_sec * 1000 +
+ (tv.tv_usec + 999) / 1000;
+ } else
+ /* No timeouts, so wait forever. */
+ msecs = -1;
+
+ /* Allocate memory for our pollfds as and when needed.
+ * We don't bother shrinking it. */
+ nfds = 0;
+ for (e = events; e; e = e->next)
+ nfds++;
+ if (msecs == -1 && nfds == 0) {
+ logger(LOG_ERR, "nothing to do");
+ exit(EXIT_FAILURE);
+ }
+ if (nfds > fds_len) {
+ free(fds);
+ fds = xmalloc(sizeof(*fds) * nfds);
+ fds_len = nfds;
+ }
+ nfds = 0;
+ for (e = events; e; e = e->next) {
+ fds[nfds].fd = e->fd;
+ fds[nfds].events = POLLIN;
+ fds[nfds].revents = 0;
+ nfds++;
+ }
+ n = poll(fds, nfds, msecs);
+ if (n == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ get_monotonic(&now);
+ continue;
+ }
+ logger(LOG_ERR, "poll: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Get the now time and process any triggered events. */
+ get_monotonic(&now);
+ if (n == 0)
+ continue;
+ for (i = 0; i < nfds; i++) {
+ if (!(fds[i].revents & (POLLIN | POLLHUP)))
+ continue;
+ for (e = events; e; e = e->next) {
+ if (e->fd == fds[i].fd) {
+ e->callback(e->iface);
+ break;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef ELOOP_H
+#define ELOOP_H
+
+#include <time.h>
+
+#include "dhcpcd.h"
+
+void add_event(int fd, void (*)(struct interface *), struct interface *);
+void delete_event(int fd);
+void add_timeout_sec(time_t, void (*)(struct interface *), struct interface *);
+void add_timeout_tv(const struct timeval *,
+ void (*)(struct interface *), struct interface *);
+void delete_timeout(void (*)(struct interface *), struct interface *);
+void delete_timeouts(struct interface *, void (*)(struct interface *), ...);
+void start_eloop(void);
+
+#endif
#endif
int
-if_address(const char *ifname, const struct in_addr *address,
+if_address(const struct interface *iface, const struct in_addr *address,
const struct in_addr *netmask, const struct in_addr *broadcast,
int action)
{
return -1;
memset(&ifa, 0, sizeof(ifa));
- strlcpy(ifa.ifra_name, ifname, sizeof(ifa.ifra_name));
+ strlcpy(ifa.ifra_name, iface->name, sizeof(ifa.ifra_name));
#define ADDADDR(_var, _addr) \
_s.sa = &_var; \
}
int
-if_route(const char *ifname, const struct in_addr *destination,
+if_route(const struct interface *iface, const struct in_addr *destination,
const struct in_addr *netmask, const struct in_addr *gateway,
_unused int metric, int action)
{
} rtm;
char *bp = rtm.buffer;
size_t l;
- unsigned char *hwaddr;
- size_t hwlen = 0;
int retval = 0;
if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) == -1)
if (gateway->s_addr == INADDR_ANY) {
/* Make us a link layer socket */
- hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN);
- do_interface(ifname, hwaddr, &hwlen, NULL, 0, 0);
memset(&su, 0, sizeof(su));
su.sdl.sdl_len = sizeof(su.sdl);
su.sdl.sdl_family = AF_LINK;
- su.sdl.sdl_nlen = strlen(ifname);
- memcpy(&su.sdl.sdl_data, ifname, (size_t)su.sdl.sdl_nlen);
- su.sdl.sdl_alen = hwlen;
+ su.sdl.sdl_nlen = strlen(iface->name);
+ memcpy(&su.sdl.sdl_data, iface->name, (size_t)su.sdl.sdl_nlen);
+ su.sdl.sdl_alen = iface->hwlen;
memcpy(((unsigned char *)&su.sdl.sdl_data) + su.sdl.sdl_nlen,
- hwaddr, (size_t)su.sdl.sdl_alen);
+ iface->hwaddr, (size_t)su.sdl.sdl_alen);
l = SA_SIZE(&(su.sa));
memcpy(bp, &su, l);
bp += l;
- free(hwaddr);
} else {
rtm.hdr.rtm_flags |= RTF_GATEWAY;
ADDADDR(gateway);
}
int
-open_link_socket(struct interface *iface)
+open_link_socket(void)
{
int fd;
fd = socket(PF_ROUTE, SOCK_RAW, 0);
- if (fd == -1)
- return -1;
- set_cloexec(fd);
- if (iface->link_fd != -1)
- close(iface->link_fd);
- iface->link_fd = fd;
- return 0;
+ if (fd != -1)
+ set_cloexec(fd);
+ return fd;
}
#define BUFFER_LEN 2048
int
-link_changed(struct interface *iface)
+link_changed(int fd, const struct interface *ifaces)
{
char buffer[2048], *p;
ssize_t bytes;
struct rt_msghdr *rtm;
struct if_msghdr *ifm;
- int i;
+ const struct interface *iface;
- if ((i = if_nametoindex(iface->name)) == -1)
- return -1;
for (;;) {
- bytes = recv(iface->link_fd, buffer, BUFFER_LEN, MSG_DONTWAIT);
+ bytes = recv(fd, buffer, BUFFER_LEN, MSG_DONTWAIT);
if (bytes == -1) {
if (errno == EAGAIN)
return 0;
if (rtm->rtm_type != RTM_IFINFO)
continue;
ifm = (struct if_msghdr *)p;
- if (ifm->ifm_index != i)
- continue;
-
- /* Link changed */
- return 1;
+ for (iface = ifaces; iface; iface = iface->next)
+ if (ifm->ifm_index == if_nametoindex(iface->name))
+ return 1;
}
}
return 0;
}
+
+struct interface *
+discover_interfaces(int argc, char * const *argv)
+{
+ struct interface *ifaces = NULL;
+
+ do_interface(NULL, &ifaces, argc, argv, NULL, NULL, 2);
+ return ifaces;
+}
#include <netpacket/packet.h>
#include <errno.h>
+#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFERLEN 256
int
-open_link_socket(struct interface *iface)
+open_link_socket(void)
{
int fd;
struct sockaddr_nl nl;
if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1)
return -1;
set_cloexec(fd);
- if (iface->link_fd != -1)
- close(iface->link_fd);
- iface->link_fd = fd;
- return 0;
+ return fd;
}
static int
get_netlink(int fd, int flags,
- int (*callback)(struct nlmsghdr *, const char *),
- const char *ifname)
+ int (*callback)(struct nlmsghdr *, const struct interface *),
+ const struct interface *iface)
{
char *buffer = NULL;
ssize_t bytes;
NLMSG_OK(nlm, (size_t)bytes);
nlm = NLMSG_NEXT(nlm, bytes))
{
- r = callback(nlm, ifname);
+ r = callback(nlm, iface);
if (r != 0)
goto eexit;
}
}
static int
-err_netlink(struct nlmsghdr *nlm, _unused const char *ifname)
+err_netlink(struct nlmsghdr *nlm, _unused const struct interface *iface)
{
struct nlmsgerr *err;
int l;
}
static int
-link_netlink(struct nlmsghdr *nlm, const char *ifname)
+link_netlink(struct nlmsghdr *nlm, const struct interface *ifaces)
{
int len;
struct rtattr *rta;
struct ifinfomsg *ifi;
char ifn[IF_NAMESIZE + 1];
+ const struct interface *iface;
if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK)
return 0;
rta = RTA_NEXT(rta, len);
}
- if (strncmp(ifname, ifn, sizeof(ifn)) == 0)
- return 1;
+ for (iface = ifaces; iface; iface = iface->next)
+ if (strcmp(iface->name, ifn) == 0)
+ return 1;
return 0;
}
int
-link_changed(struct interface *iface)
+link_changed(int fd, const struct interface *iface)
{
- return get_netlink(iface->link_fd, MSG_DONTWAIT,
- &link_netlink, iface->name);
+ return get_netlink(fd, MSG_DONTWAIT, &link_netlink, iface);
}
static int
};
int
-if_address(const char *ifname,
+if_address(const struct interface *iface,
const struct in_addr *address, const struct in_addr *netmask,
const struct in_addr *broadcast, int action)
{
nlm->hdr.nlmsg_type = RTM_NEWADDR;
} else
nlm->hdr.nlmsg_type = RTM_DELADDR;
- if (!(nlm->ifa.ifa_index = if_nametoindex(ifname))) {
+ if (!(nlm->ifa.ifa_index = if_nametoindex(iface->name))) {
free(nlm);
errno = ENODEV;
return -1;
nlm->ifa.ifa_prefixlen = inet_ntocidr(*netmask);
/* This creates the aliased interface */
add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LABEL,
- ifname, strlen(ifname) + 1);
+ iface->name, strlen(iface->name) + 1);
add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL,
&address->s_addr, sizeof(address->s_addr));
if (action >= 0)
}
int
-if_route(const char *ifname,
+if_route(const struct interface *iface,
const struct in_addr *destination, const struct in_addr *netmask,
const struct in_addr *gateway, int metric, int action)
{
int retval = 0;
- if (!(ifindex = if_nametoindex(ifname))) {
+ if (!(ifindex = if_nametoindex(iface->name))) {
errno = ENODEV;
return -1;
}
free(nlm);
return retval;
}
+
+struct interface *
+discover_interfaces(int argc, char * const *argv)
+{
+ FILE *f;
+ char *buffer = NULL, *p;
+ size_t len = 0, ln = 0, n;
+ int i, s;
+ struct interface *ifaces = NULL, *ifp, *ifl;
+
+ if ((f = fopen("/proc/net/dev", "r"))) {
+ while (get_line(&buffer, &len, f)) {
+ if (++ln < 2)
+ continue;
+ p = buffer;
+ while (isspace((unsigned int)*p))
+ p++;
+ n = strcspn(p, ": \t");
+ p[n]= '\0';
+ ifl = NULL;
+ for (ifp = ifaces; ifp; ifp = ifp->next) {
+ if (strcmp(ifp->name, p) == 0)
+ break;
+ ifl = ifp;
+ }
+ if (ifp)
+ continue;
+ if (argc > 0) {
+ for (i = 0; i < argc; i++)
+ if (strcmp(argv[i], p) == 0)
+ break;
+ if (i == argc)
+ continue;
+ }
+ if (ifp = init_interface(p)) {
+ if (ifl)
+ ifl->next =ifp;
+ else
+ ifaces = ifp;
+ }
+ }
+ fclose(f);
+ free(buffer);
+ }
+ return ifaces;
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "common.h"
+#include "config.h"
+#include "dhcpf.h"
+#include "if-options.h"
+#include "logger.h"
+#include "net.h"
+
+/* Don't set any optional arguments here so we retain POSIX
+ * compatibility with getopt */
+#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:"
+
+const struct option cf_options[] = {
+ {"background", no_argument, NULL, 'b'},
+ {"script", required_argument, NULL, 'c'},
+ {"debug", no_argument, NULL, 'd'},
+ {"config", required_argument, NULL, 'f'},
+ {"hostname", optional_argument, NULL, 'h'},
+ {"vendorclassid", optional_argument, NULL, 'i'},
+ {"release", no_argument, NULL, 'k'},
+ {"leasetime", required_argument, NULL, 'l'},
+ {"metric", required_argument, NULL, 'm'},
+ {"rebind", no_argument, NULL, 'n'},
+ {"option", required_argument, NULL, 'o'},
+ {"persistent", no_argument, NULL, 'p'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"request", optional_argument, NULL, 'r'},
+ {"inform", optional_argument, NULL, 's'},
+ {"timeout", required_argument, NULL, 't'},
+ {"userclass", required_argument, NULL, 'u'},
+ {"vendor", required_argument, NULL, 'v'},
+ {"exit", no_argument, NULL, 'x'},
+ {"noarp", no_argument, NULL, 'A'},
+ {"nobackground", no_argument, NULL, 'B'},
+ {"nohook", required_argument, NULL, 'C'},
+ {"duid", no_argument, NULL, 'D'},
+ {"lastlease", no_argument, NULL, 'E'},
+ {"fqdn", optional_argument, NULL, 'F'},
+ {"nogateway", no_argument, NULL, 'G'},
+ {"clientid", optional_argument, NULL, 'I'},
+ {"nolink", no_argument, NULL, 'K'},
+ {"noipv4ll", no_argument, NULL, 'L'},
+ {"nooption", optional_argument, NULL, 'O'},
+ {"require", required_argument, NULL, 'Q'},
+ {"test", no_argument, NULL, 'T'},
+ {"variables", no_argument, NULL, 'V'},
+ {"blacklist", required_argument, NULL, 'X'},
+ {NULL, 0, NULL, '\0'}
+};
+
+static int
+atoint(const char *s)
+{
+ char *t;
+ long n;
+
+ errno = 0;
+ n = strtol(s, &t, 0);
+ if ((errno != 0 && n == 0) || s == t ||
+ (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)))
+ {
+ logger(LOG_ERR, "`%s' out of range", s);
+ return -1;
+ }
+
+ return (int)n;
+}
+
+static char *
+add_environ(struct if_options *ifo, const char *value, int uniq)
+{
+ char **newlist;
+ char **lst = ifo->environ;
+ size_t i = 0, l, lv;
+ char *match = NULL, *p;
+
+ match = xstrdup(value);
+ p = strchr(match, '=');
+ if (p)
+ *p++ = '\0';
+ l = strlen(match);
+
+ while (lst && lst[i]) {
+ if (match && strncmp(lst[i], match, l) == 0) {
+ if (uniq) {
+ free(lst[i]);
+ lst[i] = xstrdup(value);
+ } else {
+ /* Append a space and the value to it */
+ l = strlen(lst[i]);
+ lv = strlen(p);
+ lst[i] = xrealloc(lst[i], l + lv + 2);
+ lst[i][l] = ' ';
+ memcpy(lst[i] + l + 1, p, lv);
+ lst[i][l + lv + 1] = '\0';
+ }
+ free(match);
+ return lst[i];
+ }
+ i++;
+ }
+
+ newlist = xrealloc(lst, sizeof(char *) * (i + 2));
+ newlist[i] = xstrdup(value);
+ newlist[i + 1] = NULL;
+ ifo->environ = newlist;
+ free(match);
+ return newlist[i];
+}
+
+#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0)
+static ssize_t
+parse_string_hwaddr(char *sbuf, ssize_t slen, const char *str, int clid)
+{
+ ssize_t l;
+ const char *p;
+ int i, punt_last = 0;
+ char c[4];
+
+ /* If surrounded by quotes then it's a string */
+ if (*str == '"') {
+ str++;
+ l = strlen(str);
+ p = str + l - 1;
+ if (*p == '"')
+ punt_last = 1;
+ } else {
+ l = hwaddr_aton(NULL, str);
+ if (l > 1) {
+ if (l > slen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ hwaddr_aton((uint8_t *)sbuf, str);
+ return l;
+ }
+ }
+
+ /* Process escapes */
+ l = 0;
+ /* If processing a string on the clientid, first byte should be
+ * 0 to indicate a non hardware type */
+ if (clid) {
+ *sbuf++ = 0;
+ l++;
+ }
+ c[3] = '\0';
+ while (*str) {
+ if (++l > slen) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ if (*str == '\\') {
+ str++;
+ switch(*str++) {
+ case '\0':
+ break;
+ case 'b':
+ *sbuf++ = '\b';
+ break;
+ case 'n':
+ *sbuf++ = '\n';
+ break;
+ case 'r':
+ *sbuf++ = '\r';
+ break;
+ case 't':
+ *sbuf++ = '\t';
+ break;
+ case 'x':
+ /* Grab a hex code */
+ c[1] = '\0';
+ for (i = 0; i < 2; i++) {
+ if (isxdigit((unsigned char)*str) == 0)
+ break;
+ c[i] = *str++;
+ }
+ if (c[1] != '\0') {
+ c[2] = '\0';
+ *sbuf++ = strtol(c, NULL, 16);
+ } else
+ l--;
+ break;
+ case '0':
+ /* Grab an octal code */
+ c[2] = '\0';
+ for (i = 0; i < 3; i++) {
+ if (*str < '0' || *str > '7')
+ break;
+ c[i] = *str++;
+ }
+ if (c[2] != '\0') {
+ i = strtol(c, NULL, 8);
+ if (i > 255)
+ i = 255;
+ *sbuf ++= i;
+ } else
+ l--;
+ break;
+ default:
+ *sbuf++ = *str++;
+ }
+ } else
+ *sbuf++ = *str++;
+ }
+ if (punt_last)
+ *--sbuf = '\0';
+ return l;
+}
+
+static int
+parse_option(struct if_options *ifo, int opt, const char *arg)
+{
+ int i;
+ char *p;
+ ssize_t s;
+ struct in_addr addr;
+
+ switch(opt) {
+ case 'b':
+ ifo->options |= DHCPCD_BACKGROUND;
+ break;
+ case 'c':
+ strlcpy(ifo->script, arg, sizeof(ifo->script));
+ break;
+ case 'd':
+ break;
+ case 'h':
+ if (arg)
+ s = parse_string(ifo->hostname + 1,
+ HOSTNAME_MAX_LEN, arg);
+ else
+ s = 0;
+ if (s == -1) {
+ logger(LOG_ERR, "hostname: %s", strerror(errno));
+ return -1;
+ }
+ if (s != 0 && ifo->hostname[1] == '.') {
+ logger(LOG_ERR, "hostname cannot begin with a .");
+ return -1;
+ }
+ ifo->hostname[0] = (uint8_t)s;
+ break;
+ case 'i':
+ if (arg)
+ s = parse_string((char *)ifo->vendorclassid + 1,
+ VENDORCLASSID_MAX_LEN, arg);
+ else
+ s = 0;
+ if (s == -1) {
+ logger(LOG_ERR, "vendorclassid: %s", strerror(errno));
+ return -1;
+ }
+ *ifo->vendorclassid = (uint8_t)s;
+ break;
+ case 'l':
+ if (*arg == '-') {
+ logger(LOG_ERR,
+ "leasetime must be a positive value");
+ return -1;
+ }
+ errno = 0;
+ ifo->leasetime = (uint32_t)strtol(arg, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE) {
+ logger(LOG_ERR, "`%s' out of range", arg);
+ return -1;
+ }
+ break;
+ case 'm':
+ ifo->metric = atoint(arg);
+ if (ifo->metric < 0) {
+ logger(LOG_ERR, "metric must be a positive value");
+ return -1;
+ }
+ break;
+ case 'o':
+ if (make_option_mask(ifo->requestmask, arg, 1) != 0) {
+ logger(LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case 'p':
+ ifo->options |= DHCPCD_PERSISTENT;
+ break;
+ case 'q':
+ setloglevel(LOG_WARNING);
+ break;
+ case 's':
+ ifo->options |= DHCPCD_INFORM;
+ ifo->options |= DHCPCD_PERSISTENT;
+ ifo->options &= ~DHCPCD_ARP;
+ if (!arg || *arg == '\0') {
+ ifo->request_address.s_addr = 0;
+ break;
+ } else {
+ if ((p = strchr(arg, '/'))) {
+ /* nullify the slash, so the -r option
+ * can read the address */
+ *p++ = '\0';
+ if (sscanf(p, "%d", &i) != 1 ||
+ inet_cidrtoaddr(i, &ifo->request_netmask) != 0)
+ {
+ logger(LOG_ERR,
+ "`%s' is not a valid CIDR",
+ p);
+ return -1;
+ }
+ }
+ }
+ /* FALLTHROUGH */
+ case 'r':
+ if (!(ifo->options & DHCPCD_INFORM))
+ ifo->options |= DHCPCD_REQUEST;
+ if (arg && !inet_aton(arg, &ifo->request_address)) {
+ logger(LOG_ERR, "`%s' is not a valid IP address",
+ arg);
+ return -1;
+ }
+ break;
+ case 't':
+ ifo->timeout = atoint(arg);
+ if (ifo->timeout < 0) {
+ logger (LOG_ERR, "timeout must be a positive value");
+ return -1;
+ }
+ break;
+ case 'u':
+ s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1;
+ s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2,
+ s, arg);
+ if (s == -1) {
+ logger(LOG_ERR, "userclass: %s", strerror(errno));
+ return -1;
+ }
+ if (s != 0) {
+ ifo->userclass[ifo->userclass[0] + 1] = s;
+ ifo->userclass[0] += s + 1;
+ }
+ break;
+ case 'v':
+ p = strchr(arg, ',');
+ if (!p || !p[1]) {
+ logger(LOG_ERR, "invalid vendor format");
+ return -1;
+ }
+ *p = '\0';
+ i = atoint(arg);
+ arg = p + 1;
+ if (i < 1 || i > 254) {
+ logger(LOG_ERR, "vendor option should be between"
+ " 1 and 254 inclusive");
+ return -1;
+ }
+ s = VENDOR_MAX_LEN - ifo->vendor[0] - 2;
+ if (inet_aton(arg, &addr) == 1) {
+ if (s < 6) {
+ s = -1;
+ errno = ENOBUFS;
+ } else
+ memcpy(ifo->vendor + ifo->vendor[0] + 3,
+ &addr.s_addr, sizeof(addr.s_addr));
+ } else {
+ s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3,
+ s, arg);
+ }
+ if (s == -1) {
+ logger(LOG_ERR, "vendor: %s", strerror(errno));
+ return -1;
+ }
+ if (s != 0) {
+ ifo->vendor[ifo->vendor[0] + 1] = i;
+ ifo->vendor[ifo->vendor[0] + 2] = s;
+ ifo->vendor[0] += s + 2;
+ }
+ break;
+ case 'x':
+ break;
+ case 'A':
+ ifo->options &= ~DHCPCD_ARP;
+ /* IPv4LL requires ARP */
+ ifo->options &= ~DHCPCD_IPV4LL;
+ break;
+ case 'B':
+ ifo->options &= ~DHCPCD_DAEMONISE;
+ break;
+ case 'C':
+ /* Commas to spaces for shell */
+ while ((p = strchr(arg, ',')))
+ *p = ' ';
+ s = strlen("skip_hooks=") + strlen(arg) + 1;
+ p = xmalloc(sizeof(char) * s);
+ snprintf(p, s, "skip_hooks=%s", arg);
+ add_environ(ifo, p, 0);
+ free(p);
+ break;
+ case 'D':
+ ifo->options |= DHCPCD_DUID;
+ break;
+ case 'E':
+ ifo->options |= DHCPCD_LASTLEASE;
+ break;
+ case 'F':
+ if (!arg) {
+ ifo->fqdn = FQDN_BOTH;
+ break;
+ }
+ if (strcmp(arg, "none") == 0)
+ ifo->fqdn = FQDN_NONE;
+ else if (strcmp(arg, "ptr") == 0)
+ ifo->fqdn = FQDN_PTR;
+ else if (strcmp(arg, "both") == 0)
+ ifo->fqdn = FQDN_BOTH;
+ else if (strcmp(arg, "disable") == 0)
+ ifo->fqdn = FQDN_DISABLE;
+ else {
+ logger(LOG_ERR, "invalid value `%s' for FQDN", arg);
+ return -1;
+ }
+ break;
+ case 'G':
+ ifo->options &= ~DHCPCD_GATEWAY;
+ break;
+ case 'I':
+ /* Strings have a type of 0 */;
+ ifo->clientid[1] = 0;
+ if (arg)
+ s = parse_string_hwaddr((char *)ifo->clientid + 1,
+ CLIENTID_MAX_LEN, arg, 1);
+ else
+ s = 0;
+ if (s == -1) {
+ logger(LOG_ERR, "clientid: %s", strerror(errno));
+ return -1;
+ }
+ ifo->clientid[0] = (uint8_t)s;
+ if (s == 0) {
+ ifo->options &= ~DHCPCD_DUID;
+ ifo->options &= ~DHCPCD_CLIENTID;
+ }
+ break;
+ case 'K':
+ ifo->options &= ~DHCPCD_LINK;
+ break;
+ case 'L':
+ ifo->options &= ~DHCPCD_IPV4LL;
+ break;
+ case 'O':
+ if (make_option_mask(ifo->requestmask, arg, -1) != 0 ||
+ make_option_mask(ifo->requiremask, arg, -1) != 0 ||
+ make_option_mask(ifo->nomask, arg, 1) != 0)
+ {
+ logger(LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case 'Q':
+ if (make_option_mask(ifo->requiremask, arg, 1) != 0 ||
+ make_option_mask(ifo->requestmask, arg, 1) != 0)
+ {
+ logger(LOG_ERR, "unknown option `%s'", arg);
+ return -1;
+ }
+ break;
+ case 'X':
+ if (!inet_aton(arg, &addr)) {
+ logger(LOG_ERR, "`%s' is not a valid IP address",
+ arg);
+ return -1;
+ }
+ ifo->blacklist = xrealloc(ifo->blacklist,
+ sizeof(in_addr_t) * (ifo->blacklist_len + 1));
+ ifo->blacklist[ifo->blacklist_len] = addr.s_addr;
+ ifo->blacklist_len++;
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+parse_config_line(struct if_options *ifo, const char *opt, char *line)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(cf_options) / sizeof(cf_options[0]); i++) {
+ if (!cf_options[i].name ||
+ strcmp(cf_options[i].name, opt) != 0)
+ continue;
+
+ if (cf_options[i].has_arg == required_argument && !line) {
+ fprintf(stderr,
+ PACKAGE ": option requires an argument -- %s\n",
+ opt);
+ return -1;
+ }
+
+ return parse_option(ifo, cf_options[i].val, line);
+ }
+
+ fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt);
+ return -1;
+}
+
+struct if_options *
+read_config(const char *file, const char *ifname)
+{
+ struct if_options *ifo;
+ FILE *f;
+ size_t len = 0;
+ char *line, *option, *p, *buffer = NULL;
+ int skip = 0;
+
+ /* Seed our default options */
+ ifo = xzalloc(sizeof(*ifo));
+ ifo->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE;
+ ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK;
+ ifo->timeout = DEFAULT_TIMEOUT;
+ gethostname(ifo->hostname + 1, sizeof(ifo->hostname));
+ if (strcmp(ifo->hostname + 1, "(none)") == 0 ||
+ strcmp(ifo->hostname + 1, "localhost") == 0)
+ ifo->hostname[1] = '\0';
+ *ifo->hostname = strlen(ifo->hostname + 1);
+ strlcpy(ifo->script, SCRIPT, sizeof(ifo->script));
+ ifo->vendorclassid[0] = snprintf((char *)ifo->vendorclassid + 1,
+ VENDORCLASSID_MAX_LEN,
+ "%s %s", PACKAGE, VERSION);
+
+ /* Parse our options file */
+ f = fopen(file, "r");
+ if (!f)
+ return ifo;
+
+ while ((get_line(&buffer, &len, f))) {
+ line = buffer;
+ while ((option = strsep(&line, " \t")))
+ if (*option != '\0')
+ break;
+ if (!option || *option == '\0' || *option == '#')
+ continue;
+ /* Trim leading whitespace */
+ if (line) {
+ while (*line != '\0' && (*line == ' ' || *line == '\t'))
+ line++;
+ }
+ /* Trim trailing whitespace */
+ if (line && *line) {
+ p = line + strlen(line) - 1;
+ while (p != line &&
+ (*p == ' ' || *p == '\t') &&
+ *(p - 1) != '\\')
+ *p-- = '\0';
+ }
+ /* Start of an interface block, skip if not ours */
+ if (strcmp(option, "interface") == 0) {
+ if (ifname && line && strcmp(line, ifname) == 0)
+ skip = 0;
+ else
+ skip = 1;
+ continue;
+ }
+ if (skip)
+ continue;
+ if (parse_config_line(ifo, option, line) != 1) {
+ break;
+ }
+ }
+ free(buffer);
+ fclose(f);
+
+ /* Terminate the encapsulated options */
+ if (ifo->vendor[0]) {
+ ifo->vendor[0]++;
+ ifo->vendor[ifo->vendor[0]] = DHO_END;
+ }
+ return ifo;
+}
+
+int
+add_options(struct if_options *ifo, int argc, char **argv)
+{
+ int oi, opt, r = 1;
+
+ optind = 0;
+ while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
+ {
+ r = parse_option(ifo, opt, optarg);
+ if (r != 1)
+ break;
+ }
+ /* Terminate the encapsulated options */
+ if (r == 1 && ifo->vendor[0]) {
+ ifo->vendor[0]++;
+ ifo->vendor[ifo->vendor[0]] = DHO_END;
+ }
+ return r;
+}
+
+void
+free_options(struct if_options *ifo)
+{
+ size_t i;
+
+ if (ifo->environ) {
+ i = 0;
+ while (ifo->environ[i])
+ free(ifo->environ[i++]);
+ free(ifo->environ);
+ }
+ free(ifo->blacklist);
+ free(ifo);
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef IF_OPTIONS_H
+#define IF_OPTIONS_H
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <getopt.h>
+#include <limits.h>
+
+/* Don't set any optional arguments here so we retain POSIX
+ * * compatibility with getopt */
+#define IF_OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:"
+
+#define DEFAULT_TIMEOUT 30
+#define DEFAULT_LEASETIME 3600 /* 1 hour */
+
+#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */
+#define VENDORCLASSID_MAX_LEN 48
+#define CLIENTID_MAX_LEN 48
+#define USERCLASS_MAX_LEN 255
+#define VENDOR_MAX_LEN 255
+
+#define DHCPCD_ARP (1 << 0)
+#define DHCPCD_DOMAIN (1 << 2)
+#define DHCPCD_GATEWAY (1 << 3)
+#define DHCPCD_LASTLEASE (1 << 7)
+#define DHCPCD_INFORM (1 << 8)
+#define DHCPCD_REQUEST (1 << 9)
+#define DHCPCD_IPV4LL (1 << 10)
+#define DHCPCD_DUID (1 << 11)
+#define DHCPCD_PERSISTENT (1 << 12)
+#define DHCPCD_DAEMONISE (1 << 14)
+#define DHCPCD_DAEMONISED (1 << 15)
+#define DHCPCD_TEST (1 << 16)
+#define DHCPCD_FORKED (1 << 17)
+#define DHCPCD_HOSTNAME (1 << 18)
+#define DHCPCD_CLIENTID (1 << 19)
+#define DHCPCD_LINK (1 << 20)
+#define DHCPCD_BACKGROUND (1 << 21)
+
+extern const struct option cf_options[];
+
+struct if_options {
+ int metric;
+ uint8_t requestmask[256 / 8];
+ uint8_t requiremask[256 / 8];
+ uint8_t nomask[256 / 8];
+ uint32_t leasetime;
+ time_t timeout;
+ int options;
+
+ struct in_addr request_address;
+ struct in_addr request_netmask;
+
+ char **environ;
+ char script[PATH_MAX];
+
+ char hostname[HOSTNAME_MAX_LEN + 1];
+ int fqdn;
+ uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 1];
+ char clientid[CLIENTID_MAX_LEN + 1];
+ uint8_t userclass[USERCLASS_MAX_LEN + 1];
+ uint8_t vendor[VENDOR_MAX_LEN + 1];
+
+ size_t blacklist_len;
+ in_addr_t *blacklist;
+};
+
+struct if_options *read_config(const char *, const char *);
+int add_options(struct if_options *, int, char **);
+void free_options(struct if_options *);
+
+#endif
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "common.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+#include "logger.h"
+#include "net.h"
+
+static struct dhcp_message*
+make_ipv4ll_lease(uint32_t old_addr)
+{
+ uint32_t u32;
+ struct dhcp_message *dhcp;
+ uint8_t *p;
+
+ dhcp = xzalloc(sizeof(*dhcp));
+ /* Put some LL options in */
+ p = dhcp->options;
+ *p++ = DHO_SUBNETMASK;
+ *p++ = sizeof(u32);
+ u32 = htonl(LINKLOCAL_MASK);
+ memcpy(p, &u32, sizeof(u32));
+ p += sizeof(u32);
+ *p++ = DHO_BROADCAST;
+ *p++ = sizeof(u32);
+ u32 = htonl(LINKLOCAL_BRDC);
+ memcpy(p, &u32, sizeof(u32));
+ p += sizeof(u32);
+ *p++ = DHO_END;
+
+ for (;;) {
+ dhcp->yiaddr = htonl(LINKLOCAL_ADDR |
+ (((uint32_t)abs((int)arc4random())
+ % 0xFD00) + 0x0100));
+ if (dhcp->yiaddr != old_addr &&
+ IN_LINKLOCAL(ntohl(dhcp->yiaddr)))
+ break;
+ }
+ return dhcp;
+}
+
+void
+start_ipv4ll(struct interface *iface)
+{
+ iface->state->probes = 0;
+ iface->state->claims = 0;
+ if (iface->addr.s_addr) {
+ iface->state->conflicts = 0;
+ if (IN_LINKLOCAL(htonl(iface->addr.s_addr))) {
+ send_arp_announce(iface);
+ return;
+ }
+ }
+
+ logger(LOG_INFO, "%s: probing for an IPv4LL address", iface->name);
+ delete_timeout(NULL, iface);
+ iface->state->state = DHS_PROBING;
+ free(iface->state->offer);
+ iface->state->offer = make_ipv4ll_lease(0);
+ send_arp_probe(iface);
+}
+
+void
+handle_ipv4ll_failure(struct interface *iface)
+{
+ time_t up;
+
+ if (iface->state->fail.s_addr == iface->state->lease.addr.s_addr) {
+ if (iface->state->state == DHS_PROBING)
+ drop_config(iface, "EXPIRE");
+ else {
+ up = uptime();
+ if (iface->state->defend + DEFEND_INTERVAL > up) {
+ drop_config(iface, "EXPIRE");
+ iface->state->conflicts = -1;
+ } else {
+ iface->state->defend = up;
+ return;
+ }
+ }
+ }
+
+ close_sockets(iface);
+ if (++iface->state->conflicts > MAX_CONFLICTS) {
+ logger(LOG_ERR, "%s: failed to acquire an IPv4LL address",
+ iface->name);
+ iface->state->interval = RATE_LIMIT_INTERVAL / 2;
+ start_discover(iface);
+ } else
+ add_timeout_sec(PROBE_WAIT, start_ipv4ll, iface);
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef IPV4LL_H
+#define IPV4LL_H
+
+#include "net.h"
+
+void start_ipv4ll(struct interface *);
+void handle_ipv4ll_failure(struct interface *);
+#endif
#include "logger.h"
static int loglevel = LOG_INFO;
-static char logprefix[12] = {0};
void
setloglevel(int level)
loglevel = level;
}
-void
-setlogprefix(const char *prefix)
-{
- strlcpy(logprefix, prefix, sizeof(logprefix));
-}
-
void
logger(int level, const char *fmt, ...)
{
va_list p, p2;
FILE *f = stderr;
- size_t len, fmt2len;
- char *fmt2, *pf;
va_start(p, fmt);
va_copy(p2, p);
-
if (level <= LOG_ERR || level <= loglevel) {
- fprintf(f, "%s", logprefix);
vfprintf(f, fmt, p);
fputc('\n', f);
}
-
- if (level < LOG_DEBUG || level <= loglevel) {
- len = strlen(logprefix);
- fmt2len = strlen(fmt) + len + 1;
- fmt2 = pf = malloc(sizeof(char) * fmt2len);
- if (fmt2) {
- strlcpy(pf, logprefix, fmt2len);
- pf += len;
- strlcpy(pf, fmt, fmt2len - len);
- vsyslog(level, fmt2, p2);
- free(fmt2);
- } else {
- vsyslog(level, fmt, p2);
- syslog(LOG_ERR, "logger: memory exhausted");
- exit(EXIT_FAILURE);
- }
- }
-
+ if (level < LOG_DEBUG || level <= loglevel)
+ vsyslog(level, fmt, p2);
va_end(p2);
va_end(p);
}
#include <syslog.h>
void setloglevel(int);
-void setlogprefix(const char *);
void logger(int, const char *, ...) _PRINTF_LIKE (2, 3);
#endif
${PROG}: ${OBJS}
${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
-# We could save about 600 bytes by building it like this
-# instead of the more traditional method above
-small: ${SRCS}
- echo "" > _${PROG}.c
- for src in ${SRCS}; do echo "#include \"$$src\"" >> _${PROG}.c; done
- ${CC} ${CFLAGS} ${CPPFLAGS} -c _${PROG}.c -o _${PROG}.o
- ${CC} ${LDFLAGS} -o ${PROG} _${PROG}.o ${LDADD}
-
_proginstall: ${PROG}
${INSTALL} -d ${DESTDIR}${BINDIR}
${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${BINDIR}
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#ifdef __linux__
+#include <linux/wireless.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#endif
#include "common.h"
#include "dhcp.h"
#include "logger.h"
+#include "if-options.h"
#include "net.h"
#include "signals.h"
return len;
}
+struct interface *
+init_interface(const char *ifname)
+{
+ int s, arpable;
+ struct ifreq ifr;
+ struct interface *iface = NULL;
+#ifdef SIOCGIWNAME
+ struct iwreq iwr;
+#endif
+
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ return NULL;
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1)
+ goto eexit;
+ if (ifr.ifr_flags & IFF_LOOPBACK || ifr.ifr_flags & IFF_POINTOPOINT)
+ goto eexit;
+ arpable = !(ifr.ifr_flags & IFF_NOARP);
+
+ iface = xzalloc(sizeof(*iface));
+ strlcpy(iface->name, ifname, sizeof(iface->name));
+ iface->metric = 100 + if_nametoindex(iface->name);
+
+#ifdef SIOCGIFHWADDR
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1)
+ goto eexit;
+
+ switch (ifr.ifr_hwaddr.sa_family) {
+ case ARPHRD_ETHER:
+ case ARPHRD_IEEE802:
+ iface->hwlen = ETHER_ADDR_LEN;
+ break;
+ case ARPHRD_IEEE1394:
+ iface->hwlen = EUI64_ADDR_LEN;
+ case ARPHRD_INFINIBAND:
+ iface->hwlen = INFINIBAND_ADDR_LEN;
+ break;
+ default:
+ logger(LOG_ERR, "%s: unsupported media family", iface->name);
+ goto eexit;
+ }
+ memcpy(iface->hwaddr, ifr.ifr_hwaddr.sa_data, iface->hwlen);
+ iface->family = ifr.ifr_hwaddr.sa_family;
+
+#else
+ iface->family = ARPHRD_ETHER;
+#endif
+
+#ifdef SIOCGIWNAME
+ memset(&iwr, 0, sizeof(iwr));
+ strlcpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name));
+ /* Check for wireless */
+ if (ioctl(s, SIOCGIWNAME, &iwr) != -1)
+ iface->metric += 100;
+#endif
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFMTU, &ifr) == -1)
+ goto eexit;
+
+ /* Ensure that the MTU is big enough for DHCP */
+ if (ifr.ifr_mtu < MTU_MIN) {
+ ifr.ifr_mtu = MTU_MIN;
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCSIFMTU, &ifr) == -1)
+ goto eexit;
+ }
+
+ if (up_interface(ifname) != 0)
+ goto eexit;
+
+ snprintf(iface->leasefile, PATH_MAX, LEASEFILE, ifname);
+ iface->arpable = arpable;
+
+ /* 0 is a valid fd, so init to -1 */
+ iface->raw_fd = -1;
+ iface->udp_fd = -1;
+ iface->arp_fd = -1;
+ close(s);
+ return iface;
+
+eexit:
+ free(iface);
+ close(s);
+ return NULL;
+}
+
+void
+free_interface(struct interface *iface)
+{
+ if (!iface)
+ return;
+ if (iface->state) {
+ free_options(iface->state->options);
+ free(iface->state->old);
+ free(iface->state->new);
+ free(iface->state->offer);
+ free(iface->state);
+ }
+ free(iface->clientid);
+ free(iface);
+}
+
int
do_interface(const char *ifname,
- _unused unsigned char *hwaddr, _unused size_t *hwlen,
- struct in_addr *addr, struct in_addr *net, int get)
+ _unused struct interface **ifaces,
+ _unused int argc, _unused char * const *argv,
+ struct in_addr *addr, struct in_addr *net, int act)
{
int s;
struct ifconf ifc;
struct ifreq *ifr;
struct sockaddr_in netmask;
#ifdef AF_LINK
+ int n;
struct sockaddr_dl *sdl;
+ struct interface *ifp, *ifl = NULL, *ifn = NULL;
#endif
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
#endif
p += sizeof(*ifr);
- if (strcmp(ifname, ifr->ifr_name) != 0)
+ if (ifname && strcmp(ifname, ifr->ifr_name) != 0)
continue;
found = 1;
#ifdef AF_LINK
- if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) {
+ /* Interface discovery for BSD's */
+ if (act == 2 && ifr->ifr_addr.sa_family == AF_LINK) {
+ for (ifp = *ifaces; ifp; ifp = ifp->next) {
+ ifl = ifp;
+ if (strcmp(ifp->name, ifr->ifr_name) == 0)
+ break;
+ }
+ if (ifp)
+ continue;
+ if (argc > 0) {
+ for (n = 0; n < argc; n++)
+ if (strcmp(ifr->ifr_name, argv[n]) == 0)
+ break;
+ if (n == argc)
+ continue;
+ }
+ ifn = init_interface(ifr->ifr_name);
+ if (!ifn)
+ continue;
+ if (ifl)
+ ifp = ifl->next = ifn;
+ else
+ ifp = *ifaces = ifn;
sdl = xmalloc(ifr->ifr_addr.sa_len);
memcpy(sdl, &ifr->ifr_addr, ifr->ifr_addr.sa_len);
- *hwlen = sdl->sdl_alen;
- memcpy(hwaddr, LLADDR(sdl), *hwlen);
+ ifp->hwlen = sdl->sdl_alen;
+ memcpy(ifp->hwaddr, LLADDR(sdl), sdl->sdl_alen);
free(sdl);
- retval = 1;
- break;
}
#endif
- if (ifr->ifr_addr.sa_family == AF_INET) {
+ if (ifr->ifr_addr.sa_family == AF_INET && addr) {
memcpy(&address, &ifr->ifr_addr, sizeof(address));
if (ioctl(s, SIOCGIFNETMASK, ifr) == -1)
continue;
memcpy(&netmask, &ifr->ifr_addr, sizeof(netmask));
- if (get) {
+ if (act == 1) {
addr->s_addr = address.sin_addr.s_addr;
net->s_addr = netmask.sin_addr.s_addr;
retval = 1;
return retval;
}
-struct interface *
-read_interface(const char *ifname, _unused int metric)
-{
- int s;
- struct ifreq ifr;
- struct interface *iface = NULL;
- unsigned char *hwaddr = NULL;
- size_t hwlen = 0;
- sa_family_t family = 0;
-
- memset(&ifr, 0, sizeof(ifr));
- strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
-
- if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
- return NULL;
-
-#ifdef __linux__
- strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
- if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1)
- goto eexit;
-
- switch (ifr.ifr_hwaddr.sa_family) {
- case ARPHRD_ETHER:
- case ARPHRD_IEEE802:
- hwlen = ETHER_ADDR_LEN;
- break;
- case ARPHRD_IEEE1394:
- hwlen = EUI64_ADDR_LEN;
- case ARPHRD_INFINIBAND:
- hwlen = INFINIBAND_ADDR_LEN;
- break;
- }
-
- hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN);
- memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, hwlen);
- family = ifr.ifr_hwaddr.sa_family;
-#else
- ifr.ifr_metric = metric;
- strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
- if (ioctl(s, SIOCSIFMETRIC, &ifr) == -1)
- goto eexit;
-
- hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN);
- if (do_interface(ifname, hwaddr, &hwlen, NULL, NULL, 0) != 1)
- goto eexit;
-
- family = ARPHRD_ETHER;
-#endif
-
- strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
- if (ioctl(s, SIOCGIFMTU, &ifr) == -1)
- goto eexit;
-
- /* Ensure that the MTU is big enough for DHCP */
- if (ifr.ifr_mtu < MTU_MIN) {
- ifr.ifr_mtu = MTU_MIN;
- strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
- if (ioctl(s, SIOCSIFMTU, &ifr) == -1)
- goto eexit;
- }
-
- if (up_interface(ifname) != 0)
- goto eexit;
-
- iface = xzalloc(sizeof(*iface));
- strlcpy(iface->name, ifname, IF_NAMESIZE);
- snprintf(iface->leasefile, PATH_MAX, LEASEFILE, ifname);
- memcpy(&iface->hwaddr, hwaddr, hwlen);
- iface->hwlen = hwlen;
-
- iface->family = family;
- iface->arpable = !(ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK));
-
- /* 0 is a valid fd, so init to -1 */
- iface->raw_fd = -1;
- iface->udp_fd = -1;
- iface->arp_fd = -1;
- iface->link_fd = -1;
-
-eexit:
- close(s);
- free(hwaddr);
- return iface;
-}
int
do_mtu(const char *ifname, short int mtu)
#include <limits.h>
#include "config.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
#ifndef DUID_LEN
# define DUID_LEN 128 + 2
# define ARPHRD_INFINIBAND 32
#endif
-#define HWADDR_LEN 20
/* Work out if we have a private address or not
* 10/8
struct rt *next;
};
-struct interface
-{
- char name[IF_NAMESIZE];
- sa_family_t family;
- unsigned char hwaddr[HWADDR_LEN];
- size_t hwlen;
- int arpable;
-
- int raw_fd;
- int udp_fd;
- int arp_fd;
- int link_fd;
- size_t buffer_size, buffer_len, buffer_pos;
- unsigned char *buffer;
-
- struct in_addr addr;
- struct in_addr net;
- struct rt *routes;
-
- char leasefile[PATH_MAX];
- time_t start_uptime;
-
- unsigned char *clientid;
-};
-
uint32_t get_netmask(uint32_t);
char *hwaddr_ntoa(const unsigned char *, size_t);
size_t hwaddr_aton(unsigned char *, const char *);
-struct interface *read_interface(const char *, int);
+struct interface *init_interface(const char *);
+struct interface *discover_interfaces(int, char * const *);
+void free_interface(struct interface *);
int do_mtu(const char *, short int);
#define get_mtu(iface) do_mtu(iface, 0)
#define set_mtu(iface, mtu) do_mtu(iface, mtu)
int inet_cidrtoaddr(int, struct in_addr *);
int up_interface(const char *);
-int do_interface(const char *, unsigned char *, size_t *,
+int do_interface(const char *, struct interface **,
+ int argc, char * const *argv,
struct in_addr *, struct in_addr *, int);
-int if_address(const char *, const struct in_addr *, const struct in_addr *,
+int if_address(const struct interface *,
+ const struct in_addr *, const struct in_addr *,
const struct in_addr *, int);
-#define add_address(ifname, addr, net, brd) \
- if_address(ifname, addr, net, brd, 1)
-#define del_address(ifname, addr, net) \
- if_address(ifname, addr, net, NULL, -1)
-#define has_address(ifname, addr, net) \
- do_interface(ifname, NULL, NULL, addr, net, 0)
-#define get_address(ifname, addr, net) \
- do_interface(ifname, NULL, NULL, addr, net, 1)
-
-int if_route(const char *, const struct in_addr *, const struct in_addr *,
- const struct in_addr *, int, int);
-#define add_route(ifname, dest, mask, gate, metric) \
- if_route(ifname, dest, mask, gate, metric, 1)
-#define change_route(ifname, dest, mask, gate, metric) \
- if_route(ifname, dest, mask, gate, metric, 0)
-#define del_route(ifname, dest, mask, gate, metric) \
- if_route(ifname, dest, mask, gate, metric, -1)
+#define add_address(iface, addr, net, brd) \
+ if_address(iface, addr, net, brd, 1)
+#define del_address(iface, addr, net) \
+ if_address(iface, addr, net, NULL, -1)
+#define has_address(iface, addr, net) \
+ do_interface(iface, NULL, 0, NULL, addr, net, 0)
+#define get_address(iface, addr, net) \
+ do_interface(iface, NULL, 0, NULL, addr, net, 1)
+
+int if_route(const struct interface *, const struct in_addr *,
+ const struct in_addr *, const struct in_addr *, int, int);
+#define add_route(iface, dest, mask, gate, metric) \
+ if_route(iface, dest, mask, gate, metric, 1)
+#define change_route(iface, dest, mask, gate, metric) \
+ if_route(iface, dest, mask, gate, metric, 0)
+#define del_route(iface, dest, mask, gate, metric) \
+ if_route(iface, dest, mask, gate, metric, -1)
void free_routes(struct rt *);
int open_udp_socket(struct interface *);
int send_arp(const struct interface *, int, in_addr_t, in_addr_t);
-int open_link_socket(struct interface *);
-int link_changed(struct interface *);
+int open_link_socket(void);
+int link_changed(int, const struct interface *);
int carrier_status(const char *);
#endif
errno = serrno;
}
-int
-signal_fd(void)
-{
- return (signal_pipe[0]);
-}
-
/* Read a signal from the signal pipe. Returns 0 if there is
* no signal, -1 on error (and sets errno appropriately), and
* your signal on success */
return -1;
if (set_cloexec(signal_pipe[1]) == -1)
return -1;
- return 0;
+ return signal_pipe[0];
}
static int
{
return signal_handle(SIG_DFL);
}
+
int signal_init(void);
int signal_setup(void);
int signal_reset(void);
-int signal_fd(void);
int signal_read(void);
#endif