view src/route.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 08426e8a98a7
children 4da45107d87a
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * dhcpcd - route management
 * 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 <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "common.h"
#include "dhcpcd.h"
#include "if.h"
#include "if-options.h"
#include "ipv4.h"
#include "ipv4ll.h"
#include "ipv6.h"
#include "logerr.h"
#include "route.h"
#include "sa.h"

/* Needed for NetBSD-6, 7 and 8. */
#ifndef RB_TREE_FOREACH_SAFE
#ifndef RB_TREE_PREV
#define RB_TREE_NEXT(T, N) rb_tree_iterate((T), (N), RB_DIR_RIGHT)
#define RB_TREE_PREV(T, N) rb_tree_iterate((T), (N), RB_DIR_LEFT)
#endif
#define RB_TREE_FOREACH_SAFE(N, T, S) \
    for ((N) = RB_TREE_MIN(T); \
        (N) && ((S) = RB_TREE_NEXT((T), (N)), 1); \
        (N) = (S))
#define RB_TREE_FOREACH_REVERSE_SAFE(N, T, S) \
    for ((N) = RB_TREE_MAX(T); \
        (N) && ((S) = RB_TREE_PREV((T), (N)), 1); \
        (N) = (S))
#endif

#ifdef RT_FREE_ROUTE_TABLE_STATS
static size_t croutes;
static size_t nroutes;
static size_t froutes;
static size_t mroutes;
#endif

static void
rt_maskedaddr(struct sockaddr *dst,
	const struct sockaddr *addr, const struct sockaddr *netmask)
{
	const char *addrp = addr->sa_data, *netmaskp = netmask->sa_data;
	char *dstp = dst->sa_data;
	const char *addre = (char *)dst + sa_len(addr);
	const char *netmaske = (char *)dst + MIN(sa_len(addr), sa_len(netmask));

	dst->sa_family = addr->sa_family;
#ifdef HAVE_SA_LEN
	dst->sa_len = addr->sa_len;
#endif

	if (sa_is_unspecified(netmask)) {
		if (addre > dstp)
			memcpy(dstp, addrp, (size_t)(addre - dstp));
		return;
	}

	while (dstp < netmaske)
		*dstp++ = *addrp++ & *netmaskp++;
	if (dstp < addre)
		memset(dstp, 0, (size_t)(addre - dstp));
}

int
rt_cmp_dest(const struct rt *rt1, const struct rt *rt2)
{
	union sa_ss ma1 = { .sa.sa_family = AF_UNSPEC };
	union sa_ss ma2 = { .sa.sa_family = AF_UNSPEC };

	rt_maskedaddr(&ma1.sa, &rt1->rt_dest, &rt1->rt_netmask);
	rt_maskedaddr(&ma2.sa, &rt2->rt_dest, &rt2->rt_netmask);
	return sa_cmp(&ma1.sa, &ma2.sa);
}

/*
 * On some systems, host routes have no need for a netmask.
 * However DHCP specifies host routes using an all-ones netmask.
 * This handy function allows easy comparison when the two
 * differ.
 */
static int
rt_cmp_netmask(const struct rt *rt1, const struct rt *rt2)
{

	if (rt1->rt_flags & RTF_HOST && rt2->rt_flags & RTF_HOST)
		return 0;
	return sa_cmp(&rt1->rt_netmask, &rt2->rt_netmask);
}

static int
rt_compare_os(__unused void *context, const void *node1, const void *node2)
{
	const struct rt *rt1 = node1, *rt2 = node2;
	int c;

	/* Sort by masked destination. */
	c = rt_cmp_dest(rt1, rt2);
	if (c != 0)
		return c;

#ifdef HAVE_ROUTE_METRIC
	c = (int)(rt1->rt_ifp->metric - rt2->rt_ifp->metric);
#endif
	return c;
}

static int
rt_compare_list(__unused void *context, const void *node1, const void *node2)
{
	const struct rt *rt1 = node1, *rt2 = node2;

	if (rt1->rt_order > rt2->rt_order)
		return 1;
	if (rt1->rt_order < rt2->rt_order)
		return -1;
	return 0;
}

