view src/arp.c @ 5229:bb468c1a3b46 draft

ARP: Remove ability to filter specific addresses This is only really needed for long lasting ARP, which is only used for IPv4 address defence. Modern NetBSD does not need this and it fails to work with OpenBSD Pledge. FreeBSD Capsicum is more secure without this as the BPF fd can then be locked for other changes [1]. That just leaves Linux and Solaris. If anyone feels dhcpcd is processing to much ARP then please implement RFC 5227 in the kernel like NetBSD. [1] Locking the BPF fd is questionable because the inet proxy using sendmsg can send any packet to any destination.
author Roy Marples <roy@marples.name>
date Fri, 15 May 2020 22:29:30 +0100
parents a70f6ddefe3c
children a2c342295221
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * dhcpcd - ARP handler
 * Copyright (c) 2006-2020 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/socket.h>
#include <sys/types.h>

#include <arpa/inet.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define ELOOP_QUEUE	ELOOP_ARP
#include "config.h"
#include "arp.h"
#include "bpf.h"
#include "ipv4.h"
#include "common.h"
#include "dhcpcd.h"
#include "eloop.h"
#include "if.h"
#include "if-options.h"
#include "ipv4ll.h"
#include "logerr.h"
#include "privsep.h"

#if defined(ARP)
#define ARP_LEN								\
	(FRAMEHDRLEN_MAX +						\
	 sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN))

/* ARP debugging can be quite noisy. Enable this for more noise! */
//#define	ARP_DEBUG

/* Assert the correct structure size for on wire */
__CTASSERT(sizeof(struct arphdr) == 8);

static ssize_t
arp_request(const struct interface *ifp,
    const struct in_addr *sip, const struct in_addr *tip)
{
	uint8_t arp_buffer[ARP_LEN];
	struct arphdr ar;
	size_t len;
	uint8_t *p;
	const struct iarp_state *state;

	ar.ar_hrd = htons(ifp->hwtype);
	ar.ar_pro = htons(ETHERTYPE_IP);
	ar.ar_hln = ifp->hwlen;
	ar.ar_pln = sizeof(tip->s_addr);
	ar.ar_op = htons(ARPOP_REQUEST);

	p = arp_buffer;
	len = 0;

#define CHECK(fun, b, l)						\
	do {								\
		if (len + (l) > sizeof(arp_buffer))			\
			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);
	if (sip != NULL)
		APPEND(&sip->s_addr, sizeof(sip->s_addr));
	else
		ZERO(sizeof(tip->s_addr));
	ZERO(ifp->hwlen);
	APPEND(&tip->s_addr, sizeof(tip->s_addr));

#ifdef PRIVSEP
	if (ifp->ctx->options & DHCPCD_PRIVSEP)
		return ps_bpf_sendarp(ifp, arp_buffer, len);
#endif
	state = ARP_CSTATE(ifp);
	/* Note that well formed ethernet will add extra padding
	 * to ensure that the packet is at least 60 bytes (64 including FCS). */
	return bpf_send(ifp, state->bpf_fd, ETHERTYPE_ARP, arp_buffer, len);

eexit:
	errno = ENOBUFS;
	return -1;
}

static void
arp_report_conflicted(const struct arp_state *astate,
    const struct arp_msg *amsg)
{
	char abuf[HWADDR_LEN * 3];
	char fbuf[HWADDR_LEN * 3];

	if (amsg == NULL) {
		logerrx("%s: DAD detected %s",
		    astate->iface->name, inet_ntoa(astate->addr));
		return;
	}

	hwaddr_ntoa(amsg->sha, astate->iface->hwlen, abuf, sizeof(abuf));
	if (bpf_frame_header_len(astate->iface) == 0) {
		logerrx("%s: %s claims %s",
		    astate->iface->name, abuf, inet_ntoa(astate->addr));
		return;
	}

	logerrx("%s: %s(%s) claims %s",
	    astate->iface->name, abuf,
	    hwaddr_ntoa(amsg->fsha, astate->iface->hwlen, fbuf, sizeof(fbuf)),
	    inet_ntoa(astate->addr));
}

