[BACK]Return to config.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / bgpd

File: [local] / src / usr.sbin / bgpd / config.c (download)

Revision 1.108, Wed Aug 16 08:26:35 2023 UTC (9 months, 3 weeks ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4
Changes since 1.107: +1 -2 lines

Remove per-AFI ASPA handling in bgpd internals

With draft-ietf-sidrops-aspa-profile-16 and
draft-ietf-sidrops-aspa-verification-15 the AFI dependence of ASPA
records was dropped. So remove this complication form the code.

This only removes the AFI handling internally in bgpd but still allows
the old syntax in aspa-set tables. The optional address family is just
ignored and records are merged together.

For RTR sessions draft-ietf-sidrops-8210bis has not yet been updated so
right now we still handle RTR sessions as specified there. The IPv4 and
IPv6 ASPA entries are handled in two trees and merged together into one
AFI independent tree. This is the best we can do for now until IETF
updates draft-ietf-sidrops-8210bis.

OK tb@ job@

/*	$OpenBSD: config.c,v 1.108 2023/08/16 08:26:35 claudio Exp $ */

/*
 * Copyright (c) 2003, 2004, 2005 Henning Brauer <henning@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "bgpd.h"
#include "session.h"
#include "log.h"

int		host_ip(const char *, struct bgpd_addr *, uint8_t *);
void		free_networks(struct network_head *);
void		free_flowspecs(struct flowspec_tree *);

struct bgpd_config *
new_config(void)
{
	struct bgpd_config	*conf;

	if ((conf = calloc(1, sizeof(struct bgpd_config))) == NULL)
		fatal(NULL);

	if ((conf->filters = calloc(1, sizeof(struct filter_head))) == NULL)
		fatal(NULL);
	if ((conf->listen_addrs = calloc(1, sizeof(struct listen_addrs))) ==
	    NULL)
		fatal(NULL);
	if ((conf->mrt = calloc(1, sizeof(struct mrt_head))) == NULL)
		fatal(NULL);

	/* init the various list for later */
	RB_INIT(&conf->peers);
	TAILQ_INIT(&conf->networks);
	RB_INIT(&conf->flowspecs);
	SIMPLEQ_INIT(&conf->l3vpns);
	SIMPLEQ_INIT(&conf->prefixsets);
	SIMPLEQ_INIT(&conf->originsets);
	SIMPLEQ_INIT(&conf->rde_prefixsets);
	SIMPLEQ_INIT(&conf->rde_originsets);
	RB_INIT(&conf->roa);
	RB_INIT(&conf->aspa);
	SIMPLEQ_INIT(&conf->as_sets);
	SIMPLEQ_INIT(&conf->rtrs);

	TAILQ_INIT(conf->filters);
	TAILQ_INIT(conf->listen_addrs);
	LIST_INIT(conf->mrt);

	return (conf);
}

void
copy_config(struct bgpd_config *to, struct bgpd_config *from)
{
	to->flags = from->flags;
	to->log = from->log;
	to->default_tableid = from->default_tableid;
	to->bgpid = from->bgpid;
	to->clusterid = from->clusterid;
	to->as = from->as;
	to->short_as = from->short_as;
	to->holdtime = from->holdtime;
	to->min_holdtime = from->min_holdtime;
	to->connectretry = from->connectretry;
	to->fib_priority = from->fib_priority;
}

void
network_free(struct network *n)
{
	rtlabel_unref(n->net.rtlabel);
	filterset_free(&n->net.attrset);
	free(n);
}

void
free_networks(struct network_head *networks)
{
	struct network		*n;

	while ((n = TAILQ_FIRST(networks)) != NULL) {
		TAILQ_REMOVE(networks, n, entry);
		network_free(n);
	}
}

