xref: /openbsd/usr.sbin/bgpd/rtr.c (revision aaaf7e1f)
1 /*	$OpenBSD: rtr.c,v 1.29 2024/12/02 15:13:57 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 
35 static void	rtr_dispatch_imsg_parent(struct imsgbuf *);
36 static void	rtr_dispatch_imsg_rde(struct imsgbuf *);
37 
38 volatile sig_atomic_t		 rtr_quit;
39 static struct imsgbuf		*ibuf_main;
40 static struct imsgbuf		*ibuf_rde;
41 static struct bgpd_config	*conf, *nconf;
42 static struct timer_head	 expire_timer;
43 static int			 rtr_recalc_semaphore;
44 
45 static void
rtr_sighdlr(int sig)46 rtr_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 
62 void
rtr_sem_acquire(int cnt)63 rtr_sem_acquire(int cnt)
64 {
65 	rtr_recalc_semaphore += cnt;
66 }
67 
68 void
rtr_sem_release(int cnt)69 rtr_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  */
81 static unsigned int
rtr_expire_roas(time_t now)82 rtr_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 
99 static unsigned int
rtr_expire_aspa(time_t now)100 rtr_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 
117 void
rtr_roa_insert(struct roa_tree * rt,struct roa * in)118 rtr_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  */
133 static void
aspa_set_entry(struct aspa_set * aspa,uint32_t asnum)134 aspa_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 = reallocarray(aspa->tas, 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  */
163 void
rtr_aspa_insert(struct aspa_tree * at,struct aspa_set * mergeset)164 rtr_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 
181 void
rtr_main(int debug,int verbose)182 rtr_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 	if (imsgbuf_init(ibuf_main, 3) == -1 ||
223 	    imsgbuf_set_maxsize(ibuf_main, MAX_BGPD_IMSGSIZE) == -1)
224 		fatal(NULL);
225 	imsgbuf_allow_fdpass(ibuf_main);
226 
227 	conf = new_config();
228 	log_info("rtr engine ready");
229 
230 	TAILQ_INIT(&expire_timer);
231 	timer_set(&expire_timer, Timer_Rtr_Expire, EXPIRE_TIMEOUT);
232 
233 	while (rtr_quit == 0) {
234 		i = rtr_count();
235 		if (pfd_elms < PFD_PIPE_COUNT + i) {
236 			if ((newp = reallocarray(pfd,
237 			    PFD_PIPE_COUNT + i,
238 			    sizeof(struct pollfd))) == NULL)
239 				fatal("realloc pollfd");
240 			pfd = newp;
241 			pfd_elms = PFD_PIPE_COUNT + i;
242 		}
243 
244 		/* run the expire timeout every EXPIRE_TIMEOUT seconds */
245 		timeout = timer_nextduein(&expire_timer, getmonotime());
246 		if (timeout == -1)
247 			fatalx("roa-set expire timer no longer running");
248 
249 		memset(pfd, 0, sizeof(struct pollfd) * pfd_elms);
250 
251 		set_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main);
252 		set_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde);
253 
254 		i = PFD_PIPE_COUNT;
255 		i += rtr_poll_events(pfd + i, pfd_elms - i, &timeout);
256 
257 		if (poll(pfd, i, timeout * 1000) == -1) {
258 			if (errno == EINTR)
259 				continue;
260 			fatal("poll error");
261 		}
262 
263 		if (handle_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main) == -1)
264 			fatalx("Lost connection to parent");
265 		else
266 			rtr_dispatch_imsg_parent(ibuf_main);
267 
268 		if (handle_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde) == -1) {
269 			log_warnx("RTR: Lost connection to RDE");
270 			imsgbuf_clear(ibuf_rde);
271 			free(ibuf_rde);
272 			ibuf_rde = NULL;
273 		} else
274 			rtr_dispatch_imsg_rde(ibuf_rde);
275 
276 		i = PFD_PIPE_COUNT;
277 		rtr_check_events(pfd + i, pfd_elms - i);
278 
279 		if (timer_nextisdue(&expire_timer, getmonotime()) != NULL) {
280 			timer_set(&expire_timer, Timer_Rtr_Expire,
281 			    EXPIRE_TIMEOUT);
282 			if (rtr_expire_roas(time(NULL)) != 0)
283 				rtr_recalc();
284 			if (rtr_expire_aspa(time(NULL)) != 0)
285 				rtr_recalc();
286 		}
287 	}
288 
289 	rtr_shutdown();
290 
291 	free_config(conf);
292 	free(pfd);
293 
294 	/* close pipes */
295 	if (ibuf_rde) {
296 		imsgbuf_clear(ibuf_rde);
297 		close(ibuf_rde->fd);
298 		free(ibuf_rde);
299 	}
300 	imsgbuf_clear(ibuf_main);
301 	close(ibuf_main->fd);
302 	free(ibuf_main);
303 
304 	log_info("rtr engine exiting");
305 	exit(0);
306 }
307 
308 static void
rtr_dispatch_imsg_parent(struct imsgbuf * imsgbuf)309 rtr_dispatch_imsg_parent(struct imsgbuf *imsgbuf)
310 {
311 	static struct aspa_set	*aspa;
312 	struct imsg		 imsg;
313 	struct bgpd_config	 tconf;
314 	struct roa		 roa;
315 	struct rtr_config_msg	 rtrconf;
316 	struct rtr_session	*rs;
317 	uint32_t		 rtrid;
318 	int			 n, fd;
319 
320 	while (imsgbuf) {
321 		if ((n = imsg_get(imsgbuf, &imsg)) == -1)
322 			fatal("%s: imsg_get error", __func__);
323 		if (n == 0)
324 			break;
325 
326 		rtrid = imsg_get_id(&imsg);
327 		switch (imsg_get_type(&imsg)) {
328 		case IMSG_SOCKET_CONN_RTR:
329 			if ((fd = imsg_get_fd(&imsg)) == -1) {
330 				log_warnx("expected to receive imsg fd "
331 				    "but didn't receive any");
332 				break;
333 			}
334 			if (ibuf_rde) {
335 				log_warnx("Unexpected imsg ctl "
336 				    "connection to RDE received");
337 				imsgbuf_clear(ibuf_rde);
338 				free(ibuf_rde);
339 			}
340 			if ((ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL)
341 				fatal(NULL);
342 			if (imsgbuf_init(ibuf_rde, fd) == -1 ||
343 			    imsgbuf_set_maxsize(ibuf_rde, MAX_BGPD_IMSGSIZE) ==
344 			    -1)
345 				fatal(NULL);
346 			break;
347 		case IMSG_SOCKET_SETUP:
348 			if ((fd = imsg_get_fd(&imsg)) == -1) {
349 				log_warnx("expected to receive imsg fd "
350 				    "but didn't receive any");
351 				break;
352 			}
353 			if ((rs = rtr_get(rtrid)) == NULL) {
354 				log_warnx("IMSG_SOCKET_SETUP: "
355 				    "unknown rtr id %d", rtrid);
356 				close(fd);
357 				break;
358 			}
359 			rtr_open(rs, fd);
360 			break;
361 		case IMSG_RECONF_CONF:
362 			if (imsg_get_data(&imsg, &tconf, sizeof(tconf)) == -1)
363 				fatal("imsg_get_data");
364 
365 			nconf = new_config();
366 			copy_config(nconf, &tconf);
367 			rtr_config_prep();
368 			break;
369 		case IMSG_RECONF_ROA_ITEM:
370 			if (imsg_get_data(&imsg, &roa, sizeof(roa)) == -1)
371 				fatal("imsg_get_data");
372 			rtr_roa_insert(&nconf->roa, &roa);
373 			break;
374 		case IMSG_RECONF_ASPA:
375 			if (aspa != NULL)
376 				fatalx("unexpected IMSG_RECONF_ASPA");
377 			if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
378 				fatal("aspa alloc");
379 			if (imsg_get_data(&imsg, aspa,
380 			    offsetof(struct aspa_set, tas)) == -1)
381 				fatal("imsg_get_data");
382 			break;
383 		case IMSG_RECONF_ASPA_TAS:
384 			if (aspa == NULL)
385 				fatalx("unexpected IMSG_RECONF_ASPA_TAS");
386 			aspa->tas = reallocarray(NULL, aspa->num,
387 			    sizeof(*aspa->tas));
388 			if (aspa->tas == NULL)
389 				fatal("aspa tas alloc");
390 			if (imsg_get_data(&imsg, aspa->tas,
391 			    aspa->num * sizeof(*aspa->tas)) == -1)
392 				fatal("imsg_get_data");
393 			break;
394 		case IMSG_RECONF_ASPA_DONE:
395 			if (aspa == NULL)
396 				fatalx("unexpected IMSG_RECONF_ASPA_DONE");
397 			if (RB_INSERT(aspa_tree, &nconf->aspa, aspa) != NULL) {
398 				log_warnx("duplicate ASPA set received");
399 				free_aspa(aspa);
400 			}
401 			aspa = NULL;
402 			break;
403 		case IMSG_RECONF_RTR_CONFIG:
404 			if (imsg_get_data(&imsg, &rtrconf,
405 			    sizeof(rtrconf)) == -1)
406 				fatal("imsg_get_data");
407 			rs = rtr_get(rtrid);
408 			if (rs == NULL)
409 				rtr_new(rtrid, &rtrconf);
410 			else
411 				rtr_config_keep(rs, &rtrconf);
412 			break;
413 		case IMSG_RECONF_DRAIN:
414 			imsg_compose(ibuf_main, IMSG_RECONF_DRAIN, 0, 0,
415 			    -1, NULL, 0);
416 			break;
417 		case IMSG_RECONF_DONE:
418 			if (nconf == NULL)
419 				fatalx("got IMSG_RECONF_DONE but no config");
420 			copy_config(conf, nconf);
421 			/* switch the roa, first remove the old one */
422 			free_roatree(&conf->roa);
423 			/* then move the RB tree root */
424 			RB_ROOT(&conf->roa) = RB_ROOT(&nconf->roa);
425 			RB_ROOT(&nconf->roa) = NULL;
426 			/* switch the aspa tree, first remove the old one */
427 			free_aspatree(&conf->aspa);
428 			/* then move the RB tree root */
429 			RB_ROOT(&conf->aspa) = RB_ROOT(&nconf->aspa);
430 			RB_ROOT(&nconf->aspa) = NULL;
431 			/* finally merge the rtr session */
432 			rtr_config_merge();
433 			rtr_expire_roas(time(NULL));
434 			rtr_expire_aspa(time(NULL));
435 			rtr_recalc();
436 			log_info("RTR engine reconfigured");
437 			imsg_compose(ibuf_main, IMSG_RECONF_DONE, 0, 0,
438 			    -1, NULL, 0);
439 			free_config(nconf);
440 			nconf = NULL;
441 			break;
442 		case IMSG_CTL_SHOW_RTR:
443 			if ((rs = rtr_get(rtrid)) == NULL) {
444 				log_warnx("IMSG_CTL_SHOW_RTR: "
445 				    "unknown rtr id %d", rtrid);
446 				break;
447 			}
448 			rtr_show(rs, imsg_get_pid(&imsg));
449 			break;
450 		case IMSG_CTL_END:
451 			imsg_compose(ibuf_main, IMSG_CTL_END, 0,
452 			    imsg_get_pid(&imsg), -1, NULL, 0);
453 			break;
454 		}
455 		imsg_free(&imsg);
456 	}
457 }
458 
459 static void
rtr_dispatch_imsg_rde(struct imsgbuf * imsgbuf)460 rtr_dispatch_imsg_rde(struct imsgbuf *imsgbuf)
461 {
462 	struct imsg	imsg;
463 	int		n;
464 
465 	while (imsgbuf) {
466 		if ((n = imsg_get(imsgbuf, &imsg)) == -1)
467 			fatal("%s: imsg_get error", __func__);
468 		if (n == 0)
469 			break;
470 
471 		/* NOTHING */
472 
473 		imsg_free(&imsg);
474 	}
475 }
476 
477 void
rtr_imsg_compose(int type,uint32_t id,pid_t pid,void * data,size_t datalen)478 rtr_imsg_compose(int type, uint32_t id, pid_t pid, void *data, size_t datalen)
479 {
480 	imsg_compose(ibuf_main, type, id, pid, -1, data, datalen);
481 }
482 
483 /*
484  * Compress aspa_set tas_aid into the bitfield used by the RDE.
485  * Returns the size of tas and tas_aid bitfield required for this aspa_set.
486  * At the same time tas_aid is overwritten with the bitmasks or cleared
487  * if no extra aid masks are needed.
488  */
489 static size_t
rtr_aspa_set_size(struct aspa_set * aspa)490 rtr_aspa_set_size(struct aspa_set *aspa)
491 {
492 	return aspa->num * sizeof(uint32_t);
493 }
494 
495 /*
496  * Merge all RPKI ROA trees into one as one big union.
497  * Simply try to add all roa entries into a new RB tree.
498  * This could be made a fair bit faster but for now this is good enough.
499  */
500 void
rtr_recalc(void)501 rtr_recalc(void)
502 {
503 	struct roa_tree rt;
504 	struct aspa_tree at;
505 	struct roa *roa, *nr;
506 	struct aspa_set *aspa;
507 	struct aspa_prep ap = { 0 };
508 
509 	if (rtr_recalc_semaphore > 0)
510 		return;
511 
512 	RB_INIT(&rt);
513 	RB_INIT(&at);
514 
515 	RB_FOREACH(roa, roa_tree, &conf->roa)
516 		rtr_roa_insert(&rt, roa);
517 	rtr_roa_merge(&rt);
518 
519 	imsg_compose(ibuf_rde, IMSG_RECONF_ROA_SET, 0, 0, -1, NULL, 0);
520 	RB_FOREACH_SAFE(roa, roa_tree, &rt, nr) {
521 		imsg_compose(ibuf_rde, IMSG_RECONF_ROA_ITEM, 0, 0, -1,
522 		    roa, sizeof(*roa));
523 	}
524 	free_roatree(&rt);
525 
526 	RB_FOREACH(aspa, aspa_tree, &conf->aspa)
527 		rtr_aspa_insert(&at, aspa);
528 	rtr_aspa_merge(&at);
529 
530 	RB_FOREACH(aspa, aspa_tree, &at) {
531 		ap.datasize += rtr_aspa_set_size(aspa);
532 		ap.entries++;
533 	}
534 
535 	imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_PREP, 0, 0, -1,
536 	    &ap, sizeof(ap));
537 
538 	/* walk tree in reverse because aspa_add_set requires that */
539 	RB_FOREACH_REVERSE(aspa, aspa_tree, &at) {
540 		struct aspa_set	as = { .as = aspa->as, .num = aspa->num };
541 
542 		imsg_compose(ibuf_rde, IMSG_RECONF_ASPA, 0, 0, -1,
543 		    &as, offsetof(struct aspa_set, tas));
544 		imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_TAS, 0, 0, -1,
545 		    aspa->tas, aspa->num * sizeof(*aspa->tas));
546 		imsg_compose(ibuf_rde, IMSG_RECONF_ASPA_DONE, 0, 0, -1,
547 		    NULL, 0);
548 	}
549 
550 	free_aspatree(&at);
551 
552 	imsg_compose(ibuf_rde, IMSG_RECONF_DONE, 0, 0, -1, NULL, 0);
553 }
554