view src/ipv4ll.c @ 5567:4fe5c2a71254 draft

hooks: add NOCARRIER_ROAMING reason This is given when the OS supports the concept of wireless roaming or the IP setup can be persisted when the carrier drops. When this happens, routes are moved to a higher metric (if supported) to support non preferred but non roaming routes. The `interface_order` hook variable will now order the interfaces according to priority and move roaming interfaces to the back of the list. If resolvconf is present then it is called with the -C option to deprecate DNS and if carrier comes back it is called again with the -c option to activate it once more. As part of this change, default route metrics have been changed to support a larger number of interfaces. base metric 1000 (was 200) wireless offset 2000 (was 100) IPv4LL offset 1000000 (was 10000) roaming offset 2000000
author Roy Marples <roy@marples.name>
date Sun, 27 Dec 2020 19:53:31 +0000
parents a0d828e25482
children
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * dhcpcd - DHCP client daemon
 * 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 <arpa/inet.h>

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

#define ELOOP_QUEUE	IPV4LL
#include "config.h"
#include "arp.h"
#include "common.h"
#include "dhcp.h"
#include "eloop.h"
#include "if.h"
#include "if-options.h"
#include "ipv4.h"
#include "ipv4ll.h"
#include "logerr.h"
#include "sa.h"
#include "script.h"

static const struct in_addr inaddr_llmask = {
	.s_addr = HTONL(LINKLOCAL_MASK)
};
static const struct in_addr inaddr_llbcast = {
	.s_addr = HTONL(LINKLOCAL_BCAST)
};

static void
ipv4ll_pickaddr(struct interface *ifp)
{
	struct in_addr addr = { .s_addr = 0 };
	struct ipv4ll_state *state;

	state = IPV4LL_STATE(ifp);
	setstate(state->randomstate);

	do {
		long r;

again:
		/* RFC 3927 Section 2.1 states that the first 256 and
		 * last 256 addresses are reserved for future use.
		 * See ipv4ll_start for why we don't use arc4random. */
		/* coverity[dont_call] */
		r = random();
		addr.s_addr = ntohl(LINKLOCAL_ADDR |
		    ((uint32_t)(r % 0xFD00) + 0x0100));

		/* No point using a failed address */
		if (IN_ARE_ADDR_EQUAL(&addr, &state->pickedaddr))
			goto again;
		/* Ensure we don't have the address on another interface */
	} while (ipv4_findaddr(ifp->ctx, &addr) != NULL);

	/* Restore the original random state */
	setstate(ifp->ctx->randomstate);
	state->pickedaddr = addr;
}

int
ipv4ll_subnetroute(rb_tree_t *routes, struct interface *ifp)
{
	struct ipv4ll_state *state;
	struct rt *rt;
	struct in_addr in;

	assert(ifp != NULL);
	if ((state = IPV4LL_STATE(ifp)) == NULL ||
	    state->addr == NULL)
		return 0;

	if ((rt = rt_new(ifp)) == NULL)
		return -1;

	in.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr;
	sa_in_init(&rt->rt_dest, &in);
	in.s_addr = state->addr->mask.s_addr;
	sa_in_init(&rt->rt_netmask, &in);
	in.s_addr = INADDR_ANY;
	sa_in_init(&rt->rt_gateway, &in);
	sa_in_init(&rt->rt_ifa, &state->addr->addr);
	rt->rt_dflags |= RTDF_IPV4LL;
	return rt_proto_add(routes, rt) ? 1 : 0;
}

int
ipv4ll_defaultroute(rb_tree_t *routes, struct interface *ifp)
{
	struct ipv4ll_state *state;
	struct rt *rt;
	struct in_addr in;

	assert(ifp != NULL);
	if ((state = IPV4LL_STATE(ifp)) == NULL ||
	    state->addr == NULL)
		return 0;

	if ((rt = rt_new(ifp)) == NULL)
		return -1;

	in.s_addr = INADDR_ANY;
	sa_in_init(&rt->rt_dest, &in);
	sa_in_init(&rt->rt_netmask, &in);
	sa_in_init(&rt->rt_gateway, &in);
	sa_in_init(&rt->rt_ifa, &state->addr->addr);
	rt->rt_dflags |= RTDF_IPV4LL;
#ifdef HAVE_ROUTE_METRIC
	rt->rt_metric += RTMETRIC_IPV4LL;
#endif
	return rt_proto_add(routes, rt) ? 1 : 0;
}