struct flowspec_config *
flowspec_alloc(uint8_t aid, int len)
{
	struct flowspec_config *conf;
	struct flowspec *flow;

	flow = malloc(FLOWSPEC_SIZE + len);
	if (flow == NULL)
		return NULL;
	memset(flow, 0, FLOWSPEC_SIZE);

	conf = calloc(1, sizeof(*conf));
	if (conf == NULL) {
		free(flow);
		return NULL;
	}

	conf->flow = flow;
	TAILQ_INIT(&conf->attrset);
	flow->len = len;
	flow->aid = aid;

	return conf;
}

void
flowspec_free(struct flowspec_config *f)
{
	filterset_free(&f->attrset);
	free(f->flow);
	free(f);
}

void
free_flowspecs(struct flowspec_tree *flowspecs)
{
	struct flowspec_config *f, *nf;

	RB_FOREACH_SAFE(f, flowspec_tree, flowspecs, nf) {
		RB_REMOVE(flowspec_tree, flowspecs, f);
		flowspec_free(f);
	}
}

void
free_l3vpns(struct l3vpn_head *l3vpns)
{
	struct l3vpn		*vpn;

	while ((vpn = SIMPLEQ_FIRST(l3vpns)) != NULL) {
		SIMPLEQ_REMOVE_HEAD(l3vpns, entry);
		filterset_free(&vpn->export);
		filterset_free(&vpn->import);
		free_networks(&vpn->net_l);
		free(vpn);
	}
}

void
free_prefixsets(struct prefixset_head *psh)
{
	struct prefixset	*ps;

	while (!SIMPLEQ_EMPTY(psh)) {
		ps = SIMPLEQ_FIRST(psh);
		free_roatree(&ps->roaitems);
		free_prefixtree(&ps->psitems);
		SIMPLEQ_REMOVE_HEAD(psh, entry);
		free(ps);
	}
}

void
free_rde_prefixsets(struct rde_prefixset_head *psh)
{
	struct rde_prefixset	*ps;

	if (psh == NULL)
		return;

	while (!SIMPLEQ_EMPTY(psh)) {
		ps = SIMPLEQ_FIRST(psh);
		trie_free(&ps->th);
		SIMPLEQ_REMOVE_HEAD(psh, entry);
		free(ps);
	}
}

void
free_prefixtree(struct prefixset_tree *p)
{
	struct prefixset_item	*psi, *npsi;

	RB_FOREACH_SAFE(psi, prefixset_tree, p, npsi) {
		RB_REMOVE(prefixset_tree, p, psi);
		free(psi);
	}
}

void
free_roatree(struct roa_tree *r)
{
	struct roa	*roa, *nroa;

	RB_FOREACH_SAFE(roa, roa_tree, r, nroa) {
		RB_REMOVE(roa_tree, r, roa);
		free(roa);
	}
}

void
free_aspa(struct aspa_set *aspa)
{
	if (aspa == NULL)
		return;
	free(aspa->tas);
	free(aspa);
}

void
free_aspatree(struct aspa_tree *a)
{
	struct aspa_set	*aspa, *naspa;

	RB_FOREACH_SAFE(aspa, aspa_tree, a, naspa) {
		RB_REMOVE(aspa_tree, a, aspa);
		free_aspa(aspa);
	}
}

void
free_rtrs(struct rtr_config_head *rh)
{
	struct rtr_config	*r;

	while (!SIMPLEQ_EMPTY(rh)) {
		r = SIMPLEQ_FIRST(rh);
		SIMPLEQ_REMOVE_HEAD(rh, entry);
		free(r);
	}
}

