diff options
| -rw-r--r-- | Makefile | 12 | ||||
| -rw-r--r-- | opentracker.c | 478 | ||||
| -rw-r--r-- | opentracker.conf.sample | 28 | ||||
| -rw-r--r-- | ot_accesslist.c | 8 | ||||
| -rw-r--r-- | ot_accesslist.h | 6 | ||||
| -rw-r--r-- | ot_config.c | 565 | ||||
| -rw-r--r-- | ot_config.h | 35 | ||||
| -rw-r--r-- | ot_fullscrape.c | 2 | ||||
| -rw-r--r-- | ot_http.c | 19 | ||||
| -rw-r--r-- | ot_http.h | 2 | ||||
| -rw-r--r-- | ot_socket.c | 36 | ||||
| -rw-r--r-- | ot_socket.h | 9 | ||||
| -rw-r--r-- | ot_stats.c | 2 | ||||
| -rw-r--r-- | ot_udp.c | 24 | ||||
| -rw-r--r-- | ot_udp.h | 4 | ||||
| -rw-r--r-- | ot_vector.c | 8 | ||||
| -rw-r--r-- | proxy.c | 4 | ||||
| -rw-r--r-- | scan_urlencoded_query.h | 4 | ||||
| -rw-r--r-- | tests/runner.c | 1 | ||||
| -rw-r--r-- | tests/stubs.c | 9 | ||||
| -rw-r--r-- | tests/t_mutex.c | 107 | ||||
| -rw-r--r-- | tests/t_socket.c | 34 | ||||
| -rw-r--r-- | tests/t_vector.c | 104 | ||||
| -rw-r--r-- | tests/test.h | 1 | ||||
| -rw-r--r-- | trackerlogic.c | 3 | ||||
| -rw-r--r-- | trackerlogic.h | 2 |
26 files changed, 971 insertions, 536 deletions
| @@ -64,12 +64,12 @@ GIT_VERSION=`sh -c 'command -v git >/dev/null && test -d .git && git rev-parse H | |||
| 64 | OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage | 64 | OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage |
| 65 | OPTS_production=-O3 | 65 | OPTS_production=-O3 |
| 66 | 66 | ||
| 67 | CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra #-ansi -pedantic | 67 | CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic |
| 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread | 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread |
| 69 | 69 | ||
| 70 | BINARY =opentracker | 70 | BINARY =opentracker |
| 71 | HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h | 71 | HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h |
| 72 | SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.c ot_hex.c ot_ip.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c | 72 | SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c |
| 73 | SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c | 73 | SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c |
| 74 | 74 | ||
| 75 | OBJECTS = $(SOURCES:%.c=%.o) | 75 | OBJECTS = $(SOURCES:%.c=%.o) |
| @@ -119,8 +119,8 @@ $(OBJECTS_proxy_debug): $(HEADERS) | |||
| 119 | # has to. tests/stubs.c supplies the few symbols those modules reference but | 119 | # has to. tests/stubs.c supplies the few symbols those modules reference but |
| 120 | # the tests never reach. | 120 | # the tests never reach. |
| 121 | TESTBIN=tests/runner | 121 | TESTBIN=tests/runner |
| 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c | 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c |
| 123 | TEST_UNITS=scan_urlencoded_query.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c | 123 | TEST_UNITS=scan_urlencoded_query.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c |
| 124 | TEST_HEADERS=tests/test.h $(HEADERS) | 124 | TEST_HEADERS=tests/test.h $(HEADERS) |
| 125 | # Tests reach code that sits behind feature flags, so the units under test are | 125 | # Tests reach code that sits behind feature flags, so the units under test are |
| 126 | # built with those features enabled. A test binary is not a production binary. | 126 | # built with those features enabled. A test binary is not a production binary. |
| @@ -130,7 +130,7 @@ CFLAGS_test = $(CFLAGS) -I. -g -O1 $(FEATURES_test) | |||
| 130 | $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) | 130 | $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) |
| 131 | $(CC) -o $@ $(CFLAGS_test) $(TEST_SOURCES) $(TEST_UNITS) | 131 | $(CC) -o $@ $(CFLAGS_test) $(TEST_SOURCES) $(TEST_UNITS) |
| 132 | 132 | ||
| 133 | check: $(TESTBIN) | 133 | check: $(TESTBIN) $(BINARY) |
| 134 | ./$(TESTBIN) | 134 | ./$(TESTBIN) |
| 135 | 135 | ||
| 136 | .PHONY: all check clean install | 136 | .PHONY: all check clean install |
diff --git a/opentracker.c b/opentracker.c index 5b197d1..96a265a 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -27,6 +27,7 @@ | |||
| 27 | 27 | ||
| 28 | /* Opentracker */ | 28 | /* Opentracker */ |
| 29 | #include "ot_accesslist.h" | 29 | #include "ot_accesslist.h" |
| 30 | #include "ot_config.h" | ||
| 30 | #include "ot_hex.h" | 31 | #include "ot_hex.h" |
| 31 | #include "ot_http.h" | 32 | #include "ot_http.h" |
| 32 | #include "ot_ip.h" | 33 | #include "ot_ip.h" |
| @@ -41,14 +42,10 @@ | |||
| 41 | 42 | ||
| 42 | /* Globals */ | 43 | /* Globals */ |
| 43 | time_t g_now_seconds; | 44 | time_t g_now_seconds; |
| 44 | char *g_redirecturl; | ||
| 45 | uint32_t g_tracker_id; | 45 | uint32_t g_tracker_id; |
| 46 | volatile int g_opentracker_running = 1; | 46 | volatile int g_opentracker_running = 1; |
| 47 | int g_self_pipe[2]; | 47 | int g_self_pipe[2]; |
| 48 | 48 | ||
| 49 | static char *g_serverdir; | ||
| 50 | static char *g_serveruser; | ||
| 51 | static unsigned int g_udp_workers; | ||
| 52 | 49 | ||
| 53 | static void panic(const char *routine) __attribute__((noreturn)); | 50 | static void panic(const char *routine) __attribute__((noreturn)); |
| 54 | static void panic(const char *routine) { | 51 | static void panic(const char *routine) { |
| @@ -100,42 +97,6 @@ static void install_signal_handlers(void) { | |||
| 100 | pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); | 97 | pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); |
| 101 | } | 98 | } |
| 102 | 99 | ||
| 103 | static void usage(char *name) { | ||
| 104 | fprintf(stderr, | ||
| 105 | "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" | ||
| 106 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 107 | " [-b blacklistfile]" | ||
| 108 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 109 | " [-w whitelistfile]" | ||
| 110 | #endif | ||
| 111 | "\n", | ||
| 112 | name); | ||
| 113 | } | ||
| 114 | |||
| 115 | #define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) | ||
| 116 | static void help(char *name) { | ||
| 117 | usage(name); | ||
| 118 | |||
| 119 | HELPLINE("-f config", "include and execute the config file"); | ||
| 120 | HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); | ||
| 121 | HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); | ||
| 122 | HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); | ||
| 123 | HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); | ||
| 124 | HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); | ||
| 125 | HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); | ||
| 126 | HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); | ||
| 127 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 128 | HELPLINE("-b file", "specify blacklist file."); | ||
| 129 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 130 | HELPLINE("-w file", "specify whitelist file."); | ||
| 131 | #endif | ||
| 132 | |||
| 133 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); | ||
| 134 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); | ||
| 135 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); | ||
| 136 | fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); | ||
| 137 | } | ||
| 138 | #undef HELPLINE | ||
| 139 | 100 | ||
| 140 | static ssize_t header_complete(char *request, ssize_t byte_count) { | 101 | static ssize_t header_complete(char *request, ssize_t byte_count) { |
| 141 | ssize_t i = 0, state = 0; | 102 | ssize_t i = 0, state = 0; |
| @@ -338,320 +299,75 @@ static void *server_mainloop(void *args) { | |||
| 338 | return 0; | 299 | return 0; |
| 339 | } | 300 | } |
| 340 | 301 | ||
| 341 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port, PROTO_FLAG proto) { | 302 | |
| 342 | int64_t sock; | 303 | static void listen_bind_one(ot_listen *entry) { |
| 304 | socket_sharing sharing = entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | ||
| 343 | 305 | ||
| 344 | #ifdef _DEBUG | 306 | #ifdef _DEBUG |
| 345 | { | 307 | { |
| 346 | char *protos[] = {"TCP", "UDP", "UDP mcast"}; | 308 | const char *protos[] = {"TCP", "UDP", "UDP mcast"}; |
| 347 | char _debug[512]; | 309 | char _debug[512]; |
| 348 | int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[proto]); | 310 | int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[entry->proto]); |
| 349 | off += ip_format(_debug + off, ip); | 311 | off += ip_format(_debug + off, entry->ip); |
| 350 | snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", port); | 312 | snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", entry->port); |
| 351 | fputs(_debug, stderr); | 313 | fputs(_debug, stderr); |
| 352 | } | 314 | } |
| 353 | #endif | 315 | #endif |
| 354 | 316 | ||
| 355 | if (proto == FLAG_TCP) { | 317 | #ifdef WANT_SYNC_LIVE |
| 356 | if ((sock = socket_tcp_listen(ip, port)) == -1) | 318 | if (entry->proto == FLAG_MCA) { |
| 319 | livesync_bind_mcast(entry->ip, entry->port); | ||
| 320 | #ifdef _DEBUG | ||
| 321 | fputs(" success.\n", stderr); | ||
| 322 | #endif | ||
| 323 | return; | ||
| 324 | } | ||
| 325 | #endif | ||
| 326 | |||
| 327 | if (entry->proto == FLAG_TCP) { | ||
| 328 | if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1) | ||
| 357 | panic("socket_tcp_listen"); | 329 | panic("socket_tcp_listen"); |
| 358 | } else { | 330 | } else { |
| 359 | if ((sock = socket_udp_bind(ip, port)) == -1) | 331 | if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1) |
| 360 | panic("socket_udp_bind"); | 332 | panic("socket_udp_bind"); |
| 361 | } | 333 | } |
| 362 | 334 | ||
| 363 | if (!io_fd(sock)) | 335 | if (!io_fd(entry->sock)) |
| 364 | panic("io_fd"); | 336 | panic("io_fd"); |
| 365 | 337 | ||
| 366 | io_setcookie(sock, (void *)proto); | 338 | io_setcookie(entry->sock, (void *)entry->proto); |
| 367 | 339 | 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 | 340 | ||
| 374 | #ifdef _DEBUG | 341 | #ifdef _DEBUG |
| 375 | fputs(" success.\n", stderr); | 342 | fputs(" success.\n", stderr); |
| 376 | #endif | 343 | #endif |
| 377 | |||
| 378 | return sock; | ||
| 379 | } | 344 | } |
| 380 | 345 | ||
| 381 | char *set_config_option(char **option, char *value) { | 346 | /* Bound in the order the entries were given. |
| 382 | #ifdef _DEBUG | 347 | Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */ |
| 383 | fprintf(stderr, "Setting config option: %s\n", value); | 348 | static void listen_bind_all(void) { |
| 384 | #endif | 349 | size_t count, i; |
| 385 | while (isspace(*value)) | 350 | ot_listen *entries = config_listen_entries(&count); |
| 386 | ++value; | ||
| 387 | free(*option); | ||
| 388 | return *option = strdup(value); | ||
| 389 | } | ||
| 390 | 351 | ||
| 391 | static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { | 352 | for (i = 0; i < count; ++i) |
| 392 | const char *s = src; | 353 | listen_bind_one(entries + i); |
| 393 | uint64_t value; | ||
| 394 | int off, bracket = 0; | ||
| 395 | while (isspace(*s)) | ||
| 396 | ++s; | ||
| 397 | if (*s == '[') | ||
| 398 | ++s, ++bracket; /* for v6 style notation */ | ||
| 399 | if (!(off = ip_scan(s, ip))) | ||
| 400 | return 0; | ||
| 401 | s += off; | ||
| 402 | if (bracket && *s == ']') | ||
| 403 | ++s; | ||
| 404 | if (*s == 0 || isspace(*s)) | ||
| 405 | return s - src; | ||
| 406 | if (!ip_is_v4mapped(ip)) { | ||
| 407 | if (*s != ':' && *s != '.') | ||
| 408 | return 0; | ||
| 409 | if (!bracket && *(s) == ':') | ||
| 410 | return 0; | ||
| 411 | s++; | ||
| 412 | } else { | ||
| 413 | if (*(s++) != ':') | ||
| 414 | return 0; | ||
| 415 | } | ||
| 416 | if (!(off = scan_udec(s, &value, 65535))) | ||
| 417 | return 0; | ||
| 418 | *port = (uint16_t)value; | ||
| 419 | return off + s - src; | ||
| 420 | } | 354 | } |
| 421 | 355 | ||
| 422 | static int scan_ip6_net(const char *src, ot_net *net) { | 356 | static void listen_start_udp_workers(void) { |
| 423 | const char *s = src; | 357 | size_t count, i; |
| 424 | uint64_t value; | 358 | ot_listen *entries = config_listen_entries(&count); |
| 425 | int off; | ||
| 426 | while (isspace(*s)) | ||
| 427 | ++s; | ||
| 428 | if (!(off = ip_scan(s, net->address))) | ||
| 429 | return 0; | ||
| 430 | s += off; | ||
| 431 | if (*s != '/') | ||
| 432 | net->bits = 128; | ||
| 433 | else { | ||
| 434 | s++; | ||
| 435 | if (!(off = scan_udec(s, &value, 128))) | ||
| 436 | return 0; | ||
| 437 | net->bits = (int)value; | ||
| 438 | if (ip_is_v4mapped(net->address)) | ||
| 439 | net->bits += 96; | ||
| 440 | if (net->bits > 128) | ||
| 441 | return 0; | ||
| 442 | s += off; | ||
| 443 | } | ||
| 444 | return off + s - src; | ||
| 445 | } | ||
| 446 | 359 | ||
| 447 | typedef enum { | 360 | udp_init(); |
| 448 | CONFIG_UNKNOWN, | ||
| 449 | CONFIG_TRACKER_ROOTDIR, | ||
| 450 | CONFIG_TRACKER_USER, | ||
| 451 | CONFIG_LISTEN_TCP_UDP, | ||
| 452 | CONFIG_LISTEN_TCP, | ||
| 453 | CONFIG_LISTEN_UDP, | ||
| 454 | CONFIG_LISTEN_UDP_WORKERS, | ||
| 455 | CONFIG_LISTEN_REUSEPORT, | ||
| 456 | CONFIG_ACCESS_WHITELIST, | ||
| 457 | CONFIG_ACCESS_BLACKLIST, | ||
| 458 | CONFIG_ACCESS_FIFO_ADD, | ||
| 459 | CONFIG_ACCESS_FIFO_DELETE, | ||
| 460 | CONFIG_ACCESS_STATS, | ||
| 461 | CONFIG_ACCESS_STATS_PATH, | ||
| 462 | CONFIG_ACCESS_PROXY, | ||
| 463 | CONFIG_TRACKER_REDIRECT_URL, | ||
| 464 | CONFIG_LIVESYNC_CLUSTER_NODE_IP, | ||
| 465 | CONFIG_LIVESYNC_CLUSTER_LISTEN, | ||
| 466 | } config_kind; | ||
| 467 | 361 | ||
| 468 | typedef struct { | 362 | for (i = 0; i < count; ++i) { |
| 469 | const char *name; | 363 | ot_listen *entry = entries + i; |
| 470 | size_t len; | ||
| 471 | config_kind kind; | ||
| 472 | } config_key; | ||
| 473 | 364 | ||
| 474 | #define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } | 365 | if (entry->proto != FLAG_UDP || entry->workers <= 0) |
| 475 | |||
| 476 | static const config_key config_keys[] = { | ||
| 477 | CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), | ||
| 478 | CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), | ||
| 479 | CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), | ||
| 480 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), | ||
| 481 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), | ||
| 482 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), | ||
| 483 | CONFIG_KEY("listen.reuseport", CONFIG_LISTEN_REUSEPORT), | ||
| 484 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 485 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), | ||
| 486 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 487 | CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST), | ||
| 488 | #endif | ||
| 489 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 490 | CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD), | ||
| 491 | CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE), | ||
| 492 | #endif | ||
| 493 | #ifdef WANT_RESTRICT_STATS | ||
| 494 | CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS), | ||
| 495 | #endif | ||
| 496 | CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH), | ||
| 497 | #ifdef WANT_IP_FROM_PROXY | ||
| 498 | CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY), | ||
| 499 | #endif | ||
| 500 | CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL), | ||
| 501 | #ifdef WANT_SYNC_LIVE | ||
| 502 | CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), | ||
| 503 | CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), | ||
| 504 | #endif | ||
| 505 | }; | ||
| 506 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); | ||
| 507 | |||
| 508 | static config_kind parse_config_key(char **p) { | ||
| 509 | size_t i; | ||
| 510 | for (i = 0; i < config_entry_count; ++i) { | ||
| 511 | const config_key *key = &config_keys[i]; | ||
| 512 | if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { | ||
| 513 | *p += key->len + 1; | ||
| 514 | /* Skip white spaces */ | ||
| 515 | while (isspace((unsigned char)**p)) | ||
| 516 | ++(*p); | ||
| 517 | return key->kind; | ||
| 518 | } | ||
| 519 | } | ||
| 520 | return CONFIG_UNKNOWN; | ||
| 521 | } | ||
| 522 | |||
| 523 | int parse_configfile(char *config_filename) { | ||
| 524 | FILE *config_file_handle; | ||
| 525 | char inbuf[512]; | ||
| 526 | ot_ip6 tmpip; | ||
| 527 | #if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) | ||
| 528 | ot_net tmpnet; | ||
| 529 | #endif | ||
| 530 | int bound = 0; | ||
| 531 | |||
| 532 | config_file_handle = fopen(config_filename, "r"); | ||
| 533 | |||
| 534 | if (config_file_handle == NULL) { | ||
| 535 | fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); | ||
| 536 | return 0; | ||
| 537 | } | ||
| 538 | |||
| 539 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { | ||
| 540 | char *p = inbuf, *end; | ||
| 541 | uint64_t value; | ||
| 542 | uint16_t tmpport = 6969; | ||
| 543 | |||
| 544 | /* Skip white spaces */ | ||
| 545 | while (isspace((unsigned char)*p)) | ||
| 546 | ++p; | ||
| 547 | |||
| 548 | /* Ignore comments and empty lines */ | ||
| 549 | if ((*p == '#') || (*p == '\n') || (*p == 0)) | ||
| 550 | continue; | 366 | continue; |
| 551 | 367 | ||
| 552 | /* consume trailing new lines and spaces */ | 368 | io_block(entry->sock); |
| 553 | end = p + strlen(p); | 369 | udp_workers_install(entry->sock, (unsigned int)entry->workers); |
| 554 | while (end > p && isspace((unsigned char)end[-1])) | ||
| 555 | *--end = 0; | ||
| 556 | |||
| 557 | /* Scan for commands */ | ||
| 558 | switch (parse_config_key(&p)) { | ||
| 559 | case CONFIG_TRACKER_ROOTDIR: | ||
| 560 | set_config_option(&g_serverdir, p); | ||
| 561 | break; | ||
| 562 | case CONFIG_TRACKER_USER: | ||
| 563 | set_config_option(&g_serveruser, p); | ||
| 564 | break; | ||
| 565 | 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: | ||
| 574 | if (!scan_ip6_port(p, tmpip, &tmpport)) | ||
| 575 | goto parse_error; | ||
| 576 | ot_try_bind(tmpip, tmpport, FLAG_TCP); | ||
| 577 | ++bound; | ||
| 578 | break; | ||
| 579 | case CONFIG_LISTEN_UDP: | ||
| 580 | if (!scan_ip6_port(p, tmpip, &tmpport)) | ||
| 581 | goto parse_error; | ||
| 582 | ot_try_bind(tmpip, tmpport, FLAG_UDP); | ||
| 583 | ++bound; | ||
| 584 | break; | ||
| 585 | case CONFIG_LISTEN_UDP_WORKERS: | ||
| 586 | if (scan_udec(p, &value, UINT_MAX)) | ||
| 587 | g_udp_workers = (unsigned int)value; | ||
| 588 | break; | ||
| 589 | case CONFIG_LISTEN_REUSEPORT: | ||
| 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)) | ||
| 593 | goto parse_error; | ||
| 594 | g_socket_reuseport = (int)value; | ||
| 595 | break; | ||
| 596 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 597 | case CONFIG_ACCESS_WHITELIST: | ||
| 598 | set_config_option(&g_accesslist_filename, p); | ||
| 599 | break; | ||
| 600 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 601 | case CONFIG_ACCESS_BLACKLIST: | ||
| 602 | set_config_option(&g_accesslist_filename, p); | ||
| 603 | break; | ||
| 604 | #endif | ||
| 605 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 606 | case CONFIG_ACCESS_FIFO_ADD: | ||
| 607 | set_config_option(&g_accesslist_pipe_add, p); | ||
| 608 | break; | ||
| 609 | case CONFIG_ACCESS_FIFO_DELETE: | ||
| 610 | set_config_option(&g_accesslist_pipe_delete, p); | ||
| 611 | break; | ||
| 612 | #endif | ||
| 613 | #ifdef WANT_RESTRICT_STATS | ||
| 614 | case CONFIG_ACCESS_STATS: | ||
| 615 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 616 | goto parse_error; | ||
| 617 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT); | ||
| 618 | break; | ||
| 619 | #endif | ||
| 620 | case CONFIG_ACCESS_STATS_PATH: | ||
| 621 | set_config_option(&g_stats_path, p); | ||
| 622 | break; | ||
| 623 | #ifdef WANT_IP_FROM_PROXY | ||
| 624 | case CONFIG_ACCESS_PROXY: | ||
| 625 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 626 | goto parse_error; | ||
| 627 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY); | ||
| 628 | break; | ||
| 629 | #endif | ||
| 630 | case CONFIG_TRACKER_REDIRECT_URL: | ||
| 631 | set_config_option(&g_redirecturl, p); | ||
| 632 | break; | ||
| 633 | #ifdef WANT_SYNC_LIVE | ||
| 634 | case CONFIG_LIVESYNC_CLUSTER_NODE_IP: | ||
| 635 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 636 | goto parse_error; | ||
| 637 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC); | ||
| 638 | break; | ||
| 639 | case CONFIG_LIVESYNC_CLUSTER_LISTEN: | ||
| 640 | tmpport = LIVESYNC_PORT; | ||
| 641 | if (!scan_ip6_port(p, tmpip, &tmpport)) | ||
| 642 | goto parse_error; | ||
| 643 | livesync_bind_mcast(tmpip, tmpport); | ||
| 644 | break; | ||
| 645 | #endif | ||
| 646 | default: | ||
| 647 | fprintf(stderr, "Unhandled line in config file: %s\n", inbuf); | ||
| 648 | break; | ||
| 649 | parse_error: | ||
| 650 | fprintf(stderr, "Parse error in config file: %s\n", inbuf); | ||
| 651 | } | ||
| 652 | } | 370 | } |
| 653 | fclose(config_file_handle); | ||
| 654 | return bound; | ||
| 655 | } | 371 | } |
| 656 | 372 | ||
| 657 | void load_state(const char *const state_filename) { | 373 | void load_state(const char *const state_filename) { |
| @@ -745,115 +461,12 @@ static void *time_caching_worker(void *args) { | |||
| 745 | } | 461 | } |
| 746 | 462 | ||
| 747 | int main(int argc, char **argv) { | 463 | int main(int argc, char **argv) { |
| 748 | ot_ip6 serverip; | ||
| 749 | ot_net tmpnet; | ||
| 750 | int bound = 0, scanon = 1; | ||
| 751 | uint64_t value; | ||
| 752 | char *statefile = 0; | ||
| 753 | pthread_t thread_id; /* time cacher */ | 464 | pthread_t thread_id; /* time cacher */ |
| 754 | 465 | ||
| 755 | memset(serverip, 0, sizeof(ot_ip6)); | ||
| 756 | #ifdef WANT_V4_ONLY | ||
| 757 | serverip[10] = serverip[11] = -1; | ||
| 758 | #endif | ||
| 759 | |||
| 760 | random_init(); | 466 | random_init(); |
| 467 | config_parse(argc, argv); | ||
| 761 | 468 | ||
| 762 | while (scanon) { | 469 | listen_bind_all(); |
| 763 | switch (getopt(argc, argv, | ||
| 764 | ":i:p:A:P:d:u:r:s:f:l:v" | ||
| 765 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 766 | "b:" | ||
| 767 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 768 | "w:" | ||
| 769 | #endif | ||
| 770 | "h")) { | ||
| 771 | case -1: | ||
| 772 | scanon = 0; | ||
| 773 | break; | ||
| 774 | case 'i': | ||
| 775 | if (!ip_scan(optarg, serverip)) { | ||
| 776 | usage(argv[0]); | ||
| 777 | exit(1); | ||
| 778 | } | ||
| 779 | break; | ||
| 780 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 781 | case 'b': | ||
| 782 | set_config_option(&g_accesslist_filename, optarg); | ||
| 783 | break; | ||
| 784 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 785 | case 'w': | ||
| 786 | set_config_option(&g_accesslist_filename, optarg); | ||
| 787 | break; | ||
| 788 | #endif | ||
| 789 | case 'p': | ||
| 790 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 791 | usage(argv[0]); | ||
| 792 | exit(1); | ||
| 793 | } | ||
| 794 | ot_try_bind(serverip, (uint16_t)value, FLAG_TCP); | ||
| 795 | bound++; | ||
| 796 | break; | ||
| 797 | case 'P': | ||
| 798 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 799 | usage(argv[0]); | ||
| 800 | exit(1); | ||
| 801 | } | ||
| 802 | ot_try_bind(serverip, (uint16_t)value, FLAG_UDP); | ||
| 803 | bound++; | ||
| 804 | break; | ||
| 805 | #ifdef WANT_SYNC_LIVE | ||
| 806 | case 's': | ||
| 807 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 808 | usage(argv[0]); | ||
| 809 | exit(1); | ||
| 810 | } | ||
| 811 | livesync_bind_mcast(serverip, (uint16_t)value); | ||
| 812 | break; | ||
| 813 | #endif | ||
| 814 | case 'd': | ||
| 815 | set_config_option(&g_serverdir, optarg); | ||
| 816 | break; | ||
| 817 | case 'u': | ||
| 818 | set_config_option(&g_serveruser, optarg); | ||
| 819 | break; | ||
| 820 | case 'r': | ||
| 821 | set_config_option(&g_redirecturl, optarg); | ||
| 822 | break; | ||
| 823 | case 'l': | ||
| 824 | statefile = optarg; | ||
| 825 | break; | ||
| 826 | case 'A': | ||
| 827 | if (!scan_ip6_net(optarg, &tmpnet)) { | ||
| 828 | usage(argv[0]); | ||
| 829 | exit(1); | ||
| 830 | } | ||
| 831 | accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */ | ||
| 832 | break; | ||
| 833 | case 'f': | ||
| 834 | bound += parse_configfile(optarg); | ||
| 835 | break; | ||
| 836 | case 'h': | ||
| 837 | help(argv[0]); | ||
| 838 | exit(0); | ||
| 839 | case 'v': { | ||
| 840 | char buffer[8192]; | ||
| 841 | stats_return_tracker_version(buffer); | ||
| 842 | fputs(buffer, stderr); | ||
| 843 | exit(0); | ||
| 844 | } | ||
| 845 | default: | ||
| 846 | case '?': | ||
| 847 | usage(argv[0]); | ||
| 848 | exit(1); | ||
| 849 | } | ||
| 850 | } | ||
| 851 | |||
| 852 | /* Bind to our default tcp/udp ports */ | ||
| 853 | if (!bound) { | ||
| 854 | ot_try_bind(serverip, 6969, FLAG_TCP); | ||
| 855 | ot_try_bind(serverip, 6969, FLAG_UDP); | ||
| 856 | } | ||
| 857 | 470 | ||
| 858 | defaul_signal_handlers(); | 471 | defaul_signal_handlers(); |
| 859 | 472 | ||
| @@ -888,13 +501,12 @@ int main(int argc, char **argv) { | |||
| 888 | fprintf(stderr, "... done.\n"); | 501 | fprintf(stderr, "... done.\n"); |
| 889 | #endif | 502 | #endif |
| 890 | 503 | ||
| 891 | if (statefile) | 504 | if (g_statefile) |
| 892 | load_state(statefile); | 505 | load_state(g_statefile); |
| 893 | 506 | ||
| 894 | install_signal_handlers(); | 507 | install_signal_handlers(); |
| 895 | 508 | ||
| 896 | if (!g_udp_workers) | 509 | listen_start_udp_workers(); |
| 897 | udp_init(-1, 0); | ||
| 898 | 510 | ||
| 899 | server_mainloop(0); | 511 | server_mainloop(0); |
| 900 | 512 | ||
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_accesslist.c b/ot_accesslist.c index 2465ada..d7e88ee 100644 --- a/ot_accesslist.c +++ b/ot_accesslist.c | |||
| @@ -32,10 +32,10 @@ | |||
| 32 | 32 | ||
| 33 | /* GLOBAL VARIABLES */ | 33 | /* GLOBAL VARIABLES */ |
| 34 | #ifdef WANT_ACCESSLIST | 34 | #ifdef WANT_ACCESSLIST |
| 35 | char *g_accesslist_filename = NULL; | 35 | const char *g_accesslist_filename = NULL; |
| 36 | #ifdef WANT_DYNAMIC_ACCESSLIST | 36 | #ifdef WANT_DYNAMIC_ACCESSLIST |
| 37 | char *g_accesslist_pipe_add = NULL; | 37 | const char *g_accesslist_pipe_add = NULL; |
| 38 | char *g_accesslist_pipe_delete = NULL; | 38 | const char *g_accesslist_pipe_delete = NULL; |
| 39 | #endif | 39 | #endif |
| 40 | static pthread_mutex_t g_accesslist_mutex; | 40 | static pthread_mutex_t g_accesslist_mutex; |
| 41 | 41 | ||
| @@ -272,7 +272,7 @@ static void *accesslist_worker(void *args) { | |||
| 272 | 272 | ||
| 273 | #ifdef WANT_DYNAMIC_ACCESSLIST | 273 | #ifdef WANT_DYNAMIC_ACCESSLIST |
| 274 | static pthread_t thread_adder_id, thread_deleter_id; | 274 | static pthread_t thread_adder_id, thread_deleter_id; |
| 275 | static void *accesslist_adddel_worker(char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { | 275 | static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { |
| 276 | struct stat st; | 276 | struct stat st; |
| 277 | 277 | ||
| 278 | if (!stat(fifoname, &st)) { | 278 | if (!stat(fifoname, &st)) { |
diff --git a/ot_accesslist.h b/ot_accesslist.h index 30aaebd..9dfdd95 100644 --- a/ot_accesslist.h +++ b/ot_accesslist.h | |||
| @@ -23,10 +23,10 @@ void accesslist_cleanup(void); | |||
| 23 | line, and return how many were written to dest */ | 23 | line, and return how many were written to dest */ |
| 24 | size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len); | 24 | size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len); |
| 25 | 25 | ||
| 26 | extern char *g_accesslist_filename; | 26 | extern const char *g_accesslist_filename; |
| 27 | #ifdef WANT_DYNAMIC_ACCESSLIST | 27 | #ifdef WANT_DYNAMIC_ACCESSLIST |
| 28 | extern char *g_accesslist_pipe_add; | 28 | extern const char *g_accesslist_pipe_add; |
| 29 | extern char *g_accesslist_pipe_delete; | 29 | extern const char *g_accesslist_pipe_delete; |
| 30 | #endif | 30 | #endif |
| 31 | 31 | ||
| 32 | #else | 32 | #else |
diff --git a/ot_config.c b/ot_config.c new file mode 100644 index 0000000..e9666f9 --- /dev/null +++ b/ot_config.c | |||
| @@ -0,0 +1,565 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <ctype.h> | ||
| 8 | #include <errno.h> | ||
| 9 | #include <limits.h> | ||
| 10 | #include <stdio.h> | ||
| 11 | #include <stdlib.h> | ||
| 12 | #include <string.h> | ||
| 13 | #include <unistd.h> | ||
| 14 | |||
| 15 | /* Opentracker */ | ||
| 16 | #include "ot_accesslist.h" | ||
| 17 | #include "ot_config.h" | ||
| 18 | #include "ot_http.h" | ||
| 19 | #include "ot_ip.h" | ||
| 20 | #include "ot_livesync.h" | ||
| 21 | #include "ot_scan.h" | ||
| 22 | #include "ot_stats.h" | ||
| 23 | #include "trackerlogic.h" | ||
| 24 | |||
| 25 | const char *g_redirecturl; | ||
| 26 | const char *g_serverdir; | ||
| 27 | const char *g_serveruser; | ||
| 28 | const char *g_statefile; | ||
| 29 | |||
| 30 | #define OT_LISTEN_INHERIT (-1) | ||
| 31 | |||
| 32 | static ot_listen *g_listen; | ||
| 33 | static size_t g_listen_count; | ||
| 34 | static int g_listen_shared; | ||
| 35 | static int g_listen_workers; | ||
| 36 | |||
| 37 | static void panic(const char *routine) __attribute__((noreturn)); | ||
| 38 | static void panic(const char *routine) { | ||
| 39 | fprintf(stderr, "%s: %s\n", routine, strerror(errno)); | ||
| 40 | exit(111); | ||
| 41 | } | ||
| 42 | |||
| 43 | static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) { | ||
| 44 | ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen)); | ||
| 45 | ot_listen *entry; | ||
| 46 | |||
| 47 | if (!grown) | ||
| 48 | panic("listen_append"); | ||
| 49 | g_listen = grown; | ||
| 50 | |||
| 51 | entry = g_listen + g_listen_count++; | ||
| 52 | memcpy(entry->ip, ip, sizeof(ot_ip6)); | ||
| 53 | entry->port = port; | ||
| 54 | entry->proto = proto; | ||
| 55 | entry->shared = shared; | ||
| 56 | entry->workers = workers; | ||
| 57 | entry->sock = -1; | ||
| 58 | } | ||
| 59 | |||
| 60 | typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind; | ||
| 61 | |||
| 62 | typedef struct { | ||
| 63 | const char *name; | ||
| 64 | size_t len; | ||
| 65 | listen_option_kind kind; | ||
| 66 | int takes_value; | ||
| 67 | } listen_option_key; | ||
| 68 | |||
| 69 | #define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v} | ||
| 70 | |||
| 71 | static const listen_option_key listen_option_keys[] = { | ||
| 72 | LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0), | ||
| 73 | LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1), | ||
| 74 | }; | ||
| 75 | static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]); | ||
| 76 | |||
| 77 | static listen_option_kind parse_listen_option(char **p) { | ||
| 78 | size_t i; | ||
| 79 | |||
| 80 | for (i = 0; i < listen_option_count; ++i) { | ||
| 81 | const listen_option_key *key = &listen_option_keys[i]; | ||
| 82 | char delimiter = (*p)[key->len]; | ||
| 83 | |||
| 84 | if (strncmp(*p, key->name, key->len)) | ||
| 85 | continue; | ||
| 86 | if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter))) | ||
| 87 | continue; | ||
| 88 | |||
| 89 | *p += key->len + (key->takes_value ? 1 : 0); | ||
| 90 | return key->kind; | ||
| 91 | } | ||
| 92 | return LISTEN_OPTION_UNKNOWN; | ||
| 93 | } | ||
| 94 | |||
| 95 | /* Options trailing an address in a listen statement, in any order */ | ||
| 96 | static int listen_scan_options(char *p, int *shared, int *workers) { | ||
| 97 | uint64_t value; | ||
| 98 | |||
| 99 | for (;;) { | ||
| 100 | while (isspace((unsigned char)*p)) | ||
| 101 | ++p; | ||
| 102 | if (!*p) | ||
| 103 | return 1; | ||
| 104 | |||
| 105 | switch (parse_listen_option(&p)) { | ||
| 106 | case LISTEN_OPTION_SHARED: | ||
| 107 | *shared = 1; | ||
| 108 | break; | ||
| 109 | case LISTEN_OPTION_WORKERS: { | ||
| 110 | size_t off = scan_udec(p, &value, UINT_MAX); | ||
| 111 | |||
| 112 | if (!off) | ||
| 113 | return 0; | ||
| 114 | *workers = (int)value; | ||
| 115 | p += off; | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | default: | ||
| 119 | return 0; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | |||
| 125 | static const char *set_config_option(const char **option, char *value) { | ||
| 126 | #ifdef _DEBUG | ||
| 127 | fprintf(stderr, "Setting config option: %s\n", value); | ||
| 128 | #endif | ||
| 129 | while (isspace(*value)) | ||
| 130 | ++value; | ||
| 131 | free((char *)*option); | ||
| 132 | return *option = strdup(value); | ||
| 133 | } | ||
| 134 | |||
| 135 | static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { | ||
| 136 | const char *s = src; | ||
| 137 | uint64_t value; | ||
| 138 | int off, bracket = 0; | ||
| 139 | while (isspace(*s)) | ||
| 140 | ++s; | ||
| 141 | if (*s == '[') | ||
| 142 | ++s, ++bracket; /* for v6 style notation */ | ||
| 143 | if (!(off = ip_scan(s, ip))) | ||
| 144 | return 0; | ||
| 145 | s += off; | ||
| 146 | if (bracket && *s == ']') | ||
| 147 | ++s; | ||
| 148 | if (*s == 0 || isspace(*s)) | ||
| 149 | return s - src; | ||
| 150 | if (!ip_is_v4mapped(ip)) { | ||
| 151 | if (*s != ':' && *s != '.') | ||
| 152 | return 0; | ||
| 153 | if (!bracket && *(s) == ':') | ||
| 154 | return 0; | ||
| 155 | s++; | ||
| 156 | } else { | ||
| 157 | if (*(s++) != ':') | ||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | if (!(off = scan_udec(s, &value, 65535))) | ||
| 161 | return 0; | ||
| 162 | *port = (uint16_t)value; | ||
| 163 | return off + s - src; | ||
| 164 | } | ||
| 165 | |||
| 166 | static int scan_ip6_net(const char *src, ot_net *net) { | ||
| 167 | const char *s = src; | ||
| 168 | uint64_t value; | ||
| 169 | int off; | ||
| 170 | while (isspace(*s)) | ||
| 171 | ++s; | ||
| 172 | if (!(off = ip_scan(s, net->address))) | ||
| 173 | return 0; | ||
| 174 | s += off; | ||
| 175 | if (*s != '/') | ||
| 176 | net->bits = 128; | ||
| 177 | else { | ||
| 178 | s++; | ||
| 179 | if (!(off = scan_udec(s, &value, 128))) | ||
| 180 | return 0; | ||
| 181 | net->bits = (int)value; | ||
| 182 | if (ip_is_v4mapped(net->address)) | ||
| 183 | net->bits += 96; | ||
| 184 | if (net->bits > 128) | ||
| 185 | return 0; | ||
| 186 | s += off; | ||
| 187 | } | ||
| 188 | return off + s - src; | ||
| 189 | } | ||
| 190 | |||
| 191 | typedef enum { | ||
| 192 | CONFIG_UNKNOWN, | ||
| 193 | CONFIG_TRACKER_ROOTDIR, | ||
| 194 | CONFIG_TRACKER_USER, | ||
| 195 | CONFIG_LISTEN_TCP_UDP, | ||
| 196 | CONFIG_LISTEN_TCP, | ||
| 197 | CONFIG_LISTEN_UDP, | ||
| 198 | CONFIG_LISTEN_UDP_WORKERS, | ||
| 199 | CONFIG_LISTEN_SHARED, | ||
| 200 | CONFIG_ACCESS_WHITELIST, | ||
| 201 | CONFIG_ACCESS_BLACKLIST, | ||
| 202 | CONFIG_ACCESS_FIFO_ADD, | ||
| 203 | CONFIG_ACCESS_FIFO_DELETE, | ||
| 204 | CONFIG_ACCESS_STATS, | ||
| 205 | CONFIG_ACCESS_STATS_PATH, | ||
| 206 | CONFIG_ACCESS_PROXY, | ||
| 207 | CONFIG_TRACKER_REDIRECT_URL, | ||
| 208 | CONFIG_LIVESYNC_CLUSTER_NODE_IP, | ||
| 209 | CONFIG_LIVESYNC_CLUSTER_LISTEN, | ||
| 210 | } config_kind; | ||
| 211 | |||
| 212 | typedef struct { | ||
| 213 | const char *name; | ||
| 214 | size_t len; | ||
| 215 | config_kind kind; | ||
| 216 | } config_key; | ||
| 217 | |||
| 218 | #define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } | ||
| 219 | |||
| 220 | static const config_key config_keys[] = { | ||
| 221 | CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), | ||
| 222 | CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), | ||
| 223 | CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), | ||
| 224 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), | ||
| 225 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), | ||
| 226 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), | ||
| 227 | CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), | ||
| 228 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 229 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), | ||
| 230 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 231 | CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST), | ||
| 232 | #endif | ||
| 233 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 234 | CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD), | ||
| 235 | CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE), | ||
| 236 | #endif | ||
| 237 | #ifdef WANT_RESTRICT_STATS | ||
| 238 | CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS), | ||
| 239 | #endif | ||
| 240 | CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH), | ||
| 241 | #ifdef WANT_IP_FROM_PROXY | ||
| 242 | CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY), | ||
| 243 | #endif | ||
| 244 | CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL), | ||
| 245 | #ifdef WANT_SYNC_LIVE | ||
| 246 | CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), | ||
| 247 | CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), | ||
| 248 | #endif | ||
| 249 | }; | ||
| 250 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); | ||
| 251 | |||
| 252 | static config_kind parse_config_key(char **p) { | ||
| 253 | size_t i; | ||
| 254 | for (i = 0; i < config_entry_count; ++i) { | ||
| 255 | const config_key *key = &config_keys[i]; | ||
| 256 | if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { | ||
| 257 | *p += key->len + 1; | ||
| 258 | /* Skip white spaces */ | ||
| 259 | while (isspace((unsigned char)**p)) | ||
| 260 | ++(*p); | ||
| 261 | return key->kind; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | return CONFIG_UNKNOWN; | ||
| 265 | } | ||
| 266 | |||
| 267 | int parse_configfile(char *config_filename) { | ||
| 268 | FILE *config_file_handle; | ||
| 269 | char inbuf[512]; | ||
| 270 | ot_ip6 tmpip; | ||
| 271 | #if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) | ||
| 272 | ot_net tmpnet; | ||
| 273 | #endif | ||
| 274 | int bound = 0; | ||
| 275 | |||
| 276 | config_file_handle = fopen(config_filename, "r"); | ||
| 277 | |||
| 278 | if (config_file_handle == NULL) { | ||
| 279 | fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); | ||
| 280 | return 0; | ||
| 281 | } | ||
| 282 | |||
| 283 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { | ||
| 284 | char *p = inbuf, *end; | ||
| 285 | uint64_t value; | ||
| 286 | uint16_t tmpport = 6969; | ||
| 287 | config_kind kind; | ||
| 288 | |||
| 289 | /* Skip white spaces */ | ||
| 290 | while (isspace((unsigned char)*p)) | ||
| 291 | ++p; | ||
| 292 | |||
| 293 | /* Ignore comments and empty lines */ | ||
| 294 | if ((*p == '#') || (*p == '\n') || (*p == 0)) | ||
| 295 | continue; | ||
| 296 | |||
| 297 | /* consume trailing new lines and spaces */ | ||
| 298 | end = p + strlen(p); | ||
| 299 | while (end > p && isspace((unsigned char)end[-1])) | ||
| 300 | *--end = 0; | ||
| 301 | |||
| 302 | /* Scan for commands */ | ||
| 303 | kind = parse_config_key(&p); | ||
| 304 | switch (kind) { | ||
| 305 | case CONFIG_TRACKER_ROOTDIR: | ||
| 306 | set_config_option(&g_serverdir, p); | ||
| 307 | break; | ||
| 308 | case CONFIG_TRACKER_USER: | ||
| 309 | set_config_option(&g_serveruser, p); | ||
| 310 | break; | ||
| 311 | case CONFIG_LISTEN_TCP_UDP: | ||
| 312 | case CONFIG_LISTEN_TCP: | ||
| 313 | case CONFIG_LISTEN_UDP: { | ||
| 314 | int shared = OT_LISTEN_INHERIT; | ||
| 315 | int workers = OT_LISTEN_INHERIT; | ||
| 316 | int off = scan_ip6_port(p, tmpip, &tmpport); | ||
| 317 | |||
| 318 | if (!off || !listen_scan_options(p + off, &shared, &workers)) | ||
| 319 | goto parse_error; | ||
| 320 | if (kind != CONFIG_LISTEN_UDP) | ||
| 321 | listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound; | ||
| 322 | if (kind != CONFIG_LISTEN_TCP) | ||
| 323 | listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | case CONFIG_LISTEN_UDP_WORKERS: | ||
| 327 | if (!scan_udec(p, &value, UINT_MAX)) | ||
| 328 | goto parse_error; | ||
| 329 | g_listen_workers = (int)value; | ||
| 330 | break; | ||
| 331 | case CONFIG_LISTEN_SHARED: | ||
| 332 | if (!scan_udec(p, &value, 1)) | ||
| 333 | goto parse_error; | ||
| 334 | g_listen_shared = (int)value; | ||
| 335 | break; | ||
| 336 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 337 | case CONFIG_ACCESS_WHITELIST: | ||
| 338 | set_config_option(&g_accesslist_filename, p); | ||
| 339 | break; | ||
| 340 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 341 | case CONFIG_ACCESS_BLACKLIST: | ||
| 342 | set_config_option(&g_accesslist_filename, p); | ||
| 343 | break; | ||
| 344 | #endif | ||
| 345 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 346 | case CONFIG_ACCESS_FIFO_ADD: | ||
| 347 | set_config_option(&g_accesslist_pipe_add, p); | ||
| 348 | break; | ||
| 349 | case CONFIG_ACCESS_FIFO_DELETE: | ||
| 350 | set_config_option(&g_accesslist_pipe_delete, p); | ||
| 351 | break; | ||
| 352 | #endif | ||
| 353 | #ifdef WANT_RESTRICT_STATS | ||
| 354 | case CONFIG_ACCESS_STATS: | ||
| 355 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 356 | goto parse_error; | ||
| 357 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT); | ||
| 358 | break; | ||
| 359 | #endif | ||
| 360 | case CONFIG_ACCESS_STATS_PATH: | ||
| 361 | set_config_option(&g_stats_path, p); | ||
| 362 | break; | ||
| 363 | #ifdef WANT_IP_FROM_PROXY | ||
| 364 | case CONFIG_ACCESS_PROXY: | ||
| 365 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 366 | goto parse_error; | ||
| 367 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY); | ||
| 368 | break; | ||
| 369 | #endif | ||
| 370 | case CONFIG_TRACKER_REDIRECT_URL: | ||
| 371 | set_config_option(&g_redirecturl, p); | ||
| 372 | break; | ||
| 373 | #ifdef WANT_SYNC_LIVE | ||
| 374 | case CONFIG_LIVESYNC_CLUSTER_NODE_IP: | ||
| 375 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 376 | goto parse_error; | ||
| 377 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC); | ||
| 378 | break; | ||
| 379 | case CONFIG_LIVESYNC_CLUSTER_LISTEN: | ||
| 380 | tmpport = LIVESYNC_PORT; | ||
| 381 | { | ||
| 382 | int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT; | ||
| 383 | int off = scan_ip6_port(p, tmpip, &tmpport); | ||
| 384 | |||
| 385 | if (!off || !listen_scan_options(p + off, &shared, &dummy)) | ||
| 386 | goto parse_error; | ||
| 387 | listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT); | ||
| 388 | } | ||
| 389 | break; | ||
| 390 | #endif | ||
| 391 | default: | ||
| 392 | fprintf(stderr, "Unhandled line in config file: %s\n", inbuf); | ||
| 393 | break; | ||
| 394 | parse_error: | ||
| 395 | fprintf(stderr, "Parse error in config file: %s\n", inbuf); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | fclose(config_file_handle); | ||
| 399 | return bound; | ||
| 400 | } | ||
| 401 | |||
| 402 | static void usage(char *name) { | ||
| 403 | fprintf(stderr, | ||
| 404 | "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" | ||
| 405 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 406 | " [-b blacklistfile]" | ||
| 407 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 408 | " [-w whitelistfile]" | ||
| 409 | #endif | ||
| 410 | "\n", | ||
| 411 | name); | ||
| 412 | } | ||
| 413 | |||
| 414 | #define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) | ||
| 415 | static void help(char *name) { | ||
| 416 | usage(name); | ||
| 417 | |||
| 418 | HELPLINE("-f config", "include and execute the config file"); | ||
| 419 | HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); | ||
| 420 | HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); | ||
| 421 | HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); | ||
| 422 | HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); | ||
| 423 | HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); | ||
| 424 | HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); | ||
| 425 | HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); | ||
| 426 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 427 | HELPLINE("-b file", "specify blacklist file."); | ||
| 428 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 429 | HELPLINE("-w file", "specify whitelist file."); | ||
| 430 | #endif | ||
| 431 | |||
| 432 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); | ||
| 433 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); | ||
| 434 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); | ||
| 435 | fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); | ||
| 436 | } | ||
| 437 | #undef HELPLINE | ||
| 438 | |||
| 439 | |||
| 440 | static void listen_resolve_defaults(void) { | ||
| 441 | size_t i; | ||
| 442 | |||
| 443 | for (i = 0; i < g_listen_count; ++i) { | ||
| 444 | ot_listen *entry = g_listen + i; | ||
| 445 | |||
| 446 | if (entry->shared == OT_LISTEN_INHERIT) | ||
| 447 | entry->shared = g_listen_shared; | ||
| 448 | if (entry->workers == OT_LISTEN_INHERIT) | ||
| 449 | entry->workers = g_listen_workers; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | ot_listen *config_listen_entries(size_t *count) { | ||
| 454 | *count = g_listen_count; | ||
| 455 | return g_listen; | ||
| 456 | } | ||
| 457 | |||
| 458 | void config_parse(int argc, char **argv) { | ||
| 459 | ot_ip6 serverip; | ||
| 460 | ot_net tmpnet; | ||
| 461 | int bound = 0, scanon = 1; | ||
| 462 | uint64_t value; | ||
| 463 | |||
| 464 | memset(serverip, 0, sizeof(ot_ip6)); | ||
| 465 | #ifdef WANT_V4_ONLY | ||
| 466 | serverip[10] = serverip[11] = -1; | ||
| 467 | #endif | ||
| 468 | |||
| 469 | while (scanon) { | ||
| 470 | switch (getopt(argc, argv, | ||
| 471 | ":i:p:A:P:d:u:r:s:f:l:v" | ||
| 472 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 473 | "b:" | ||
| 474 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 475 | "w:" | ||
| 476 | #endif | ||
| 477 | "h")) { | ||
| 478 | case -1: | ||
| 479 | scanon = 0; | ||
| 480 | break; | ||
| 481 | case 'i': | ||
| 482 | if (!ip_scan(optarg, serverip)) { | ||
| 483 | usage(argv[0]); | ||
| 484 | exit(1); | ||
| 485 | } | ||
| 486 | break; | ||
| 487 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 488 | case 'b': | ||
| 489 | set_config_option(&g_accesslist_filename, optarg); | ||
| 490 | break; | ||
| 491 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 492 | case 'w': | ||
| 493 | set_config_option(&g_accesslist_filename, optarg); | ||
| 494 | break; | ||
| 495 | #endif | ||
| 496 | case 'p': | ||
| 497 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 498 | usage(argv[0]); | ||
| 499 | exit(1); | ||
| 500 | } | ||
| 501 | listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 502 | bound++; | ||
| 503 | break; | ||
| 504 | case 'P': | ||
| 505 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 506 | usage(argv[0]); | ||
| 507 | exit(1); | ||
| 508 | } | ||
| 509 | listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 510 | bound++; | ||
| 511 | break; | ||
| 512 | #ifdef WANT_SYNC_LIVE | ||
| 513 | case 's': | ||
| 514 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 515 | usage(argv[0]); | ||
| 516 | exit(1); | ||
| 517 | } | ||
| 518 | listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 519 | break; | ||
| 520 | #endif | ||
| 521 | case 'd': | ||
| 522 | set_config_option(&g_serverdir, optarg); | ||
| 523 | break; | ||
| 524 | case 'u': | ||
| 525 | set_config_option(&g_serveruser, optarg); | ||
| 526 | break; | ||
| 527 | case 'r': | ||
| 528 | set_config_option(&g_redirecturl, optarg); | ||
| 529 | break; | ||
| 530 | case 'l': | ||
| 531 | g_statefile = optarg; | ||
| 532 | break; | ||
| 533 | case 'A': | ||
| 534 | if (!scan_ip6_net(optarg, &tmpnet)) { | ||
| 535 | usage(argv[0]); | ||
| 536 | exit(1); | ||
| 537 | } | ||
| 538 | accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */ | ||
| 539 | break; | ||
| 540 | case 'f': | ||
| 541 | bound += parse_configfile(optarg); | ||
| 542 | break; | ||
| 543 | case 'h': | ||
| 544 | help(argv[0]); | ||
| 545 | exit(0); | ||
| 546 | case 'v': { | ||
| 547 | char buffer[8192]; | ||
| 548 | stats_return_tracker_version(buffer); | ||
| 549 | fputs(buffer, stderr); | ||
| 550 | exit(0); | ||
| 551 | } | ||
| 552 | default: | ||
| 553 | case '?': | ||
| 554 | usage(argv[0]); | ||
| 555 | exit(1); | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | if (!bound) { | ||
| 560 | listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 561 | listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 562 | } | ||
| 563 | |||
| 564 | listen_resolve_defaults(); | ||
| 565 | } | ||
diff --git a/ot_config.h b/ot_config.h new file mode 100644 index 0000000..059573a --- /dev/null +++ b/ot_config.h | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | #ifndef OT_CONFIG_H__ | ||
| 7 | #define OT_CONFIG_H__ | ||
| 8 | |||
| 9 | #include <stdint.h> | ||
| 10 | |||
| 11 | #include "trackerlogic.h" | ||
| 12 | |||
| 13 | /* An address opentracker was asked to listen on. Options the statement did | ||
| 14 | not name have been filled in from the file wide defaults by the time these | ||
| 15 | are handed out. sock is filled in by whoever binds them. */ | ||
| 16 | typedef struct { | ||
| 17 | ot_ip6 ip; | ||
| 18 | uint16_t port; | ||
| 19 | PROTO_FLAG proto; | ||
| 20 | int shared; | ||
| 21 | int workers; | ||
| 22 | int64_t sock; | ||
| 23 | } ot_listen; | ||
| 24 | |||
| 25 | extern const char *g_redirecturl; | ||
| 26 | extern const char *g_serverdir; | ||
| 27 | extern const char *g_serveruser; | ||
| 28 | extern const char *g_statefile; | ||
| 29 | |||
| 30 | /* Reads the command line and every config file it names. Exits on error. */ | ||
| 31 | void config_parse(int argc, char **argv); | ||
| 32 | |||
| 33 | ot_listen *config_listen_entries(size_t *count); | ||
| 34 | |||
| 35 | #endif | ||
diff --git a/ot_fullscrape.c b/ot_fullscrape.c index 9dcbe68..eade79d 100644 --- a/ot_fullscrape.c +++ b/ot_fullscrape.c | |||
| @@ -83,7 +83,7 @@ void fullscrape_deinit(void) { | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { | 85 | void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { |
| 86 | mutex_workqueue_pushtask( sock, tasktype ); | 86 | mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); |
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { | 89 | static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { |
| @@ -37,9 +37,9 @@ | |||
| 37 | 37 | ||
| 38 | #define OT_MAXMULTISCRAPE_COUNT 64 | 38 | #define OT_MAXMULTISCRAPE_COUNT 64 |
| 39 | #define OT_BATCH_LIMIT (1024 * 1024 * 16) | 39 | #define OT_BATCH_LIMIT (1024 * 1024 * 16) |
| 40 | extern char *g_redirecturl; | 40 | extern const char *g_redirecturl; |
| 41 | 41 | ||
| 42 | char *g_stats_path; | 42 | const char *g_stats_path; |
| 43 | ssize_t g_stats_path_len; | 43 | ssize_t g_stats_path_len; |
| 44 | 44 | ||
| 45 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; | 45 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; |
| @@ -125,9 +125,9 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) { | |||
| 125 | #define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404) | 125 | #define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404) |
| 126 | #define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500) | 126 | #define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500) |
| 127 | ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { | 127 | ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { |
| 128 | char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required", | 128 | const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required", |
| 129 | "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"}; | 129 | "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"}; |
| 130 | char *title = error_code[code]; | 130 | const char *title = error_code[code]; |
| 131 | 131 | ||
| 132 | ws->reply = ws->outbuf; | 132 | ws->reply = ws->outbuf; |
| 133 | if (code == CODE_HTTPERROR_302) | 133 | if (code == CODE_HTTPERROR_302) |
| @@ -216,11 +216,14 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t | |||
| 216 | /* If the current batch's limit is reached, try to reallocate a new batch to work on */ | 216 | /* If the current batch's limit is reached, try to reallocate a new batch to work on */ |
| 217 | if (current->bytesleft > OT_BATCH_LIMIT) { | 217 | if (current->bytesleft > OT_BATCH_LIMIT) { |
| 218 | io_batch *new_batch = realloc(cookie->batch, (cookie->batches + 1) * sizeof(io_batch)); | 218 | io_batch *new_batch = realloc(cookie->batch, (cookie->batches + 1) * sizeof(io_batch)); |
| 219 | |||
| 219 | if (new_batch) { | 220 | if (new_batch) { |
| 220 | cookie->batch = new_batch; | 221 | cookie->batch = new_batch; |
| 221 | if (OT_IOB_INIT(current) != -1) | 222 | if (OT_IOB_INIT(cookie->batch + cookie->batches) != -1) |
| 222 | current = cookie->batch + cookie->batches++; | 223 | ++cookie->batches; |
| 223 | } | 224 | } |
| 225 | /* realloc may have moved the batches, so current is stale either way */ | ||
| 226 | current = cookie->batch + cookie->batches - 1; | ||
| 224 | } | 227 | } |
| 225 | iob_addbuf_free(current, iovector[i].iov_base, iovector[i].iov_len); | 228 | iob_addbuf_free(current, iovector[i].iov_base, iovector[i].iov_len); |
| 226 | } | 229 | } |
| @@ -432,7 +435,7 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct * | |||
| 432 | /* Clients waiting for us should not easily timeout */ | 435 | /* Clients waiting for us should not easily timeout */ |
| 433 | taia_uint(&t, 0); | 436 | taia_uint(&t, 0); |
| 434 | io_timeout(sock, t); | 437 | io_timeout(sock, t); |
| 435 | fullscrape_deliver(sock, TASK_FULLSCRAPE | format); | 438 | fullscrape_deliver(sock, format); |
| 436 | io_dontwantread(sock); | 439 | io_dontwantread(sock); |
| 437 | return ws->reply_size = -2; | 440 | return ws->reply_size = -2; |
| 438 | } | 441 | } |
| @@ -491,7 +494,7 @@ unsigned long long numwants[201]; | |||
| 491 | #endif | 494 | #endif |
| 492 | 495 | ||
| 493 | #if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY) | 496 | #if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY) |
| 494 | static char *http_header(char *data, size_t byte_count, char *header) { | 497 | static char *http_header(char *data, size_t byte_count, const char *header) { |
| 495 | size_t i; | 498 | size_t i; |
| 496 | long sl = strlen(header); | 499 | long sl = strlen(header); |
| 497 | for (i = 0; i + sl + 2 < byte_count; ++i) { | 500 | for (i = 0; i + sl + 2 < byte_count; ++i) { |
| @@ -37,7 +37,7 @@ ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); | |||
| 37 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); | 37 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); |
| 38 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); | 38 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); |
| 39 | 39 | ||
| 40 | extern char *g_stats_path; | 40 | extern const char *g_stats_path; |
| 41 | extern ssize_t g_stats_path_len; | 41 | extern ssize_t g_stats_path_len; |
| 42 | 42 | ||
| 43 | #endif | 43 | #endif |
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); |
| @@ -53,7 +53,7 @@ static unsigned long long ot_full_scrape_count; | |||
| 53 | static unsigned long long ot_full_scrape_request_count; | 53 | static unsigned long long ot_full_scrape_request_count; |
| 54 | static unsigned long long ot_full_scrape_size; | 54 | static unsigned long long ot_full_scrape_size; |
| 55 | static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT]; | 55 | static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT]; |
| 56 | static char *ot_failed_request_names[] = { | 56 | static const char *ot_failed_request_names[] = { |
| 57 | "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", | 57 | "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", |
| 58 | "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"}; | 58 | "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"}; |
| 59 | static unsigned long long ot_renewed[OT_PEER_TIMEOUT]; | 59 | static unsigned long long ot_renewed[OT_PEER_TIMEOUT]; |
| @@ -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/ot_vector.c b/ot_vector.c index c4cd171..7f18246 100644 --- a/ot_vector.c +++ b/ot_vector.c | |||
| @@ -8,7 +8,6 @@ | |||
| 8 | #include <stdint.h> | 8 | #include <stdint.h> |
| 9 | #include <stdlib.h> | 9 | #include <stdlib.h> |
| 10 | #include <string.h> | 10 | #include <string.h> |
| 11 | #include <strings.h> | ||
| 12 | 11 | ||
| 13 | /* Opentracker */ | 12 | /* Opentracker */ |
| 14 | #include "trackerlogic.h" | 13 | #include "trackerlogic.h" |
| @@ -174,7 +173,7 @@ void vector_clean_list(ot_vector *vector, size_t num_buckets) { | |||
| 174 | } | 173 | } |
| 175 | 174 | ||
| 176 | void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { | 175 | void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { |
| 177 | size_t tmp, bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1; | 176 | size_t bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1; |
| 178 | ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers; | 177 | ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers; |
| 179 | int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4; | 178 | int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4; |
| 180 | 179 | ||
| @@ -205,11 +204,10 @@ void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { | |||
| 205 | bucket_list_new = malloc(num_buckets_new * sizeof(ot_vector)); | 204 | bucket_list_new = malloc(num_buckets_new * sizeof(ot_vector)); |
| 206 | if (!bucket_list_new) | 205 | if (!bucket_list_new) |
| 207 | return; | 206 | return; |
| 208 | bzero(bucket_list_new, num_buckets_new * sizeof(ot_vector)); | 207 | memset(bucket_list_new, 0, num_buckets_new * sizeof(ot_vector)); |
| 209 | 208 | ||
| 210 | tmp = peer_list->peer_count / num_buckets_new; | ||
| 211 | bucket_size_new = OT_VECTOR_MIN_MEMBERS; | 209 | bucket_size_new = OT_VECTOR_MIN_MEMBERS; |
| 212 | while (bucket_size_new < tmp) | 210 | while (bucket_size_new * num_buckets_new < peer_list->peer_count) |
| 213 | bucket_size_new *= OT_VECTOR_GROW_RATIO; | 211 | bucket_size_new *= OT_VECTOR_GROW_RATIO; |
| 214 | 212 | ||
| 215 | /* preallocate vectors to hold all peers */ | 213 | /* preallocate vectors to hold all peers */ |
| @@ -72,7 +72,7 @@ static void *livesync_worker(void *args); | |||
| 72 | static void *streamsync_worker(void *args); | 72 | static void *streamsync_worker(void *args); |
| 73 | static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer); | 73 | static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer); |
| 74 | 74 | ||
| 75 | void exerr(char *message) { | 75 | void exerr(const char *message) { |
| 76 | fprintf(stderr, "%s\n", message); | 76 | fprintf(stderr, "%s\n", message); |
| 77 | exit(111); | 77 | exit(111); |
| 78 | } | 78 | } |
| @@ -485,7 +485,7 @@ static void panic(const char *routine) { | |||
| 485 | } | 485 | } |
| 486 | 486 | ||
| 487 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) { | 487 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) { |
| 488 | int64_t sock = socket_tcp_listen(ip, port); | 488 | int64_t sock = socket_tcp_listen(ip, port, SOCKET_EXCLUSIVE); |
| 489 | 489 | ||
| 490 | if (sock == -1) | 490 | if (sock == -1) |
| 491 | panic("socket_tcp_listen"); | 491 | panic("socket_tcp_listen"); |
diff --git a/scan_urlencoded_query.h b/scan_urlencoded_query.h index d0929cf..f71752d 100644 --- a/scan_urlencoded_query.h +++ b/scan_urlencoded_query.h | |||
| @@ -11,8 +11,8 @@ | |||
| 11 | #include <sys/types.h> | 11 | #include <sys/types.h> |
| 12 | 12 | ||
| 13 | typedef struct { | 13 | typedef struct { |
| 14 | char *key; | 14 | const char *key; |
| 15 | int value; | 15 | int value; |
| 16 | } ot_keywords; | 16 | } ot_keywords; |
| 17 | 17 | ||
| 18 | typedef enum { | 18 | typedef enum { |
diff --git a/tests/runner.c b/tests/runner.c index db62246..972a56a 100644 --- a/tests/runner.c +++ b/tests/runner.c | |||
| @@ -23,6 +23,7 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle | |||
| 23 | {"ot_buf", test_buf}, | 23 | {"ot_buf", test_buf}, |
| 24 | {"ot_ip", test_ip}, | 24 | {"ot_ip", test_ip}, |
| 25 | {"ot_socket", test_socket}, | 25 | {"ot_socket", test_socket}, |
| 26 | {"ot_mutex", test_mutex}, | ||
| 26 | {"ot_accesslist", test_accesslist}, | 27 | {"ot_accesslist", test_accesslist}, |
| 27 | {NULL, NULL}}; | 28 | {NULL, NULL}}; |
| 28 | 29 | ||
diff --git a/tests/stubs.c b/tests/stubs.c index ce93071..09a7312 100644 --- a/tests/stubs.c +++ b/tests/stubs.c | |||
| @@ -18,3 +18,12 @@ void free_peerlist(ot_peerlist *peer_list) { (void)peer_list; } | |||
| 18 | neither of which any test drives. */ | 18 | neither of which any test drives. */ |
| 19 | time_t g_now_seconds; | 19 | time_t g_now_seconds; |
| 20 | volatile int g_opentracker_running; | 20 | volatile int g_opentracker_running; |
| 21 | |||
| 22 | /* ot_mutex.c wakes the main loop through the self pipe when a result is | ||
| 23 | ready. The queue tests never get that far. */ | ||
| 24 | int g_self_pipe[2] = {-1, -1}; | ||
| 25 | long io_trywrite(long d, const char *buf, long len) { | ||
| 26 | (void)d; | ||
| 27 | (void)buf; | ||
| 28 | return len; | ||
| 29 | } | ||
diff --git a/tests/t_mutex.c b/tests/t_mutex.c new file mode 100644 index 0000000..3307d70 --- /dev/null +++ b/tests/t_mutex.c | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <stdlib.h> | ||
| 8 | #include <string.h> | ||
| 9 | |||
| 10 | /* Opentracker */ | ||
| 11 | #include "ot_mutex.h" | ||
| 12 | #include "test.h" | ||
| 13 | |||
| 14 | /* A worker asks for a class of task and blocks until one turns up, so a task | ||
| 15 | whose class matches no worker is never claimed and whoever is waiting for | ||
| 16 | its answer waits forever. These cases pin the matching. */ | ||
| 17 | |||
| 18 | static void case_task_goes_to_its_own_class(void) { | ||
| 19 | ot_tasktype asked; | ||
| 20 | ot_taskid taskid; | ||
| 21 | |||
| 22 | test_case("a worker is handed the task of the class it asked for"); | ||
| 23 | CHECK_INT(mutex_workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0); | ||
| 24 | asked = TASK_FULLSCRAPE; | ||
| 25 | taskid = mutex_workqueue_poptask(&asked); | ||
| 26 | CHECK(taskid != 0); | ||
| 27 | |||
| 28 | test_case("and the full task type, not just its class"); | ||
| 29 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII); | ||
| 30 | mutex_workqueue_canceltask(41); | ||
| 31 | } | ||
| 32 | |||
| 33 | static void case_flags_survive_the_queue(void) { | ||
| 34 | ot_tasktype asked; | ||
| 35 | |||
| 36 | test_case("flags outside the class mask are carried through"); | ||
| 37 | CHECK_INT(mutex_workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0); | ||
| 38 | asked = TASK_FULLSCRAPE; | ||
| 39 | CHECK(mutex_workqueue_poptask(&asked) != 0); | ||
| 40 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP); | ||
| 41 | mutex_workqueue_canceltask(42); | ||
| 42 | } | ||
| 43 | |||
| 44 | static void case_classes_do_not_cross(void) { | ||
| 45 | ot_tasktype asked; | ||
| 46 | ot_taskid taskid; | ||
| 47 | |||
| 48 | test_case("a stats worker passes over a fullscrape task"); | ||
| 49 | CHECK_INT(mutex_workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0); | ||
| 50 | CHECK_INT(mutex_workqueue_pushtask(44, TASK_STATS_PEERS), 0); | ||
| 51 | |||
| 52 | asked = TASK_STATS; | ||
| 53 | taskid = mutex_workqueue_poptask(&asked); | ||
| 54 | CHECK(taskid != 0); | ||
| 55 | CHECK_INT(asked, TASK_STATS_PEERS); | ||
| 56 | |||
| 57 | test_case("and leaves it for the one that wants it"); | ||
| 58 | asked = TASK_FULLSCRAPE; | ||
| 59 | taskid = mutex_workqueue_poptask(&asked); | ||
| 60 | CHECK(taskid != 0); | ||
| 61 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY); | ||
| 62 | |||
| 63 | mutex_workqueue_canceltask(43); | ||
| 64 | mutex_workqueue_canceltask(44); | ||
| 65 | } | ||
| 66 | |||
| 67 | static void case_every_task_gets_its_own_id(void) { | ||
| 68 | ot_tasktype asked; | ||
| 69 | ot_taskid first, second; | ||
| 70 | |||
| 71 | test_case("two queued tasks are handed out under different ids"); | ||
| 72 | CHECK_INT(mutex_workqueue_pushtask(45, TASK_FULLSCRAPE), 0); | ||
| 73 | CHECK_INT(mutex_workqueue_pushtask(46, TASK_FULLSCRAPE), 0); | ||
| 74 | |||
| 75 | asked = TASK_FULLSCRAPE; | ||
| 76 | first = mutex_workqueue_poptask(&asked); | ||
| 77 | asked = TASK_FULLSCRAPE; | ||
| 78 | second = mutex_workqueue_poptask(&asked); | ||
| 79 | CHECK(first != 0); | ||
| 80 | CHECK(second != 0); | ||
| 81 | CHECK(first != second); | ||
| 82 | |||
| 83 | mutex_workqueue_canceltask(45); | ||
| 84 | mutex_workqueue_canceltask(46); | ||
| 85 | } | ||
| 86 | |||
| 87 | static void case_cancelled_task_is_not_handed_out(void) { | ||
| 88 | ot_tasktype asked; | ||
| 89 | |||
| 90 | test_case("a cancelled task is gone before any worker sees it"); | ||
| 91 | CHECK_INT(mutex_workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0); | ||
| 92 | CHECK_INT(mutex_workqueue_pushtask(48, TASK_FULLSCRAPE), 0); | ||
| 93 | mutex_workqueue_canceltask(47); | ||
| 94 | |||
| 95 | asked = TASK_FULLSCRAPE; | ||
| 96 | CHECK(mutex_workqueue_poptask(&asked) != 0); | ||
| 97 | CHECK_INT(asked, TASK_FULLSCRAPE); | ||
| 98 | mutex_workqueue_canceltask(48); | ||
| 99 | } | ||
| 100 | |||
| 101 | void test_mutex(void) { | ||
| 102 | case_task_goes_to_its_own_class(); | ||
| 103 | case_flags_survive_the_queue(); | ||
| 104 | case_classes_do_not_cross(); | ||
| 105 | case_every_task_gets_its_own_id(); | ||
| 106 | case_cancelled_task_is_not_handed_out(); | ||
| 107 | } | ||
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/tests/t_vector.c b/tests/t_vector.c index fee336b..a8186b2 100644 --- a/tests/t_vector.c +++ b/tests/t_vector.c | |||
| @@ -4,10 +4,12 @@ | |||
| 4 | $id$ */ | 4 | $id$ */ |
| 5 | 5 | ||
| 6 | /* System */ | 6 | /* System */ |
| 7 | #include <stdint.h> | ||
| 7 | #include <stdlib.h> | 8 | #include <stdlib.h> |
| 8 | #include <string.h> | 9 | #include <string.h> |
| 9 | 10 | ||
| 10 | /* Opentracker */ | 11 | /* Opentracker */ |
| 12 | #include "ot_vector.h" | ||
| 11 | #include "trackerlogic.h" | 13 | #include "trackerlogic.h" |
| 12 | #include "test.h" | 14 | #include "test.h" |
| 13 | 15 | ||
| @@ -149,6 +151,105 @@ static void case_insert_compare_prefix(void) { | |||
| 149 | free(vector.data); | 151 | free(vector.data); |
| 150 | } | 152 | } |
| 151 | 153 | ||
| 154 | /* ------------------------------------------------- bucket redistribution */ | ||
| 155 | |||
| 156 | /* A flat peer list of count distinct v4 peers. The low byte of the index goes | ||
| 157 | into the first address byte, so the peers are distinct but arrive in no | ||
| 158 | particular order: a list generated in ascending order would come back | ||
| 159 | sorted whether or not vector_redistribute_buckets sorted it. */ | ||
| 160 | static void build_flat_peerlist(ot_peerlist *peer_list, size_t count) { | ||
| 161 | size_t i; | ||
| 162 | |||
| 163 | memset(peer_list, 0, sizeof(*peer_list)); | ||
| 164 | peer_list->peers.data = malloc(count * OT_PEER_SIZE4); | ||
| 165 | peer_list->peers.size = count; | ||
| 166 | peer_list->peers.space = count; | ||
| 167 | peer_list->peer_count = count; | ||
| 168 | |||
| 169 | for (i = 0; i < count; ++i) { | ||
| 170 | uint8_t *peer = (uint8_t *)peer_list->peers.data + i * OT_PEER_SIZE4; | ||
| 171 | |||
| 172 | memset(peer, 0, OT_PEER_SIZE4); | ||
| 173 | peer[0] = (uint8_t)i; | ||
| 174 | peer[1] = (uint8_t)(i >> 8); | ||
| 175 | peer[2] = (uint8_t)(i >> 16); | ||
| 176 | peer[3] = 1; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | static void case_redistribute_holds_every_peer(void) { | ||
| 181 | const size_t count = 513; | ||
| 182 | ot_peerlist peer_list; | ||
| 183 | size_t bucket, total_size = 0, empty = 0, largest = 0; | ||
| 184 | ot_vector *buckets; | ||
| 185 | |||
| 186 | test_case("a peer list past the threshold is split into buckets"); | ||
| 187 | build_flat_peerlist(&peer_list, count); | ||
| 188 | CHECK(peer_list.peers.data != NULL); | ||
| 189 | vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); | ||
| 190 | CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1); | ||
| 191 | CHECK_INT(peer_list.peers.size, 16); | ||
| 192 | |||
| 193 | buckets = peer_list.peers.data; | ||
| 194 | for (bucket = 0; bucket < peer_list.peers.size; ++bucket) { | ||
| 195 | total_size += buckets[bucket].size; | ||
| 196 | if (!buckets[bucket].size) | ||
| 197 | ++empty; | ||
| 198 | if (buckets[bucket].size > largest) | ||
| 199 | largest = buckets[bucket].size; | ||
| 200 | } | ||
| 201 | |||
| 202 | test_case("no peer is lost on the way"); | ||
| 203 | CHECK_INT(total_size, count); | ||
| 204 | |||
| 205 | /* The point of the exercise is that a lookup only searches one bucket, so | ||
| 206 | a hash that piled everything into one would defeat it. The input and the | ||
| 207 | hash are both fixed, so this is not a matter of luck. */ | ||
| 208 | test_case("and they are spread over the buckets rather than piled up"); | ||
| 209 | CHECK_INT(empty, 0); | ||
| 210 | CHECK(largest < count / 2); | ||
| 211 | |||
| 212 | vector_clean_list(peer_list.peers.data, peer_list.peers.size); | ||
| 213 | } | ||
| 214 | |||
| 215 | static void case_redistribute_sorts_each_bucket(void) { | ||
| 216 | const size_t count = 1000; | ||
| 217 | ot_peerlist peer_list; | ||
| 218 | size_t bucket, i, out_of_order = 0; | ||
| 219 | ot_vector *buckets; | ||
| 220 | |||
| 221 | test_case("every bucket comes back sorted, so bsearch can be used on it"); | ||
| 222 | build_flat_peerlist(&peer_list, count); | ||
| 223 | CHECK(peer_list.peers.data != NULL); | ||
| 224 | vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); | ||
| 225 | CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1); | ||
| 226 | |||
| 227 | buckets = peer_list.peers.data; | ||
| 228 | for (bucket = 0; bucket < peer_list.peers.size; ++bucket) | ||
| 229 | for (i = 1; i < buckets[bucket].size; ++i) { | ||
| 230 | const uint8_t *prev = (uint8_t *)buckets[bucket].data + (i - 1) * OT_PEER_SIZE4; | ||
| 231 | const uint8_t *next = (uint8_t *)buckets[bucket].data + i * OT_PEER_SIZE4; | ||
| 232 | |||
| 233 | if (memcmp(prev, next, OT_PEER_COMPARE_SIZE4) >= 0) | ||
| 234 | ++out_of_order; | ||
| 235 | } | ||
| 236 | CHECK_INT(out_of_order, 0); | ||
| 237 | |||
| 238 | vector_clean_list(peer_list.peers.data, peer_list.peers.size); | ||
| 239 | } | ||
| 240 | |||
| 241 | static void case_redistribute_below_threshold(void) { | ||
| 242 | ot_peerlist peer_list; | ||
| 243 | |||
| 244 | test_case("a small peer list is left flat"); | ||
| 245 | build_flat_peerlist(&peer_list, 100); | ||
| 246 | CHECK(peer_list.peers.data != NULL); | ||
| 247 | vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); | ||
| 248 | CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 0); | ||
| 249 | CHECK_INT(peer_list.peers.size, 100); | ||
| 250 | free(peer_list.peers.data); | ||
| 251 | } | ||
| 252 | |||
| 152 | void test_vector(void) { | 253 | void test_vector(void) { |
| 153 | case_search_finds_member(); | 254 | case_search_finds_member(); |
| 154 | case_search_finds_first_and_last(); | 255 | case_search_finds_first_and_last(); |
| @@ -158,4 +259,7 @@ void test_vector(void) { | |||
| 158 | case_insert_keeps_order(); | 259 | case_insert_keeps_order(); |
| 159 | case_insert_finds_existing(); | 260 | case_insert_finds_existing(); |
| 160 | case_insert_compare_prefix(); | 261 | case_insert_compare_prefix(); |
| 262 | case_redistribute_holds_every_peer(); | ||
| 263 | case_redistribute_sorts_each_bucket(); | ||
| 264 | case_redistribute_below_threshold(); | ||
| 161 | } | 265 | } |
diff --git a/tests/test.h b/tests/test.h index d79c4d2..9677524 100644 --- a/tests/test.h +++ b/tests/test.h | |||
| @@ -86,5 +86,6 @@ void test_scan(void); | |||
| 86 | void test_buf(void); | 86 | void test_buf(void); |
| 87 | void test_ip(void); | 87 | void test_ip(void); |
| 88 | void test_socket(void); | 88 | void test_socket(void); |
| 89 | void test_mutex(void); | ||
| 89 | 90 | ||
| 90 | #endif | 91 | #endif |
diff --git a/trackerlogic.c b/trackerlogic.c index db1d787..0fa7bfb 100644 --- a/trackerlogic.c +++ b/trackerlogic.c | |||
| @@ -550,7 +550,7 @@ void trackerlogic_add_random_torrents(size_t amount) { | |||
| 550 | } | 550 | } |
| 551 | #endif | 551 | #endif |
| 552 | 552 | ||
| 553 | void exerr(char *message) { | 553 | void exerr(const char *message) { |
| 554 | fprintf(stderr, "%s\n", message); | 554 | fprintf(stderr, "%s\n", message); |
| 555 | exit(111); | 555 | exit(111); |
| 556 | } | 556 | } |
| @@ -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) { |
diff --git a/trackerlogic.h b/trackerlogic.h index aa10a54..42018c3 100644 --- a/trackerlogic.h +++ b/trackerlogic.h | |||
| @@ -181,7 +181,7 @@ struct ot_workstruct { | |||
| 181 | 181 | ||
| 182 | void trackerlogic_init(void); | 182 | void trackerlogic_init(void); |
| 183 | void trackerlogic_deinit(void); | 183 | void trackerlogic_deinit(void); |
| 184 | void exerr(char *message); | 184 | void exerr(const char *message); |
| 185 | 185 | ||
| 186 | /* add_peer_to_torrent does only release the torrent bucket if from_sync is set, | 186 | /* add_peer_to_torrent does only release the torrent bucket if from_sync is set, |
| 187 | otherwise it is released in return_peers_for_torrent */ | 187 | otherwise it is released in return_peers_for_torrent */ |