static void
arp_found(struct arp_state *astate, const struct arp_msg *amsg)
{
	struct interface *ifp;
	struct ipv4_addr *ia;
#ifndef KERNEL_RFC5227
	struct timespec now;
#endif

	arp_report_conflicted(astate, amsg);
	ifp = astate->iface;

	/* If we haven't added the address we're doing a probe. */
	ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
	if (ia == NULL) {
		if (astate->found_cb != NULL)
			astate->found_cb(astate, amsg);
		return;
	}

#ifndef KERNEL_RFC5227
	/* RFC 3927 Section 2.5 says a defence should
	 * broadcast an ARP announcement.
	 * Because the kernel will also unicast a reply to the
	 * hardware address which requested the IP address
	 * the other IPv4LL client will receieve two ARP
	 * messages.
	 * If another conflict happens within DEFEND_INTERVAL
	 * then we must drop our address and negotiate a new one. */
	clock_gettime(CLOCK_MONOTONIC, &now);
	if (timespecisset(&astate->defend) &&
	    eloop_timespec_diff(&now, &astate->defend, NULL) < DEFEND_INTERVAL)
		logwarnx("%s: %d second defence failed for %s",
		    ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr));
	else if (arp_request(ifp, &astate->addr, &astate->addr) == -1)
		logerr(__func__);
	else {
		logdebugx("%s: defended address %s",
		    ifp->name, inet_ntoa(astate->addr));
		astate->defend = now;
		return;
	}
#endif

	if (astate->defend_failed_cb != NULL)
		astate->defend_failed_cb(astate);
}

static bool
arp_validate(const struct interface *ifp, struct arphdr *arp)
{

	/* Address type must match */
	if (arp->ar_hrd != htons(ifp->hwtype))
		return false;

	/* Protocol must be IP. */
	if (arp->ar_pro != htons(ETHERTYPE_IP))
		return false;

	/* lladdr length matches */
	if (arp->ar_hln != ifp->hwlen)
		return false;

	/* Protocol length must match in_addr_t */
	if (arp->ar_pln != sizeof(in_addr_t))
		return false;

	/* Only these types are recognised */
	if (arp->ar_op != htons(ARPOP_REPLY) &&
	    arp->ar_op != htons(ARPOP_REQUEST))
		return false;

	return true;
}

void
arp_packet(struct interface *ifp, uint8_t *data, size_t len)
{
	size_t fl = bpf_frame_header_len(ifp), falen;
	const struct interface *ifn;
	struct arphdr ar;
	struct arp_msg arm;
	const struct iarp_state *state;
	struct arp_state *astate, *astaten;
	uint8_t *hw_s, *hw_t;

	/* Copy the frame header source and destination out */
	memset(&arm, 0, sizeof(arm));
	if (fl != 0) {
		hw_s = bpf_frame_header_src(ifp, data, &falen);
		if (hw_s != NULL && falen <= sizeof(arm.fsha))
			memcpy(arm.fsha, hw_s, falen);
		hw_t = bpf_frame_header_dst(ifp, data, &falen);
		if (hw_t != NULL && falen <= sizeof(arm.ftha))
			memcpy(arm.ftha, hw_t, falen);

		/* Skip past the frame header */
		data += fl;
		len -= fl;
	}

	/* We must have a full ARP header */
	if (len < sizeof(ar))
		return;
	memcpy(&ar, data, sizeof(ar));

	if (!arp_validate(ifp, &ar)) {
#ifdef BPF_DEBUG
		logerrx("%s: ARP BPF validation failure", ifp->name);
#endif
		return;
	}

	/* Get pointers to the hardware addresses */
	hw_s = data + sizeof(ar);
	hw_t = hw_s + ar.ar_hln + ar.ar_pln;
	/* Ensure we got all the data */
	if ((size_t)((hw_t + ar.ar_hln + ar.ar_pln) - data) > len)
		return;
	/* Ignore messages from ourself */
	TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
		if (ar.ar_hln == ifn->hwlen &&
		    memcmp(hw_s, ifn->hwaddr, ifn->hwlen) == 0)
			break;
	}
	if (ifn) {
#ifdef ARP_DEBUG
		logdebugx("%s: ignoring ARP from self", ifp->name);
#endif
		return;
	}
	/* Copy out the HW and IP addresses */
	memcpy(&arm.sha, hw_s, ar.ar_hln);
	memcpy(&arm.sip.s_addr, hw_s + ar.ar_hln, ar.ar_pln);
	memcpy(&arm.tha, hw_t, ar.ar_hln);
	memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln);

	/* Match the ARP probe to our states.
	 * Ignore Unicast Poll, RFC1122. */
	state = ARP_CSTATE(ifp);
	if (state == NULL)
		return;
	TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) {
		if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) ||
		    (IN_IS_ADDR_UNSPECIFIED(&arm.sip) &&
		    IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr) &&
		    state->bpf_flags & BPF_BCAST))
			arp_found(astate, &arm);
	}
}