ssize_t
ipv4ll_env(FILE *fp, const char *prefix, const struct interface *ifp)
{
	const struct ipv4ll_state *state;
	const char *pf = prefix == NULL ? "" : "_";
	struct in_addr netnum;

	assert(ifp != NULL);
	if ((state = IPV4LL_CSTATE(ifp)) == NULL || state->addr == NULL)
		return 0;

	/* Emulate a DHCP environment */
	if (efprintf(fp, "%s%sip_address=%s",
	    prefix, pf, inet_ntoa(state->addr->addr)) == -1)
		return -1;
	if (efprintf(fp, "%s%ssubnet_mask=%s",
	    prefix, pf, inet_ntoa(state->addr->mask)) == -1)
		return -1;
	if (efprintf(fp, "%s%ssubnet_cidr=%d",
	    prefix, pf, inet_ntocidr(state->addr->mask)) == -1)
		return -1;
	if (efprintf(fp, "%s%sbroadcast_address=%s",
	    prefix, pf, inet_ntoa(state->addr->brd)) == -1)
		return -1;
	netnum.s_addr = state->addr->addr.s_addr & state->addr->mask.s_addr;
	if (efprintf(fp, "%s%snetwork_number=%s",
	    prefix, pf, inet_ntoa(netnum)) == -1)
		return -1;
	return 5;
}

static void
ipv4ll_announced_arp(struct arp_state *astate)
{
	struct ipv4ll_state *state = IPV4LL_STATE(astate->iface);

	state->conflicts = 0;
#ifdef KERNEL_RFC5227
	arp_free(astate);
#endif
}

#ifndef KERNEL_RFC5227
/* This is the callback by ARP freeing */
static void
ipv4ll_free_arp(struct arp_state *astate)
{
	struct ipv4ll_state *state;

	state = IPV4LL_STATE(astate->iface);
	if (state != NULL && state->arp == astate)
		state->arp = NULL;
}

/* This is us freeing any ARP state */
static void
ipv4ll_freearp(struct interface *ifp)
{
	struct ipv4ll_state *state;

	state = IPV4LL_STATE(ifp);
	if (state == NULL || state->arp == NULL)
		return;

	eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp);
	arp_free(state->arp);
	state->arp = NULL;
}
#else
#define	ipv4ll_freearp(ifp)
#endif

static void
ipv4ll_not_found(struct interface *ifp)
{
	struct ipv4ll_state *state;
	struct ipv4_addr *ia;
	struct arp_state *astate;

	state = IPV4LL_STATE(ifp);
	ia = ipv4_iffindaddr(ifp, &state->pickedaddr, &inaddr_llmask);
#ifdef IN_IFF_NOTREADY
	if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY)
#endif
		loginfox("%s: using IPv4LL address %s",
		  ifp->name, inet_ntoa(state->pickedaddr));
	if (!(ifp->options->options & DHCPCD_CONFIGURE))
		goto run;
	if (ia == NULL) {
		if (ifp->ctx->options & DHCPCD_TEST)
			goto test;
		ia = ipv4_addaddr(ifp, &state->pickedaddr,
		    &inaddr_llmask, &inaddr_llbcast,
		    DHCP_INFINITE_LIFETIME, DHCP_INFINITE_LIFETIME);
	}
	if (ia == NULL)
		return;
#ifdef IN_IFF_NOTREADY
	if (ia->addr_flags & IN_IFF_NOTREADY)
		return;
	logdebugx("%s: DAD completed for %s", ifp->name, ia->saddr);
#endif

test:
	state->addr = ia;
	state->down = false;
	if (ifp->ctx->options & DHCPCD_TEST) {
		script_runreason(ifp, "TEST");
		eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
		return;
	}
	rt_build(ifp->ctx, AF_INET);
run:
	astate = arp_announceaddr(ifp->ctx, &ia->addr);
	if (astate != NULL)
		astate->announced_cb = ipv4ll_announced_arp;
	script_runreason(ifp, "IPV4LL");
	dhcpcd_daemonise(ifp->ctx);
}

