1/*	$OpenBSD: rtr.c,v 1.21 2024/04/09 12:05:07 claudio Exp $ */
2
3/*
4 * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#include <sys/tree.h>
19#include <errno.h>
20#include <poll.h>
21#include <pwd.h>
22#include <signal.h>
23#include <stddef.h>
24#include <stdint.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <syslog.h>
29#include <unistd.h>
30
31#include "bgpd.h"
32#include "session.h"
33#include "log.h"
34
35static void	rtr_dispatch_imsg_parent(struct imsgbuf *);
36static void	rtr_dispatch_imsg_rde(struct imsgbuf *);
37
38volatile sig_atomic_t		 rtr_quit;
39static struct imsgbuf		*ibuf_main;
40static struct imsgbuf		*ibuf_rde;
41static struct bgpd_config	*conf, *nconf;
42static struct timer_head	 expire_timer;
43static int			 rtr_recalc_semaphore;
44
45static void
46rtr_sighdlr(int sig)
47{
48	switch (sig) {
49	case SIGINT:
50	case SIGTERM:
51		rtr_quit = 1;
52		break;
53	}
54}
55
56#define PFD_PIPE_MAIN	0
57#define PFD_PIPE_RDE	1
58#define PFD_PIPE_COUNT	2
59
60#define EXPIRE_TIMEOUT	300
61
62void
63rtr_sem_acquire(int cnt)
64{
65	rtr_recalc_semaphore += cnt;
66}
67
68void
69rtr_sem_release(int cnt)
70{
71	rtr_recalc_semaphore -= cnt;
72	if (rtr_recalc_semaphore < 0)
73		fatalx("rtr recalc semaphore underflow");
74}
75
76/*
77 * Every EXPIRE_TIMEOUT seconds traverse the static roa-set table and expire
78 * all elements where the expires timestamp is smaller or equal to now.
79 * If any change is done recalculate the RTR table.
80 */
81static unsigned int
82rtr_expire_roas(time_t now)
83{
84	struct roa *roa, *nr;
85	unsigned int recalc = 0;
86
87	RB_FOREACH_SAFE(roa, roa_tree, &conf->roa, nr) {
88		if (roa->expires != 0 && roa->expires <= now) {
89			recalc++;
90			RB_REMOVE(roa_tree, &conf->roa, roa);
91			free(roa);
92		}
93	}
94	if (recalc != 0)
95		log_info("%u roa-set entries expired", recalc);
96	return recalc;
97}
98
99static unsigned int
100rtr_expire_aspa(time_t now)
101{
102	struct aspa_set *aspa, *na;
103	unsigned int recalc = 0;
104
105	RB_FOREACH_SAFE(aspa, aspa_tree, &conf->aspa, na) {
106		if (aspa->expires != 0 && aspa->expires <= now) {
107			recalc++;
108			RB_REMOVE(aspa_tree, &conf->aspa, aspa);
109			free_aspa(aspa);
110		}
111	}
112	if (recalc != 0)
113		log_info("%u aspa-set entries expired", recalc);
114	return recalc;
115}
116
117void
118rtr_roa_insert(struct roa_tree *rt, struct roa *in)
119{
120	struct roa *roa;
121
122	if ((roa = malloc(sizeof(*roa))) == NULL)
123		fatal("roa alloc");
124	memcpy(roa, in, sizeof(*roa));
125	if (RB_INSERT(roa_tree, rt, roa) != NULL)
126		/* just ignore duplicates */
127		free(roa);
128}
129
130/*
131 * Add an asnum to the aspa_set. The aspa_set is sorted by asnum.
132 */
133static void
134aspa_set_entry(struct aspa_set *aspa, uint32_t asnum)
135{
136	uint32_t i, num, *newtas;
137
138	for (i = 0; i < aspa->num; i++) {
139		if (asnum < aspa->tas[i])
140			break;
141		if (asnum == aspa->tas[i])
142			return;
143	}
144
145	num = aspa->num + 1;
146	newtas = recallocarray(aspa->tas, aspa->num, num, sizeof(uint32_t));
147	if (newtas == NULL)
148		fatal("aspa_set merge");
149
150	if (i < aspa->num) {
151		memmove(newtas + i + 1, newtas + i,
152		    (aspa->num - i) * sizeof(uint32_t));
153	}
154	newtas[i] = asnum;
155
156	aspa->num = num;
157	aspa->tas = newtas;
158}
159
160/*
161 * Insert and merge an aspa_set into the aspa_tree at.
162 */
163void
164rtr_aspa_insert(struct aspa_tree *at, struct aspa_set *mergeset)
165{
166	struct aspa_set *aspa, needle = { .as = mergeset->as };
167	uint32_t i;
168
169	aspa = RB_FIND(aspa_tree, at, &needle);
170	if (aspa == NULL) {
171		if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
172			fatal("aspa insert");
173		aspa->as = mergeset->as;
174		RB_INSERT(aspa_tree, at, aspa);
175	}
176
177	for (i = 0; i < mergeset->num; i++)
178		aspa_set_entry(aspa, mergeset->tas[i]);
179}
180
181void
182rtr_main(int debug, int verbose)
183{
184	struct passwd		*pw;
185	struct pollfd		*pfd = NULL;
186	void			*newp;
187	size_t			 pfd_elms = 0, i;
188	time_t			 timeout;
189
190	log_init(debug, LOG_DAEMON);
191	log_setverbose(verbose);
192
193	log_procinit(log_procnames[PROC_RTR]);
194
195	if ((pw = getpwnam(BGPD_USER)) == NULL)
196		fatal("getpwnam");
197
198	if (chroot(pw->pw_dir) == -1)
199		fatal("chroot");
200	if (chdir("/") == -1)
201		fatal("chdir(\"/\")");
202
203	setproctitle("rtr engine");
204
205	if (setgroups(1, &pw->pw_gid) ||
206	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
207	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
208		fatal("can't drop privileges");
209
210	if (pledge("stdio recvfd", NULL) == -1)
211		fatal("pledge");
212
213	signal(SIGTERM, rtr_sighdlr);
214	signal(SIGINT, rtr_sighdlr);
215	signal(SIGPIPE, SIG_IGN);
216	signal(SIGHUP, SIG_IGN);
217	signal(SIGALRM, SIG_IGN);
218	signal(SIGUSR1, SIG_IGN);
219
220	if ((ibuf_main = malloc(sizeof(struct imsgbuf))) == NULL)
221		fatal(NULL);
222	imsg_init(ibuf_main, 3);
223
224	conf = new_config();
225	log_info("rtr engine ready");
226
227	TAILQ_INIT(&expire_timer);
228	timer_set(&expire_timer, Timer_Rtr_Expire, EXPIRE_TIMEOUT);
229
230	while (rtr_quit == 0) {
231		i = rtr_count();
232		if (pfd_elms < PFD_PIPE_COUNT + i) {
233			if ((newp = reallocarray(pfd,
234			    PFD_PIPE_COUNT + i,
235			    sizeof(struct pollfd))) == NULL)
236				fatal("realloc pollfd");
237			pfd = newp;
238			pfd_elms = PFD_PIPE_COUNT + i;
239		}
240
241		/* run the expire timeout every EXPIRE_TIMEOUT seconds */
242		timeout = timer_nextduein(&expire_timer, getmonotime());
243		if (timeout == -1)
244			fatalx("roa-set expire timer no longer running");
245
246		memset(pfd, 0, sizeof(struct pollfd) * pfd_elms);
247
248		set_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main);
249		set_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde);
250
251		i = PFD_PIPE_COUNT;
252		i += rtr_poll_events(pfd + i, pfd_elms - i, &timeout);
253
254		if (poll(pfd, i, timeout * 1000) == -1) {
255			if (errno == EINTR)
256				continue;
257			fatal("poll error");
258		}
259
260		if (handle_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main) == -1)
261			fatalx("Lost connection to parent");
262		else
263			rtr_dispatch_imsg_parent(ibuf_main);
264
265		if (handle_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde) == -1) {
266			log_warnx("RTR: Lost connection to RDE");
267			msgbuf_clear(&ibuf_rde->w);
268			free(ibuf_rde);
269			ibuf_rde = NULL;
270		} else
271			rtr_dispatch_imsg_rde(ibuf_rde);
272
273		i = PFD_PIPE_COUNT;
274		rtr_check_events(pfd + i, pfd_elms - i);
275
276		if (timer_nextisdue(&expire_timer, getmonotime()) != NULL) {
277			timer_set(&expire_timer, Timer_Rtr_Expire,
278			    EXPIRE_TIMEOUT);
279			if (rtr_expire_roas(time(NULL)) != 0)
280				rtr_recalc();
281			if (rtr_expire_aspa(time(NULL)) != 0)
282				rtr_recalc();
283		}
284	}
285
286	rtr_shutdown();
287
288	free_config(conf);
289	free(pfd);
290
291	/* close pipes */
292	if (ibuf_rde) {
293		msgbuf_clear(&ibuf_rde->w);
294		close(ibuf_rde->fd);
295		free(ibuf_rde);
296	}
297	msgbuf_clear(&ibuf_main->w);
298	close(ibuf_main->fd);
299	free(ibuf_main);
300
301	log_info("rtr engine exiting");
302	exit(0);
303}
304
305static void
306rtr_dispatch_imsg_parent(struct imsgbuf *imsgbuf)
307{
308	static struct aspa_set	*aspa;
309	struct imsg		 imsg;
310	struct bgpd_config	 tconf;
311	struct roa		 roa;
312	char			 descr[PEER_DESCR_LEN];
313	struct rtr_session	*rs;
314	uint32_t		 rtrid;
315	int			 n, fd;
316
317	while (imsgbuf) {
318		if ((n = imsg_get(imsgbuf, &imsg)) == -1)
319			fatal("%s: imsg_get error", __func__);
320		if (n == 0)
321			break;
322
323		rtrid = imsg_get_id(&imsg);
324		switch (imsg_get_type(&imsg)) {
325		case IMSG_SOCKET_CONN_RTR:
326			if ((fd = imsg_get_fd(&imsg)) == -1) {
327				log_warnx("expected to receive imsg fd "
328				    "but didn't receive any");
329				break;
330			}
331			if (ibuf_rde) {
332				log_warnx("Unexpected imsg ctl "
333				    "connection to RDE received");
334				msgbuf_clear(&ibuf_rde->w);
335				free(ibuf_rde);
336			}
337			if ((ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL)
338				fatal(NULL);
339			imsg_init(ibuf_rde, fd);
340			break;
341		case IMSG_SOCKET_CONN:
342			if ((fd = imsg_get_fd(&imsg)) == -1) {
343				log_warnx("expected to receive imsg fd "
344				    "but didn't receive any");
345				break;
346			}
347			if ((rs = rtr_get(rtrid)) == NULL) {
348				log_warnx("IMSG_SOCKET_CONN: unknown rtr id %d",
349				    rtrid);
350				close(fd);
351				break;
352			}
353			rtr_open(rs, fd);
354			break;
355		case IMSG_RECONF_CONF:
356			if (imsg_get_data(&imsg, &tconf, sizeof(tconf)) == -1)
357				fatal("imsg_get_data");
358
359			nconf = new_config();
360			copy_config(nconf, &tconf);
361			rtr_config_prep();
362			break;
363		case IMSG_RECONF_ROA_ITEM:
364			if (imsg_get_data(&imsg, &roa, sizeof(roa)) == -1)
365				fatal("imsg_get_data");
366			rtr_roa_insert(&nconf->roa, &roa);
367			break;
368		case IMSG_RECONF_ASPA:
369			if (aspa != NULL)
370				fatalx("unexpected IMSG_RECONF_ASPA");
371			if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
372				fatal("aspa alloc");
373			if (imsg_get_data(&imsg, aspa,
374			    offsetof(struct aspa_set, tas)) == -1)
375				fatal("imsg_get_data");
376			break;
377		case IMSG_RECONF_ASPA_TAS:
378			if (aspa == NULL)
379				fatalx("unexpected IMSG_RECONF_ASPA_TAS");
380			aspa->tas = reallocarray(NULL, aspa->num,
381			    sizeof(*aspa->tas));
382			if (aspa->tas == NULL)
383				fatal("aspa tas alloc");
384			if (imsg_get_data(&imsg, aspa->tas,
385			    aspa->num * sizeof(*aspa->tas)) == -1)
386				fatal("imsg_get_data");
387			break;
388		case IMSG_RECONF_ASPA_DONE:
389			if (aspa == NULL)
390				fatalx("unexpected IMSG_RECONF_ASPA_DONE");
391			if (RB_INSERT(aspa_tree, &nconf->aspa, aspa) != NULL) {
392				log_warnx("duplicate ASPA set received");
393				free_aspa(aspa);
394			}
395			aspa = NULL;
396			break;
397		case IMSG_RECONF_RTR_CONFIG:
398			if (imsg_get_data(&imsg, descr, sizeof(descr)) == -1)
399				fatal("imsg_get_data");
400			rs = rtr_get(rtrid);
401			if (rs == NULL)
402				rtr_new(rtrid, descr);
403			else
404				rtr_config_keep(rs);
405			break;
406		case IMSG_RECONF_DRAIN:
407			imsg_compose(ibuf_main, IMSG_RECONF_DRAIN, 0, 0,
408			    -1, NULL, 0);
409			break;
410		case IMSG_RECONF_DONE:
411			if (nconf == NULL)
412				fatalx("got IMSG_RECONF_DONE but no config");
413			copy_config(conf, nconf);
414			/* switch the roa, first remove the old one */
415			free_roatree(&conf->roa);
416			/* then move the RB tree root */
417			RB_ROOT(&conf->roa) = RB_ROOT(&nconf->roa);
418			RB_ROOT(&nconf->roa) = NULL;
419			/* switch the aspa tree, first remove the old one */
420			free_aspatree(&conf->aspa);
421			/* then move the RB tree root */
422			RB_ROOT(&conf->aspa) = RB_ROOT(&nconf->aspa);
423			RB_ROOT(&nconf->aspa) = NULL;
424			/* finally merge the rtr session */
425			rtr_config_merge();
426			rtr_expire_roas(time(NULL));
427			rtr_expire_aspa(time(NULL));
428			rtr_recalc();
429			log_info("RTR engine reconfigured");
430			imsg_compose(ibuf_main, IMSG_RECONF_DONE, 0, 0,
431			    -1, NULL, 0);
432			free_config(nconf);
433			nconf = NULL;
434			break;
435		case IMSG_CTL_SHOW_RTR:
436			if ((rs = rtr_get(rtrid)) == NULL) {
437				log_warnx("IMSG_CTL_SHOW_RTR: "
438				    "unknown rtr id %d", rtrid);
439				break;
440			}
441			rtr_show(rs, imsg_get_pid(&imsg));
442			break;
443		case IMSG_CTL_END:
444			imsg_compose(ibuf_main, IMSG_CTL_END, 0,
445			    imsg_get_pid(&imsg), -1, NULL, 0);
446			break;
447		}
448		imsg_free(&imsg);
449	}
450}
451
452static void
453rtr_dispatch_imsg_rde(struct imsgbuf *imsgbuf)
454{
455	struct imsg	imsg;
456	int		n;
457
458	while (imsgbuf) {
459		if ((n = imsg_get(imsgbuf, &imsg)) == -1)
460			fatal("%s: imsg_get error", __func__);
461		if (n == 0)
462			break;
463
464		/* NOTHING */
465
466		imsg_free(&imsg);
467	}
468}
469
470void
471rtr_imsg_compose(int type, uint32_t id, pid_t pid, void *data, size_t datalen)
472{
473	imsg_compose(ibuf_main, type, id, pid, -1, data, datalen);
474}
475
476/*
477 * Compress aspa_set tas_aid into the bitfield used by the RDE.
478 * Returns the size of tas and tas_aid bitfield required for this aspa_set.
479 * At the same time tas_aid is overwritten with the bitmasks or cleared
480 * if no extra aid masks are needed.
481 */
482static size_t
483rtr_aspa_set_size(struct aspa_set *aspa)
484{
485	return aspa->num * sizeof(uint32_t);
486}
487
488/*
489 * Merge all RPKI ROA trees into one as one big union.
490 * Simply try to add all roa entries into a new RB tree.
491 * This could be made a fair bit faster but for now this is good enough.
492 */
493void
494rtr_recalc(void)
495{
496	struct roa_tree rt;
497	struct aspa_tree at;
498	struct roa *roa, *nr;
499	struct aspa_set *aspa;
500	struct aspa_prep ap = { 0 };
501
502	if (rtr_recalc_semaphore > 0)
503		return;
504
505	RB_INIT(&rt);
506	RB_INIT(&at);
507
508	RB_FOREACH(roa, roa_tree, &conf->roa)
509		rtr_roa_insert(&rt, roa);
510	rtr_roa_merge(&rt);
511
512	imsg_compose(ibuf_rde, IMSG_RECONF_ROA_SET, 0, 0, -1, NULL, 0);
513	RB_FOREACH_SAFE(roa, roa_tree, &rt, nr) {
514		imsg_compose(ibuf_rde, IMSG_RECONF_ROA_ITEM, 0, 0, -1,
515		    roa, sizeof(*roa));
516	}
517	free_roatree(&rt);
518
519	RB_FOREACH(aspa, aspa_tree, &conf->aspa)
520		rtr_aspa_insert(&at, aspa);
521	rtr_aspa_merge(&at);
522
523	RB_FOREACH(aspa, aspa_tree, &at) {
524		ap.datasize += rtr_aspa_set_size(aspa);
525		ap.entries++;
526	}
527
528	imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_PREP, 0, 0, -1,
529	    &ap, sizeof(ap));
530
531	/* walk tree in reverse because aspa_add_set requires that */
532	RB_FOREACH_REVERSE(aspa, aspa_tree, &at) {
533		struct aspa_set	as = { .as = aspa->as, .num = aspa->num };
534
535		/* XXX prevent oversized IMSG for now */
536		if (aspa->num * sizeof(*aspa->tas) >
537		    MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
538			log_warnx("oversized ASPA set for customer-as %s, %s",
539			    log_as(aspa->as), "dropped");
540			continue;
541		}
542
543		imsg_compose(ibuf_rde, IMSG_RECONF_ASPA, 0, 0, -1,
544		    &as, offsetof(struct aspa_set, tas));
545		imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_TAS, 0, 0, -1,
546		    aspa->tas, aspa->num * sizeof(*aspa->tas));
547		imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_DONE, 0, 0, -1,
548		    NULL, 0);
549	}
550
551	free_aspatree(&at);
552
553	imsg_compose(ibuf_rde, IMSG_RECONF_DONE, 0, 0, -1, NULL, 0);
554}
555