static int
rt_compare_proto(void *context, const void *node1, const void *node2)
{
	const struct rt *rt1 = node1, *rt2 = node2;
	int c;
	struct interface *ifp1, *ifp2;

	assert(rt1->rt_ifp != NULL);
	assert(rt2->rt_ifp != NULL);
	ifp1 = rt1->rt_ifp;
	ifp2 = rt2->rt_ifp;

	/* Prefer interfaces with a carrier. */
	c = ifp1->carrier - ifp2->carrier;
	if (c != 0)
		return -c;

	/* Prefer roaming over non roaming if both carriers are down. */
	if (ifp1->carrier == LINK_DOWN && ifp2->carrier == LINK_DOWN) {
		bool roam1 = if_roaming(ifp1);
		bool roam2 = if_roaming(ifp2);

		if (roam1 != roam2)
			return roam1 ? 1 : -1;
	}

#ifdef INET
	/* IPv4LL routes always come last */
	if (rt1->rt_dflags & RTDF_IPV4LL && !(rt2->rt_dflags & RTDF_IPV4LL))
		return -1;
	else if (!(rt1->rt_dflags & RTDF_IPV4LL) && rt2->rt_dflags & RTDF_IPV4LL)
		return 1;
#endif

	/* Lower metric interfaces come first. */
	c = (int)(ifp1->metric - ifp2->metric);
	if (c != 0)
		return c;

	/* Finally the order in which the route was given to us. */
	return rt_compare_list(context, rt1, rt2);
}

static const rb_tree_ops_t rt_compare_os_ops = {
	.rbto_compare_nodes = rt_compare_os,
	.rbto_compare_key = rt_compare_os,
	.rbto_node_offset = offsetof(struct rt, rt_tree),
	.rbto_context = NULL
};

const rb_tree_ops_t rt_compare_list_ops = {
	.rbto_compare_nodes = rt_compare_list,
	.rbto_compare_key = rt_compare_list,
	.rbto_node_offset = offsetof(struct rt, rt_tree),
	.rbto_context = NULL
};

const rb_tree_ops_t rt_compare_proto_ops = {
	.rbto_compare_nodes = rt_compare_proto,
	.rbto_compare_key = rt_compare_proto,
	.rbto_node_offset = offsetof(struct rt, rt_tree),
	.rbto_context = NULL
};

#ifdef RT_FREE_ROUTE_TABLE
static int
rt_compare_free(__unused void *context, const void *node1, const void *node2)
{

	return node1 == node2 ? 0 : node1 < node2 ? -1 : 1;
}

static const rb_tree_ops_t rt_compare_free_ops = {
	.rbto_compare_nodes = rt_compare_free,
	.rbto_compare_key = rt_compare_free,
	.rbto_node_offset = offsetof(struct rt, rt_tree),
	.rbto_context = NULL
};
#endif

void
rt_init(struct dhcpcd_ctx *ctx)
{

	rb_tree_init(&ctx->routes, &rt_compare_os_ops);
#ifdef RT_FREE_ROUTE_TABLE
	rb_tree_init(&ctx->froutes, &rt_compare_free_ops);
#endif
}

bool
rt_is_default(const struct rt *rt)
{

	return sa_is_unspecified(&rt->rt_dest) &&
	    sa_is_unspecified(&rt->rt_netmask);
}