static void
ipv4ll_found(struct interface *ifp)
{
	struct ipv4ll_state *state = IPV4LL_STATE(ifp);

	ipv4ll_freearp(ifp);
	if (++state->conflicts == MAX_CONFLICTS)
		logerrx("%s: failed to acquire an IPv4LL address",
		    ifp->name);
	ipv4ll_pickaddr(ifp);
	eloop_timeout_add_sec(ifp->ctx->eloop,
	    state->conflicts >= MAX_CONFLICTS ?
	    RATE_LIMIT_INTERVAL : PROBE_WAIT,
	    ipv4ll_start, ifp);
}

static void
ipv4ll_defend_failed(struct interface *ifp)
{
	struct ipv4ll_state *state = IPV4LL_STATE(ifp);

	ipv4ll_freearp(ifp);
	if (ifp->options->options & DHCPCD_CONFIGURE)
		ipv4_deladdr(state->addr, 1);
	state->addr = NULL;
	rt_build(ifp->ctx, AF_INET);
	script_runreason(ifp, "IPV4LL");
	ipv4ll_pickaddr(ifp);
	ipv4ll_start(ifp);
}

#ifndef KERNEL_RFC5227
static void
ipv4ll_not_found_arp(struct arp_state *astate)
{

	ipv4ll_not_found(astate->iface);
}

static void
ipv4ll_found_arp(struct arp_state *astate, __unused const struct arp_msg *amsg)
{

	ipv4ll_found(astate->iface);
}

static void
ipv4ll_defend_failed_arp(struct arp_state *astate)
{

	ipv4ll_defend_failed(astate->iface);
}
#endif

void
ipv4ll_start(void *arg)
{
	struct interface *ifp = arg;
	struct ipv4ll_state *state;
	struct ipv4_addr *ia;
	bool repick;
#ifndef KERNEL_RFC5227
	struct arp_state *astate;
#endif

	if ((state = IPV4LL_STATE(ifp)) == NULL) {
		ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state));
		if ((state = IPV4LL_STATE(ifp)) == NULL) {
			logerr(__func__);
			return;
		}
	}

	/* RFC 3927 Section 2.1 states that the random number generator
	 * SHOULD be seeded with a value derived from persistent information
	 * such as the IEEE 802 MAC address so that it usually picks
	 * the same address without persistent storage. */
	if (!state->seeded) {
		unsigned int seed;
		char *orig;

		if (sizeof(seed) > ifp->hwlen) {
			seed = 0;
			memcpy(&seed, ifp->hwaddr, ifp->hwlen);
		} else
			memcpy(&seed, ifp->hwaddr + ifp->hwlen - sizeof(seed),
			    sizeof(seed));
		/* coverity[dont_call] */
		orig = initstate(seed,
		    state->randomstate, sizeof(state->randomstate));

		/* Save the original state. */
		if (ifp->ctx->randomstate == NULL)
			ifp->ctx->randomstate = orig;

		/* Set back the original state until we need the seeded one. */
		setstate(ifp->ctx->randomstate);
		state->seeded = true;
	}

	/* Find the previosuly used address. */
	if (state->pickedaddr.s_addr != INADDR_ANY)
		ia = ipv4_iffindaddr(ifp, &state->pickedaddr, NULL);
	else
		ia = NULL;

	/* Find an existing IPv4LL address and ensure we can work with it. */
	if (ia == NULL)
		ia = ipv4_iffindlladdr(ifp);

	repick = false;
#ifdef IN_IFF_DUPLICATED
	if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) {
		state->pickedaddr = ia->addr; /* So it's not picked again. */
		repick = true;
		if (ifp->options->options & DHCPCD_CONFIGURE)
			ipv4_deladdr(ia, 0);
		ia = NULL;
	}
#endif

	state->addr = ia;
	state->down = true;
	if (ia != NULL) {
		state->pickedaddr = ia->addr;
#ifdef IN_IFF_TENTATIVE
		if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) {
			loginfox("%s: waiting for DAD to complete on %s",
			    ifp->name, inet_ntoa(ia->addr));
			return;
		}
#endif
#ifdef IN_IFF_DUPLICATED
		loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr);
#endif
	} else {
		loginfox("%s: probing for an IPv4LL address", ifp->name);
		if (repick || state->pickedaddr.s_addr == INADDR_ANY)
			ipv4ll_pickaddr(ifp);
	}

#ifdef KERNEL_RFC5227
	ipv4ll_not_found(ifp);
