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