void
free_config(struct bgpd_config *conf)
{
	struct peer		*p, *next;
	struct listen_addr	*la;
	struct mrt		*m;

	free_l3vpns(&conf->l3vpns);
	free_networks(&conf->networks);
	free_flowspecs(&conf->flowspecs);
	filterlist_free(conf->filters);
	free_prefixsets(&conf->prefixsets);
	free_prefixsets(&conf->originsets);
	free_rde_prefixsets(&conf->rde_prefixsets);
	free_rde_prefixsets(&conf->rde_originsets);
	as_sets_free(&conf->as_sets);
	free_roatree(&conf->roa);
	free_aspatree(&conf->aspa);
	free_rtrs(&conf->rtrs);

	while ((la = TAILQ_FIRST(conf->listen_addrs)) != NULL) {
		TAILQ_REMOVE(conf->listen_addrs, la, entry);
		free(la);
	}
	free(conf->listen_addrs);

	while ((m = LIST_FIRST(conf->mrt)) != NULL) {
		LIST_REMOVE(m, entry);
		free(m);
	}
	free(conf->mrt);

	RB_FOREACH_SAFE(p, peer_head, &conf->peers, next) {
		RB_REMOVE(peer_head, &conf->peers, p);
		free(p);
	}

	free(conf->csock);
	free(conf->rcsock);

	free(conf);
}

