546 lines
13 KiB
C
546 lines
13 KiB
C
/* $OpenBSD: rtr.c,v 1.20 2024/01/18 09:39:36 claudio Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2020 Claudio Jeker <claudio@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/tree.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#include "bgpd.h"
|
|
#include "session.h"
|
|
#include "log.h"
|
|
|
|
static void rtr_dispatch_imsg_parent(struct imsgbuf *);
|
|
static void rtr_dispatch_imsg_rde(struct imsgbuf *);
|
|
|
|
volatile sig_atomic_t rtr_quit;
|
|
static struct imsgbuf *ibuf_main;
|
|
static struct imsgbuf *ibuf_rde;
|
|
static struct bgpd_config *conf, *nconf;
|
|
static struct timer_head expire_timer;
|
|
static int rtr_recalc_semaphore;
|
|
|
|
static void
|
|
rtr_sighdlr(int sig)
|
|
{
|
|
switch (sig) {
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
rtr_quit = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define PFD_PIPE_MAIN 0
|
|
#define PFD_PIPE_RDE 1
|
|
#define PFD_PIPE_COUNT 2
|
|
|
|
#define EXPIRE_TIMEOUT 300
|
|
|
|
void
|
|
rtr_sem_acquire(int cnt)
|
|
{
|
|
rtr_recalc_semaphore += cnt;
|
|
}
|
|
|
|
void
|
|
rtr_sem_release(int cnt)
|
|
{
|
|
rtr_recalc_semaphore -= cnt;
|
|
if (rtr_recalc_semaphore < 0)
|
|
fatalx("rtr recalc semaphore underflow");
|
|
}
|
|
|
|
/*
|
|
* Every EXPIRE_TIMEOUT seconds traverse the static roa-set table and expire
|
|
* all elements where the expires timestamp is smaller or equal to now.
|
|
* If any change is done recalculate the RTR table.
|
|
*/
|
|
static unsigned int
|
|
rtr_expire_roas(time_t now)
|
|
{
|
|
struct roa *roa, *nr;
|
|
unsigned int recalc = 0;
|
|
|
|
RB_FOREACH_SAFE(roa, roa_tree, &conf->roa, nr) {
|
|
if (roa->expires != 0 && roa->expires <= now) {
|
|
recalc++;
|
|
RB_REMOVE(roa_tree, &conf->roa, roa);
|
|
free(roa);
|
|
}
|
|
}
|
|
if (recalc != 0)
|
|
log_info("%u roa-set entries expired", recalc);
|
|
return recalc;
|
|
}
|
|
|
|
static unsigned int
|
|
rtr_expire_aspa(time_t now)
|
|
{
|
|
struct aspa_set *aspa, *na;
|
|
unsigned int recalc = 0;
|
|
|
|
RB_FOREACH_SAFE(aspa, aspa_tree, &conf->aspa, na) {
|
|
if (aspa->expires != 0 && aspa->expires <= now) {
|
|
recalc++;
|
|
RB_REMOVE(aspa_tree, &conf->aspa, aspa);
|
|
free_aspa(aspa);
|
|
}
|
|
}
|
|
if (recalc != 0)
|
|
log_info("%u aspa-set entries expired", recalc);
|
|
return recalc;
|
|
}
|
|
|
|
void
|
|
rtr_roa_insert(struct roa_tree *rt, struct roa *in)
|
|
{
|
|
struct roa *roa;
|
|
|
|
if ((roa = malloc(sizeof(*roa))) == NULL)
|
|
fatal("roa alloc");
|
|
memcpy(roa, in, sizeof(*roa));
|
|
if (RB_INSERT(roa_tree, rt, roa) != NULL)
|
|
/* just ignore duplicates */
|
|
free(roa);
|
|
}
|
|
|
|
/*
|
|
* Add an asnum to the aspa_set. The aspa_set is sorted by asnum.
|
|
*/
|
|
static void
|
|
aspa_set_entry(struct aspa_set *aspa, uint32_t asnum)
|
|
{
|
|
uint32_t i, num, *newtas;
|
|
|
|
for (i = 0; i < aspa->num; i++) {
|
|
if (asnum < aspa->tas[i])
|
|
break;
|
|
if (asnum == aspa->tas[i])
|
|
return;
|
|
}
|
|
|
|
num = aspa->num + 1;
|
|
newtas = recallocarray(aspa->tas, aspa->num, num, sizeof(uint32_t));
|
|
if (newtas == NULL)
|
|
fatal("aspa_set merge");
|
|
|
|
if (i < aspa->num) {
|
|
memmove(newtas + i + 1, newtas + i,
|
|
(aspa->num - i) * sizeof(uint32_t));
|
|
}
|
|
newtas[i] = asnum;
|
|
|
|
aspa->num = num;
|
|
aspa->tas = newtas;
|
|
}
|
|
|
|
/*
|
|
* Insert and merge an aspa_set into the aspa_tree at.
|
|
*/
|
|
void
|
|
rtr_aspa_insert(struct aspa_tree *at, struct aspa_set *mergeset)
|
|
{
|
|
struct aspa_set *aspa, needle = { .as = mergeset->as };
|
|
uint32_t i;
|
|
|
|
aspa = RB_FIND(aspa_tree, at, &needle);
|
|
if (aspa == NULL) {
|
|
if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
|
|
fatal("aspa insert");
|
|
aspa->as = mergeset->as;
|
|
RB_INSERT(aspa_tree, at, aspa);
|
|
}
|
|
|
|
for (i = 0; i < mergeset->num; i++)
|
|
aspa_set_entry(aspa, mergeset->tas[i]);
|
|
}
|
|
|
|
void
|
|
rtr_main(int debug, int verbose)
|
|
{
|
|
struct passwd *pw;
|
|
struct pollfd *pfd = NULL;
|
|
void *newp;
|
|
size_t pfd_elms = 0, i;
|
|
time_t timeout;
|
|
|
|
log_init(debug, LOG_DAEMON);
|
|
log_setverbose(verbose);
|
|
|
|
log_procinit(log_procnames[PROC_RTR]);
|
|
|
|
if ((pw = getpwnam(BGPD_USER)) == NULL)
|
|
fatal("getpwnam");
|
|
|
|
if (chroot(pw->pw_dir) == -1)
|
|
fatal("chroot");
|
|
if (chdir("/") == -1)
|
|
fatal("chdir(\"/\")");
|
|
|
|
setproctitle("rtr engine");
|
|
|
|
if (setgroups(1, &pw->pw_gid) ||
|
|
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
|
|
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
|
|
fatal("can't drop privileges");
|
|
|
|
if (pledge("stdio recvfd", NULL) == -1)
|
|
fatal("pledge");
|
|
|
|
signal(SIGTERM, rtr_sighdlr);
|
|
signal(SIGINT, rtr_sighdlr);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGHUP, SIG_IGN);
|
|
signal(SIGALRM, SIG_IGN);
|
|
signal(SIGUSR1, SIG_IGN);
|
|
|
|
if ((ibuf_main = malloc(sizeof(struct imsgbuf))) == NULL)
|
|
fatal(NULL);
|
|
imsg_init(ibuf_main, 3);
|
|
|
|
conf = new_config();
|
|
log_info("rtr engine ready");
|
|
|
|
TAILQ_INIT(&expire_timer);
|
|
timer_set(&expire_timer, Timer_Rtr_Expire, EXPIRE_TIMEOUT);
|
|
|
|
while (rtr_quit == 0) {
|
|
i = rtr_count();
|
|
if (pfd_elms < PFD_PIPE_COUNT + i) {
|
|
if ((newp = reallocarray(pfd,
|
|
PFD_PIPE_COUNT + i,
|
|
sizeof(struct pollfd))) == NULL)
|
|
fatal("realloc pollfd");
|
|
pfd = newp;
|
|
pfd_elms = PFD_PIPE_COUNT + i;
|
|
}
|
|
|
|
/* run the expire timeout every EXPIRE_TIMEOUT seconds */
|
|
timeout = timer_nextduein(&expire_timer, getmonotime());
|
|
if (timeout == -1)
|
|
fatalx("roa-set expire timer no longer running");
|
|
|
|
memset(pfd, 0, sizeof(struct pollfd) * pfd_elms);
|
|
|
|
set_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main);
|
|
set_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde);
|
|
|
|
i = PFD_PIPE_COUNT;
|
|
i += rtr_poll_events(pfd + i, pfd_elms - i, &timeout);
|
|
|
|
if (poll(pfd, i, timeout * 1000) == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
fatal("poll error");
|
|
}
|
|
|
|
if (handle_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main) == -1)
|
|
fatalx("Lost connection to parent");
|
|
else
|
|
rtr_dispatch_imsg_parent(ibuf_main);
|
|
|
|
if (handle_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde) == -1) {
|
|
log_warnx("RTR: Lost connection to RDE");
|
|
msgbuf_clear(&ibuf_rde->w);
|
|
free(ibuf_rde);
|
|
ibuf_rde = NULL;
|
|
} else
|
|
rtr_dispatch_imsg_rde(ibuf_rde);
|
|
|
|
i = PFD_PIPE_COUNT;
|
|
rtr_check_events(pfd + i, pfd_elms - i);
|
|
|
|
if (timer_nextisdue(&expire_timer, getmonotime()) != NULL) {
|
|
timer_set(&expire_timer, Timer_Rtr_Expire,
|
|
EXPIRE_TIMEOUT);
|
|
if (rtr_expire_roas(time(NULL)) != 0)
|
|
rtr_recalc();
|
|
if (rtr_expire_aspa(time(NULL)) != 0)
|
|
rtr_recalc();
|
|
}
|
|
}
|
|
|
|
rtr_shutdown();
|
|
|
|
free_config(conf);
|
|
free(pfd);
|
|
|
|
/* close pipes */
|
|
if (ibuf_rde) {
|
|
msgbuf_clear(&ibuf_rde->w);
|
|
close(ibuf_rde->fd);
|
|
free(ibuf_rde);
|
|
}
|
|
msgbuf_clear(&ibuf_main->w);
|
|
close(ibuf_main->fd);
|
|
free(ibuf_main);
|
|
|
|
log_info("rtr engine exiting");
|
|
exit(0);
|
|
}
|
|
|
|
static void
|
|
rtr_dispatch_imsg_parent(struct imsgbuf *imsgbuf)
|
|
{
|
|
static struct aspa_set *aspa;
|
|
struct imsg imsg;
|
|
struct bgpd_config tconf;
|
|
struct roa roa;
|
|
char descr[PEER_DESCR_LEN];
|
|
struct rtr_session *rs;
|
|
uint32_t rtrid;
|
|
int n, fd;
|
|
|
|
while (imsgbuf) {
|
|
if ((n = imsg_get(imsgbuf, &imsg)) == -1)
|
|
fatal("%s: imsg_get error", __func__);
|
|
if (n == 0)
|
|
break;
|
|
|
|
rtrid = imsg_get_id(&imsg);
|
|
switch (imsg_get_type(&imsg)) {
|
|
case IMSG_SOCKET_CONN_RTR:
|
|
if ((fd = imsg_get_fd(&imsg)) == -1) {
|
|
log_warnx("expected to receive imsg fd "
|
|
"but didn't receive any");
|
|
break;
|
|
}
|
|
if (ibuf_rde) {
|
|
log_warnx("Unexpected imsg ctl "
|
|
"connection to RDE received");
|
|
msgbuf_clear(&ibuf_rde->w);
|
|
free(ibuf_rde);
|
|
}
|
|
if ((ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL)
|
|
fatal(NULL);
|
|
imsg_init(ibuf_rde, fd);
|
|
break;
|
|
case IMSG_SOCKET_CONN:
|
|
if ((fd = imsg_get_fd(&imsg)) == -1) {
|
|
log_warnx("expected to receive imsg fd "
|
|
"but didn't receive any");
|
|
break;
|
|
}
|
|
if ((rs = rtr_get(rtrid)) == NULL) {
|
|
log_warnx("IMSG_SOCKET_CONN: unknown rtr id %d",
|
|
rtrid);
|
|
close(fd);
|
|
break;
|
|
}
|
|
rtr_open(rs, fd);
|
|
break;
|
|
case IMSG_RECONF_CONF:
|
|
if (imsg_get_data(&imsg, &tconf, sizeof(tconf)) == -1)
|
|
fatal("imsg_get_data");
|
|
|
|
nconf = new_config();
|
|
copy_config(nconf, &tconf);
|
|
rtr_config_prep();
|
|
break;
|
|
case IMSG_RECONF_ROA_ITEM:
|
|
if (imsg_get_data(&imsg, &roa, sizeof(roa)) == -1)
|
|
fatal("imsg_get_data");
|
|
rtr_roa_insert(&nconf->roa, &roa);
|
|
break;
|
|
case IMSG_RECONF_ASPA:
|
|
if (aspa != NULL)
|
|
fatalx("unexpected IMSG_RECONF_ASPA");
|
|
if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
|
|
fatal("aspa alloc");
|
|
if (imsg_get_data(&imsg, aspa,
|
|
offsetof(struct aspa_set, tas)) == -1)
|
|
fatal("imsg_get_data");
|
|
break;
|
|
case IMSG_RECONF_ASPA_TAS:
|
|
if (aspa == NULL)
|
|
fatalx("unexpected IMSG_RECONF_ASPA_TAS");
|
|
aspa->tas = reallocarray(NULL, aspa->num,
|
|
sizeof(*aspa->tas));
|
|
if (aspa->tas == NULL)
|
|
fatal("aspa tas alloc");
|
|
if (imsg_get_data(&imsg, aspa->tas,
|
|
aspa->num * sizeof(*aspa->tas)) == -1)
|
|
fatal("imsg_get_data");
|
|
break;
|
|
case IMSG_RECONF_ASPA_DONE:
|
|
if (aspa == NULL)
|
|
fatalx("unexpected IMSG_RECONF_ASPA_DONE");
|
|
if (RB_INSERT(aspa_tree, &nconf->aspa, aspa) != NULL) {
|
|
log_warnx("duplicate ASPA set received");
|
|
free_aspa(aspa);
|
|
}
|
|
aspa = NULL;
|
|
break;
|
|
case IMSG_RECONF_RTR_CONFIG:
|
|
if (imsg_get_data(&imsg, descr, sizeof(descr)) == -1)
|
|
fatal("imsg_get_data");
|
|
rs = rtr_get(rtrid);
|
|
if (rs == NULL)
|
|
rtr_new(rtrid, descr);
|
|
else
|
|
rtr_config_keep(rs);
|
|
break;
|
|
case IMSG_RECONF_DRAIN:
|
|
imsg_compose(ibuf_main, IMSG_RECONF_DRAIN, 0, 0,
|
|
-1, NULL, 0);
|
|
break;
|
|
case IMSG_RECONF_DONE:
|
|
if (nconf == NULL)
|
|
fatalx("got IMSG_RECONF_DONE but no config");
|
|
copy_config(conf, nconf);
|
|
/* switch the roa, first remove the old one */
|
|
free_roatree(&conf->roa);
|
|
/* then move the RB tree root */
|
|
RB_ROOT(&conf->roa) = RB_ROOT(&nconf->roa);
|
|
RB_ROOT(&nconf->roa) = NULL;
|
|
/* switch the aspa tree, first remove the old one */
|
|
free_aspatree(&conf->aspa);
|
|
/* then move the RB tree root */
|
|
RB_ROOT(&conf->aspa) = RB_ROOT(&nconf->aspa);
|
|
RB_ROOT(&nconf->aspa) = NULL;
|
|
/* finally merge the rtr session */
|
|
rtr_config_merge();
|
|
rtr_expire_roas(time(NULL));
|
|
rtr_expire_aspa(time(NULL));
|
|
rtr_recalc();
|
|
log_info("RTR engine reconfigured");
|
|
imsg_compose(ibuf_main, IMSG_RECONF_DONE, 0, 0,
|
|
-1, NULL, 0);
|
|
free_config(nconf);
|
|
nconf = NULL;
|
|
break;
|
|
case IMSG_CTL_SHOW_RTR:
|
|
if ((rs = rtr_get(rtrid)) == NULL) {
|
|
log_warnx("IMSG_CTL_SHOW_RTR: "
|
|
"unknown rtr id %d", rtrid);
|
|
break;
|
|
}
|
|
rtr_show(rs, imsg_get_pid(&imsg));
|
|
break;
|
|
case IMSG_CTL_END:
|
|
imsg_compose(ibuf_main, IMSG_CTL_END, 0,
|
|
imsg_get_pid(&imsg), -1, NULL, 0);
|
|
break;
|
|
}
|
|
imsg_free(&imsg);
|
|
}
|
|
}
|
|
|
|
static void
|
|
rtr_dispatch_imsg_rde(struct imsgbuf *imsgbuf)
|
|
{
|
|
struct imsg imsg;
|
|
int n;
|
|
|
|
while (imsgbuf) {
|
|
if ((n = imsg_get(imsgbuf, &imsg)) == -1)
|
|
fatal("%s: imsg_get error", __func__);
|
|
if (n == 0)
|
|
break;
|
|
|
|
/* NOTHING */
|
|
|
|
imsg_free(&imsg);
|
|
}
|
|
}
|
|
|
|
void
|
|
rtr_imsg_compose(int type, uint32_t id, pid_t pid, void *data, size_t datalen)
|
|
{
|
|
imsg_compose(ibuf_main, type, id, pid, -1, data, datalen);
|
|
}
|
|
|
|
/*
|
|
* Compress aspa_set tas_aid into the bitfield used by the RDE.
|
|
* Returns the size of tas and tas_aid bitfield required for this aspa_set.
|
|
* At the same time tas_aid is overwritten with the bitmasks or cleared
|
|
* if no extra aid masks are needed.
|
|
*/
|
|
static size_t
|
|
rtr_aspa_set_size(struct aspa_set *aspa)
|
|
{
|
|
return aspa->num * sizeof(uint32_t);
|
|
}
|
|
|
|
/*
|
|
* Merge all RPKI ROA trees into one as one big union.
|
|
* Simply try to add all roa entries into a new RB tree.
|
|
* This could be made a fair bit faster but for now this is good enough.
|
|
*/
|
|
void
|
|
rtr_recalc(void)
|
|
{
|
|
struct roa_tree rt;
|
|
struct aspa_tree at;
|
|
struct roa *roa, *nr;
|
|
struct aspa_set *aspa;
|
|
struct aspa_prep ap = { 0 };
|
|
|
|
if (rtr_recalc_semaphore > 0)
|
|
return;
|
|
|
|
RB_INIT(&rt);
|
|
RB_INIT(&at);
|
|
|
|
RB_FOREACH(roa, roa_tree, &conf->roa)
|
|
rtr_roa_insert(&rt, roa);
|
|
rtr_roa_merge(&rt);
|
|
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_ROA_SET, 0, 0, -1, NULL, 0);
|
|
RB_FOREACH_SAFE(roa, roa_tree, &rt, nr) {
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_ROA_ITEM, 0, 0, -1,
|
|
roa, sizeof(*roa));
|
|
}
|
|
free_roatree(&rt);
|
|
|
|
RB_FOREACH(aspa, aspa_tree, &conf->aspa)
|
|
rtr_aspa_insert(&at, aspa);
|
|
rtr_aspa_merge(&at);
|
|
|
|
RB_FOREACH(aspa, aspa_tree, &at) {
|
|
ap.datasize += rtr_aspa_set_size(aspa);
|
|
ap.entries++;
|
|
}
|
|
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_PREP, 0, 0, -1,
|
|
&ap, sizeof(ap));
|
|
|
|
/* walk tree in reverse because aspa_add_set requires that */
|
|
RB_FOREACH_REVERSE(aspa, aspa_tree, &at) {
|
|
struct aspa_set as = { .as = aspa->as, .num = aspa->num };
|
|
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_ASPA, 0, 0, -1,
|
|
&as, offsetof(struct aspa_set, tas));
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_TAS, 0, 0, -1,
|
|
aspa->tas, aspa->num * sizeof(*aspa->tas));
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_DONE, 0, 0, -1,
|
|
NULL, 0);
|
|
}
|
|
|
|
free_aspatree(&at);
|
|
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_DONE, 0, 0, -1, NULL, 0);
|
|
}
|