#else
	ipv4ll_freearp(ifp);
	state->arp = astate = arp_new(ifp, &state->pickedaddr);
	if (state->arp == NULL)
		return;

	astate->found_cb = ipv4ll_found_arp;
	astate->not_found_cb = ipv4ll_not_found_arp;
	astate->announced_cb = ipv4ll_announced_arp;
	astate->defend_failed_cb = ipv4ll_defend_failed_arp;
	astate->free_cb = ipv4ll_free_arp;
	arp_probe(astate);
#endif
}

void
ipv4ll_drop(struct interface *ifp)
{
	struct ipv4ll_state *state;
	bool dropped = false;
	struct ipv4_state *istate;

	assert(ifp != NULL);

	ipv4ll_freearp(ifp);

	if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP)
		return;

	state = IPV4LL_STATE(ifp);
	if (state && state->addr != NULL) {
		if (ifp->options->options & DHCPCD_CONFIGURE)
			ipv4_deladdr(state->addr, 1);
		state->addr = NULL;
		dropped = true;
	}

	/* Free any other link local addresses that might exist. */
	if ((istate = IPV4_STATE(ifp)) != NULL) {
		struct ipv4_addr *ia, *ian;

		TAILQ_FOREACH_SAFE(ia, &istate->addrs, next, ian) {
			if (IN_LINKLOCAL(ntohl(ia->addr.s_addr))) {
				if (ifp->options->options & DHCPCD_CONFIGURE)
					ipv4_deladdr(ia, 0);
				dropped = true;
			}
		}
	}

	if (dropped) {
		rt_build(ifp->ctx, AF_INET);
		script_runreason(ifp, "IPV4LL");
	}
}

void
ipv4ll_reset(struct interface *ifp)
{
	struct ipv4ll_state *state = IPV4LL_STATE(ifp);

	if (state == NULL)
		return;
	ipv4ll_freearp(ifp);
	state->pickedaddr.s_addr = INADDR_ANY;
	state->seeded = false;
}

void
ipv4ll_free(struct interface *ifp)
{

	assert(ifp != NULL);

	ipv4ll_freearp(ifp);
	free(IPV4LL_STATE(ifp));
	ifp->if_data[IF_DATA_IPV4LL] = NULL;
}

/* This may cause issues in BSD systems, where running as a single dhcpcd
 * daemon would solve this issue easily. */
#ifdef HAVE_ROUTE_METRIC
int
ipv4ll_recvrt(__unused int cmd, const struct rt *rt)
{
	struct dhcpcd_ctx *ctx;
	struct interface *ifp;

	/* Only interested in default route changes. */
	if (sa_is_unspecified(&rt->rt_dest))
		return 0;

	/* If any interface is running IPv4LL, rebuild our routing table. */
	ctx = rt->rt_ifp->ctx;
	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		if (IPV4LL_STATE_RUNNING(ifp)) {
			rt_build(ctx, AF_INET);
			break;
		}
	}

	return 0;
}
#endif

struct ipv4_addr *
ipv4ll_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid)
{
	struct interface *ifp;
	struct ipv4ll_state *state;

	ifp = ia->iface;
	state = IPV4LL_STATE(ifp);
	if (state == NULL)
		return ia;

	if (cmd == RTM_DELADDR &&
	    state->addr != NULL &&
	    IN_ARE_ADDR_EQUAL(&state->addr->addr, &ia->addr))
	{
		loginfox("%s: pid %d deleted IP address %s",
		    ifp->name, pid, ia->saddr);
		ipv4ll_defend_failed(ifp);
		return ia;
	}

#ifdef IN_IFF_DUPLICATED
	if (cmd != RTM_NEWADDR)
		return ia;
	if (!IN_ARE_ADDR_EQUAL(&state->pickedaddr, &ia->addr))
		return ia;
	if (!(ia->addr_flags & IN_IFF_NOTUSEABLE))
		ipv4ll_not_found(ifp);
	else if (ia->addr_flags & IN_IFF_DUPLICATED) {
		logerrx("%s: DAD detected %s", ifp->name, ia->saddr);
		ipv4ll_freearp(ifp);
		if (ifp->options->options & DHCPCD_CONFIGURE)
			ipv4_deladdr(ia, 1);
		state->addr = NULL;
		rt_build(ifp->ctx, AF_INET);
		ipv4ll_found(ifp);
		return NULL;
	}
#endif

	return ia;
}