void
merge_config(struct bgpd_config *xconf, struct bgpd_config *conf)
{
	struct listen_addr	*nla, *ola, *next;
	struct peer		*p, *np, *nextp;
	struct flowspec_config	*f, *nextf, *xf;

	/*
	 * merge the freshly parsed conf into the running xconf
	 */

	/* adjust FIB priority if changed */
	/* if xconf is uninitialized we get RTP_NONE */
	if (xconf->fib_priority != conf->fib_priority) {
		kr_fib_decouple_all();
		kr_fib_prio_set(conf->fib_priority);
		kr_fib_couple_all();
	}

	/* take over the easy config changes */
	copy_config(xconf, conf);

	/* clear old control sockets and use new */
	free(xconf->csock);
	free(xconf->rcsock);
	xconf->csock = conf->csock;
	xconf->rcsock = conf->rcsock;
	/* set old one to NULL so we don't double free */
	conf->csock = NULL;
	conf->rcsock = NULL;

	/* clear all current filters and take over the new ones */
	filterlist_free(xconf->filters);
	xconf->filters = conf->filters;
	conf->filters = NULL;

	/* merge mrt config */
	mrt_mergeconfig(xconf->mrt, conf->mrt);

	/* switch the roa, first remove the old one */
	free_roatree(&xconf->roa);
	/* then move the RB tree root */
	RB_ROOT(&xconf->roa) = RB_ROOT(&conf->roa);
	RB_ROOT(&conf->roa) = NULL;

	/* switch the aspa, first remove the old one */
	free_aspatree(&xconf->aspa);
	/* then move the RB tree root */
	RB_ROOT(&xconf->aspa) = RB_ROOT(&conf->aspa);
	RB_ROOT(&conf->aspa) = NULL;

	/* switch the rtr_configs, first remove the old ones */
	free_rtrs(&xconf->rtrs);
	SIMPLEQ_CONCAT(&xconf->rtrs, &conf->rtrs);

	/* switch the prefixsets, first remove the old ones */
	free_prefixsets(&xconf->prefixsets);
	SIMPLEQ_CONCAT(&xconf->prefixsets, &conf->prefixsets);

	/* switch the originsets, first remove the old ones */
	free_prefixsets(&xconf->originsets);
	SIMPLEQ_CONCAT(&xconf->originsets, &conf->originsets);

	/* switch the as_sets, first remove the old ones */
	as_sets_free(&xconf->as_sets);
	SIMPLEQ_CONCAT(&xconf->as_sets, &conf->as_sets);

	/* switch the network statements, but first remove the old ones */
	free_networks(&xconf->networks);
	TAILQ_CONCAT(&xconf->networks, &conf->networks, entry);

	/*
	 * Merge the flowspec statements. Mark the old ones for deletion
	 * which happens when the flowspec is sent to the RDE.
	 */
	RB_FOREACH(f, flowspec_tree, &xconf->flowspecs)
		f->reconf_action = RECONF_DELETE;

	RB_FOREACH_SAFE(f, flowspec_tree, &conf->flowspecs, nextf) {
		RB_REMOVE(flowspec_tree, &conf->flowspecs, f);

		xf = RB_INSERT(flowspec_tree, &xconf->flowspecs, f);
		if (xf != NULL) {
			filterset_free(&xf->attrset);
			filterset_move(&f->attrset, &xf->attrset);
			flowspec_free(f);
			xf->reconf_action = RECONF_KEEP;
		} else
			f->reconf_action = RECONF_KEEP;
	}

	/* switch the l3vpn configs, first remove the old ones */
	free_l3vpns(&xconf->l3vpns);
	SIMPLEQ_CONCAT(&xconf->l3vpns, &conf->l3vpns);

	/*
	 * merge new listeners:
	 * -flag all existing ones as to be deleted
	 * -those that are in both new and old: flag to keep
	 * -new ones get inserted and flagged as to reinit
	 * -remove all that are still flagged for deletion
	 */

	TAILQ_FOREACH(nla, xconf->listen_addrs, entry)
		nla->reconf = RECONF_DELETE;

	/* no new listeners? preserve default ones */
	if (TAILQ_EMPTY(conf->listen_addrs))
		TAILQ_FOREACH(ola, xconf->listen_addrs, entry)
			if (ola->flags & DEFAULT_LISTENER)
				ola->reconf = RECONF_KEEP;
	/* else loop over listeners and merge configs */
	for (nla = TAILQ_FIRST(conf->listen_addrs); nla != NULL; nla = next) {
		next = TAILQ_NEXT(nla, entry);

		TAILQ_FOREACH(ola, xconf->listen_addrs, entry)
			if (!memcmp(&nla->sa, &ola->sa, sizeof(nla->sa)))
				break;

		if (ola == NULL) {
			/* new listener, copy over */
			TAILQ_REMOVE(conf->listen_addrs, nla, entry);
			TAILQ_INSERT_TAIL(xconf->listen_addrs, nla, entry);
			nla->reconf = RECONF_REINIT;
		} else		/* exists, just flag */
			ola->reconf = RECONF_KEEP;
	}
	/* finally clean up the original list and remove all stale entries */
	for (nla = TAILQ_FIRST(xconf->listen_addrs); nla != NULL; nla = next) {
		next = TAILQ_NEXT(nla, entry);
		if (nla->reconf == RECONF_DELETE) {
			TAILQ_REMOVE(xconf->listen_addrs, nla, entry);
			free(nla);
		}
	}

	/*
	 * merge peers:
	 * - need to know which peers are new, replaced and removed
	 * - walk over old peers and check if there is a corresponding new
	 *   peer if so mark it RECONF_KEEP. Remove all old peers.
	 * - swap lists (old peer list is actually empty).
	 */
	RB_FOREACH_SAFE(p, peer_head, &xconf->peers, nextp) {
		np = getpeerbyid(conf, p->conf.id);
		if (np != NULL) {
			np->reconf_action = RECONF_KEEP;
			/* copy the auth state since parent uses it */
			np->auth = p->auth;
		} else {
			/* peer no longer exists, clear pfkey state */
			pfkey_remove(p);
		}

		RB_REMOVE(peer_head, &xconf->peers, p);
		free(p);
	}
	RB_FOREACH_SAFE(np, peer_head, &conf->peers, nextp) {
		RB_REMOVE(peer_head, &conf->peers, np);
		if (RB_INSERT(peer_head, &xconf->peers, np) != NULL)
			fatalx("%s: peer tree is corrupt", __func__);
	}

	/* conf is merged so free it */
	free_config(conf);
}

uint32_t
get_bgpid(void)
{
	struct ifaddrs		*ifap, *ifa;
	uint32_t		 ip = 0, cur, localnet;

	localnet = htonl(INADDR_LOOPBACK & IN_CLASSA_NET);

	if (getifaddrs(&ifap) == -1)
		fatal("getifaddrs");

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL ||
		    ifa->ifa_addr->sa_family != AF_INET)
			continue;
		cur = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
		if ((cur & localnet) == localnet)	/* skip 127/8 */
			continue;
		if (ntohl(cur) > ntohl(ip))
			ip = cur;
	}
	freeifaddrs(ifap);

	return (ip);
}

