summaryrefslogtreecommitdiffstats
path: root/dhcpcd/arp.c
diff options
context:
space:
mode:
Diffstat (limited to 'dhcpcd/arp.c')
-rw-r--r--dhcpcd/arp.c338
1 files changed, 338 insertions, 0 deletions
diff --git a/dhcpcd/arp.c b/dhcpcd/arp.c
new file mode 100644
index 00000000..13779a48
--- /dev/null
+++ b/dhcpcd/arp.c
@@ -0,0 +1,338 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 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 <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "arp.h"
+#include "ipv4.h"
+#include "common.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "if-options.h"
+#include "ipv4ll.h"
+#include "net.h"
+
+#define ARP_LEN \
+ (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN))
+
+static int
+arp_send(const struct interface *ifp, int op, in_addr_t sip, in_addr_t tip)
+{
+ uint8_t arp_buffer[ARP_LEN];
+ struct arphdr ar;
+ size_t len;
+ uint8_t *p;
+
+ ar.ar_hrd = htons(ifp->family);
+ ar.ar_pro = htons(ETHERTYPE_IP);
+ ar.ar_hln = ifp->hwlen;
+ ar.ar_pln = sizeof(sip);
+ ar.ar_op = htons(op);
+
+ p = arp_buffer;
+ len = sizeof(arp_buffer);
+
+#define CHECK(fun, b, l) \
+ do { \
+ if (len < (l)) \
+ goto eexit; \
+ fun(p, (b), (l)); \
+ p += (l); \
+ len -= (l); \
+ } while (/* CONSTCOND */ 0)
+#define APPEND(b, l) CHECK(memcpy, b, l)
+#define ZERO(l) CHECK(memset, 0, l)
+
+ APPEND(&ar, sizeof(ar));
+ APPEND(ifp->hwaddr, ifp->hwlen);
+ APPEND(&sip, sizeof(sip));
+ ZERO(ifp->hwlen);
+ APPEND(&tip, sizeof(tip));
+ len = p - arp_buffer;
+ return ipv4_sendrawpacket(ifp, ETHERTYPE_ARP, arp_buffer, len);
+
+eexit:
+ errno = ENOBUFS;
+ return -1;
+}
+
+static void
+arp_failure(struct interface *ifp)
+{
+ const struct dhcp_state *state = D_CSTATE(ifp);
+
+ /* If we failed without a magic cookie then we need to try
+ * and defend our IPv4LL address. */
+ if ((state->offer != NULL &&
+ state->offer->cookie != htonl(MAGIC_COOKIE)) ||
+ (state->new != NULL &&
+ state->new->cookie != htonl(MAGIC_COOKIE)))
+ {
+ ipv4ll_handle_failure(ifp);
+ return;
+ }
+
+ unlink(state->leasefile);
+ if (!state->lease.frominfo)
+ dhcp_decline(ifp);
+ dhcp_close(ifp);
+ eloop_timeout_delete(NULL, ifp);
+ if (state->lease.frominfo)
+ start_interface(ifp);
+ else
+ eloop_timeout_add_sec(DHCP_ARP_FAIL, start_interface, ifp);
+}
+
+static void
+arp_packet(void *arg)
+{
+ struct interface *ifp = arg;
+ uint8_t arp_buffer[ARP_LEN];
+ struct arphdr ar;
+ uint32_t reply_s;
+ uint32_t reply_t;
+ uint8_t *hw_s, *hw_t;
+ ssize_t bytes;
+ struct dhcp_state *state;
+ struct if_options *opts = ifp->options;
+ const char *hwaddr;
+ struct in_addr ina;
+
+ state = D_STATE(ifp);
+ state->fail.s_addr = 0;
+ for(;;) {
+ bytes = ipv4_getrawpacket(ifp, ETHERTYPE_ARP,
+ arp_buffer, sizeof(arp_buffer), NULL);
+ if (bytes == 0 || bytes == -1)
+ return;
+ /* We must have a full ARP header */
+ if ((size_t)bytes < sizeof(ar))
+ continue;
+ memcpy(&ar, arp_buffer, sizeof(ar));
+ /* Protocol must be IP. */
+ if (ar.ar_pro != htons(ETHERTYPE_IP))
+ continue;
+ if (ar.ar_pln != sizeof(reply_s))
+ continue;
+ /* Only these types are recognised */
+ if (ar.ar_op != htons(ARPOP_REPLY) &&
+ ar.ar_op != htons(ARPOP_REQUEST))
+ continue;
+
+ /* Get pointers to the hardware addreses */
+ hw_s = arp_buffer + sizeof(ar);
+ hw_t = hw_s + ar.ar_hln + ar.ar_pln;
+ /* Ensure we got all the data */
+ if ((hw_t + ar.ar_hln + ar.ar_pln) - arp_buffer > bytes)
+ continue;
+ /* Ignore messages from ourself */
+ if (ar.ar_hln == ifp->hwlen &&
+ memcmp(hw_s, ifp->hwaddr, ifp->hwlen) == 0)
+ continue;
+ /* Copy out the IP addresses */
+ memcpy(&reply_s, hw_s + ar.ar_hln, ar.ar_pln);
+ memcpy(&reply_t, hw_t + ar.ar_hln, ar.ar_pln);
+
+ /* Check for arping */
+ if (state->arping_index &&
+ state->arping_index <= opts->arping_len &&
+ (reply_s == opts->arping[state->arping_index - 1] ||
+ (reply_s == 0 &&
+ reply_t == opts->arping[state->arping_index - 1])))
+ {
+ ina.s_addr = reply_s;
+ hwaddr = hwaddr_ntoa((unsigned char *)hw_s,
+ (size_t)ar.ar_hln);
+ syslog(LOG_INFO,
+ "%s: found %s on hardware address %s",
+ ifp->name, inet_ntoa(ina), hwaddr);
+ if (select_profile(ifp, hwaddr) == -1 &&
+ errno == ENOENT)
+ select_profile(ifp, inet_ntoa(ina));
+ dhcp_close(ifp);
+ eloop_timeout_delete(NULL, ifp);
+ start_interface(ifp);
+ return;
+ }
+
+ /* RFC 2131 3.1.5, Client-server interaction
+ * RFC 3927 2.2.1, Probe Conflict Detection */
+ if (state->offer &&
+ (reply_s == state->offer->yiaddr ||
+ (reply_s == 0 && reply_t == state->offer->yiaddr)))
+ state->fail.s_addr = state->offer->yiaddr;
+
+ /* RFC 3927 2.5, Conflict Defense */
+ if (IN_LINKLOCAL(htonl(state->addr.s_addr)) &&
+ reply_s == state->addr.s_addr)
+ state->fail.s_addr = state->addr.s_addr;
+
+ if (state->fail.s_addr) {
+ syslog(LOG_ERR, "%s: hardware address %s claims %s",
+ ifp->name,
+ hwaddr_ntoa((unsigned char *)hw_s,
+ (size_t)ar.ar_hln),
+ inet_ntoa(state->fail));
+ errno = EEXIST;
+ arp_failure(ifp);
+ return;
+ }
+ }
+}
+
+void
+arp_announce(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct timeval tv;
+
+ if (state->new == NULL)
+ return;
+ if (state->arp_fd == -1) {
+ state->arp_fd = ipv4_opensocket(ifp, ETHERTYPE_ARP);
+ if (state->arp_fd == -1) {
+ syslog(LOG_ERR, "%s: %s: %m", __func__, ifp->name);
+ return;
+ }
+ eloop_event_add(state->arp_fd, arp_packet, ifp);
+ }
+ if (++state->claims < ANNOUNCE_NUM)
+ syslog(LOG_DEBUG,
+ "%s: sending ARP announce (%d of %d), "
+ "next in %d.0 seconds",
+ ifp->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
+ else
+ syslog(LOG_DEBUG,
+ "%s: sending ARP announce (%d of %d)",
+ ifp->name, state->claims, ANNOUNCE_NUM);
+ if (arp_send(ifp, ARPOP_REQUEST,
+ state->new->yiaddr, state->new->yiaddr) == -1)
+ syslog(LOG_ERR, "send_arp: %m");
+ if (state->claims < ANNOUNCE_NUM) {
+ eloop_timeout_add_sec(ANNOUNCE_WAIT, arp_announce, ifp);
+ return;
+ }
+ if (state->new->cookie != htonl(MAGIC_COOKIE)) {
+ /* Check if doing DHCP */
+ if (!(ifp->options->options & DHCPCD_DHCP))
+ return;
+ /* We should pretend to be at the end
+ * of the DHCP negotation cycle unless we rebooted */
+ if (state->interval != 0)
+ 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);
+ timernorm(&tv);
+ eloop_timeout_add_tv(&tv, dhcp_discover, ifp);
+ } else {
+ eloop_event_delete(state->arp_fd);
+ close(state->arp_fd);
+ state->arp_fd = -1;
+ }
+}
+
+void
+arp_probe(void *arg)
+{
+ struct interface *ifp = arg;
+ struct dhcp_state *state = D_STATE(ifp);
+ struct in_addr addr;
+ struct timeval tv;
+ int arping = 0;
+
+ if (state->arp_fd == -1) {
+ state->arp_fd = ipv4_opensocket(ifp, ETHERTYPE_ARP);
+ if (state->arp_fd == -1) {
+ syslog(LOG_ERR, "%s: %s: %m", __func__, ifp->name);
+ return;
+ }
+ eloop_event_add(state->arp_fd, arp_packet, ifp);
+ }
+
+ if (state->arping_index < ifp->options->arping_len) {
+ addr.s_addr = ifp->options->arping[state->arping_index];
+ arping = 1;
+ } else if (state->offer) {
+ if (state->offer->yiaddr)
+ addr.s_addr = state->offer->yiaddr;
+ else
+ addr.s_addr = state->offer->ciaddr;
+ } else
+ addr.s_addr = state->addr.s_addr;
+
+ if (state->probes == 0) {
+ if (arping)
+ syslog(LOG_DEBUG, "%s: searching for %s",
+ ifp->name, inet_ntoa(addr));
+ else
+ syslog(LOG_DEBUG, "%s: checking for %s",
+ ifp->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);
+ eloop_timeout_add_tv(&tv, arp_probe, ifp);
+ } else {
+ tv.tv_sec = ANNOUNCE_WAIT;
+ tv.tv_usec = 0;
+ if (arping) {
+ state->probes = 0;
+ if (++state->arping_index < ifp->options->arping_len)
+ eloop_timeout_add_tv(&tv, arp_probe, ifp);
+ else
+ eloop_timeout_add_tv(&tv, start_interface, ifp);
+ } else
+ eloop_timeout_add_tv(&tv, dhcp_bind, ifp);
+ }
+ syslog(LOG_DEBUG,
+ "%s: sending ARP probe (%d of %d), next in %0.1f seconds",
+ ifp->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM,
+ timeval_to_double(&tv));
+ if (arp_send(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1)
+ syslog(LOG_ERR, "send_arp: %m");
+}
+
+void
+arp_start(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+
+ state->probes = 0;
+ state->arping_index = 0;
+ arp_probe(ifp);
+}