xref: /openbsd/usr.sbin/bgpd/rtr.c (revision bb1a6d1a)
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 
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 = 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  */
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 	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 
305 static void
rtr_dispatch_imsg_parent(struct imsgbuf * imsgbuf)306 rtr_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 
452 static void
rtr_dispatch_imsg_rde(struct imsgbuf * imsgbuf)453 rtr_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 
470 void
rtr_imsg_compose(int type,uint32_t id,pid_t pid,void * data,size_t datalen)471 rtr_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  */
482 static size_t
rtr_aspa_set_size(struct aspa_set * aspa)483 rtr_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  */
493 void
rtr_recalc(void)494 rtr_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