int
host(const char *s, struct bgpd_addr *h, uint8_t *len)
{
	int			 mask = 128;
	char			*p, *ps;
	const char		*errstr;

	if ((ps = strdup(s)) == NULL)
		fatal("%s: strdup", __func__);

	if ((p = strrchr(ps, '/')) != NULL) {
		mask = strtonum(p+1, 0, 128, &errstr);
		if (errstr) {
			log_warnx("prefixlen is %s: %s", errstr, p);
			free(ps);
			return (0);
		}
		p[0] = '\0';
	}

	memset(h, 0, sizeof(*h));

	if (host_ip(ps, h, len) == 0) {
		free(ps);
		return (0);
	}

	if (p != NULL)
		*len = mask;

	free(ps);
	return (1);
}

int
host_ip(const char *s, struct bgpd_addr *h, uint8_t *len)
{
	struct addrinfo		 hints, *res;
	int			 bits;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM; /*dummy*/
	hints.ai_flags = AI_NUMERICHOST;
	if (getaddrinfo(s, NULL, &hints, &res) == 0) {
		*len = res->ai_family == AF_INET6 ? 128 : 32;
		sa2addr(res->ai_addr, h, NULL);
		freeaddrinfo(res);
	} else {	/* ie. for 10/8 parsing */
		if ((bits = inet_net_pton(AF_INET, s, &h->v4,
		    sizeof(h->v4))) == -1)
			return (0);
		*len = bits;
		h->aid = AID_INET;
	}

	return (1);
}

int
prepare_listeners(struct bgpd_config *conf)
{
	struct listen_addr	*la, *next;
	int			 opt = 1;
	int			 r = 0;

	for (la = TAILQ_FIRST(conf->listen_addrs); la != NULL; la = next) {
		next = TAILQ_NEXT(la, entry);
		if (la->reconf != RECONF_REINIT)
			continue;

		if ((la->fd = socket(la->sa.ss_family,
		    SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
		    IPPROTO_TCP)) == -1) {
			if (la->flags & DEFAULT_LISTENER && (errno ==
			    EAFNOSUPPORT || errno == EPROTONOSUPPORT)) {
				TAILQ_REMOVE(conf->listen_addrs, la, entry);
				free(la);
				continue;
			} else
				fatal("socket");
		}

		opt = 1;
		if (setsockopt(la->fd, SOL_SOCKET, SO_REUSEADDR,
		    &opt, sizeof(opt)) == -1)
			fatal("setsockopt SO_REUSEADDR");

		if (bind(la->fd, (struct sockaddr *)&la->sa, la->sa_len) ==
		    -1) {
			switch (la->sa.ss_family) {
			case AF_INET:
				log_warn("cannot bind to %s:%u",
				    log_sockaddr((struct sockaddr *)&la->sa,
				    la->sa_len), ntohs(((struct sockaddr_in *)
				    &la->sa)->sin_port));
				break;
			case AF_INET6:
				log_warn("cannot bind to [%s]:%u",
				    log_sockaddr((struct sockaddr *)&la->sa,
				    la->sa_len), ntohs(((struct sockaddr_in6 *)
				    &la->sa)->sin6_port));
				break;
			default:
				log_warn("cannot bind to %s",
				    log_sockaddr((struct sockaddr *)&la->sa,
				    la->sa_len));
				break;
			}
			close(la->fd);
			TAILQ_REMOVE(conf->listen_addrs, la, entry);
			free(la);
			r = -1;
			continue;
		}
	}

	return (r);
}