static void
arp_close(struct interface *ifp)
{
	struct dhcpcd_ctx *ctx = ifp->ctx;
	struct iarp_state *state;

#ifdef PRIVSEP
	if (IN_PRIVSEP(ctx)) {
		if (IN_PRIVSEP_SE(ctx) &&
		    ps_bpf_closearp(ifp) == -1)
			logerr(__func__);
		return;
	}
#endif

	if ((state = ARP_STATE(ifp)) == NULL)
		return;

	if (state->bpf_fd == -1)
		return;
	eloop_event_delete(ctx->eloop, state->bpf_fd);
	bpf_close(ifp, state->bpf_fd);
	state->bpf_fd = -1;
	state->bpf_flags |= BPF_EOF;
}

static void
arp_tryfree(struct iarp_state *state)
{
	struct interface *ifp = state->ifp;

	/* If there are no more ARP states, close the socket. */
	if (TAILQ_FIRST(&state->arp_states) == NULL) {
		arp_close(ifp);
		if (state->bpf_flags & BPF_READING)
			state->bpf_flags |= BPF_EOF;
		else {
			free(state);
			ifp->if_data[IF_DATA_ARP] = NULL;
		}
	} else if (state->bpf_fd != -1) {
		if (bpf_arp(ifp, state->bpf_fd) == -1)
			logerr(__func__);
	}
}

static void
arp_read(void *arg)
{
	struct iarp_state *state = arg;
	struct interface *ifp = state->ifp;
	uint8_t buf[ARP_LEN];
	ssize_t bytes;

	/* Some RAW mechanisms are generic file descriptors, not sockets.
	 * This means we have no kernel call to just get one packet,
	 * so we have to process the entire buffer. */
	state->bpf_flags &= ~BPF_EOF;
	state->bpf_flags |= BPF_READING;
	while (!(state->bpf_flags & BPF_EOF)) {
		bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf),
				 &state->bpf_flags);
		if (bytes == -1) {
			logerr("%s: %s", __func__, ifp->name);
			arp_close(ifp);
			break;
		}
		arp_packet(ifp, buf, (size_t)bytes);
		/* Check we still have a state after processing. */
		if ((state = ARP_STATE(ifp)) == NULL)
			break;
	}
	if (state != NULL) {
		state->bpf_flags &= ~BPF_READING;
		/* Try and free the state if nothing left to do. */
		arp_tryfree(state);
	}
}

static int
arp_open(struct interface *ifp)
{
	struct iarp_state *state;

#ifdef PRIVSEP
	if (IN_PRIVSEP_SE(ifp->ctx))
		return ps_bpf_openarp(ifp) == -1 ? -1 : 0;
#endif

	state = ARP_STATE(ifp);
	if (state->bpf_fd == -1) {
		state->bpf_fd = bpf_open(ifp, bpf_arp);
		if (state->bpf_fd == -1)
			return -1;
		eloop_event_add(ifp->ctx->eloop, state->bpf_fd, arp_read, state);
	}
	return state->bpf_fd;
}