static void
rt_desc(const char *cmd, const struct rt *rt)
{
	char dest[INET_MAX_ADDRSTRLEN], gateway[INET_MAX_ADDRSTRLEN];
	int prefix;
	const char *ifname;
	bool gateway_unspec;

	assert(cmd != NULL);
	assert(rt != NULL);

	sa_addrtop(&rt->rt_dest, dest, sizeof(dest));
	prefix = sa_toprefix(&rt->rt_netmask);
	sa_addrtop(&rt->rt_gateway, gateway, sizeof(gateway));
	gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
	ifname = rt->rt_ifp == NULL ? "(null)" : rt->rt_ifp->name;

	if (rt->rt_flags & RTF_HOST) {
		if (gateway_unspec)
			loginfox("%s: %s host route to %s",
			    ifname, cmd, dest);
		else
			loginfox("%s: %s host route to %s via %s",
			    ifname, cmd, dest, gateway);
	} else if (rt_is_default(rt)) {
		if (gateway_unspec)
			loginfox("%s: %s default route",
			    ifname, cmd);
		else
			loginfox("%s: %s default route via %s",
			    ifname, cmd, gateway);
	} else if (gateway_unspec)
		loginfox("%s: %s%s route to %s/%d",
		    ifname, cmd,
		    rt->rt_flags & RTF_REJECT ? " reject" : "",
		    dest, prefix);
	else
		loginfox("%s: %s%s route to %s/%d via %s",
		    ifname, cmd,
		    rt->rt_flags & RTF_REJECT ? " reject" : "",
		    dest, prefix, gateway);
}

void
rt_headclear0(struct dhcpcd_ctx *ctx, rb_tree_t *rts, int af)
{
	struct rt *rt, *rtn;

	if (rts == NULL)
		return;
	assert(ctx != NULL);
#ifdef RT_FREE_ROUTE_TABLE
	assert(&ctx->froutes != rts);
#endif

	RB_TREE_FOREACH_SAFE(rt, rts, rtn) {
		if (af != AF_UNSPEC &&
		    rt->rt_dest.sa_family != af &&
		    rt->rt_gateway.sa_family != af)
			continue;
		rb_tree_remove_node(rts, rt);
		rt_free(rt);
	}
}

void
rt_headclear(rb_tree_t *rts, int af)
{
	struct rt *rt;

	if (rts == NULL || (rt = RB_TREE_MIN(rts)) == NULL)
		return;
	rt_headclear0(rt->rt_ifp->ctx, rts, af);
}

static void
rt_headfree(rb_tree_t *rts)
{
	struct rt *rt;

	while ((rt = RB_TREE_MIN(rts)) != NULL) {
		rb_tree_remove_node(rts, rt);
		free(rt);
	}
}

void
rt_dispose(struct dhcpcd_ctx *ctx)
{

	assert(ctx != NULL);
	rt_headfree(&ctx->routes);
#ifdef RT_FREE_ROUTE_TABLE
	rt_headfree(&ctx->froutes);
#ifdef RT_FREE_ROUTE_TABLE_STATS
	logdebugx("free route list used %zu times", froutes);
	logdebugx("new routes from route free list %zu", nroutes);
	logdebugx("maximum route free list size %zu", mroutes);
#endif
#endif
}

struct rt *
rt_new0(struct dhcpcd_ctx *ctx)
{
	struct rt *rt;

	assert(ctx != NULL);
#ifdef RT_FREE_ROUTE_TABLE
	if ((rt = RB_TREE_MIN(&ctx->froutes)) != NULL) {
		rb_tree_remove_node(&ctx->froutes, rt);
#ifdef RT_FREE_ROUTE_TABLE_STATS
		croutes--;
		nroutes++;
#endif
	} else
#endif
	if ((rt = malloc(sizeof(*rt))) == NULL) {
		logerr(__func__);
		return NULL;
	}
	memset(rt, 0, sizeof(*rt));
	return rt;
}

void
rt_setif(struct rt *rt, struct interface *ifp)
{

	assert(rt != NULL);
	assert(ifp != NULL);
	rt->rt_ifp = ifp;
#ifdef HAVE_ROUTE_METRIC
	rt->rt_metric = ifp->metric;
	if (if_roaming(ifp))
		rt->rt_metric += RTMETRIC_ROAM;
#endif
}

struct rt *
rt_new(struct interface *ifp)
{
	struct rt *rt;

	assert(ifp != NULL);
	if ((rt = rt_new0(ifp->ctx)) == NULL)
		return NULL;
	rt_setif(rt, ifp);
	return rt;
}

