summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile14
-rw-r--r--opentracker.c516
-rw-r--r--opentracker.conf.sample28
-rw-r--r--ot_accesslist.c8
-rw-r--r--ot_accesslist.h6
-rw-r--r--ot_batch.c158
-rw-r--r--ot_batch.h48
-rw-r--r--ot_config.c565
-rw-r--r--ot_config.h35
-rw-r--r--ot_fullscrape.c2
-rw-r--r--ot_http.c85
-rw-r--r--ot_http.h9
-rw-r--r--ot_socket.c36
-rw-r--r--ot_socket.h9
-rw-r--r--ot_stats.c2
-rw-r--r--ot_timeout.h39
-rw-r--r--ot_udp.c24
-rw-r--r--ot_udp.h4
-rw-r--r--ot_vector.c8
-rw-r--r--proxy.c18
-rw-r--r--scan_urlencoded_query.h4
-rw-r--r--tests/runner.c2
-rw-r--r--tests/stubs.c9
-rw-r--r--tests/t_batch.c348
-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.h2
-rw-r--r--trackerlogic.c6
-rw-r--r--trackerlogic.h2
30 files changed, 1600 insertions, 632 deletions
diff --git a/Makefile b/Makefile
index 9a09e7b..c7105d4 100644
--- a/Makefile
+++ b/Makefile
@@ -64,13 +64,13 @@ 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_batch.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_batch.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_batch.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)
76OBJECTS_debug = $(SOURCES:%.c=%.debug.o) 76OBJECTS_debug = $(SOURCES:%.c=%.debug.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 tests/t_batch.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_batch.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..60bdceb 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -23,10 +23,10 @@
23 23
24/* Libowfat */ 24/* Libowfat */
25#include "io.h" 25#include "io.h"
26#include "iob.h"
27 26
28/* Opentracker */ 27/* Opentracker */
29#include "ot_accesslist.h" 28#include "ot_accesslist.h"
29#include "ot_config.h"
30#include "ot_hex.h" 30#include "ot_hex.h"
31#include "ot_http.h" 31#include "ot_http.h"
32#include "ot_ip.h" 32#include "ot_ip.h"
@@ -35,20 +35,17 @@
35#include "ot_random.h" 35#include "ot_random.h"
36#include "ot_scan.h" 36#include "ot_scan.h"
37#include "ot_socket.h" 37#include "ot_socket.h"
38#include "ot_timeout.h"
38#include "ot_stats.h" 39#include "ot_stats.h"
39#include "ot_udp.h" 40#include "ot_udp.h"
40#include "trackerlogic.h" 41#include "trackerlogic.h"
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;
@@ -159,9 +120,8 @@ static ssize_t header_complete(char *request, ssize_t byte_count) {
159static void handle_dead(const int64_t sock) { 120static void handle_dead(const int64_t sock) {
160 struct http_data *cookie = io_getcookie(sock); 121 struct http_data *cookie = io_getcookie(sock);
161 if (cookie) { 122 if (cookie) {
162 size_t i; 123 if (cookie->batch)
163 for (i = 0; i < cookie->batches; ++i) 124 batch_reset(cookie->batch);
164 iob_reset(cookie->batch + i);
165 free(cookie->batch); 125 free(cookie->batch);
166 buf_reset(&cookie->request); 126 buf_reset(&cookie->request);
167 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) 127 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))
@@ -217,33 +177,23 @@ static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
217 177
218static void handle_write(const int64_t sock) { 178static void handle_write(const int64_t sock) {
219 struct http_data *cookie = io_getcookie(sock); 179 struct http_data *cookie = io_getcookie(sock);
220 size_t i;
221 int chunked = 0; 180 int chunked = 0;
222 181
223 /* Look for the first io_batch still containing bytes to write */
224 if (cookie) { 182 if (cookie) {
225 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) 183 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)
226 chunked = 1; 184 chunked = 1;
227 185
228 for (i = 0; i < cookie->batches; ++i) { 186 if (cookie->batch) {
229 if (cookie->batch[i].bytesleft) { 187 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
230 int64_t res = iob_send(sock, cookie->batch + i); 188 handle_dead(sock);
231 189 return;
232 if (res == -3) {
233 handle_dead(sock);
234 return;
235 }
236
237 if (!cookie->batch[i].bytesleft)
238 continue;
239
240 if (res == -1 || res > 0 || i < cookie->batches - 1)
241 return;
242 } 190 }
191 if (batch_pending(cookie->batch))
192 return;
243 } 193 }
244 } 194 }
245 195
246 /* In a chunked transfer after all batches accumulated have been sent, wait for the next one */ 196 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
247 if (chunked) 197 if (chunked)
248 io_dontwantwrite(sock); 198 io_dontwantwrite(sock);
249 else 199 else
@@ -255,7 +205,6 @@ static void handle_accept(const int64_t serversocket) {
255 int64_t sock; 205 int64_t sock;
256 ot_ip6 ip; 206 ot_ip6 ip;
257 uint16_t port; 207 uint16_t port;
258 tai6464 t;
259 208
260 while ((sock = socket_accept(serversocket, ip, &port)) != -1) { 209 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
261 210
@@ -274,11 +223,7 @@ static void handle_accept(const int64_t serversocket) {
274 223
275 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); 224 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
276 225
277 /* That breaks taia encapsulation. But there is no way to take system 226 timeout_in(sock, OT_CLIENT_TIMEOUT);
278 time this often in FreeBSD and libowfat does not allow to set unix time */
279 taia_uint(&t, 0); /* Clear t */
280 tai_unix(&(t.sec), (g_now_seconds + OT_CLIENT_TIMEOUT));
281 io_timeout(sock, t);
282 } 227 }
283 io_eagain(serversocket); 228 io_eagain(serversocket);
284} 229}
@@ -338,320 +283,75 @@ static void *server_mainloop(void *args) {
338 return 0; 283 return 0;
339} 284}
340 285
341static int64_t ot_try_bind(ot_ip6 ip, uint16_t port, PROTO_FLAG proto) { 286
342 int64_t sock; 287static void listen_bind_one(ot_listen *entry) {
288 socket_sharing sharing = entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE;
343 289
344#ifdef _DEBUG 290#ifdef _DEBUG
345 { 291 {
346 char *protos[] = {"TCP", "UDP", "UDP mcast"}; 292 const char *protos[] = {"TCP", "UDP", "UDP mcast"};
347 char _debug[512]; 293 char _debug[512];
348 int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[proto]); 294 int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[entry->proto]);
349 off += ip_format(_debug + off, ip); 295 off += ip_format(_debug + off, entry->ip);
350 snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", port); 296 snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", entry->port);
351 fputs(_debug, stderr); 297 fputs(_debug, stderr);
352 } 298 }
353#endif 299#endif
354 300
355 if (proto == FLAG_TCP) { 301#ifdef WANT_SYNC_LIVE
356 if ((sock = socket_tcp_listen(ip, port)) == -1) 302 if (entry->proto == FLAG_MCA) {
303 livesync_bind_mcast(entry->ip, entry->port);
304#ifdef _DEBUG
305 fputs(" success.\n", stderr);
306#endif
307 return;
308 }
309#endif
310
311 if (entry->proto == FLAG_TCP) {
312 if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1)
357 panic("socket_tcp_listen"); 313 panic("socket_tcp_listen");
358 } else { 314 } else {
359 if ((sock = socket_udp_bind(ip, port)) == -1) 315 if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1)
360 panic("socket_udp_bind"); 316 panic("socket_udp_bind");
361 } 317 }
362 318
363 if (!io_fd(sock)) 319 if (!io_fd(entry->sock))
364 panic("io_fd"); 320 panic("io_fd");
365 321
366 io_setcookie(sock, (void *)proto); 322 io_setcookie(entry->sock, (void *)entry->proto);
367 323 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 324
374#ifdef _DEBUG 325#ifdef _DEBUG
375 fputs(" success.\n", stderr); 326 fputs(" success.\n", stderr);
376#endif 327#endif
377
378 return sock;
379} 328}
380 329
381char *set_config_option(char **option, char *value) { 330/* Bound in the order the entries were given.
382#ifdef _DEBUG 331 Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */
383 fprintf(stderr, "Setting config option: %s\n", value); 332static void listen_bind_all(void) {
384#endif 333 size_t count, i;
385 while (isspace(*value)) 334 ot_listen *entries = config_listen_entries(&count);
386 ++value;
387 free(*option);
388 return *option = strdup(value);
389}
390
391static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
392 const char *s = src;
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}
421 335
422static int scan_ip6_net(const char *src, ot_net *net) { 336 for (i = 0; i < count; ++i)
423 const char *s = src; 337 listen_bind_one(entries + i);
424 uint64_t value;
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} 338}
446 339
447typedef enum { 340static void listen_start_udp_workers(void) {
448 CONFIG_UNKNOWN, 341 size_t count, i;
449 CONFIG_TRACKER_ROOTDIR, 342 ot_listen *entries = config_listen_entries(&count);
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 343
468typedef struct { 344 udp_init();
469 const char *name;
470 size_t len;
471 config_kind kind;
472} config_key;
473 345
474#define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } 346 for (i = 0; i < count; ++i) {
347 ot_listen *entry = entries + i;
475 348
476static const config_key config_keys[] = { 349 if (entry->proto != FLAG_UDP || entry->workers <= 0)
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; 350 continue;
551 351
552 /* consume trailing new lines and spaces */ 352 io_block(entry->sock);
553 end = p + strlen(p); 353 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 } 354 }
653 fclose(config_file_handle);
654 return bound;
655} 355}
656 356
657void load_state(const char *const state_filename) { 357void load_state(const char *const state_filename) {
@@ -745,115 +445,12 @@ static void *time_caching_worker(void *args) {
745} 445}
746 446
747int main(int argc, char **argv) { 447int 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 */ 448 pthread_t thread_id; /* time cacher */
754 449
755 memset(serverip, 0, sizeof(ot_ip6));
756#ifdef WANT_V4_ONLY
757 serverip[10] = serverip[11] = -1;
758#endif
759
760 random_init(); 450 random_init();
451 config_parse(argc, argv);
761 452
762 while (scanon) { 453 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 454
858 defaul_signal_handlers(); 455 defaul_signal_handlers();
859 456
@@ -888,13 +485,12 @@ int main(int argc, char **argv) {
888 fprintf(stderr, "... done.\n"); 485 fprintf(stderr, "... done.\n");
889#endif 486#endif
890 487
891 if (statefile) 488 if (g_statefile)
892 load_state(statefile); 489 load_state(g_statefile);
893 490
894 install_signal_handlers(); 491 install_signal_handlers();
895 492
896 if (!g_udp_workers) 493 listen_start_udp_workers();
897 udp_init(-1, 0);
898 494
899 server_mainloop(0); 495 server_mainloop(0);
900 496
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_batch.c b/ot_batch.c
new file mode 100644
index 0000000..84eb4a8
--- /dev/null
+++ b/ot_batch.c
@@ -0,0 +1,158 @@
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 <errno.h>
8#include <limits.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <sys/uio.h>
13#include <unistd.h>
14
15/* Opentracker */
16#include "ot_batch.h"
17
18#ifndef IOV_MAX
19#define IOV_MAX 1024
20#endif
21
22#define OT_BATCH_MIN_ENTRIES 16
23
24static int batch_room_for(ot_batch *batch, size_t wanted) {
25 size_t space = batch->space ? batch->space : OT_BATCH_MIN_ENTRIES;
26 struct iovec *grown;
27
28 if (batch->count + wanted <= batch->space)
29 return 0;
30
31 /* During a chunked transfer entries are appended while earlier ones are
32 still going out, so the front of the array frees up as we go. Slide the
33 rest down before asking for more. */
34 if (batch->first && batch->count - batch->first + wanted <= batch->space) {
35 memmove(batch->entries, batch->entries + batch->first, (batch->count - batch->first) * sizeof(struct iovec));
36 batch->count -= batch->first;
37 batch->first = 0;
38 return 0;
39 }
40
41 while (space < batch->count + wanted)
42 space *= 2;
43
44 if (!(grown = realloc(batch->entries, space * sizeof(struct iovec))))
45 return -1;
46
47 batch->entries = grown;
48 batch->space = space;
49 return 0;
50}
51
52int batch_add(ot_batch *batch, void *buf, size_t len) {
53 if (batch_room_for(batch, 1)) {
54 free(buf);
55 return -1;
56 }
57
58 batch->entries[batch->count].iov_base = buf;
59 batch->entries[batch->count].iov_len = len;
60 ++batch->count;
61 return 0;
62}
63
64int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count) {
65 if (batch_room_for(batch, count)) {
66 while (count--)
67 free(entries[count].iov_base);
68 return -1;
69 }
70
71 memcpy(batch->entries + batch->count, entries, count * sizeof(struct iovec));
72 batch->count += count;
73 return 0;
74}
75
76/* Drop everything the kernel accepted, freeing each buffer as it goes */
77static void batch_consume(ot_batch *batch, size_t written) {
78 while (written) {
79 struct iovec *entry = batch->entries + batch->first;
80 size_t left = entry->iov_len - batch->offset;
81
82 if (written < left) {
83 batch->offset += written;
84 return;
85 }
86
87 free(entry->iov_base);
88 written -= left;
89 batch->offset = 0;
90 ++batch->first;
91 }
92
93 /* Nothing is outstanding, so the array can start over from the front */
94 if (batch->first == batch->count)
95 batch->first = batch->count = 0;
96}
97
98/* A peer that has gone away must come back as an error rather than as a
99 signal that kills the tracker, so ask the kernel not to raise one where it
100 can be asked. macOS cannot, and relies on SIGPIPE being blocked. */
101static ssize_t batch_writev(int s, struct iovec *entries, int count) {
102#ifdef MSG_NOSIGNAL
103 struct msghdr msg;
104
105 memset(&msg, 0, sizeof(msg));
106 msg.msg_iov = entries;
107 msg.msg_iovlen = count;
108 return sendmsg(s, &msg, MSG_NOSIGNAL);
109#else
110 return writev(s, entries, count);
111#endif
112}
113
114ssize_t batch_send(int s, ot_batch *batch) {
115 size_t entries = batch->count - batch->first;
116 ssize_t written;
117
118 if (!batch_pending(batch))
119 return BATCH_DONE;
120
121 if (entries > IOV_MAX)
122 entries = IOV_MAX;
123
124 /* The first entry may be half way out already */
125 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base + batch->offset;
126 batch->entries[batch->first].iov_len -= batch->offset;
127 written = batch_writev(s, batch->entries + batch->first, (int)entries);
128 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base - batch->offset;
129 batch->entries[batch->first].iov_len += batch->offset;
130
131 if (written < 0)
132 return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ? BATCH_AGAIN : BATCH_FAILED;
133 if (!written)
134 return BATCH_FAILED;
135
136 batch_consume(batch, (size_t)written);
137 return written;
138}
139
140void batch_reset(ot_batch *batch) {
141 while (batch->first < batch->count)
142 free(batch->entries[batch->first++].iov_base);
143
144 free(batch->entries);
145 batch->entries = NULL;
146 batch->count = batch->first = batch->offset = batch->space = 0;
147}
148
149/* For the few constants the chunked encoding needs, which the batch frees
150 along with everything else */
151int batch_add_copy(ot_batch *batch, const void *buf, size_t len) {
152 void *copy = malloc(len);
153
154 if (!copy)
155 return -1;
156 memcpy(copy, buf, len);
157 return batch_add(batch, copy, len);
158}
diff --git a/ot_batch.h b/ot_batch.h
new file mode 100644
index 0000000..c855671
--- /dev/null
+++ b/ot_batch.h
@@ -0,0 +1,48 @@
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_BATCH_H__
7#define OT_BATCH_H__
8
9#include <stddef.h>
10#include <sys/types.h>
11#include <sys/uio.h>
12
13/* A queue of buffers on their way to one socket. The batch owns every buffer
14 handed to it and frees each one as soon as it has left, so a client that
15 reads slowly costs memory only for what it has not received yet.
16
17 A zeroed ot_batch is ready to use. */
18typedef struct {
19 struct iovec *entries;
20 size_t space;
21 size_t count;
22 size_t first; /* entries before this one are sent and freed */
23 size_t offset; /* bytes of entries[first] already sent */
24} ot_batch;
25
26static inline int batch_pending(const ot_batch *batch) { return batch->first != batch->count; }
27
28/* What batch_send reports instead of a byte count */
29enum { BATCH_DONE = 0, BATCH_AGAIN = -1, BATCH_FAILED = -2 };
30
31/* Queue buf and take ownership of it. Returns 0, or -1 having freed buf. */
32int batch_add(ot_batch *batch, void *buf, size_t len);
33
34/* Write as much as the socket takes. Returns the number of bytes written, or
35 BATCH_DONE, BATCH_AGAIN or BATCH_FAILED. */
36ssize_t batch_send(int s, ot_batch *batch);
37
38/* Queue every entry of a vector, taking ownership of each buffer but not of
39 the vector itself. Returns 0, or -1 having freed all of them. */
40int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count);
41
42/* Queue a copy of buf, which the batch then owns. Returns 0, or -1. */
43int batch_add_copy(ot_batch *batch, const void *buf, size_t len);
44
45/* Free whatever has not left yet, along with the batch's own array */
46void batch_reset(ot_batch *batch);
47
48#endif
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..331a457 100644
--- a/ot_http.c
+++ b/ot_http.c
@@ -15,7 +15,7 @@
15#include <unistd.h> 15#include <unistd.h>
16 16
17/* Libowfat */ 17/* Libowfat */
18#include "iob.h" 18#include "io.h"
19 19
20/* Opentracker */ 20/* Opentracker */
21#include "ot_accesslist.h" 21#include "ot_accesslist.h"
@@ -26,20 +26,14 @@
26#include "ot_mutex.h" 26#include "ot_mutex.h"
27#include "ot_scan.h" 27#include "ot_scan.h"
28#include "ot_stats.h" 28#include "ot_stats.h"
29#include "ot_timeout.h"
29#include "scan_urlencoded_query.h" 30#include "scan_urlencoded_query.h"
30#include "trackerlogic.h" 31#include "trackerlogic.h"
31 32
32#ifdef WANT_NO_AUTO_FREE
33#define OT_IOB_INIT(B) (bzero(B, sizeof(io_batch)), 0)
34#else
35#define OT_IOB_INIT(B) iob_init_autofree(B, 0)
36#endif
37
38#define OT_MAXMULTISCRAPE_COUNT 64 33#define OT_MAXMULTISCRAPE_COUNT 64
39#define OT_BATCH_LIMIT (1024 * 1024 * 16) 34extern const char *g_redirecturl;
40extern char *g_redirecturl;
41 35
42char *g_stats_path; 36const char *g_stats_path;
43ssize_t g_stats_path_len; 37ssize_t g_stats_path_len;
44 38
45enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; 39enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 };
@@ -79,7 +73,6 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
79 73
80 if (written_size < ws->reply_size) { 74 if (written_size < ws->reply_size) {
81 char *outbuf; 75 char *outbuf;
82 tai6464 t;
83 76
84 if (!(outbuf = malloc(ws->reply_size - written_size))) { 77 if (!(outbuf = malloc(ws->reply_size - written_size))) {
85 buf_reset(&cookie->request); 78 buf_reset(&cookie->request);
@@ -90,25 +83,21 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
90 83
91 memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size); 84 memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size);
92 if (!cookie->batch) { 85 if (!cookie->batch) {
93 cookie->batch = malloc(sizeof(io_batch)); 86 cookie->batch = calloc(1, sizeof(ot_batch));
94 if (!cookie->batch || OT_IOB_INIT(cookie->batch) == -1) { 87 if (!cookie->batch) {
95 free(cookie->batch);
96 free(outbuf); 88 free(outbuf);
97 buf_reset(&cookie->request); 89 buf_reset(&cookie->request);
98 free(cookie); 90 free(cookie);
99 io_close(sock); 91 io_close(sock);
100 return; 92 return;
101 } 93 }
102
103 cookie->batches = 1;
104 } 94 }
105 95
106 iob_addbuf_free(cookie->batch, outbuf, ws->reply_size - written_size); 96 batch_add(cookie->batch, outbuf, ws->reply_size - written_size);
107 97
108 /* writeable short data sockets just have a tcp timeout */ 98 /* writeable short data sockets just have a tcp timeout */
109 if (!ws->keep_alive) { 99 if (!ws->keep_alive) {
110 taia_uint(&t, 0); 100 timeout_never(sock);
111 io_timeout(sock, t);
112 io_dontwantread(sock); 101 io_dontwantread(sock);
113 } 102 }
114 io_wantwrite(sock); 103 io_wantwrite(sock);
@@ -125,9 +114,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) 114#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) 115#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) { 116ssize_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", 117 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"}; 118 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"};
130 char *title = error_code[code]; 119 const char *title = error_code[code];
131 120
132 ws->reply = ws->outbuf; 121 ws->reply = ws->outbuf;
133 if (code == CODE_HTTPERROR_302) 122 if (code == CODE_HTTPERROR_302)
@@ -146,11 +135,9 @@ ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code)
146 135
147ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { 136ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) {
148 struct http_data *cookie = io_getcookie(sock); 137 struct http_data *cookie = io_getcookie(sock);
149 io_batch *current;
150 char *header; 138 char *header;
151 const char *encoding = ""; 139 const char *encoding = "";
152 size_t i, header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); 140 size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector);
153 tai6464 t;
154 141
155 /* No cookie? Bad socket. Leave. */ 142 /* No cookie? Bad socket. Leave. */
156 if (!cookie) { 143 if (!cookie) {
@@ -199,46 +186,28 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
199 } 186 }
200 187
201 if (!cookie->batch) { 188 if (!cookie->batch) {
202 cookie->batch = malloc(sizeof(io_batch)); 189 cookie->batch = calloc(1, sizeof(ot_batch));
203 if (!cookie->batch || OT_IOB_INIT(cookie->batch) == -1) { 190 if (!cookie->batch) {
204 free(cookie->batch);
205 free(header); 191 free(header);
206 iovec_free(&iovec_entries, &iovector); 192 iovec_free(&iovec_entries, &iovector);
207 HTTPERROR_500; 193 HTTPERROR_500;
208 } 194 }
209 cookie->batches = 1;
210 } 195 }
211 current = cookie->batch + cookie->batches - 1;
212 iob_addbuf_free(current, header, header_size);
213 196
214 /* Split huge iovectors into separate io_batches */ 197 batch_add(cookie->batch, header, header_size);
215 for (i = 0; i < (size_t)iovec_entries; ++i) { 198 batch_add_vector(cookie->batch, iovector, iovec_entries);
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) {
218 io_batch *new_batch = realloc(cookie->batch, (cookie->batches + 1) * sizeof(io_batch));
219 if (new_batch) {
220 cookie->batch = new_batch;
221 if (OT_IOB_INIT(current) != -1)
222 current = cookie->batch + cookie->batches++;
223 }
224 }
225 iob_addbuf_free(current, iovector[i].iov_base, iovector[i].iov_len);
226 }
227 free(iovector); 199 free(iovector);
200
228 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) 201 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)
229 iob_addbuf(current, "\r\n", 2); 202 batch_add_copy(cookie->batch, "\r\n", 2);
230 } 203 }
231 204
232 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) { 205 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) {
233 current = cookie->batch + cookie->batches - 1; 206 batch_add_copy(cookie->batch, "0\r\n\r\n", 5);
234 iob_addbuf(current, "0\r\n\r\n", 5);
235 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; 207 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
236 } 208 }
237 209
238 /* writeable sockets timeout after 10 minutes */ 210 timeout_in(sock, OT_CLIENT_TIMEOUT_SEND);
239 taia_now(&t);
240 taia_addsec(&t, &t, OT_CLIENT_TIMEOUT_SEND);
241 io_timeout(sock, t);
242 io_wantwrite(sock); 211 io_wantwrite(sock);
243 return 0; 212 return 0;
244} 213}
@@ -317,7 +286,6 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
317 } 286 }
318 287
319 if (mode == TASK_STATS_TPB) { 288 if (mode == TASK_STATS_TPB) {
320 tai6464 t;
321#ifdef WANT_COMPRESSION_GZIP 289#ifdef WANT_COMPRESSION_GZIP
322 ws->request[ws->request_size - 1] = 0; 290 ws->request[ws->request_size - 1] = 0;
323#ifndef WANT_COMPRESSION_GZIP_ALWAYS 291#ifndef WANT_COMPRESSION_GZIP_ALWAYS
@@ -333,8 +301,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
333 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; 301 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
334 302
335 /* Clients waiting for us should not easily timeout */ 303 /* Clients waiting for us should not easily timeout */
336 taia_uint(&t, 0); 304 timeout_never(sock);
337 io_timeout(sock, t);
338 fullscrape_deliver(sock, format); 305 fullscrape_deliver(sock, format);
339 io_dontwantread(sock); 306 io_dontwantread(sock);
340 return ws->reply_size = -2; 307 return ws->reply_size = -2;
@@ -343,13 +310,11 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
343 310
344 /* default format for now */ 311 /* default format for now */
345 if ((mode & TASK_CLASS_MASK) == TASK_STATS) { 312 if ((mode & TASK_CLASS_MASK) == TASK_STATS) {
346 tai6464 t;
347 313
348 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; 314 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
349 315
350 /* Complex stats also include expensive memory debugging tools */ 316 /* Complex stats also include expensive memory debugging tools */
351 taia_uint(&t, 0); 317 timeout_never(sock);
352 io_timeout(sock, t);
353 stats_deliver(sock, mode); 318 stats_deliver(sock, mode);
354 return ws->reply_size = -2; 319 return ws->reply_size = -2;
355 } 320 }
@@ -371,7 +336,6 @@ typedef struct {
371static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) { 336static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) {
372 struct http_data *cookie = io_getcookie(sock); 337 struct http_data *cookie = io_getcookie(sock);
373 int format = 0; 338 int format = 0;
374 tai6464 t;
375 339
376#ifdef WANT_MODEST_FULLSCRAPES 340#ifdef WANT_MODEST_FULLSCRAPES
377 { 341 {
@@ -430,9 +394,8 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
430 /* Pass this task to the worker thread */ 394 /* Pass this task to the worker thread */
431 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; 395 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
432 /* Clients waiting for us should not easily timeout */ 396 /* Clients waiting for us should not easily timeout */
433 taia_uint(&t, 0); 397 timeout_never(sock);
434 io_timeout(sock, t); 398 fullscrape_deliver(sock, format);
435 fullscrape_deliver(sock, TASK_FULLSCRAPE | format);
436 io_dontwantread(sock); 399 io_dontwantread(sock);
437 return ws->reply_size = -2; 400 return ws->reply_size = -2;
438} 401}
@@ -491,7 +454,7 @@ unsigned long long numwants[201];
491#endif 454#endif
492 455
493#if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY) 456#if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY)
494static char *http_header(char *data, size_t byte_count, char *header) { 457static char *http_header(char *data, size_t byte_count, const char *header) {
495 size_t i; 458 size_t i;
496 long sl = strlen(header); 459 long sl = strlen(header);
497 for (i = 0; i + sl + 2 < byte_count; ++i) { 460 for (i = 0; i + sl + 2 < byte_count; ++i) {
diff --git a/ot_http.h b/ot_http.h
index 965820f..003b545 100644
--- a/ot_http.h
+++ b/ot_http.h
@@ -10,9 +10,7 @@
10#include <sys/types.h> 10#include <sys/types.h>
11#include <sys/uio.h> 11#include <sys/uio.h>
12 12
13/* Libowfat */ 13#include "ot_batch.h"
14#include "iob.h"
15
16#include "ot_buf.h" 14#include "ot_buf.h"
17#include "trackerlogic.h" 15#include "trackerlogic.h"
18 16
@@ -27,8 +25,7 @@ typedef enum {
27 25
28struct http_data { 26struct http_data {
29 ot_buf request; 27 ot_buf request;
30 io_batch *batch; 28 ot_batch *batch;
31 size_t batches;
32 ot_ip6 ip; 29 ot_ip6 ip;
33 STRUCT_HTTP_FLAG flag; 30 STRUCT_HTTP_FLAG flag;
34}; 31};
@@ -37,7 +34,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); 34ssize_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); 35ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code);
39 36
40extern char *g_stats_path; 37extern const char *g_stats_path;
41extern ssize_t g_stats_path_len; 38extern ssize_t g_stats_path_len;
42 39
43#endif 40#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_timeout.h b/ot_timeout.h
new file mode 100644
index 0000000..1027c2e
--- /dev/null
+++ b/ot_timeout.h
@@ -0,0 +1,39 @@
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_TIMEOUT_H__
7#define OT_TIMEOUT_H__
8
9#include <stdint.h>
10#include <string.h>
11
12/* Libowfat */
13#include "io.h"
14
15#include "trackerlogic.h"
16
17/* io_timeout wants an absolute TAI64 deadline and io_timeouted skips any
18 socket whose deadline is zero, so that is how a socket is told to wait
19 indefinitely.
20
21 The deadline is taken from the cached clock rather than from
22 gettimeofday, which is too expensive to call at this rate on FreeBSD. */
23
24static inline void timeout_never(int64_t sock) {
25 tai6464 t;
26
27 memset(&t, 0, sizeof(t));
28 io_timeout(sock, t);
29}
30
31static inline void timeout_in(int64_t sock, unsigned int seconds) {
32 tai6464 t;
33
34 memset(&t, 0, sizeof(t));
35 tai_unix(&t.sec, g_now_seconds + seconds);
36 io_timeout(sock, t);
37}
38
39#endif
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..63b4c4f 100644
--- a/proxy.c
+++ b/proxy.c
@@ -16,9 +16,9 @@
16 16
17/* Libowfat */ 17/* Libowfat */
18#include "io.h" 18#include "io.h"
19#include "iob.h"
20 19
21/* Opentracker */ 20/* Opentracker */
21#include "ot_batch.h"
22#include "ot_ip.h" 22#include "ot_ip.h"
23#include "ot_mutex.h" 23#include "ot_mutex.h"
24#include "ot_random.h" 24#include "ot_random.h"
@@ -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}
@@ -228,7 +228,7 @@ typedef struct {
228 size_t indata_length; /* Length of unprocessed data */ 228 size_t indata_length; /* Length of unprocessed data */
229 uint32_t tracker_id; /* How the other end greeted */ 229 uint32_t tracker_id; /* How the other end greeted */
230 int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */ 230 int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */
231 io_batch outdata; /* The iobatch containing our sync data */ 231 ot_batch outdata; /* The batch containing our sync data */
232 232
233 size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */ 233 size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */
234 uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */ 234 uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */
@@ -243,7 +243,7 @@ void reset_info_block(proxy_peer *peer) {
243 peer->tracker_id = 0; 243 peer->tracker_id = 0;
244 peer->fd = -1; 244 peer->fd = -1;
245 peer->packet_tcount = 0; 245 peer->packet_tcount = 0;
246 iob_reset(&peer->outdata); 246 batch_reset(&peer->outdata);
247 PROXYPEER_SETDISCONNECTED(peer->state); 247 PROXYPEER_SETDISCONNECTED(peer->state);
248} 248}
249 249
@@ -424,11 +424,11 @@ static void handle_write(int64_t peersocket) {
424 } 424 }
425 break; 425 break;
426 case FLAG_CONNECTED: 426 case FLAG_CONNECTED:
427 switch (iob_send(peersocket, &peer->outdata)) { 427 switch (batch_send(peersocket, &peer->outdata)) {
428 case 0: /* all data sent */ 428 case BATCH_DONE:
429 io_dontwantwrite(peersocket); 429 io_dontwantwrite(peersocket);
430 break; 430 break;
431 case -3: /* an error occured */ 431 case BATCH_FAILED:
432 io_close(peersocket); 432 io_close(peersocket);
433 reset_info_block(peer); 433 reset_info_block(peer);
434 break; 434 break;
@@ -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");
@@ -728,7 +728,7 @@ static void *streamsync_worker(void *args) {
728 void *tmp = malloc(mem); 728 void *tmp = malloc(mem);
729 if (tmp) { 729 if (tmp) {
730 memcpy(tmp, ptr, mem); 730 memcpy(tmp, ptr, mem);
731 iob_addbuf_free(&g_connections[i].outdata, tmp, mem); 731 batch_add(&g_connections[i].outdata, tmp, mem);
732 io_wantwrite(g_connections[i].fd); 732 io_wantwrite(g_connections[i].fd);
733 } 733 }
734 } 734 }
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..148b928 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -23,6 +23,8 @@ 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},
27 {"ot_batch", test_batch},
26 {"ot_accesslist", test_accesslist}, 28 {"ot_accesslist", test_accesslist},
27 {NULL, NULL}}; 29 {NULL, NULL}};
28 30
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_batch.c b/tests/t_batch.c
new file mode 100644
index 0000000..02743f1
--- /dev/null
+++ b/tests/t_batch.c
@@ -0,0 +1,348 @@
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 <fcntl.h>
8#include <poll.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14/* Opentracker */
15#include "ot_batch.h"
16#include "test.h"
17
18/* The batch writes to a real socket pair, because what is worth pinning down
19 is what it does when the kernel takes only part of what it was offered. */
20
21static char *dup_of(const char *text) {
22 size_t len = strlen(text);
23 char *copy = malloc(len);
24
25 memcpy(copy, text, len);
26 return copy;
27}
28
29static void drain(int s, char *into, size_t len) {
30 size_t got = 0;
31
32 while (got < len) {
33 ssize_t r = read(s, into + got, len - got);
34
35 if (r <= 0)
36 break;
37 got += (size_t)r;
38 }
39 into[got] = 0;
40}
41
42static void case_queue_accounting(void) {
43 ot_batch batch;
44
45 memset(&batch, 0, sizeof(batch));
46
47 test_case("a zeroed batch is empty and sends nothing");
48 CHECK_INT(batch_pending(&batch), 0);
49 CHECK_INT(batch_send(-1, &batch), BATCH_DONE);
50
51 test_case("anything queued makes it pending");
52 CHECK_INT(batch_add(&batch, dup_of("hello "), 6), 0);
53 CHECK_INT(batch_add_copy(&batch, "world", 5), 0);
54 CHECK_INT(batch_pending(&batch), 1);
55 CHECK_INT(batch.count, 2);
56
57 batch_reset(&batch);
58 test_case("and a reset empties it");
59 CHECK_INT(batch_pending(&batch), 0);
60 CHECK_PTR(batch.entries, NULL);
61}
62
63static void case_send_in_one_go(void) {
64 ot_batch batch;
65 int sv[2];
66 char got[32];
67
68 memset(&batch, 0, sizeof(batch));
69 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
70
71 test_case("several buffers arrive as one stream, in order");
72 CHECK_INT(batch_add(&batch, dup_of("opentracker"), 11), 0);
73 CHECK_INT(batch_add_copy(&batch, " says ", 6), 0);
74 CHECK_INT(batch_add(&batch, dup_of("hello"), 5), 0);
75 CHECK_INT(batch_send(sv[0], &batch), 22);
76 CHECK_INT(batch_pending(&batch), 0);
77
78 drain(sv[1], got, 22);
79 CHECK_STR(got, "opentracker says hello");
80
81 test_case("and an empty batch reports it has nothing left");
82 CHECK_INT(batch_send(sv[0], &batch), BATCH_DONE);
83
84 batch_reset(&batch);
85 close(sv[0]);
86 close(sv[1]);
87}
88
89/* Send everything the batch holds, reading the other end as we go so the
90 socket never stays full, and return what arrived. */
91static size_t pump(int writer, int reader, ot_batch *batch, char *into, size_t room) {
92 size_t got = 0;
93
94 while (batch_pending(batch) || got < room) {
95 ssize_t r;
96
97 if (batch_pending(batch)) {
98 ssize_t res = batch_send(writer, batch);
99
100 if (res == BATCH_FAILED)
101 break;
102 if (res > 0)
103 continue;
104 }
105
106 r = read(reader, into + got, room - got);
107 if (r <= 0)
108 break;
109 got += (size_t)r;
110 }
111 return got;
112}
113
114static void case_partial_write_resumes(void) {
115 const size_t size = 200000;
116 ot_batch batch;
117 int sv[2], sndbuf = 4096;
118 char *sent, *got;
119 size_t i, arrived;
120
121 memset(&batch, 0, sizeof(batch));
122 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
123 setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
124 fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK);
125
126 sent = malloc(size);
127 got = malloc(size);
128 for (i = 0; i < size; ++i)
129 sent[i] = (char)(i % 251);
130
131 test_case("a buffer the kernel takes in pieces arrives whole and in order");
132 CHECK_INT(batch_add(&batch, memcpy(malloc(size), sent, size), size), 0);
133 arrived = pump(sv[0], sv[1], &batch, got, size);
134 CHECK_INT(arrived, size);
135 CHECK_INT(batch_pending(&batch), 0);
136 CHECK_MEM(got, sent, size);
137
138 free(sent);
139 free(got);
140 batch_reset(&batch);
141 close(sv[0]);
142 close(sv[1]);
143}
144
145/* writev refuses more than IOV_MAX entries at once, so a long batch has to be
146 sent across several calls. */
147static void case_more_entries_than_one_writev(void) {
148 const size_t entries = 3000, each = 4;
149 ot_batch batch;
150 int sv[2], sndbuf = 4096;
151 char *sent, *got;
152 size_t i, arrived, refused = 0;
153
154 memset(&batch, 0, sizeof(batch));
155 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
156 setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
157 fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK);
158
159 sent = malloc(entries * each);
160 got = malloc(entries * each);
161
162 test_case("a batch of more entries than writev accepts still arrives whole");
163 for (i = 0; i < entries; ++i) {
164 char piece[4];
165
166 piece[0] = (char)(i), piece[1] = (char)(i >> 8), piece[2] = 'x', piece[3] = 'y';
167 memcpy(sent + i * each, piece, each);
168 if (batch_add(&batch, memcpy(malloc(each), piece, each), each))
169 ++refused;
170 }
171 CHECK_INT(refused, 0);
172 CHECK_INT(batch.count, entries);
173
174 arrived = pump(sv[0], sv[1], &batch, got, entries * each);
175 CHECK_INT(arrived, entries * each);
176 CHECK_MEM(got, sent, entries * each);
177
178 free(sent);
179 free(got);
180 batch_reset(&batch);
181 close(sv[0]);
182 close(sv[1]);
183}
184
185static void case_reset_frees_the_unsent(void) {
186 ot_batch batch;
187 int sv[2];
188
189 memset(&batch, 0, sizeof(batch));
190 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
191
192 test_case("a batch torn down mid flight keeps no bytes outstanding");
193 CHECK_INT(batch_add(&batch, dup_of("first"), 5), 0);
194 CHECK_INT(batch_add(&batch, dup_of("second"), 6), 0);
195 CHECK_INT(batch_send(sv[0], &batch), 11);
196 CHECK_INT(batch_add(&batch, dup_of("never sent"), 10), 0);
197 CHECK_INT(batch_pending(&batch), 1);
198
199 /* Whether the unsent buffer is freed is for the sanitizer to say */
200 batch_reset(&batch);
201 CHECK_INT(batch_pending(&batch), 0);
202 CHECK_INT(batch.count, 0);
203
204 close(sv[0]);
205 close(sv[1]);
206}
207
208static void case_closed_socket_is_fatal(void) {
209 ot_batch batch;
210 int sv[2];
211
212 memset(&batch, 0, sizeof(batch));
213 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
214 close(sv[1]);
215
216 test_case("writing to a socket whose peer is gone reports a dead connection");
217 CHECK_INT(batch_add(&batch, dup_of("nobody is listening"), 19), 0);
218 CHECK_INT(batch_send(sv[0], &batch), BATCH_FAILED);
219
220 batch_reset(&batch);
221 close(sv[0]);
222}
223
224static void case_vector_is_taken_whole(void) {
225 ot_batch batch;
226 struct iovec *vector = malloc(3 * sizeof(struct iovec));
227 int sv[2];
228 char got[32];
229
230 memset(&batch, 0, sizeof(batch));
231 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
232
233 test_case("a whole vector is queued in one go, in order");
234 vector[0].iov_base = dup_of("one "), vector[0].iov_len = 4;
235 vector[1].iov_base = dup_of("two "), vector[1].iov_len = 4;
236 vector[2].iov_base = dup_of("three"), vector[2].iov_len = 5;
237 CHECK_INT(batch_add_vector(&batch, vector, 3), 0);
238 free(vector);
239 CHECK_INT(batch.count, 3);
240 CHECK_INT(batch_send(sv[0], &batch), 13);
241
242 drain(sv[1], got, 13);
243 CHECK_STR(got, "one two three");
244
245 batch_reset(&batch);
246 close(sv[0]);
247 close(sv[1]);
248}
249
250/* The entry array starts small and doubles, so crossing that boundary one
251 buffer at a time is worth its own case. */
252static void case_growing_one_entry_at_a_time(void) {
253 const size_t entries = 100;
254 ot_batch batch;
255 int sv[2];
256 char sent[400], got[401];
257 size_t i, refused = 0;
258
259 memset(&batch, 0, sizeof(batch));
260 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
261
262 test_case("a batch grown one buffer at a time keeps them all, in order");
263 for (i = 0; i < entries; ++i) {
264 char piece[4];
265
266 piece[0] = (char)('a' + i % 26), piece[1] = (char)('0' + i % 10);
267 piece[2] = (char)('A' + i % 26), piece[3] = '.';
268 memcpy(sent + i * 4, piece, 4);
269 if (batch_add(&batch, memcpy(malloc(4), piece, 4), 4))
270 ++refused;
271 }
272 CHECK_INT(refused, 0);
273 CHECK_INT(batch.count, entries);
274 CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(entries * 4));
275
276 drain(sv[1], got, entries * 4);
277 CHECK_MEM(got, sent, entries * 4);
278
279 batch_reset(&batch);
280 close(sv[0]);
281 close(sv[1]);
282}
283
284/* Appending while earlier entries are still on their way is what a chunked
285 transfer does. Rather than depend on when a kernel decides to block, this
286 takes entries off the front the way a completed send would and checks that
287 the array reuses the space instead of growing. */
288static void case_append_while_sending(void) {
289 const size_t per_round = 12, each = 8;
290 ot_batch batch;
291 int sv[2];
292 char expected[16], got[17];
293 size_t i, round, refused = 0, space_after_first = 0;
294
295 memset(&batch, 0, sizeof(batch));
296 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
297
298 test_case("an array whose front has gone out is reused, not grown");
299 for (round = 0; round < 8; ++round) {
300 for (i = 0; i < per_round; ++i) {
301 /* Distinct per entry, so a move from the wrong index shows up */
302 char *piece = malloc(each);
303
304 memset(piece, (int)('a' + (round * per_round + i) % 26), each);
305 piece[0] = (char)('0' + i);
306 piece[1] = (char)('0' + round);
307 if (batch_add(&batch, piece, each))
308 ++refused;
309 }
310
311 /* All but the last entry have left and been freed */
312 while (batch.count - batch.first > 1)
313 free(batch.entries[batch.first++].iov_base);
314
315 if (!round)
316 space_after_first = batch.space;
317 }
318 CHECK_INT(refused, 0);
319 CHECK_INT(batch.space, space_after_first);
320
321 test_case("and what is still queued survives the move, in order");
322 CHECK_INT(batch.count - batch.first, 1);
323 CHECK_INT(batch_add(&batch, memset(malloc(each), 'Z', each), each), 0);
324 CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(2 * each));
325
326 memset(expected, (int)('a' + (7 * per_round + per_round - 1) % 26), each);
327 expected[0] = (char)('0' + per_round - 1);
328 expected[1] = '7';
329 memset(expected + each, 'Z', each);
330 drain(sv[1], got, 2 * each);
331 CHECK_MEM(got, expected, 2 * each);
332
333 batch_reset(&batch);
334 close(sv[0]);
335 close(sv[1]);
336}
337
338void test_batch(void) {
339 case_queue_accounting();
340 case_send_in_one_go();
341 case_vector_is_taken_whole();
342 case_growing_one_entry_at_a_time();
343 case_append_while_sending();
344 case_partial_write_resumes();
345 case_more_entries_than_one_writev();
346 case_reset_frees_the_unsent();
347 case_closed_socket_is_fatal();
348}
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..fd574c2 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,5 +86,7 @@ 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);
90void test_batch(void);
89 91
90#endif 92#endif
diff --git a/trackerlogic.c b/trackerlogic.c
index db1d787..74fea6d 100644
--- a/trackerlogic.c
+++ b/trackerlogic.c
@@ -10,9 +10,6 @@
10#include <stdlib.h> 10#include <stdlib.h>
11#include <string.h> 11#include <string.h>
12 12
13/* Libowfat */
14#include "io.h"
15#include "iob.h"
16 13
17/* Opentracker */ 14/* Opentracker */
18#include "ot_accesslist.h" 15#include "ot_accesslist.h"
@@ -550,7 +547,7 @@ void trackerlogic_add_random_torrents(size_t amount) {
550} 547}
551#endif 548#endif
552 549
553void exerr(char *message) { 550void exerr(const char *message) {
554 fprintf(stderr, "%s\n", message); 551 fprintf(stderr, "%s\n", message);
555 exit(111); 552 exit(111);
556} 553}
@@ -569,7 +566,6 @@ void trackerlogic_init(void) {
569 accesslist_init(); 566 accesslist_init();
570 livesync_init(); 567 livesync_init();
571 stats_init(); 568 stats_init();
572 udp_kickoff();
573} 569}
574 570
575void trackerlogic_deinit(void) { 571void 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 */