void
expand_networks(struct bgpd_config *c, struct network_head *nw)
{
	struct network		*n, *m, *tmp;
	struct prefixset	*ps;
	struct prefixset_item	*psi;

	TAILQ_FOREACH_SAFE(n, nw, entry, tmp) {
		if (n->net.type == NETWORK_PREFIXSET) {
			TAILQ_REMOVE(nw, n, entry);
			if ((ps = find_prefixset(n->net.psname, &c->prefixsets))
			    == NULL)
				fatal("%s: prefixset %s not found", __func__,
				    n->net.psname);
			RB_FOREACH(psi, prefixset_tree, &ps->psitems) {
				if ((m = calloc(1, sizeof(struct network)))
				    == NULL)
					fatal(NULL);
				memcpy(&m->net.prefix, &psi->p.addr,
				    sizeof(m->net.prefix));
				m->net.prefixlen = psi->p.len;
				filterset_copy(&n->net.attrset,
				    &m->net.attrset);
				TAILQ_INSERT_TAIL(nw, m, entry);
			}
			network_free(n);
		}
	}
}

static inline int
prefixset_cmp(struct prefixset_item *a, struct prefixset_item *b)
{
	int i;

	if (a->p.addr.aid < b->p.addr.aid)
		return (-1);
	if (a->p.addr.aid > b->p.addr.aid)
		return (1);

	switch (a->p.addr.aid) {
	case AID_INET:
		i = memcmp(&a->p.addr.v4, &b->p.addr.v4,
		    sizeof(struct in_addr));
		break;
	case AID_INET6:
		i = memcmp(&a->p.addr.v6, &b->p.addr.v6,
		    sizeof(struct in6_addr));
		break;
	default:
		fatalx("%s: unknown af", __func__);
	}
	if (i > 0)
		return (1);
	if (i < 0)
		return (-1);
	if (a->p.len < b->p.len)
		return (-1);
	if (a->p.len > b->p.len)
		return (1);
	if (a->p.len_min < b->p.len_min)
		return (-1);
	if (a->p.len_min > b->p.len_min)
		return (1);
	if (a->p.len_max < b->p.len_max)
		return (-1);
	if (a->p.len_max > b->p.len_max)
		return (1);
	return (0);
}

RB_GENERATE(prefixset_tree, prefixset_item, entry, prefixset_cmp);

static inline int
roa_cmp(struct roa *a, struct roa *b)
{
	int i;

	if (a->aid < b->aid)
		return (-1);
	if (a->aid > b->aid)
		return (1);

	switch (a->aid) {
	case AID_INET:
		i = memcmp(&a->prefix.inet, &b->prefix.inet,
		    sizeof(struct in_addr));
		break;
	case AID_INET6:
		i = memcmp(&a->prefix.inet6, &b->prefix.inet6,
		    sizeof(struct in6_addr));
		break;
	default:
		fatalx("%s: unknown af", __func__);
	}
	if (i > 0)
		return (1);
	if (i < 0)
		return (-1);
	if (a->prefixlen < b->prefixlen)
		return (-1);
	if (a->prefixlen > b->prefixlen)
		return (1);

	if (a->asnum < b->asnum)
		return (-1);
	if (a->asnum > b->asnum)
		return (1);

	if (a->maxlen < b->maxlen)
		return (-1);
	if (a->maxlen > b->maxlen)
		return (1);

	return (0);
}

RB_GENERATE(roa_tree, roa, entry, roa_cmp);

static inline int
aspa_cmp(struct aspa_set *a, struct aspa_set *b)
{
	if (a->as < b->as)
		return (-1);
	if (a->as > b->as)
		return (1);
	return (0);
}

RB_GENERATE(aspa_tree, aspa_set, entry, aspa_cmp);

static inline int
flowspec_config_cmp(struct flowspec_config *a, struct flowspec_config *b)
{
	if (a->flow->aid < b->flow->aid)
		return -1;
	if (a->flow->aid > b->flow->aid)
		return 1;

	return flowspec_cmp(a->flow->data, a->flow->len,
	    b->flow->data, b->flow->len, a->flow->aid == AID_FLOWSPECv6);
}

RB_GENERATE(flowspec_tree, flowspec_config, entry, flowspec_config_cmp);