struct rt *
rt_proto_add_ctx(rb_tree_t *tree, struct rt *rt, struct dhcpcd_ctx *ctx)
{

	rt->rt_order = ctx->rt_order++;
	if (rb_tree_insert_node(tree, rt) == rt)
		return rt;

	rt_free(rt);
	return NULL;
}

struct rt *
rt_proto_add(rb_tree_t *tree, struct rt *rt)
{

	assert (rt->rt_ifp != NULL);
	return rt_proto_add_ctx(tree, rt, rt->rt_ifp->ctx);
}

void
rt_free(struct rt *rt)
{
#ifdef RT_FREE_ROUTE_TABLE
	struct dhcpcd_ctx *ctx;

	assert(rt != NULL);
	if (rt->rt_ifp == NULL) {
		free(rt);
		return;
	}

	ctx = rt->rt_ifp->ctx;
	rb_tree_insert_node(&ctx->froutes, rt);
#ifdef RT_FREE_ROUTE_TABLE_STATS
	croutes++;
	froutes++;
	if (croutes > mroutes)
		mroutes = croutes;
#endif
#else
	free(rt);
#endif
}

void
rt_freeif(struct interface *ifp)
{
	struct dhcpcd_ctx *ctx;
	struct rt *rt, *rtn;

	if (ifp == NULL)
		return;
	ctx = ifp->ctx;
	RB_TREE_FOREACH_SAFE(rt, &ctx->routes, rtn) {
		if (rt->rt_ifp == ifp) {
			rb_tree_remove_node(&ctx->routes, rt);
			rt_free(rt);
		}
	}
}

/* If something other than dhcpcd removes a route,
 * we need to remove it from our internal table. */
void
rt_recvrt(int cmd, const struct rt *rt, pid_t pid)
{
	struct dhcpcd_ctx *ctx;
	struct rt *f;

	assert(rt != NULL);
	assert(rt->rt_ifp != NULL);
	assert(rt->rt_ifp->ctx != NULL);

	ctx = rt->rt_ifp->ctx;

	switch(cmd) {
	case RTM_DELETE:
		f = rb_tree_find_node(&ctx->routes, rt);
		if (f != NULL) {
			char buf[32];

			rb_tree_remove_node(&ctx->routes, f);
			snprintf(buf, sizeof(buf), "pid %d deleted", pid);
			rt_desc(buf, f);
			rt_free(f);
		}
		break;
	}

#if defined(IPV4LL) && defined(HAVE_ROUTE_METRIC)
	if (rt->rt_dest.sa_family == AF_INET)
		ipv4ll_recvrt(cmd, rt);
#endif
}

