diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-25 18:51:51 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-25 18:51:51 +0100 |
| commit | 2c7659ec930277981b6333b5b3aff7840d11933c (patch) | |
| tree | 1c363ce905ef1a62395f398335a94a47916af278 /opentracker.c | |
| parent | 632960a33eb8d23925b2bbef24d5065de7280aef (diff) | |
Decouple config parsing from binding and worker spawning
* Options are parsed now into a listener description list before
binding them at once and starting the workers later
* UDP workers can now be configured per connection
* socket reuse is now done more sensibly, mcast sockets always
reused
* Hold-off logic after spawning udp workers removed
Diffstat (limited to 'opentracker.c')
| -rw-r--r-- | opentracker.c | 242 |
1 files changed, 185 insertions, 57 deletions
diff --git a/opentracker.c b/opentracker.c index 5b197d1..5f21ede 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -48,7 +48,22 @@ int g_self_pipe[2]; | |||
| 48 | 48 | ||
| 49 | static char *g_serverdir; | 49 | static char *g_serverdir; |
| 50 | static char *g_serveruser; | 50 | static char *g_serveruser; |
| 51 | static unsigned int g_udp_workers; | 51 | |
| 52 | typedef struct { | ||
| 53 | ot_ip6 ip; | ||
| 54 | uint16_t port; | ||
| 55 | PROTO_FLAG proto; | ||
| 56 | int shared; | ||
| 57 | int workers; | ||
| 58 | int64_t sock; | ||
| 59 | } ot_listen; | ||
| 60 | #define OT_LISTEN_INHERIT (-1) | ||
| 61 | |||
| 62 | /* Config entries collected from command line and config files */ | ||
| 63 | static ot_listen *g_listen; | ||
| 64 | static size_t g_listen_count; | ||
| 65 | static int g_listen_shared; | ||
| 66 | static int g_listen_workers; | ||
| 52 | 67 | ||
| 53 | static void panic(const char *routine) __attribute__((noreturn)); | 68 | static void panic(const char *routine) __attribute__((noreturn)); |
| 54 | static void panic(const char *routine) { | 69 | static void panic(const char *routine) { |
| @@ -338,44 +353,156 @@ static void *server_mainloop(void *args) { | |||
| 338 | return 0; | 353 | return 0; |
| 339 | } | 354 | } |
| 340 | 355 | ||
| 341 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port, PROTO_FLAG proto) { | 356 | static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) { |
| 342 | int64_t sock; | 357 | ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen)); |
| 358 | ot_listen *entry; | ||
| 359 | |||
| 360 | if (!grown) | ||
| 361 | panic("listen_append"); | ||
| 362 | g_listen = grown; | ||
| 363 | |||
| 364 | entry = g_listen + g_listen_count++; | ||
| 365 | memcpy(entry->ip, ip, sizeof(ot_ip6)); | ||
| 366 | entry->port = port; | ||
| 367 | entry->proto = proto; | ||
| 368 | entry->shared = shared; | ||
| 369 | entry->workers = workers; | ||
| 370 | entry->sock = -1; | ||
| 371 | } | ||
| 372 | |||
| 373 | typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind; | ||
| 374 | |||
| 375 | typedef struct { | ||
| 376 | const char *name; | ||
| 377 | size_t len; | ||
| 378 | listen_option_kind kind; | ||
| 379 | int takes_value; | ||
| 380 | } listen_option_key; | ||
| 381 | |||
| 382 | #define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v} | ||
| 383 | |||
| 384 | static const listen_option_key listen_option_keys[] = { | ||
| 385 | LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0), | ||
| 386 | LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1), | ||
| 387 | }; | ||
| 388 | static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]); | ||
| 389 | |||
| 390 | static listen_option_kind parse_listen_option(char **p) { | ||
| 391 | size_t i; | ||
| 392 | |||
| 393 | for (i = 0; i < listen_option_count; ++i) { | ||
| 394 | const listen_option_key *key = &listen_option_keys[i]; | ||
| 395 | char delimiter = (*p)[key->len]; | ||
| 396 | |||
| 397 | if (strncmp(*p, key->name, key->len)) | ||
| 398 | continue; | ||
| 399 | if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter))) | ||
| 400 | continue; | ||
| 401 | |||
| 402 | *p += key->len + (key->takes_value ? 1 : 0); | ||
| 403 | return key->kind; | ||
| 404 | } | ||
| 405 | return LISTEN_OPTION_UNKNOWN; | ||
| 406 | } | ||
| 407 | |||
| 408 | /* Options trailing an address in a listen statement, in any order */ | ||
| 409 | static int listen_scan_options(char *p, int *shared, int *workers) { | ||
| 410 | uint64_t value; | ||
| 411 | |||
| 412 | for (;;) { | ||
| 413 | while (isspace((unsigned char)*p)) | ||
| 414 | ++p; | ||
| 415 | if (!*p) | ||
| 416 | return 1; | ||
| 417 | |||
| 418 | switch (parse_listen_option(&p)) { | ||
| 419 | case LISTEN_OPTION_SHARED: | ||
| 420 | *shared = 1; | ||
| 421 | break; | ||
| 422 | case LISTEN_OPTION_WORKERS: { | ||
| 423 | size_t off = scan_udec(p, &value, UINT_MAX); | ||
| 424 | |||
| 425 | if (!off) | ||
| 426 | return 0; | ||
| 427 | *workers = (int)value; | ||
| 428 | p += off; | ||
| 429 | break; | ||
| 430 | } | ||
| 431 | default: | ||
| 432 | return 0; | ||
| 433 | } | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | static int listen_option(int value, int fallback) { return value == OT_LISTEN_INHERIT ? fallback : value; } | ||
| 438 | |||
| 439 | static void listen_bind_one(ot_listen *entry) { | ||
| 440 | socket_sharing sharing = listen_option(entry->shared, g_listen_shared) ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | ||
| 343 | 441 | ||
| 344 | #ifdef _DEBUG | 442 | #ifdef _DEBUG |
| 345 | { | 443 | { |
| 346 | char *protos[] = {"TCP", "UDP", "UDP mcast"}; | 444 | char *protos[] = {"TCP", "UDP", "UDP mcast"}; |
| 347 | char _debug[512]; | 445 | char _debug[512]; |
| 348 | int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[proto]); | 446 | int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[entry->proto]); |
| 349 | off += ip_format(_debug + off, ip); | 447 | off += ip_format(_debug + off, entry->ip); |
| 350 | snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", port); | 448 | snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", entry->port); |
| 351 | fputs(_debug, stderr); | 449 | fputs(_debug, stderr); |
| 352 | } | 450 | } |
| 353 | #endif | 451 | #endif |
| 354 | 452 | ||
| 355 | if (proto == FLAG_TCP) { | 453 | #ifdef WANT_SYNC_LIVE |
| 356 | if ((sock = socket_tcp_listen(ip, port)) == -1) | 454 | if (entry->proto == FLAG_MCA) { |
| 455 | livesync_bind_mcast(entry->ip, entry->port); | ||
| 456 | #ifdef _DEBUG | ||
| 457 | fputs(" success.\n", stderr); | ||
| 458 | #endif | ||
| 459 | return; | ||
| 460 | } | ||
| 461 | #endif | ||
| 462 | |||
| 463 | if (entry->proto == FLAG_TCP) { | ||
| 464 | if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1) | ||
| 357 | panic("socket_tcp_listen"); | 465 | panic("socket_tcp_listen"); |
| 358 | } else { | 466 | } else { |
| 359 | if ((sock = socket_udp_bind(ip, port)) == -1) | 467 | if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1) |
| 360 | panic("socket_udp_bind"); | 468 | panic("socket_udp_bind"); |
| 361 | } | 469 | } |
| 362 | 470 | ||
| 363 | if (!io_fd(sock)) | 471 | if (!io_fd(entry->sock)) |
| 364 | panic("io_fd"); | 472 | panic("io_fd"); |
| 365 | 473 | ||
| 366 | io_setcookie(sock, (void *)proto); | 474 | io_setcookie(entry->sock, (void *)entry->proto); |
| 367 | 475 | io_wantread(entry->sock); | |
| 368 | if ((proto == FLAG_UDP) && g_udp_workers) { | ||
| 369 | io_block(sock); | ||
| 370 | udp_init(sock, g_udp_workers); | ||
| 371 | } else | ||
| 372 | io_wantread(sock); | ||
| 373 | 476 | ||
| 374 | #ifdef _DEBUG | 477 | #ifdef _DEBUG |
| 375 | fputs(" success.\n", stderr); | 478 | fputs(" success.\n", stderr); |
| 376 | #endif | 479 | #endif |
| 480 | } | ||
| 481 | |||
| 482 | /* Bound in the order the entries were given. | ||
| 483 | Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */ | ||
| 484 | static void listen_bind_all(void) { | ||
| 485 | size_t i; | ||
| 486 | |||
| 487 | for (i = 0; i < g_listen_count; ++i) | ||
| 488 | listen_bind_one(g_listen + i); | ||
| 489 | } | ||
| 490 | |||
| 491 | static void listen_start_udp_workers(void) { | ||
| 492 | size_t i; | ||
| 377 | 493 | ||
| 378 | return sock; | 494 | udp_init(); |
| 495 | |||
| 496 | for (i = 0; i < g_listen_count; ++i) { | ||
| 497 | ot_listen *entry = g_listen + i; | ||
| 498 | int workers = listen_option(entry->workers, g_listen_workers); | ||
| 499 | |||
| 500 | if (entry->proto != FLAG_UDP || workers <= 0) | ||
| 501 | continue; | ||
| 502 | |||
| 503 | io_block(entry->sock); | ||
| 504 | udp_workers_install(entry->sock, (unsigned int)workers); | ||
| 505 | } | ||
| 379 | } | 506 | } |
| 380 | 507 | ||
| 381 | char *set_config_option(char **option, char *value) { | 508 | char *set_config_option(char **option, char *value) { |
| @@ -452,7 +579,7 @@ typedef enum { | |||
| 452 | CONFIG_LISTEN_TCP, | 579 | CONFIG_LISTEN_TCP, |
| 453 | CONFIG_LISTEN_UDP, | 580 | CONFIG_LISTEN_UDP, |
| 454 | CONFIG_LISTEN_UDP_WORKERS, | 581 | CONFIG_LISTEN_UDP_WORKERS, |
| 455 | CONFIG_LISTEN_REUSEPORT, | 582 | CONFIG_LISTEN_SHARED, |
| 456 | CONFIG_ACCESS_WHITELIST, | 583 | CONFIG_ACCESS_WHITELIST, |
| 457 | CONFIG_ACCESS_BLACKLIST, | 584 | CONFIG_ACCESS_BLACKLIST, |
| 458 | CONFIG_ACCESS_FIFO_ADD, | 585 | CONFIG_ACCESS_FIFO_ADD, |
| @@ -480,7 +607,7 @@ static const config_key config_keys[] = { | |||
| 480 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), | 607 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), |
| 481 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), | 608 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), |
| 482 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), | 609 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), |
| 483 | CONFIG_KEY("listen.reuseport", CONFIG_LISTEN_REUSEPORT), | 610 | CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), |
| 484 | #ifdef WANT_ACCESSLIST_WHITE | 611 | #ifdef WANT_ACCESSLIST_WHITE |
| 485 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), | 612 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), |
| 486 | #elif defined(WANT_ACCESSLIST_BLACK) | 613 | #elif defined(WANT_ACCESSLIST_BLACK) |
| @@ -537,9 +664,10 @@ int parse_configfile(char *config_filename) { | |||
| 537 | } | 664 | } |
| 538 | 665 | ||
| 539 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { | 666 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { |
| 540 | char *p = inbuf, *end; | 667 | char *p = inbuf, *end; |
| 541 | uint64_t value; | 668 | uint64_t value; |
| 542 | uint16_t tmpport = 6969; | 669 | uint16_t tmpport = 6969; |
| 670 | config_kind kind; | ||
| 543 | 671 | ||
| 544 | /* Skip white spaces */ | 672 | /* Skip white spaces */ |
| 545 | while (isspace((unsigned char)*p)) | 673 | while (isspace((unsigned char)*p)) |
| @@ -555,7 +683,8 @@ int parse_configfile(char *config_filename) { | |||
| 555 | *--end = 0; | 683 | *--end = 0; |
| 556 | 684 | ||
| 557 | /* Scan for commands */ | 685 | /* Scan for commands */ |
| 558 | switch (parse_config_key(&p)) { | 686 | kind = parse_config_key(&p); |
| 687 | switch (kind) { | ||
| 559 | case CONFIG_TRACKER_ROOTDIR: | 688 | case CONFIG_TRACKER_ROOTDIR: |
| 560 | set_config_option(&g_serverdir, p); | 689 | set_config_option(&g_serverdir, p); |
| 561 | break; | 690 | break; |
| @@ -563,35 +692,29 @@ int parse_configfile(char *config_filename) { | |||
| 563 | set_config_option(&g_serveruser, p); | 692 | set_config_option(&g_serveruser, p); |
| 564 | break; | 693 | break; |
| 565 | case CONFIG_LISTEN_TCP_UDP: | 694 | case CONFIG_LISTEN_TCP_UDP: |
| 566 | if (!scan_ip6_port(p, tmpip, &tmpport)) | ||
| 567 | goto parse_error; | ||
| 568 | ot_try_bind(tmpip, tmpport, FLAG_TCP); | ||
| 569 | ++bound; | ||
| 570 | ot_try_bind(tmpip, tmpport, FLAG_UDP); | ||
| 571 | ++bound; | ||
| 572 | break; | ||
| 573 | case CONFIG_LISTEN_TCP: | 695 | case CONFIG_LISTEN_TCP: |
| 574 | if (!scan_ip6_port(p, tmpip, &tmpport)) | 696 | case CONFIG_LISTEN_UDP: { |
| 575 | goto parse_error; | 697 | int shared = OT_LISTEN_INHERIT |
| 576 | ot_try_bind(tmpip, tmpport, FLAG_TCP); | 698 | int workers = OT_LISTEN_INHERIT; |
| 577 | ++bound; | 699 | int off = scan_ip6_port(p, tmpip, &tmpport); |
| 578 | break; | 700 | |
| 579 | case CONFIG_LISTEN_UDP: | 701 | if (!off || !listen_scan_options(p + off, &shared, &workers)) |
| 580 | if (!scan_ip6_port(p, tmpip, &tmpport)) | ||
| 581 | goto parse_error; | 702 | goto parse_error; |
| 582 | ot_try_bind(tmpip, tmpport, FLAG_UDP); | 703 | if (kind != CONFIG_LISTEN_UDP) |
| 583 | ++bound; | 704 | listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound; |
| 705 | if (kind != CONFIG_LISTEN_TCP) | ||
| 706 | listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; | ||
| 584 | break; | 707 | break; |
| 708 | } | ||
| 585 | case CONFIG_LISTEN_UDP_WORKERS: | 709 | case CONFIG_LISTEN_UDP_WORKERS: |
| 586 | if (scan_udec(p, &value, UINT_MAX)) | 710 | if (!scan_udec(p, &value, UINT_MAX)) |
| 587 | g_udp_workers = (unsigned int)value; | 711 | goto parse_error; |
| 712 | g_listen_workers = (int)value; | ||
| 588 | break; | 713 | break; |
| 589 | case CONFIG_LISTEN_REUSEPORT: | 714 | case CONFIG_LISTEN_SHARED: |
| 590 | /* Like listen.udp.workers this is read when a socket is bound, so it | ||
| 591 | has to come before the listen lines it should apply to. */ | ||
| 592 | if (!scan_udec(p, &value, 1)) | 715 | if (!scan_udec(p, &value, 1)) |
| 593 | goto parse_error; | 716 | goto parse_error; |
| 594 | g_socket_reuseport = (int)value; | 717 | g_listen_shared = (int)value; |
| 595 | break; | 718 | break; |
| 596 | #ifdef WANT_ACCESSLIST_WHITE | 719 | #ifdef WANT_ACCESSLIST_WHITE |
| 597 | case CONFIG_ACCESS_WHITELIST: | 720 | case CONFIG_ACCESS_WHITELIST: |
| @@ -638,9 +761,14 @@ int parse_configfile(char *config_filename) { | |||
| 638 | break; | 761 | break; |
| 639 | case CONFIG_LIVESYNC_CLUSTER_LISTEN: | 762 | case CONFIG_LIVESYNC_CLUSTER_LISTEN: |
| 640 | tmpport = LIVESYNC_PORT; | 763 | tmpport = LIVESYNC_PORT; |
| 641 | if (!scan_ip6_port(p, tmpip, &tmpport)) | 764 | { |
| 642 | goto parse_error; | 765 | int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT; |
| 643 | livesync_bind_mcast(tmpip, tmpport); | 766 | int off = scan_ip6_port(p, tmpip, &tmpport); |
| 767 | |||
| 768 | if (!off || !listen_scan_options(p + off, &shared, &dummy)) | ||
| 769 | goto parse_error; | ||
| 770 | listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT); | ||
| 771 | } | ||
| 644 | break; | 772 | break; |
| 645 | #endif | 773 | #endif |
| 646 | default: | 774 | default: |
| @@ -791,7 +919,7 @@ int main(int argc, char **argv) { | |||
| 791 | usage(argv[0]); | 919 | usage(argv[0]); |
| 792 | exit(1); | 920 | exit(1); |
| 793 | } | 921 | } |
| 794 | ot_try_bind(serverip, (uint16_t)value, FLAG_TCP); | 922 | listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); |
| 795 | bound++; | 923 | bound++; |
| 796 | break; | 924 | break; |
| 797 | case 'P': | 925 | case 'P': |
| @@ -799,7 +927,7 @@ int main(int argc, char **argv) { | |||
| 799 | usage(argv[0]); | 927 | usage(argv[0]); |
| 800 | exit(1); | 928 | exit(1); |
| 801 | } | 929 | } |
| 802 | ot_try_bind(serverip, (uint16_t)value, FLAG_UDP); | 930 | listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); |
| 803 | bound++; | 931 | bound++; |
| 804 | break; | 932 | break; |
| 805 | #ifdef WANT_SYNC_LIVE | 933 | #ifdef WANT_SYNC_LIVE |
| @@ -808,7 +936,7 @@ int main(int argc, char **argv) { | |||
| 808 | usage(argv[0]); | 936 | usage(argv[0]); |
| 809 | exit(1); | 937 | exit(1); |
| 810 | } | 938 | } |
| 811 | livesync_bind_mcast(serverip, (uint16_t)value); | 939 | listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); |
| 812 | break; | 940 | break; |
| 813 | #endif | 941 | #endif |
| 814 | case 'd': | 942 | case 'd': |
| @@ -849,12 +977,13 @@ int main(int argc, char **argv) { | |||
| 849 | } | 977 | } |
| 850 | } | 978 | } |
| 851 | 979 | ||
| 852 | /* Bind to our default tcp/udp ports */ | ||
| 853 | if (!bound) { | 980 | if (!bound) { |
| 854 | ot_try_bind(serverip, 6969, FLAG_TCP); | 981 | listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); |
| 855 | ot_try_bind(serverip, 6969, FLAG_UDP); | 982 | listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); |
| 856 | } | 983 | } |
| 857 | 984 | ||
| 985 | listen_bind_all(); | ||
| 986 | |||
| 858 | defaul_signal_handlers(); | 987 | defaul_signal_handlers(); |
| 859 | 988 | ||
| 860 | #ifdef WANT_SYSLOGS | 989 | #ifdef WANT_SYSLOGS |
| @@ -893,8 +1022,7 @@ int main(int argc, char **argv) { | |||
| 893 | 1022 | ||
| 894 | install_signal_handlers(); | 1023 | install_signal_handlers(); |
| 895 | 1024 | ||
| 896 | if (!g_udp_workers) | 1025 | listen_start_udp_workers(); |
| 897 | udp_init(-1, 0); | ||
| 898 | 1026 | ||
| 899 | server_mainloop(0); | 1027 | server_mainloop(0); |
| 900 | 1028 | ||
