diff options
| -rw-r--r-- | Makefile | 14 | ||||
| -rw-r--r-- | opentracker.c | 516 | ||||
| -rw-r--r-- | opentracker.conf.sample | 28 | ||||
| -rw-r--r-- | ot_accesslist.c | 8 | ||||
| -rw-r--r-- | ot_accesslist.h | 6 | ||||
| -rw-r--r-- | ot_batch.c | 158 | ||||
| -rw-r--r-- | ot_batch.h | 48 | ||||
| -rw-r--r-- | ot_config.c | 565 | ||||
| -rw-r--r-- | ot_config.h | 35 | ||||
| -rw-r--r-- | ot_fullscrape.c | 2 | ||||
| -rw-r--r-- | ot_http.c | 85 | ||||
| -rw-r--r-- | ot_http.h | 9 | ||||
| -rw-r--r-- | ot_socket.c | 36 | ||||
| -rw-r--r-- | ot_socket.h | 9 | ||||
| -rw-r--r-- | ot_stats.c | 2 | ||||
| -rw-r--r-- | ot_timeout.h | 39 | ||||
| -rw-r--r-- | ot_udp.c | 24 | ||||
| -rw-r--r-- | ot_udp.h | 4 | ||||
| -rw-r--r-- | ot_vector.c | 8 | ||||
| -rw-r--r-- | proxy.c | 18 | ||||
| -rw-r--r-- | scan_urlencoded_query.h | 4 | ||||
| -rw-r--r-- | tests/runner.c | 2 | ||||
| -rw-r--r-- | tests/stubs.c | 9 | ||||
| -rw-r--r-- | tests/t_batch.c | 348 | ||||
| -rw-r--r-- | tests/t_mutex.c | 107 | ||||
| -rw-r--r-- | tests/t_socket.c | 34 | ||||
| -rw-r--r-- | tests/t_vector.c | 104 | ||||
| -rw-r--r-- | tests/test.h | 2 | ||||
| -rw-r--r-- | trackerlogic.c | 6 | ||||
| -rw-r--r-- | trackerlogic.h | 2 |
30 files changed, 1600 insertions, 632 deletions
| @@ -64,13 +64,13 @@ GIT_VERSION=`sh -c 'command -v git >/dev/null && test -d .git && git rev-parse H | |||
| 64 | OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage | 64 | OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage |
| 65 | OPTS_production=-O3 | 65 | OPTS_production=-O3 |
| 66 | 66 | ||
| 67 | CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra #-ansi -pedantic | 67 | CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic |
| 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread | 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread |
| 69 | 69 | ||
| 70 | BINARY =opentracker | 70 | BINARY =opentracker |
| 71 | HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h | 71 | HEADERS=trackerlogic.h scan_urlencoded_query.h ot_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 |
| 72 | SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.c ot_hex.c ot_ip.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c | 72 | SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_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 |
| 73 | SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c | 73 | SOURCES_proxy=proxy.c ot_batch.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c |
| 74 | 74 | ||
| 75 | OBJECTS = $(SOURCES:%.c=%.o) | 75 | OBJECTS = $(SOURCES:%.c=%.o) |
| 76 | OBJECTS_debug = $(SOURCES:%.c=%.debug.o) | 76 | OBJECTS_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. |
| 121 | TESTBIN=tests/runner | 121 | TESTBIN=tests/runner |
| 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c | 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c |
| 123 | TEST_UNITS=scan_urlencoded_query.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c | 123 | TEST_UNITS=scan_urlencoded_query.c ot_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 |
| 124 | TEST_HEADERS=tests/test.h $(HEADERS) | 124 | TEST_HEADERS=tests/test.h $(HEADERS) |
| 125 | # Tests reach code that sits behind feature flags, so the units under test are | 125 | # Tests reach code that sits behind feature flags, so the units under test are |
| 126 | # built with those features enabled. A test binary is not a production binary. | 126 | # built with those features enabled. A test binary is not a production binary. |
| @@ -130,7 +130,7 @@ CFLAGS_test = $(CFLAGS) -I. -g -O1 $(FEATURES_test) | |||
| 130 | $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) | 130 | $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) |
| 131 | $(CC) -o $@ $(CFLAGS_test) $(TEST_SOURCES) $(TEST_UNITS) | 131 | $(CC) -o $@ $(CFLAGS_test) $(TEST_SOURCES) $(TEST_UNITS) |
| 132 | 132 | ||
| 133 | check: $(TESTBIN) | 133 | check: $(TESTBIN) $(BINARY) |
| 134 | ./$(TESTBIN) | 134 | ./$(TESTBIN) |
| 135 | 135 | ||
| 136 | .PHONY: all check clean install | 136 | .PHONY: all check clean install |
diff --git a/opentracker.c b/opentracker.c index 5b197d1..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 */ |
| 43 | time_t g_now_seconds; | 44 | time_t g_now_seconds; |
| 44 | char *g_redirecturl; | ||
| 45 | uint32_t g_tracker_id; | 45 | uint32_t g_tracker_id; |
| 46 | volatile int g_opentracker_running = 1; | 46 | volatile int g_opentracker_running = 1; |
| 47 | int g_self_pipe[2]; | 47 | int g_self_pipe[2]; |
| 48 | 48 | ||
| 49 | static char *g_serverdir; | ||
| 50 | static char *g_serveruser; | ||
| 51 | static unsigned int g_udp_workers; | ||
| 52 | 49 | ||
| 53 | static void panic(const char *routine) __attribute__((noreturn)); | 50 | static void panic(const char *routine) __attribute__((noreturn)); |
| 54 | static void panic(const char *routine) { | 51 | static void panic(const char *routine) { |
| @@ -100,42 +97,6 @@ static void install_signal_handlers(void) { | |||
| 100 | pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); | 97 | pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); |
| 101 | } | 98 | } |
| 102 | 99 | ||
| 103 | static void usage(char *name) { | ||
| 104 | fprintf(stderr, | ||
| 105 | "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" | ||
| 106 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 107 | " [-b blacklistfile]" | ||
| 108 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 109 | " [-w whitelistfile]" | ||
| 110 | #endif | ||
| 111 | "\n", | ||
| 112 | name); | ||
| 113 | } | ||
| 114 | |||
| 115 | #define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) | ||
| 116 | static void help(char *name) { | ||
| 117 | usage(name); | ||
| 118 | |||
| 119 | HELPLINE("-f config", "include and execute the config file"); | ||
| 120 | HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); | ||
| 121 | HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); | ||
| 122 | HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); | ||
| 123 | HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); | ||
| 124 | HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); | ||
| 125 | HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); | ||
| 126 | HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); | ||
| 127 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 128 | HELPLINE("-b file", "specify blacklist file."); | ||
| 129 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 130 | HELPLINE("-w file", "specify whitelist file."); | ||
| 131 | #endif | ||
| 132 | |||
| 133 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); | ||
| 134 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); | ||
| 135 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); | ||
| 136 | fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); | ||
| 137 | } | ||
| 138 | #undef HELPLINE | ||
| 139 | 100 | ||
| 140 | static ssize_t header_complete(char *request, ssize_t byte_count) { | 101 | static ssize_t header_complete(char *request, ssize_t byte_count) { |
| 141 | ssize_t i = 0, state = 0; | 102 | ssize_t i = 0, state = 0; |
| @@ -159,9 +120,8 @@ static ssize_t header_complete(char *request, ssize_t byte_count) { | |||
| 159 | static void handle_dead(const int64_t sock) { | 120 | static 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 | ||
| 218 | static void handle_write(const int64_t sock) { | 178 | static 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 | ||
| 341 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port, PROTO_FLAG proto) { | 286 | |
| 342 | int64_t sock; | 287 | static 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 | ||
| 381 | char *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); | 332 | static 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 | |||
| 391 | static 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 | ||
| 422 | static 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 | ||
| 447 | typedef enum { | 340 | static 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 | ||
| 468 | typedef 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 | ||
| 476 | static 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 | }; | ||
| 506 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); | ||
| 507 | |||
| 508 | static config_kind parse_config_key(char **p) { | ||
| 509 | size_t i; | ||
| 510 | for (i = 0; i < config_entry_count; ++i) { | ||
| 511 | const config_key *key = &config_keys[i]; | ||
| 512 | if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { | ||
| 513 | *p += key->len + 1; | ||
| 514 | /* Skip white spaces */ | ||
| 515 | while (isspace((unsigned char)**p)) | ||
| 516 | ++(*p); | ||
| 517 | return key->kind; | ||
| 518 | } | ||
| 519 | } | ||
| 520 | return CONFIG_UNKNOWN; | ||
| 521 | } | ||
| 522 | |||
| 523 | int parse_configfile(char *config_filename) { | ||
| 524 | FILE *config_file_handle; | ||
| 525 | char inbuf[512]; | ||
| 526 | ot_ip6 tmpip; | ||
| 527 | #if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) | ||
| 528 | ot_net tmpnet; | ||
| 529 | #endif | ||
| 530 | int bound = 0; | ||
| 531 | |||
| 532 | config_file_handle = fopen(config_filename, "r"); | ||
| 533 | |||
| 534 | if (config_file_handle == NULL) { | ||
| 535 | fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); | ||
| 536 | return 0; | ||
| 537 | } | ||
| 538 | |||
| 539 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { | ||
| 540 | char *p = inbuf, *end; | ||
| 541 | uint64_t value; | ||
| 542 | uint16_t tmpport = 6969; | ||
| 543 | |||
| 544 | /* Skip white spaces */ | ||
| 545 | while (isspace((unsigned char)*p)) | ||
| 546 | ++p; | ||
| 547 | |||
| 548 | /* Ignore comments and empty lines */ | ||
| 549 | if ((*p == '#') || (*p == '\n') || (*p == 0)) | ||
| 550 | continue; | 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 | ||
| 657 | void load_state(const char *const state_filename) { | 357 | void load_state(const char *const state_filename) { |
| @@ -745,115 +445,12 @@ static void *time_caching_worker(void *args) { | |||
| 745 | } | 445 | } |
| 746 | 446 | ||
| 747 | int main(int argc, char **argv) { | 447 | int main(int argc, char **argv) { |
| 748 | ot_ip6 serverip; | ||
| 749 | ot_net tmpnet; | ||
| 750 | int bound = 0, scanon = 1; | ||
| 751 | uint64_t value; | ||
| 752 | char *statefile = 0; | ||
| 753 | pthread_t thread_id; /* time cacher */ | 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 |
| 35 | char *g_accesslist_filename = NULL; | 35 | const char *g_accesslist_filename = NULL; |
| 36 | #ifdef WANT_DYNAMIC_ACCESSLIST | 36 | #ifdef WANT_DYNAMIC_ACCESSLIST |
| 37 | char *g_accesslist_pipe_add = NULL; | 37 | const char *g_accesslist_pipe_add = NULL; |
| 38 | char *g_accesslist_pipe_delete = NULL; | 38 | const char *g_accesslist_pipe_delete = NULL; |
| 39 | #endif | 39 | #endif |
| 40 | static pthread_mutex_t g_accesslist_mutex; | 40 | static pthread_mutex_t g_accesslist_mutex; |
| 41 | 41 | ||
| @@ -272,7 +272,7 @@ static void *accesslist_worker(void *args) { | |||
| 272 | 272 | ||
| 273 | #ifdef WANT_DYNAMIC_ACCESSLIST | 273 | #ifdef WANT_DYNAMIC_ACCESSLIST |
| 274 | static pthread_t thread_adder_id, thread_deleter_id; | 274 | static pthread_t thread_adder_id, thread_deleter_id; |
| 275 | static void *accesslist_adddel_worker(char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { | 275 | static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { |
| 276 | struct stat st; | 276 | struct stat st; |
| 277 | 277 | ||
| 278 | if (!stat(fifoname, &st)) { | 278 | if (!stat(fifoname, &st)) { |
diff --git a/ot_accesslist.h b/ot_accesslist.h index 30aaebd..9dfdd95 100644 --- a/ot_accesslist.h +++ b/ot_accesslist.h | |||
| @@ -23,10 +23,10 @@ void accesslist_cleanup(void); | |||
| 23 | line, and return how many were written to dest */ | 23 | line, and return how many were written to dest */ |
| 24 | size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len); | 24 | size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len); |
| 25 | 25 | ||
| 26 | extern char *g_accesslist_filename; | 26 | extern const char *g_accesslist_filename; |
| 27 | #ifdef WANT_DYNAMIC_ACCESSLIST | 27 | #ifdef WANT_DYNAMIC_ACCESSLIST |
| 28 | extern char *g_accesslist_pipe_add; | 28 | extern const char *g_accesslist_pipe_add; |
| 29 | extern char *g_accesslist_pipe_delete; | 29 | extern const char *g_accesslist_pipe_delete; |
| 30 | #endif | 30 | #endif |
| 31 | 31 | ||
| 32 | #else | 32 | #else |
diff --git a/ot_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 | |||
| 24 | static 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 | |||
| 52 | int 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 | |||
| 64 | int 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 */ | ||
| 77 | static 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. */ | ||
| 101 | static 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 | |||
| 114 | ssize_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 | |||
| 140 | void 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 */ | ||
| 151 | int 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. */ | ||
| 18 | typedef 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 | |||
| 26 | static inline int batch_pending(const ot_batch *batch) { return batch->first != batch->count; } | ||
| 27 | |||
| 28 | /* What batch_send reports instead of a byte count */ | ||
| 29 | enum { 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. */ | ||
| 32 | int 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. */ | ||
| 36 | ssize_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. */ | ||
| 40 | int 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. */ | ||
| 43 | int 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 */ | ||
| 46 | void 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 | |||
| 25 | const char *g_redirecturl; | ||
| 26 | const char *g_serverdir; | ||
| 27 | const char *g_serveruser; | ||
| 28 | const char *g_statefile; | ||
| 29 | |||
| 30 | #define OT_LISTEN_INHERIT (-1) | ||
| 31 | |||
| 32 | static ot_listen *g_listen; | ||
| 33 | static size_t g_listen_count; | ||
| 34 | static int g_listen_shared; | ||
| 35 | static int g_listen_workers; | ||
| 36 | |||
| 37 | static void panic(const char *routine) __attribute__((noreturn)); | ||
| 38 | static void panic(const char *routine) { | ||
| 39 | fprintf(stderr, "%s: %s\n", routine, strerror(errno)); | ||
| 40 | exit(111); | ||
| 41 | } | ||
| 42 | |||
| 43 | static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) { | ||
| 44 | ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen)); | ||
| 45 | ot_listen *entry; | ||
| 46 | |||
| 47 | if (!grown) | ||
| 48 | panic("listen_append"); | ||
| 49 | g_listen = grown; | ||
| 50 | |||
| 51 | entry = g_listen + g_listen_count++; | ||
| 52 | memcpy(entry->ip, ip, sizeof(ot_ip6)); | ||
| 53 | entry->port = port; | ||
| 54 | entry->proto = proto; | ||
| 55 | entry->shared = shared; | ||
| 56 | entry->workers = workers; | ||
| 57 | entry->sock = -1; | ||
| 58 | } | ||
| 59 | |||
| 60 | typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind; | ||
| 61 | |||
| 62 | typedef struct { | ||
| 63 | const char *name; | ||
| 64 | size_t len; | ||
| 65 | listen_option_kind kind; | ||
| 66 | int takes_value; | ||
| 67 | } listen_option_key; | ||
| 68 | |||
| 69 | #define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v} | ||
| 70 | |||
| 71 | static const listen_option_key listen_option_keys[] = { | ||
| 72 | LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0), | ||
| 73 | LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1), | ||
| 74 | }; | ||
| 75 | static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]); | ||
| 76 | |||
| 77 | static listen_option_kind parse_listen_option(char **p) { | ||
| 78 | size_t i; | ||
| 79 | |||
| 80 | for (i = 0; i < listen_option_count; ++i) { | ||
| 81 | const listen_option_key *key = &listen_option_keys[i]; | ||
| 82 | char delimiter = (*p)[key->len]; | ||
| 83 | |||
| 84 | if (strncmp(*p, key->name, key->len)) | ||
| 85 | continue; | ||
| 86 | if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter))) | ||
| 87 | continue; | ||
| 88 | |||
| 89 | *p += key->len + (key->takes_value ? 1 : 0); | ||
| 90 | return key->kind; | ||
| 91 | } | ||
| 92 | return LISTEN_OPTION_UNKNOWN; | ||
| 93 | } | ||
| 94 | |||
| 95 | /* Options trailing an address in a listen statement, in any order */ | ||
| 96 | static int listen_scan_options(char *p, int *shared, int *workers) { | ||
| 97 | uint64_t value; | ||
| 98 | |||
| 99 | for (;;) { | ||
| 100 | while (isspace((unsigned char)*p)) | ||
| 101 | ++p; | ||
| 102 | if (!*p) | ||
| 103 | return 1; | ||
| 104 | |||
| 105 | switch (parse_listen_option(&p)) { | ||
| 106 | case LISTEN_OPTION_SHARED: | ||
| 107 | *shared = 1; | ||
| 108 | break; | ||
| 109 | case LISTEN_OPTION_WORKERS: { | ||
| 110 | size_t off = scan_udec(p, &value, UINT_MAX); | ||
| 111 | |||
| 112 | if (!off) | ||
| 113 | return 0; | ||
| 114 | *workers = (int)value; | ||
| 115 | p += off; | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | default: | ||
| 119 | return 0; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | |||
| 125 | static const char *set_config_option(const char **option, char *value) { | ||
| 126 | #ifdef _DEBUG | ||
| 127 | fprintf(stderr, "Setting config option: %s\n", value); | ||
| 128 | #endif | ||
| 129 | while (isspace(*value)) | ||
| 130 | ++value; | ||
| 131 | free((char *)*option); | ||
| 132 | return *option = strdup(value); | ||
| 133 | } | ||
| 134 | |||
| 135 | static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { | ||
| 136 | const char *s = src; | ||
| 137 | uint64_t value; | ||
| 138 | int off, bracket = 0; | ||
| 139 | while (isspace(*s)) | ||
| 140 | ++s; | ||
| 141 | if (*s == '[') | ||
| 142 | ++s, ++bracket; /* for v6 style notation */ | ||
| 143 | if (!(off = ip_scan(s, ip))) | ||
| 144 | return 0; | ||
| 145 | s += off; | ||
| 146 | if (bracket && *s == ']') | ||
| 147 | ++s; | ||
| 148 | if (*s == 0 || isspace(*s)) | ||
| 149 | return s - src; | ||
| 150 | if (!ip_is_v4mapped(ip)) { | ||
| 151 | if (*s != ':' && *s != '.') | ||
| 152 | return 0; | ||
| 153 | if (!bracket && *(s) == ':') | ||
| 154 | return 0; | ||
| 155 | s++; | ||
| 156 | } else { | ||
| 157 | if (*(s++) != ':') | ||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | if (!(off = scan_udec(s, &value, 65535))) | ||
| 161 | return 0; | ||
| 162 | *port = (uint16_t)value; | ||
| 163 | return off + s - src; | ||
| 164 | } | ||
| 165 | |||
| 166 | static int scan_ip6_net(const char *src, ot_net *net) { | ||
| 167 | const char *s = src; | ||
| 168 | uint64_t value; | ||
| 169 | int off; | ||
| 170 | while (isspace(*s)) | ||
| 171 | ++s; | ||
| 172 | if (!(off = ip_scan(s, net->address))) | ||
| 173 | return 0; | ||
| 174 | s += off; | ||
| 175 | if (*s != '/') | ||
| 176 | net->bits = 128; | ||
| 177 | else { | ||
| 178 | s++; | ||
| 179 | if (!(off = scan_udec(s, &value, 128))) | ||
| 180 | return 0; | ||
| 181 | net->bits = (int)value; | ||
| 182 | if (ip_is_v4mapped(net->address)) | ||
| 183 | net->bits += 96; | ||
| 184 | if (net->bits > 128) | ||
| 185 | return 0; | ||
| 186 | s += off; | ||
| 187 | } | ||
| 188 | return off + s - src; | ||
| 189 | } | ||
| 190 | |||
| 191 | typedef enum { | ||
| 192 | CONFIG_UNKNOWN, | ||
| 193 | CONFIG_TRACKER_ROOTDIR, | ||
| 194 | CONFIG_TRACKER_USER, | ||
| 195 | CONFIG_LISTEN_TCP_UDP, | ||
| 196 | CONFIG_LISTEN_TCP, | ||
| 197 | CONFIG_LISTEN_UDP, | ||
| 198 | CONFIG_LISTEN_UDP_WORKERS, | ||
| 199 | CONFIG_LISTEN_SHARED, | ||
| 200 | CONFIG_ACCESS_WHITELIST, | ||
| 201 | CONFIG_ACCESS_BLACKLIST, | ||
| 202 | CONFIG_ACCESS_FIFO_ADD, | ||
| 203 | CONFIG_ACCESS_FIFO_DELETE, | ||
| 204 | CONFIG_ACCESS_STATS, | ||
| 205 | CONFIG_ACCESS_STATS_PATH, | ||
| 206 | CONFIG_ACCESS_PROXY, | ||
| 207 | CONFIG_TRACKER_REDIRECT_URL, | ||
| 208 | CONFIG_LIVESYNC_CLUSTER_NODE_IP, | ||
| 209 | CONFIG_LIVESYNC_CLUSTER_LISTEN, | ||
| 210 | } config_kind; | ||
| 211 | |||
| 212 | typedef struct { | ||
| 213 | const char *name; | ||
| 214 | size_t len; | ||
| 215 | config_kind kind; | ||
| 216 | } config_key; | ||
| 217 | |||
| 218 | #define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } | ||
| 219 | |||
| 220 | static const config_key config_keys[] = { | ||
| 221 | CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), | ||
| 222 | CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), | ||
| 223 | CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), | ||
| 224 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), | ||
| 225 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), | ||
| 226 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), | ||
| 227 | CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), | ||
| 228 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 229 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), | ||
| 230 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 231 | CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST), | ||
| 232 | #endif | ||
| 233 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 234 | CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD), | ||
| 235 | CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE), | ||
| 236 | #endif | ||
| 237 | #ifdef WANT_RESTRICT_STATS | ||
| 238 | CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS), | ||
| 239 | #endif | ||
| 240 | CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH), | ||
| 241 | #ifdef WANT_IP_FROM_PROXY | ||
| 242 | CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY), | ||
| 243 | #endif | ||
| 244 | CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL), | ||
| 245 | #ifdef WANT_SYNC_LIVE | ||
| 246 | CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), | ||
| 247 | CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), | ||
| 248 | #endif | ||
| 249 | }; | ||
| 250 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); | ||
| 251 | |||
| 252 | static config_kind parse_config_key(char **p) { | ||
| 253 | size_t i; | ||
| 254 | for (i = 0; i < config_entry_count; ++i) { | ||
| 255 | const config_key *key = &config_keys[i]; | ||
| 256 | if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { | ||
| 257 | *p += key->len + 1; | ||
| 258 | /* Skip white spaces */ | ||
| 259 | while (isspace((unsigned char)**p)) | ||
| 260 | ++(*p); | ||
| 261 | return key->kind; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | return CONFIG_UNKNOWN; | ||
| 265 | } | ||
| 266 | |||
| 267 | int parse_configfile(char *config_filename) { | ||
| 268 | FILE *config_file_handle; | ||
| 269 | char inbuf[512]; | ||
| 270 | ot_ip6 tmpip; | ||
| 271 | #if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) | ||
| 272 | ot_net tmpnet; | ||
| 273 | #endif | ||
| 274 | int bound = 0; | ||
| 275 | |||
| 276 | config_file_handle = fopen(config_filename, "r"); | ||
| 277 | |||
| 278 | if (config_file_handle == NULL) { | ||
| 279 | fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); | ||
| 280 | return 0; | ||
| 281 | } | ||
| 282 | |||
| 283 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { | ||
| 284 | char *p = inbuf, *end; | ||
| 285 | uint64_t value; | ||
| 286 | uint16_t tmpport = 6969; | ||
| 287 | config_kind kind; | ||
| 288 | |||
| 289 | /* Skip white spaces */ | ||
| 290 | while (isspace((unsigned char)*p)) | ||
| 291 | ++p; | ||
| 292 | |||
| 293 | /* Ignore comments and empty lines */ | ||
| 294 | if ((*p == '#') || (*p == '\n') || (*p == 0)) | ||
| 295 | continue; | ||
| 296 | |||
| 297 | /* consume trailing new lines and spaces */ | ||
| 298 | end = p + strlen(p); | ||
| 299 | while (end > p && isspace((unsigned char)end[-1])) | ||
| 300 | *--end = 0; | ||
| 301 | |||
| 302 | /* Scan for commands */ | ||
| 303 | kind = parse_config_key(&p); | ||
| 304 | switch (kind) { | ||
| 305 | case CONFIG_TRACKER_ROOTDIR: | ||
| 306 | set_config_option(&g_serverdir, p); | ||
| 307 | break; | ||
| 308 | case CONFIG_TRACKER_USER: | ||
| 309 | set_config_option(&g_serveruser, p); | ||
| 310 | break; | ||
| 311 | case CONFIG_LISTEN_TCP_UDP: | ||
| 312 | case CONFIG_LISTEN_TCP: | ||
| 313 | case CONFIG_LISTEN_UDP: { | ||
| 314 | int shared = OT_LISTEN_INHERIT; | ||
| 315 | int workers = OT_LISTEN_INHERIT; | ||
| 316 | int off = scan_ip6_port(p, tmpip, &tmpport); | ||
| 317 | |||
| 318 | if (!off || !listen_scan_options(p + off, &shared, &workers)) | ||
| 319 | goto parse_error; | ||
| 320 | if (kind != CONFIG_LISTEN_UDP) | ||
| 321 | listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound; | ||
| 322 | if (kind != CONFIG_LISTEN_TCP) | ||
| 323 | listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | case CONFIG_LISTEN_UDP_WORKERS: | ||
| 327 | if (!scan_udec(p, &value, UINT_MAX)) | ||
| 328 | goto parse_error; | ||
| 329 | g_listen_workers = (int)value; | ||
| 330 | break; | ||
| 331 | case CONFIG_LISTEN_SHARED: | ||
| 332 | if (!scan_udec(p, &value, 1)) | ||
| 333 | goto parse_error; | ||
| 334 | g_listen_shared = (int)value; | ||
| 335 | break; | ||
| 336 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 337 | case CONFIG_ACCESS_WHITELIST: | ||
| 338 | set_config_option(&g_accesslist_filename, p); | ||
| 339 | break; | ||
| 340 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 341 | case CONFIG_ACCESS_BLACKLIST: | ||
| 342 | set_config_option(&g_accesslist_filename, p); | ||
| 343 | break; | ||
| 344 | #endif | ||
| 345 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 346 | case CONFIG_ACCESS_FIFO_ADD: | ||
| 347 | set_config_option(&g_accesslist_pipe_add, p); | ||
| 348 | break; | ||
| 349 | case CONFIG_ACCESS_FIFO_DELETE: | ||
| 350 | set_config_option(&g_accesslist_pipe_delete, p); | ||
| 351 | break; | ||
| 352 | #endif | ||
| 353 | #ifdef WANT_RESTRICT_STATS | ||
| 354 | case CONFIG_ACCESS_STATS: | ||
| 355 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 356 | goto parse_error; | ||
| 357 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT); | ||
| 358 | break; | ||
| 359 | #endif | ||
| 360 | case CONFIG_ACCESS_STATS_PATH: | ||
| 361 | set_config_option(&g_stats_path, p); | ||
| 362 | break; | ||
| 363 | #ifdef WANT_IP_FROM_PROXY | ||
| 364 | case CONFIG_ACCESS_PROXY: | ||
| 365 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 366 | goto parse_error; | ||
| 367 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY); | ||
| 368 | break; | ||
| 369 | #endif | ||
| 370 | case CONFIG_TRACKER_REDIRECT_URL: | ||
| 371 | set_config_option(&g_redirecturl, p); | ||
| 372 | break; | ||
| 373 | #ifdef WANT_SYNC_LIVE | ||
| 374 | case CONFIG_LIVESYNC_CLUSTER_NODE_IP: | ||
| 375 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 376 | goto parse_error; | ||
| 377 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC); | ||
| 378 | break; | ||
| 379 | case CONFIG_LIVESYNC_CLUSTER_LISTEN: | ||
| 380 | tmpport = LIVESYNC_PORT; | ||
| 381 | { | ||
| 382 | int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT; | ||
| 383 | int off = scan_ip6_port(p, tmpip, &tmpport); | ||
| 384 | |||
| 385 | if (!off || !listen_scan_options(p + off, &shared, &dummy)) | ||
| 386 | goto parse_error; | ||
| 387 | listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT); | ||
| 388 | } | ||
| 389 | break; | ||
| 390 | #endif | ||
| 391 | default: | ||
| 392 | fprintf(stderr, "Unhandled line in config file: %s\n", inbuf); | ||
| 393 | break; | ||
| 394 | parse_error: | ||
| 395 | fprintf(stderr, "Parse error in config file: %s\n", inbuf); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | fclose(config_file_handle); | ||
| 399 | return bound; | ||
| 400 | } | ||
| 401 | |||
| 402 | static void usage(char *name) { | ||
| 403 | fprintf(stderr, | ||
| 404 | "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" | ||
| 405 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 406 | " [-b blacklistfile]" | ||
| 407 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 408 | " [-w whitelistfile]" | ||
| 409 | #endif | ||
| 410 | "\n", | ||
| 411 | name); | ||
| 412 | } | ||
| 413 | |||
| 414 | #define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) | ||
| 415 | static void help(char *name) { | ||
| 416 | usage(name); | ||
| 417 | |||
| 418 | HELPLINE("-f config", "include and execute the config file"); | ||
| 419 | HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); | ||
| 420 | HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); | ||
| 421 | HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); | ||
| 422 | HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); | ||
| 423 | HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); | ||
| 424 | HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); | ||
| 425 | HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); | ||
| 426 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 427 | HELPLINE("-b file", "specify blacklist file."); | ||
| 428 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 429 | HELPLINE("-w file", "specify whitelist file."); | ||
| 430 | #endif | ||
| 431 | |||
| 432 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); | ||
| 433 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); | ||
| 434 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); | ||
| 435 | fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); | ||
| 436 | } | ||
| 437 | #undef HELPLINE | ||
| 438 | |||
| 439 | |||
| 440 | static void listen_resolve_defaults(void) { | ||
| 441 | size_t i; | ||
| 442 | |||
| 443 | for (i = 0; i < g_listen_count; ++i) { | ||
| 444 | ot_listen *entry = g_listen + i; | ||
| 445 | |||
| 446 | if (entry->shared == OT_LISTEN_INHERIT) | ||
| 447 | entry->shared = g_listen_shared; | ||
| 448 | if (entry->workers == OT_LISTEN_INHERIT) | ||
| 449 | entry->workers = g_listen_workers; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | ot_listen *config_listen_entries(size_t *count) { | ||
| 454 | *count = g_listen_count; | ||
| 455 | return g_listen; | ||
| 456 | } | ||
| 457 | |||
| 458 | void config_parse(int argc, char **argv) { | ||
| 459 | ot_ip6 serverip; | ||
| 460 | ot_net tmpnet; | ||
| 461 | int bound = 0, scanon = 1; | ||
| 462 | uint64_t value; | ||
| 463 | |||
| 464 | memset(serverip, 0, sizeof(ot_ip6)); | ||
| 465 | #ifdef WANT_V4_ONLY | ||
| 466 | serverip[10] = serverip[11] = -1; | ||
| 467 | #endif | ||
| 468 | |||
| 469 | while (scanon) { | ||
| 470 | switch (getopt(argc, argv, | ||
| 471 | ":i:p:A:P:d:u:r:s:f:l:v" | ||
| 472 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 473 | "b:" | ||
| 474 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 475 | "w:" | ||
| 476 | #endif | ||
| 477 | "h")) { | ||
| 478 | case -1: | ||
| 479 | scanon = 0; | ||
| 480 | break; | ||
| 481 | case 'i': | ||
| 482 | if (!ip_scan(optarg, serverip)) { | ||
| 483 | usage(argv[0]); | ||
| 484 | exit(1); | ||
| 485 | } | ||
| 486 | break; | ||
| 487 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 488 | case 'b': | ||
| 489 | set_config_option(&g_accesslist_filename, optarg); | ||
| 490 | break; | ||
| 491 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 492 | case 'w': | ||
| 493 | set_config_option(&g_accesslist_filename, optarg); | ||
| 494 | break; | ||
| 495 | #endif | ||
| 496 | case 'p': | ||
| 497 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 498 | usage(argv[0]); | ||
| 499 | exit(1); | ||
| 500 | } | ||
| 501 | listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 502 | bound++; | ||
| 503 | break; | ||
| 504 | case 'P': | ||
| 505 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 506 | usage(argv[0]); | ||
| 507 | exit(1); | ||
| 508 | } | ||
| 509 | listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 510 | bound++; | ||
| 511 | break; | ||
| 512 | #ifdef WANT_SYNC_LIVE | ||
| 513 | case 's': | ||
| 514 | if (!scan_udec(optarg, &value, 65535)) { | ||
| 515 | usage(argv[0]); | ||
| 516 | exit(1); | ||
| 517 | } | ||
| 518 | listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 519 | break; | ||
| 520 | #endif | ||
| 521 | case 'd': | ||
| 522 | set_config_option(&g_serverdir, optarg); | ||
| 523 | break; | ||
| 524 | case 'u': | ||
| 525 | set_config_option(&g_serveruser, optarg); | ||
| 526 | break; | ||
| 527 | case 'r': | ||
| 528 | set_config_option(&g_redirecturl, optarg); | ||
| 529 | break; | ||
| 530 | case 'l': | ||
| 531 | g_statefile = optarg; | ||
| 532 | break; | ||
| 533 | case 'A': | ||
| 534 | if (!scan_ip6_net(optarg, &tmpnet)) { | ||
| 535 | usage(argv[0]); | ||
| 536 | exit(1); | ||
| 537 | } | ||
| 538 | accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */ | ||
| 539 | break; | ||
| 540 | case 'f': | ||
| 541 | bound += parse_configfile(optarg); | ||
| 542 | break; | ||
| 543 | case 'h': | ||
| 544 | help(argv[0]); | ||
| 545 | exit(0); | ||
| 546 | case 'v': { | ||
| 547 | char buffer[8192]; | ||
| 548 | stats_return_tracker_version(buffer); | ||
| 549 | fputs(buffer, stderr); | ||
| 550 | exit(0); | ||
| 551 | } | ||
| 552 | default: | ||
| 553 | case '?': | ||
| 554 | usage(argv[0]); | ||
| 555 | exit(1); | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | if (!bound) { | ||
| 560 | listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 561 | listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 562 | } | ||
| 563 | |||
| 564 | listen_resolve_defaults(); | ||
| 565 | } | ||
diff --git a/ot_config.h b/ot_config.h new file mode 100644 index 0000000..059573a --- /dev/null +++ b/ot_config.h | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | #ifndef OT_CONFIG_H__ | ||
| 7 | #define OT_CONFIG_H__ | ||
| 8 | |||
| 9 | #include <stdint.h> | ||
| 10 | |||
| 11 | #include "trackerlogic.h" | ||
| 12 | |||
| 13 | /* An address opentracker was asked to listen on. Options the statement did | ||
| 14 | not name have been filled in from the file wide defaults by the time these | ||
| 15 | are handed out. sock is filled in by whoever binds them. */ | ||
| 16 | typedef struct { | ||
| 17 | ot_ip6 ip; | ||
| 18 | uint16_t port; | ||
| 19 | PROTO_FLAG proto; | ||
| 20 | int shared; | ||
| 21 | int workers; | ||
| 22 | int64_t sock; | ||
| 23 | } ot_listen; | ||
| 24 | |||
| 25 | extern const char *g_redirecturl; | ||
| 26 | extern const char *g_serverdir; | ||
| 27 | extern const char *g_serveruser; | ||
| 28 | extern const char *g_statefile; | ||
| 29 | |||
| 30 | /* Reads the command line and every config file it names. Exits on error. */ | ||
| 31 | void config_parse(int argc, char **argv); | ||
| 32 | |||
| 33 | ot_listen *config_listen_entries(size_t *count); | ||
| 34 | |||
| 35 | #endif | ||
diff --git a/ot_fullscrape.c b/ot_fullscrape.c index 9dcbe68..eade79d 100644 --- a/ot_fullscrape.c +++ b/ot_fullscrape.c | |||
| @@ -83,7 +83,7 @@ void fullscrape_deinit(void) { | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { | 85 | void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { |
| 86 | mutex_workqueue_pushtask( sock, tasktype ); | 86 | mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); |
| 87 | } | 87 | } |
| 88 | 88 | ||
| 89 | static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { | 89 | static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { |
| @@ -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) | 34 | extern const char *g_redirecturl; |
| 40 | extern char *g_redirecturl; | ||
| 41 | 35 | ||
| 42 | char *g_stats_path; | 36 | const char *g_stats_path; |
| 43 | ssize_t g_stats_path_len; | 37 | ssize_t g_stats_path_len; |
| 44 | 38 | ||
| 45 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; | 39 | enum { 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) |
| 127 | ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { | 116 | ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { |
| 128 | char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required", | 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 | ||
| 147 | ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { | 136 | ssize_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 { | |||
| 371 | static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) { | 336 | static 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) |
| 494 | static char *http_header(char *data, size_t byte_count, char *header) { | 457 | static 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) { |
| @@ -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 | ||
| 28 | struct http_data { | 26 | struct 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); | |||
| 37 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); | 34 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); |
| 38 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); | 35 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); |
| 39 | 36 | ||
| 40 | extern char *g_stats_path; | 37 | extern const char *g_stats_path; |
| 41 | extern ssize_t g_stats_path_len; | 38 | extern 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 | ||
| 18 | int g_socket_reuseport; | ||
| 19 | |||
| 20 | static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { | 18 | static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { |
| 21 | memset(sa, 0, sizeof(*sa)); | 19 | memset(sa, 0, sizeof(*sa)); |
| 22 | sa->sin6_family = AF_INET6; | 20 | sa->sin6_family = AF_INET6; |
| @@ -75,37 +73,43 @@ static int socket_v6(int type) { | |||
| 75 | return s; | 73 | return s; |
| 76 | } | 74 | } |
| 77 | 75 | ||
| 78 | static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port) { | 76 | /* The only place in the tree that needs to know what a kernel calls this */ |
| 79 | struct sockaddr_in6 sa; | 77 | static void socket_set_sharing(int s, socket_sharing sharing) { |
| 80 | int one = 1; | 78 | int one = 1; |
| 81 | 79 | ||
| 82 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | 80 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| 83 | #ifdef SO_REUSEPORT | 81 | #ifdef SO_REUSEPORT |
| 84 | if (g_socket_reuseport) | 82 | if (sharing == SOCKET_SHARED) |
| 85 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); | 83 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); |
| 86 | #endif | 84 | #endif |
| 85 | } | ||
| 86 | |||
| 87 | static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) { | ||
| 88 | struct sockaddr_in6 sa; | ||
| 89 | |||
| 90 | socket_set_sharing(s, sharing); | ||
| 87 | 91 | ||
| 88 | sockaddr_from_ip(&sa, ip, port); | 92 | sockaddr_from_ip(&sa, ip, port); |
| 89 | return bind(s, (struct sockaddr *)&sa, sizeof(sa)); | 93 | return bind(s, (struct sockaddr *)&sa, sizeof(sa)); |
| 90 | } | 94 | } |
| 91 | 95 | ||
| 92 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port) { | 96 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { |
| 93 | int s = socket_v6(SOCK_STREAM); | 97 | int s = socket_v6(SOCK_STREAM); |
| 94 | 98 | ||
| 95 | if (s == -1) | 99 | if (s == -1) |
| 96 | return -1; | 100 | return -1; |
| 97 | if (socket_bind_v6(s, ip, port) == -1 || listen(s, SOMAXCONN) == -1) | 101 | if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1) |
| 98 | return close_and_fail(s); | 102 | return close_and_fail(s); |
| 99 | 103 | ||
| 100 | return s; | 104 | return s; |
| 101 | } | 105 | } |
| 102 | 106 | ||
| 103 | int socket_udp_bind(const ot_ip6 ip, uint16_t port) { | 107 | int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { |
| 104 | int s = socket_v6(SOCK_DGRAM); | 108 | int s = socket_v6(SOCK_DGRAM); |
| 105 | 109 | ||
| 106 | if (s == -1) | 110 | if (s == -1) |
| 107 | return -1; | 111 | return -1; |
| 108 | if (socket_bind_v6(s, ip, port) == -1) | 112 | if (socket_bind_v6(s, ip, port, sharing) == -1) |
| 109 | return close_and_fail(s); | 113 | return close_and_fail(s); |
| 110 | 114 | ||
| 111 | return s; | 115 | return s; |
| @@ -117,7 +121,7 @@ int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, | |||
| 117 | 121 | ||
| 118 | if (s == -1) | 122 | if (s == -1) |
| 119 | return -1; | 123 | return -1; |
| 120 | if (socket_bind_v6(s, srcip, srcport) == -1) | 124 | if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1) |
| 121 | return close_and_fail(s); | 125 | return close_and_fail(s); |
| 122 | 126 | ||
| 123 | sockaddr_from_ip(&sa, dstip, dstport); | 127 | sockaddr_from_ip(&sa, dstip, dstport); |
| @@ -187,18 +191,16 @@ static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t | |||
| 187 | memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); | 191 | memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); |
| 188 | } | 192 | } |
| 189 | 193 | ||
| 194 | /* A multicast group is meant to have several listeners, so these are always | ||
| 195 | shared */ | ||
| 190 | static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { | 196 | static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { |
| 191 | struct sockaddr_in sa; | 197 | struct sockaddr_in sa; |
| 192 | int s, one = 1; | 198 | int s; |
| 193 | 199 | ||
| 194 | if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) | 200 | if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) |
| 195 | return -1; | 201 | return -1; |
| 196 | 202 | ||
| 197 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | 203 | socket_set_sharing(s, SOCKET_SHARED); |
| 198 | #ifdef SO_REUSEPORT | ||
| 199 | if (g_socket_reuseport) | ||
| 200 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); | ||
| 201 | #endif | ||
| 202 | 204 | ||
| 203 | sockaddr4_from_ip(&sa, ip, port); | 205 | sockaddr4_from_ip(&sa, ip, port); |
| 204 | if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) | 206 | if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) |
diff --git a/ot_socket.h b/ot_socket.h index 20fd839..21d04b1 100644 --- a/ot_socket.h +++ b/ot_socket.h | |||
| @@ -15,13 +15,14 @@ | |||
| 15 | cleared, so one descriptor serves both families and an IPv4 peer arrives | 15 | cleared, so one descriptor serves both families and an IPv4 peer arrives |
| 16 | as the v4 mapped address */ | 16 | as the v4 mapped address */ |
| 17 | 17 | ||
| 18 | /* Ask the kernel to let several processes bind the same address */ | 18 | /* How an address may be shared with other sockets. Which socket option says |
| 19 | extern int g_socket_reuseport; | 19 | so differs between kernels, which is socket_set_sharing's business */ |
| 20 | typedef enum { SOCKET_EXCLUSIVE, SOCKET_SHARED } socket_sharing; | ||
| 20 | 21 | ||
| 21 | /* Create, bind and, for TCP, listen. The returned socket is non blocking | 22 | /* Create, bind and, for TCP, listen. The returned socket is non blocking |
| 22 | Returns -1 on failure with errno set */ | 23 | Returns -1 on failure with errno set */ |
| 23 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port); | 24 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing); |
| 24 | int socket_udp_bind(const ot_ip6 ip, uint16_t port); | 25 | int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing); |
| 25 | 26 | ||
| 26 | int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); | 27 | int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); |
| 27 | int socket_is_connected(int s); | 28 | int socket_is_connected(int s); |
| @@ -53,7 +53,7 @@ static unsigned long long ot_full_scrape_count; | |||
| 53 | static unsigned long long ot_full_scrape_request_count; | 53 | static unsigned long long ot_full_scrape_request_count; |
| 54 | static unsigned long long ot_full_scrape_size; | 54 | static unsigned long long ot_full_scrape_size; |
| 55 | static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT]; | 55 | static unsigned long long ot_failed_request_counts[CODE_HTTPERROR_COUNT]; |
| 56 | static char *ot_failed_request_names[] = { | 56 | static const char *ot_failed_request_names[] = { |
| 57 | "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", | 57 | "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", |
| 58 | "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"}; | 58 | "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"}; |
| 59 | static unsigned long long ot_renewed[OT_PEER_TIMEOUT]; | 59 | static unsigned long long ot_renewed[OT_PEER_TIMEOUT]; |
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 | |||
| 24 | static inline void timeout_never(int64_t sock) { | ||
| 25 | tai6464 t; | ||
| 26 | |||
| 27 | memset(&t, 0, sizeof(t)); | ||
| 28 | io_timeout(sock, t); | ||
| 29 | } | ||
| 30 | |||
| 31 | static 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 | ||
| @@ -26,9 +26,6 @@ static uint32_t g_rijndael_round_key[44] = {0}; | |||
| 26 | static volatile uint32_t g_key_of_the_hour[2] = {0}; | 26 | static volatile uint32_t g_key_of_the_hour[2] = {0}; |
| 27 | static volatile ot_time g_hour_of_the_key; | 27 | static volatile ot_time g_hour_of_the_key; |
| 28 | 28 | ||
| 29 | static pthread_mutex_t udp_start_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 30 | static pthread_cond_t udp_start_cond = PTHREAD_COND_INITIALIZER; | ||
| 31 | static int g_opentracker_started = 0; | ||
| 32 | 29 | ||
| 33 | static void udp_generate_rijndael_round_key(void) { | 30 | static void udp_generate_rijndael_round_key(void) { |
| 34 | uint32_t key[16]; | 31 | uint32_t key[16]; |
| @@ -211,11 +208,6 @@ static void *udp_worker(void *args) { | |||
| 211 | ws.debugbuf = malloc(G_DEBUGBUF_SIZE); | 208 | ws.debugbuf = malloc(G_DEBUGBUF_SIZE); |
| 212 | #endif | 209 | #endif |
| 213 | 210 | ||
| 214 | pthread_mutex_lock(&udp_start_mutex); | ||
| 215 | while(g_opentracker_running && !g_opentracker_started) | ||
| 216 | pthread_cond_wait(&udp_start_cond, &udp_start_mutex); | ||
| 217 | pthread_mutex_unlock(&udp_start_mutex); | ||
| 218 | |||
| 219 | while (g_opentracker_running) | 211 | while (g_opentracker_running) |
| 220 | handle_udp6(sock, &ws); | 212 | handle_udp6(sock, &ws); |
| 221 | 213 | ||
| @@ -227,20 +219,18 @@ static void *udp_worker(void *args) { | |||
| 227 | return NULL; | 219 | return NULL; |
| 228 | } | 220 | } |
| 229 | 221 | ||
| 230 | void udp_init(int64_t sock, unsigned int worker_count) { | 222 | void udp_init(void) { |
| 231 | pthread_t thread_id; | ||
| 232 | if (!g_rijndael_round_key[0]) | 223 | if (!g_rijndael_round_key[0]) |
| 233 | udp_generate_rijndael_round_key(); | 224 | udp_generate_rijndael_round_key(); |
| 225 | } | ||
| 226 | |||
| 227 | void udp_workers_install(int64_t sock, unsigned int worker_count) { | ||
| 228 | pthread_t thread_id; | ||
| 229 | |||
| 234 | #ifdef _DEBUG | 230 | #ifdef _DEBUG |
| 235 | fprintf(stderr, " installing %d workers on udp socket %ld\n", worker_count, (unsigned long)sock); | 231 | fprintf(stderr, "Installing %d workers on udp socket %ld\n", worker_count, (unsigned long)sock); |
| 236 | #endif | 232 | #endif |
| 237 | while (worker_count--) | 233 | while (worker_count--) |
| 238 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); | 234 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); |
| 239 | } | 235 | } |
| 240 | 236 | ||
| 241 | void udp_kickoff(void) { | ||
| 242 | pthread_mutex_lock(&udp_start_mutex); | ||
| 243 | g_opentracker_started = 1; | ||
| 244 | pthread_cond_broadcast(&udp_start_cond); | ||
| 245 | pthread_mutex_unlock(&udp_start_mutex); | ||
| 246 | } | ||
| @@ -10,8 +10,8 @@ | |||
| 10 | 10 | ||
| 11 | #include "trackerlogic.h" | 11 | #include "trackerlogic.h" |
| 12 | 12 | ||
| 13 | void udp_init(int64_t sock, unsigned int worker_count); | 13 | void udp_init(void); |
| 14 | void udp_kickoff(void); | 14 | void udp_workers_install(int64_t sock, unsigned int worker_count); |
| 15 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); | 15 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); |
| 16 | 16 | ||
| 17 | #endif | 17 | #endif |
diff --git a/ot_vector.c b/ot_vector.c index c4cd171..7f18246 100644 --- a/ot_vector.c +++ b/ot_vector.c | |||
| @@ -8,7 +8,6 @@ | |||
| 8 | #include <stdint.h> | 8 | #include <stdint.h> |
| 9 | #include <stdlib.h> | 9 | #include <stdlib.h> |
| 10 | #include <string.h> | 10 | #include <string.h> |
| 11 | #include <strings.h> | ||
| 12 | 11 | ||
| 13 | /* Opentracker */ | 12 | /* Opentracker */ |
| 14 | #include "trackerlogic.h" | 13 | #include "trackerlogic.h" |
| @@ -174,7 +173,7 @@ void vector_clean_list(ot_vector *vector, size_t num_buckets) { | |||
| 174 | } | 173 | } |
| 175 | 174 | ||
| 176 | void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { | 175 | void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { |
| 177 | size_t tmp, bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1; | 176 | size_t bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1; |
| 178 | ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers; | 177 | ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers; |
| 179 | int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4; | 178 | int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4; |
| 180 | 179 | ||
| @@ -205,11 +204,10 @@ void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { | |||
| 205 | bucket_list_new = malloc(num_buckets_new * sizeof(ot_vector)); | 204 | bucket_list_new = malloc(num_buckets_new * sizeof(ot_vector)); |
| 206 | if (!bucket_list_new) | 205 | if (!bucket_list_new) |
| 207 | return; | 206 | return; |
| 208 | bzero(bucket_list_new, num_buckets_new * sizeof(ot_vector)); | 207 | memset(bucket_list_new, 0, num_buckets_new * sizeof(ot_vector)); |
| 209 | 208 | ||
| 210 | tmp = peer_list->peer_count / num_buckets_new; | ||
| 211 | bucket_size_new = OT_VECTOR_MIN_MEMBERS; | 209 | bucket_size_new = OT_VECTOR_MIN_MEMBERS; |
| 212 | while (bucket_size_new < tmp) | 210 | while (bucket_size_new * num_buckets_new < peer_list->peer_count) |
| 213 | bucket_size_new *= OT_VECTOR_GROW_RATIO; | 211 | bucket_size_new *= OT_VECTOR_GROW_RATIO; |
| 214 | 212 | ||
| 215 | /* preallocate vectors to hold all peers */ | 213 | /* preallocate vectors to hold all peers */ |
| @@ -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); | |||
| 72 | static void *streamsync_worker(void *args); | 72 | static void *streamsync_worker(void *args); |
| 73 | static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer); | 73 | static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer); |
| 74 | 74 | ||
| 75 | void exerr(char *message) { | 75 | void exerr(const char *message) { |
| 76 | fprintf(stderr, "%s\n", message); | 76 | fprintf(stderr, "%s\n", message); |
| 77 | exit(111); | 77 | exit(111); |
| 78 | } | 78 | } |
| @@ -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 | ||
| 487 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) { | 487 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) { |
| 488 | int64_t sock = socket_tcp_listen(ip, port); | 488 | int64_t sock = socket_tcp_listen(ip, port, SOCKET_EXCLUSIVE); |
| 489 | 489 | ||
| 490 | if (sock == -1) | 490 | if (sock == -1) |
| 491 | panic("socket_tcp_listen"); | 491 | panic("socket_tcp_listen"); |
| @@ -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 | ||
| 13 | typedef struct { | 13 | typedef struct { |
| 14 | char *key; | 14 | const char *key; |
| 15 | int value; | 15 | int value; |
| 16 | } ot_keywords; | 16 | } ot_keywords; |
| 17 | 17 | ||
| 18 | typedef enum { | 18 | typedef enum { |
diff --git a/tests/runner.c b/tests/runner.c index db62246..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. */ |
| 19 | time_t g_now_seconds; | 19 | time_t g_now_seconds; |
| 20 | volatile int g_opentracker_running; | 20 | volatile int g_opentracker_running; |
| 21 | |||
| 22 | /* ot_mutex.c wakes the main loop through the self pipe when a result is | ||
| 23 | ready. The queue tests never get that far. */ | ||
| 24 | int g_self_pipe[2] = {-1, -1}; | ||
| 25 | long io_trywrite(long d, const char *buf, long len) { | ||
| 26 | (void)d; | ||
| 27 | (void)buf; | ||
| 28 | return len; | ||
| 29 | } | ||
diff --git a/tests/t_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 | |||
| 21 | static 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 | |||
| 29 | static 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 | |||
| 42 | static 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 | |||
| 63 | static 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. */ | ||
| 91 | static 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 | |||
| 114 | static 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. */ | ||
| 147 | static 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 | |||
| 185 | static 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 | |||
| 208 | static 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 | |||
| 224 | static 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. */ | ||
| 252 | static 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. */ | ||
| 288 | static 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 | |||
| 338 | void 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 | |||
| 18 | static void case_task_goes_to_its_own_class(void) { | ||
| 19 | ot_tasktype asked; | ||
| 20 | ot_taskid taskid; | ||
| 21 | |||
| 22 | test_case("a worker is handed the task of the class it asked for"); | ||
| 23 | CHECK_INT(mutex_workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0); | ||
| 24 | asked = TASK_FULLSCRAPE; | ||
| 25 | taskid = mutex_workqueue_poptask(&asked); | ||
| 26 | CHECK(taskid != 0); | ||
| 27 | |||
| 28 | test_case("and the full task type, not just its class"); | ||
| 29 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII); | ||
| 30 | mutex_workqueue_canceltask(41); | ||
| 31 | } | ||
| 32 | |||
| 33 | static void case_flags_survive_the_queue(void) { | ||
| 34 | ot_tasktype asked; | ||
| 35 | |||
| 36 | test_case("flags outside the class mask are carried through"); | ||
| 37 | CHECK_INT(mutex_workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0); | ||
| 38 | asked = TASK_FULLSCRAPE; | ||
| 39 | CHECK(mutex_workqueue_poptask(&asked) != 0); | ||
| 40 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP); | ||
| 41 | mutex_workqueue_canceltask(42); | ||
| 42 | } | ||
| 43 | |||
| 44 | static void case_classes_do_not_cross(void) { | ||
| 45 | ot_tasktype asked; | ||
| 46 | ot_taskid taskid; | ||
| 47 | |||
| 48 | test_case("a stats worker passes over a fullscrape task"); | ||
| 49 | CHECK_INT(mutex_workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0); | ||
| 50 | CHECK_INT(mutex_workqueue_pushtask(44, TASK_STATS_PEERS), 0); | ||
| 51 | |||
| 52 | asked = TASK_STATS; | ||
| 53 | taskid = mutex_workqueue_poptask(&asked); | ||
| 54 | CHECK(taskid != 0); | ||
| 55 | CHECK_INT(asked, TASK_STATS_PEERS); | ||
| 56 | |||
| 57 | test_case("and leaves it for the one that wants it"); | ||
| 58 | asked = TASK_FULLSCRAPE; | ||
| 59 | taskid = mutex_workqueue_poptask(&asked); | ||
| 60 | CHECK(taskid != 0); | ||
| 61 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY); | ||
| 62 | |||
| 63 | mutex_workqueue_canceltask(43); | ||
| 64 | mutex_workqueue_canceltask(44); | ||
| 65 | } | ||
| 66 | |||
| 67 | static void case_every_task_gets_its_own_id(void) { | ||
| 68 | ot_tasktype asked; | ||
| 69 | ot_taskid first, second; | ||
| 70 | |||
| 71 | test_case("two queued tasks are handed out under different ids"); | ||
| 72 | CHECK_INT(mutex_workqueue_pushtask(45, TASK_FULLSCRAPE), 0); | ||
| 73 | CHECK_INT(mutex_workqueue_pushtask(46, TASK_FULLSCRAPE), 0); | ||
| 74 | |||
| 75 | asked = TASK_FULLSCRAPE; | ||
| 76 | first = mutex_workqueue_poptask(&asked); | ||
| 77 | asked = TASK_FULLSCRAPE; | ||
| 78 | second = mutex_workqueue_poptask(&asked); | ||
| 79 | CHECK(first != 0); | ||
| 80 | CHECK(second != 0); | ||
| 81 | CHECK(first != second); | ||
| 82 | |||
| 83 | mutex_workqueue_canceltask(45); | ||
| 84 | mutex_workqueue_canceltask(46); | ||
| 85 | } | ||
| 86 | |||
| 87 | static void case_cancelled_task_is_not_handed_out(void) { | ||
| 88 | ot_tasktype asked; | ||
| 89 | |||
| 90 | test_case("a cancelled task is gone before any worker sees it"); | ||
| 91 | CHECK_INT(mutex_workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0); | ||
| 92 | CHECK_INT(mutex_workqueue_pushtask(48, TASK_FULLSCRAPE), 0); | ||
| 93 | mutex_workqueue_canceltask(47); | ||
| 94 | |||
| 95 | asked = TASK_FULLSCRAPE; | ||
| 96 | CHECK(mutex_workqueue_poptask(&asked) != 0); | ||
| 97 | CHECK_INT(asked, TASK_FULLSCRAPE); | ||
| 98 | mutex_workqueue_canceltask(48); | ||
| 99 | } | ||
| 100 | |||
| 101 | void test_mutex(void) { | ||
| 102 | case_task_goes_to_its_own_class(); | ||
| 103 | case_flags_survive_the_queue(); | ||
| 104 | case_classes_do_not_cross(); | ||
| 105 | case_every_task_gets_its_own_id(); | ||
| 106 | case_cancelled_task_is_not_handed_out(); | ||
| 107 | } | ||
diff --git a/tests/t_socket.c b/tests/t_socket.c index bbc9e5e..8eef7fd 100644 --- a/tests/t_socket.c +++ b/tests/t_socket.c | |||
| @@ -91,14 +91,14 @@ static void case_socket_options(void) { | |||
| 91 | int udp, tcp; | 91 | int udp, tcp; |
| 92 | 92 | ||
| 93 | test_case("a bound udp socket is non blocking and serves both families"); | 93 | test_case("a bound udp socket is non blocking and serves both families"); |
| 94 | udp = socket_udp_bind(ip_any, 0); | 94 | udp = socket_udp_bind(ip_any, 0, SOCKET_EXCLUSIVE); |
| 95 | CHECK(udp != -1); | 95 | CHECK(udp != -1); |
| 96 | CHECK_INT(is_nonblocking(udp), 1); | 96 | CHECK_INT(is_nonblocking(udp), 1); |
| 97 | CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0); | 97 | CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0); |
| 98 | close(udp); | 98 | close(udp); |
| 99 | 99 | ||
| 100 | test_case("a listening tcp socket is non blocking and serves both families"); | 100 | test_case("a listening tcp socket is non blocking and serves both families"); |
| 101 | tcp = socket_tcp_listen(ip_any, 0); | 101 | tcp = socket_tcp_listen(ip_any, 0, SOCKET_EXCLUSIVE); |
| 102 | CHECK(tcp != -1); | 102 | CHECK(tcp != -1); |
| 103 | CHECK_INT(is_nonblocking(tcp), 1); | 103 | CHECK_INT(is_nonblocking(tcp), 1); |
| 104 | CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0); | 104 | CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0); |
| @@ -106,24 +106,20 @@ static void case_socket_options(void) { | |||
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | #ifdef SO_REUSEPORT | 108 | #ifdef SO_REUSEPORT |
| 109 | static void case_reuseport_is_opt_in(void) { | 109 | static void case_sharing_is_opt_in(void) { |
| 110 | int s; | 110 | int s; |
| 111 | 111 | ||
| 112 | test_case("reuseport stays off unless the operator asks for it"); | 112 | test_case("an address is exclusive unless sharing is asked for"); |
| 113 | g_socket_reuseport = 0; | 113 | s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 114 | s = socket_udp_bind(ip_loopback, 0); | ||
| 115 | CHECK(s != -1); | 114 | CHECK(s != -1); |
| 116 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0); | 115 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0); |
| 117 | close(s); | 116 | close(s); |
| 118 | 117 | ||
| 119 | test_case("reuseport reaches the socket when it is asked for"); | 118 | test_case("and shared when it is"); |
| 120 | g_socket_reuseport = 1; | 119 | s = socket_udp_bind(ip_loopback, 0, SOCKET_SHARED); |
| 121 | s = socket_udp_bind(ip_loopback, 0); | ||
| 122 | CHECK(s != -1); | 120 | CHECK(s != -1); |
| 123 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1); | 121 | CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1); |
| 124 | close(s); | 122 | close(s); |
| 125 | |||
| 126 | g_socket_reuseport = 0; | ||
| 127 | } | 123 | } |
| 128 | #endif | 124 | #endif |
| 129 | 125 | ||
| @@ -135,7 +131,7 @@ static void case_udp_roundtrip(void) { | |||
| 135 | int s; | 131 | int s; |
| 136 | 132 | ||
| 137 | test_case("a datagram comes back with the sender's address and port"); | 133 | test_case("a datagram comes back with the sender's address and port"); |
| 138 | s = socket_udp_bind(ip_loopback, 0); | 134 | s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 139 | CHECK(s != -1); | 135 | CHECK(s != -1); |
| 140 | bound_address(s, own_ip, &own_port); | 136 | bound_address(s, own_ip, &own_port); |
| 141 | CHECK(own_port != 0); | 137 | CHECK(own_port != 0); |
| @@ -158,9 +154,9 @@ static void case_udp_v4mapped(void) { | |||
| 158 | int receiver, sender; | 154 | int receiver, sender; |
| 159 | 155 | ||
| 160 | test_case("an IPv4 sender is reported in its v4 mapped form"); | 156 | test_case("an IPv4 sender is reported in its v4 mapped form"); |
| 161 | receiver = socket_udp_bind(ip_v4_loopback, 0); | 157 | receiver = socket_udp_bind(ip_v4_loopback, 0, SOCKET_EXCLUSIVE); |
| 162 | CHECK(receiver != -1); | 158 | CHECK(receiver != -1); |
| 163 | sender = socket_udp_bind(ip_v4_loopback, 0); | 159 | sender = socket_udp_bind(ip_v4_loopback, 0, SOCKET_EXCLUSIVE); |
| 164 | CHECK(sender != -1); | 160 | CHECK(sender != -1); |
| 165 | bound_address(receiver, receiver_ip, &receiver_port); | 161 | bound_address(receiver, receiver_ip, &receiver_port); |
| 166 | bound_address(sender, sender_ip, &sender_port); | 162 | bound_address(sender, sender_ip, &sender_port); |
| @@ -184,7 +180,7 @@ static void case_tcp_roundtrip(void) { | |||
| 184 | int listener, client, accepted; | 180 | int listener, client, accepted; |
| 185 | 181 | ||
| 186 | test_case("a connection is accepted and carries the caller's address"); | 182 | test_case("a connection is accepted and carries the caller's address"); |
| 187 | listener = socket_tcp_listen(ip_loopback, 0); | 183 | listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 188 | CHECK(listener != -1); | 184 | CHECK(listener != -1); |
| 189 | bound_address(listener, listener_ip, &listener_port); | 185 | bound_address(listener, listener_ip, &listener_port); |
| 190 | CHECK(listener_port != 0); | 186 | CHECK(listener_port != 0); |
| @@ -216,7 +212,7 @@ static void case_connect_refused(void) { | |||
| 216 | 212 | ||
| 217 | test_case("a refused connection is reported by socket_is_connected"); | 213 | test_case("a refused connection is reported by socket_is_connected"); |
| 218 | /* Take a port, then drop it, so nothing is listening on it any more */ | 214 | /* Take a port, then drop it, so nothing is listening on it any more */ |
| 219 | listener = socket_tcp_listen(ip_loopback, 0); | 215 | listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE); |
| 220 | CHECK(listener != -1); | 216 | CHECK(listener != -1); |
| 221 | bound_address(listener, listener_ip, &port); | 217 | bound_address(listener, listener_ip, &port); |
| 222 | close(listener); | 218 | close(listener); |
| @@ -247,8 +243,8 @@ static void case_bind_failure(void) { | |||
| 247 | test_case("binding an address no interface carries fails"); | 243 | test_case("binding an address no interface carries fails"); |
| 248 | before = next_descriptor(); | 244 | before = next_descriptor(); |
| 249 | CHECK(before != -1); | 245 | CHECK(before != -1); |
| 250 | CHECK_INT(socket_tcp_listen(ip_unassigned, 0), -1); | 246 | CHECK_INT(socket_tcp_listen(ip_unassigned, 0, SOCKET_EXCLUSIVE), -1); |
| 251 | CHECK_INT(socket_udp_bind(ip_unassigned, 0), -1); | 247 | CHECK_INT(socket_udp_bind(ip_unassigned, 0, SOCKET_EXCLUSIVE), -1); |
| 252 | 248 | ||
| 253 | test_case("a failed bind leaves no socket behind"); | 249 | test_case("a failed bind leaves no socket behind"); |
| 254 | CHECK_INT(next_descriptor(), before); | 250 | CHECK_INT(next_descriptor(), before); |
| @@ -294,7 +290,7 @@ static void case_mcast_sender(void) { | |||
| 294 | void test_socket(void) { | 290 | void test_socket(void) { |
| 295 | case_socket_options(); | 291 | case_socket_options(); |
| 296 | #ifdef SO_REUSEPORT | 292 | #ifdef SO_REUSEPORT |
| 297 | case_reuseport_is_opt_in(); | 293 | case_sharing_is_opt_in(); |
| 298 | #endif | 294 | #endif |
| 299 | case_udp_roundtrip(); | 295 | case_udp_roundtrip(); |
| 300 | case_udp_v4mapped(); | 296 | case_udp_v4mapped(); |
diff --git a/tests/t_vector.c b/tests/t_vector.c index fee336b..a8186b2 100644 --- a/tests/t_vector.c +++ b/tests/t_vector.c | |||
| @@ -4,10 +4,12 @@ | |||
| 4 | $id$ */ | 4 | $id$ */ |
| 5 | 5 | ||
| 6 | /* System */ | 6 | /* System */ |
| 7 | #include <stdint.h> | ||
| 7 | #include <stdlib.h> | 8 | #include <stdlib.h> |
| 8 | #include <string.h> | 9 | #include <string.h> |
| 9 | 10 | ||
| 10 | /* Opentracker */ | 11 | /* Opentracker */ |
| 12 | #include "ot_vector.h" | ||
| 11 | #include "trackerlogic.h" | 13 | #include "trackerlogic.h" |
| 12 | #include "test.h" | 14 | #include "test.h" |
| 13 | 15 | ||
| @@ -149,6 +151,105 @@ static void case_insert_compare_prefix(void) { | |||
| 149 | free(vector.data); | 151 | free(vector.data); |
| 150 | } | 152 | } |
| 151 | 153 | ||
| 154 | /* ------------------------------------------------- bucket redistribution */ | ||
| 155 | |||
| 156 | /* A flat peer list of count distinct v4 peers. The low byte of the index goes | ||
| 157 | into the first address byte, so the peers are distinct but arrive in no | ||
| 158 | particular order: a list generated in ascending order would come back | ||
| 159 | sorted whether or not vector_redistribute_buckets sorted it. */ | ||
| 160 | static void build_flat_peerlist(ot_peerlist *peer_list, size_t count) { | ||
| 161 | size_t i; | ||
| 162 | |||
| 163 | memset(peer_list, 0, sizeof(*peer_list)); | ||
| 164 | peer_list->peers.data = malloc(count * OT_PEER_SIZE4); | ||
| 165 | peer_list->peers.size = count; | ||
| 166 | peer_list->peers.space = count; | ||
| 167 | peer_list->peer_count = count; | ||
| 168 | |||
| 169 | for (i = 0; i < count; ++i) { | ||
| 170 | uint8_t *peer = (uint8_t *)peer_list->peers.data + i * OT_PEER_SIZE4; | ||
| 171 | |||
| 172 | memset(peer, 0, OT_PEER_SIZE4); | ||
| 173 | peer[0] = (uint8_t)i; | ||
| 174 | peer[1] = (uint8_t)(i >> 8); | ||
| 175 | peer[2] = (uint8_t)(i >> 16); | ||
| 176 | peer[3] = 1; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | static void case_redistribute_holds_every_peer(void) { | ||
| 181 | const size_t count = 513; | ||
| 182 | ot_peerlist peer_list; | ||
| 183 | size_t bucket, total_size = 0, empty = 0, largest = 0; | ||
| 184 | ot_vector *buckets; | ||
| 185 | |||
| 186 | test_case("a peer list past the threshold is split into buckets"); | ||
| 187 | build_flat_peerlist(&peer_list, count); | ||
| 188 | CHECK(peer_list.peers.data != NULL); | ||
| 189 | vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); | ||
| 190 | CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1); | ||
| 191 | CHECK_INT(peer_list.peers.size, 16); | ||
| 192 | |||
| 193 | buckets = peer_list.peers.data; | ||
| 194 | for (bucket = 0; bucket < peer_list.peers.size; ++bucket) { | ||
| 195 | total_size += buckets[bucket].size; | ||
| 196 | if (!buckets[bucket].size) | ||
| 197 | ++empty; | ||
| 198 | if (buckets[bucket].size > largest) | ||
| 199 | largest = buckets[bucket].size; | ||
| 200 | } | ||
| 201 | |||
| 202 | test_case("no peer is lost on the way"); | ||
| 203 | CHECK_INT(total_size, count); | ||
| 204 | |||
| 205 | /* The point of the exercise is that a lookup only searches one bucket, so | ||
| 206 | a hash that piled everything into one would defeat it. The input and the | ||
| 207 | hash are both fixed, so this is not a matter of luck. */ | ||
| 208 | test_case("and they are spread over the buckets rather than piled up"); | ||
| 209 | CHECK_INT(empty, 0); | ||
| 210 | CHECK(largest < count / 2); | ||
| 211 | |||
| 212 | vector_clean_list(peer_list.peers.data, peer_list.peers.size); | ||
| 213 | } | ||
| 214 | |||
| 215 | static void case_redistribute_sorts_each_bucket(void) { | ||
| 216 | const size_t count = 1000; | ||
| 217 | ot_peerlist peer_list; | ||
| 218 | size_t bucket, i, out_of_order = 0; | ||
| 219 | ot_vector *buckets; | ||
| 220 | |||
| 221 | test_case("every bucket comes back sorted, so bsearch can be used on it"); | ||
| 222 | build_flat_peerlist(&peer_list, count); | ||
| 223 | CHECK(peer_list.peers.data != NULL); | ||
| 224 | vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); | ||
| 225 | CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1); | ||
| 226 | |||
| 227 | buckets = peer_list.peers.data; | ||
| 228 | for (bucket = 0; bucket < peer_list.peers.size; ++bucket) | ||
| 229 | for (i = 1; i < buckets[bucket].size; ++i) { | ||
| 230 | const uint8_t *prev = (uint8_t *)buckets[bucket].data + (i - 1) * OT_PEER_SIZE4; | ||
| 231 | const uint8_t *next = (uint8_t *)buckets[bucket].data + i * OT_PEER_SIZE4; | ||
| 232 | |||
| 233 | if (memcmp(prev, next, OT_PEER_COMPARE_SIZE4) >= 0) | ||
| 234 | ++out_of_order; | ||
| 235 | } | ||
| 236 | CHECK_INT(out_of_order, 0); | ||
| 237 | |||
| 238 | vector_clean_list(peer_list.peers.data, peer_list.peers.size); | ||
| 239 | } | ||
| 240 | |||
| 241 | static void case_redistribute_below_threshold(void) { | ||
| 242 | ot_peerlist peer_list; | ||
| 243 | |||
| 244 | test_case("a small peer list is left flat"); | ||
| 245 | build_flat_peerlist(&peer_list, 100); | ||
| 246 | CHECK(peer_list.peers.data != NULL); | ||
| 247 | vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); | ||
| 248 | CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 0); | ||
| 249 | CHECK_INT(peer_list.peers.size, 100); | ||
| 250 | free(peer_list.peers.data); | ||
| 251 | } | ||
| 252 | |||
| 152 | void test_vector(void) { | 253 | void test_vector(void) { |
| 153 | case_search_finds_member(); | 254 | case_search_finds_member(); |
| 154 | case_search_finds_first_and_last(); | 255 | case_search_finds_first_and_last(); |
| @@ -158,4 +259,7 @@ void test_vector(void) { | |||
| 158 | case_insert_keeps_order(); | 259 | case_insert_keeps_order(); |
| 159 | case_insert_finds_existing(); | 260 | case_insert_finds_existing(); |
| 160 | case_insert_compare_prefix(); | 261 | case_insert_compare_prefix(); |
| 262 | case_redistribute_holds_every_peer(); | ||
| 263 | case_redistribute_sorts_each_bucket(); | ||
| 264 | case_redistribute_below_threshold(); | ||
| 161 | } | 265 | } |
diff --git a/tests/test.h b/tests/test.h index d79c4d2..fd574c2 100644 --- a/tests/test.h +++ b/tests/test.h | |||
| @@ -86,5 +86,7 @@ void test_scan(void); | |||
| 86 | void test_buf(void); | 86 | void test_buf(void); |
| 87 | void test_ip(void); | 87 | void test_ip(void); |
| 88 | void test_socket(void); | 88 | void test_socket(void); |
| 89 | void test_mutex(void); | ||
| 90 | void 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 | ||
| 553 | void exerr(char *message) { | 550 | void 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 | ||
| 575 | void trackerlogic_deinit(void) { | 571 | void trackerlogic_deinit(void) { |
diff --git a/trackerlogic.h b/trackerlogic.h index aa10a54..42018c3 100644 --- a/trackerlogic.h +++ b/trackerlogic.h | |||
| @@ -181,7 +181,7 @@ struct ot_workstruct { | |||
| 181 | 181 | ||
| 182 | void trackerlogic_init(void); | 182 | void trackerlogic_init(void); |
| 183 | void trackerlogic_deinit(void); | 183 | void trackerlogic_deinit(void); |
| 184 | void exerr(char *message); | 184 | void exerr(const char *message); |
| 185 | 185 | ||
| 186 | /* add_peer_to_torrent does only release the torrent bucket if from_sync is set, | 186 | /* add_peer_to_torrent does only release the torrent bucket if from_sync is set, |
| 187 | otherwise it is released in return_peers_for_torrent */ | 187 | otherwise it is released in return_peers_for_torrent */ |