static bool
rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort)
{
	struct dhcpcd_ctx *ctx;
	bool change, kroute, result;

	assert(nrt != NULL);
	ctx = nrt->rt_ifp->ctx;

	/*
	 * Don't install a gateway if not asked to.
	 * This option is mainly for VPN users who want their VPN to be the
	 * default route.
	 * Because VPN's generally don't care about route management
	 * beyond their own, a longer term solution would be to remove this
	 * and get the VPN to inject the default route into dhcpcd somehow.
	 */
	if (((nrt->rt_ifp->active &&
	    !(nrt->rt_ifp->options->options & DHCPCD_GATEWAY)) ||
	    (!nrt->rt_ifp->active && !(ctx->options & DHCPCD_GATEWAY))) &&
	    sa_is_unspecified(&nrt->rt_dest) &&
	    sa_is_unspecified(&nrt->rt_netmask))
		return false;

	rt_desc(ort == NULL ? "adding" : "changing", nrt);

	change = kroute = result = false;
	if (ort == NULL) {
		ort = rb_tree_find_node(kroutes, nrt);
		if (ort != NULL &&
		    ((ort->rt_flags & RTF_REJECT &&
		      nrt->rt_flags & RTF_REJECT) ||
		     (ort->rt_ifp == nrt->rt_ifp &&
#ifdef HAVE_ROUTE_METRIC
		    ort->rt_metric == nrt->rt_metric &&
#endif
		    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)))
		{
			if (ort->rt_mtu == nrt->rt_mtu)
				return true;
			change = true;
			kroute = true;
		}
	} else if (ort->rt_dflags & RTDF_FAKE &&
	    !(nrt->rt_dflags & RTDF_FAKE) &&
	    ort->rt_ifp == nrt->rt_ifp &&
#ifdef HAVE_ROUTE_METRIC
	    ort->rt_metric == nrt->rt_metric &&
#endif
	    sa_cmp(&ort->rt_dest, &nrt->rt_dest) == 0 &&
	    rt_cmp_netmask(ort, nrt) == 0 &&
	    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)
	{
		if (ort->rt_mtu == nrt->rt_mtu)
			return true;
		change = true;
	}

#ifdef RTF_CLONING
	/* BSD can set routes to be cloning routes.
	 * Cloned routes inherit the parent flags.
	 * As such, we need to delete and re-add the route to flush children
	 * to correct the flags. */
	if (change && ort != NULL && ort->rt_flags & RTF_CLONING)
		change = false;
#endif

	if (change) {
		if (if_route(RTM_CHANGE, nrt) != -1) {
			result = true;
			goto out;
		}
		if (errno != ESRCH)
			logerr("if_route (CHG)");
	}

#ifdef HAVE_ROUTE_METRIC
	/* With route metrics, we can safely add the new route before
	 * deleting the old route. */
	if (if_route(RTM_ADD, nrt) != -1) {
		if (ort != NULL) {
			if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
				logerr("if_route (DEL)");
		}
		result = true;
		goto out;
	}

	/* If the kernel claims the route exists we need to rip out the
	 * old one first. */
	if (errno != EEXIST || ort == NULL)
		goto logerr;
#endif

	/* No route metrics, we need to delete the old route before
	 * adding the new one. */
#ifdef ROUTE_PER_GATEWAY
	errno = 0;
#endif
	if (ort != NULL) {
		if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
			logerr("if_route (DEL)");
		else
			kroute = false;
	}
#ifdef ROUTE_PER_GATEWAY
	/* The OS allows many routes to the same dest with different gateways.
	 * dhcpcd does not support this yet, so for the time being just keep on
	 * deleting the route until there is an error. */
	if (ort != NULL && errno == 0) {
		for (;;) {
			if (if_route(RTM_DELETE, ort) == -1)
				break;
		}
	}
#endif

	/* Shouldn't need to check for EEXIST, but some kernels don't
	 * dump the subnet route just after we added the address. */
	if (if_route(RTM_ADD, nrt) != -1 || errno == EEXIST) {
		result = true;
		goto out;
	}

#ifdef HAVE_ROUTE_METRIC
logerr:
#endif
	logerr("if_route (ADD)");

out:
	if (kroute) {
		rb_tree_remove_node(kroutes, ort);
		rt_free(ort);
	}
	return result;
}

static bool
rt_delete(struct rt *rt)
{
	int retval;

	rt_desc("deleting", rt);
	retval = if_route(RTM_DELETE, rt) == -1 ? false : true;
	if (!retval && errno != ENOENT && errno != ESRCH)
		logerr(__func__);
	return retval;
}

static bool
rt_cmp(const struct rt *r1, const struct rt *r2)
{

	return (r1->rt_ifp == r2->rt_ifp &&
#ifdef HAVE_ROUTE_METRIC
	    r1->rt_metric == r2->rt_metric &&
#endif
	    sa_cmp(&r1->rt_gateway, &r2->rt_gateway) == 0);
}

static bool
rt_doroute(rb_tree_t *kroutes, struct rt *rt)
{
	struct dhcpcd_ctx *ctx;
	struct rt *or;

	ctx = rt->rt_ifp->ctx;
	/* Do we already manage it? */
	or = rb_tree_find_node(&ctx->routes, rt);
	if (or != NULL) {
		if (rt->rt_dflags & RTDF_FAKE)
			return true;
		if (or->rt_dflags & RTDF_FAKE ||
		    !rt_cmp(rt, or) ||
		    (rt->rt_ifa.sa_family != AF_UNSPEC &&
		    sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) ||
		    or->rt_mtu != rt->rt_mtu)
		{
			if (!rt_add(kroutes, rt, or))
				return false;
		}
		rb_tree_remove_node(&ctx->routes, or);
		rt_free(or);
	} else {
		if (rt->rt_dflags & RTDF_FAKE) {
			or = rb_tree_find_node(kroutes, rt);
			if (or == NULL)
				return false;
			if (!rt_cmp(rt, or))
				return false;
		} else {
			if (!rt_add(kroutes, rt, NULL))
				return false;
		}
	}

	return true;
}

