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