static void
arp_probed(void *arg)
{
	struct arp_state *astate = arg;

	timespecclear(&astate->defend);
	astate->not_found_cb(astate);
}

static void
arp_probe1(void *arg)
{
	struct arp_state *astate = arg;
	struct interface *ifp = astate->iface;
	unsigned int delay;

	if (++astate->probes < PROBE_NUM) {
		delay = (PROBE_MIN * MSEC_PER_SEC) +
		    (arc4random_uniform(
		    (PROBE_MAX - PROBE_MIN) * MSEC_PER_SEC));
		eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probe1, astate);
	} else {
		delay = ANNOUNCE_WAIT *	MSEC_PER_SEC;
		eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probed, astate);
	}
	logdebugx("%s: ARP probing %s (%d of %d), next in %0.1f seconds",
	    ifp->name, inet_ntoa(astate->addr),
	    astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM,
	    (float)delay / MSEC_PER_SEC);
	if (arp_request(ifp, NULL, &astate->addr) == -1)
		logerr(__func__);
}

void
arp_probe(struct arp_state *astate)
{

	astate->probes = 0;
	logdebugx("%s: probing for %s",
	    astate->iface->name, inet_ntoa(astate->addr));
	if (!(IN_PRIVSEP(astate->iface->ctx)) && arp_open(astate->iface) == -1)
	{
		logerr(__func__);
		return;
	}
	arp_probe1(astate);
}
#endif	/* ARP */

struct arp_state *
arp_find(struct interface *ifp, const struct in_addr *addr)
{
	struct iarp_state *state;
	struct arp_state *astate;

	if ((state = ARP_STATE(ifp)) == NULL)
		goto out;
	TAILQ_FOREACH(astate, &state->arp_states, next) {
		if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp)
			return astate;
	}
out:
	errno = ESRCH;
	return NULL;
}

static void
arp_announced(void *arg)
{
	struct arp_state *astate = arg;

	if (astate->announced_cb) {
		astate->announced_cb(astate);
		return;
	}

	/* Keep the ARP state open to handle ongoing ACD. */
}

static void
arp_announce1(void *arg)
{
	struct arp_state *astate = arg;
	struct interface *ifp = astate->iface;
	struct ipv4_addr *ia;

	if (++astate->claims < ANNOUNCE_NUM)
		logdebugx("%s: ARP announcing %s (%d of %d), "
		    "next in %d.0 seconds",
		    ifp->name, inet_ntoa(astate->addr),
		    astate->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
	else
		logdebugx("%s: ARP announcing %s (%d of %d)",
		    ifp->name, inet_ntoa(astate->addr),
		    astate->claims, ANNOUNCE_NUM);

	/* The kernel will send a Gratuitous ARP for newly added addresses.
	 * So we can avoid sending the same.
	 * Linux is special and doesn't send one. */
	ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
#ifndef __linux__
	if (astate->claims == 1 && ia != NULL && ia->flags & IPV4_AF_NEW)
		goto skip_request;
#endif

	if (arp_request(ifp, &astate->addr, &astate->addr) == -1)
		logerr(__func__);

#ifndef __linux__
skip_request:
#endif
	/* No longer a new address. */
	if (ia != NULL)
		ia->flags |= ~IPV4_AF_NEW;

	eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT,
	    astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced,
	    astate);
}

void
arp_announce(struct arp_state *astate)
{
	struct iarp_state *state;
	struct interface *ifp;
	struct arp_state *a2;
	int r;

	if (!(IN_PRIVSEP(astate->iface->ctx)) && arp_open(astate->iface) == -1)
	{
		logerr(__func__);
		return;
	}

	/* Cancel any other ARP announcements for this address. */
	TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) {
		state = ARP_STATE(ifp);
		if (state == NULL)
			continue;
		TAILQ_FOREACH(a2, &state->arp_states, next) {
			if (astate == a2 ||
			    a2->addr.s_addr != astate->addr.s_addr)
				continue;
			r = eloop_timeout_delete(a2->iface->ctx->eloop,
			    a2->claims < ANNOUNCE_NUM
			    ? arp_announce1 : arp_announced,
			    a2);
			if (r == -1)
				logerr(__func__);
			else if (r != 0)
				logdebugx("%s: ARP announcement "
				    "of %s cancelled",
				    a2->iface->name,
				    inet_ntoa(a2->addr));
		}
	}

	astate->claims = 0;
	arp_announce1(astate);
}