void
rt_build(struct dhcpcd_ctx *ctx, int af)
{
	rb_tree_t routes, added, kroutes;
	struct rt *rt, *rtn;
	unsigned long long o;

	rb_tree_init(&routes, &rt_compare_proto_ops);
	rb_tree_init(&added, &rt_compare_os_ops);
	rb_tree_init(&kroutes, &rt_compare_os_ops);
	if_initrt(ctx, &kroutes, af);
	ctx->rt_order = 0;
	ctx->options |= DHCPCD_RTBUILD;

#ifdef INET
	if (!inet_getroutes(ctx, &routes))
		goto getfail;
#endif
#ifdef INET6
	if (!inet6_getroutes(ctx, &routes))
		goto getfail;
#endif

#ifdef BSD
	/* Rewind the miss filter */
	ctx->rt_missfilterlen = 0;
#endif

	RB_TREE_FOREACH_SAFE(rt, &routes, rtn) {
		if (rt->rt_ifp->active) {
			if (!(rt->rt_ifp->options->options & DHCPCD_CONFIGURE))
				continue;
		} else if (!(ctx->options & DHCPCD_CONFIGURE))
			continue;
#ifdef BSD
		if (rt_is_default(rt) &&
		    if_missfilter(rt->rt_ifp, &rt->rt_gateway) == -1)
			logerr("if_missfilter");
#endif
		if ((rt->rt_dest.sa_family != af &&
		    rt->rt_dest.sa_family != AF_UNSPEC) ||
		    (rt->rt_gateway.sa_family != af &&
		    rt->rt_gateway.sa_family != AF_UNSPEC))
			continue;
		/* Is this route already in our table? */
		if (rb_tree_find_node(&added, rt) != NULL)
			continue;
		if (rt_doroute(&kroutes, rt)) {
			rb_tree_remove_node(&routes, rt);
			if (rb_tree_insert_node(&added, rt) != rt) {
				errno = EEXIST;
				logerr(__func__);
				rt_free(rt);
			}
		}
	}

#ifdef BSD
	if (if_missfilter_apply(ctx) == -1 && errno != ENOTSUP)
		logerr("if_missfilter_apply");
#endif

	/* Remove old routes we used to manage. */
	RB_TREE_FOREACH_REVERSE_SAFE(rt, &ctx->routes, rtn) {
		if ((rt->rt_dest.sa_family != af &&
		    rt->rt_dest.sa_family != AF_UNSPEC) ||
		    (rt->rt_gateway.sa_family != af &&
		    rt->rt_gateway.sa_family != AF_UNSPEC))
			continue;
		rb_tree_remove_node(&ctx->routes, rt);
		if (rb_tree_find_node(&added, rt) == NULL) {
			o = rt->rt_ifp->options ?
			    rt->rt_ifp->options->options :
			    ctx->options;
			if ((o &
				(DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
				(DHCPCD_EXITING | DHCPCD_PERSISTENT))
				rt_delete(rt);
		}
		rt_free(rt);
	}

	/* XXX This needs to be optimised. */
	while ((rt = RB_TREE_MIN(&added)) != NULL) {
		rb_tree_remove_node(&added, rt);
		if (rb_tree_insert_node(&ctx->routes, rt) != rt) {
			errno = EEXIST;
			logerr(__func__);
			rt_free(rt);
		}
	}

getfail:
	rt_headclear(&routes, AF_UNSPEC);
	rt_headclear(&kroutes, AF_UNSPEC);
}