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 | |
| 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
| -rw-r--r-- | opentracker.c | 242 | ||||
| -rw-r--r-- | opentracker.conf.sample | 28 | ||||
| -rw-r--r-- | ot_socket.c | 36 | ||||
| -rw-r--r-- | ot_socket.h | 9 | ||||
| -rw-r--r-- | ot_udp.c | 24 | ||||
| -rw-r--r-- | ot_udp.h | 4 | ||||
| -rw-r--r-- | tests/t_socket.c | 34 | ||||
| -rw-r--r-- | trackerlogic.c | 1 |
8 files changed, 253 insertions, 125 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 | ||
diff --git a/opentracker.conf.sample b/opentracker.conf.sample index 054e405..4adb736 100644 --- a/opentracker.conf.sample +++ b/opentracker.conf.sample | |||
| @@ -7,14 +7,8 @@ | |||
| 7 | # If no listen option is given (here or on the command line), opentracker | 7 | # If no listen option is given (here or on the command line), opentracker |
| 8 | # listens on 0.0.0.0:6969 tcp and udp. | 8 | # listens on 0.0.0.0:6969 tcp and udp. |
| 9 | # | 9 | # |
| 10 | # The next variable determines if udp sockets are handled in the event | 10 | # Addresses are collected while this file is read and bound afterwards, |
| 11 | # loop (set it to 0, the default) or are handled in blocking reads in | 11 | # so the order of the statements below does not matter. |
| 12 | # dedicated worker threads. You have to set this value before the | ||
| 13 | # listen.tcp_udp or listen.udp statements before it takes effect, but you | ||
| 14 | # can re-set it for each listen statement. Normally you should keep it at | ||
| 15 | # the top of the config file. | ||
| 16 | # | ||
| 17 | # listen.udp.workers 4 | ||
| 18 | # | 12 | # |
| 19 | # listen.tcp_udp 0.0.0.0 | 13 | # listen.tcp_udp 0.0.0.0 |
| 20 | # listen.tcp_udp 192.168.0.1:80 | 14 | # listen.tcp_udp 192.168.0.1:80 |
| @@ -25,6 +19,24 @@ | |||
| 25 | # listen.tcp 0.0.0.0 | 19 | # listen.tcp 0.0.0.0 |
| 26 | # listen.udp 192.168.0.1:6969 | 20 | # listen.udp 192.168.0.1:6969 |
| 27 | # | 21 | # |
| 22 | # A listen statement takes options after the address: | ||
| 23 | # | ||
| 24 | # workers=<count> handle this udp socket in blocking reads in that | ||
| 25 | # many dedicated worker threads instead of in the | ||
| 26 | # event loop. Ignored for tcp. | ||
| 27 | # shared allow another process to bind the same address and | ||
| 28 | # let the kernel share the traffic. Only useful if you | ||
| 29 | # deliberately run a second tracker there. | ||
| 30 | # | ||
| 31 | # listen.udp 10.0.0.5:6969 workers=4 shared | ||
| 32 | # | ||
| 33 | # The two statements below set defaults for every listen statement in | ||
| 34 | # this file that does not name the option itself. They may appear | ||
| 35 | # anywhere. | ||
| 36 | # | ||
| 37 | # listen.udp.workers 4 | ||
| 38 | # listen.shared 1 | ||
| 39 | # | ||
| 28 | # Note, that using 0.0.0.0 for udp sockets may yield surprising results. | 40 | # Note, that using 0.0.0.0 for udp sockets may yield surprising results. |
| 29 | # An answer packet sent on that socket will not necessarily have the | 41 | # An answer packet sent on that socket will not necessarily have the |
| 30 | # source address that the requesting client may expect, but any address | 42 | # source address that the requesting client may expect, but any address |
diff --git a/ot_socket.c b/ot_socket.c index e26ad52..84020ca 100644 --- a/ot_socket.c +++ b/ot_socket.c | |||
| @@ -15,8 +15,6 @@ | |||
| 15 | #include "ot_ip.h" | 15 | #include "ot_ip.h" |
| 16 | #include "ot_socket.h" | 16 | #include "ot_socket.h" |
| 17 | 17 | ||
| 18 | int g_socket_reuseport; | ||
| 19 | |||
| 20 | static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { | 18 | static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { |
| 21 | memset(sa, 0, sizeof(*sa)); | 19 | memset(sa, 0, sizeof(*sa)); |
| 22 | sa->sin6_family = AF_INET6; | 20 | sa->sin6_family = AF_INET6; |
| @@ -75,37 +73,43 @@ static int socket_v6(int type) { | |||
| 75 | return s; | 73 | return s; |
| 76 | } | 74 | } |
| 77 | 75 | ||
| 78 | static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port) { | 76 | /* The only place in the tree that needs to know what a kernel calls this */ |
| 79 | struct sockaddr_in6 sa; | 77 | static void socket_set_sharing(int s, socket_sharing sharing) { |
| 80 | int one = 1; | 78 | int one = 1; |
| 81 | 79 | ||
| 82 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | 80 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| 83 | #ifdef SO_REUSEPORT | 81 | #ifdef SO_REUSEPORT |
| 84 | if (g_socket_reuseport) | 82 | if (sharing == SOCKET_SHARED) |
| 85 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); | 83 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); |
| 86 | #endif | 84 | #endif |
| 85 | } | ||
| 86 | |||
| 87 | static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) { | ||
| 88 | struct sockaddr_in6 sa; | ||
| 89 | |||
| 90 | socket_set_sharing(s, sharing); | ||
| 87 | 91 | ||
| 88 | sockaddr_from_ip(&sa, ip, port); | 92 | sockaddr_from_ip(&sa, ip, port); |
| 89 | return bind(s, (struct sockaddr *)&sa, sizeof(sa)); | 93 | return bind(s, (struct sockaddr *)&sa, sizeof(sa)); |
| 90 | } | 94 | } |
| 91 | 95 | ||
| 92 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port) { | 96 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { |
| 93 | int s = socket_v6(SOCK_STREAM); | 97 | int s = socket_v6(SOCK_STREAM); |
| 94 | 98 | ||
| 95 | if (s == -1) | 99 | if (s == -1) |
| 96 | return -1; | 100 | return -1; |
| 97 | if (socket_bind_v6(s, ip, port) == -1 || listen(s, SOMAXCONN) == -1) | 101 | if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1) |
| 98 | return close_and_fail(s); | 102 | return close_and_fail(s); |
| 99 | 103 | ||
| 100 | return s; | 104 | return s; |
| 101 | } | 105 | } |
| 102 | 106 | ||
| 103 | int socket_udp_bind(const ot_ip6 ip, uint16_t port) { | 107 | int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { |
| 104 | int s = socket_v6(SOCK_DGRAM); | 108 | int s = socket_v6(SOCK_DGRAM); |
| 105 | 109 | ||
| 106 | if (s == -1) | 110 | if (s == -1) |
| 107 | return -1; | 111 | return -1; |
| 108 | if (socket_bind_v6(s, ip, port) == -1) | 112 | if (socket_bind_v6(s, ip, port, sharing) == -1) |
| 109 | return close_and_fail(s); | 113 | return close_and_fail(s); |
| 110 | 114 | ||
| 111 | return s; | 115 | return s; |
| @@ -117,7 +121,7 @@ int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, | |||
| 117 | 121 | ||
| 118 | if (s == -1) | 122 | if (s == -1) |
| 119 | return -1; | 123 | return -1; |
| 120 | if (socket_bind_v6(s, srcip, srcport) == -1) | 124 | if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1) |
| 121 | return close_and_fail(s); | 125 | return close_and_fail(s); |
| 122 | 126 | ||
| 123 | sockaddr_from_ip(&sa, dstip, dstport); | 127 | sockaddr_from_ip(&sa, dstip, dstport); |
| @@ -187,18 +191,16 @@ static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t | |||
| 187 | memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); | 191 | memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); |
| 188 | } | 192 | } |
| 189 | 193 | ||
| 194 | /* A multicast group is meant to have several listeners, so these are always | ||
| 195 | shared */ | ||
| 190 | static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { | 196 | static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { |
| 191 | struct sockaddr_in sa; | 197 | struct sockaddr_in sa; |
| 192 | int s, one = 1; | 198 | int s; |
| 193 | 199 | ||
| 194 | if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) | 200 | if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) |
| 195 | return -1; | 201 | return -1; |
| 196 | 202 | ||
| 197 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | 203 | socket_set_sharing(s, SOCKET_SHARED); |
| 198 | #ifdef SO_REUSEPORT | ||
| 199 | if (g_socket_reuseport) | ||
| 200 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); | ||
| 201 | #endif | ||
| 202 | 204 | ||
| 203 | sockaddr4_from_ip(&sa, ip, port); | 205 | sockaddr4_from_ip(&sa, ip, port); |
| 204 | if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) | 206 | if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) |
diff --git a/ot_socket.h b/ot_socket.h index 20fd839..21d04b1 100644 --- a/ot_socket.h +++ b/ot_socket.h | |||
| @@ -15,13 +15,14 @@ | |||
| 15 | cleared, so one descriptor serves both families and an IPv4 peer arrives | 15 | cleared, so one descriptor serves both families and an IPv4 peer arrives |
| 16 | as the v4 mapped address */ | 16 | as the v4 mapped address */ |
| 17 | 17 | ||
| 18 | /* Ask the kernel to let several processes bind the same address */ | 18 | /* How an address may be shared with other sockets. Which socket option says |
| 19 | extern int g_socket_reuseport; | 19 | so differs between kernels, which is socket_set_sharing's business */ |
| 20 | typedef enum { SOCKET_EXCLUSIVE, SOCKET_SHARED } socket_sharing; | ||
| 20 | 21 | ||
| 21 | /* Create, bind and, for TCP, listen. The returned socket is non blocking | 22 | /* Create, bind and, for TCP, listen. The returned socket is non blocking |
| 22 | Returns -1 on failure with errno set */ | 23 | Returns -1 on failure with errno set */ |
| 23 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port); | 24 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing); |
| 24 | int socket_udp_bind(const ot_ip6 ip, uint16_t port); | 25 | int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing); |
| 25 | 26 | ||
| 26 | int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); | 27 | int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); |
| 27 | int socket_is_connected(int s); | 28 | int socket_is_connected(int s); |
| @@ -26,9 +26,6 @@ static uint32_t g_rijndael_round_key[44] = {0}; | |||
| 26 | static volatile uint32_t g_key_of_the_hour[2] = {0}; | 26 | static volatile uint32_t g_key_of_the_hour[2] = {0}; |
| 27 | static volatile ot_time g_hour_of_the_key; | 27 | static volatile ot_time g_hour_of_the_key; |
| 28 | 28 | ||
| 29 | static pthread_mutex_t udp_start_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 30 | static pthread_cond_t udp_start_cond = PTHREAD_COND_INITIALIZER; | ||
| 31 | static int g_opentracker_started = 0; | ||
| 32 | 29 | ||
| 33 | static void udp_generate_rijndael_round_key(void) { | 30 | static void udp_generate_rijndael_round_key(void) { |
| 34 | uint32_t key[16]; | 31 | uint32_t key[16]; |
| @@ -211,11 +208,6 @@ static void *udp_worker(void *args) { | |||
| 211 | ws.debugbuf = malloc(G_DEBUGBUF_SIZE); | 208 | ws.debugbuf = malloc(G_DEBUGBUF_SIZE); |
| 212 | #endif | 209 | #endif |
| 213 | 210 | ||
| 214 | pthread_mutex_lock(&udp_start_mutex); | ||
| 215 | while(g_opentracker_running && !g_opentracker_started) | ||
| 216 | pthread_cond_wait(&udp_start_cond, &udp_start_mutex); | ||
| 217 | pthread_mutex_unlock(&udp_start_mutex); | ||
| 218 | |||
| 219 | while (g_opentracker_running) | 211 | while (g_opentracker_running) |
| 220 | handle_udp6(sock, &ws); | 212 | handle_udp6(sock, &ws); |
| 221 | 213 | ||
| @@ -227,20 +219,18 @@ static void *udp_worker(void *args) { | |||
| 227 | return NULL; | 219 | return NULL; |
| 228 | } | 220 | } |
| 229 | 221 | ||
| 230 | void udp_init(int64_t sock, unsigned int worker_count) { | 222 | void udp_init(void) { |
| 231 | pthread_t thread_id; | ||
| 232 | if (!g_rijndael_round_key[0]) | 223 | if (!g_rijndael_round_key[0]) |
| 233 | udp_generate_rijndael_round_key(); | 224 | udp_generate_rijndael_round_key(); |
| 225 | } | ||
| 226 | |||
| 227 | void udp_workers_install(int64_t sock, unsigned int worker_count) { | ||
| 228 | pthread_t thread_id; | ||
| 229 | |||
| 234 | #ifdef _DEBUG | 230 | #ifdef _DEBUG |
| 235 | fprintf(stderr, " installing %d workers on udp socket %ld\n", worker_count, (unsigned long)sock); | 231 | fprintf(stderr, "Installing %d workers on udp socket %ld\n", worker_count, (unsigned long)sock); |
| 236 | #endif | 232 | #endif |
| 237 | while (worker_count--) | 233 | while (worker_count--) |
| 238 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); | 234 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); |
| 239 | } | 235 | } |
| 240 | 236 | ||
| 241 | void udp_kickoff(void) { | ||
| 242 | pthread_mutex_lock(&udp_start_mutex); | ||
| 243 | g_opentracker_started = 1; | ||
| 244 | pthread_cond_broadcast(&udp_start_cond); | ||
| 245 | pthread_mutex_unlock(&udp_start_mutex); | ||
| 246 | } | ||
| @@ -10,8 +10,8 @@ | |||
| 10 | 10 | ||
| 11 | #include "trackerlogic.h" | 11 | #include "trackerlogic.h" |
| 12 | 12 | ||
| 13 | void udp_init(int64_t sock, unsigned int worker_count); | 13 | void udp_init(void); |
| 14 | void udp_kickoff(void); | 14 | void udp_workers_install(int64_t sock, unsigned int worker_count); |
| 15 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); | 15 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); |
| 16 | 16 | ||
| 17 | #endif | 17 | #endif |
diff --git a/tests/t_socket.c b/tests/t_socket.c index bbc9e5e..8eef7fd 100644 --- a/tests/t_socket.c +++ b/tests/t_socket.c | |||
| @@ -91,14 +91,14 @@ static void case_socket_options(void) { | |||
| 91 | int udp, tcp; | 91 | int udp, tcp; |
| 92 | 92 | ||
| 93 | test_case("a bound udp socket is non blocking and serves both families"); | 93 | test_case("a bound udp socket is non blocking and serves both families"); |
| 94 | udp = socket_udp_bind(ip_any, 0); | 94 | udp = socket_udp_bind(ip_any, 0, SOCKET_EXCLUSIVE); |
| 95 | CHECK(udp != -1); | 95 | CHECK(udp != -1); |
| 96 | CHECK_INT(is_nonblocking(udp), 1); | 96 | CHECK_INT(is_nonblocking(udp), 1); |
| 97 | CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0); | 97 | CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0); |
| 98 | close(udp); | 98 | close(udp); |
| 99 | 99 | ||
| 100 | test_case("a listening tcp socket is non blocking and serves both families"); | 100 | test_case("a listening tcp socket is non blocking and serves both families"); |
| 101 | tcp = socket_tcp_listen(ip_any, 0); | 101 | tcp = socket_tcp_listen(ip_any, 0, SOCKET_EXCLUSIVE); |
| 102 | CHECK(tcp != -1); | 102 | CHECK(tcp != -1); |
| 103 | CHECK_INT(is_nonblocking(tcp), 1); | 103 | CHECK_INT(is_nonblocking(tcp), 1); |
| 104 | CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0); | 104 | CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0); |
| @@ -106,24 +106,20 @@ static void case_socket_options(void) { | |||
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | #ifdef SO_REUSEPORT | 108 | #ifdef SO_REUSEPORT |
| 109 | static void case_reuseport_is_opt_in(void) { | 109 | static void case_sharing_is_opt_in(void) { |
| 110 | int s; | 110 | int s; |
| 111 | 111 | ||
| 112 | test_case("reuseport stays off unless the operator asks for it"); | 112 | test_case("an address is exclusive unless sharing is asked for"); |
| 113 | g_socket_reuseport = 0; | 113 | s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 114 | s = socket_udp_bind(ip_loopback, 0); | ||
| 115 | CHECK(s != -1); | 114 | CHECK(s != -1); |
| 116 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0); | 115 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0); |
| 117 | close(s); | 116 | close(s); |
| 118 | 117 | ||
| 119 | test_case("reuseport reaches the socket when it is asked for"); | 118 | test_case("and shared when it is"); |
| 120 | g_socket_reuseport = 1; | 119 | s = socket_udp_bind(ip_loopback, 0, SOCKET_SHARED); |
| 121 | s = socket_udp_bind(ip_loopback, 0); | ||
| 122 | CHECK(s != -1); | 120 | CHECK(s != -1); |
| 123 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1); | 121 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1); |
| 124 | close(s); | 122 | close(s); |
| 125 | |||
| 126 | g_socket_reuseport = 0; | ||
| 127 | } | 123 | } |
| 128 | #endif | 124 | #endif |
| 129 | 125 | ||
| @@ -135,7 +131,7 @@ static void case_udp_roundtrip(void) { | |||
| 135 | int s; | 131 | int s; |
| 136 | 132 | ||
| 137 | test_case("a datagram comes back with the sender's address and port"); | 133 | test_case("a datagram comes back with the sender's address and port"); |
| 138 | s = socket_udp_bind(ip_loopback, 0); | 134 | s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 139 | CHECK(s != -1); | 135 | CHECK(s != -1); |
| 140 | bound_address(s, own_ip, &own_port); | 136 | bound_address(s, own_ip, &own_port); |
| 141 | CHECK(own_port != 0); | 137 | CHECK(own_port != 0); |
| @@ -158,9 +154,9 @@ static void case_udp_v4mapped(void) { | |||
| 158 | int receiver, sender; | 154 | int receiver, sender; |
| 159 | 155 | ||
| 160 | test_case("an IPv4 sender is reported in its v4 mapped form"); | 156 | test_case("an IPv4 sender is reported in its v4 mapped form"); |
| 161 | receiver = socket_udp_bind(ip_v4_loopback, 0); | 157 | receiver = socket_udp_bind(ip_v4_loopback, 0, SOCKET_EXCLUSIVE); |
| 162 | CHECK(receiver != -1); | 158 | CHECK(receiver != -1); |
| 163 | sender = socket_udp_bind(ip_v4_loopback, 0); | 159 | sender = socket_udp_bind(ip_v4_loopback, 0, SOCKET_EXCLUSIVE); |
| 164 | CHECK(sender != -1); | 160 | CHECK(sender != -1); |
| 165 | bound_address(receiver, receiver_ip, &receiver_port); | 161 | bound_address(receiver, receiver_ip, &receiver_port); |
| 166 | bound_address(sender, sender_ip, &sender_port); | 162 | bound_address(sender, sender_ip, &sender_port); |
| @@ -184,7 +180,7 @@ static void case_tcp_roundtrip(void) { | |||
| 184 | int listener, client, accepted; | 180 | int listener, client, accepted; |
| 185 | 181 | ||
| 186 | test_case("a connection is accepted and carries the caller's address"); | 182 | test_case("a connection is accepted and carries the caller's address"); |
| 187 | listener = socket_tcp_listen(ip_loopback, 0); | 183 | listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 188 | CHECK(listener != -1); | 184 | CHECK(listener != -1); |
| 189 | bound_address(listener, listener_ip, &listener_port); | 185 | bound_address(listener, listener_ip, &listener_port); |
| 190 | CHECK(listener_port != 0); | 186 | CHECK(listener_port != 0); |
| @@ -216,7 +212,7 @@ static void case_connect_refused(void) { | |||
| 216 | 212 | ||
| 217 | test_case("a refused connection is reported by socket_is_connected"); | 213 | test_case("a refused connection is reported by socket_is_connected"); |
| 218 | /* Take a port, then drop it, so nothing is listening on it any more */ | 214 | /* Take a port, then drop it, so nothing is listening on it any more */ |
| 219 | listener = socket_tcp_listen(ip_loopback, 0); | 215 | listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 220 | CHECK(listener != -1); | 216 | CHECK(listener != -1); |
| 221 | bound_address(listener, listener_ip, &port); | 217 | bound_address(listener, listener_ip, &port); |
| 222 | close(listener); | 218 | close(listener); |
| @@ -247,8 +243,8 @@ static void case_bind_failure(void) { | |||
| 247 | test_case("binding an address no interface carries fails"); | 243 | test_case("binding an address no interface carries fails"); |
| 248 | before = next_descriptor(); | 244 | before = next_descriptor(); |
| 249 | CHECK(before != -1); | 245 | CHECK(before != -1); |
| 250 | CHECK_INT(socket_tcp_listen(ip_unassigned, 0), -1); | 246 | CHECK_INT(socket_tcp_listen(ip_unassigned, 0, SOCKET_EXCLUSIVE), -1); |
| 251 | CHECK_INT(socket_udp_bind(ip_unassigned, 0), -1); | 247 | CHECK_INT(socket_udp_bind(ip_unassigned, 0, SOCKET_EXCLUSIVE), -1); |
| 252 | 248 | ||
| 253 | test_case("a failed bind leaves no socket behind"); | 249 | test_case("a failed bind leaves no socket behind"); |
| 254 | CHECK_INT(next_descriptor(), before); | 250 | CHECK_INT(next_descriptor(), before); |
| @@ -294,7 +290,7 @@ static void case_mcast_sender(void) { | |||
| 294 | void test_socket(void) { | 290 | void test_socket(void) { |
| 295 | case_socket_options(); | 291 | case_socket_options(); |
| 296 | #ifdef SO_REUSEPORT | 292 | #ifdef SO_REUSEPORT |
| 297 | case_reuseport_is_opt_in(); | 293 | case_sharing_is_opt_in(); |
| 298 | #endif | 294 | #endif |
| 299 | case_udp_roundtrip(); | 295 | case_udp_roundtrip(); |
| 300 | case_udp_v4mapped(); | 296 | case_udp_v4mapped(); |
diff --git a/trackerlogic.c b/trackerlogic.c index db1d787..52c8c18 100644 --- a/trackerlogic.c +++ b/trackerlogic.c | |||
| @@ -569,7 +569,6 @@ void trackerlogic_init(void) { | |||
| 569 | accesslist_init(); | 569 | accesslist_init(); |
| 570 | livesync_init(); | 570 | livesync_init(); |
| 571 | stats_init(); | 571 | stats_init(); |
| 572 | udp_kickoff(); | ||
| 573 | } | 572 | } |
| 574 | 573 | ||
| 575 | void trackerlogic_deinit(void) { | 574 | void trackerlogic_deinit(void) { |