void
arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia)
{
	struct arp_state *astate;

	if (ifp->flags & IFF_NOARP)
		return;

	astate = arp_find(ifp, ia);
	if (astate == NULL) {
		astate = arp_new(ifp, ia);
		if (astate == NULL)
			return;
		astate->announced_cb = arp_free;
	}
	arp_announce(astate);
}

void
arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia)
{
	struct interface *ifp, *iff = NULL;
	struct ipv4_addr *iap;

	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		if (!ifp->active || ifp->carrier <= LINK_DOWN)
			continue;
		iap = ipv4_iffindaddr(ifp, ia, NULL);
		if (iap == NULL)
			continue;
#ifdef IN_IFF_NOTUSEABLE
		if (!(iap->addr_flags & IN_IFF_NOTUSEABLE))
			continue;
#endif
		if (iff != NULL && iff->metric < ifp->metric)
			continue;
		iff = ifp;
	}
	if (iff == NULL)
		return;

	arp_ifannounceaddr(iff, ia);
}

struct arp_state *
arp_new(struct interface *ifp, const struct in_addr *addr)
{
	struct iarp_state *state;
	struct arp_state *astate;

	if ((state = ARP_STATE(ifp)) == NULL) {
#ifdef PRIVSEP
		/* We need to ensure ARP is spawned so we can add to it. */
		if (IN_PRIVSEP_SE(ifp->ctx) && arp_open(ifp) == -1) {
			logerr(__func__);
			return NULL;
		}
#endif
	        ifp->if_data[IF_DATA_ARP] = malloc(sizeof(*state));
		state = ARP_STATE(ifp);
		if (state == NULL) {
			logerr(__func__);
			return NULL;
		}
		state->ifp = ifp;
		state->bpf_fd = -1;
		state->bpf_flags = 0;
		TAILQ_INIT(&state->arp_states);
	} else {
		if (addr && (astate = arp_find(ifp, addr)))
			return astate;
	}

	if ((astate = calloc(1, sizeof(*astate))) == NULL) {
		logerr(__func__);
		return NULL;
	}
	astate->iface = ifp;
	state = ARP_STATE(ifp);
	TAILQ_INSERT_TAIL(&state->arp_states, astate, next);
	if (state->bpf_fd != -1) {
		if (bpf_arp(ifp, state->bpf_fd) == -1)
			logerr(__func__); /* try and continue */
	}
	return astate;
}

void
arp_cancel(struct arp_state *astate)
{

	eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate);
}

void
arp_free(struct arp_state *astate)
{
	struct interface *ifp;
	struct iarp_state *state;

	if (astate == NULL)
		return;

	ifp = astate->iface;
	eloop_timeout_delete(ifp->ctx->eloop, NULL, astate);
	state =	ARP_STATE(ifp);
	TAILQ_REMOVE(&state->arp_states, astate, next);
	if (astate->free_cb)
		astate->free_cb(astate);
	free(astate);
	arp_tryfree(state);
}

void
arp_freeaddr(struct interface *ifp, const struct in_addr *ia)
{
	struct arp_state *astate;

	astate = arp_find(ifp, ia);
	arp_free(astate);
}

void
arp_drop(struct interface *ifp)
{
	struct iarp_state *state;
	struct arp_state *astate;

	while ((state = ARP_STATE(ifp)) != NULL &&
	    (astate = TAILQ_FIRST(&state->arp_states)) != NULL)
		arp_free(astate);

	/* No need to close because the last free will close */
}