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 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 63 rtr_sem_acquire(int cnt) 64 { 65 rtr_recalc_semaphore += cnt; 66 } 67 68 void 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 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 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 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 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 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 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 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 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 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 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 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