summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile12
-rw-r--r--opentracker.c478
-rw-r--r--opentracker.conf.sample28
-rw-r--r--ot_accesslist.c8
-rw-r--r--ot_accesslist.h6
-rw-r--r--ot_config.c565
-rw-r--r--ot_config.h35
-rw-r--r--ot_fullscrape.c2
-rw-r--r--ot_http.c19
-rw-r--r--ot_http.h2
-rw-r--r--ot_socket.c36
-rw-r--r--ot_socket.h9
-rw-r--r--ot_stats.c2
-rw-r--r--ot_udp.c24
-rw-r--r--ot_udp.h4
-rw-r--r--ot_vector.c8
-rw-r--r--proxy.c4
-rw-r--r--scan_urlencoded_query.h4
-rw-r--r--tests/runner.c1
-rw-r--r--tests/stubs.c9
-rw-r--r--tests/t_mutex.c107
-rw-r--r--tests/t_socket.c34
-rw-r--r--tests/t_vector.c104
-rw-r--r--tests/test.h1
-rw-r--r--trackerlogic.c3
-rw-r--r--trackerlogic.h2
26 files changed, 971 insertions, 536 deletions
diff --git a/Makefile b/Makefile
index 9a09e7b..00a9f93 100644
--- a/Makefile
+++ b/Makefile
@@ -64,12 +64,12 @@ GIT_VERSION=`sh -c 'command -v git >/dev/null && test -d .git && git rev-parse H
64OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage 64OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage
65OPTS_production=-O3 65OPTS_production=-O3
66 66
67CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra #-ansi -pedantic 67CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic
68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread 68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread
69 69
70BINARY =opentracker 70BINARY =opentracker
71HEADERS=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 71HEADERS=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
72SOURCES=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 72SOURCES=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
73SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 73SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
74 74
75OBJECTS = $(SOURCES:%.c=%.o) 75OBJECTS = $(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.
121TESTBIN=tests/runner 121TESTBIN=tests/runner
122TEST_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 122TEST_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
123TEST_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 123TEST_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
124TEST_HEADERS=tests/test.h $(HEADERS) 124TEST_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
133check: $(TESTBIN) 133check: $(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 */
43time_t g_now_seconds; 44time_t g_now_seconds;
44char *g_redirecturl;
45uint32_t g_tracker_id; 45uint32_t g_tracker_id;
46volatile int g_opentracker_running = 1; 46volatile int g_opentracker_running = 1;
47int g_self_pipe[2]; 47int g_self_pipe[2];
48 48
49static char *g_serverdir;
50static char *g_serveruser;
51static unsigned int g_udp_workers;
52 49
53static void panic(const char *routine) __attribute__((noreturn)); 50static void panic(const char *routine) __attribute__((noreturn));
54static void panic(const char *routine) { 51static 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
103static 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)
116static 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
140static ssize_t header_complete(char *request, ssize_t byte_count) { 101static 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
341static int64_t ot_try_bind(ot_ip6 ip, uint16_t port, PROTO_FLAG proto) { 302
342 int64_t sock; 303static 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
381char *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); 348static 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
391static 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
422static int scan_ip6_net(const char *src, ot_net *net) { 356static 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
447typedef 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
468typedef 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
476static 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};
506static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]);
507
508static 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
523int 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
657void load_state(const char *const state_filename) { 373void load_state(const char *const state_filename) {
@@ -745,115 +461,12 @@ static void *time_caching_worker(void *args) {
745} 461}
746 462
747int main(int argc, char **argv) { 463int 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
35char *g_accesslist_filename = NULL; 35const char *g_accesslist_filename = NULL;
36#ifdef WANT_DYNAMIC_ACCESSLIST 36#ifdef WANT_DYNAMIC_ACCESSLIST
37char *g_accesslist_pipe_add = NULL; 37const char *g_accesslist_pipe_add = NULL;
38char *g_accesslist_pipe_delete = NULL; 38const char *g_accesslist_pipe_delete = NULL;
39#endif 39#endif
40static pthread_mutex_t g_accesslist_mutex; 40static 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
274static pthread_t thread_adder_id, thread_deleter_id; 274static pthread_t thread_adder_id, thread_deleter_id;
275static void *accesslist_adddel_worker(char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { 275static 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 */
24size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len); 24size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len);
25 25
26extern char *g_accesslist_filename; 26extern const char *g_accesslist_filename;
27#ifdef WANT_DYNAMIC_ACCESSLIST 27#ifdef WANT_DYNAMIC_ACCESSLIST
28extern char *g_accesslist_pipe_add; 28extern const char *g_accesslist_pipe_add;
29extern char *g_accesslist_pipe_delete; 29extern 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
25const char *g_redirecturl;
26const char *g_serverdir;
27const char *g_serveruser;
28const char *g_statefile;
29
30#define OT_LISTEN_INHERIT (-1)
31
32static ot_listen *g_listen;
33static size_t g_listen_count;
34static int g_listen_shared;
35static int g_listen_workers;
36
37static void panic(const char *routine) __attribute__((noreturn));
38static void panic(const char *routine) {
39 fprintf(stderr, "%s: %s\n", routine, strerror(errno));
40 exit(111);
41}
42
43static 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
60typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind;
61
62typedef 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
71static 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};
75static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]);
76
77static 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 */
96static 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
125static 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
135static 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
166static 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
191typedef 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
212typedef 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
220static 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};
250static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]);
251
252static 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
267int 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
402static 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)
415static 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
440static 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
453ot_listen *config_listen_entries(size_t *count) {
454 *count = g_listen_count;
455 return g_listen;
456}
457
458void 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. */
16typedef 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
25extern const char *g_redirecturl;
26extern const char *g_serverdir;
27extern const char *g_serveruser;
28extern const char *g_statefile;
29
30/* Reads the command line and every config file it names. Exits on error. */
31void config_parse(int argc, char **argv);
32
33ot_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
85void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { 85void 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
89static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { 89static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) {
diff --git a/ot_http.c b/ot_http.c
index 46ca5a9..4c4121b 100644
--- a/ot_http.c
+++ b/ot_http.c
@@ -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)
40extern char *g_redirecturl; 40extern const char *g_redirecturl;
41 41
42char *g_stats_path; 42const char *g_stats_path;
43ssize_t g_stats_path_len; 43ssize_t g_stats_path_len;
44 44
45enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; 45enum { 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)
127ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { 127ssize_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)
494static char *http_header(char *data, size_t byte_count, char *header) { 497static 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) {
diff --git a/ot_http.h b/ot_http.h
index 965820f..abad83a 100644
--- a/ot_http.h
+++ b/ot_http.h
@@ -37,7 +37,7 @@ ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws);
37ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); 37ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial);
38ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); 38ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code);
39 39
40extern char *g_stats_path; 40extern const char *g_stats_path;
41extern ssize_t g_stats_path_len; 41extern 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
18int g_socket_reuseport;
19
20static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { 18static 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
78static 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; 77static 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
87static 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
92int socket_tcp_listen(const ot_ip6 ip, uint16_t port) { 96int 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
103int socket_udp_bind(const ot_ip6 ip, uint16_t port) { 107int 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 */
190static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { 196static 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
19extern int g_socket_reuseport; 19 so differs between kernels, which is socket_set_sharing's business */
20typedef 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 */
23int socket_tcp_listen(const ot_ip6 ip, uint16_t port); 24int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing);
24int socket_udp_bind(const ot_ip6 ip, uint16_t port); 25int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing);
25 26
26int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); 27int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport);
27int socket_is_connected(int s); 28int socket_is_connected(int s);
diff --git a/ot_stats.c b/ot_stats.c
index f938e91..6589aef 100644
--- a/ot_stats.c
+++ b/ot_stats.c
@@ -53,7 +53,7 @@ static unsigned long long ot_full_scrape_count;
53static unsigned long long ot_full_scrape_request_count; 53static unsigned long long ot_full_scrape_request_count;
54static unsigned long long ot_full_scrape_size; 54static unsigned long long ot_full_scrape_size;
55static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT]; 55static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT];
56static char *ot_failed_request_names[] = { 56static 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"};
59static unsigned long long ot_renewed[OT_PEER_TIMEOUT]; 59static unsigned long long ot_renewed[OT_PEER_TIMEOUT];
diff --git a/ot_udp.c b/ot_udp.c
index dc8267c..7f6517f 100644
--- a/ot_udp.c
+++ b/ot_udp.c
@@ -26,9 +26,6 @@ static uint32_t g_rijndael_round_key[44] = {0};
26static volatile uint32_t g_key_of_the_hour[2] = {0}; 26static volatile uint32_t g_key_of_the_hour[2] = {0};
27static volatile ot_time g_hour_of_the_key; 27static volatile ot_time g_hour_of_the_key;
28 28
29static pthread_mutex_t udp_start_mutex = PTHREAD_MUTEX_INITIALIZER;
30static pthread_cond_t udp_start_cond = PTHREAD_COND_INITIALIZER;
31static int g_opentracker_started = 0;
32 29
33static void udp_generate_rijndael_round_key(void) { 30static 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
230void udp_init(int64_t sock, unsigned int worker_count) { 222void 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
227void 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
241void 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}
diff --git a/ot_udp.h b/ot_udp.h
index 87d99a9..a8ab9fc 100644
--- a/ot_udp.h
+++ b/ot_udp.h
@@ -10,8 +10,8 @@
10 10
11#include "trackerlogic.h" 11#include "trackerlogic.h"
12 12
13void udp_init(int64_t sock, unsigned int worker_count); 13void udp_init(void);
14void udp_kickoff(void); 14void udp_workers_install(int64_t sock, unsigned int worker_count);
15int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); 15int 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
176void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { 175void 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 */
diff --git a/proxy.c b/proxy.c
index 16b8286..219d926 100644
--- a/proxy.c
+++ b/proxy.c
@@ -72,7 +72,7 @@ static void *livesync_worker(void *args);
72static void *streamsync_worker(void *args); 72static void *streamsync_worker(void *args);
73static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer); 73static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer);
74 74
75void exerr(char *message) { 75void 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
487static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) { 487static 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
13typedef struct { 13typedef struct {
14 char *key; 14 const char *key;
15 int value; 15 int value;
16} ot_keywords; 16} ot_keywords;
17 17
18typedef enum { 18typedef 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. */
19time_t g_now_seconds; 19time_t g_now_seconds;
20volatile int g_opentracker_running; 20volatile 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. */
24int g_self_pipe[2] = {-1, -1};
25long 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
18static 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
33static 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
44static 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
67static 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
87static 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
101void 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
109static void case_reuseport_is_opt_in(void) { 109static 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) {
294void test_socket(void) { 290void 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. */
160static 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
180static 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
215static 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
241static 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
152void test_vector(void) { 253void 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);
86void test_buf(void); 86void test_buf(void);
87void test_ip(void); 87void test_ip(void);
88void test_socket(void); 88void test_socket(void);
89void 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
553void exerr(char *message) { 553void 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
575void trackerlogic_deinit(void) { 574void 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
182void trackerlogic_init(void); 182void trackerlogic_init(void);
183void trackerlogic_deinit(void); 183void trackerlogic_deinit(void);
184void exerr(char *message); 184void 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 */