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 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 = 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 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 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 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 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 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 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 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