/* $OpenBSD: config.c,v 1.106 2022/12/28 21:30:15 jmc Exp $ */ /* * Copyright (c) 2003, 2004, 2005 Henning Brauer * * 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 #include #include #include #include #include #include #include #include #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 *); 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); 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); } } 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->tas_aid); 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); 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; /* * 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); /* 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);