src/usr.sbin/bgpd/config.c

753 lines
17 KiB
C

/* $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);