summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/opentracker.c443
-rw-r--r--src/ot_accesslist.c601
-rw-r--r--src/ot_accesslist.h77
-rw-r--r--src/ot_batch.c156
-rw-r--r--src/ot_batch.h46
-rw-r--r--src/ot_buf.c39
-rw-r--r--src/ot_buf.h25
-rw-r--r--src/ot_clean.c176
-rw-r--r--src/ot_clean.h28
-rw-r--r--src/ot_config.c586
-rw-r--r--src/ot_config.h33
-rw-r--r--src/ot_event.c357
-rw-r--r--src/ot_event.h47
-rw-r--r--src/ot_fdstate.c202
-rw-r--r--src/ot_fdstate.h62
-rw-r--r--src/ot_fmt.c40
-rw-r--r--src/ot_fmt.h19
-rw-r--r--src/ot_fullscrape.c455
-rw-r--r--src/ot_fullscrape.h23
-rw-r--r--src/ot_hex.c56
-rw-r--r--src/ot_hex.h27
-rw-r--r--src/ot_http.c810
-rw-r--r--src/ot_http.h53
-rw-r--r--src/ot_iovec.c87
-rw-r--r--src/ot_iovec.h18
-rw-r--r--src/ot_ip.c199
-rw-r--r--src/ot_ip.h31
-rw-r--r--src/ot_livesync.c241
-rw-r--r--src/ot_livesync.h75
-rw-r--r--src/ot_mutex.c70
-rw-r--r--src/ot_mutex.h21
-rw-r--r--src/ot_random.c51
-rw-r--r--src/ot_random.h17
-rw-r--r--src/ot_rijndael.c488
-rw-r--r--src/ot_rijndael.h20
-rw-r--r--src/ot_scan.c65
-rw-r--r--src/ot_scan.h25
-rw-r--r--src/ot_socket.c310
-rw-r--r--src/ot_socket.h56
-rw-r--r--src/ot_stats.c866
-rw-r--r--src/ot_stats.h40
-rw-r--r--src/ot_tcp.c180
-rw-r--r--src/ot_tcp.h24
-rw-r--r--src/ot_udp.c226
-rw-r--r--src/ot_udp.h17
-rw-r--r--src/ot_urlencoded.c168
-rw-r--r--src/ot_urlencoded.h53
-rw-r--r--src/ot_vector.c287
-rw-r--r--src/ot_vector.h39
-rw-r--r--src/ot_worker.c73
-rw-r--r--src/ot_worker.h37
-rw-r--r--src/ot_workqueue.c241
-rw-r--r--src/ot_workqueue.h74
-rw-r--r--src/proxy.c871
-rw-r--r--src/trackerlogic.c628
-rw-r--r--src/trackerlogic.h283
56 files changed, 10242 insertions, 0 deletions
diff --git a/src/opentracker.c b/src/opentracker.c
new file mode 100644
index 0000000..14b817e
--- /dev/null
+++ b/src/opentracker.c
@@ -0,0 +1,443 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3 Some of the stuff below is stolen from Fefes example libowfat httpd. */
4
5/* System */
6#include <ctype.h>
7#include <errno.h>
8#include <limits.h>
9#include <pthread.h>
10#include <pwd.h>
11#include <signal.h>
12#include <stdint.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/socket.h>
17#include <unistd.h>
18#ifdef WANT_SYSLOGS
19#include <syslog.h>
20#endif
21
22/* Opentracker */
23#include "ot_accesslist.h"
24#include "ot_config.h"
25#include "ot_hex.h"
26#include "ot_http.h"
27#include "ot_ip.h"
28#include "ot_livesync.h"
29#include "ot_mutex.h"
30#include "ot_workqueue.h"
31#include "ot_random.h"
32#include "ot_scan.h"
33#include "ot_event.h"
34#include "ot_fdstate.h"
35#include "ot_socket.h"
36#include "ot_stats.h"
37#include "ot_tcp.h"
38#include "ot_udp.h"
39#include "ot_worker.h"
40#include "trackerlogic.h"
41
42/* Globals */
43uint32_t g_tracker_id;
44_Atomic int g_opentracker_running = 1;
45int g_self_pipe[2];
46
47static void panic(const char *routine) __attribute__((noreturn));
48static void panic(const char *routine) {
49 fprintf(stderr, "%s: %s\n", routine, strerror(errno));
50 exit(111);
51}
52
53/* Say we are stopping, wake the loop, and let main do the work on its own
54 stack. Everything other than SIGINT falls through and does nothing, which
55 is what OT_SIGNAL_WAKE wants: returning from the read is all it is for. */
56static void signal_handler(int s) {
57 if (s == SIGINT) {
58 const char byte = 0;
59 int saved = errno;
60
61 /* Any new interrupt signal quits the application */
62 signal(SIGINT, SIG_DFL);
63
64 /* Tell all other threads to not acquire any new lock on a bucket
65 but cancel their operations and return */
66 g_opentracker_running = 0;
67
68 (void)!write(g_self_pipe[1], &byte, 1);
69 errno = saved;
70 }
71}
72
73static void defaul_signal_handlers(void) {
74 sigset_t signal_mask;
75 sigemptyset(&signal_mask);
76 sigaddset(&signal_mask, SIGPIPE);
77 sigaddset(&signal_mask, SIGHUP);
78 sigaddset(&signal_mask, SIGINT);
79 sigaddset(&signal_mask, SIGALRM);
80 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
81}
82
83static void install_signal_handlers(void) {
84 struct sigaction sa;
85 sigset_t signal_mask;
86 sigemptyset(&signal_mask);
87
88 sa.sa_handler = signal_handler;
89 sigemptyset(&sa.sa_mask);
90 sa.sa_flags = 0;
91 if ((sigaction(SIGINT, &sa, NULL) == -1) || (sigaction(SIGALRM, &sa, NULL) == -1) ||
92 (sigaction(OT_SIGNAL_WAKE, &sa, NULL) == -1))
93 panic("install_signal_handlers");
94
95 sigaddset(&signal_mask, SIGINT);
96 pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
97}
98
99
100/* How many ready descriptors one wait hands back at a time */
101#define OT_EVENT_BATCH 512
102
103static void *server_mainloop(void *args) {
104 struct ot_workstruct ws;
105 time_t next_timeout_check = time_future_seconds(OT_CLIENT_TIMEOUT_CHECKINTERVAL);
106 struct iovec *iovector;
107 size_t iovec_entries;
108 int is_partial;
109
110 (void)args;
111
112 if (workstruct_init(&ws) == -1)
113 panic("Initializing worker failed");
114
115 while (g_opentracker_running) {
116 ot_event ready[OT_EVENT_BATCH];
117 int64_t sock;
118 int count, i;
119
120 count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000);
121
122 for (i = 0; i < count; ++i) {
123 const void *cookie;
124
125 sock = ready[i].fd;
126 /* A descriptor closed earlier in this same batch may already have been
127 handed out again by accept, so an event is only ours if the
128 generation it was registered under is still the one in place */
129 if (fdstate_generation((int)sock) != ready[i].generation)
130 continue;
131 cookie = fdstate_cookie((int)sock);
132
133 if ((intptr_t)cookie == FLAG_TCP)
134 tcp_accept(sock, &ws);
135 else if ((intptr_t)cookie == FLAG_UDP)
136 handle_udp6(sock, &ws);
137 else if ((intptr_t)cookie == FLAG_SELFPIPE)
138 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
139 ;
140 else if (fdstate_claim((int)sock, ready[i].generation)) {
141 if (ready[i].what & EVENT_IN)
142 tcp_read(sock, &ws);
143 /* The read may have closed it, so look again before writing */
144 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation)
145 tcp_write(sock);
146 if (fdstate_generation((int)sock) == ready[i].generation)
147 fdstate_release((int)sock);
148 }
149 }
150
151 while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
152 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
153
154 if (time_now_seconds() > next_timeout_check) {
155 int cursor = 0;
156
157 while ((sock = fdstate_next_expired(time_now_seconds(), &cursor)) != -1)
158 http_close(sock);
159 next_timeout_check = time_future_seconds(OT_CLIENT_TIMEOUT_CHECKINTERVAL);
160 }
161
162 livesync_ticker();
163 }
164
165 /* This work struct is about to go out of scope, so hand its tally over
166 before anything can read it there */
167 workstruct_deinit(&ws);
168 return 0;
169}
170
171
172/* A group of sockets sharing one address only spreads datagrams if every
173 member asked for it before binding, the first one included, so whether
174 this entry will have workers has to be known here. */
175static socket_sharing listen_sharing_for(const ot_listen *entry) {
176 if (entry->proto != FLAG_MCA && entry->workers > 1 && socket_can_balance())
177 return SOCKET_BALANCED;
178 return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE;
179}
180
181static void listen_bind_one(ot_listen *entry) {
182 socket_sharing sharing = listen_sharing_for(entry);
183
184#ifdef _DEBUG
185 {
186 const char *protos[] = {"TCP", "UDP", "UDP mcast"};
187 char _debug[512];
188 int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[entry->proto]);
189 off += ip_format(_debug + off, entry->ip);
190 snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", entry->port);
191 fputs(_debug, stderr);
192 }
193#endif
194
195#ifdef WANT_SYNC_LIVE
196 if (entry->proto == FLAG_MCA) {
197 livesync_bind_mcast(entry->ip, entry->port);
198#ifdef _DEBUG
199 fputs(" success.\n", stderr);
200#endif
201 return;
202 }
203#endif
204
205 if (entry->proto == FLAG_TCP) {
206 if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1)
207 panic("socket_tcp_listen");
208 socket_defer_accept((int)entry->sock);
209 } else {
210 if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1)
211 panic("socket_udp_bind");
212 }
213
214 if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1)
215 panic("event_add");
216
217#ifdef _DEBUG
218 fputs(" success.\n", stderr);
219#endif
220}
221
222/* Bound in the order the entries were given.
223 Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */
224static void listen_bind_all(void) {
225 size_t count, i;
226 ot_listen *entries = config_listen_entries(&count);
227
228 for (i = 0; i < count; ++i)
229 listen_bind_one(entries + i);
230}
231
232/* The socket the event loop was using is passed to the first worker.
233 If start up tests show that the kernel can spread connections over
234 a group bound to one address, every worker binds one of its own.
235 If not, all workers call accept on the first. */
236static void listen_start_tcp_workers(void) {
237 size_t count, i;
238 ot_listen *entries = config_listen_entries(&count);
239
240 for (i = 0; i < count; ++i) {
241 ot_listen *entry = entries + i;
242 int balance = listen_sharing_for(entry) == SOCKET_BALANCED;
243 int worker;
244
245 if (entry->proto != FLAG_TCP || entry->workers <= 0)
246 continue;
247
248 event_del((int)entry->sock);
249 tcp_worker_install(entry->sock);
250
251 for (worker = 1; worker < entry->workers; ++worker) {
252 int64_t sock = entry->sock;
253
254 if (balance) {
255 sock = socket_tcp_listen(entry->ip, entry->port, SOCKET_BALANCED);
256 if (sock == -1) {
257 fprintf(stderr, "Warning: could not bind another socket for tcp worker %d: %s. Sharing the first instead.\n", worker,
258 strerror(errno));
259 sock = entry->sock;
260 }
261 }
262 tcp_worker_install(sock);
263 }
264 }
265}
266
267static void listen_start_udp_workers(void) {
268 size_t count, i;
269 ot_listen *entries = config_listen_entries(&count);
270
271 udp_init();
272
273 for (i = 0; i < count; ++i) {
274 ot_listen *entry = entries + i;
275 int balance = listen_sharing_for(entry) == SOCKET_BALANCED;
276 int worker;
277
278 if (entry->proto != FLAG_UDP || entry->workers <= 0)
279 continue;
280
281 event_del((int)entry->sock);
282 udp_worker_install(entry->sock);
283
284 for (worker = 1; worker < entry->workers; ++worker) {
285 int64_t sock = entry->sock;
286
287 if (balance) {
288 sock = socket_udp_bind(entry->ip, entry->port, SOCKET_BALANCED);
289 if (sock == -1) {
290 fprintf(stderr, "Warning: could not bind another socket for udp worker %d: %s. Sharing the first instead.\n", worker, strerror(errno));
291 sock = entry->sock;
292 }
293 }
294 udp_worker_install(sock);
295 }
296 }
297}
298
299void load_state(const char *const state_filename) {
300 FILE *state_filehandle;
301 char inbuf[512];
302 ot_hash infohash;
303 uint64_t base, downcount;
304 int consumed;
305
306 state_filehandle = fopen(state_filename, "r");
307
308 if (state_filehandle == NULL) {
309 fprintf(stderr, "Warning: Can't open config file: %s.", state_filename);
310 return;
311 }
312
313 /* We do ignore anything that is not of the form "^[:xdigit:]:\d+:\d+" */
314 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) {
315 size_t i;
316
317 if (!hex_to_hash(infohash, inbuf))
318 continue;
319 i = 2 * sizeof(ot_hash);
320
321 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX)))
322 continue;
323 i += consumed;
324 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX)))
325 continue;
326 add_torrent_from_saved_state(infohash, base, downcount);
327 }
328
329 fclose(state_filehandle);
330}
331
332int drop_privileges(const char *const serveruser, const char *const serverdir) {
333 struct passwd *pws = NULL;
334
335#ifdef _DEBUG
336 if (!geteuid())
337 fprintf(stderr, "Dropping to user %s.\n", serveruser);
338 if (serverdir)
339 fprintf(stderr, "ch%s'ing to directory %s.\n", geteuid() ? "dir" : "root", serverdir);
340#endif
341
342 /* Grab pws entry before chrooting */
343 pws = getpwnam(serveruser);
344 endpwent();
345
346 if (geteuid() == 0) {
347 /* Running as root: chroot and drop privileges */
348 if (serverdir && chroot(serverdir)) {
349 fprintf(stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno));
350 return -1;
351 }
352
353 if (chdir("/"))
354 panic("chdir() failed after chrooting: ");
355
356 /* If we can't find server user, revert to nobody's default uid */
357 if (!pws) {
358 fprintf(stderr, "Warning: Could not get password entry for %s. Reverting to uid -2.\n", serveruser);
359 if (setegid((gid_t)-2) || setgid((gid_t)-2) || setuid((uid_t)-2) || seteuid((uid_t)-2))
360 panic("Could not set uid to value -2");
361 } else {
362 if (setegid(pws->pw_gid) || setgid(pws->pw_gid) || setuid(pws->pw_uid) || seteuid(pws->pw_uid))
363 panic("Could not set uid to specified value");
364 }
365
366 if (geteuid() == 0 || getegid() == 0)
367 panic("Still running with root privileges?!");
368 } else {
369 /* Normal user, just chdir() */
370 if (serverdir && chdir(serverdir)) {
371 fprintf(stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno));
372 return -1;
373 }
374 }
375
376 return 0;
377}
378
379/* Maintain our copy of the clock. time() on BSDs is very expensive. */
380int main(int argc, char **argv) {
381
382 random_init();
383 if (fdstate_init() == -1)
384 panic("fdstate_init");
385 if (event_init() == -1)
386 panic("event_init");
387 config_parse(argc, argv);
388
389 listen_bind_all();
390
391 defaul_signal_handlers();
392
393#ifdef WANT_SYSLOGS
394 openlog("opentracker", 0, LOG_USER);
395 setlogmask(LOG_UPTO(LOG_INFO));
396#endif
397
398 if (drop_privileges(g_serveruser ? g_serveruser : "nobody", g_serverdir) == -1)
399 panic("drop_privileges failed, exiting. Last error");
400
401
402 /* Create our self pipe which allows us to interrupt mainloops
403 wait in case some data is available to send out */
404 if (pipe(g_self_pipe) == -1)
405 panic("selfpipe failed: ");
406 /* Both ends have to be non blocking: the loop drains the read end until it
407 is empty, and a worker nudging a full pipe has already made its point */
408 socket_set_blocking(g_self_pipe[0], 0);
409 socket_set_blocking(g_self_pipe[1], 0);
410 if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1)
411 panic("selfpipe event_add");
412
413 /* Init all sub systems. This call may fail with an exit() */
414 trackerlogic_init();
415
416#ifdef _DEBUG_RANDOMTORRENTS
417 fprintf(stderr, "DEBUG: Generating %d random peers on random torrents. This may take a while. (Setting RANDOMTORRENTS in trackerlogic.h)\n", RANDOMTORRENTS);
418 trackerlogic_add_random_torrents(RANDOMTORRENTS);
419 fprintf(stderr, "... done.\n");
420#endif
421
422 if (g_statefile)
423 load_state(g_statefile);
424
425 install_signal_handlers();
426
427 listen_start_tcp_workers();
428 listen_start_udp_workers();
429
430 server_mainloop(0);
431 worker_stop_all();
432
433 /* No thread is serving any more, so the teardown can run where a debugger
434 and the sanitizers can follow it */
435 trackerlogic_deinit();
436#ifdef WANT_SYSLOGS
437 closelog();
438#endif
439
440 event_deinit();
441 fdstate_deinit();
442 return 0;
443}
diff --git a/src/ot_accesslist.c b/src/ot_accesslist.c
new file mode 100644
index 0000000..86f9a7d
--- /dev/null
+++ b/src/ot_accesslist.c
@@ -0,0 +1,601 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <pthread.h>
6#include <signal.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11#if defined(WANT_ACCESSLIST_BLACK) || defined(WANT_ACCESSLIST_WHITE)
12#include <fcntl.h>
13#include <stdint.h>
14#include <sys/mman.h>
15#include <sys/stat.h>
16#include <sys/types.h>
17#endif
18#ifdef WANT_DYNAMIC_ACCESSLIST
19#include <errno.h>
20#endif
21
22/* Opentracker */
23#include "ot_accesslist.h"
24#include "ot_hex.h"
25#include "ot_ip.h"
26#include "ot_vector.h"
27#include "trackerlogic.h"
28
29/* GLOBAL VARIABLES */
30#ifdef WANT_ACCESSLIST
31const char *g_accesslist_filename = NULL;
32#ifdef WANT_DYNAMIC_ACCESSLIST
33const char *g_accesslist_pipe_add = NULL;
34const char *g_accesslist_pipe_delete = NULL;
35#endif
36static pthread_mutex_t g_accesslist_mutex;
37
38/* Accesslists are lock free linked lists. We can not make them locking, because every announce
39 would try to acquire the mutex, making it the most contested mutex in the whole of opentracker,
40 basically creating a central performance choke point.
41
42 The idea is that updating the list heads happens under the g_accesslist_mutex guard and is
43 done atomically, while consumers might potentially still hold pointers deeper inside the list.
44
45 Consumers (for now only via accesslist_hashisvalid) will always fetch the list head pointer
46 that is guaranteed to live for at least five minutes. This should be many orders of magnitudes
47 more than how long it will be needed by the bsearch done on the list. */
48struct ot_accesslist;
49typedef struct ot_accesslist ot_accesslist;
50struct ot_accesslist {
51 ot_hash *list;
52 size_t size;
53 ot_time base;
54 ot_accesslist *next;
55};
56static ot_accesslist *_Atomic g_accesslist = NULL;
57#ifdef WANT_DYNAMIC_ACCESSLIST
58static ot_accesslist *_Atomic g_accesslist_add = NULL;
59static ot_accesslist *_Atomic g_accesslist_delete = NULL;
60#endif
61
62/* Helpers to work on access lists */
63static int vector_compare_hash(const void *hash1, const void *hash2) { return memcmp(hash1, hash2, OT_HASH_COMPARE_SIZE); }
64
65static ot_accesslist *accesslist_free(ot_accesslist *accesslist) {
66 while (accesslist) {
67 ot_accesslist *this_accesslist = accesslist;
68 accesslist = this_accesslist->next;
69 free(this_accesslist->list);
70 free(this_accesslist);
71 }
72 return NULL;
73}
74
75static ot_accesslist *accesslist_make(ot_accesslist *next, size_t size) {
76 ot_accesslist *accesslist_new = malloc(sizeof(ot_accesslist));
77 if (accesslist_new) {
78 accesslist_new->list = size ? malloc(sizeof(ot_hash) * size) : NULL;
79 accesslist_new->size = size;
80 accesslist_new->base = 0;
81 accesslist_new->next = next;
82 if (size && !accesslist_new->list) {
83 free(accesslist_new);
84 accesslist_new = NULL;
85 }
86 }
87 return accesslist_new;
88}
89
90/* This must be called with g_accesslist_mutex held.
91 This will never delete head, because that might still be in use. */
92static void accesslist_clean(ot_accesslist *accesslist) {
93 while (accesslist && accesslist->next) {
94 if (accesslist->next->base + 5 < time_now_minutes())
95 accesslist->next = accesslist_free(accesslist->next);
96 accesslist = accesslist->next;
97 }
98}
99
100/* A hash needs forty bytes, and all but a final one are followed by at least
101 one separator, so n hashes need at least 41n - 1 bytes. (len + 1) / 41 is
102 therefore an exact upper bound on what a buffer of len bytes can hold. */
103size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len) {
104 const char *end = buf + len;
105 const char *offs = buf;
106 size_t count = 0;
107
108 while (count < capacity && end - offs >= 40) {
109 if (hex_to_hash(dest[count], offs)) {
110 offs += 40;
111
112 if (offs == end || !hex_isdigit((unsigned char)*offs))
113 ++count;
114 }
115
116 /* Find start of next line */
117 while (offs < end && *(offs++) != '\n') {}
118 }
119
120 return count;
121}
122
123/* Map a file read only. Returns 0 and sets map and maplen on success, -1 if
124 the file cannot be opened or mapped. */
125static int accesslist_map(const char *filename, const char **map, size_t *maplen) {
126 struct stat st;
127 void *mapped;
128 int fd = open(filename, O_RDONLY);
129
130 if (fd < 0)
131 return -1;
132
133 if (fstat(fd, &st) < 0 || st.st_size < 0 || (uintmax_t)st.st_size > (uintmax_t)SIZE_MAX) {
134 close(fd);
135 return -1;
136 }
137
138 if (st.st_size == 0) {
139 close(fd);
140 *map = NULL;
141 *maplen = 0;
142 return 0;
143 }
144
145 mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0);
146 close(fd);
147
148 if (mapped == MAP_FAILED)
149 return -1;
150
151 *map = mapped;
152 *maplen = (size_t)st.st_size;
153 return 0;
154}
155
156static void accesslist_readfile(void) {
157 ot_accesslist *accesslist_new;
158 const char *map;
159 size_t maplen, capacity, found;
160
161 if (!g_accesslist_filename)
162 return;
163
164 if (accesslist_map(g_accesslist_filename, &map, &maplen) < 0) {
165 char *wd = getcwd(NULL, 0);
166 fprintf(stderr, "Warning: Can't open accesslist file: %s (but will try to create it later, if necessary and possible).\nPWD: %s\n", g_accesslist_filename, wd);
167 free(wd);
168 return;
169 }
170
171 /* Make room for the most info_hashes this file could possibly hold */
172 capacity = (maplen + 1) / 41;
173 accesslist_new = accesslist_make(g_accesslist, capacity);
174 if (!accesslist_new) {
175 fprintf(stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", capacity * sizeof(ot_hash));
176 if (map)
177 munmap((void *)map, maplen);
178 return;
179 }
180
181 found = maplen ? accesslist_parse_hashes(accesslist_new->list, capacity, map, maplen) : 0;
182#ifdef _DEBUG
183 fprintf(stderr, "Added %zd info_hashes to accesslist\n", found);
184#endif
185
186 if (map)
187 munmap((void *)map, maplen);
188
189 if (found)
190 qsort(accesslist_new->list, found, sizeof(ot_hash), vector_compare_hash);
191 accesslist_new->size = found;
192
193 /* Now exchange the accesslist vector in the least race condition prone way */
194 pthread_mutex_lock(&g_accesslist_mutex);
195
196 /* Mark old accesslist as "needs to live another five minutes" */
197 if (g_accesslist)
198 g_accesslist->base = time_now_minutes();
199 accesslist_new->next = g_accesslist;
200 g_accesslist = accesslist_new; /* Only now set a new list */
201
202#ifdef WANT_DYNAMIC_ACCESSLIST
203 /* If we have dynamic accesslists, reloading a new one will always void the add/delete lists.
204 Insert empty ones at the list head */
205 if (g_accesslist_add && (accesslist_new = accesslist_make(g_accesslist_add, 0)) != NULL) {
206 g_accesslist_add->base = time_now_minutes();
207 g_accesslist_add = accesslist_new;
208 }
209 if (g_accesslist_delete && (accesslist_new = accesslist_make(g_accesslist_delete, 0)) != NULL) {
210 g_accesslist_delete->base = time_now_minutes();
211 g_accesslist_delete = accesslist_new;
212 }
213#endif
214
215 accesslist_clean(g_accesslist);
216
217 pthread_mutex_unlock(&g_accesslist_mutex);
218}
219
220int accesslist_hashisvalid(const ot_hash hash) {
221 /* Get working copy of current access list */
222 ot_accesslist *accesslist = g_accesslist;
223#ifdef WANT_DYNAMIC_ACCESSLIST
224 ot_accesslist *accesslist_add, *accesslist_delete;
225#endif
226 void *exactmatch = NULL;
227
228 if (accesslist && accesslist->size)
229 exactmatch = bsearch(hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
230
231#ifdef WANT_DYNAMIC_ACCESSLIST
232 /* If we had no match on the main list, scan the list of dynamically added hashes */
233 accesslist_add = g_accesslist_add;
234 if ((exactmatch == NULL) && accesslist_add && accesslist_add->size)
235 exactmatch = bsearch(hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
236
237 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */
238 accesslist_delete = g_accesslist_delete;
239 if ((exactmatch != NULL) && accesslist_delete && accesslist_delete->size && bsearch(hash, accesslist_delete->list, accesslist_delete->size, OT_HASH_COMPARE_SIZE, vector_compare_hash))
240 exactmatch = NULL;
241#endif
242
243#ifdef WANT_ACCESSLIST_BLACK
244 return exactmatch == NULL;
245#else
246 return exactmatch != NULL;
247#endif
248}
249
250static void *accesslist_worker(void *args) {
251 int sig;
252 sigset_t signal_mask;
253
254 sigemptyset(&signal_mask);
255 sigaddset(&signal_mask, SIGHUP);
256
257 (void)args;
258
259 while (1) {
260 if (!g_opentracker_running)
261 return NULL;
262
263 /* Initial attempt to read accesslist */
264 accesslist_readfile();
265
266 /* Wait for signals */
267 while (sigwait(&signal_mask, &sig) != 0 && sig != SIGHUP)
268 ;
269 }
270 return NULL;
271}
272
273#ifdef WANT_DYNAMIC_ACCESSLIST
274static pthread_t thread_adder_id, thread_deleter_id;
275static _Atomic int adder_left, deleter_left;
276static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) {
277 struct stat st;
278
279 if (!stat(fifoname, &st)) {
280 if (!S_ISFIFO(st.st_mode)) {
281 fprintf(stderr, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname);
282 return NULL;
283 }
284 } else {
285 int error = mkfifo(fifoname, 0755);
286 if (error && error != EEXIST) {
287 fprintf(stderr, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname, strerror(errno));
288 return NULL;
289 }
290 }
291
292 while (g_opentracker_running) {
293 FILE *fifo = fopen(fifoname, "r");
294 char *line = NULL;
295 size_t linecap = 0;
296 ssize_t linelen;
297
298 if (!fifo) {
299 /* Woken on purpose: the loop condition above decides what happens next */
300 if (errno == EINTR)
301 continue;
302 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno));
303 return NULL;
304 }
305
306 while ((linelen = getline(&line, &linecap, fifo)) > 0) {
307 ot_hash info_hash;
308
309 printf("Got line %*s", (int)linelen, line);
310 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*"
311 If there's not enough characters for an info_hash in the line, skip it. */
312 if (linelen < 41)
313 continue;
314
315 if (!hex_to_hash(info_hash, line))
316 continue;
317
318 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
319 other worker threads from doing the same */
320 pthread_mutex_lock(&g_accesslist_mutex);
321
322 /* If the info hash is in the removing_from list, create a new head without that entry */
323 if (*removing_from && (*removing_from)->list) {
324 ot_hash *exactmatch = bsearch(info_hash, (*removing_from)->list, (*removing_from)->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
325 if (exactmatch) {
326 ptrdiff_t off = exactmatch - (*removing_from)->list;
327 ot_accesslist *accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1);
328 if (accesslist_new) {
329 size_t after = (*removing_from)->size - off - 1;
330
331 /* Removing the only entry leaves a list with no room at all, and
332 memcpy may not be handed that even for nothing */
333 if (off)
334 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off);
335 if (after)
336 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, sizeof(ot_hash) * after);
337 (*removing_from)->base = time_now_minutes();
338 *removing_from = accesslist_new;
339 }
340 }
341 }
342
343 /* Simple case: there's no adding_to list yet, create one with one member */
344 if (!*adding_to) {
345 ot_accesslist *accesslist_new = accesslist_make(NULL, 1);
346 if (accesslist_new) {
347 memcpy(accesslist_new->list, info_hash, sizeof(ot_hash));
348 *adding_to = accesslist_new;
349 }
350 } else {
351 int exactmatch = 0;
352 ot_hash *insert_point = vector_binary_search(info_hash, (*adding_to)->list, (*adding_to)->size, sizeof(ot_hash), OT_HASH_COMPARE_SIZE, &exactmatch);
353
354 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
355 if (!exactmatch) {
356 ot_accesslist *accesslist_new = accesslist_make(*adding_to, (*adding_to)->size + 1);
357 ptrdiff_t off = insert_point - (*adding_to)->list;
358 if (accesslist_new) {
359 memcpy(accesslist_new->list, (*adding_to)->list, sizeof(ot_hash) * off);
360 memcpy(accesslist_new->list + off, info_hash, sizeof(info_hash));
361 memcpy(accesslist_new->list + off + 1, (*adding_to)->list + off, sizeof(ot_hash) * ((*adding_to)->size - off));
362 (*adding_to)->base = time_now_minutes();
363 *adding_to = accesslist_new;
364 }
365 }
366 }
367
368 pthread_mutex_unlock(&g_accesslist_mutex);
369 }
370
371 free(line);
372 fclose(fifo);
373 }
374 return NULL;
375}
376
377static void *accesslist_adder_worker(void *args) {
378 (void)args;
379 accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete);
380 atomic_store(&adder_left, 1);
381 return NULL;
382}
383static void *accesslist_deleter_worker(void *args) {
384 (void)args;
385 accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add);
386 atomic_store(&deleter_left, 1);
387 return NULL;
388}
389
390/* These sit in a blocking open or read on a FIFO, so they are woken the way
391 the other workers are, and the signal is repeated because it only
392 interrupts a call already under way. The pipe name is what says whether
393 the thread was ever started. */
394static void accesslist_stop_fifo_worker(pthread_t thread, _Atomic int *left, const char *pipe_name) {
395 if (!pipe_name)
396 return;
397
398 while (!atomic_load(left)) {
399 pthread_kill(thread, OT_SIGNAL_WAKE);
400 usleep(1000);
401 }
402 pthread_join(thread, NULL);
403}
404#endif
405
406static pthread_t thread_id;
407void accesslist_init(void) {
408 pthread_mutex_init(&g_accesslist_mutex, NULL);
409 pthread_create(&thread_id, NULL, accesslist_worker, NULL);
410#ifdef WANT_DYNAMIC_ACCESSLIST
411 if (g_accesslist_pipe_add)
412 pthread_create(&thread_adder_id, NULL, accesslist_adder_worker, NULL);
413 if (g_accesslist_pipe_delete)
414 pthread_create(&thread_deleter_id, NULL, accesslist_deleter_worker, NULL);
415#endif
416}
417
418void accesslist_deinit(void) {
419 /* Wake the worker and wait for it to notice that the tracker is stopping. */
420 pthread_kill(thread_id, SIGHUP);
421 pthread_join(thread_id, NULL);
422
423#ifdef WANT_DYNAMIC_ACCESSLIST
424 /* Before the lists they are reading are freed below */
425 accesslist_stop_fifo_worker(thread_adder_id, &adder_left, g_accesslist_pipe_add);
426 accesslist_stop_fifo_worker(thread_deleter_id, &deleter_left, g_accesslist_pipe_delete);
427#endif
428
429 pthread_mutex_lock(&g_accesslist_mutex);
430
431 g_accesslist = accesslist_free(g_accesslist);
432
433#ifdef WANT_DYNAMIC_ACCESSLIST
434 g_accesslist_add = accesslist_free(g_accesslist_add);
435 g_accesslist_delete = accesslist_free(g_accesslist_delete);
436#endif
437
438 pthread_mutex_unlock(&g_accesslist_mutex);
439 pthread_mutex_destroy(&g_accesslist_mutex);
440}
441
442void accesslist_cleanup(void) {
443 pthread_mutex_lock(&g_accesslist_mutex);
444
445 accesslist_clean(g_accesslist);
446#if WANT_DYNAMIC_ACCESSLIST
447 accesslist_clean(g_accesslist_add);
448 accesslist_clean(g_accesslist_delete);
449#endif
450
451 pthread_mutex_unlock(&g_accesslist_mutex);
452}
453#endif
454
455int accesslist_address_in_net(const ot_ip6 address, const ot_net *net) {
456 int bits = net->bits, checkbits = (0x7f00 >> (bits & 7));
457 int result = memcmp(address, &net->address, bits >> 3);
458 if (!result && (bits & 7))
459 result = (checkbits & address[bits >> 3]) - (checkbits & net->address[bits >> 3]);
460 return result == 0;
461}
462
463void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size) {
464 size_t i;
465 int exactmatch;
466
467 /* Caller must have a concept of ot_net in it's member */
468 if (member_size < sizeof(ot_net))
469 return 0;
470
471 /* Check each net in vector for overlap */
472 uint8_t *member = ((uint8_t *)vector->data);
473 for (i = 0; i < vector->size; ++i) {
474 if (accesslist_address_in_net(*(ot_ip6 *)member, net) || accesslist_address_in_net(net->address, (ot_net *)member))
475 return 0;
476 member += member_size;
477 }
478
479 member = vector_find_or_insert(vector, (void *)net, member_size, sizeof(ot_net), &exactmatch);
480 if (member) {
481 memcpy(member, net, sizeof(ot_net));
482 memcpy(member + sizeof(ot_net), value, member_size - sizeof(ot_net));
483 }
484
485 return member;
486}
487
488/* Takes a vector filled with { ot_net net, uint8_t[x] value };
489 Returns value associated with the net, or NULL if not found */
490void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size) {
491 int exactmatch;
492 /* This binary search will return a pointer to the first non-containing network... */
493 ot_net *net = vector_binary_search(address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch);
494 if (!net)
495 return NULL;
496 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
497 if (!exactmatch && ((void *)net > vector->data))
498 --net;
499 if (!accesslist_address_in_net(address, net))
500 return NULL;
501 return (void *)net;
502}
503
504#ifdef WANT_IP_FROM_PROXY
505typedef struct {
506 ot_net *proxy;
507 ot_vector networks;
508} ot_proxymap;
509
510static ot_vector g_proxies_list;
511static pthread_mutex_t g_proxies_list_mutex = PTHREAD_MUTEX_INITIALIZER;
512
513int proxylist_add_network(const ot_net *proxy, const ot_net *net) {
514 ot_proxymap *map;
515 int exactmatch, result = 1;
516 pthread_mutex_lock(&g_proxies_list_mutex);
517
518 /* If we have a direct hit, use and extend the vector there */
519 map = vector_binary_search(proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch);
520
521 if (!map || !exactmatch) {
522 /* else see, if we've got overlapping networks
523 and get a new empty vector if not */
524 ot_vector empty;
525 memset(&empty, 0, sizeof(ot_vector));
526 map = accesslist_set_value_for_net(proxy, &g_proxies_list, &empty, sizeof(ot_proxymap));
527 }
528
529 if (map && accesslist_set_value_for_net(net, &map->networks, NULL, sizeof(ot_net)))
530 result = 1;
531
532 pthread_mutex_unlock(&g_proxies_list_mutex);
533 return result;
534}
535
536int proxylist_check_proxy(const ot_ip6 proxy, const ot_ip6 address) {
537 int result = 0;
538 ot_proxymap *map;
539
540 pthread_mutex_lock(&g_proxies_list_mutex);
541
542 if ((map = accesslist_get_value_for_net(proxy, &g_proxies_list, sizeof(ot_proxymap))))
543 if (!address || accesslist_get_value_for_net(address, &map->networks, sizeof(ot_net)))
544 result = 1;
545
546 pthread_mutex_unlock(&g_proxies_list_mutex);
547 return result;
548}
549
550#endif
551
552static ot_net g_admin_nets[OT_ADMINIP_MAX];
553static ot_permissions g_admin_nets_permissions[OT_ADMINIP_MAX];
554static size_t g_admin_nets_count = 0;
555
556int accesslist_bless_net(ot_net *net, ot_permissions permissions) {
557 if (g_admin_nets_count >= OT_ADMINIP_MAX)
558 return -1;
559
560 memcpy(g_admin_nets + g_admin_nets_count, net, sizeof(ot_net));
561 g_admin_nets_permissions[g_admin_nets_count++] = permissions;
562
563#ifdef _DEBUG
564 {
565 char _debug[512];
566 int off = snprintf(_debug, sizeof(_debug), "Blessing ip net ");
567 off += ip_format(_debug + off, net->address);
568 if (net->bits < 128) {
569 _debug[off++] = '/';
570 if (ip_is_v4mapped(net->address))
571 off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits - 96);
572 else
573 off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits);
574 }
575
576 if (permissions & OT_PERMISSION_MAY_STAT)
577 off += snprintf(_debug + off, 512 - off, " may_fetch_stats");
578 if (permissions & OT_PERMISSION_MAY_LIVESYNC)
579 off += snprintf(_debug + off, 512 - off, " may_sync_live");
580 if (permissions & OT_PERMISSION_MAY_FULLSCRAPE)
581 off += snprintf(_debug + off, 512 - off, " may_fetch_fullscrapes");
582 if (permissions & OT_PERMISSION_MAY_PROXY)
583 off += snprintf(_debug + off, 512 - off, " may_proxy");
584 if (!permissions)
585 off += snprintf(_debug + off, sizeof(_debug) - off, " nothing");
586 _debug[off++] = '.';
587 _debug[off++] = '\n';
588 (void)write(2, _debug, off);
589 }
590#endif
591
592 return 0;
593}
594
595int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions) {
596 size_t i;
597 for (i = 0; i < g_admin_nets_count; ++i)
598 if (accesslist_address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[i] & permissions))
599 return 1;
600 return 0;
601}
diff --git a/src/ot_accesslist.h b/src/ot_accesslist.h
new file mode 100644
index 0000000..68c88dc
--- /dev/null
+++ b/src/ot_accesslist.h
@@ -0,0 +1,77 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_ACCESSLIST_H__
5#define OT_ACCESSLIST_H__
6
7#include "trackerlogic.h"
8
9#if defined(WANT_ACCESSLIST_BLACK) && defined(WANT_ACCESSLIST_WHITE)
10#error WANT_ACCESSLIST_BLACK and WANT_ACCESSLIST_WHITE are exclusive.
11#endif
12
13#if defined(WANT_ACCESSLIST_BLACK) || defined(WANT_ACCESSLIST_WHITE)
14#define WANT_ACCESSLIST
15/* Starts a thread that rereads the list when sent SIGHUP, and is stopped by
16 being sent one, so the caller blocks SIGHUP process wide beforehand */
17void accesslist_init(void);
18void accesslist_deinit(void);
19int accesslist_hashisvalid(const ot_hash hash);
20void accesslist_cleanup(void);
21
22/* Scan buf for up to capacity info_hashes in hexadecimal notation, one per
23 line, and return how many were written to dest */
24size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len);
25
26extern const char *g_accesslist_filename;
27#ifdef WANT_DYNAMIC_ACCESSLIST
28extern const char *g_accesslist_pipe_add;
29extern const char *g_accesslist_pipe_delete;
30#endif
31
32#else
33#ifdef WANT_DYNAMIC_ACCESSLIST
34#error WANT_DYNAMIC_ACCESSLIST needs either WANT_ACCESSLIST_BLACK or WANT_ACCESSLIST_WHITE
35#endif
36
37#define accesslist_init(accesslist_filename)
38#define accesslist_deinit()
39#define accesslist_hashisvalid(hash) 1
40#endif
41
42/* Test if an address is subset of an ot_net, return value is considered a bool */
43int accesslist_address_in_net(const ot_ip6 address, const ot_net *net);
44
45/* Store a value into a vector of struct { ot_net net, uint8_t[x] value } member;
46 returns NULL
47 if member_size is too small, or
48 if one of the nets inside the vector are a subnet of _net_, or
49 if _net_ is a subnet of one of the nets inside the vector, or
50 if the vector could not be resized
51 returns pointer to new member in vector for success
52 member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping
53*/
54void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size);
55
56/* Takes a vector filled with struct { ot_net net, uint8_t[x] value } member;
57 Returns pointer to _member_ associated with the net, or NULL if not found
58 member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping
59*/
60void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size);
61
62#ifdef WANT_IP_FROM_PROXY
63int proxylist_add_network(const ot_net *proxy, const ot_net *net);
64int proxylist_check_network(const ot_ip6 *proxy, const ot_ip6 address /* can be NULL to only check proxy */);
65#endif
66
67typedef enum {
68 OT_PERMISSION_MAY_FULLSCRAPE = 0x1,
69 OT_PERMISSION_MAY_STAT = 0x2,
70 OT_PERMISSION_MAY_LIVESYNC = 0x4,
71 OT_PERMISSION_MAY_PROXY = 0x8
72} ot_permissions;
73
74int accesslist_bless_net(ot_net *net, ot_permissions permissions);
75int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions);
76
77#endif
diff --git a/src/ot_batch.c b/src/ot_batch.c
new file mode 100644
index 0000000..e4dda0b
--- /dev/null
+++ b/src/ot_batch.c
@@ -0,0 +1,156 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <limits.h>
7#include <stdlib.h>
8#include <string.h>
9#include <sys/socket.h>
10#include <sys/uio.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_batch.h"
15
16#ifndef IOV_MAX
17#define IOV_MAX 1024
18#endif
19
20#define OT_BATCH_MIN_ENTRIES 16
21
22static int batch_room_for(ot_batch *batch, size_t wanted) {
23 size_t space = batch->space ? batch->space : OT_BATCH_MIN_ENTRIES;
24 struct iovec *grown;
25
26 if (batch->count + wanted <= batch->space)
27 return 0;
28
29 /* During a chunked transfer entries are appended while earlier ones are
30 still going out, so the front of the array frees up as we go. Slide the
31 rest down before asking for more. */
32 if (batch->first && batch->count - batch->first + wanted <= batch->space) {
33 memmove(batch->entries, batch->entries + batch->first, (batch->count - batch->first) * sizeof(struct iovec));
34 batch->count -= batch->first;
35 batch->first = 0;
36 return 0;
37 }
38
39 while (space < batch->count + wanted)
40 space *= 2;
41
42 if (!(grown = realloc(batch->entries, space * sizeof(struct iovec))))
43 return -1;
44
45 batch->entries = grown;
46 batch->space = space;
47 return 0;
48}
49
50int batch_add(ot_batch *batch, void *buf, size_t len) {
51 if (batch_room_for(batch, 1)) {
52 free(buf);
53 return -1;
54 }
55
56 batch->entries[batch->count].iov_base = buf;
57 batch->entries[batch->count].iov_len = len;
58 ++batch->count;
59 return 0;
60}
61
62int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count) {
63 if (batch_room_for(batch, count)) {
64 while (count--)
65 free(entries[count].iov_base);
66 return -1;
67 }
68
69 memcpy(batch->entries + batch->count, entries, count * sizeof(struct iovec));
70 batch->count += count;
71 return 0;
72}
73
74/* Drop everything the kernel accepted, freeing each buffer as it goes */
75static void batch_consume(ot_batch *batch, size_t written) {
76 while (written) {
77 struct iovec *entry = batch->entries + batch->first;
78 size_t left = entry->iov_len - batch->offset;
79
80 if (written < left) {
81 batch->offset += written;
82 return;
83 }
84
85 free(entry->iov_base);
86 written -= left;
87 batch->offset = 0;
88 ++batch->first;
89 }
90
91 /* Nothing is outstanding, so the array can start over from the front */
92 if (batch->first == batch->count)
93 batch->first = batch->count = 0;
94}
95
96/* A peer that has gone away must come back as an error rather than as a
97 signal that kills the tracker, so ask the kernel not to raise one where it
98 can be asked. macOS cannot, and relies on SIGPIPE being blocked. */
99static ssize_t batch_writev(int s, struct iovec *entries, int count) {
100#ifdef MSG_NOSIGNAL
101 struct msghdr msg;
102
103 memset(&msg, 0, sizeof(msg));
104 msg.msg_iov = entries;
105 msg.msg_iovlen = count;
106 return sendmsg(s, &msg, MSG_NOSIGNAL);
107#else
108 return writev(s, entries, count);
109#endif
110}
111
112ssize_t batch_send(int s, ot_batch *batch) {
113 size_t entries = batch->count - batch->first;
114 ssize_t written;
115
116 if (!batch_pending(batch))
117 return BATCH_DONE;
118
119 if (entries > IOV_MAX)
120 entries = IOV_MAX;
121
122 /* The first entry may be half way out already */
123 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base + batch->offset;
124 batch->entries[batch->first].iov_len -= batch->offset;
125 written = batch_writev(s, batch->entries + batch->first, (int)entries);
126 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base - batch->offset;
127 batch->entries[batch->first].iov_len += batch->offset;
128
129 if (written < 0)
130 return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ? BATCH_AGAIN : BATCH_FAILED;
131 if (!written)
132 return BATCH_FAILED;
133
134 batch_consume(batch, (size_t)written);
135 return written;
136}
137
138void batch_reset(ot_batch *batch) {
139 while (batch->first < batch->count)
140 free(batch->entries[batch->first++].iov_base);
141
142 free(batch->entries);
143 batch->entries = NULL;
144 batch->count = batch->first = batch->offset = batch->space = 0;
145}
146
147/* For the few constants the chunked encoding needs, which the batch frees
148 along with everything else */
149int batch_add_copy(ot_batch *batch, const void *buf, size_t len) {
150 void *copy = malloc(len);
151
152 if (!copy)
153 return -1;
154 memcpy(copy, buf, len);
155 return batch_add(batch, copy, len);
156}
diff --git a/src/ot_batch.h b/src/ot_batch.h
new file mode 100644
index 0000000..910bea7
--- /dev/null
+++ b/src/ot_batch.h
@@ -0,0 +1,46 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_BATCH_H__
5#define OT_BATCH_H__
6
7#include <stddef.h>
8#include <sys/types.h>
9#include <sys/uio.h>
10
11/* A queue of buffers on their way to one socket. The batch owns every buffer
12 handed to it and frees each one as soon as it has left, so a client that
13 reads slowly costs memory only for what it has not received yet.
14
15 A zeroed ot_batch is ready to use. */
16typedef struct {
17 struct iovec *entries;
18 size_t space;
19 size_t count;
20 size_t first; /* entries before this one are sent and freed */
21 size_t offset; /* bytes of entries[first] already sent */
22} ot_batch;
23
24static inline int batch_pending(const ot_batch *batch) { return batch->first != batch->count; }
25
26/* What batch_send reports instead of a byte count */
27enum { BATCH_DONE = 0, BATCH_AGAIN = -1, BATCH_FAILED = -2 };
28
29/* Queue buf and take ownership of it. Returns 0, or -1 having freed buf. */
30int batch_add(ot_batch *batch, void *buf, size_t len);
31
32/* Write as much as the socket takes. Returns the number of bytes written, or
33 BATCH_DONE, BATCH_AGAIN or BATCH_FAILED. */
34ssize_t batch_send(int s, ot_batch *batch);
35
36/* Queue every entry of a vector, taking ownership of each buffer but not of
37 the vector itself. Returns 0, or -1 having freed all of them. */
38int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count);
39
40/* Queue a copy of buf, which the batch then owns. Returns 0, or -1. */
41int batch_add_copy(ot_batch *batch, const void *buf, size_t len);
42
43/* Free whatever has not left yet, along with the batch's own array */
44void batch_reset(ot_batch *batch);
45
46#endif
diff --git a/src/ot_buf.c b/src/ot_buf.c
new file mode 100644
index 0000000..ecd3f8d
--- /dev/null
+++ b/src/ot_buf.c
@@ -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/* System */
5#include <stdlib.h>
6#include <string.h>
7
8/* Opentracker */
9#include "ot_buf.h"
10
11int buf_cat(ot_buf *buf, const void *src, size_t len) {
12 char *data;
13
14 if (!len)
15 return 0;
16
17 if (buf->size + len < buf->size)
18 return -1;
19
20 if (!(data = realloc(buf->data, buf->size + len)))
21 return -1;
22
23 buf->data = data;
24 memcpy(data + buf->size, src, len);
25 buf->size += len;
26
27 return 0;
28}
29
30void buf_truncate(ot_buf *buf, size_t len) {
31 if (len < buf->size)
32 buf->size = len;
33}
34
35void buf_reset(ot_buf *buf) {
36 free(buf->data);
37 buf->data = NULL;
38 buf->size = 0;
39}
diff --git a/src/ot_buf.h b/src/ot_buf.h
new file mode 100644
index 0000000..7fb944e
--- /dev/null
+++ b/src/ot_buf.h
@@ -0,0 +1,25 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_BUF_H__
5#define OT_BUF_H__
6
7#include <stddef.h>
8
9/* A growable byte buffer, holding an HTTP request that did not arrive in a
10 single packet. Requests that arrive whole are served straight from the
11 worker's input buffer and never come near this. */
12typedef struct {
13 char *data;
14 size_t size;
15} ot_buf;
16
17/* Append len bytes from src. Returns 0, or -1 if the memory could not be
18 had, in which case the buffer keeps whatever it already held. */
19int buf_cat(ot_buf *buf, const void *src, size_t len);
20
21void buf_truncate(ot_buf *buf, size_t len);
22
23void buf_reset(ot_buf *buf);
24
25#endif
diff --git a/src/ot_clean.c b/src/ot_clean.c
new file mode 100644
index 0000000..f181e83
--- /dev/null
+++ b/src/ot_clean.c
@@ -0,0 +1,176 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <pthread.h>
7#include <string.h>
8#include <unistd.h>
9
10/* Opentracker */
11#include "ot_accesslist.h"
12#include "ot_clean.h"
13#include "ot_mutex.h"
14#include "ot_stats.h"
15#include "ot_vector.h"
16#include "trackerlogic.h"
17
18/* Returns amount of removed peers */
19static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t peer_size, time_t timedout, size_t *removed_seeders) {
20 ot_peer *last_peer = peers + peer_count * peer_size, *insert_point;
21
22 /* Two scan modes: unless there is one peer removed, just increase ot_peertime */
23 while (peers < last_peer) {
24 time_t timediff = timedout + OT_PEERTIME(peers, peer_size);
25 if (timediff >= OT_PEER_TIMEOUT)
26 break;
27 OT_PEERTIME(peers, peer_size) = timediff;
28 peers += peer_size;
29 }
30
31 /* If we at least remove one peer, we have to copy */
32 for (insert_point = peers; peers < last_peer; peers += peer_size) {
33 time_t timediff = timedout + OT_PEERTIME(peers, peer_size);
34
35 if (timediff < OT_PEER_TIMEOUT) {
36 OT_PEERTIME(peers, peer_size) = timediff;
37 memcpy(insert_point, peers, peer_size);
38 insert_point += peer_size;
39 } else if (OT_PEERFLAG_D(peers, peer_size) & PEER_FLAG_SEEDING)
40 (*removed_seeders)++;
41 }
42
43 return (peers - insert_point) / peer_size;
44}
45
46int clean_single_peer_list(ot_peerlist *peer_list, size_t peer_size) {
47 ot_vector *peer_vector = &peer_list->peers;
48 time_t timedout = (time_t)(time_now_minutes() - peer_list->base);
49 size_t num_buckets = 1, removed_seeders = 0;
50
51 /* No need to clean empty torrent */
52 if (!timedout)
53 return 0;
54
55 /* Torrent has idled out */
56 if (timedout > OT_TORRENT_TIMEOUT)
57 return 1;
58
59 /* Nothing to be cleaned here? Test if torrent is worth keeping */
60 if (timedout > OT_PEER_TIMEOUT) {
61 if (!peer_list->peer_count)
62 return peer_list->down_count ? 0 : 1;
63 timedout = OT_PEER_TIMEOUT;
64 }
65
66 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
67 num_buckets = peer_vector->size;
68 peer_vector = (ot_vector *)peer_vector->data;
69 }
70
71 while (num_buckets--) {
72 size_t removed_peers = clean_single_bucket(peer_vector->data, peer_vector->size, peer_size, timedout, &removed_seeders);
73 peer_list->peer_count -= removed_peers;
74 peer_vector->size -= removed_peers;
75 if (removed_peers)
76 vector_fixup_peers(peer_vector, peer_size);
77
78 /* Skip to next bucket, a vector containing peers */
79 ++peer_vector;
80 }
81
82 peer_list->seed_count -= removed_seeders;
83
84 /* See if we need to convert a torrent from simple vector to bucket list */
85 if ((peer_list->peer_count > OT_PEER_BUCKET_MINCOUNT) || OT_PEERLIST_HASBUCKETS(peer_list))
86 vector_redistribute_buckets(peer_list, peer_size);
87
88 if (peer_list->peer_count)
89 peer_list->base = time_now_minutes();
90 else {
91 /* When we got here, the last time that torrent
92 has been touched is OT_PEER_TIMEOUT Minutes before */
93 peer_list->base = time_now_minutes() - OT_PEER_TIMEOUT;
94 }
95 return 0;
96}
97
98/* Clean a single torrent
99 return 1 if torrent timed out
100*/
101int clean_single_torrent(ot_torrent *torrent) {
102 return clean_single_peer_list(torrent->peer_list6, OT_PEER_SIZE6) * clean_single_peer_list(torrent->peer_list4, OT_PEER_SIZE4);
103}
104
105static pthread_mutex_t g_clean_mutex = PTHREAD_MUTEX_INITIALIZER;
106static pthread_cond_t g_clean_wakeup = PTHREAD_COND_INITIALIZER;
107
108static void clean_sleep(void) {
109 struct timespec deadline;
110
111 clock_gettime(CLOCK_REALTIME, &deadline);
112 deadline.tv_sec += OT_CLEAN_SLEEP / 1000000;
113 deadline.tv_nsec += (OT_CLEAN_SLEEP % 1000000) * 1000;
114 if (deadline.tv_nsec >= 1000000000) {
115 ++deadline.tv_sec;
116 deadline.tv_nsec -= 1000000000;
117 }
118
119 /* Both sides hold the mutex, so the flag cannot be cleared and signalled
120 in the window between testing it and waiting on it */
121 pthread_mutex_lock(&g_clean_mutex);
122 while (g_opentracker_running)
123 if (pthread_cond_timedwait(&g_clean_wakeup, &g_clean_mutex, &deadline) == ETIMEDOUT)
124 break;
125 pthread_mutex_unlock(&g_clean_mutex);
126}
127
128/* Clean up all peers in one bucket, remove timedout pools and torrents */
129static void clean_bucket(size_t bucket) {
130 ot_vector *torrents_list = mutex_bucket_lock((int)bucket);
131 size_t toffs;
132 int delta_torrentcount = 0;
133
134 for (toffs = 0; toffs < torrents_list->size; ++toffs) {
135 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + toffs;
136 if (clean_single_torrent(torrent)) {
137 vector_remove_torrent(torrents_list, torrent);
138 --delta_torrentcount;
139 --toffs;
140 }
141 }
142 mutex_bucket_unlock((int)bucket, delta_torrentcount);
143}
144
145static void *clean_worker(void *args) {
146 (void)args;
147 while (1) {
148 size_t bucket = OT_BUCKET_COUNT;
149 while (bucket) {
150 size_t todo = bucket < OT_CLEAN_BUCKETS_PER_WAKE ? bucket : OT_CLEAN_BUCKETS_PER_WAKE;
151
152 /* One bucket is locked at a time, however many a wake gets through */
153 while (todo--)
154 clean_bucket(--bucket);
155
156 if (!g_opentracker_running)
157 return NULL;
158 clean_sleep();
159 }
160 stats_cleanup();
161#ifdef WANT_ACCESSLIST
162 accesslist_cleanup();
163#endif
164 }
165 return NULL;
166}
167
168static pthread_t thread_id;
169void clean_init(void) { pthread_create(&thread_id, NULL, clean_worker, NULL); }
170
171void clean_deinit(void) {
172 pthread_mutex_lock(&g_clean_mutex);
173 pthread_cond_signal(&g_clean_wakeup);
174 pthread_mutex_unlock(&g_clean_mutex);
175 pthread_join(thread_id, NULL);
176}
diff --git a/src/ot_clean.h b/src/ot_clean.h
new file mode 100644
index 0000000..0592791
--- /dev/null
+++ b/src/ot_clean.h
@@ -0,0 +1,28 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_CLEAN_H__
5#define OT_CLEAN_H__
6
7#include "trackerlogic.h"
8
9/* The amount of time a clean cycle should take */
10#define OT_CLEAN_INTERVAL_MINUTES 2
11
12/* How often the cleaner may wake. A full sweep takes OT_CLEAN_INTERVAL_MINUTES.
13 The clean operations per wake is goverend by OT_BUCKET_COUNT_BITS,
14 set by the Makefile.
15 At the default of 1024 buckets this works out at one bucket every
16 117 ms. */
17#define OT_CLEAN_MAX_WAKES_PER_SECOND 10
18
19#define OT_CLEAN_WAKE_BUDGET ((OT_CLEAN_INTERVAL_MINUTES) * 60 * (OT_CLEAN_MAX_WAKES_PER_SECOND))
20#define OT_CLEAN_BUCKETS_PER_WAKE (((OT_BUCKET_COUNT) + (OT_CLEAN_WAKE_BUDGET) - 1) / (OT_CLEAN_WAKE_BUDGET))
21#define OT_CLEAN_WAKES ((OT_BUCKET_COUNT) / (OT_CLEAN_BUCKETS_PER_WAKE))
22#define OT_CLEAN_SLEEP (((OT_CLEAN_INTERVAL_MINUTES) * 60 * 1000000) / (OT_CLEAN_WAKES))
23
24void clean_init(void);
25void clean_deinit(void);
26int clean_single_torrent(ot_torrent *torrent);
27
28#endif
diff --git a/src/ot_config.c b/src/ot_config.c
new file mode 100644
index 0000000..95136b5
--- /dev/null
+++ b/src/ot_config.c
@@ -0,0 +1,586 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <ctype.h>
6#include <errno.h>
7#include <limits.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_accesslist.h"
15#include "ot_event.h"
16#include "ot_config.h"
17#include "ot_http.h"
18#include "ot_ip.h"
19#include "ot_livesync.h"
20#include "ot_scan.h"
21#include "ot_stats.h"
22#include "trackerlogic.h"
23
24const char *g_redirecturl;
25const char *g_serverdir;
26const char *g_serveruser;
27const char *g_statefile;
28
29#define OT_LISTEN_INHERIT (-1)
30
31static ot_listen *g_listen;
32static size_t g_listen_count;
33static int g_listen_shared;
34static int g_listen_workers_udp = 1;
35static int g_listen_workers_tcp = 0;
36
37static void panic(const char *routine) __attribute__((noreturn));
38static void panic(const char *routine) {
39 fprintf(stderr, "%s: %s\n", routine, strerror(errno));
40 exit(111);
41}
42
43static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) {
44 ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen));
45 ot_listen *entry;
46
47 if (!grown)
48 panic("listen_append");
49 g_listen = grown;
50
51 entry = g_listen + g_listen_count++;
52 memcpy(entry->ip, ip, sizeof(ot_ip6));
53 entry->port = port;
54 entry->proto = proto;
55 entry->shared = shared;
56 entry->workers = workers;
57 entry->sock = -1;
58}
59
60typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind;
61
62typedef struct {
63 const char *name;
64 size_t len;
65 listen_option_kind kind;
66 int takes_value;
67} listen_option_key;
68
69#define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v}
70
71static const listen_option_key listen_option_keys[] = {
72 LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0),
73 LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1),
74};
75static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]);
76
77static listen_option_kind parse_listen_option(char **p) {
78 size_t i;
79
80 for (i = 0; i < listen_option_count; ++i) {
81 const listen_option_key *key = &listen_option_keys[i];
82 char delimiter = (*p)[key->len];
83
84 if (strncmp(*p, key->name, key->len))
85 continue;
86 if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter)))
87 continue;
88
89 *p += key->len + (key->takes_value ? 1 : 0);
90 return key->kind;
91 }
92 return LISTEN_OPTION_UNKNOWN;
93}
94
95/* Options trailing an address in a listen statement, in any order */
96static int listen_scan_options(char *p, int *shared, int *workers) {
97 uint64_t value;
98
99 for (;;) {
100 while (isspace((unsigned char)*p))
101 ++p;
102 if (!*p)
103 return 1;
104
105 switch (parse_listen_option(&p)) {
106 case LISTEN_OPTION_SHARED:
107 *shared = 1;
108 break;
109 case LISTEN_OPTION_WORKERS: {
110 size_t off = scan_uint(p, &value, UINT_MAX);
111
112 if (!off)
113 return 0;
114 *workers = (int)value;
115 p += off;
116 break;
117 }
118 default:
119 return 0;
120 }
121 }
122}
123
124
125static const char *set_config_option(const char **option, char *value) {
126#ifdef _DEBUG
127 fprintf(stderr, "Setting config option: %s\n", value);
128#endif
129 while (isspace(*value))
130 ++value;
131 free((char *)*option);
132 return *option = strdup(value);
133}
134
135static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
136 const char *s = src;
137 uint64_t value;
138 int off, bracket = 0;
139 while (isspace(*s))
140 ++s;
141 if (*s == '[')
142 ++s, ++bracket; /* for v6 style notation */
143 if (!(off = ip_parse(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_uint(s, &value, 65535)))
161 return 0;
162 *port = (uint16_t)value;
163 return off + s - src;
164}
165
166static int scan_ip6_net(const char *src, ot_net *net) {
167 const char *s = src;
168 uint64_t value;
169 int off;
170 while (isspace(*s))
171 ++s;
172 if (!(off = ip_parse(s, net->address)))
173 return 0;
174 s += off;
175 if (*s != '/')
176 net->bits = 128;
177 else {
178 s++;
179 if (!(off = scan_uint(s, &value, 128)))
180 return 0;
181 net->bits = (int)value;
182 if (ip_is_v4mapped(net->address))
183 net->bits += 96;
184 if (net->bits > 128)
185 return 0;
186 s += off;
187 }
188 return off + s - src;
189}
190
191typedef enum {
192 CONFIG_UNKNOWN,
193 CONFIG_TRACKER_ROOTDIR,
194 CONFIG_TRACKER_USER,
195 CONFIG_LISTEN_TCP_UDP,
196 CONFIG_LISTEN_TCP,
197 CONFIG_LISTEN_UDP,
198 CONFIG_LISTEN_UDP_WORKERS,
199 CONFIG_LISTEN_TCP_WORKERS,
200 CONFIG_LISTEN_SHARED,
201 CONFIG_ACCESS_WHITELIST,
202 CONFIG_ACCESS_BLACKLIST,
203 CONFIG_ACCESS_FIFO_ADD,
204 CONFIG_ACCESS_FIFO_DELETE,
205 CONFIG_ACCESS_STATS,
206 CONFIG_ACCESS_STATS_PATH,
207 CONFIG_ACCESS_PROXY,
208 CONFIG_TRACKER_REDIRECT_URL,
209 CONFIG_LIVESYNC_CLUSTER_NODE_IP,
210 CONFIG_LIVESYNC_CLUSTER_LISTEN,
211} config_kind;
212
213typedef struct {
214 const char *name;
215 size_t len;
216 config_kind kind;
217} config_key;
218
219#define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k }
220
221static const config_key config_keys[] = {
222 CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR),
223 CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER),
224 CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP),
225 CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP),
226 CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP),
227 CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS),
228 CONFIG_KEY("listen.tcp.workers", CONFIG_LISTEN_TCP_WORKERS),
229 CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED),
230#ifdef WANT_ACCESSLIST_WHITE
231 CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST),
232#elif defined(WANT_ACCESSLIST_BLACK)
233 CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST),
234#endif
235#ifdef WANT_DYNAMIC_ACCESSLIST
236 CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD),
237 CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE),
238#endif
239#ifdef WANT_RESTRICT_STATS
240 CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS),
241#endif
242 CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH),
243#ifdef WANT_IP_FROM_PROXY
244 CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY),
245#endif
246 CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL),
247#ifdef WANT_SYNC_LIVE
248 CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP),
249 CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN),
250#endif
251};
252static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]);
253
254static config_kind parse_config_key(char **p) {
255 size_t i;
256 for (i = 0; i < config_entry_count; ++i) {
257 const config_key *key = &config_keys[i];
258 if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) {
259 *p += key->len + 1;
260 /* Skip white spaces */
261 while (isspace((unsigned char)**p))
262 ++(*p);
263 return key->kind;
264 }
265 }
266 return CONFIG_UNKNOWN;
267}
268
269static int parse_configfile(char *config_filename) {
270 FILE *config_file_handle;
271 char inbuf[512];
272 ot_ip6 tmpip;
273#if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE)
274 ot_net tmpnet;
275#endif
276 int bound = 0;
277
278 config_file_handle = fopen(config_filename, "r");
279
280 if (config_file_handle == NULL) {
281 fprintf(stderr, "Warning: Can't open config file: %s.", config_filename);
282 return 0;
283 }
284
285 while (fgets(inbuf, sizeof(inbuf), config_file_handle)) {
286 char *p = inbuf, *end;
287 uint64_t value;
288 uint16_t tmpport = 6969;
289 config_kind kind;
290
291 /* Skip white spaces */
292 while (isspace((unsigned char)*p))
293 ++p;
294
295 /* Ignore comments and empty lines */
296 if ((*p == '#') || (*p == '\n') || (*p == 0))
297 continue;
298
299 /* consume trailing new lines and spaces */
300 end = p + strlen(p);
301 while (end > p && isspace((unsigned char)end[-1]))
302 *--end = 0;
303
304 /* Scan for commands */
305 kind = parse_config_key(&p);
306 switch (kind) {
307 case CONFIG_TRACKER_ROOTDIR:
308 set_config_option(&g_serverdir, p);
309 break;
310 case CONFIG_TRACKER_USER:
311 set_config_option(&g_serveruser, p);
312 break;
313 case CONFIG_LISTEN_TCP_UDP:
314 case CONFIG_LISTEN_TCP:
315 case CONFIG_LISTEN_UDP: {
316 int shared = OT_LISTEN_INHERIT;
317 int workers = OT_LISTEN_INHERIT;
318 int off = scan_ip6_port(p, tmpip, &tmpport);
319
320 if (!off || !listen_scan_options(p + off, &shared, &workers))
321 goto parse_error;
322 if (kind != CONFIG_LISTEN_UDP)
323 listen_append(tmpip, tmpport, FLAG_TCP, shared, workers), ++bound;
324 if (kind != CONFIG_LISTEN_TCP)
325 listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound;
326 break;
327 }
328 case CONFIG_LISTEN_UDP_WORKERS:
329 if (!scan_uint(p, &value, UINT_MAX))
330 goto parse_error;
331 g_listen_workers_udp = (int)value;
332 break;
333 case CONFIG_LISTEN_TCP_WORKERS:
334 if (!scan_uint(p, &value, UINT_MAX))
335 goto parse_error;
336 g_listen_workers_tcp = (int)value;
337 break;
338 case CONFIG_LISTEN_SHARED:
339 if (!scan_uint(p, &value, 1))
340 goto parse_error;
341 g_listen_shared = (int)value;
342 break;
343#ifdef WANT_ACCESSLIST_WHITE
344 case CONFIG_ACCESS_WHITELIST:
345 set_config_option(&g_accesslist_filename, p);
346 break;
347#elif defined(WANT_ACCESSLIST_BLACK)
348 case CONFIG_ACCESS_BLACKLIST:
349 set_config_option(&g_accesslist_filename, p);
350 break;
351#endif
352#ifdef WANT_DYNAMIC_ACCESSLIST
353 case CONFIG_ACCESS_FIFO_ADD:
354 set_config_option(&g_accesslist_pipe_add, p);
355 break;
356 case CONFIG_ACCESS_FIFO_DELETE:
357 set_config_option(&g_accesslist_pipe_delete, p);
358 break;
359#endif
360#ifdef WANT_RESTRICT_STATS
361 case CONFIG_ACCESS_STATS:
362 if (!scan_ip6_net(p, &tmpnet))
363 goto parse_error;
364 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT);
365 break;
366#endif
367 case CONFIG_ACCESS_STATS_PATH:
368 set_config_option(&g_stats_path, p);
369 break;
370#ifdef WANT_IP_FROM_PROXY
371 case CONFIG_ACCESS_PROXY:
372 if (!scan_ip6_net(p, &tmpnet))
373 goto parse_error;
374 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY);
375 break;
376#endif
377 case CONFIG_TRACKER_REDIRECT_URL:
378 set_config_option(&g_redirecturl, p);
379 break;
380#ifdef WANT_SYNC_LIVE
381 case CONFIG_LIVESYNC_CLUSTER_NODE_IP:
382 if (!scan_ip6_net(p, &tmpnet))
383 goto parse_error;
384 accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC);
385 break;
386 case CONFIG_LIVESYNC_CLUSTER_LISTEN:
387 tmpport = LIVESYNC_PORT;
388 {
389 int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT;
390 int off = scan_ip6_port(p, tmpip, &tmpport);
391
392 if (!off || !listen_scan_options(p + off, &shared, &dummy))
393 goto parse_error;
394 listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT);
395 }
396 break;
397#endif
398 default:
399 fprintf(stderr, "Unhandled line in config file: %s\n", inbuf);
400 break;
401 parse_error:
402 fprintf(stderr, "Parse error in config file: %s\n", inbuf);
403 }
404 }
405 fclose(config_file_handle);
406 return bound;
407}
408
409static void usage(char *name) {
410 fprintf(stderr,
411 "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]"
412#ifdef WANT_ACCESSLIST_BLACK
413 " [-b blacklistfile]"
414#elif defined(WANT_ACCESSLIST_WHITE)
415 " [-w whitelistfile]"
416#endif
417 "\n",
418 name);
419}
420
421#define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc)
422static void help(char *name) {
423 usage(name);
424
425 HELPLINE("-f config", "include and execute the config file");
426 HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)");
427 HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)");
428 HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)");
429 HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)");
430 HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")");
431 HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")");
432 HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)");
433 HELPLINE("-v", "report version, event mechanism and the feature flags opentracker was built with");
434#ifdef WANT_ACCESSLIST_BLACK
435 HELPLINE("-b file", "specify blacklist file.");
436#elif defined(WANT_ACCESSLIST_WHITE)
437 HELPLINE("-w file", "specify whitelist file.");
438#endif
439
440 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");
441 fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n");
442 fprintf(stderr, " If no port is bound from config file or command line, the last address given\n");
443 fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n");
444}
445#undef HELPLINE
446
447
448static void listen_resolve_defaults(void) {
449 size_t i;
450
451 for (i = 0; i < g_listen_count; ++i) {
452 ot_listen *entry = g_listen + i;
453
454 if (entry->shared == OT_LISTEN_INHERIT)
455 entry->shared = g_listen_shared;
456 if (entry->workers == OT_LISTEN_INHERIT)
457 entry->workers = entry->proto == FLAG_TCP ? g_listen_workers_tcp : g_listen_workers_udp;
458
459 /* A tcp worker hands a connection it cannot finish to the event loop,
460 which only one thread may touch where the set is ours rather than the
461 kernel's */
462 if (entry->proto == FLAG_TCP && entry->workers > 1 && !event_can_share()) {
463 fprintf(stderr, "Warning: Multiple workers impossible on this platform with event mechanism %s chosen.\n",
464 event_mechanism());
465 entry->workers = 1;
466 }
467 }
468}
469
470ot_listen *config_listen_entries(size_t *count) {
471 *count = g_listen_count;
472 return g_listen;
473}
474
475void config_parse(int argc, char **argv) {
476 ot_ip6 serverip;
477 ot_net tmpnet;
478 int bound = 0, scanon = 1;
479 uint64_t value;
480
481 memset(serverip, 0, sizeof(ot_ip6));
482#ifdef WANT_V4_ONLY
483 serverip[10] = serverip[11] = -1;
484#endif
485
486 while (scanon) {
487 switch (getopt(argc, argv,
488 ":i:p:A:P:d:u:r:s:f:l:v"
489#ifdef WANT_ACCESSLIST_BLACK
490 "b:"
491#elif defined(WANT_ACCESSLIST_WHITE)
492 "w:"
493#endif
494 "h")) {
495 case -1:
496 scanon = 0;
497 break;
498 case 'i':
499 if (!ip_parse(optarg, serverip)) {
500 usage(argv[0]);
501 exit(1);
502 }
503 break;
504#ifdef WANT_ACCESSLIST_BLACK
505 case 'b':
506 set_config_option(&g_accesslist_filename, optarg);
507 break;
508#elif defined(WANT_ACCESSLIST_WHITE)
509 case 'w':
510 set_config_option(&g_accesslist_filename, optarg);
511 break;
512#endif
513 case 'p':
514 if (!scan_uint(optarg, &value, 65535)) {
515 usage(argv[0]);
516 exit(1);
517 }
518 listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
519 bound++;
520 break;
521 case 'P':
522 if (!scan_uint(optarg, &value, 65535)) {
523 usage(argv[0]);
524 exit(1);
525 }
526 listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
527 bound++;
528 break;
529#ifdef WANT_SYNC_LIVE
530 case 's':
531 if (!scan_uint(optarg, &value, 65535)) {
532 usage(argv[0]);
533 exit(1);
534 }
535 listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
536 break;
537#endif
538 case 'd':
539 set_config_option(&g_serverdir, optarg);
540 break;
541 case 'u':
542 set_config_option(&g_serveruser, optarg);
543 break;
544 case 'r':
545 set_config_option(&g_redirecturl, optarg);
546 break;
547 case 'l':
548 g_statefile = optarg;
549 break;
550 case 'A':
551 if (!scan_ip6_net(optarg, &tmpnet)) {
552 usage(argv[0]);
553 exit(1);
554 }
555 accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */
556 break;
557 case 'f':
558 bound += parse_configfile(optarg);
559 break;
560 case 'h':
561 help(argv[0]);
562 exit(0);
563 case 'v': {
564 char buffer[8192];
565 char *cursor = buffer;
566
567 stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1);
568 stats_return_tracker_features(&cursor, buffer + sizeof(buffer) - 1);
569 *cursor = 0;
570 fputs(buffer, stderr);
571 exit(0);
572 }
573 default:
574 case '?':
575 usage(argv[0]);
576 exit(1);
577 }
578 }
579
580 if (!bound) {
581 listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
582 listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT);
583 }
584
585 listen_resolve_defaults();
586}
diff --git a/src/ot_config.h b/src/ot_config.h
new file mode 100644
index 0000000..79187ee
--- /dev/null
+++ b/src/ot_config.h
@@ -0,0 +1,33 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_CONFIG_H__
5#define OT_CONFIG_H__
6
7#include <stdint.h>
8
9#include "trackerlogic.h"
10
11/* An address opentracker was asked to listen on. Options the statement did
12 not name have been filled in from the file wide defaults by the time these
13 are handed out. sock is filled in by whoever binds them. */
14typedef struct {
15 ot_ip6 ip;
16 uint16_t port;
17 PROTO_FLAG proto;
18 int shared;
19 int workers;
20 int64_t sock;
21} ot_listen;
22
23extern const char *g_redirecturl;
24extern const char *g_serverdir;
25extern const char *g_serveruser;
26extern const char *g_statefile;
27
28/* Reads the command line and every config file it names. Exits on error. */
29void config_parse(int argc, char **argv);
30
31ot_listen *config_listen_entries(size_t *count);
32
33#endif
diff --git a/src/ot_event.c b/src/ot_event.c
new file mode 100644
index 0000000..6062f70
--- /dev/null
+++ b/src/ot_event.c
@@ -0,0 +1,357 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <stdlib.h>
7#include <string.h>
8#include <unistd.h>
9
10/* Opentracker */
11#include "ot_event.h"
12
13/* One caller waits, so the scratch the mechanism needs is kept here rather
14 than allocated per call. */
15
16/* poll works everywhere and is what the BSDs get until kqueue is written.
17 Defining OT_EVENT_FORCE_POLL picks it on a platform that would otherwise
18 use something better, which is how both are exercised by the tests. */
19#if defined(OT_EVENT_FORCE_POLL)
20#define OT_EVENT_POLL
21#elif defined(OT_EVENT_FORCE_KQUEUE)
22#define OT_EVENT_KQUEUE
23#elif defined(__linux__)
24#define OT_EVENT_EPOLL
25#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__)
26#define OT_EVENT_KQUEUE
27#else
28#define OT_EVENT_POLL
29#endif
30
31#ifdef OT_EVENT_EPOLL
32/* ------------------------------------------------------------- epoll */
33
34#include <sys/epoll.h>
35
36static int g_epoll = -1;
37static struct epoll_event *g_ready;
38static int g_ready_space;
39
40const char *event_mechanism(void) { return "epoll"; }
41/* The set lives in the kernel, and epoll_ctl during another thread's
42 epoll_wait is what it is for */
43int event_can_share(void) { return 1; }
44
45int event_init(void) {
46 g_ready = NULL;
47 g_ready_space = 0;
48 return (g_epoll = epoll_create1(EPOLL_CLOEXEC)) == -1 ? -1 : 0;
49}
50
51void event_deinit(void) {
52 if (g_epoll != -1)
53 close(g_epoll);
54 free(g_ready);
55 g_epoll = -1;
56 g_ready = NULL;
57 g_ready_space = 0;
58}
59
60static int event_ctl(int op, int fd, int what, uint32_t generation) {
61 struct epoll_event ev;
62
63 memset(&ev, 0, sizeof(ev));
64 ev.events = (what & EVENT_IN ? (uint32_t)EPOLLIN : 0) | (what & EVENT_OUT ? (uint32_t)EPOLLOUT : 0);
65 /* The generation rides along inside the kernel, so an event says which
66 connection it belongs to without anybody looking it up */
67 ev.data.u64 = ((uint64_t)generation << 32) | (uint32_t)fd;
68 return epoll_ctl(g_epoll, op, fd, &ev);
69}
70
71int event_add(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_ADD, fd, what, generation); }
72int event_modify(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_MOD, fd, what, generation); }
73int event_del(int fd) { return epoll_ctl(g_epoll, EPOLL_CTL_DEL, fd, NULL); }
74
75/* A descriptor leaves the interest list when the last reference to its open
76 file description is closed, and the tracker never duplicates one */
77int event_close(int fd) { return close(fd); }
78
79int event_wait(ot_event *out, int max, int timeout_ms) {
80 int count, i;
81
82 if (max > g_ready_space) {
83 struct epoll_event *grown = realloc(g_ready, (size_t)max * sizeof(struct epoll_event));
84
85 if (!grown)
86 return -1;
87 g_ready = grown;
88 g_ready_space = max;
89 }
90
91 /* An interrupted wait is reported as no events rather than retried here,
92 so that the caller gets to look at whatever the signal was about */
93 if ((count = epoll_wait(g_epoll, g_ready, max, timeout_ms)) == -1)
94 return errno == EINTR ? 0 : -1;
95 for (i = 0; i < count; ++i) {
96 uint32_t events = g_ready[i].events;
97
98 out[i].fd = (int)(uint32_t)g_ready[i].data.u64;
99 out[i].generation = (uint32_t)(g_ready[i].data.u64 >> 32);
100 /* A hangup or an error is reported as both, so whichever side the caller
101 was waiting on gets to notice */
102 out[i].what = (events & (EPOLLIN | EPOLLHUP | EPOLLERR) ? EVENT_IN : 0) |
103 (events & (EPOLLOUT | EPOLLHUP | EPOLLERR) ? EVENT_OUT : 0);
104 }
105 return count;
106}
107
108#endif /* OT_EVENT_EPOLL */
109
110#ifdef OT_EVENT_KQUEUE
111/* ------------------------------------------------------------ kqueue */
112
113#include <sys/event.h>
114#include <sys/time.h>
115#include <sys/types.h>
116
117static int g_kqueue = -1;
118static struct kevent *g_ready;
119static int g_ready_space;
120
121const char *event_mechanism(void) { return "kqueue"; }
122/* The set lives in the kernel, and kevent may be called from any thread */
123int event_can_share(void) { return 1; }
124
125int event_init(void) {
126 g_ready = NULL;
127 g_ready_space = 0;
128 return (g_kqueue = kqueue()) == -1 ? -1 : 0;
129}
130
131void event_deinit(void) {
132 if (g_kqueue != -1)
133 close(g_kqueue);
134 free(g_ready);
135 g_kqueue = -1;
136 g_ready = NULL;
137 g_ready_space = 0;
138}
139
140/* kqueue keeps one registration per filter, so reading and writing are two
141 entries. A change of interest is an add of the one wanted and a delete
142 of the one not. The generation travels in udata, which the kernel passes
143 back untouched. */
144static int event_change(int fd, int what, uint32_t generation, int deleting) {
145 struct kevent change[2], result[2];
146 void *udata = (void *)(uintptr_t)(((uint64_t)generation << 32) | (uint32_t)fd);
147 int succeeded = 0, count, i;
148
149 EV_SET(&change[0], (uintptr_t)fd, EVFILT_READ,
150 EV_RECEIPT | ((deleting || !(what & EVENT_IN)) ? EV_DELETE : EV_ADD), 0, 0, udata);
151 EV_SET(&change[1], (uintptr_t)fd, EVFILT_WRITE,
152 EV_RECEIPT | ((deleting || !(what & EVENT_OUT)) ? EV_DELETE : EV_ADD), 0, 0, udata);
153
154 if ((count = kevent(g_kqueue, change, 2, result, 2, NULL)) == -1)
155 return -1;
156
157 for (i = 0; i < count && i < 2; ++i) {
158 if (!result[i].data) {
159 ++succeeded;
160 continue;
161 }
162 /* Dropping interest we never registered is not a failure */
163 if ((change[i].flags & EV_DELETE) && result[i].data == ENOENT)
164 continue;
165 errno = (int)result[i].data;
166 return -1;
167 }
168
169 /* Nothing at all was registered or removed, so the descriptor is unknown */
170 if (!succeeded) {
171 errno = ENOENT;
172 return -1;
173 }
174 return 0;
175}
176
177int event_add(int fd, int what, uint32_t generation) { return what ? event_change(fd, what, generation, 0) : 0; }
178int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
179int event_del(int fd) { return event_change(fd, 0, 0, 1); }
180
181/* Closing a descriptor removes every kevent that references it */
182int event_close(int fd) { return close(fd); }
183
184int event_wait(ot_event *out, int max, int timeout_ms) {
185 struct timespec timeout;
186 struct timespec *deadline = NULL;
187 int count, i;
188
189 if (max > g_ready_space) {
190 struct kevent *grown = realloc(g_ready, (size_t)max * sizeof(struct kevent));
191
192 if (!grown)
193 return -1;
194 g_ready = grown;
195 g_ready_space = max;
196 }
197
198 if (timeout_ms >= 0) {
199 timeout.tv_sec = timeout_ms / 1000;
200 timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000;
201 deadline = &timeout;
202 }
203
204 /* An interrupted wait is reported as no events rather than retried here,
205 so that the caller gets to look at whatever the signal was about */
206 if ((count = kevent(g_kqueue, NULL, 0, g_ready, max, deadline)) == -1)
207 return errno == EINTR ? 0 : -1;
208 for (i = 0; i < count; ++i) {
209 uint64_t udata = (uint64_t)(uintptr_t)g_ready[i].udata;
210 int eof = (g_ready[i].flags & (EV_EOF | EV_ERROR)) != 0;
211
212 out[i].fd = (int)(uint32_t)udata;
213 out[i].generation = (uint32_t)(udata >> 32);
214 out[i].what = ((g_ready[i].filter == EVFILT_READ || eof) ? EVENT_IN : 0) |
215 ((g_ready[i].filter == EVFILT_WRITE || eof) ? EVENT_OUT : 0);
216 }
217 return count;
218}
219
220#endif /* OT_EVENT_KQUEUE */
221
222#ifdef OT_EVENT_POLL
223/* -------------------------------------------------------------- poll */
224
225#include <poll.h>
226
227/* poll wants its descriptors packed, so the registered ones live in one
228 array and a second maps a descriptor number to its place in it. */
229static struct pollfd *g_pfds;
230static uint32_t *g_generation;
231static int *g_where;
232static int g_count, g_space, g_where_space;
233
234const char *event_mechanism(void) { return "poll"; }
235/* The arrays below are ours and nothing guards them */
236int event_can_share(void) { return 0; }
237
238int event_init(void) {
239 g_pfds = NULL, g_generation = NULL, g_where = NULL;
240 g_count = g_space = g_where_space = 0;
241 return 0;
242}
243
244void event_deinit(void) {
245 free(g_pfds), free(g_generation), free(g_where);
246 g_pfds = NULL, g_generation = NULL, g_where = NULL;
247 g_count = g_space = g_where_space = 0;
248}
249
250static short event_to_poll(int what) {
251 return (short)((what & EVENT_IN ? POLLIN : 0) | (what & EVENT_OUT ? POLLOUT : 0));
252}
253
254static int event_room_for(int fd) {
255 if (fd >= g_where_space) {
256 int space = g_where_space ? g_where_space : 64;
257 int *grown;
258
259 while (space <= fd)
260 space *= 2;
261 if (!(grown = realloc(g_where, (size_t)space * sizeof(int))))
262 return -1;
263 while (g_where_space < space)
264 grown[g_where_space++] = -1;
265 g_where = grown;
266 }
267
268 if (g_count == g_space) {
269 int space = g_space ? g_space * 2 : 64;
270 struct pollfd *pfds = realloc(g_pfds, (size_t)space * sizeof(struct pollfd));
271 uint32_t *gens;
272
273 if (!pfds)
274 return -1;
275 g_pfds = pfds;
276 if (!(gens = realloc(g_generation, (size_t)space * sizeof(uint32_t))))
277 return -1;
278 g_generation = gens;
279 g_space = space;
280 }
281 return 0;
282}
283
284int event_add(int fd, int what, uint32_t generation) {
285 if (fd < 0 || event_room_for(fd) == -1)
286 return -1;
287 if (g_where[fd] != -1) {
288 errno = EEXIST;
289 return -1;
290 }
291
292 g_pfds[g_count].fd = fd;
293 g_pfds[g_count].events = event_to_poll(what);
294 g_pfds[g_count].revents = 0;
295 g_generation[g_count] = generation;
296 g_where[fd] = g_count++;
297 return 0;
298}
299
300int event_modify(int fd, int what, uint32_t generation) {
301 if (fd < 0 || fd >= g_where_space || g_where[fd] == -1) {
302 errno = ENOENT;
303 return -1;
304 }
305 g_pfds[g_where[fd]].events = event_to_poll(what);
306 g_generation[g_where[fd]] = generation;
307 return 0;
308}
309
310int event_del(int fd) {
311 int at;
312
313 if (fd < 0 || fd >= g_where_space || (at = g_where[fd]) == -1) {
314 errno = ENOENT;
315 return -1;
316 }
317
318 /* The last one moves into the gap, so the array stays packed */
319 g_pfds[at] = g_pfds[--g_count];
320 g_generation[at] = g_generation[g_count];
321 g_where[g_pfds[at].fd] = at;
322 g_where[fd] = -1;
323 return 0;
324}
325
326/* Nothing here lives in the kernel, so the entry has to come out by hand
327 before the descriptor goes, or poll reports POLLNVAL for it forever */
328int event_close(int fd) {
329 event_del(fd);
330 return close(fd);
331}
332
333int event_wait(ot_event *out, int max, int timeout_ms) {
334 int ready, found = 0, i;
335
336 /* An interrupted wait is reported as no events rather than retried here,
337 so that the caller gets to look at whatever the signal was about */
338 if ((ready = poll(g_pfds, (nfds_t)g_count, timeout_ms)) == -1)
339 return errno == EINTR ? 0 : -1;
340 if (ready <= 0)
341 return ready;
342
343 for (i = 0; i < g_count && found < max; ++i) {
344 short revents = g_pfds[i].revents;
345
346 if (!revents)
347 continue;
348 out[found].fd = g_pfds[i].fd;
349 out[found].generation = g_generation[i];
350 out[found].what = (revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL) ? EVENT_IN : 0) |
351 (revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL) ? EVENT_OUT : 0);
352 ++found;
353 }
354 return found;
355}
356
357#endif /* OT_EVENT_POLL */
diff --git a/src/ot_event.h b/src/ot_event.h
new file mode 100644
index 0000000..cb84ca8
--- /dev/null
+++ b/src/ot_event.h
@@ -0,0 +1,47 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_EVENT_H__
5#define OT_EVENT_H__
6
7#include <stdint.h>
8
9/* Which descriptors have become readable or writable, asked of whichever
10 mechanism the kernel offers. The generation a descriptor was registered
11 with is handed back with every event, so a caller can tell an event for
12 the connection it is holding from one for a connection that has since been
13 closed and had its number reused. */
14
15enum { EVENT_IN = 1, EVENT_OUT = 2 };
16
17typedef struct {
18 int fd;
19 uint32_t generation;
20 int what;
21} ot_event;
22
23int event_init(void);
24void event_deinit(void);
25
26int event_add(int fd, int what, uint32_t generation);
27int event_modify(int fd, int what, uint32_t generation);
28int event_del(int fd);
29
30/* Stop watching a descriptor and close it. Where the kernel drops a closed
31 descriptor from its set by itself, this is one system call where doing the
32 two separately would be two. */
33int event_close(int fd);
34
35/* Waits at most timeout_ms, or indefinitely when that is negative, and fills
36 out with up to max ready descriptors. Returns how many, or -1. */
37int event_wait(ot_event *out, int max, int timeout_ms);
38
39/* Whether the registered set may be changed from another thread while this
40 one is waiting on it. True where the kernel keeps the set, false where
41 this module keeps it in memory of its own. */
42int event_can_share(void);
43
44/* Which mechanism was compiled in, for the version string */
45const char *event_mechanism(void);
46
47#endif
diff --git a/src/ot_fdstate.c b/src/ot_fdstate.c
new file mode 100644
index 0000000..8e4bd89
--- /dev/null
+++ b/src/ot_fdstate.c
@@ -0,0 +1,202 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdatomic.h>
6#include <stdint.h>
7#include <stdlib.h>
8#include <sys/mman.h>
9#include <sys/resource.h>
10#include <sys/types.h>
11
12/* Opentracker */
13#include "ot_fdstate.h"
14
15enum { FD_FREE, FD_IDLE, FD_BUSY, FD_CLOSING };
16
17/* Generation and state share one word so that taking the state is also a
18 check that the generation has not moved */
19#define FD_GEN(w) ((uint32_t)((w) >> 32))
20#define FD_ST(w) ((uint32_t)(((w) >> 24) & 0xff))
21#define FD_WORD(g, s) (((uint64_t)(g) << 32) | ((uint64_t)(s) << 24))
22#define FD_SET_ST(w, s) (((w) & ~(uint64_t)0xff000000) | ((uint64_t)(s) << 24))
23
24typedef struct {
25 _Atomic uint64_t state;
26 _Atomic(void *) cookie;
27 _Atomic ot_time expiry;
28} ot_fdslot;
29
30/* A descriptor the kernel can hand out is below the limit it was handed out
31 under, but the limit can be raised after we sized the table, so an out of
32 range descriptor is rejected */
33#define FD_SLOTS_MAX (1u << 20)
34
35static ot_fdslot *g_slots;
36static size_t g_slot_count;
37/* Descriptors are handed out lowest first, so a sweep only has to walk as
38 far as the highest one ever seen rather than the whole table. */
39static _Atomic size_t g_slot_highest;
40
41int fdstate_init(void) {
42 struct rlimit limit;
43 size_t count = FD_SLOTS_MAX;
44
45 if (getrlimit(RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < FD_SLOTS_MAX)
46 count = (size_t)limit.rlim_cur;
47
48 g_slots = mmap(NULL, count * sizeof(ot_fdslot), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
49 if (g_slots == MAP_FAILED) {
50 g_slots = NULL;
51 return -1;
52 }
53
54 g_slot_count = count;
55 atomic_store(&g_slot_highest, 0);
56 return 0;
57}
58
59void fdstate_deinit(void) {
60 if (g_slots)
61 munmap(g_slots, g_slot_count * sizeof(ot_fdslot));
62 g_slots = NULL;
63 g_slot_count = 0;
64 atomic_store(&g_slot_highest, 0);
65}
66
67size_t fdstate_slots(void) { return g_slot_count; }
68
69static ot_fdslot *fdstate_slot(int fd) {
70 if (fd < 0 || (size_t)fd >= g_slot_count)
71 return NULL;
72 return g_slots + fd;
73}
74
75uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) {
76 ot_fdslot *slot = fdstate_slot(fd);
77 uint64_t word;
78
79 if (!slot)
80 return 0;
81
82 {
83 size_t highest = atomic_load_explicit(&g_slot_highest, memory_order_relaxed);
84
85 while ((size_t)fd > highest &&
86 !atomic_compare_exchange_weak_explicit(&g_slot_highest, &highest, (size_t)fd, memory_order_relaxed,
87 memory_order_relaxed))
88 ;
89 }
90
91 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
92 atomic_store_explicit(&slot->cookie, cookie, memory_order_release);
93 atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
94 /* Publishes the two above to whoever claims the slot next */
95 atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word), FD_IDLE), memory_order_release);
96
97 return FD_GEN(word);
98}
99
100uint32_t fdstate_generation(int fd) {
101 ot_fdslot *slot = fdstate_slot(fd);
102
103 return slot ? FD_GEN(atomic_load_explicit(&slot->state, memory_order_relaxed)) : 0;
104}
105
106int fdstate_claim(int fd, uint32_t generation) {
107 ot_fdslot *slot = fdstate_slot(fd);
108 uint64_t word;
109
110 if (!slot)
111 return 0;
112
113 word = atomic_load_explicit(&slot->state, memory_order_acquire);
114 for (;;) {
115 if (FD_GEN(word) != generation || FD_ST(word) != FD_IDLE)
116 return 0;
117 if (atomic_compare_exchange_weak_explicit(&slot->state, &word, FD_SET_ST(word, FD_BUSY), memory_order_acquire,
118 memory_order_relaxed))
119 return 1;
120 /* word now holds what was really there, so the test above runs again */
121 }
122}
123
124void fdstate_release(int fd) {
125 ot_fdslot *slot = fdstate_slot(fd);
126 uint64_t word;
127
128 if (!slot)
129 return;
130
131 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
132 atomic_store_explicit(&slot->state, FD_SET_ST(word, FD_IDLE), memory_order_release);
133}
134
135int fdstate_claim_expired(int fd, ot_time now) {
136 ot_fdslot *slot = fdstate_slot(fd);
137 uint64_t word;
138 ot_time expiry;
139
140 if (!slot)
141 return 0;
142
143 /* Reading before asking for the slot keeps the common case, a descriptor
144 that has not expired, off the bus entirely */
145 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
146 if (FD_ST(word) != FD_IDLE)
147 return 0;
148 expiry = atomic_load_explicit(&slot->expiry, memory_order_relaxed);
149 if (!expiry || expiry > now)
150 return 0;
151
152 /* Nothing can move the expiry while the slot sits at FD_IDLE, so winning
153 the slot is the last word */
154 return atomic_compare_exchange_strong_explicit(&slot->state, &word, FD_SET_ST(word, FD_CLOSING), memory_order_acquire,
155 memory_order_relaxed);
156}
157
158void fdstate_close(int fd) {
159 ot_fdslot *slot = fdstate_slot(fd);
160 uint64_t word;
161
162 if (!slot)
163 return;
164
165 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
166 atomic_store_explicit(&slot->cookie, NULL, memory_order_relaxed);
167 atomic_store_explicit(&slot->expiry, 0, memory_order_relaxed);
168 atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word) + 1, FD_FREE), memory_order_release);
169}
170
171int fdstate_next_expired(ot_time now, int *cursor) {
172 size_t highest = atomic_load_explicit(&g_slot_highest, memory_order_relaxed);
173
174 for (; (size_t)*cursor <= highest; ++*cursor)
175 if (fdstate_claim_expired(*cursor, now))
176 return (*cursor)++;
177
178 return -1;
179}
180
181void *fdstate_cookie(int fd) {
182 ot_fdslot *slot = fdstate_slot(fd);
183
184 /* Pairs with the release in fdstate_set_cookie and fdstate_open */
185 return slot ? atomic_load_explicit(&slot->cookie, memory_order_acquire) : NULL;
186}
187
188void fdstate_set_cookie(int fd, void *cookie) {
189 ot_fdslot *slot = fdstate_slot(fd);
190
191 /* Publishes what the cookie points at to whoever reads it next, which may
192 be another thread entirely */
193 if (slot)
194 atomic_store_explicit(&slot->cookie, cookie, memory_order_release);
195}
196
197void fdstate_set_expiry(int fd, ot_time expiry) {
198 ot_fdslot *slot = fdstate_slot(fd);
199
200 if (slot)
201 atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
202}
diff --git a/src/ot_fdstate.h b/src/ot_fdstate.h
new file mode 100644
index 0000000..60fe070
--- /dev/null
+++ b/src/ot_fdstate.h
@@ -0,0 +1,62 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_FDSTATE_H__
5#define OT_FDSTATE_H__
6
7#include <stdint.h>
8
9#include "trackerlogic.h"
10
11/* What the tracker knows about a file descriptor: who owns it, when it
12 should be given up on, and whether anyone is working on it right now.
13
14 A descriptor number says nothing about identity, since the kernel hands
15 the same number out again as soon as it is closed. Every slot therefore
16 carries a generation that is bumped when the descriptor is given up, and
17 every caller that was told about a descriptor has to say which generation
18 it was told about. A caller holding a stale generation is turned away
19 instead of being handed somebody else's connection.
20
21 The table is sized once from RLIMIT_NOFILE and never moves, so looking a
22 descriptor up needs no synchronisation at all. */
23
24int fdstate_init(void);
25void fdstate_deinit(void);
26size_t fdstate_slots(void);
27
28/* Take a freshly accepted descriptor into the table and return the
29 generation to quote when asking for it back. expiry of 0 never expires. */
30uint32_t fdstate_open(int fd, void *cookie, ot_time expiry);
31
32/* Which generation a descriptor currently carries, to check an event that
33 was handed out earlier against the connection living there now. */
34uint32_t fdstate_generation(int fd);
35
36/* Ask for exclusive use. Returns 0 if the descriptor has been recycled since
37 generation was handed out, or if somebody else is using it. */
38int fdstate_claim(int fd, uint32_t generation);
39void fdstate_release(int fd);
40
41/* The cleaner's side of the same thing: takes the descriptor only if nobody
42 is working on it and it really has expired. Never waits. */
43int fdstate_claim_expired(int fd, ot_time now);
44
45/* Give the descriptor up. The generation is bumped here, which has to happen
46 before close(2) so that the kernel cannot reissue the number while events
47 quoting the old generation are still in flight. */
48void fdstate_close(int fd);
49
50/* Walks the table for descriptors that have expired, handing back one at a
51 time until it returns -1. The cursor belongs to the caller, so several
52 sweepers can run without sharing anything. */
53int fdstate_next_expired(ot_time now, int *cursor);
54
55/* Both only valid while the caller holds the descriptor. */
56void *fdstate_cookie(int fd);
57
58/* Hand the slot a different cookie, for a connection whose state has moved */
59void fdstate_set_cookie(int fd, void *cookie);
60void fdstate_set_expiry(int fd, ot_time expiry);
61
62#endif
diff --git a/src/ot_fmt.c b/src/ot_fmt.c
new file mode 100644
index 0000000..362c9b5
--- /dev/null
+++ b/src/ot_fmt.c
@@ -0,0 +1,40 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdarg.h>
6#include <stdio.h>
7#include <string.h>
8
9/* Opentracker */
10#include "ot_fmt.h"
11
12void fmt_append(char **cursor, const char *end, const char *format, ...) {
13 va_list arguments;
14 size_t room;
15 int written;
16
17 if (*cursor >= end) {
18 *cursor = (char *)end;
19 return;
20 }
21
22 room = (size_t)(end - *cursor);
23 va_start(arguments, format);
24 written = vsnprintf(*cursor, room, format, arguments);
25 va_end(arguments);
26
27 if (written < 0)
28 return;
29 *cursor = (size_t)written >= room ? (char *)end : *cursor + written;
30}
31
32void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) {
33 if (*cursor >= end || (size_t)(end - *cursor) < length) {
34 *cursor = (char *)end;
35 return;
36 }
37
38 memcpy(*cursor, bytes, length);
39 *cursor += length;
40}
diff --git a/src/ot_fmt.h b/src/ot_fmt.h
new file mode 100644
index 0000000..214cbc4
--- /dev/null
+++ b/src/ot_fmt.h
@@ -0,0 +1,19 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_FMT_H__
5#define OT_FMT_H__
6
7#include <stddef.h>
8
9/* Appending to a cursor that walks a fixed buffer. Both take the cursor by
10 reference and advance it, so a reply is written as a run of statements and
11 no caller does pointer arithmetic of its own. */
12
13void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4)));
14
15/* Append raw bytes for the binary stretches of a bencoded reply.
16 Either everything is appended or nothing at all */
17void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length);
18
19#endif
diff --git a/src/ot_fullscrape.c b/src/ot_fullscrape.c
new file mode 100644
index 0000000..9afad92
--- /dev/null
+++ b/src/ot_fullscrape.c
@@ -0,0 +1,455 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifdef WANT_FULLSCRAPE
5
6/* System */
7#include <arpa/inet.h>
8#include <pthread.h>
9#include <stdio.h>
10#include <string.h>
11#ifdef WANT_COMPRESSION_GZIP
12#include <zlib.h>
13#endif
14#ifdef WANT_COMPRESSION_ZSTD
15#include <zstd.h>
16#endif
17
18/* Opentracker */
19#include "ot_fmt.h"
20#include "ot_hex.h"
21#include "ot_fullscrape.h"
22#include "ot_iovec.h"
23#include "ot_mutex.h"
24#include "ot_workqueue.h"
25#include "ot_urlencoded.h"
26#include "trackerlogic.h"
27
28/* Fetch full scrape info for all torrents
29 Full scrapes usually are huge and one does not want to
30 allocate more memory. So lets get them in 512k units
31*/
32#define OT_SCRAPE_CHUNK_SIZE (1024 * 1024)
33
34/* "d8:completei%zde10:downloadedi%zde10:incompletei%zdee" */
35#define OT_SCRAPE_MAXENTRYLEN 256
36
37/* Forward declaration */
38static void fullscrape_make(int taskid, ot_tasktype mode);
39#ifdef WANT_COMPRESSION_GZIP
40static void fullscrape_make_gzip(int taskid, ot_tasktype mode);
41#endif
42#ifdef WANT_COMPRESSION_ZSTD
43static void fullscrape_make_zstd(int taskid, ot_tasktype mode);
44#endif
45
46/* Converter function from memory to human readable hex strings
47 XXX - Duplicated from ot_stats. Needs fix. */
48/* This is the entry point into this worker thread
49 It grabs tasks from mutex_tasklist and delivers results back
50*/
51static void *fullscrape_worker(void *args) {
52 (void)args;
53
54 while (g_opentracker_running) {
55 ot_tasktype tasktype = TASK_FULLSCRAPE;
56 ot_taskid taskid = workqueue_poptask(&tasktype);
57 if (!taskid)
58 break;
59#ifdef WANT_COMPRESSION_ZSTD
60 if (tasktype & TASK_FLAG_ZSTD)
61 fullscrape_make_zstd(taskid, tasktype);
62 else
63#endif
64#ifdef WANT_COMPRESSION_GZIP
65 if (tasktype & TASK_FLAG_GZIP)
66 fullscrape_make_gzip(taskid, tasktype);
67 else
68#endif
69 fullscrape_make(taskid, tasktype);
70 workqueue_pushchunked(taskid, NULL);
71 }
72 return NULL;
73}
74
75static pthread_t thread_id;
76void fullscrape_init(void) {
77 pthread_create( &thread_id, NULL, fullscrape_worker, NULL );
78}
79
80void fullscrape_deinit(void) {
81 workqueue_wakeall();
82 pthread_join( thread_id, NULL );
83}
84
85void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) {
86 workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype );
87}
88
89static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) {
90 /* Callers guarantee this much room: the chunk writer keeps its low
91 watermark that far from the end, the compressors hand over a buffer of
92 exactly that size */
93 char *r = *cursor;
94 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN;
95 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
96 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
97 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
98
99 switch (mode & TASK_TASK_MASK) {
100 case TASK_FULLSCRAPE:
101 default:
102 /* push hash as bencoded string */
103 *r++ = '2';
104 *r++ = '0';
105 *r++ = ':';
106 memcpy(r, hash, sizeof(ot_hash));
107 r += sizeof(ot_hash);
108 /* push rest of the scrape string */
109 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count);
110
111 break;
112 case TASK_FULLSCRAPE_TPB_ASCII:
113 hex_from_hash(r, *hash);
114 r += 2 * sizeof(ot_hash);
115 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
116 break;
117 case TASK_FULLSCRAPE_TPB_ASCII_PLUS:
118 hex_from_hash(r, *hash);
119 r += 2 * sizeof(ot_hash);
120 fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count);
121 break;
122 case TASK_FULLSCRAPE_TPB_BINARY:
123 memcpy(r, *hash, sizeof(ot_hash));
124 r += sizeof(ot_hash);
125 *(uint32_t *)(r + 0) = htonl((uint32_t)seed_count);
126 *(uint32_t *)(r + 4) = htonl((uint32_t)(peer_count - seed_count));
127 r += 8;
128 break;
129 case TASK_FULLSCRAPE_TPB_URLENCODED:
130 r += urlencoded_format(r, *hash, sizeof(ot_hash));
131 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
132 break;
133 case TASK_FULLSCRAPE_TRACKERSTATE:
134 hex_from_hash(r, *hash);
135 r += 2 * sizeof(ot_hash);
136 fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count);
137 break;
138 }
139 *cursor = r;
140}
141
142static void fullscrape_make(int taskid, ot_tasktype mode) {
143 size_t bucket;
144 char *r, *re;
145 struct iovec iovector = {NULL, 0};
146
147 /* Setup return vector... */
148 r = iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
149 if (!r)
150 return;
151
152 /* re points to low watermark */
153 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
154
155 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
156 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd");
157
158 /* For each bucket... */
159 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
160 /* Get exclusive access to that bucket */
161 ot_vector *torrents_list = mutex_bucket_lock(bucket);
162 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
163 size_t i;
164
165 /* For each torrent in this bucket.. */
166 for (i = 0; i < torrents_list->size; ++i) {
167 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
168
169 if (r > re) {
170 iovector.iov_len = r - (char *)iovector.iov_base;
171
172 if (workqueue_pushchunked(taskid, &iovector)) {
173 free(iovector.iov_base);
174 mutex_bucket_unlock(bucket, 0);
175 return;
176 }
177 /* Allocate a fresh output buffer */
178 r = iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
179 if (!r) {
180 mutex_bucket_unlock(bucket, 0);
181 return;
182 }
183
184 /* re points to low watermark */
185 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
186 }
187 }
188
189 /* All torrents done: release lock on current bucket */
190 mutex_bucket_unlock(bucket, 0);
191
192 /* Parent thread died? */
193 if (!g_opentracker_running)
194 return;
195 }
196
197 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
198 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee");
199
200 /* Send rest of data */
201 iovector.iov_len = r - (char *)iovector.iov_base;
202 if (workqueue_pushchunked(taskid, &iovector))
203 free(iovector.iov_base);
204}
205
206#ifdef WANT_COMPRESSION_GZIP
207
208static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
209 size_t bucket;
210 char *r;
211 struct iovec iovector = {NULL, 0};
212 int zres;
213 z_stream strm;
214 /* Setup return vector... */
215 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
216 if (!iovector.iov_base)
217 return;
218
219 memset(&strm, 0, sizeof(strm));
220 strm.next_out = (uint8_t *)iovector.iov_base;
221 strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
222 if (deflateInit2(&strm, 7, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY) != Z_OK)
223 fprintf(stderr, "not ok.\n");
224
225 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
226 strm.next_in = (uint8_t *)"d5:filesd";
227 strm.avail_in = strlen("d5:filesd");
228 zres = deflate(&strm, Z_NO_FLUSH);
229 }
230
231 /* For each bucket... */
232 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
233 /* Get exclusive access to that bucket */
234 ot_vector *torrents_list = mutex_bucket_lock(bucket);
235 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
236 size_t i;
237
238 /* For each torrent in this bucket.. */
239 for (i = 0; i < torrents_list->size; ++i) {
240 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
241 r = compress_buffer;
242 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
243 strm.next_in = (uint8_t *)compress_buffer;
244 strm.avail_in = r - compress_buffer;
245 zres = deflate(&strm, Z_NO_FLUSH);
246 if ((zres < Z_OK) && (zres != Z_BUF_ERROR))
247 fprintf(stderr, "deflate() failed while in fullscrape_make().\n");
248
249 /* Check if there still is enough buffer left */
250 while (!strm.avail_out) {
251 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
252
253 if (workqueue_pushchunked(taskid, &iovector)) {
254 free(iovector.iov_base);
255 mutex_bucket_unlock(bucket, 0);
256 return;
257 }
258 /* Allocate a fresh output buffer */
259 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
260 if (!iovector.iov_base) {
261 fprintf(stderr, "Out of memory trying to claim ouput buffer\n");
262 deflateEnd(&strm);
263 mutex_bucket_unlock(bucket, 0);
264 return;
265 }
266 strm.next_out = (uint8_t *)iovector.iov_base;
267 strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
268 zres = deflate(&strm, Z_NO_FLUSH);
269 if ((zres < Z_OK) && (zres != Z_BUF_ERROR))
270 fprintf(stderr, "deflate() failed while in fullscrape_make().\n");
271 }
272 }
273
274 /* All torrents done: release lock on current bucket */
275 mutex_bucket_unlock(bucket, 0);
276
277 /* Parent thread died? */
278 if (!g_opentracker_running) {
279 deflateEnd(&strm);
280 return;
281 }
282 }
283
284 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
285 strm.next_in = (uint8_t *)"ee";
286 strm.avail_in = strlen("ee");
287 }
288
289 if (deflate(&strm, Z_FINISH) < Z_OK)
290 fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n");
291
292 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
293 if (workqueue_pushchunked(taskid, &iovector)) {
294 free(iovector.iov_base);
295 deflateEnd(&strm);
296 return;
297 }
298
299 /* Check if there's a last batch of data in the zlib buffer */
300 if (!strm.avail_out) {
301 /* Allocate a fresh output buffer */
302 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
303
304 if (!iovector.iov_base) {
305 fprintf(stderr, "Problem with iovec_next_chunk\n");
306 deflateEnd(&strm);
307 return;
308 }
309 strm.next_out = iovector.iov_base;
310 strm.avail_out = OT_SCRAPE_CHUNK_SIZE;
311 if (deflate(&strm, Z_FINISH) < Z_OK)
312 fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n");
313
314 /* Only pass the new buffer if there actually was some data left in the buffer */
315 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
316 if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector))
317 free(iovector.iov_base);
318 }
319
320 deflateEnd(&strm);
321}
322/* WANT_COMPRESSION_GZIP */
323#endif
324
325#ifdef WANT_COMPRESSION_ZSTD
326
327static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
328 int bucket;
329 char *r;
330 struct iovec iovector = {NULL, 0};
331 ZSTD_CCtx *zstream = ZSTD_createCCtx();
332 ZSTD_inBuffer inbuf;
333 ZSTD_outBuffer outbuf;
334 size_t more_bytes;
335
336 if (!zstream)
337 return;
338
339 /* Setup return vector... */
340 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
341 if (!iovector.iov_base) {
342 ZSTD_freeCCtx(zstream);
343 return;
344 }
345
346 /* Working with a compression level 6 is half as fast as level 3, but
347 seems to be the last reasonable bump that's worth extra cpu */
348 ZSTD_CCtx_setParameter(zstream, ZSTD_c_compressionLevel, 6);
349
350 outbuf.dst = iovector.iov_base;
351 outbuf.size = OT_SCRAPE_CHUNK_SIZE;
352 outbuf.pos = 0;
353
354 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
355 inbuf.src = (const void *)"d5:filesd";
356 inbuf.size = strlen("d5:filesd");
357 inbuf.pos = 0;
358 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue);
359 }
360
361 /* For each bucket... */
362 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
363 /* Get exclusive access to that bucket */
364 ot_vector *torrents_list = mutex_bucket_lock(bucket);
365 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
366 size_t i;
367
368 /* For each torrent in this bucket.. */
369 for (i = 0; i < torrents_list->size; ++i) {
370 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
371 r = compress_buffer;
372 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
373 inbuf.src = compress_buffer;
374 inbuf.size = r - compress_buffer;
375 inbuf.pos = 0;
376 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue);
377
378 /* Check if there still is enough buffer left */
379 while (outbuf.pos + OT_SCRAPE_MAXENTRYLEN > outbuf.size) {
380 iovector.iov_len = outbuf.size;
381
382 if (workqueue_pushchunked(taskid, &iovector)) {
383 free(iovector.iov_base);
384 ZSTD_freeCCtx(zstream);
385 return mutex_bucket_unlock(bucket, 0);
386 }
387 /* Allocate a fresh output buffer */
388 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
389 if (!iovector.iov_base) {
390 fprintf(stderr, "Out of memory trying to claim ouput buffer\n");
391 ZSTD_freeCCtx(zstream);
392 return mutex_bucket_unlock(bucket, 0);
393 }
394
395 outbuf.dst = iovector.iov_base;
396 outbuf.size = OT_SCRAPE_CHUNK_SIZE;
397 outbuf.pos = 0;
398
399 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue);
400 }
401 }
402
403 /* All torrents done: release lock on current bucket */
404 mutex_bucket_unlock(bucket, 0);
405
406 /* Parent thread died? */
407 if (!g_opentracker_running)
408 return;
409 }
410
411 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) {
412 inbuf.src = (const void *)"ee";
413 inbuf.size = strlen("ee");
414 inbuf.pos = 0;
415 }
416
417 more_bytes = ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end);
418
419 iovector.iov_len = outbuf.pos;
420 if (workqueue_pushchunked(taskid, &iovector)) {
421 free(iovector.iov_base);
422 ZSTD_freeCCtx(zstream);
423 return;
424 }
425
426 /* Check if there's a last batch of data in the zlib buffer */
427 if (more_bytes) {
428 /* Allocate a fresh output buffer */
429 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
430
431 if (!iovector.iov_base) {
432 fprintf(stderr, "Problem with iovec_next_chunk\n");
433 ZSTD_freeCCtx(zstream);
434 return;
435 }
436
437 outbuf.dst = iovector.iov_base;
438 outbuf.size = OT_SCRAPE_CHUNK_SIZE;
439 outbuf.pos = 0;
440
441 ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end);
442
443 /* Only pass the new buffer if there actually was some data left in the buffer */
444 iovector.iov_len = outbuf.pos;
445 if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector))
446 free(iovector.iov_base);
447 }
448
449 ZSTD_freeCCtx(zstream);
450}
451/* WANT_COMPRESSION_ZSTD */
452#endif
453
454/* WANT_FULLSCRAPE */
455#endif
diff --git a/src/ot_fullscrape.h b/src/ot_fullscrape.h
new file mode 100644
index 0000000..8d11cd3
--- /dev/null
+++ b/src/ot_fullscrape.h
@@ -0,0 +1,23 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_FULLSCRAPE_H__
5#define OT_FULLSCRAPE_H__
6
7#ifdef WANT_FULLSCRAPE
8
9#include "ot_mutex.h"
10#include "ot_workqueue.h"
11
12void fullscrape_init(void);
13void fullscrape_deinit(void);
14void fullscrape_deliver(int64_t sock, ot_tasktype tasktype);
15
16#else
17
18#define fullscrape_init()
19#define fullscrape_deinit()
20
21#endif
22
23#endif
diff --git a/src/ot_hex.c b/src/ot_hex.c
new file mode 100644
index 0000000..b537094
--- /dev/null
+++ b/src/ot_hex.c
@@ -0,0 +1,56 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdint.h>
6#include <string.h>
7
8/* Opentracker */
9#include "ot_hex.h"
10
11int hex_value(int c) {
12 unsigned char u = (unsigned char)c;
13
14 if (u >= '0' && u <= '9')
15 return u - '0';
16 u |= 0x20; /* fold upper case onto lower, digits are already handled */
17 if (u >= 'a' && u <= 'f')
18 return u - 'a' + 10;
19 return -1;
20}
21
22int hex_isdigit(int c) { return hex_value(c) >= 0; }
23
24char *hex_from_hash(char *dest, const uint8_t *hash) {
25 static const char digits[] = "0123456789ABCDEF";
26 char *d = dest;
27 size_t i;
28
29 for (i = 0; i < sizeof(ot_hash); ++i) {
30 *d++ = digits[hash[i] >> 4];
31 *d++ = digits[hash[i] & 15];
32 }
33 *d = 0;
34 return dest;
35}
36
37int hex_to_hash(ot_hash hash, const char *src) {
38 uint8_t decoded[sizeof(ot_hash)];
39 size_t i;
40
41 for (i = 0; i < sizeof(ot_hash); ++i) {
42 int high, low;
43
44 /* Tested one character at a time so that a string ending early stops us
45 at its terminator rather than one byte past it */
46 if ((high = hex_value((unsigned char)src[2 * i])) < 0)
47 return 0;
48 if ((low = hex_value((unsigned char)src[2 * i + 1])) < 0)
49 return 0;
50
51 decoded[i] = (uint8_t)((high << 4) | low);
52 }
53
54 memcpy(hash, decoded, sizeof(decoded));
55 return 1;
56}
diff --git a/src/ot_hex.h b/src/ot_hex.h
new file mode 100644
index 0000000..ddbbb44
--- /dev/null
+++ b/src/ot_hex.h
@@ -0,0 +1,27 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_HEX_H__
5#define OT_HEX_H__
6
7#include <stdint.h>
8
9#include "trackerlogic.h"
10
11/* Write hash as forty upper case hex digits followed by a terminating zero.
12 dest must have room for forty one bytes. Returns dest. */
13char *hex_from_hash(char *dest, const uint8_t *hash);
14
15/* Read forty hex digits from src into hash. Returns non-zero on success.
16 On failure hash is left untouched and no byte of src beyond the offending
17 character is read, so src need not be forty characters long. */
18int hex_to_hash(ot_hash hash, const char *src);
19
20/* Non-zero if c is a hexadecimal digit, in either case. */
21int hex_isdigit(int c);
22
23/* The value of a hexadecimal digit, or -1 if c is not one. Shared with the
24 address scanner so that both agree on what a digit is. */
25int hex_value(int c);
26
27#endif
diff --git a/src/ot_http.c b/src/ot_http.c
new file mode 100644
index 0000000..96b8f4e
--- /dev/null
+++ b/src/ot_http.c
@@ -0,0 +1,810 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#define _GNU_SOURCE
6#include <arpa/inet.h>
7#include <errno.h>
8#include <pthread.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <strings.h>
13#include <sys/types.h>
14#include <unistd.h>
15
16/* Opentracker */
17#include "ot_accesslist.h"
18#include "ot_fullscrape.h"
19#include "ot_http.h"
20#include "ot_iovec.h"
21#include "ot_ip.h"
22#include "ot_mutex.h"
23#include "ot_workqueue.h"
24#include "ot_scan.h"
25#include "ot_event.h"
26#include "ot_fdstate.h"
27#include "ot_vector.h"
28#include "ot_fmt.h"
29#include "ot_stats.h"
30#include "ot_urlencoded.h"
31#include "trackerlogic.h"
32
33#define OT_MAXMULTISCRAPE_COUNT 64
34extern const char *g_redirecturl;
35
36const char *g_stats_path;
37ssize_t g_stats_path_len;
38
39enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 };
40
41struct http_data *http_adopt(const int64_t sock) {
42 struct http_data *cookie = fdstate_cookie(sock), *heap;
43 int interest;
44
45 if (!cookie || !(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK))
46 return cookie;
47
48 if (!(heap = malloc(sizeof(*heap))))
49 return NULL;
50
51 *heap = *cookie;
52 heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK;
53
54 /* Publishing the cookie is what lets another thread reach this connection,
55 so nothing may touch it afterwards, not even to read what it asked for */
56 interest = heap->interest;
57 fdstate_set_cookie((int)sock, heap);
58
59 if (event_add((int)sock, interest, fdstate_generation((int)sock)) == -1) {
60 fdstate_set_cookie((int)sock, cookie);
61 free(heap);
62 return NULL;
63 }
64 return heap;
65}
66
67/* Give up a connection: the one place that knows everything hanging off it. */
68void http_close(const int64_t sock) {
69 struct http_data *cookie = fdstate_cookie(sock);
70
71 fdstate_close((int)sock);
72
73 if (cookie) {
74 if (cookie->batch)
75 batch_reset(cookie->batch);
76 free(cookie->batch);
77 buf_reset(&cookie->request);
78 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))
79 workqueue_canceltask(sock);
80 if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK))
81 free(cookie);
82 }
83
84 event_close((int)sock);
85}
86
87static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
88 struct http_data *cookie = fdstate_cookie(sock);
89 ssize_t written_size;
90
91 if (!cookie) {
92 http_close(sock);
93 return;
94 }
95
96 /* whoever sends data is not interested in its input buffer */
97 if (ws->keep_alive && ws->header_size != ws->request_size) {
98 size_t rest = ws->request_size - ws->header_size;
99 if (cookie->request.data) {
100 memmove(cookie->request.data, ws->request + ws->header_size, rest);
101 buf_truncate(&cookie->request, rest);
102 } else if (buf_cat(&cookie->request, ws->request + ws->header_size, rest)) {
103 /* Without room for what the client pipelined behind this request, answer
104 this one and let the connection close, rather than keeping it open
105 around a request we have silently dropped */
106 buf_reset(&cookie->request);
107 ws->keep_alive = 0;
108 }
109 } else
110 buf_reset(&cookie->request);
111
112 written_size = write(sock, ws->reply, ws->reply_size);
113 /* A send buffer with no room in it is not a failure: nothing went out, so
114 the whole reply goes into the batch below and leaves when the socket
115 drains. Only a real error ends the connection here. */
116 if (written_size < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
117 written_size = 0;
118
119 if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) {
120 http_close(sock);
121 return;
122 }
123
124 if (written_size < ws->reply_size) {
125 char *outbuf;
126
127 if (!(outbuf = malloc(ws->reply_size - written_size))) {
128 http_close(sock);
129 return;
130 }
131
132 memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size);
133 if (!cookie->batch) {
134 cookie->batch = calloc(1, sizeof(ot_batch));
135 if (!cookie->batch) {
136 free(outbuf);
137 http_close(sock);
138 return;
139 }
140 }
141
142 batch_add(cookie->batch, outbuf, ws->reply_size - written_size);
143
144 /* writeable short data sockets just have a tcp timeout */
145 if (!ws->keep_alive)
146 fdstate_set_expiry(sock, 0);
147 http_interest_set(sock, cookie, EVENT_OUT);
148 }
149}
150
151#define HTTPERROR_302 return http_issue_error(sock, ws, CODE_HTTPERROR_302)
152#define HTTPERROR_400 return http_issue_error(sock, ws, CODE_HTTPERROR_400)
153#define HTTPERROR_400_PARAM return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM)
154#define HTTPERROR_400_COMPACT return http_issue_error(sock, ws, CODE_HTTPERROR_400_COMPACT)
155#define HTTPERROR_400_DOUBLEHASH return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM)
156#define HTTPERROR_402_NOTMODEST return http_issue_error(sock, ws, CODE_HTTPERROR_402_NOTMODEST)
157#define HTTPERROR_403_IP return http_issue_error(sock, ws, CODE_HTTPERROR_403_IP)
158#define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404)
159#define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500)
160/* A connection is either being read or being written, never both, so what it
161 waits for is set outright rather than added to and taken away again. The
162 event layer only hears about it when the answer changes. */
163void http_interest_set(const int64_t sock, struct http_data *cookie, int what) {
164 if (cookie->interest == what)
165 return;
166 cookie->interest = what;
167 /* Still on a stack, so not in the event loop yet: whoever adopts this
168 connection registers whatever it asked for last */
169 if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)
170 return;
171 event_modify((int)sock, what, fdstate_generation((int)sock));
172}
173
174ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) {
175 const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required",
176 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"};
177 const char *title = error_code[code];
178
179 ws->reply = ws->outbuf;
180 if (code == CODE_HTTPERROR_302)
181 ws->reply_size = snprintf(ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 302 Found\r\nContent-Length: 0\r\nLocation: %s\r\n\r\n", g_redirecturl);
182 else
183 ws->reply_size = snprintf(ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", title,
184 strlen(title) + 16 - 4, title + 4);
185
186#ifdef _DEBUG_HTTPERROR
187 fprintf(stderr, "DEBUG: invalid request was: %s\n", ws->debugbuf);
188#endif
189 stats_issue_event(ws, EVENT_FAILED, FLAG_TCP, code);
190 http_senddata(sock, ws);
191 return ws->reply_size = -2;
192}
193
194ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) {
195 struct http_data *cookie = fdstate_cookie(sock);
196 char *header;
197 const char *encoding = "";
198 size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector);
199
200 /* No cookie? Bad socket. Leave. */
201 if (!cookie) {
202 iovec_free(&iovec_entries, &iovector);
203 HTTPERROR_500;
204 }
205
206 /* Descriptor numbers get recycled, so a result can only belong to this
207 connection if this connection is in fact waiting for one. */
208 if (!(cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))) {
209 iovec_free(&iovec_entries, &iovector);
210 return 0;
211 }
212
213 /* If this socket collected request in a buffer, free it now */
214 buf_reset(&cookie->request);
215
216 /* If we came here, wait for the answer is over */
217 if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) {
218 http_interest_set(sock, cookie, 0);
219 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
220 }
221
222 if (iovec_entries) {
223
224 if (cookie->flag & STRUCT_HTTP_FLAG_ZSTD)
225 encoding = "Content-Encoding: zstd\r\n";
226 else if (cookie->flag & STRUCT_HTTP_FLAG_GZIP)
227 encoding = "Content-Encoding: gzip\r\n";
228 else if (cookie->flag & STRUCT_HTTP_FLAG_BZIP2)
229 encoding = "Content-Encoding: bzip2\r\n";
230
231 if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED))
232 header_size = asprintf(&header, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n%sContent-Length: %zd\r\n\r\n", encoding, size);
233 else {
234 if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) {
235 header_size =
236 asprintf(&header, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n%sTransfer-Encoding: chunked\r\n\r\n%zx\r\n", encoding, size);
237 cookie->flag |= STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
238 } else
239 header_size = asprintf(&header, "%zx\r\n", size);
240 }
241 if (!header) {
242 iovec_free(&iovec_entries, &iovector);
243 HTTPERROR_500;
244 }
245
246 if (!cookie->batch) {
247 cookie->batch = calloc(1, sizeof(ot_batch));
248 if (!cookie->batch) {
249 free(header);
250 iovec_free(&iovec_entries, &iovector);
251 HTTPERROR_500;
252 }
253 }
254
255 batch_add(cookie->batch, header, header_size);
256 batch_add_vector(cookie->batch, iovector, iovec_entries);
257 free(iovector);
258
259 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)
260 batch_add_copy(cookie->batch, "\r\n", 2);
261 }
262
263 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) {
264 batch_add_copy(cookie->batch, "0\r\n\r\n", 5);
265 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
266 }
267
268 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT_SEND));
269 http_interest_set(sock, cookie, EVENT_OUT);
270 return 0;
271}
272
273static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
274 static const ot_keywords keywords_main[] = {{"mode", 1}, {"format", 2}, {"info_hash", 3}, {NULL, -3}};
275 static const ot_keywords keywords_mode[] = {{"peer", TASK_STATS_PEERS},
276 {"conn", TASK_STATS_CONNS},
277 {"scrp", TASK_STATS_SCRAPE},
278 {"udp4", TASK_STATS_UDP},
279 {"tcp4", TASK_STATS_TCP},
280 {"torr", TASK_STATS_TORRENTS},
281 {"fscr", TASK_STATS_FULLSCRAPE},
282 {"s24s", TASK_STATS_SLASH24S},
283 {"tpbs", TASK_STATS_TPB},
284 {"herr", TASK_STATS_HTTPERRORS},
285 {"completed", TASK_STATS_COMPLETED},
286 {"top100", TASK_STATS_TOP100},
287 {"top10", TASK_STATS_TOP10},
288 {"renew", TASK_STATS_RENEW},
289 {"syncs", TASK_STATS_SYNCS},
290 {"everything", TASK_STATS_EVERYTHING},
291 {"statedump", TASK_FULLSCRAPE_TRACKERSTATE},
292 {"woodpeckers", TASK_STATS_WOODPECKERS},
293#ifdef WANT_LOG_NUMWANT
294 {"numwants", TASK_STATS_NUMWANTS},
295#endif
296 {NULL, -3}};
297 static const ot_keywords keywords_format[] = {{"bin", TASK_FULLSCRAPE_TPB_BINARY}, {"ben", TASK_FULLSCRAPE},
298 {"url", TASK_FULLSCRAPE_TPB_URLENCODED}, {"txt", TASK_FULLSCRAPE_TPB_ASCII},
299 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}};
300
301 struct http_data *cookie = fdstate_cookie(sock);
302 char *reply;
303 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
304
305 /* A socket without one is a dead socket, which is what http_sendiovecdata
306 already answers 500 to. */
307 if (!cookie)
308 HTTPERROR_500;
309
310#ifdef WANT_RESTRICT_STATS
311 if (!accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_STAT))
312 HTTPERROR_403_IP;
313#endif
314
315 while (scanon) {
316 switch (urlencoded_match_keyword(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
317 case -2:
318 scanon = 0;
319 break; /* TERMINATOR */
320 case -1:
321 HTTPERROR_400_PARAM; /* PARSE ERROR */
322 case -3:
323 urlencoded_skip_value(&read_ptr);
324 break;
325 case 1: /* matched "mode" */
326 if ((mode = urlencoded_match_keyword(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
327 HTTPERROR_400_PARAM;
328 break;
329 case 2: /* matched "format" */
330 if ((format = urlencoded_match_keyword(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
331 HTTPERROR_400_PARAM;
332 break;
333 case 3:
334 HTTPERROR_400_PARAM; /* If the stats URL was mistakenly added as announce URL, return a 400 */
335 }
336 }
337
338#ifdef WANT_FULLSCRAPE
339 if (mode == TASK_FULLSCRAPE_TRACKERSTATE) {
340 format = mode;
341 mode = TASK_STATS_TPB;
342 }
343
344 if (mode == TASK_STATS_TPB) {
345#ifdef WANT_COMPRESSION_GZIP
346 ws->request[ws->request_size - 1] = 0;
347#ifndef WANT_COMPRESSION_GZIP_ALWAYS
348 if (strstr(read_ptr - 1, "gzip")) {
349#endif
350 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
351 format |= TASK_FLAG_GZIP;
352#ifndef WANT_COMPRESSION_GZIP_ALWAYS
353 }
354#endif
355#endif
356 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
357
358 /* Clients waiting for us should not easily timeout */
359 fdstate_set_expiry(sock, 0);
360 http_interest_set(sock, cookie, 0);
361
362 /* Everything this connection needs has been written, so it can be given
363 a home of its own and only then be made reachable by a worker thread */
364 if (!http_adopt(sock))
365 HTTPERROR_500;
366 fullscrape_deliver(sock, format);
367 return ws->reply_size = -2;
368 }
369#endif
370
371 /* default format for now */
372 if ((mode & TASK_CLASS_MASK) == TASK_STATS) {
373
374 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
375
376 /* Complex stats also include expensive memory debugging tools */
377 fdstate_set_expiry(sock, 0);
378
379 if (!http_adopt(sock))
380 HTTPERROR_500;
381 stats_deliver(sock, mode);
382 return ws->reply_size = -2;
383 }
384
385 /* Simple stats can be answerred immediately */
386 reply = ws->reply;
387 return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0);
388 return ws->reply_size = reply - ws->reply;
389}
390
391#ifdef WANT_MODEST_FULLSCRAPES
392static pthread_mutex_t g_modest_fullscrape_mutex = PTHREAD_MUTEX_INITIALIZER;
393static ot_vector g_modest_fullscrape_timeouts;
394typedef struct {
395 ot_ip6 ip;
396 ot_time last_fullscrape;
397} ot_scrape_log;
398#endif
399
400#ifdef WANT_FULLSCRAPE
401static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) {
402 struct http_data *cookie = fdstate_cookie(sock);
403 int format = 0;
404
405#ifdef WANT_MODEST_FULLSCRAPES
406 {
407 ot_scrape_log this_peer, *new_peer;
408 int exactmatch;
409 memcpy(this_peer.ip, cookie->ip, sizeof(ot_ip6));
410 this_peer.last_fullscrape = time_now_seconds();
411 pthread_mutex_lock(&g_modest_fullscrape_mutex);
412 new_peer = vector_find_or_insert(&g_modest_fullscrape_timeouts, &this_peer, sizeof(ot_scrape_log), sizeof(ot_ip6), &exactmatch);
413 if (!new_peer) {
414 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
415 HTTPERROR_500;
416 }
417 if (exactmatch && (this_peer.last_fullscrape - new_peer->last_fullscrape) < OT_MODEST_PEER_TIMEOUT) {
418 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
419 HTTPERROR_402_NOTMODEST;
420 }
421 memcpy(new_peer, &this_peer, sizeof(ot_scrape_log));
422 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
423 }
424#endif
425
426
427#if defined(WANT_COMPRESSION_GZIP) || defined(WANT_COMPRESSION_ZSTD)
428 ws->request[ws->request_size - 1] = 0;
429#ifdef WANT_COMPRESSION_GZIP
430 if (strstr(ws->request, "gzip")) {
431 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
432 format |= TASK_FLAG_GZIP;
433 }
434#endif
435#ifdef WANT_COMPRESSION_ZSTD
436 if (strstr(ws->request, "zstd")) {
437 cookie->flag |= STRUCT_HTTP_FLAG_ZSTD;
438 format |= TASK_FLAG_ZSTD;
439 }
440#endif
441
442#if defined(WANT_COMPRESSION_ZSTD) && defined(WANT_COMPRESSION_ZSTD_ALWAYS)
443 cookie->flag |= STRUCT_HTTP_FLAG_ZSTD;
444 format |= TASK_FLAG_ZSTD;
445#endif
446
447#if defined(WANT_COMPRESSION_GZIP) && defined(WANT_COMPRESSION_GZIP_ALWAYS)
448 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
449 format |= TASK_FLAG_GZIP;
450#endif
451#endif
452
453 stats_issue_event(ws, EVENT_FULLSCRAPE_REQUEST, 0, (uintptr_t)cookie->ip);
454
455#ifdef _DEBUG_HTTPERROR
456 fprintf(stderr, "%s", ws->debugbuf);
457#endif
458
459 /* Pass this task to the worker thread */
460 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
461 /* Clients waiting for us should not easily timeout */
462 fdstate_set_expiry(sock, 0);
463 http_interest_set(sock, cookie, 0);
464
465 if (!http_adopt(sock))
466 HTTPERROR_500;
467 fullscrape_deliver(sock, format);
468 return ws->reply_size = -2;
469}
470#endif
471
472static int compare_hash(const void *left, const void *right) { return memcmp(left, right, sizeof(ot_hash)); }
473
474static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
475 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}};
476
477 ot_hash *multiscrape_buf = (ot_hash *)ws->request;
478 char *reply = ws->reply;
479 int scanon = 1;
480 size_t i, unique, numwant = 0;
481
482 /* This is to hack around stupid clients that send "scrape ?info_hash" */
483 if (read_ptr[-1] != '?') {
484 while ((*read_ptr != '?') && (*read_ptr != '\n'))
485 ++read_ptr;
486 if (*read_ptr == '\n')
487 HTTPERROR_400_PARAM;
488 ++read_ptr;
489 }
490
491 while (scanon) {
492 switch (urlencoded_match_keyword(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
493 case -2:
494 scanon = 0;
495 break; /* TERMINATOR */
496 default:
497 HTTPERROR_400_PARAM; /* PARSE ERROR */
498 case -3:
499 urlencoded_skip_value(&read_ptr);
500 break;
501 case 1: /* matched "info_hash" */
502 /* ignore this, when we have less than 20 bytes */
503 if (urlencoded_parse(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash))
504 HTTPERROR_400_PARAM;
505 break;
506 }
507 }
508
509 /* No info_hash found? Inform user */
510 if (!numwant)
511 HTTPERROR_400_PARAM;
512
513 /* Limit number of hashes to process */
514 if (numwant > OT_MAXMULTISCRAPE_COUNT)
515 numwant = OT_MAXMULTISCRAPE_COUNT;
516
517 /* A bencoded dictionary has its keys in order and names each one once, so
518 the answer cannot just follow the order the client asked in */
519 qsort(multiscrape_buf, numwant, sizeof(ot_hash), compare_hash);
520 for (i = unique = 1; i < numwant; ++i)
521 if (memcmp(multiscrape_buf[i], multiscrape_buf[unique - 1], sizeof(ot_hash)))
522 memcpy(multiscrape_buf[unique++], multiscrape_buf[i], sizeof(ot_hash));
523 numwant = unique;
524
525 /* Enough for http header + whole scrape string */
526 return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE);
527 ws->reply_size = reply - ws->reply;
528 stats_issue_event(ws, EVENT_SCRAPE, FLAG_TCP, ws->reply_size);
529 return ws->reply_size;
530}
531
532#ifdef WANT_LOG_NUMWANT
533unsigned long long numwants[201];
534#endif
535
536#if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY)
537static char *http_header(char *data, size_t byte_count, const char *header) {
538 size_t i;
539 long sl = strlen(header);
540 for (i = 0; i + sl + 2 < byte_count; ++i) {
541 if (data[i] != '\n' || data[i + sl + 1] != ':')
542 continue;
543 if (strncasecmp(data + i + 1, header, sl))
544 continue;
545 data += i + sl + 2;
546 while (*data == ' ' || *data == '\t')
547 ++data;
548 return data;
549 }
550 return 0;
551}
552#endif
553
554static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", 3}, {"numwant", 4}, {"compact", 5}, {"compact6", 5}, {"info_hash", 6},
555#ifdef WANT_IP_FROM_QUERY_STRING
556 {"ip", 7},
557#endif
558 {"peer_id", 8}, {NULL, -3}};
559static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}};
560
561/* For the replies that are one fixed string and nothing else, where the only
562 thing the caller wants back is how long it turned out */
563static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) {
564 char *reply = ws->reply;
565
566 fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text));
567 return reply - ws->reply;
568}
569
570static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
571 size_t numwant;
572 int64_t tmp;
573 int scanon;
574 unsigned short port = 0;
575 char *write_ptr;
576 ssize_t len;
577 struct http_data *cookie = fdstate_cookie(sock);
578
579 /* This is to hack around stupid clients that send "announce ?info_hash" */
580 if (read_ptr[-1] != '?') {
581 while ((*read_ptr != '?') && (*read_ptr != '\n'))
582 ++read_ptr;
583 if (*read_ptr == '\n')
584 HTTPERROR_400_PARAM;
585 ++read_ptr;
586 }
587
588#ifdef WANT_IP_FROM_PROXY
589 if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) {
590 ot_ip6 proxied_ip;
591 char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for");
592 if (fwd && ip_parse(fwd, proxied_ip)) {
593 OT_SETIP(ws->peer, proxied_ip);
594 } else
595 OT_SETIP(ws->peer, cookie->ip);
596 } else
597#endif
598 OT_SETIP(ws->peer, cookie->ip);
599
600 ws->peer_id = NULL;
601 ws->hash = NULL;
602
603 OT_SETPORT(ws->peer, &port);
604 OT_PEERFLAG(ws->peer) = 0;
605 numwant = 50;
606 scanon = 1;
607
608 while (scanon) {
609 switch (urlencoded_match_keyword(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
610 case -2:
611 scanon = 0;
612 break; /* TERMINATOR */
613 case -1:
614 HTTPERROR_400_PARAM; /* PARSE ERROR */
615 case -3:
616 urlencoded_skip_value(&read_ptr);
617 break;
618 case 1: /* matched "port" */
619 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
620 if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff))
621 HTTPERROR_400_PARAM;
622 port = htons((uint16_t)tmp);
623 OT_SETPORT(&ws->peer, &port);
624 break;
625 case 2: /* matched "left" */
626 if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
627 HTTPERROR_400_PARAM;
628 if (scan_int(write_ptr, len, &tmp))
629 tmp = 0;
630 if (!tmp)
631 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING;
632 break;
633 case 3: /* matched "event" */
634 switch (urlencoded_match_keyword(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) {
635 case -1:
636 HTTPERROR_400_PARAM;
637 case 1: /* matched "completed" */
638 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_COMPLETED;
639 break;
640 case 2: /* matched "stopped" */
641 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_STOPPED;
642 break;
643 default:
644 break;
645 }
646 break;
647 case 4: /* matched "numwant" */
648 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
649 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
650 HTTPERROR_400_PARAM;
651 if (tmp < 0)
652 tmp = 50;
653 if (tmp > 200)
654 tmp = 200;
655 numwant = (size_t)tmp;
656 break;
657 case 5: /* matched "compact" */
658 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
659 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
660 HTTPERROR_400_PARAM;
661 if (!tmp)
662 HTTPERROR_400_COMPACT;
663 break;
664 case 6: /* matched "info_hash" */
665 if (ws->hash)
666 HTTPERROR_400_DOUBLEHASH;
667 /* ignore this, when we have less than 20 bytes */
668 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
669 HTTPERROR_400_PARAM;
670 ws->hash = (ot_hash *)write_ptr;
671 break;
672#ifdef WANT_IP_FROM_QUERY_STRING
673 case 7: /* matched "ip" */
674 {
675 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16;
676 len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE);
677 tmp_buf2[len] = 0;
678 if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len)
679 HTTPERROR_400_PARAM;
680 OT_SETIP(&ws->peer, tmp_buf1);
681 } break;
682#endif
683 case 8: /* matched "peer_id" */
684 /* ignore this, when we have less than 20 bytes */
685 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
686 HTTPERROR_400_PARAM;
687 ws->peer_id = write_ptr;
688 break;
689 }
690 }
691
692#ifdef WANT_LOG_NUMWANT
693 numwants[numwant]++;
694#endif
695
696 /* XXX DEBUG
697 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply );
698 */
699
700 /* Scanned whole query string */
701 if (!ws->hash)
702 return ws->reply_size = reply_literal(ws, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e");
703
704 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED)
705 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws);
706 else
707 ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, numwant);
708
709 stats_issue_event(ws, EVENT_ANNOUNCE, FLAG_TCP, ws->reply_size);
710 return ws->reply_size;
711}
712
713ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
714 ssize_t reply_off, len;
715 char *read_ptr = ws->request, *write_ptr;
716
717#ifdef _DEBUG_HTTPERROR
718 reply_off = ws->request_size;
719 if (ws->request_size >= G_DEBUGBUF_SIZE)
720 reply_off = G_DEBUGBUF_SIZE - 1;
721 memcpy(ws->debugbuf, ws->request, reply_off);
722 ws->debugbuf[reply_off] = 0;
723#endif
724
725 /* Tell subroutines where to put reply data */
726 ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH;
727
728 /* Find out if the client wants to keep this connection alive, before any
729 subroutine gets to answer: an error reply finishes with the connection
730 on its way out, and ws outlives the connection it was serving */
731 ws->keep_alive = 0;
732#ifdef WANT_KEEPALIVE
733 if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k'))
734 ws->keep_alive = 1;
735#endif
736 {
737 struct http_data *connection = fdstate_cookie(sock);
738 if (connection) {
739 if (ws->keep_alive)
740 connection->flag |= STRUCT_HTTP_FLAG_KEEPALIVE;
741 else
742 connection->flag &= ~STRUCT_HTTP_FLAG_KEEPALIVE;
743 }
744 }
745
746 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
747 if (memcmp(read_ptr, "GET /", 5))
748 HTTPERROR_400;
749
750 /* Skip leading '/' */
751 for (read_ptr += 4; *read_ptr == '/'; ++read_ptr)
752 ;
753
754 /* Try to parse the request.
755 In reality we abandoned requiring the url to be correct. This now
756 only decodes url encoded characters, we check for announces and
757 scrapes by looking for "a*" or "sc" */
758 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_PATH);
759
760 /* If parsing returned an error, leave with not found */
761 if (g_redirecturl && (len == -2))
762 HTTPERROR_302;
763 if (len <= 0)
764 HTTPERROR_404;
765
766 /* This is the hardcore match for announce*/
767 if ((*write_ptr == 'a') || (*write_ptr == '?'))
768 http_handle_announce(sock, ws, read_ptr);
769#ifdef WANT_FULLSCRAPE
770 else if (!memcmp(write_ptr, "scrape HTTP/", 12))
771 http_handle_fullscrape(sock, ws);
772#endif
773 /* This is the hardcore match for scrape */
774 else if (!memcmp(write_ptr, "sc", 2))
775 http_handle_scrape(sock, ws, read_ptr);
776 /* All the rest is matched the standard way */
777 else if (len == g_stats_path_len && !memcmp(write_ptr, g_stats_path, len))
778 http_handle_stats(sock, ws, read_ptr);
779 else
780 HTTPERROR_404;
781
782 /* If routines handled sending themselves, just return */
783 if (ws->reply_size == -2)
784 return 0;
785 /* If routine failed, let http error take over */
786 if (ws->reply_size <= 0)
787 HTTPERROR_500;
788
789 /* This one is rather ugly, so I take you step by step through it.
790
791 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
792 write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string
793 plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
794 the space NOT needed to expand in reply_off
795 */
796 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size);
797 ws->reply = ws->outbuf + reply_off;
798
799 /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the
800 space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus
801 one byte '\n', we will copy over '\0' in next step */
802 ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off),
803 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size);
804
805 /* 3. Finally we join both blocks neatly */
806 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n';
807
808 http_senddata(sock, ws);
809 return ws->reply_size;
810}
diff --git a/src/ot_http.h b/src/ot_http.h
new file mode 100644
index 0000000..fb56c64
--- /dev/null
+++ b/src/ot_http.h
@@ -0,0 +1,53 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_HTTP_H__
5#define OT_HTTP_H__
6
7#include <stdint.h>
8#include <sys/types.h>
9#include <sys/uio.h>
10
11#include "ot_batch.h"
12#include "ot_event.h"
13#include "ot_buf.h"
14#include "trackerlogic.h"
15
16typedef enum {
17 STRUCT_HTTP_FLAG_WAITINGFORTASK = 1,
18 STRUCT_HTTP_FLAG_GZIP = 2,
19 STRUCT_HTTP_FLAG_BZIP2 = 4,
20 STRUCT_HTTP_FLAG_ZSTD = 8,
21 STRUCT_HTTP_FLAG_CHUNKED = 16,
22 STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32,
23 STRUCT_HTTP_FLAG_KEEPALIVE = 64,
24 STRUCT_HTTP_FLAG_ONSTACK = 128
25} STRUCT_HTTP_FLAG;
26
27struct http_data {
28 ot_buf request;
29 int interest; /* EVENT_IN and EVENT_OUT as asked for */
30 ot_batch *batch;
31 ot_ip6 ip;
32 STRUCT_HTTP_FLAG flag;
33};
34
35/* What this connection is waiting for: EVENT_IN, EVENT_OUT, or nothing */
36void http_interest_set(const int64_t s, struct http_data *cookie, int what);
37
38/* Close a connection and release everything that belongs to it */
39void http_close(const int64_t s);
40
41/* Promote a stack cookie to a heap one, for persistence and for handing it
42 over to the event loop or to another thread. Does nothing to one that is
43 already on the heap. Returns the connection's state, or NULL. */
44struct http_data *http_adopt(const int64_t s);
45
46ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws);
47ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial);
48ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code);
49
50extern const char *g_stats_path;
51extern ssize_t g_stats_path_len;
52
53#endif
diff --git a/src/ot_iovec.c b/src/ot_iovec.c
new file mode 100644
index 0000000..328f160
--- /dev/null
+++ b/src/ot_iovec.c
@@ -0,0 +1,87 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdlib.h>
6#include <sys/uio.h>
7
8/* Opentracker */
9#include "ot_iovec.h"
10
11void *iovec_new_chunk(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc) {
12 void *new_data;
13 size_t new_entries = 1 + *iovec_entries;
14 struct iovec *new_vec = realloc(*iovector, new_entries * sizeof(struct iovec));
15
16 if (!new_vec)
17 return NULL;
18
19 *iovector = new_vec;
20
21 /* Only allocate after we have a place to store the pointer */
22 new_data = malloc(new_alloc);
23 if (!new_data)
24 return NULL;
25
26 new_vec[new_entries - 1].iov_base = new_data;
27 new_vec[new_entries - 1].iov_len = new_alloc;
28
29 ++*iovec_entries;
30 return new_data;
31}
32
33void *iovec_append(size_t *iovec_entries, struct iovec **iovector, struct iovec *append_iovector) {
34 size_t new_entries = *iovec_entries + 1;
35 struct iovec *new_vec = realloc(*iovector, new_entries * sizeof(struct iovec));
36 if (!new_vec)
37 return NULL;
38
39 /* Take over data from appended iovec */
40 new_vec[*iovec_entries].iov_base = append_iovector->iov_base;
41 new_vec[*iovec_entries].iov_len = append_iovector->iov_len;
42
43 append_iovector->iov_base = NULL;
44 append_iovector->iov_len = 0;
45
46 *iovector = new_vec;
47 *iovec_entries = new_entries;
48
49 return new_vec;
50}
51
52void iovec_free(size_t *iovec_entries, struct iovec **iovector) {
53 size_t i;
54 for (i = 0; i < *iovec_entries; ++i)
55 free(((*iovector)[i]).iov_base);
56 free(*iovector);
57 *iovector = NULL;
58 *iovec_entries = 0;
59}
60
61void iovec_trim_last(size_t *iovec_entries, struct iovec **iovector, void *last_ptr) {
62 if (*iovec_entries) {
63 char *base = (char *)((*iovector)[*iovec_entries - 1]).iov_base;
64 size_t new_alloc = ((char *)last_ptr) - base;
65
66 ((*iovector)[*iovec_entries - 1]).iov_base = realloc(base, new_alloc);
67 ((*iovector)[*iovec_entries - 1]).iov_len = new_alloc;
68 }
69}
70
71void *iovec_next_chunk(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc) {
72 void *new_data;
73
74 iovec_trim_last(iovec_entries, iovector, last_ptr);
75
76 if (!(new_data = iovec_new_chunk(iovec_entries, iovector, new_alloc)))
77 iovec_free(iovec_entries, iovector);
78
79 return new_data;
80}
81
82size_t iovec_length(const size_t *iovec_entries, const struct iovec **iovector) {
83 size_t i, length = 0;
84 for (i = 0; i < *iovec_entries; ++i)
85 length += ((*iovector)[i]).iov_len;
86 return length;
87}
diff --git a/src/ot_iovec.h b/src/ot_iovec.h
new file mode 100644
index 0000000..4a8abbb
--- /dev/null
+++ b/src/ot_iovec.h
@@ -0,0 +1,18 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_IOVEC_H__
5#define OT_IOVEC_H__
6
7#include <sys/uio.h>
8
9void *iovec_new_chunk(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc);
10void *iovec_append(size_t *iovec_entries, struct iovec **iovector, struct iovec *append_iovector);
11void iovec_trim_last(size_t *iovec_entries, struct iovec **iovector, void *last_ptr);
12void iovec_free(size_t *iovec_entries, struct iovec **iovector);
13
14size_t iovec_length(const size_t *iovec_entries, const struct iovec **iovector);
15
16void *iovec_next_chunk(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc);
17
18#endif
diff --git a/src/ot_ip.c b/src/ot_ip.c
new file mode 100644
index 0000000..9c76290
--- /dev/null
+++ b/src/ot_ip.c
@@ -0,0 +1,199 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdint.h>
6#include <string.h>
7
8/* Opentracker */
9#include "ot_hex.h"
10#include "ot_ip.h"
11
12const char ip_v4mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
13
14/* The dotted quad is only ever reached through the v4 mapped form, so both
15 halves of it stay in here */
16
17static size_t ip4_format(char *dest, const uint8_t *ip) {
18 char *d = dest;
19 int i;
20
21 for (i = 0; i < 4; ++i) {
22 unsigned int octet = ip[i];
23
24 if (i)
25 *d++ = '.';
26 if (octet >= 100)
27 *d++ = (char)('0' + octet / 100);
28 if (octet >= 10)
29 *d++ = (char)('0' + (octet / 10) % 10);
30 *d++ = (char)('0' + octet % 10);
31 }
32
33 return (size_t)(d - dest);
34}
35
36static size_t ip4_scan(const char *src, uint8_t ip[4]) {
37 uint8_t octets[4];
38 const char *s = src;
39 int i;
40
41 for (i = 0; i < 4; ++i) {
42 unsigned int value = 0;
43 int digits = 0;
44
45 if (i && *s++ != '.')
46 return 0;
47
48 /* Leading zeros are tolerated, a value beyond one octet is not */
49 while (*s >= '0' && *s <= '9' && digits < 4) {
50 value = value * 10 + (unsigned int)(*s++ - '0');
51 ++digits;
52 }
53 if (!digits || value > 255)
54 return 0;
55
56 octets[i] = (uint8_t)value;
57 }
58
59 memcpy(ip, octets, sizeof(octets));
60 return (size_t)(s - src);
61}
62
63static unsigned int group_at(const char *ip, int n) { return ((unsigned int)(uint8_t)ip[2 * n] << 8) | (uint8_t)ip[2 * n + 1]; }
64
65size_t ip_format(char *dest, const ot_ip6 ip) {
66 static const char hexdigit[] = "0123456789abcdef";
67 char *d = dest;
68 int best_start = 0, best_len = 0, i = 0;
69
70 if (ip_is_v4mapped(ip))
71 return ip4_format(dest, (const uint8_t *)ip + sizeof(ip_v4mapped_prefix));
72
73 /* RFC 5952 4.2: the longest run of zero groups becomes "::", never a run
74 of one, and the first of two equal runs wins */
75 while (i < 8) {
76 int j = i;
77
78 if (group_at(ip, i)) {
79 ++i;
80 continue;
81 }
82 while (j < 8 && !group_at(ip, j))
83 ++j;
84 if (j - i > best_len) {
85 best_len = j - i;
86 best_start = i;
87 }
88 i = j;
89 }
90 if (best_len < 2)
91 best_len = 0;
92
93 for (i = 0; i < 8; ++i) {
94 unsigned int group;
95 int shift = 12;
96
97 if (best_len && i == best_start) {
98 *d++ = ':';
99 if (i + best_len == 8)
100 *d++ = ':'; /* nothing follows, so close the run */
101 i += best_len - 1;
102 continue;
103 }
104
105 if (i)
106 *d++ = ':';
107
108 group = group_at(ip, i);
109 while (shift && !(group >> shift))
110 shift -= 4; /* RFC 5952 4.1: no leading zeros */
111 for (;; shift -= 4) {
112 *d++ = hexdigit[(group >> shift) & 15];
113 if (!shift)
114 break;
115 }
116 }
117
118 return (size_t)(d - dest);
119}
120
121size_t ip_parse(const char *src, ot_ip6 ip) {
122 uint8_t prefix[16], suffix[16];
123 size_t prefixlen = 0, suffixlen = 0;
124 const char *s = src;
125 int have_gap = 0;
126 int need_colon = 0;
127
128 /* A plain IPv4 address is an address too, stored v4 mapped */
129 {
130 size_t consumed = ip4_scan(src, prefix);
131 if (consumed) {
132 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
133 memcpy(ip + sizeof(ip_v4mapped_prefix), prefix, 4);
134 return consumed;
135 }
136 }
137
138 for (;;) {
139 unsigned int value = 0;
140 int digits = 0, nibble;
141 uint8_t *into = have_gap ? suffix : prefix;
142 size_t *fill = have_gap ? &suffixlen : &prefixlen;
143
144 /* RFC 4291 2.2: one run of zero groups may be written "::" */
145 if (s[0] == ':' && s[1] == ':') {
146 if (have_gap)
147 return 0;
148 have_gap = 1;
149 need_colon = 0;
150 s += 2;
151 continue;
152 }
153
154 if (need_colon) {
155 if (*s != ':')
156 break;
157 ++s;
158 }
159
160 /* RFC 4291 2.2 form 3: the final two groups may be a dotted quad */
161 if (prefixlen + suffixlen + 4 <= 16) {
162 size_t consumed = ip4_scan(s, into + *fill);
163 if (consumed) {
164 *fill += 4;
165 s += consumed;
166 break;
167 }
168 }
169
170 while (digits < 4 && (nibble = hex_value(*s)) >= 0) {
171 value = value * 16 + (unsigned int)nibble;
172 ++digits;
173 ++s;
174 }
175 if (!digits) {
176 if (need_colon)
177 --s; /* leave the separator we ate for the caller */
178 break;
179 }
180
181 if (prefixlen + suffixlen + 2 > 16)
182 return 0;
183
184 into[(*fill)++] = (uint8_t)(value >> 8);
185 into[(*fill)++] = (uint8_t)value;
186 need_colon = 1;
187 }
188
189 if (!prefixlen && !suffixlen && !have_gap)
190 return 0;
191 if (!have_gap && prefixlen + suffixlen != 16)
192 return 0; /* without "::" every group has to be spelled out */
193
194 memset(ip, 0, 16);
195 memcpy(ip, prefix, prefixlen);
196 memcpy(ip + 16 - suffixlen, suffix, suffixlen);
197
198 return (size_t)(s - src);
199}
diff --git a/src/ot_ip.h b/src/ot_ip.h
new file mode 100644
index 0000000..533403e
--- /dev/null
+++ b/src/ot_ip.h
@@ -0,0 +1,31 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_IP_H__
5#define OT_IP_H__
6
7#include <stddef.h>
8#include <string.h>
9
10#include "trackerlogic.h"
11
12/* opentracker holds every address as sixteen bytes, writing IPv4 as the v4
13 mapped form of RFC 4291 section 2.5.5.2. */
14extern const char ip_v4mapped_prefix[12];
15
16static inline int ip_is_v4mapped(const void *ip) { return !memcmp(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); }
17
18/* Read an address and return how many characters it took, or zero if src
19 does not start with one. A plain IPv4 address is accepted and stored in
20 its v4 mapped form.
21
22 The count lets the caller carry on where the address ended, which is how
23 "address:port" and "address/bits" are read. */
24size_t ip_parse(const char *src, ot_ip6 ip);
25
26/* Write an address and return how many characters it took. Nothing is
27 terminated, so a caller may keep appending. A v4 mapped address is written
28 as the plain dotted quad rather than "::ffff:1.2.3.4". */
29size_t ip_format(char *dest, const ot_ip6 ip);
30
31#endif
diff --git a/src/ot_livesync.c b/src/ot_livesync.c
new file mode 100644
index 0000000..083fe1a
--- /dev/null
+++ b/src/ot_livesync.c
@@ -0,0 +1,241 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <arpa/inet.h>
6#include <pthread.h>
7#include <stdlib.h>
8#include <string.h>
9#include <sys/socket.h>
10#include <sys/types.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_accesslist.h"
15#include "ot_ip.h"
16#include "ot_socket.h"
17#include "ot_livesync.h"
18#include "ot_mutex.h"
19#include "ot_stats.h"
20#include "trackerlogic.h"
21
22#ifdef WANT_SYNC_LIVE
23
24ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5};
25
26#define LIVESYNC_INCOMING_BUFFSIZE (256 * 256)
27
28#define LIVESYNC_OUTGOING_BUFFSIZE_PEERS 1480
29#define LIVESYNC_OUTGOING_WATERMARK_PEERS (sizeof(ot_peer) + sizeof(ot_hash))
30
31#define LIVESYNC_MAXDELAY 15 /* seconds */
32
33enum { OT_SYNC_PEER4, OT_SYNC_PEER6 };
34
35/* Forward declaration */
36static void *livesync_worker(void *args);
37
38/* For incoming packets */
39static int g_socket_in = -1;
40
41/* For outgoing packets */
42static int g_socket_out = -1;
43
44static pthread_mutex_t g_outbuf_mutex = PTHREAD_MUTEX_INITIALIZER;
45typedef struct {
46 uint8_t data[LIVESYNC_OUTGOING_BUFFSIZE_PEERS];
47 size_t fill;
48 ot_time next_packet_time;
49} sync_buffer;
50
51static sync_buffer g_v6_buf;
52static sync_buffer g_v4_buf;
53
54static pthread_t thread_id;
55static int thread_started;
56static _Atomic int thread_left;
57void livesync_init(void) {
58 uint32_t packet_type6 = htonl(OT_SYNC_PEER6);
59 uint32_t packet_type4 = htonl(OT_SYNC_PEER4);
60
61 if (g_socket_in == -1)
62 exerr("No socket address for live sync specified.");
63
64 /* Prepare outgoing peers buffer */
65 memcpy(g_v6_buf.data, &g_tracker_id, sizeof(g_tracker_id));
66 memcpy(g_v4_buf.data, &g_tracker_id, sizeof(g_tracker_id));
67
68 memcpy(g_v6_buf.data + sizeof(g_tracker_id), &packet_type6, sizeof(packet_type6));
69 memcpy(g_v4_buf.data + sizeof(g_tracker_id), &packet_type4, sizeof(packet_type4));
70
71 g_v6_buf.fill = sizeof(g_tracker_id) + sizeof(uint32_t);
72 g_v4_buf.fill = sizeof(g_tracker_id) + sizeof(uint32_t);
73
74 g_v6_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY);
75 g_v4_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY);
76
77 thread_started = !pthread_create(&thread_id, NULL, livesync_worker, NULL);
78}
79
80void livesync_deinit(void) {
81 if (thread_started) {
82 /* Repeats so it interrupts a read already under way */
83 while (!atomic_load(&thread_left)) {
84 pthread_kill(thread_id, OT_SIGNAL_WAKE);
85 usleep(1000);
86 }
87 pthread_join(thread_id, NULL);
88 thread_started = 0;
89 }
90
91 if (g_socket_in != -1)
92 close(g_socket_in);
93 if (g_socket_out != -1)
94 close(g_socket_out);
95}
96
97void livesync_bind_mcast(ot_ip6 ip, uint16_t port) {
98 if (!ip_is_v4mapped(ip))
99 exerr("v6 mcast support not yet available.");
100
101 if (g_socket_in != -1)
102 exerr("Error: Livesync listen ip specified twice.");
103
104 if ((g_socket_in = socket_mcast_join(groupip_1, ip, port)) == -1)
105 exerr("Error: Cant bind live sync incoming socket.");
106
107 if ((g_socket_out = socket_mcast_bind(ip, port, 0)) == -1)
108 exerr("Error: Cant bind live sync outgoing socket.");
109}
110
111/* Caller MUST hold g_outbuf_mutex. Returns with g_outbuf_mutex unlocked */
112static void livesync_issue_peersync(sync_buffer *buf) {
113 char mycopy[LIVESYNC_OUTGOING_BUFFSIZE_PEERS];
114 size_t fill = buf->fill;
115
116 memcpy(mycopy, buf->data, fill);
117 buf->fill = sizeof(g_tracker_id) + sizeof(uint32_t);
118 buf->next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY);
119
120 /* From now this thread has a local copy of the buffer and
121 has modified the protected element */
122 pthread_mutex_unlock(&g_outbuf_mutex);
123
124 socket_mcast_send(g_socket_out, mycopy, fill, groupip_1, LIVESYNC_PORT);
125}
126
127static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size) {
128 size_t off = sizeof(g_tracker_id) + sizeof(uint32_t);
129
130 /* Prepare peer record for v4mapped expansion */
131 if (peer_size == OT_PEER_SIZE4)
132 memcpy(&ws->peer, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
133
134 /* Now basic sanity checks have been done on the live sync packet
135 We might add more testing and logging. */
136 while ((ssize_t)(off + sizeof(ot_hash) + peer_size) <= ws->request_size) {
137 char *peer_ip = ws->request + off + sizeof(ot_hash);
138 if (peer_size == OT_PEER_SIZE4)
139 memcpy(&ws->peer[0] + sizeof(ip_v4mapped_prefix), peer_ip, peer_size);
140 else
141 memcpy(&ws->peer, peer_ip, peer_size);
142 ws->hash = (ot_hash *)(ws->request + off);
143
144 if (!g_opentracker_running)
145 return;
146
147 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_STOPPED)
148 remove_peer_from_torrent(FLAG_MCA, ws);
149 else
150 add_peer_to_torrent_and_return_peers(FLAG_MCA, ws, /* amount = */ 0);
151
152 off += sizeof(ot_hash) + peer_size;
153 }
154
155 stats_issue_event(ws, EVENT_SYNC, 0, (ws->request_size - sizeof(g_tracker_id) - sizeof(uint32_t)) / ((ssize_t)sizeof(ot_hash) + peer_size));
156}
157
158/* Tickle the live sync module from time to time, so no events get
159 stuck when there's not enough traffic to fill udp packets fast
160 enough */
161void livesync_ticker(void) {
162 /* livesync_issue_peersync sets g_next_packet_time */
163 pthread_mutex_lock(&g_outbuf_mutex);
164 if (time_now_seconds() > g_v6_buf.next_packet_time && g_v6_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t))
165 livesync_issue_peersync(&g_v6_buf);
166 else
167 pthread_mutex_unlock(&g_outbuf_mutex);
168
169 pthread_mutex_lock(&g_outbuf_mutex);
170 if (time_now_seconds() > g_v4_buf.next_packet_time && g_v4_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t))
171 livesync_issue_peersync(&g_v4_buf);
172 else
173 pthread_mutex_unlock(&g_outbuf_mutex);
174}
175
176/* Inform live sync about whats going on. */
177void livesync_tell(struct ot_workstruct *ws) {
178 size_t peer_size; /* initialized in next line */
179 ot_peer *peer_src = peer_from_peer6(&ws->peer, &peer_size);
180 sync_buffer *dest_buf = peer_size == OT_PEER_SIZE6 ? &g_v6_buf : &g_v4_buf;
181
182 pthread_mutex_lock(&g_outbuf_mutex);
183
184 memcpy(dest_buf->data + dest_buf->fill, ws->hash, sizeof(ot_hash));
185 dest_buf->fill += sizeof(ot_hash);
186
187 memcpy(dest_buf->data + dest_buf->fill, peer_src, peer_size);
188 dest_buf->fill += peer_size;
189
190 if (dest_buf->fill >= LIVESYNC_OUTGOING_BUFFSIZE_PEERS - LIVESYNC_OUTGOING_WATERMARK_PEERS)
191 livesync_issue_peersync(dest_buf);
192 else
193 pthread_mutex_unlock(&g_outbuf_mutex);
194}
195
196static void *livesync_worker(void *args) {
197 struct ot_workstruct ws;
198 ot_ip6 in_ip;
199 uint16_t in_port;
200
201 (void)args;
202
203 /* Initialize our "thread local storage" */
204 ws.inbuf = ws.request = malloc(LIVESYNC_INCOMING_BUFFSIZE);
205 ws.outbuf = ws.reply = 0;
206
207 while (g_opentracker_running) {
208 uint32_t packet_type;
209
210 ws.request_size = socket_recv(g_socket_in, ws.inbuf, LIVESYNC_INCOMING_BUFFSIZE, in_ip, &in_port);
211
212 /* Expect at least tracker id and packet type */
213 if (ws.request_size <= (ssize_t)(sizeof(g_tracker_id) + sizeof(uint32_t)))
214 continue;
215 if (!accesslist_is_blessed(in_ip, OT_PERMISSION_MAY_LIVESYNC))
216 continue;
217 if (!memcmp(ws.inbuf, &g_tracker_id, sizeof(g_tracker_id))) {
218 /* TODO: log packet coming from ourselves */
219 continue;
220 }
221
222 memcpy(&packet_type, ws.inbuf + sizeof(g_tracker_id), sizeof(packet_type));
223
224 switch (ntohl(packet_type)) {
225 case OT_SYNC_PEER6:
226 livesync_handle_peersync(&ws, OT_PEER_SIZE6);
227 break;
228 case OT_SYNC_PEER4:
229 livesync_handle_peersync(&ws, OT_PEER_SIZE4);
230 break;
231 default:
232 break;
233 }
234 }
235
236 free(ws.inbuf);
237 atomic_store(&thread_left, 1);
238 return NULL;
239}
240
241#endif
diff --git a/src/ot_livesync.h b/src/ot_livesync.h
new file mode 100644
index 0000000..4bd3af0
--- /dev/null
+++ b/src/ot_livesync.h
@@ -0,0 +1,75 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_LIVESYNC_H__
5#define OT_LIVESYNC_H__
6
7#include "trackerlogic.h"
8
9/*
10 Syncing is done as udp packets in the multicast domain 224.0.42.5 port 9696
11
12 Each tracker should join the multicast group and send its live sync packets
13 to that group, using a ttl of 1
14
15 Format of all sync packets is straight forward, packet type determines
16 which kind of packet this is:
17
18 0x0000 0x04 id of tracker instance
19 0x0004 0x04 packet type
20
21 ########
22 ######## PEER SYNC PROTOCOL ########
23 ########
24
25 Each tracker instance accumulates announce requests until its buffer is
26 full or a timeout is reached. Then it broadcasts its live sync packer:
27
28 packet type SYNC_LIVE4
29 [ 0x0008 0x14 info_hash
30 0x001c 0x04 peer's ipv4 address
31 0x0020 0x02 peer's port
32 0x0024 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 )
33 ]*
34
35 packet type SYNC_LIVE6
36 [ 0x0008 0x14 info_hash
37 0x001c 0x10 peer's ipv6 address
38 0x002c 0x02 peer's port
39 0x002e 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 )
40 ]*
41*/
42
43#ifdef WANT_SYNC_LIVE
44
45#define LIVESYNC_PORT 9696
46
47void livesync_init(void);
48void livesync_deinit(void);
49
50/* Join multicast group for listening and create sending socket */
51void livesync_bind_mcast(ot_ip6 ip, uint16_t port);
52
53/* Inform live sync about whats going on. */
54void livesync_tell(struct ot_workstruct *ws);
55
56/* Tickle the live sync module from time to time, so no events get
57 stuck when there's not enough traffic to fill udp packets fast
58 enough */
59void livesync_ticker(void);
60
61/* Handle an incoming live sync packet */
62void handle_livesync(const int64_t sock);
63
64#else
65
66/* If no syncing is required, save calling code from #ifdef
67 constructions */
68#define livesync_deinit()
69#define livesync_init()
70#define livesync_ticker()
71#define handle_livesync(a)
72
73#endif
74
75#endif
diff --git a/src/ot_mutex.c b/src/ot_mutex.c
new file mode 100644
index 0000000..884b74c
--- /dev/null
+++ b/src/ot_mutex.c
@@ -0,0 +1,70 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <arpa/inet.h>
6#include <pthread.h>
7#include <stdatomic.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <unistd.h>
11#include <string.h>
12#include <sys/uio.h>
13
14
15/* Opentracker */
16#include "ot_iovec.h"
17#include "ot_mutex.h"
18#include "ot_stats.h"
19#include "trackerlogic.h"
20
21/* #define MTX_DBG( STRING ) fprintf( stderr, STRING ) */
22#define MTX_DBG(STRING)
23
24/* Our global all torrents list */
25static ot_vector all_torrents[OT_BUCKET_COUNT];
26static pthread_mutex_t bucket_mutex[OT_BUCKET_COUNT];
27/* Every bucket's unlock adjusts this, and a bucket lock only keeps other
28 threads out of that one bucket, so the counter needs its own protection */
29static _Atomic size_t g_torrent_count;
30
31ot_vector *mutex_bucket_lock(int bucket) {
32 pthread_mutex_lock(bucket_mutex + bucket);
33 return all_torrents + bucket;
34}
35
36/* Torrents are spread over buckets by the first four bytes of their info_hash,
37 which may sit at any alignment, hence the memcpy */
38static int bucket_from_hash(ot_hash const hash) {
39 uint32_t bucket;
40 memcpy(&bucket, hash, sizeof(bucket));
41 return ntohl(bucket) >> OT_BUCKET_COUNT_SHIFT;
42}
43
44ot_vector *mutex_bucket_lock_by_hash(ot_hash const hash) { return mutex_bucket_lock(bucket_from_hash(hash)); }
45
46void mutex_bucket_unlock(int bucket, int delta_torrentcount) {
47 pthread_mutex_unlock(bucket_mutex + bucket);
48 if (delta_torrentcount)
49 atomic_fetch_add_explicit(&g_torrent_count, (size_t)delta_torrentcount, memory_order_relaxed);
50}
51
52void mutex_bucket_unlock_by_hash(ot_hash const hash, int delta_torrentcount) {
53 mutex_bucket_unlock(bucket_from_hash(hash), delta_torrentcount);
54}
55
56size_t mutex_get_torrent_count(void) { return atomic_load_explicit(&g_torrent_count, memory_order_relaxed); }
57
58void mutex_init(void) {
59 size_t i;
60 for (i = 0; i < OT_BUCKET_COUNT; ++i)
61 pthread_mutex_init(bucket_mutex + i, NULL);
62 memset(all_torrents, 0, sizeof(all_torrents));
63}
64
65void mutex_deinit(void) {
66 size_t i;
67 for (i = 0; i < OT_BUCKET_COUNT; ++i)
68 pthread_mutex_destroy(bucket_mutex + i);
69 memset(all_torrents, 0, sizeof(all_torrents));
70}
diff --git a/src/ot_mutex.h b/src/ot_mutex.h
new file mode 100644
index 0000000..d7ded92
--- /dev/null
+++ b/src/ot_mutex.h
@@ -0,0 +1,21 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_MUTEX_H__
5#define OT_MUTEX_H__
6
7#include <sys/uio.h>
8#include "trackerlogic.h"
9
10void mutex_init(void);
11void mutex_deinit(void);
12
13ot_vector *mutex_bucket_lock(int bucket);
14ot_vector *mutex_bucket_lock_by_hash(ot_hash const hash);
15
16void mutex_bucket_unlock(int bucket, int delta_torrentcount);
17void mutex_bucket_unlock_by_hash(ot_hash const hash, int delta_torrentcount);
18
19size_t mutex_get_torrent_count(void);
20
21#endif
diff --git a/src/ot_random.c b/src/ot_random.c
new file mode 100644
index 0000000..43f91bb
--- /dev/null
+++ b/src/ot_random.c
@@ -0,0 +1,51 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdint.h>
6#include <stdlib.h>
7
8/* Opentracker */
9#include "ot_random.h"
10
11#ifdef WANT_INSECURE_RANDOM_FALLBACK
12
13#warning WANT_INSECURE_RANDOM_FALLBACK builds without arc4random. UDP connection ids become predictable and announces can be forged. Do not ship this.
14
15#include <time.h>
16
17void random_init(void) {
18#ifdef WANT_DEV_RANDOM
19 srandomdev();
20#else
21 srandom(time(NULL));
22#endif
23}
24
25/* random() yields 31 bits, so one draw can never set the top bit. Combine
26 two of them rather than hand out a value that is quietly half as wide as
27 its type claims. */
28uint32_t random_uint32(void) { return ((uint32_t)random() << 16) ^ (uint32_t)random(); }
29
30void random_bytes(void *buf, size_t len) {
31 uint8_t *out = buf;
32 /* random() leaves the top bit clear and its lowest bits are the weakest,
33 so take a byte from the middle of each draw */
34 while (len--)
35 *out++ = (uint8_t)(random() >> 8);
36}
37
38#else
39
40#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__DragonFly__) && !defined(__APPLE__) && \
41 !(defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 36)))
42#include <bsd/stdlib.h>
43#endif
44
45void random_init(void) {}
46
47uint32_t random_uint32(void) { return arc4random(); }
48
49void random_bytes(void *buf, size_t len) { arc4random_buf(buf, len); }
50
51#endif
diff --git a/src/ot_random.h b/src/ot_random.h
new file mode 100644
index 0000000..c2d0387
--- /dev/null
+++ b/src/ot_random.h
@@ -0,0 +1,17 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_RANDOM_H__
5#define OT_RANDOM_H__
6
7#include <stddef.h>
8#include <stdint.h>
9
10/* Seed whatever generator the build settled on. A no-op when backed by
11 arc4random, which seeds itself from the kernel. */
12void random_init(void);
13
14uint32_t random_uint32(void);
15void random_bytes(void *buf, size_t len);
16
17#endif
diff --git a/src/ot_rijndael.c b/src/ot_rijndael.c
new file mode 100644
index 0000000..3f36bde
--- /dev/null
+++ b/src/ot_rijndael.c
@@ -0,0 +1,488 @@
1/**
2 * rijndael-alg-fst.c
3 *
4 * @version 3.0 (December 2000)
5 *
6 * Optimised ANSI C code for the Rijndael cipher (now AES)
7 *
8 * @author Vincent Rijmen <vincent.rijmen@esat.kuleuven.ac.be>
9 * @author Antoon Bosselaers <antoon.bosselaers@esat.kuleuven.ac.be>
10 * @author Paulo Barreto <paulo.barreto@terra.com.br>
11 *
12 * This code is hereby placed in the public domain.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
15 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
21 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
24 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26#include "ot_rijndael.h"
27
28static const uint32_t Te0[256] = {
29 0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
30 0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
31 0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
32 0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
33 0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
34 0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
35 0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
36 0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
37 0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
38 0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
39 0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
40 0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
41 0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
42 0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
43 0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
44 0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
45 0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
46 0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
47 0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
48 0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
49 0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
50 0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
51 0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
52 0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
53 0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
54 0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
55 0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
56 0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
57 0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
58 0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
59 0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
60 0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
61 0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
62 0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
63 0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
64 0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
65 0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
66 0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
67 0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
68 0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
69 0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
70 0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
71 0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
72 0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
73 0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
74 0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
75 0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
76 0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
77 0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
78 0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
79 0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
80 0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
81 0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
82 0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
83 0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
84 0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
85 0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
86 0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
87 0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
88 0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
89 0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
90 0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
91 0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
92 0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
93};
94static const uint32_t Te1[256] = {
95 0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
96 0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
97 0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
98 0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
99 0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
100 0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
101 0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
102 0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
103 0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
104 0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
105 0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
106 0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
107 0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
108 0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
109 0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
110 0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
111 0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
112 0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
113 0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
114 0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
115 0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
116 0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
117 0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
118 0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
119 0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
120 0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
121 0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
122 0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
123 0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
124 0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
125 0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
126 0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
127 0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
128 0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
129 0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
130 0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
131 0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
132 0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
133 0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
134 0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
135 0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
136 0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
137 0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
138 0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
139 0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
140 0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
141 0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
142 0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
143 0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
144 0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
145 0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
146 0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
147 0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
148 0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
149 0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
150 0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
151 0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
152 0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
153 0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
154 0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
155 0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
156 0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
157 0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
158 0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
159};
160static const uint32_t Te2[256] = {
161 0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
162 0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
163 0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
164 0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
165 0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
166 0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
167 0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
168 0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
169 0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
170 0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
171 0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
172 0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
173 0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
174 0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
175 0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
176 0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
177 0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
178 0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
179 0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
180 0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
181 0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
182 0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
183 0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
184 0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
185 0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
186 0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
187 0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
188 0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
189 0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
190 0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
191 0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
192 0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
193 0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
194 0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
195 0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
196 0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
197 0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
198 0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
199 0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
200 0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
201 0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
202 0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
203 0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
204 0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
205 0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
206 0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
207 0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
208 0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
209 0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
210 0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
211 0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
212 0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
213 0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
214 0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
215 0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
216 0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
217 0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
218 0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
219 0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
220 0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
221 0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
222 0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
223 0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
224 0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
225};
226static const uint32_t Te3[256] = {
227
228 0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
229 0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
230 0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
231 0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
232 0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
233 0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
234 0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
235 0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
236 0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
237 0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
238 0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
239 0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
240 0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
241 0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
242 0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
243 0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
244 0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
245 0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
246 0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
247 0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
248 0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
249 0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
250 0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
251 0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
252 0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
253 0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
254 0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
255 0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
256 0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
257 0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
258 0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
259 0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
260 0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
261 0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
262 0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
263 0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
264 0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
265 0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
266 0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
267 0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
268 0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
269 0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
270 0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
271 0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
272 0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
273 0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
274 0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
275 0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
276 0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
277 0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
278 0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
279 0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
280 0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
281 0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
282 0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
283 0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
284 0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
285 0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
286 0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
287 0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
288 0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
289 0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
290 0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
291 0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
292};
293static const uint32_t Te4[256] = {
294 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
295 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
296 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
297 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
298 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
299 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
300 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
301 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
302 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
303 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
304 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
305 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
306 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
307 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
308 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
309 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
310 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
311 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
312 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
313 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
314 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
315 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
316 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
317 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
318 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
319 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
320 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
321 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
322 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
323 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
324 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
325 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
326 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
327 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
328 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
329 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
330 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
331 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
332 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
333 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
334 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
335 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
336 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
337 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
338 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
339 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
340 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
341 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
342 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
343 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
344 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
345 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
346 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
347 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
348 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
349 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
350 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
351 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
352 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
353 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
354 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
355 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
356 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
357 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
358};
359static const uint32_t rcon[] = {
360 0x01000000, 0x02000000, 0x04000000, 0x08000000,
361 0x10000000, 0x20000000, 0x40000000, 0x80000000,
362 0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
363};
364
365#define GETU32(pt) (((uint32_t)(pt)[0] << 24) ^ ((uint32_t)(pt)[1] << 16) ^ ((uint32_t)(pt)[2] << 8) ^ ((uint32_t)(pt)[3]))
366#define PUTU32(ct, st) { (ct)[0] = (uint8_t)((st) >> 24); (ct)[1] = (uint8_t)((st) >> 16); (ct)[2] = (uint8_t)((st) >> 8); (ct)[3] = (uint8_t)(st); }
367
368/**
369 * Expand the cipher key into the encryption key schedule.
370 *
371 * @return the number of rounds for the given cipher key size.
372 */
373int rijndaelKeySetupEnc128(uint32_t rk[44], const uint8_t cipherKey[] ) {
374 int i = 0;
375 uint32_t temp;
376
377 rk[0] = GETU32(cipherKey );
378 rk[1] = GETU32(cipherKey + 4);
379 rk[2] = GETU32(cipherKey + 8);
380 rk[3] = GETU32(cipherKey + 12);
381 for (;;) {
382 temp = rk[3];
383 rk[4] = rk[0] ^
384 (Te4[(temp >> 16) & 0xff] & 0xff000000) ^
385 (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^
386 (Te4[(temp ) & 0xff] & 0x0000ff00) ^
387 (Te4[(temp >> 24) ] & 0x000000ff) ^
388 rcon[i];
389 rk[5] = rk[1] ^ rk[4];
390 rk[6] = rk[2] ^ rk[5];
391 rk[7] = rk[3] ^ rk[6];
392 if (++i == 10) {
393 return 10;
394 }
395 rk += 4;
396 }
397}
398
399void rijndaelEncrypt128(const uint32_t rk[44], const uint8_t pt[16], uint8_t ct[16]) {
400 uint32_t s0, s1, s2, s3, t0, t1, t2, t3;
401
402 /*
403 * map byte array block to cipher state
404 * and add initial round key:
405 */
406 s0 = GETU32(pt ) ^ rk[0];
407 s1 = GETU32(pt + 4) ^ rk[1];
408 s2 = GETU32(pt + 8) ^ rk[2];
409 s3 = GETU32(pt + 12) ^ rk[3];
410 /* round 1: */
411 t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4];
412 t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5];
413 t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6];
414 t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7];
415 /* round 2: */
416 s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8];
417 s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9];
418 s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10];
419 s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11];
420 /* round 3: */
421 t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12];
422 t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13];
423 t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14];
424 t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15];
425 /* round 4: */
426 s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16];
427 s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17];
428 s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18];
429 s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19];
430 /* round 5: */
431 t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20];
432 t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21];
433 t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22];
434 t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23];
435 /* round 6: */
436 s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24];
437 s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25];
438 s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26];
439 s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27];
440 /* round 7: */
441 t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28];
442 t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29];
443 t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30];
444 t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31];
445 /* round 8: */
446 s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32];
447 s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33];
448 s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34];
449 s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35];
450 /* round 9: */
451 t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36];
452 t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37];
453 t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38];
454 t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39];
455
456 /*
457 * apply last round and
458 * map cipher state to byte array block:
459 */
460 s0 =
461 (Te4[(t0 >> 24) ] & 0xff000000) ^
462 (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^
463 (Te4[(t2 >> 8) & 0xff] & 0x0000ff00) ^
464 (Te4[(t3 ) & 0xff] & 0x000000ff) ^
465 rk[40];
466 PUTU32(ct , s0);
467 s1 =
468 (Te4[(t1 >> 24) ] & 0xff000000) ^
469 (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^
470 (Te4[(t3 >> 8) & 0xff] & 0x0000ff00) ^
471 (Te4[(t0 ) & 0xff] & 0x000000ff) ^
472 rk[41];
473 PUTU32(ct + 4, s1);
474 s2 =
475 (Te4[(t2 >> 24) ] & 0xff000000) ^
476 (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^
477 (Te4[(t0 >> 8) & 0xff] & 0x0000ff00) ^
478 (Te4[(t1 ) & 0xff] & 0x000000ff) ^
479 rk[42];
480 PUTU32(ct + 8, s2);
481 s3 =
482 (Te4[(t3 >> 24) ] & 0xff000000) ^
483 (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^
484 (Te4[(t1 >> 8) & 0xff] & 0x0000ff00) ^
485 (Te4[(t2 ) & 0xff] & 0x000000ff) ^
486 rk[43];
487 PUTU32(ct + 12, s3);
488}
diff --git a/src/ot_rijndael.h b/src/ot_rijndael.h
new file mode 100644
index 0000000..1235215
--- /dev/null
+++ b/src/ot_rijndael.h
@@ -0,0 +1,20 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 The rijndael implementation was taken from
5
6 http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-ref/rijndael-alg-fst.c
7
8 and modified to work with 128 bits (this is 10 rounds) only. */
9
10#ifndef OT_RIJNDAEL_H__
11#define OT_RIJNDAEL_H__
12
13#include <stdint.h>
14
15int rijndaelKeySetupEnc128(uint32_t rk[44], const uint8_t cipherKey[] );
16void rijndaelEncrypt128(const uint32_t rk[44], const uint8_t pt[16], uint8_t ct[16]);
17
18extern const char *g_version_rijndael_c;
19
20#endif
diff --git a/src/ot_scan.c b/src/ot_scan.c
new file mode 100644
index 0000000..3b8e87c
--- /dev/null
+++ b/src/ot_scan.c
@@ -0,0 +1,65 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <stdint.h>
7#include <stdlib.h>
8
9/* Opentracker */
10#include "ot_scan.h"
11
12size_t scan_uint(const char *src, uint64_t *dest, uint64_t max) {
13 unsigned long long value;
14 char *end;
15
16 if (*src < '0' || *src > '9')
17 return 0;
18
19 errno = 0;
20 value = strtoull(src, &end, 10);
21
22 if (errno == ERANGE || value > (unsigned long long)max)
23 return 0;
24
25 *dest = (uint64_t)value;
26 return (size_t)(end - src);
27}
28
29size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max) {
30 uint64_t value;
31 size_t consumed = scan_uint(src, &value, max);
32
33 /* bencode writes no leading zeros, so anything longer than a single digit
34 must not begin with one. */
35 if (consumed > 1 && *src == '0')
36 return 0;
37
38 if (consumed)
39 *dest = value;
40
41 return consumed;
42}
43
44int scan_int(const char *src, size_t len, int64_t *dest) {
45 int64_t value = 0;
46 int negate = 0;
47
48 if (len && *src == '-')
49 --len, ++src, negate = 1;
50 if (!len)
51 return -1;
52
53 while (len--) {
54 if (*src < '0' || *src > '9')
55 return -1;
56 /* A twenty character field of nines does not fit, and silently wrapping
57 would magically heal a nonsense announce */
58 if (value > (INT64_MAX - (*src - '0')) / 10)
59 return -1;
60 value = 10 * value + (*src++ - '0');
61 }
62
63 *dest = negate ? -value : value;
64 return 0;
65}
diff --git a/src/ot_scan.h b/src/ot_scan.h
new file mode 100644
index 0000000..a79ec13
--- /dev/null
+++ b/src/ot_scan.h
@@ -0,0 +1,25 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_SCAN_H__
5#define OT_SCAN_H__
6
7#include <stddef.h>
8#include <stddef.h>
9#include <stdint.h>
10
11/* Read an unsigned decimal number from src into dest and return how many
12 characters were consumed. Returns zero and leaves dest untouched when src
13 does not begin with a digit, or when the value would exceed max. */
14size_t scan_uint(const char *src, uint64_t *dest, uint64_t max);
15
16/* As scan_uint, but following the bencode integer grammar, which permits a
17 leading zero on zero alone. */
18size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max);
19
20/* Read a signed decimal number that has to take up exactly len characters,
21 which is how a value lifted out of a query string arrives. Returns 0, or
22 -1 leaving dest untouched. */
23int scan_int(const char *src, size_t len, int64_t *dest);
24
25#endif
diff --git a/src/ot_socket.c b/src/ot_socket.c
new file mode 100644
index 0000000..79bb01a
--- /dev/null
+++ b/src/ot_socket.c
@@ -0,0 +1,310 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#define _GNU_SOURCE /* accept4 on glibc */
6#include <errno.h>
7#include <fcntl.h>
8#include <netinet/in.h>
9#include <netinet/tcp.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14/* Opentracker */
15#include "ot_ip.h"
16#include "ot_socket.h"
17
18static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) {
19 memset(sa, 0, sizeof(*sa));
20 sa->sin6_family = AF_INET6;
21 sa->sin6_port = htons(port);
22 memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6));
23}
24
25/* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped
26 sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so
27 both forms have to be read. */
28static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) {
29 if (ss->ss_family == AF_INET) {
30 const struct sockaddr_in *sa = (const struct sockaddr_in *)ss;
31
32 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
33 memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4);
34 *port = ntohs(sa->sin_port);
35 } else {
36 const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss;
37
38 memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6));
39 *port = ntohs(sa->sin6_port);
40 }
41}
42
43int socket_set_blocking(int s, int blocking) {
44 int flags = fcntl(s, F_GETFL, 0);
45
46 if (flags == -1)
47 return -1;
48 return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK));
49}
50
51static int set_nonblocking(int s) { return socket_set_blocking(s, 0); }
52
53/* Close without letting the failure we are about to report be overwritten */
54static int close_and_fail(int s) {
55 int saved = errno;
56 close(s);
57 errno = saved;
58 return -1;
59}
60
61static int socket_v6(int type) {
62 int s = socket(PF_INET6, type, 0);
63 int zero = 0;
64
65 if (s == -1)
66 return -1;
67
68 /* A tracker that answered only one family would be half a tracker */
69 if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1)
70 return close_and_fail(s);
71
72 if (set_nonblocking(s) == -1)
73 return close_and_fail(s);
74
75 return s;
76}
77
78/* SO_REUSEPORT means three different things. Linux spreads datagrams over
79 the group. FreeBSD permits the bind and spreads nothing, which is what
80 SO_REUSEPORT_LB was added for. Darwin hands every datagram to whichever
81 socket bound last, so a group there starves all but one reader. */
82#if defined(SO_REUSEPORT_LB)
83#define OT_BALANCE_OPTION SO_REUSEPORT_LB
84#elif defined(SO_REUSEPORT) && !defined(__APPLE__)
85#define OT_BALANCE_OPTION SO_REUSEPORT
86#endif
87
88int socket_can_balance(void) {
89#ifdef OT_BALANCE_OPTION
90 return 1;
91#else
92 return 0;
93#endif
94}
95
96/* The only place in the tree that needs to know what a kernel calls this */
97static void socket_set_sharing(int s, socket_sharing sharing) {
98 int one = 1;
99
100 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
101#ifdef OT_BALANCE_OPTION
102 if (sharing == SOCKET_BALANCED) {
103 setsockopt(s, SOL_SOCKET, OT_BALANCE_OPTION, &one, sizeof(one));
104 return;
105 }
106#endif
107#ifdef SO_REUSEPORT
108 if (sharing != SOCKET_EXCLUSIVE)
109 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
110#endif
111}
112
113static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
114 struct sockaddr_in6 sa;
115
116 socket_set_sharing(s, sharing);
117
118 sockaddr_from_ip(&sa, ip, port);
119 return bind(s, (struct sockaddr *)&sa, sizeof(sa));
120}
121
122/* Defer the listening socket reporting it to be ready to accept until enough
123 sensible data is available for an accept+read pair. Where available. */
124int socket_defer_accept(int s) {
125#if defined(TCP_DEFER_ACCEPT)
126 int patience = OT_CLIENT_TIMEOUT;
127
128 return setsockopt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &patience, sizeof(patience));
129#elif defined(SO_ACCEPTFILTER)
130 struct accept_filter_arg filter;
131 const char name[sizeof(filter.af_name)] = "dataready";
132
133 memset(&filter, 0, sizeof(filter));
134 memcpy(filter.af_name, name, sizeof(name));
135 return setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &filter, sizeof(filter));
136#else
137 (void)s;
138 return -1;
139#endif
140}
141
142int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
143 int s = socket_v6(SOCK_STREAM);
144
145 if (s == -1)
146 return -1;
147 if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1)
148 return close_and_fail(s);
149
150 return s;
151}
152
153int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
154 int s = socket_v6(SOCK_DGRAM);
155
156 if (s == -1)
157 return -1;
158 if (socket_bind_v6(s, ip, port, sharing) == -1)
159 return close_and_fail(s);
160
161 return s;
162}
163
164int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) {
165 struct sockaddr_in6 sa;
166 int s = socket_v6(SOCK_STREAM);
167
168 if (s == -1)
169 return -1;
170 if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1)
171 return close_and_fail(s);
172
173 sockaddr_from_ip(&sa, dstip, dstport);
174 if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK)
175 return close_and_fail(s);
176
177 return s;
178}
179
180int socket_is_connected(int s) {
181 int error = 0;
182 socklen_t len = sizeof(error);
183
184 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
185 return 0;
186 if (error) {
187 errno = error;
188 return 0;
189 }
190 return 1;
191}
192
193/* Linux hands over a blocking socket whatever the listening socket was, the
194 BSDs and macOS inherit the listener's flags, so the accepted socket has to
195 be told either way. accept4 carries the flag itself, which saves the two
196 fcntl calls it otherwise takes. Darwin has neither accept4 nor the flag. */
197int socket_accept(int s, ot_ip6 ip, uint16_t *port) {
198 struct sockaddr_storage ss;
199 socklen_t len = sizeof(ss);
200 int fd;
201
202 memset(&ss, 0, sizeof(ss));
203#ifdef SOCK_NONBLOCK
204 if ((fd = accept4(s, (struct sockaddr *)&ss, &len, SOCK_NONBLOCK)) == -1)
205 return -1;
206#else
207 if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1)
208 return -1;
209 if (set_nonblocking(fd) == -1)
210 return close_and_fail(fd);
211#endif
212
213 ip_from_sockaddr(ip, port, &ss);
214 return fd;
215}
216
217ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) {
218 struct sockaddr_storage ss;
219 socklen_t sslen = sizeof(ss);
220 ssize_t received;
221
222 memset(&ss, 0, sizeof(ss));
223 received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen);
224 if (received == -1)
225 return -1;
226
227 ip_from_sockaddr(ip, port, &ss);
228 return received;
229}
230
231ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) {
232 struct sockaddr_in6 sa;
233
234 sockaddr_from_ip(&sa, ip, port);
235 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
236}
237
238#ifdef WANT_SYNC_LIVE
239static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) {
240 memset(sa, 0, sizeof(*sa));
241 sa->sin_family = AF_INET;
242 sa->sin_port = htons(port);
243 memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4);
244}
245
246/* A multicast group is meant to have several listeners, so these are always
247 shared */
248static int socket_v4_bind(const ot_ip6 ip, uint16_t port) {
249 struct sockaddr_in sa;
250 int s;
251
252 if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
253 return -1;
254
255 socket_set_sharing(s, SOCKET_SHARED);
256
257 sockaddr4_from_ip(&sa, ip, port);
258 if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
259 return close_and_fail(s);
260
261 return s;
262}
263
264int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) {
265 struct ip_mreq mreq;
266 ot_ip6 any;
267 int s;
268
269 /* The group is what is listened to, so the socket takes the wildcard and
270 the join names the interface to receive it on */
271 memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
272 memset(any + sizeof(ip_v4mapped_prefix), 0, 4);
273
274 if ((s = socket_v4_bind(any, port)) == -1)
275 return -1;
276
277 memset(&mreq, 0, sizeof(mreq));
278 memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4);
279 memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4);
280 if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
281 return close_and_fail(s);
282
283 return s;
284}
285
286int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) {
287 /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux
288 accepts either that or an int. */
289 unsigned char ttl = 1, loop = (unsigned char)(loopback != 0);
290 int s = socket_v4_bind(interface, port);
291
292 if (s == -1)
293 return -1;
294 if (set_nonblocking(s) == -1)
295 return close_and_fail(s);
296
297 if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 ||
298 setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
299 return close_and_fail(s);
300
301 return s;
302}
303
304ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) {
305 struct sockaddr_in sa;
306
307 sockaddr4_from_ip(&sa, group, port);
308 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
309}
310#endif
diff --git a/src/ot_socket.h b/src/ot_socket.h
new file mode 100644
index 0000000..5c1d840
--- /dev/null
+++ b/src/ot_socket.h
@@ -0,0 +1,56 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_SOCKET_H__
5#define OT_SOCKET_H__
6
7#include <stdint.h>
8#include <sys/types.h>
9
10#include "trackerlogic.h"
11
12/* Every socket opentracker opens is an AF_INET6 socket with IPV6_V6ONLY
13 cleared, so one descriptor serves both families and an IPv4 peer arrives
14 as the v4 mapped address */
15
16/* How an address may be shared with other sockets. Which socket option says
17 so differs between kernels, which is socket_set_sharing's business.
18
19 SHARED lets another socket bind the same address. BALANCED asks the kernel
20 to spread arriving datagrams over the sockets that did, which is a
21 different thing and not everywhere available: ask socket_can_balance
22 before binding a group that depends on it. */
23typedef enum { SOCKET_EXCLUSIVE, SOCKET_SHARED, SOCKET_BALANCED } socket_sharing;
24
25/* Whether this kernel spreads datagrams over a group of sockets bound to one
26 address. Where it does not, binding a group is worse than one socket with
27 several readers, so the caller wants to know first. */
28int socket_can_balance(void);
29
30/* Create, bind and, for TCP, listen. The returned socket is non blocking
31 Returns -1 on failure with errno set */
32int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing);
33int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing);
34
35int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport);
36int socket_is_connected(int s);
37int socket_accept(int s, ot_ip6 ip, uint16_t *port);
38int socket_set_blocking(int s, int blocking);
39int socket_defer_accept(int s);
40
41ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port);
42ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port);
43
44#ifdef WANT_SYNC_LIVE
45/*
46 socket_mcast_join takes the wildcard address and names the interface in the
47 join, and hands back a blocking socket, since the thread that reads it does
48 nothing else. socket_mcast_bind hands back a non blocking one, because the
49 thread that sends from it is also serving clients. Incoming datagrams are
50 read with socket_recv */
51int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port);
52int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback);
53ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port);
54#endif
55
56#endif
diff --git a/src/ot_stats.c b/src/ot_stats.c
new file mode 100644
index 0000000..2d6335b
--- /dev/null
+++ b/src/ot_stats.c
@@ -0,0 +1,866 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <pthread.h>
6#include <stdatomic.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/types.h>
11#include <sys/uio.h>
12#include <unistd.h>
13#ifdef WANT_SYSLOGS
14#include <syslog.h>
15#endif
16
17/* Opentracker */
18#include "ot_accesslist.h"
19#include "ot_event.h"
20#include "ot_hex.h"
21#include "ot_iovec.h"
22#include "ot_ip.h"
23#include "ot_mutex.h"
24#include "ot_workqueue.h"
25#include "ot_fmt.h"
26#include "ot_stats.h"
27#include "trackerlogic.h"
28
29#ifndef NO_FULLSCRAPE_LOGGING
30#define LOG_TO_STDERR(...) fprintf(stderr, __VA_ARGS__)
31#else
32#define LOG_TO_STDERR(...)
33#endif
34
35/* Forward declaration */
36static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode);
37#define OT_STATS_TMPSIZE 8192
38
39/* Each serving thread keeps its own statistics tally. The list is linked
40 through intrusive next pointers within the workstruct.
41
42 Registration happens once per thread and the adding up once per statistics
43 request, so this lock is never on anybody's hot path. */
44static struct ot_workstruct *g_counter_contexts;
45static ot_counter g_retired[OT_COUNTER_COUNT];
46static pthread_mutex_t g_counter_mutex = PTHREAD_MUTEX_INITIALIZER;
47
48void stats_counters_register(struct ot_workstruct *ws) {
49 pthread_mutex_lock(&g_counter_mutex);
50 ws->counters_next = g_counter_contexts;
51 g_counter_contexts = ws;
52 pthread_mutex_unlock(&g_counter_mutex);
53}
54
55void stats_counters_retire(struct ot_workstruct *ws) {
56 struct ot_workstruct **link;
57 size_t i;
58
59 pthread_mutex_lock(&g_counter_mutex);
60 for (link = &g_counter_contexts; *link; link = &(*link)->counters_next)
61 if (*link == ws) {
62 *link = ws->counters_next;
63 break;
64 }
65
66 /* When retiring a worker thread, collect the counter tally in the global */
67 for (i = 0; i < OT_COUNTER_COUNT; ++i)
68 atomic_fetch_add_explicit(g_retired + i, atomic_load_explicit(ws->counters + i, memory_order_relaxed), memory_order_relaxed);
69 pthread_mutex_unlock(&g_counter_mutex);
70}
71
72/* What every thread has counted so far, added together */
73static void counters_total(unsigned long long *total) {
74 struct ot_workstruct *ws;
75 size_t i;
76
77 pthread_mutex_lock(&g_counter_mutex);
78 for (i = 0; i < OT_COUNTER_COUNT; ++i)
79 total[i] = atomic_load_explicit(g_retired + i, memory_order_relaxed);
80 for (ws = g_counter_contexts; ws; ws = ws->counters_next)
81 for (i = 0; i < OT_COUNTER_COUNT; ++i)
82 total[i] += atomic_load_explicit(ws->counters + i, memory_order_relaxed);
83 pthread_mutex_unlock(&g_counter_mutex);
84}
85
86#define COUNT(index, by) atomic_fetch_add_explicit(ws->counters + (index), (unsigned long long)(by), memory_order_relaxed)
87
88static const char *ot_failed_request_names[] = {
89 "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest",
90 "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"};
91
92static time_t ot_start_time;
93
94#define STATS_NETWORK_NODE_BITWIDTH 4
95#define STATS_NETWORK_NODE_COUNT (1 << STATS_NETWORK_NODE_BITWIDTH)
96
97#define __BYTE(P, D) (((uint8_t *)P)[D / 8])
98#define __MSK (STATS_NETWORK_NODE_COUNT - 1)
99#define __SHFT(D) ((D ^ STATS_NETWORK_NODE_BITWIDTH) & STATS_NETWORK_NODE_BITWIDTH)
100
101#define __LDR(P, D) ((__BYTE((P), (D)) >> __SHFT((D))) & __MSK)
102#define __STR(P, D, V) __BYTE((P), (D)) = (__BYTE((P), (D)) & ~(__MSK << __SHFT((D)))) | ((V) << __SHFT((D)))
103
104#if 0
105// XXX
106#define STATS_NETWORK_NODE_MAXDEPTH (68 - STATS_NETWORK_NODE_BITWIDTH)
107#define STATS_NETWORK_NODE_LIMIT (48 - STATS_NETWORK_NODE_BITWIDTH)
108#endif
109#define STATS_NETWORK_NODE_MAXDEPTH (28 - STATS_NETWORK_NODE_BITWIDTH)
110#define STATS_NETWORK_NODE_LIMIT (24 - STATS_NETWORK_NODE_BITWIDTH)
111
112typedef union stats_network_node stats_network_node;
113union stats_network_node {
114 size_t counters[STATS_NETWORK_NODE_COUNT];
115 stats_network_node *children[STATS_NETWORK_NODE_COUNT];
116};
117
118static int stat_increase_network_count(stats_network_node **pnode, int depth, uintptr_t ip) {
119 int foo = __LDR(ip, depth);
120 stats_network_node *node;
121
122 if (!*pnode) {
123 *pnode = malloc(sizeof(stats_network_node));
124 if (!*pnode)
125 return -1;
126 memset(*pnode, 0, sizeof(stats_network_node));
127 }
128 node = *pnode;
129
130 if (depth < STATS_NETWORK_NODE_MAXDEPTH)
131 return stat_increase_network_count(node->children + foo, depth + STATS_NETWORK_NODE_BITWIDTH, ip);
132
133 node->counters[foo]++;
134 return 0;
135}
136
137static int stats_shift_down_network_count(stats_network_node **node, int depth, int shift) {
138 size_t i, rest = 0;
139
140 if (!*node)
141 return 0;
142
143 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
144 if (depth < STATS_NETWORK_NODE_MAXDEPTH)
145 rest += stats_shift_down_network_count((*node)->children + i, depth + STATS_NETWORK_NODE_BITWIDTH, shift);
146 else
147 rest += (*node)->counters[i] >>= shift;
148
149 if (!rest) {
150 free(*node);
151 *node = NULL;
152 }
153
154 return rest;
155}
156
157static size_t stats_get_highscore_networks(stats_network_node *node, int depth, ot_ip6 node_value, size_t *scores, ot_ip6 *networks, int network_count,
158 int limit) {
159 size_t i, score = 0;
160
161 if (!node)
162 return 0;
163
164 if (depth < limit) {
165 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
166 if (node->children[i]) {
167 __STR(node_value, depth, i);
168 score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit);
169 }
170 return score;
171 }
172
173 if (depth > limit && depth < STATS_NETWORK_NODE_MAXDEPTH) {
174 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
175 if (node->children[i])
176 score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit);
177 return score;
178 }
179
180 if (depth > limit && depth == STATS_NETWORK_NODE_MAXDEPTH) {
181 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i)
182 score += node->counters[i];
183 return score;
184 }
185
186 /* if( depth == limit ) */
187 for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) {
188 int j = 1;
189 size_t node_score;
190
191 if (depth == STATS_NETWORK_NODE_MAXDEPTH)
192 node_score = node->counters[i];
193 else
194 node_score = stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit);
195
196 score += node_score;
197
198 if (node_score <= scores[0])
199 continue;
200
201 __STR(node_value, depth, i);
202 while (j < network_count && node_score > scores[j])
203 ++j;
204 --j;
205
206 memmove(scores, scores + 1, j * sizeof(*scores));
207 memmove(networks, networks + 1, j * sizeof(*networks));
208 scores[j] = node_score;
209 memcpy(networks + j, node_value, sizeof(*networks));
210 }
211
212 return score;
213}
214
215static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) {
216 ot_ip6 networks[amount];
217 ot_ip6 node_value;
218 size_t scores[amount];
219 size_t i;
220
221 memset(scores, 0, sizeof(scores));
222 memset(networks, 0, sizeof(networks));
223 memset(node_value, 0, sizeof(node_value));
224
225 stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit);
226
227 fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH);
228 for (i = amount; i > 0; --i) {
229 if (scores[i - 1]) {
230 char ip_readable[64];
231
232 ip_readable[ip_format(ip_readable, networks[i - 1])] = 0;
233 fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable);
234 }
235 }
236 fmt_append(reply, reply_end, "\n");
237}
238
239static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) {
240 stats_network_node *slash24s_network_counters_root = NULL;
241 size_t i, bucket, peer_size = OT_PEER_SIZE4;
242
243 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
244 ot_vector *torrents_list = mutex_bucket_lock(bucket);
245 for (i = 0; i < torrents_list->size; ++i) {
246 ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[i]).peer_list4;
247 ot_vector *bucket_list = &peer_list->peers;
248 int num_buckets = 1;
249
250 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
251 num_buckets = bucket_list->size;
252 bucket_list = (ot_vector *)bucket_list->data;
253 }
254
255 while (num_buckets--) {
256 ot_peer *peers = (ot_peer *)bucket_list->data;
257 size_t numpeers = bucket_list->size;
258 while (numpeers--) {
259 if (stat_increase_network_count(&slash24s_network_counters_root, 0, (uintptr_t)(peers)))
260 goto bailout_unlock;
261 peers += peer_size;
262 }
263 ++bucket_list;
264 }
265 }
266 mutex_bucket_unlock(bucket, 0);
267 if (!g_opentracker_running)
268 goto done;
269 }
270
271 /* The tree is built. Now analyze */
272 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH);
273 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT);
274 goto done;
275
276bailout_unlock:
277 mutex_bucket_unlock(bucket, 0);
278done:
279 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1);
280}
281
282#ifdef WANT_SPOT_WOODPECKER
283static stats_network_node *stats_woodpeckers_tree;
284static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER;
285
286static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) {
287 pthread_mutex_lock(&g_woodpeckers_mutex);
288 stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH);
289 pthread_mutex_unlock(&g_woodpeckers_mutex);
290}
291#endif
292
293typedef struct {
294 unsigned long long torrent_count;
295 unsigned long long peer_count;
296 unsigned long long seed_count;
297} torrent_stats;
298
299static int torrent_statter(ot_torrent *torrent, uintptr_t data) {
300 torrent_stats *stats = (torrent_stats *)data;
301 stats->torrent_count++;
302 stats->peer_count += torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
303 stats->seed_count += torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
304 return 0;
305}
306
307/* Converter function from memory to human readable hex strings */
308typedef struct {
309 size_t val;
310 ot_hash hash;
311} ot_record;
312
313static inline void stats_insert_into_highscore(size_t val, ot_record* list, size_t amount, ot_hash* hash) {
314 size_t i;
315 for (i = 0; (i < amount) && (list[i].val > val); ++i)
316 {}
317 /* if we score better than a record in the list, we need to make room in the high
318 score array by moving lower scoring entries up */
319 if (i < amount) {
320 memmove(list + i + 1, list + i, (amount - i - 1) * sizeof(ot_record));
321
322 memcpy(&list[i].hash, hash, sizeof(ot_hash));
323 list[i].val = val;
324 }
325}
326
327
328/* Fetches stats from tracker */
329void stats_top_txt(char **reply, const char *reply_end, size_t amount) {
330 size_t j, idx, bucket;
331 ot_record top100s[100], top100c[100], top100l[100];
332 char hex_out[42];
333
334 if (amount > 100)
335 amount = 100;
336
337 memset(top100s, 0, sizeof(top100s));
338 memset(top100c, 0, sizeof(top100c));
339 memset(top100l, 0, sizeof(top100l));
340
341 /* Iterate over complete torrent list */
342 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
343 ot_vector *torrents_list = mutex_bucket_lock(bucket);
344 for (j = 0; j < torrents_list->size; ++j) {
345 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j;
346 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
347 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
348 size_t leech_count = peer_count - seed_count;
349
350 stats_insert_into_highscore(peer_count, top100c, amount, &torrent->hash);
351 stats_insert_into_highscore(seed_count, top100s, amount, &torrent->hash);
352 stats_insert_into_highscore(leech_count, top100l, amount, &torrent->hash);
353 }
354 mutex_bucket_unlock(bucket, 0);
355 if (!g_opentracker_running)
356 return;
357 }
358
359 fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount);
360 for (idx = 0; idx < amount; ++idx)
361 if (top100c[idx].val)
362 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash));
363 fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount);
364 for (idx = 0; idx < amount; ++idx)
365 if (top100s[idx].val)
366 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash));
367 fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount);
368 for (idx = 0; idx < amount; ++idx)
369 if (top100l[idx].val)
370 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash));
371}
372
373static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); }
374
375static void stats_connections_mrtg(char **reply, const char *reply_end) {
376 ot_time t = time(NULL) - ot_start_time;
377 unsigned long long total[OT_COUNTER_COUNT];
378
379 counters_total(total);
380
381 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.",
382 total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS],
383 total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600),
384 events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t),
385 events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t));
386}
387
388static void stats_udpconnections_mrtg(char **reply, const char *reply_end) {
389 ot_time t = time(NULL) - ot_start_time;
390 unsigned long long total[OT_COUNTER_COUNT];
391
392 counters_total(total);
393
394 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", total[OT_COUNTER_UDP_CONNECTIONS],
395 total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600),
396 events_per_time(total[OT_COUNTER_UDP_CONNECTIONS], t),
397 events_per_time(total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t));
398}
399
400static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) {
401 time_t t = time(NULL) - ot_start_time;
402 unsigned long long total[OT_COUNTER_COUNT];
403
404 counters_total(total);
405
406 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", total[OT_COUNTER_TCP_CONNECTIONS],
407 total[OT_COUNTER_TCP_ANNOUNCES], (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS], t),
408 events_per_time(total[OT_COUNTER_TCP_ANNOUNCES], t));
409}
410
411static void stats_scrape_mrtg(char **reply, const char *reply_end) {
412 time_t t = time(NULL) - ot_start_time;
413 unsigned long long total[OT_COUNTER_COUNT];
414
415 counters_total(total);
416
417 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)",
418 total[OT_COUNTER_TCP_SCRAPES], total[OT_COUNTER_UDP_SCRAPES], (int)t, (int)(t / 3600),
419 events_per_time((total[OT_COUNTER_TCP_SCRAPES] + total[OT_COUNTER_UDP_SCRAPES]), t));
420}
421
422static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) {
423 ot_time t = time(NULL) - ot_start_time;
424 unsigned long long total[OT_COUNTER_COUNT];
425
426 counters_total(total);
427
428 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.",
429 total[OT_COUNTER_FULLSCRAPES] * 1000, total[OT_COUNTER_FULLSCRAPE_BYTES], (int)t, (int)(t / 3600),
430 events_per_time(total[OT_COUNTER_FULLSCRAPES], t),
431 events_per_time(total[OT_COUNTER_FULLSCRAPE_BYTES], t));
432}
433
434static void stats_peers_mrtg(char **reply, const char *reply_end) {
435 torrent_stats stats = {0, 0, 0};
436
437 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
438
439 fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count,
440 stats.torrent_count);
441}
442
443static void stats_torrents_mrtg(char **reply, const char *reply_end) {
444 size_t torrent_count = mutex_get_torrent_count();
445
446 fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count);
447}
448
449static void stats_httperrors_txt(char **reply, const char *reply_end) {
450 unsigned long long total[OT_COUNTER_COUNT];
451
452 counters_total(total);
453
454 fmt_append(reply, reply_end, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n",
455 total[OT_COUNTER_FAILED + 0], total[OT_COUNTER_FAILED + 1], total[OT_COUNTER_FAILED + 2], total[OT_COUNTER_FAILED + 3],
456 total[OT_COUNTER_FAILED + 4], total[OT_COUNTER_FAILED + 5], total[OT_COUNTER_FAILED + 6]);
457}
458
459static void stats_return_renew_bucket(char **reply, const char *reply_end) {
460 unsigned long long total[OT_COUNTER_COUNT];
461
462 counters_total(total);
463
464 int i;
465
466 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
467 fmt_append(reply, reply_end, "%02i %llu\n", i, total[OT_COUNTER_RENEWED + i]);
468}
469
470static void stats_return_sync_mrtg(char **reply, const char *reply_end) {
471 ot_time t = time(NULL) - ot_start_time;
472 unsigned long long total[OT_COUNTER_COUNT];
473
474 counters_total(total);
475
476 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", total[OT_COUNTER_SYNCS], 0LL,
477 (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t),
478 events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t));
479}
480
481static void stats_return_completed_mrtg(char **reply, const char *reply_end) {
482 ot_time t = time(NULL) - ot_start_time;
483 unsigned long long total[OT_COUNTER_COUNT];
484
485 counters_total(total);
486
487
488 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", total[OT_COUNTER_COMPLETED], 0LL, (int)t, (int)(t / 3600),
489 events_per_time(total[OT_COUNTER_COMPLETED], t / 3600));
490}
491
492#ifdef WANT_LOG_NUMWANT
493extern unsigned long long numwants[201];
494static void stats_return_numwants(char **reply, const char *reply_end) {
495 int i;
496 for (i = 0; i <= 200; ++i)
497 fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]);
498}
499#endif
500
501static void stats_return_everything(char **reply, const char *reply_end) {
502 unsigned long long total[OT_COUNTER_COUNT];
503
504 counters_total(total);
505
506 torrent_stats stats = {0, 0, 0};
507 int i;
508
509 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
510
511 fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
512 fmt_append(reply, reply_end, "<stats>\n");
513 fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time));
514 fmt_append(reply, reply_end, " <torrents>\n");
515 fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count());
516 fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count);
517 fmt_append(reply, reply_end, " </torrents>\n");
518 fmt_append(reply, reply_end, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count);
519 fmt_append(reply, reply_end, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count);
520 fmt_append(reply, reply_end, " <completed>\n <count>%llu</count>\n </completed>\n", total[OT_COUNTER_COMPLETED]);
521 fmt_append(reply, reply_end, " <connections>\n");
522 fmt_append(reply, reply_end, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n",
523 total[OT_COUNTER_TCP_CONNECTIONS], total[OT_COUNTER_TCP_ANNOUNCES], total[OT_COUNTER_TCP_SCRAPES]);
524 fmt_append(reply, reply_end, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n",
525 total[OT_COUNTER_UDP_CONNECTIONS], total[OT_COUNTER_UDP_CONNECTS], total[OT_COUNTER_UDP_ANNOUNCES], total[OT_COUNTER_UDP_SCRAPES],
526 total[OT_COUNTER_UDP_CONNID_MISSMATCH]);
527 fmt_append(reply, reply_end, " <livesync>\n <count>%llu</count>\n </livesync>\n", total[OT_COUNTER_SYNCS]);
528 fmt_append(reply, reply_end, " </connections>\n");
529 fmt_append(reply, reply_end, " <debug>\n");
530 fmt_append(reply, reply_end, " <renew>\n");
531 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
532 fmt_append(reply, reply_end, " <count interval=\"%02i\">%llu</count>\n", i, total[OT_COUNTER_RENEWED + i]);
533 fmt_append(reply, reply_end, " </renew>\n");
534 fmt_append(reply, reply_end, " <http_error>\n");
535 for (i = 0; i < CODE_HTTPERROR_COUNT; ++i)
536 fmt_append(reply, reply_end, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], total[OT_COUNTER_FAILED + i]);
537 fmt_append(reply, reply_end, " </http_error>\n");
538 fmt_append(reply, reply_end, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", total[OT_COUNTER_STALLS]);
539 fmt_append(reply, reply_end, " </debug>\n");
540 fmt_append(reply, reply_end, "</stats>");
541}
542
543/* The names as the Makefile spells them, so that what a binary reports maps
544 straight back onto the FEATURES lines it was built from */
545static const char *const build_features[] = {
546#ifdef WANT_V4_ONLY
547 "WANT_V4_ONLY",
548#endif
549#ifdef WANT_ACCESSLIST_BLACK
550 "WANT_ACCESSLIST_BLACK",
551#endif
552#ifdef WANT_ACCESSLIST_WHITE
553 "WANT_ACCESSLIST_WHITE",
554#endif
555#ifdef WANT_DYNAMIC_ACCESSLIST
556 "WANT_DYNAMIC_ACCESSLIST",
557#endif
558#ifdef WANT_SYNC_LIVE
559 "WANT_SYNC_LIVE",
560#endif
561#ifdef WANT_IP_FROM_QUERY_STRING
562 "WANT_IP_FROM_QUERY_STRING",
563#endif
564#ifdef WANT_IP_FROM_PROXY
565 "WANT_IP_FROM_PROXY",
566#endif
567#ifdef WANT_COMPRESSION_GZIP
568 "WANT_COMPRESSION_GZIP",
569#endif
570#ifdef WANT_COMPRESSION_GZIP_ALWAYS
571 "WANT_COMPRESSION_GZIP_ALWAYS",
572#endif
573#ifdef WANT_COMPRESSION_ZSTD
574 "WANT_COMPRESSION_ZSTD",
575#endif
576#ifdef WANT_COMPRESSION_ZSTD_ALWAYS
577 "WANT_COMPRESSION_ZSTD_ALWAYS",
578#endif
579#ifdef WANT_FULLSCRAPE
580 "WANT_FULLSCRAPE",
581#endif
582#ifdef WANT_MODEST_FULLSCRAPES
583 "WANT_MODEST_FULLSCRAPES",
584#endif
585#ifdef WANT_KEEPALIVE
586 "WANT_KEEPALIVE",
587#endif
588#ifdef WANT_RESTRICT_STATS
589 "WANT_RESTRICT_STATS",
590#endif
591#ifdef WANT_LOG_NUMWANT
592 "WANT_LOG_NUMWANT",
593#endif
594#ifdef WANT_SPOT_WOODPECKER
595 "WANT_SPOT_WOODPECKER",
596#endif
597#ifdef WANT_SYSLOGS
598 "WANT_SYSLOGS",
599#endif
600#ifdef WANT_DEV_RANDOM
601 "WANT_DEV_RANDOM",
602#endif
603#ifdef WANT_INSECURE_RANDOM_FALLBACK
604 "WANT_INSECURE_RANDOM_FALLBACK",
605#endif
606#ifdef _DEBUG
607 "_DEBUG",
608#endif
609#ifdef _DEBUG_HTTPERROR
610 "_DEBUG_HTTPERROR",
611#endif
612#ifdef _DEBUG_RANDOMTORRENTS
613 "_DEBUG_RANDOMTORRENTS",
614#endif
615 NULL};
616
617void stats_return_tracker_features(char **reply, const char *reply_end) {
618 size_t i;
619
620 fmt_append(reply, reply_end, "events: %s\n", event_mechanism());
621 fmt_append(reply, reply_end, "features:");
622 for (i = 0; build_features[i]; ++i)
623 fmt_append(reply, reply_end, " %s", build_features[i]);
624 if (!build_features[0])
625 fmt_append(reply, reply_end, " none");
626 fmt_append(reply, reply_end, "\n");
627}
628
629void stats_return_tracker_version(char **reply, const char *reply_end) {
630 fmt_append(reply, reply_end, "opentracker %s", OT_VERSION);
631 if (*GIT_VERSION && strcmp(GIT_VERSION, OT_TAG))
632 fmt_append(reply, reply_end, " (%s)", GIT_VERSION);
633 fmt_append(reply, reply_end, "\n");
634 if (*OT_TAG)
635 fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/tag/?h=%s\n", OT_TAG);
636}
637
638void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) {
639 (void)format;
640 switch (mode & TASK_TASK_MASK) {
641 case TASK_STATS_CONNS:
642 stats_connections_mrtg(reply, reply_end);
643 break;
644 case TASK_STATS_SCRAPE:
645 stats_scrape_mrtg(reply, reply_end);
646 break;
647 case TASK_STATS_UDP:
648 stats_udpconnections_mrtg(reply, reply_end);
649 break;
650 case TASK_STATS_TCP:
651 stats_tcpconnections_mrtg(reply, reply_end);
652 break;
653 case TASK_STATS_FULLSCRAPE:
654 stats_fullscrapes_mrtg(reply, reply_end);
655 break;
656 case TASK_STATS_COMPLETED:
657 stats_return_completed_mrtg(reply, reply_end);
658 break;
659 case TASK_STATS_HTTPERRORS:
660 stats_httperrors_txt(reply, reply_end);
661 break;
662 case TASK_STATS_RENEW:
663 stats_return_renew_bucket(reply, reply_end);
664 break;
665 case TASK_STATS_SYNCS:
666 stats_return_sync_mrtg(reply, reply_end);
667 break;
668#ifdef WANT_LOG_NUMWANT
669 case TASK_STATS_NUMWANTS:
670 stats_return_numwants(reply, reply_end);
671 break;
672#endif
673 default:
674 break;
675 }
676}
677
678static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) {
679 char *r;
680 const char *r_end;
681
682 *iovec_entries = 0;
683 *iovector = NULL;
684 if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE)))
685 return;
686 r_end = r + OT_STATS_TMPSIZE;
687
688 switch (mode & TASK_TASK_MASK) {
689 case TASK_STATS_TORRENTS:
690 stats_torrents_mrtg(&r, r_end);
691 break;
692 case TASK_STATS_PEERS:
693 stats_peers_mrtg(&r, r_end);
694 break;
695 case TASK_STATS_SLASH24S:
696 stats_slash24s_txt(&r, r_end, 128);
697 break;
698 case TASK_STATS_TOP10:
699 stats_top_txt(&r, r_end, 10);
700 break;
701 case TASK_STATS_TOP100:
702 r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE);
703 if (!r)
704 return;
705 r_end = r + 4 * OT_STATS_TMPSIZE;
706 stats_top_txt(&r, r_end, 100);
707 break;
708 case TASK_STATS_EVERYTHING:
709 r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT);
710 if (!r)
711 return;
712 r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT;
713 stats_return_everything(&r, r_end);
714 break;
715#ifdef WANT_SPOT_WOODPECKER
716 case TASK_STATS_WOODPECKERS:
717 stats_return_woodpeckers(&r, r_end, 128);
718 break;
719#endif
720 default:
721 iovec_free(iovec_entries, iovector);
722 return;
723 }
724 iovec_trim_last(iovec_entries, iovector, r);
725}
726
727void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) {
728 if (!ws)
729 return;
730
731 switch (event) {
732 case EVENT_ACCEPT:
733 if (proto == FLAG_TCP)
734 COUNT(OT_COUNTER_TCP_CONNECTIONS, 1);
735 else
736 COUNT(OT_COUNTER_UDP_CONNECTIONS, 1);
737 break;
738 case EVENT_ANNOUNCE:
739 if (proto == FLAG_TCP)
740 COUNT(OT_COUNTER_TCP_ANNOUNCES, 1);
741 else
742 COUNT(OT_COUNTER_UDP_ANNOUNCES, 1);
743 break;
744 case EVENT_CONNECT:
745 if (proto == FLAG_TCP)
746 COUNT(OT_COUNTER_TCP_CONNECTS, 1);
747 else
748 COUNT(OT_COUNTER_UDP_CONNECTS, 1);
749 break;
750 case EVENT_COMPLETED:
751#ifdef WANT_SYSLOGS
752 {
753 char timestring[64];
754 char hash_hex[42], peerid_hex[42], ip_readable[64];
755 struct tm time_now;
756 time_t ttt;
757
758 time(&ttt);
759 localtime_r(&ttt, &time_now);
760 strftime(timestring, sizeof(timestring), "%FT%T%z", &time_now);
761
762 hex_from_hash(hash_hex, *ws->hash);
763 if (ws->peer_id)
764 hex_from_hash(peerid_hex, (uint8_t *)ws->peer_id);
765 else {
766 *peerid_hex = 0;
767 }
768
769 ip_readable[ip_format(ip_readable, (char *)&ws->peer)] = 0;
770 syslog(LOG_INFO, "time=%s event=completed info_hash=%s peer_id=%s ip=%s", timestring, hash_hex, peerid_hex, ip_readable);
771 }
772#endif
773 COUNT(OT_COUNTER_COMPLETED, 1);
774 break;
775 case EVENT_SCRAPE:
776 if (proto == FLAG_TCP)
777 COUNT(OT_COUNTER_TCP_SCRAPES, 1);
778 else
779 COUNT(OT_COUNTER_UDP_SCRAPES, 1);
780 break;
781 case EVENT_FULLSCRAPE:
782 COUNT(OT_COUNTER_FULLSCRAPES, 1);
783 COUNT(OT_COUNTER_FULLSCRAPE_BYTES, event_data);
784 break;
785 case EVENT_FULLSCRAPE_REQUEST: {
786 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */
787 char _debug[512];
788 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60);
789 off += ip_format(_debug + off, *ip);
790 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n");
791 (void)write(2, _debug, off);
792 COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1);
793 } break;
794 case EVENT_FULLSCRAPE_REQUEST_GZIP: {
795 ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */
796 char _debug[512];
797 int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60);
798 off += ip_format(_debug + off, *ip);
799 off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n");
800 (void)write(2, _debug, off);
801 COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1);
802 } break;
803 case EVENT_FAILED:
804 COUNT(OT_COUNTER_FAILED + event_data, 1);
805 break;
806 case EVENT_RENEW:
807 COUNT(OT_COUNTER_RENEWED + event_data, 1);
808 break;
809 case EVENT_SYNC:
810 COUNT(OT_COUNTER_SYNCS, event_data);
811 break;
812 case EVENT_BUCKET_LOCKED:
813 COUNT(OT_COUNTER_STALLS, 1);
814 break;
815#ifdef WANT_SPOT_WOODPECKER
816 case EVENT_WOODPECKER:
817 pthread_mutex_lock(&g_woodpeckers_mutex);
818 stat_increase_network_count(&stats_woodpeckers_tree, 0, event_data);
819 pthread_mutex_unlock(&g_woodpeckers_mutex);
820 break;
821#endif
822 case EVENT_CONNID_MISSMATCH:
823 COUNT(OT_COUNTER_UDP_CONNID_MISSMATCH, 1);
824 default:
825 break;
826 }
827}
828
829void stats_cleanup(void) {
830#ifdef WANT_SPOT_WOODPECKER
831 pthread_mutex_lock(&g_woodpeckers_mutex);
832 stats_shift_down_network_count(&stats_woodpeckers_tree, 0, 1);
833 pthread_mutex_unlock(&g_woodpeckers_mutex);
834#endif
835}
836
837static void *stats_worker(void *args) {
838 size_t iovec_entries;
839 struct iovec *iovector;
840
841 (void)args;
842
843 while (g_opentracker_running) {
844 ot_tasktype tasktype = TASK_STATS;
845 ot_taskid taskid = workqueue_poptask(&tasktype);
846 if (!taskid)
847 break;
848 stats_make(&iovec_entries, &iovector, tasktype);
849 if (workqueue_pushresult(taskid, iovec_entries, iovector))
850 iovec_free(&iovec_entries, &iovector);
851 }
852 return NULL;
853}
854
855void stats_deliver(int64_t sock, int tasktype) { workqueue_pushtask(sock, tasktype); }
856
857static pthread_t thread_id;
858void stats_init(void) {
859 ot_start_time = time_now_seconds();
860 pthread_create(&thread_id, NULL, stats_worker, NULL);
861}
862
863void stats_deinit(void) {
864 workqueue_wakeall();
865 pthread_join(thread_id, NULL);
866}
diff --git a/src/ot_stats.h b/src/ot_stats.h
new file mode 100644
index 0000000..45a547e
--- /dev/null
+++ b/src/ot_stats.h
@@ -0,0 +1,40 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_STATS_H__
5#define OT_STATS_H__
6
7#include "trackerlogic.h"
8
9typedef enum {
10 EVENT_ACCEPT,
11 EVENT_READ,
12 EVENT_CONNECT, /* UDP only */
13 EVENT_ANNOUNCE,
14 EVENT_COMPLETED,
15 EVENT_RENEW,
16 EVENT_SYNC,
17 EVENT_SCRAPE,
18 EVENT_FULLSCRAPE_REQUEST,
19 EVENT_FULLSCRAPE_REQUEST_GZIP,
20 EVENT_FULLSCRAPE_REQUEST_ZSTD,
21 EVENT_FULLSCRAPE, /* TCP only */
22 EVENT_FAILED,
23 EVENT_BUCKET_LOCKED,
24 EVENT_WOODPECKER,
25 EVENT_CONNID_MISSMATCH
26} ot_status_event;
27
28void stats_counters_register(struct ot_workstruct *ws);
29void stats_counters_retire(struct ot_workstruct *ws);
30
31void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data);
32void stats_deliver(int64_t sock, int tasktype);
33void stats_cleanup(void);
34void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format);
35void stats_return_tracker_version(char **reply, const char *reply_end);
36void stats_return_tracker_features(char **reply, const char *reply_end);
37void stats_init(void);
38void stats_deinit(void);
39
40#endif
diff --git a/src/ot_tcp.c b/src/ot_tcp.c
new file mode 100644
index 0000000..1351b92
--- /dev/null
+++ b/src/ot_tcp.c
@@ -0,0 +1,180 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <pthread.h>
7#include <stdatomic.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_batch.h"
15#include "ot_buf.h"
16#include "ot_event.h"
17#include "ot_fdstate.h"
18#include "ot_http.h"
19#include "ot_mutex.h"
20#include "ot_socket.h"
21#include "ot_worker.h"
22#include "ot_stats.h"
23#include "ot_tcp.h"
24#include "trackerlogic.h"
25
26static ssize_t header_complete(char *request, ssize_t byte_count) {
27 ssize_t i = 0, state = 0;
28
29 for (i = 1; i < byte_count; i += 2)
30 if (request[i] <= 13) {
31 i--;
32 for (state = 0; i < byte_count; ++i) {
33 char c = request[i];
34 if (c == '\r' || c == '\n')
35 state = (state >> 2) | ((c << 6) & 0xc0);
36 else
37 break;
38 if (state >= 0xa0 || state == 0x99)
39 return i + 1;
40 }
41 }
42 return 0;
43}
44
45void tcp_read(const int64_t sock, struct ot_workstruct *ws) {
46 struct http_data *cookie = fdstate_cookie(sock);
47 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
48
49 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
50 http_close(sock);
51 return;
52 }
53
54 if (byte_count == -1)
55 return;
56
57 ws->request = NULL;
58 ws->request_size = 0;
59 ws->header_size = 0;
60 ws->keep_alive = 0;
61
62 /* If we get the whole request in one packet, handle it without copying */
63 if (!cookie->request.data) {
64 if ((ws->header_size = header_complete(ws->inbuf, byte_count))) {
65 ws->request = ws->inbuf;
66 ws->request_size = byte_count;
67 http_handle_request(sock, ws);
68 } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count))
69 http_close(sock);
70 return;
71 }
72
73 if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) {
74 http_issue_error(sock, ws, CODE_HTTPERROR_500);
75 return;
76 }
77
78 while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) {
79 ws->request = cookie->request.data;
80 ws->request_size = (ssize_t)cookie->request.size;
81 http_handle_request(sock, ws);
82#ifdef WANT_KEEPALIVE
83 if (!ws->keep_alive)
84#endif
85 return;
86 }
87}
88
89void tcp_write(const int64_t sock) {
90 struct http_data *cookie = fdstate_cookie(sock);
91 int chunked = 0;
92 int keep_alive = 0;
93
94 if (cookie) {
95 chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0;
96 keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0;
97
98 if (cookie->batch) {
99 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
100 http_close(sock);
101 return;
102 }
103 if (batch_pending(cookie->batch))
104 return;
105 }
106 }
107
108 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
109 if (chunked)
110 http_interest_set(sock, cookie, 0);
111 else if (keep_alive) {
112 /* The reply is out and the client asked to keep the connection, so go
113 back to waiting for the next request */
114 http_interest_set(sock, cookie, EVENT_IN);
115 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT));
116 } else
117 http_close(sock);
118}
119
120/* The fast path, assuming that the complete request has usually arrived by the time a
121 connection is accepted, so it is answered here outside the event loop. The more load
122 a tracker has, the more likely it is that by the time it accepts, the roundtrip to
123 the peer has completed.
124 If the request could not be completely handled, the stack cookie is promoted to heap
125 and the connection handed over to the event loop to finish */
126void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws) {
127 int64_t sock;
128 ot_ip6 ip;
129 uint16_t port;
130
131 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
132 struct http_data cookie;
133 uint32_t generation;
134
135 memset(&cookie, 0, sizeof(cookie));
136 memcpy(cookie.ip, ip, sizeof(ot_ip6));
137 cookie.interest = EVENT_IN;
138 cookie.flag = STRUCT_HTTP_FLAG_ONSTACK;
139
140 generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT));
141 if (!fdstate_claim((int)sock, generation)) {
142 fdstate_close(sock);
143 event_close((int)sock);
144 continue;
145 }
146
147 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
148
149 tcp_read(sock, ws);
150
151 /* Gone already if it was answered and closed inline */
152 if (fdstate_generation((int)sock) != generation)
153 continue;
154
155 /* Handling it may already have given it a heap home and handed it to
156 another thread, and then it is no longer this frame's to touch */
157 if (fdstate_cookie((int)sock) != &cookie)
158 fdstate_release((int)sock);
159 else if (!http_adopt(sock))
160 http_close(sock);
161 else
162 fdstate_release((int)sock);
163 }
164}
165
166/* A worker blocks in accept and nowhere else: the connection it is handed is
167 non blocking, so a client that stops talking is given to the event loop
168 rather than keeping this thread. */
169static void tcp_run(struct ot_worker *me) {
170 while (g_opentracker_running)
171 tcp_accept(me->sock, &me->ws);
172}
173
174void tcp_worker_install(int64_t sock) {
175 socket_set_blocking((int)sock, 1);
176 socket_defer_accept((int)sock);
177
178 if (worker_start(tcp_run, sock) == -1)
179 fprintf(stderr, "Warning: could not install a tcp worker, this socket stays with the workers already running.\n");
180}
diff --git a/src/ot_tcp.h b/src/ot_tcp.h
new file mode 100644
index 0000000..afed142
--- /dev/null
+++ b/src/ot_tcp.h
@@ -0,0 +1,24 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_TCP_H__
5#define OT_TCP_H__
6
7#include <stdint.h>
8
9#include "trackerlogic.h"
10
11/* Take whatever connections are waiting on a listening socket and answer
12 them, each on this thread's stack, handing a connection that cannot be
13 finished to the event loop */
14void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws);
15
16/* Handler connections in the event loop has become readable or writable */
17void tcp_read(const int64_t sock, struct ot_workstruct *ws);
18void tcp_write(const int64_t sock);
19
20/* One thread accepting on one socket. Called once per worker. */
21void tcp_worker_install(int64_t sock);
22
23
24#endif
diff --git a/src/ot_udp.c b/src/ot_udp.c
new file mode 100644
index 0000000..644b71d
--- /dev/null
+++ b/src/ot_udp.c
@@ -0,0 +1,226 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <arpa/inet.h>
6#include <pthread.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_ip.h"
14#include "ot_random.h"
15#include "ot_rijndael.h"
16#include "ot_socket.h"
17#include "ot_worker.h"
18#include "ot_stats.h"
19#include "ot_udp.h"
20#include "trackerlogic.h"
21
22static uint32_t g_rijndael_round_key[44] = {0};
23static volatile uint32_t g_key_of_the_hour[2] = {0};
24static volatile ot_time g_hour_of_the_key;
25
26
27static void udp_generate_rijndael_round_key(void) {
28 uint32_t key[16];
29 random_bytes(key, sizeof(key));
30 rijndaelKeySetupEnc128(g_rijndael_round_key, (uint8_t *)key);
31
32 g_key_of_the_hour[0] = random_uint32();
33 g_hour_of_the_key = time_now_minutes();
34}
35
36/* Generate current and previous connection id for ip */
37static void udp_make_connectionid(uint32_t connid[2], const ot_ip6 remoteip, int age) {
38 uint32_t plain[4], crypt[4];
39 int i;
40 uint32_t current_key_of_the_hour;
41
42 if (time_now_minutes() - g_hour_of_the_key >= 60) {
43 uint32_t old_key_of_the_hour = g_key_of_the_hour[0];
44 uint32_t new_key_of_the_hour = random_uint32();
45 /* If in the meantime another thread has performed
46 key rotation, do not overwrite their results */
47 if (time_now_minutes() - g_hour_of_the_key >= 60) {
48 /* Upgrade en bloc */
49 g_hour_of_the_key = time_now_minutes();
50 g_key_of_the_hour[0] = new_key_of_the_hour;
51 g_key_of_the_hour[1] = old_key_of_the_hour;
52 }
53 }
54
55 memcpy(plain, remoteip, sizeof(plain));
56 current_key_of_the_hour = g_key_of_the_hour[age];
57 for (i = 0; i < 4; ++i)
58 plain[i] ^= current_key_of_the_hour;
59 rijndaelEncrypt128(g_rijndael_round_key, (uint8_t *)plain, (uint8_t *)crypt);
60 connid[0] = crypt[0] ^ crypt[1];
61 connid[1] = crypt[2] ^ crypt[3];
62}
63
64/* UDP implementation according to http://xbtt.sourceforge.net/udp_tracker_protocol.html */
65int handle_udp6(int64_t serversocket, struct ot_workstruct *ws) {
66 ot_ip6 remoteip;
67 uint32_t *inpacket = (uint32_t *)ws->inbuf;
68 uint32_t *outpacket = (uint32_t *)ws->outbuf;
69 uint32_t left, event;
70 uint32_t connid[2];
71 uint32_t action;
72 uint16_t port, remoteport;
73 size_t byte_count, scrape_count;
74
75 ssize_t result = socket_recv(serversocket, ws->inbuf, G_INBUF_SIZE, remoteip, &remoteport);
76 if (result <= 0)
77 return 0;
78 byte_count = (size_t)result;
79
80 stats_issue_event(ws, EVENT_ACCEPT, FLAG_UDP, (uintptr_t)remoteip);
81 stats_issue_event(ws, EVENT_READ, FLAG_UDP, byte_count);
82
83 /* Minimum udp tracker packet size, also catches error */
84 if (byte_count < 16)
85 return 1;
86
87 /* Get action to take. Ignore error messages and broken packets */
88 action = ntohl(inpacket[2]);
89 if (action > 2)
90 return 1;
91
92 /* Generate the connection id we give out and expect to and from
93 the requesting ip address, this prevents udp spoofing */
94 udp_make_connectionid(connid, remoteip, 0);
95
96 /* Initialise hash pointer */
97 ws->hash = NULL;
98 ws->peer_id = NULL;
99
100 /* If action is not 0 (connect), then we expect the derived
101 connection id in first 64 bit */
102 if ((action > 0) && (inpacket[0] != connid[0] || inpacket[1] != connid[1])) {
103 /* If connection id does not match, try the one that was
104 valid in the previous hour. Only if this also does not
105 match, return an error packet */
106 udp_make_connectionid(connid, remoteip, 1);
107 if (inpacket[0] != connid[0] || inpacket[1] != connid[1]) {
108 const char err_idmiss[] = "Connection ID missmatch.";
109 size_t s = sizeof(err_idmiss);
110 outpacket[0] = htonl(3);
111 outpacket[1] = inpacket[3];
112 memcpy(&outpacket[2], &err_idmiss[0], s);
113 socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport);
114 stats_issue_event(ws, EVENT_CONNID_MISSMATCH, FLAG_UDP, 8 + s);
115 return 1;
116 }
117 }
118
119 switch (action) {
120 case 0: /* This is a connect action */
121 /* look for udp bittorrent magic id */
122 if ((ntohl(inpacket[0]) != 0x00000417) || (ntohl(inpacket[1]) != 0x27101980))
123 return 1;
124
125 outpacket[0] = 0;
126 outpacket[1] = inpacket[3];
127 outpacket[2] = connid[0];
128 outpacket[3] = connid[1];
129
130 socket_send(serversocket, ws->outbuf, 16, remoteip, remoteport);
131 stats_issue_event(ws, EVENT_CONNECT, FLAG_UDP, 16);
132 break;
133 case 1: /* This is an announce action */
134 /* Minimum udp announce packet size */
135 if (byte_count < 98)
136 return 1;
137
138 /* We do only want to know, if it is zero */
139 left = inpacket[64 / 4] | inpacket[68 / 4];
140
141 event = ntohl(inpacket[80 / 4]);
142 port = *(uint16_t *)(((char *)inpacket) + 96);
143 ws->hash = (ot_hash *)(((char *)inpacket) + 16);
144
145 OT_SETIP(ws->peer, remoteip);
146 OT_SETPORT(ws->peer, &port);
147 OT_PEERFLAG(ws->peer) = 0;
148
149 switch (event) {
150 case 1:
151 OT_PEERFLAG(ws->peer) |= PEER_FLAG_COMPLETED;
152 break;
153 case 3:
154 OT_PEERFLAG(ws->peer) |= PEER_FLAG_STOPPED;
155 break;
156 default:
157 break;
158 }
159
160 if (!left)
161 OT_PEERFLAG(ws->peer) |= PEER_FLAG_SEEDING;
162
163 outpacket[0] = htonl(1); /* announce action */
164 outpacket[1] = inpacket[12 / 4];
165
166 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_STOPPED) { /* Peer is gone. */
167 ws->reply = ws->outbuf;
168 ws->reply_size = remove_peer_from_torrent(FLAG_UDP, ws);
169 } else {
170 /* Limit amount of peers to OT_MAX_PEERS_UDP */
171 uint32_t numwant = ntohl(inpacket[92 / 4]);
172 size_t max_peers = ip_is_v4mapped(remoteip) ? OT_MAX_PEERS_UDP4 : OT_MAX_PEERS_UDP6;
173 if (numwant > max_peers)
174 numwant = max_peers;
175
176 ws->reply = ws->outbuf + 8;
177 ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_UDP, ws, numwant);
178
179 if (!ws->reply_size) {
180 const char err_unauth[] = "Requested download is not authorized for use with this tracker.";
181 size_t s = sizeof(err_unauth);
182 outpacket[0] = htonl(3);
183 memcpy(&outpacket[2], &err_unauth[0], s);
184 socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport);
185 return 1;
186 }
187 ws->reply_size += 8;
188 }
189
190 socket_send(serversocket, ws->outbuf, ws->reply_size, remoteip, remoteport);
191 stats_issue_event(ws, EVENT_ANNOUNCE, FLAG_UDP, ws->reply_size);
192 break;
193
194 case 2: /* This is a scrape action */
195 outpacket[0] = htonl(2); /* scrape action */
196 outpacket[1] = inpacket[12 / 4];
197
198 for (scrape_count = 0; (scrape_count * 20 < byte_count - 16) && (scrape_count <= 74); scrape_count++)
199 return_udp_scrape_for_torrent(*(ot_hash *)(((char *)inpacket) + 16 + 20 * scrape_count), ((char *)outpacket) + 8 + 12 * scrape_count);
200
201 socket_send(serversocket, ws->outbuf, 8 + 12 * scrape_count, remoteip, remoteport);
202 stats_issue_event(ws, EVENT_SCRAPE, FLAG_UDP, scrape_count);
203 break;
204 }
205 return 1;
206}
207
208static void udp_run(struct ot_worker *me) {
209 while (g_opentracker_running)
210 handle_udp6(me->sock, &me->ws);
211}
212
213void udp_init(void) {
214 if (!g_rijndael_round_key[0])
215 udp_generate_rijndael_round_key();
216}
217
218void udp_worker_install(int64_t sock) {
219#ifdef _DEBUG
220 fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock);
221#endif
222 socket_set_blocking((int)sock, 1);
223
224 if (worker_start(udp_run, sock) == -1)
225 fprintf(stderr, "Warning: could not install a udp worker, this socket stays with the workers already running.\n");
226}
diff --git a/src/ot_udp.h b/src/ot_udp.h
new file mode 100644
index 0000000..2710744
--- /dev/null
+++ b/src/ot_udp.h
@@ -0,0 +1,17 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_UDP_H__
5#define OT_UDP_H__
6
7#include <stdint.h>
8
9#include "trackerlogic.h"
10
11void udp_init(void);
12/* One thread reading one socket. Called once per worker. */
13void udp_worker_install(int64_t sock);
14
15int handle_udp6(int64_t serversocket, struct ot_workstruct *ws);
16
17#endif
diff --git a/src/ot_urlencoded.c b/src/ot_urlencoded.c
new file mode 100644
index 0000000..4109bb5
--- /dev/null
+++ b/src/ot_urlencoded.c
@@ -0,0 +1,168 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* Opentracker */
5#include "ot_urlencoded.h"
6
7/* System */
8#include <string.h>
9
10/* Idea is to do a in place replacement or guarantee at least
11 strlen( string ) bytes in deststring
12 watch http://www.ietf.org/rfc/rfc2396.txt
13 unreserved = alphanum | mark
14 mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
15 we add '%' to the matrix to not stop at encoded chars.
16 After losing too many requests to being too strict, add the following characters to reserved matrix
17 relax = "+" | "," | "/" | ";" | "<" | ">" | ":"
18*/
19
20/* This matrix holds for each ascii character the information,
21 whether it is a non-terminating character for one of the three
22 scan states we are in, that is 'path', 'param' and 'value' from
23 /path?param=value&param=value, it is encoded in bit 0, 1 and 2
24 respectively
25
26 The top bit of lower nibble indicates, whether this character is
27 a hard terminator, ie. \0, \n or \s, where the whole scanning
28 process should terminate
29 */
30static const unsigned char is_unreserved[256] = {
31 8,0,0,0,0,0,0,0,0,0,8,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
32 8,7,8,8,8,7,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,4,7,6,
33 4,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,7,
34 8,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,7,0,
35 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
36 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
37 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
38 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
39};
40
41/* Do a fast nibble to hex representation conversion */
42static unsigned char fromhex(unsigned char x) {
43 x -= '0';
44 if (x <= 9)
45 return x;
46 x &= ~0x20;
47 x -= 'A' - '0';
48 if (x < 6)
49 return x + 10;
50 return 0xff;
51}
52
53/* Skip the value of a param=value pair */
54void urlencoded_skip_value(char **string) {
55 const unsigned char *s = *(const unsigned char **)string;
56 unsigned char f;
57
58 /* Since we are asked to skip the 'value', we assume to stop at
59 terminators for a 'value' string position */
60 while ((f = is_unreserved[*s++]) & SCAN_SEARCHPATH_VALUE)
61 ;
62
63 /* If we stopped at a hard terminator like \0 or \n, make the
64 next urlencoded_parse encounter it again */
65 if (f & SCAN_SEARCHPATH_TERMINATOR)
66 --s;
67
68 *string = (char *)s;
69}
70
71int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags) {
72 char *deststring = *string;
73 ssize_t match_length = urlencoded_parse(string, deststring, flags);
74
75 if (match_length < 0)
76 return match_length;
77 if (match_length == 0)
78 return -3;
79
80 while (keywords->key) {
81 if (!strncmp(keywords->key, deststring, match_length) && !keywords->key[match_length])
82 return keywords->value;
83 keywords++;
84 }
85
86 return -3;
87}
88
89ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) {
90 const unsigned char *s = *(const unsigned char **)string;
91 unsigned char *d = (unsigned char *)deststring;
92 unsigned char b, c;
93
94 /* This is the main decoding loop.
95 'flag' determines, which characters are non-terminating in current context
96 (ie. stop at '=' and '&' if scanning for a 'param'; stop at '?' if scanning for the path )
97 */
98 while (is_unreserved[c = *s++] & flags) {
99
100 /* When encountering an url escaped character, try to decode */
101 if (c == '%') {
102 if ((b = fromhex(*s++)) == 0xff)
103 return -1;
104 if ((c = fromhex(*s++)) == 0xff)
105 return -1;
106 c |= (b << 4);
107 }
108
109 /* Write (possibly decoded) character to output */
110 *d++ = c;
111 }
112
113 switch (c) {
114 case 0:
115 case '\r':
116 case '\n':
117 case ' ':
118 /* If we started scanning on a hard terminator, indicate we've finished */
119 if (d == (unsigned char *)deststring)
120 return -2;
121
122 /* Else make the next call to urlencoded_parse encounter it again */
123 --s;
124 break;
125 case '?':
126 if (flags != SCAN_PATH)
127 return -1;
128 break;
129 case '=':
130 if (flags != SCAN_SEARCHPATH_PARAM)
131 return -1;
132 break;
133 case '&':
134 if (flags == SCAN_PATH)
135 return -1;
136 if (flags == SCAN_SEARCHPATH_PARAM)
137 --s;
138 break;
139 default:
140 return -1;
141 }
142
143 *string = (char *)s;
144 return d - (unsigned char *)deststring;
145}
146
147
148size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) {
149 static const char safe[] = "$/.=~-_";
150 static const char hexdigit[] = "0123456789abcdef";
151 size_t written = 0, i;
152
153 for (i = 0; i < len; ++i) {
154 uint8_t c = src[i];
155
156 /* strchr would find the terminator, so a zero byte never counts as safe */
157 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c && strchr(safe, c))) {
158 dest[written++] = (char)c;
159 } else {
160 dest[written] = '%';
161 dest[written + 1] = hexdigit[c >> 4];
162 dest[written + 2] = hexdigit[c & 15];
163 written += 3;
164 }
165 }
166
167 return written;
168}
diff --git a/src/ot_urlencoded.h b/src/ot_urlencoded.h
new file mode 100644
index 0000000..9882468
--- /dev/null
+++ b/src/ot_urlencoded.h
@@ -0,0 +1,53 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef SCAN_URLENCODED_QUERY_H__
5#define SCAN_URLENCODED_QUERY_H__
6
7#include <stddef.h>
8#include <stdint.h>
9#include <sys/types.h>
10
11typedef struct {
12 const char *key;
13 int value;
14} ot_keywords;
15
16typedef enum {
17 SCAN_PATH = 1,
18 SCAN_SEARCHPATH_PARAM = 2,
19 SCAN_SEARCHPATH_VALUE = 4,
20 SCAN_SEARCHPATH_TERMINATOR = 8
21} SCAN_SEARCHPATH_FLAG;
22
23/* string in: pointer to source
24 out: pointer to next scan position
25 deststring pointer to destination
26 flags determines, what to parse
27 returns number of valid converted characters in deststring
28 or -1 for parse error
29 or -2 for terminator found
30*/
31ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags);
32
33/* string in: pointer to source
34 out: pointer to next scan position
35 flags determines, what to parse
36 returns value for matched keyword
37 or -1 for parse error
38 or -2 for terminator found
39 or -3 for no keyword matched
40 */
41int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags);
42
43/* string in: pointer to value of a param=value pair to skip
44 out: pointer to next scan position on return
45*/
46void urlencoded_skip_value(char **string);
47
48
49/* The other direction: percent encode len bytes from src into dest and return
50 how many characters were written. dest needs room for three times len. */
51size_t urlencoded_format(char *dest, const uint8_t *src, size_t len);
52
53#endif
diff --git a/src/ot_vector.c b/src/ot_vector.c
new file mode 100644
index 0000000..ab0566c
--- /dev/null
+++ b/src/ot_vector.c
@@ -0,0 +1,287 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stddef.h>
6#include <stdint.h>
7#include <stdlib.h>
8#include <string.h>
9
10/* Opentracker */
11#include "trackerlogic.h"
12
13static int vector_compare_peer6(const void *peer1, const void *peer2) { return memcmp(peer1, peer2, OT_PEER_COMPARE_SIZE6); }
14static int vector_compare_peer4(const void *peer1, const void *peer2) { return memcmp(peer1, peer2, OT_PEER_COMPARE_SIZE4); }
15
16/* This function gives us a binary search that returns a pointer, even if
17 no exact match is found. In that case it sets exactmatch 0 and gives
18 calling functions the chance to insert data
19*/
20void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch) {
21 size_t interval = member_count;
22
23 while (interval) {
24 uint8_t *lookat = ((uint8_t *)base) + member_size * (interval / 2);
25 int cmp = memcmp(lookat, key, compare_size);
26 if (cmp == 0) {
27 base = lookat;
28 break;
29 }
30 if (cmp < 0) {
31 base = lookat + member_size;
32 interval--;
33 }
34 interval /= 2;
35 }
36
37 *exactmatch = interval;
38 return (void *)base;
39}
40
41static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t bucket_count) {
42 unsigned int hash = 5381;
43 uint8_t *p = (uint8_t *)peer;
44 while (compare_size--)
45 hash += (hash << 5) + *(p++);
46 return hash % bucket_count;
47}
48
49/* This is the generic insert operation for our vector type.
50 It tries to locate the object at "key" with size "member_size" by comparing its first "compare_size" bytes with
51 those of objects in vector. Our special "vector_binary_search" function does that and either returns the match or a
52 pointer to where the object is to be inserted. vector_find_or_insert makes space for the object and copies it,
53 if it wasn't found in vector. Caller needs to check the passed "exactmatch" variable to see, whether an insert
54 took place. If resizing the vector failed, NULL is returned, else the pointer to the object in vector.
55*/
56void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) {
57 uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch);
58 size_t match_offset, bytes_after;
59
60 if (*exactmatch)
61 return match;
62
63 /* Hold the insertion point as an offset rather than a pointer: it survives
64 the realloc below, and an empty vector has no data pointer to offset from
65 in the first place */
66 match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0;
67 bytes_after = member_size * vector->size - match_offset;
68
69 if (vector->size + 1 > vector->space) {
70 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
71 uint8_t *new_data = realloc(vector->data, new_space * member_size);
72
73 if (!new_data)
74 return NULL;
75
76 vector->data = new_data;
77 vector->space = new_space;
78 }
79
80 match = (uint8_t *)vector->data + match_offset;
81 memmove(match + member_size, match, bytes_after);
82
83 vector->size++;
84 return match;
85}
86
87ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) {
88 ot_peer *match;
89 const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
90 size_t match_offset, bytes_after;
91
92 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
93 if (vector->space < vector->size)
94 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size);
95 match = vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, exactmatch);
96
97 if (*exactmatch)
98 return match;
99
100 /* Hold the insertion point as an offset rather than a pointer: it survives
101 the realloc below, and an empty vector has no data pointer to offset from
102 in the first place. bytes_after is what has to move up by peer_size to
103 make room for the new peer */
104 match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0;
105 bytes_after = vector->size * peer_size - match_offset;
106
107 if (vector->size + 1 > vector->space) {
108 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
109 ot_peer *new_data = realloc(vector->data, new_space * peer_size);
110
111 if (!new_data)
112 return NULL;
113
114 vector->data = new_data;
115 vector->space = new_space;
116 }
117
118 match = (ot_peer *)vector->data + match_offset;
119 memmove(match + peer_size, match, bytes_after);
120
121 vector->size++;
122 return match;
123}
124
125/* This is the non-generic delete from vector-operation specialized for peers in pools.
126 It returns 0 if no peer was found (and thus not removed)
127 1 if a non-seeding peer was removed
128 2 if a seeding peer was removed
129*/
130int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size) {
131 int exactmatch, was_seeder;
132 ot_peer *match, *end;
133 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
134
135 if (!vector->size)
136 return 0;
137
138 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
139 if (vector->space < vector->size)
140 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size);
141
142 end = ((ot_peer *)vector->data) + peer_size * vector->size;
143 match = (ot_peer *)vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, &exactmatch);
144 if (!exactmatch)
145 return 0;
146
147 was_seeder = (OT_PEERFLAG_D(match, peer_size) & PEER_FLAG_SEEDING) ? 2 : 1;
148 memmove(match, match + peer_size, end - match - peer_size);
149
150 vector->size--;
151 vector_fixup_peers(vector, peer_size);
152 return was_seeder;
153}
154
155void vector_remove_torrent(ot_vector *vector, ot_torrent *match) {
156 ot_torrent *end;
157
158 if (!vector->size)
159 return;
160
161 end = ((ot_torrent *)vector->data) + vector->size;
162
163 free_peerlist(match->peer_list6);
164 free_peerlist(match->peer_list4);
165
166 memmove(match, match + 1, sizeof(ot_torrent) * (end - match - 1));
167 if ((--vector->size * OT_VECTOR_SHRINK_THRESH < vector->space) && (vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS)) {
168 vector->space /= OT_VECTOR_SHRINK_RATIO;
169 vector->data = realloc(vector->data, vector->space * sizeof(ot_torrent));
170 }
171}
172
173void vector_clean_list(ot_vector *vector, size_t num_buckets) {
174 while (num_buckets--)
175 free(vector[num_buckets].data);
176 free(vector);
177 return;
178}
179
180void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) {
181 size_t bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1;
182 ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers;
183 int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4;
184
185 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
186 num_buckets_old = peer_list->peers.size;
187 bucket_list_old = peer_list->peers.data;
188 }
189
190 if (peer_list->peer_count < 255)
191 num_buckets_new = 1;
192 else if (peer_list->peer_count > 8192)
193 num_buckets_new = 64;
194 else if (peer_list->peer_count >= 512 && peer_list->peer_count < 4096)
195 num_buckets_new = 16;
196 else if (peer_list->peer_count < 512 && num_buckets_old <= 16)
197 num_buckets_new = num_buckets_old;
198 else if (peer_list->peer_count < 512)
199 num_buckets_new = 1;
200 else if (peer_list->peer_count < 8192 && num_buckets_old > 1)
201 num_buckets_new = num_buckets_old;
202 else
203 num_buckets_new = 16;
204
205 if (num_buckets_new == num_buckets_old)
206 return;
207
208 /* Assume near perfect distribution */
209 bucket_list_new = calloc(1, num_buckets_new * sizeof(ot_vector));
210 if (!bucket_list_new)
211 return;
212
213 bucket_size_new = OT_VECTOR_MIN_MEMBERS;
214 while (bucket_size_new * num_buckets_new < peer_list->peer_count)
215 bucket_size_new *= OT_VECTOR_GROW_RATIO;
216
217 /* preallocate vectors to hold all peers */
218 for (bucket = 0; bucket < num_buckets_new; ++bucket) {
219 bucket_list_new[bucket].space = bucket_size_new;
220 bucket_list_new[bucket].data = malloc(bucket_size_new * peer_size);
221 if (!bucket_list_new[bucket].data) {
222 vector_clean_list(bucket_list_new, num_buckets_new);
223 return;
224 }
225 }
226
227 /* Now sort them into the correct bucket */
228 for (bucket = 0; bucket < num_buckets_old; ++bucket) {
229 ot_peer *peers_old = bucket_list_old[bucket].data;
230 size_t peer_count_old = bucket_list_old[bucket].size;
231 while (peer_count_old--) {
232 ot_vector *bucket_dest = bucket_list_new;
233 if (num_buckets_new > 1)
234 bucket_dest += vector_hash_peer(peers_old, OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size), num_buckets_new);
235 if (bucket_dest->size + 1 > bucket_dest->space) {
236 void *tmp = realloc(bucket_dest->data, peer_size * OT_VECTOR_GROW_RATIO * bucket_dest->space);
237 if (!tmp) {
238 vector_clean_list(bucket_list_new, num_buckets_new);
239 return;
240 }
241 bucket_dest->data = tmp;
242 bucket_dest->space *= OT_VECTOR_GROW_RATIO;
243 }
244 memcpy((ot_peer *)bucket_dest->data + peer_size * bucket_dest->size++, peers_old, peer_size);
245 peers_old += peer_size;
246 }
247 }
248
249 /* Now sort each bucket to later allow bsearch */
250 for (bucket = 0; bucket < num_buckets_new; ++bucket)
251 qsort(bucket_list_new[bucket].data, bucket_list_new[bucket].size, peer_size, sort_func);
252
253 /* Everything worked fine. Now link new bucket_list to peer_list */
254 if (OT_PEERLIST_HASBUCKETS(peer_list))
255 vector_clean_list((ot_vector *)peer_list->peers.data, peer_list->peers.size);
256 else
257 free(peer_list->peers.data);
258
259 if (num_buckets_new > 1) {
260 peer_list->peers.data = bucket_list_new;
261 peer_list->peers.size = num_buckets_new;
262 peer_list->peers.space = 0; /* Magic marker for "is list of buckets" */
263 } else {
264 peer_list->peers.data = bucket_list_new->data;
265 peer_list->peers.size = bucket_list_new->size;
266 peer_list->peers.space = bucket_list_new->space;
267 free(bucket_list_new);
268 }
269}
270
271void vector_fixup_peers(ot_vector *vector, size_t peer_size) {
272 int need_fix = 0;
273
274 if (!vector->size) {
275 free(vector->data);
276 vector->data = NULL;
277 vector->space = 0;
278 return;
279 }
280
281 while ((vector->size * OT_VECTOR_SHRINK_THRESH < vector->space) && (vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS)) {
282 vector->space /= OT_VECTOR_SHRINK_RATIO;
283 need_fix++;
284 }
285 if (need_fix)
286 vector->data = realloc(vector->data, vector->space * peer_size);
287}
diff --git a/src/ot_vector.h b/src/ot_vector.h
new file mode 100644
index 0000000..8a5ba86
--- /dev/null
+++ b/src/ot_vector.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#ifndef OT_VECTOR_H__
5#define OT_VECTOR_H__
6
7#include <stddef.h>
8
9/* These defines control vectors behaviour */
10#define OT_VECTOR_MIN_MEMBERS 2
11#define OT_VECTOR_GROW_RATIO 2
12#define OT_VECTOR_SHRINK_THRESH 4
13#define OT_VECTOR_SHRINK_RATIO 2
14
15#define OT_PEER_BUCKET_MINCOUNT 512
16#define OT_PEER_BUCKET_MAXCOUNT 256
17
18typedef struct {
19 void *data;
20 size_t size;
21 size_t space;
22} ot_vector;
23
24/* trackerlogic.h needs ot_vector, break circular dependency here */
25#include "trackerlogic.h"
26
27void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch);
28void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch);
29ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch);
30
31int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size);
32void vector_remove_torrent(ot_vector *vector, ot_torrent *match);
33
34/* For ot_clean.c */
35void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size);
36void vector_fixup_peers(ot_vector *vector, size_t peer_size);
37void vector_clean_list(ot_vector *vector, size_t num_buckets);
38
39#endif
diff --git a/src/ot_worker.c b/src/ot_worker.c
new file mode 100644
index 0000000..432d272
--- /dev/null
+++ b/src/ot_worker.c
@@ -0,0 +1,73 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <pthread.h>
6#include <stdatomic.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <unistd.h>
10
11/* Opentracker */
12#include "ot_worker.h"
13#include "trackerlogic.h"
14
15static struct ot_worker **g_workers;
16static size_t g_worker_count;
17
18/* Everything a worker needs doing around its own loop, in one place, so that
19 a new kind of worker cannot miss a piece of it. */
20static void *worker_trampoline(void *args) {
21 struct ot_worker *me = args;
22
23 if (workstruct_init(&me->ws) == -1)
24 fprintf(stderr, "Warning: a worker could not get its buffers and will not run.\n");
25 else {
26 me->run(me);
27 workstruct_deinit(&me->ws);
28 }
29
30 atomic_store(&me->left, 1);
31 return NULL;
32}
33
34int worker_start(ot_worker_run run, int64_t sock) {
35 struct ot_worker **grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers));
36 struct ot_worker *worker;
37
38 if (!grown)
39 return -1;
40 g_workers = grown;
41
42 if (!(worker = calloc(1, sizeof(*worker))))
43 return -1;
44 worker->sock = sock;
45 worker->run = run;
46
47 if (pthread_create(&worker->thread, NULL, worker_trampoline, worker)) {
48 free(worker);
49 return -1;
50 }
51 g_workers[g_worker_count++] = worker;
52 return 0;
53}
54
55void worker_stop_all(void) {
56 size_t i;
57
58 for (i = 0; i < g_worker_count; ++i) {
59 struct ot_worker *worker = g_workers[i];
60
61 /* Repeated because a signal only interrupts a call already under way */
62 while (!atomic_load(&worker->left)) {
63 pthread_kill(worker->thread, OT_SIGNAL_WAKE);
64 usleep(1000);
65 }
66 pthread_join(worker->thread, NULL);
67 free(worker);
68 }
69
70 free(g_workers);
71 g_workers = NULL;
72 g_worker_count = 0;
73}
diff --git a/src/ot_worker.h b/src/ot_worker.h
new file mode 100644
index 0000000..4954773
--- /dev/null
+++ b/src/ot_worker.h
@@ -0,0 +1,37 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_WORKER_H__
5#define OT_WORKER_H__
6
7#include <pthread.h>
8#include <stdatomic.h>
9#include <stdint.h>
10
11#include "trackerlogic.h"
12
13struct ot_worker;
14
15/* What a worker does between being started and being told to stop. It reads
16 worker->sock and works out of worker->ws; everything around it, from the
17 buffers to the counters to saying goodbye, is handled here. */
18typedef void (*ot_worker_run)(struct ot_worker *worker);
19
20/* A thread that serves requests on a socket of its own, with the buffers and
21 counters it needs. Each is allocated on its own, so taking on another one
22 never moves the record a running worker holds. */
23struct ot_worker {
24 pthread_t thread;
25 _Atomic int left;
26 int64_t sock;
27 ot_worker_run run;
28 struct ot_workstruct ws;
29};
30
31/* Start one. Returns -1 if it could not be started. */
32int worker_start(ot_worker_run run, int64_t sock);
33
34/* Wake every worker until it has left, then join it */
35void worker_stop_all(void);
36
37#endif
diff --git a/src/ot_workqueue.c b/src/ot_workqueue.c
new file mode 100644
index 0000000..56a7cd2
--- /dev/null
+++ b/src/ot_workqueue.c
@@ -0,0 +1,241 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <pthread.h>
6#include <stdlib.h>
7#include <string.h>
8#include <sys/uio.h>
9#include <unistd.h>
10
11/* Opentracker */
12#include "ot_iovec.h"
13#include "ot_workqueue.h"
14#include "trackerlogic.h"
15
16/* Pushing a result wakes the thread that will send it */
17extern int g_self_pipe[2];
18
19struct ot_task {
20 ot_taskid taskid;
21 ot_tasktype tasktype;
22 int64_t sock;
23 size_t iovec_entries;
24 struct iovec *iovec;
25 struct ot_task *next;
26};
27
28static ot_taskid next_free_taskid = 1;
29static struct ot_task *tasklist;
30static pthread_mutex_t tasklist_mutex;
31static pthread_cond_t tasklist_being_filled;
32
33int workqueue_pushtask(int64_t sock, ot_tasktype tasktype) {
34 struct ot_task **tmptask, *task;
35
36 task = malloc(sizeof(struct ot_task));
37 if (!task)
38 return -1;
39
40 task->taskid = 0;
41 task->tasktype = tasktype;
42 task->sock = sock;
43 task->iovec_entries = 0;
44 task->iovec = NULL;
45 task->next = 0;
46
47 /* Want exclusive access to tasklist */
48 pthread_mutex_lock(&tasklist_mutex);
49
50 /* Skip to end of list */
51 tmptask = &tasklist;
52 while (*tmptask)
53 tmptask = &(*tmptask)->next;
54 *tmptask = task;
55
56 /* Inform waiting workers and release lock */
57 pthread_cond_broadcast(&tasklist_being_filled);
58 pthread_mutex_unlock(&tasklist_mutex);
59 return 0;
60}
61
62void workqueue_canceltask(int64_t sock) {
63 struct ot_task **task;
64
65 /* Want exclusive access to tasklist */
66 pthread_mutex_lock(&tasklist_mutex);
67
68 for (task = &tasklist; *task; task = &((*task)->next))
69 if ((*task)->sock == sock) {
70 struct iovec *iovec = (*task)->iovec;
71 struct ot_task *ptask = *task;
72 size_t i;
73
74 /* Free task's iovec, the buffers it points at and the vector itself */
75 for (i = 0; i < (*task)->iovec_entries; ++i)
76 free(iovec[i].iov_base);
77 free(iovec);
78
79 *task = (*task)->next;
80 free(ptask);
81 break;
82 }
83
84 /* Release lock */
85 pthread_mutex_unlock(&tasklist_mutex);
86}
87
88ot_taskid workqueue_poptask(ot_tasktype *tasktype) {
89 struct ot_task *task;
90 ot_taskid taskid = 0;
91
92 /* Want exclusive access to tasklist */
93 pthread_mutex_lock(&tasklist_mutex);
94
95 while (!taskid) {
96 /* Skip to the first unassigned task this worker wants to do */
97 for (task = tasklist; task; task = task->next)
98 if (!task->taskid && (TASK_CLASS_MASK & task->tasktype) == *tasktype) {
99 /* If we found an outstanding task, assign a taskid to it
100 and leave the loop */
101 task->taskid = taskid = ++next_free_taskid;
102 *tasktype = task->tasktype;
103 break;
104 }
105
106 /* Wait until the next task is being fed, unless the tracker is on its
107 way out, in which case the worker is meant to leave rather than wait */
108 if (!taskid) {
109 if (!g_opentracker_running)
110 break;
111 pthread_cond_wait(&tasklist_being_filled, &tasklist_mutex);
112 }
113 }
114
115 /* Release lock */
116 pthread_mutex_unlock(&tasklist_mutex);
117
118 return taskid;
119}
120
121void workqueue_pushsuccess(ot_taskid taskid) {
122 struct ot_task **task;
123
124 /* Want exclusive access to tasklist */
125 pthread_mutex_lock(&tasklist_mutex);
126
127 for (task = &tasklist; *task; task = &((*task)->next))
128 if ((*task)->taskid == taskid) {
129 struct ot_task *ptask = *task;
130 *task = (*task)->next;
131 free(ptask);
132 break;
133 }
134
135 /* Release lock */
136 pthread_mutex_unlock(&tasklist_mutex);
137}
138
139int workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovec) {
140 struct ot_task *task;
141 const char byte = 'o';
142
143 /* Want exclusive access to tasklist */
144 pthread_mutex_lock(&tasklist_mutex);
145
146 for (task = tasklist; task; task = task->next)
147 if (task->taskid == taskid) {
148 task->iovec_entries = iovec_entries;
149 task->iovec = iovec;
150 task->tasktype = TASK_DONE;
151 break;
152 }
153
154 /* Release lock */
155 pthread_mutex_unlock(&tasklist_mutex);
156
157 /* A full pipe has already done this nudge's job, so a failure is fine */
158 (void)!write(g_self_pipe[1], &byte, 1);
159
160 /* Indicate whether the worker has to throw away results */
161 return task ? 0 : -1;
162}
163
164int workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec) {
165 struct ot_task *task;
166 const char byte = 'o';
167
168 /* Want exclusive access to tasklist */
169 pthread_mutex_lock(&tasklist_mutex);
170
171 for (task = tasklist; task; task = task->next)
172 if (task->taskid == taskid) {
173 if (iovec) {
174 if (iovec_append(&task->iovec_entries, &task->iovec, iovec))
175 task->tasktype = TASK_DONE_PARTIAL;
176 else
177 task = NULL;
178 } else
179 task->tasktype = TASK_DONE;
180 break;
181 }
182
183 /* Release lock */
184 pthread_mutex_unlock(&tasklist_mutex);
185
186 /* A full pipe has already done this nudge's job, so a failure is fine */
187 (void)!write(g_self_pipe[1], &byte, 1);
188
189 /* Indicate whether the worker has to throw away results */
190 return task ? 0 : -1;
191}
192
193int64_t workqueue_popresult(size_t *iovec_entries, struct iovec **iovec, int *is_partial) {
194 struct ot_task **task;
195 int64_t sock = -1;
196
197 *is_partial = 0;
198
199 /* Want exclusive access to tasklist */
200 pthread_mutex_lock(&tasklist_mutex);
201
202 for (task = &tasklist; *task; task = &((*task)->next))
203 if (((*task)->tasktype & TASK_CLASS_MASK) == TASK_DONE) {
204 struct ot_task *ptask = *task;
205 *iovec_entries = ptask->iovec_entries;
206 *iovec = ptask->iovec;
207 sock = ptask->sock;
208
209 if ((*task)->tasktype == TASK_DONE) {
210 *task = ptask->next;
211 free(ptask);
212 } else {
213 ptask->iovec_entries = 0;
214 ptask->iovec = NULL;
215 *is_partial = 1;
216 /* Prevent task from showing up immediately again unless new data was added */
217 (*task)->tasktype = TASK_FULLSCRAPE;
218 }
219 break;
220 }
221
222 /* Release lock */
223 pthread_mutex_unlock(&tasklist_mutex);
224 return sock;
225}
226
227void workqueue_wakeall(void) {
228 pthread_mutex_lock(&tasklist_mutex);
229 pthread_cond_broadcast(&tasklist_being_filled);
230 pthread_mutex_unlock(&tasklist_mutex);
231}
232
233void workqueue_init(void) {
234 pthread_mutex_init(&tasklist_mutex, NULL);
235 pthread_cond_init(&tasklist_being_filled, NULL);
236}
237
238void workqueue_deinit(void) {
239 pthread_mutex_destroy(&tasklist_mutex);
240 pthread_cond_destroy(&tasklist_being_filled);
241}
diff --git a/src/ot_workqueue.h b/src/ot_workqueue.h
new file mode 100644
index 0000000..82ae29b
--- /dev/null
+++ b/src/ot_workqueue.h
@@ -0,0 +1,74 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_WORKQUEUE_H__
5#define OT_WORKQUEUE_H__
6
7#include <stdint.h>
8#include <sys/uio.h>
9
10#include "trackerlogic.h"
11
12/* Work the tracker hands to a thread of its own: a full scrape or an
13 expensive stats page, which take too long to build while a client waits.
14 The task goes in here, the reply comes back out. */
15
16typedef enum {
17 TASK_STATS_CONNS = 0x0001,
18 TASK_STATS_TCP = 0x0002,
19 TASK_STATS_UDP = 0x0003,
20 TASK_STATS_SCRAPE = 0x0004,
21 TASK_STATS_FULLSCRAPE = 0x0005,
22 TASK_STATS_TPB = 0x0006,
23 TASK_STATS_HTTPERRORS = 0x0007,
24 TASK_STATS_RENEW = 0x000a,
25 TASK_STATS_SYNCS = 0x000b,
26 TASK_STATS_COMPLETED = 0x000c,
27 TASK_STATS_NUMWANTS = 0x000d,
28
29 TASK_STATS = 0x0100, /* Mask */
30 TASK_STATS_TORRENTS = 0x0101,
31 TASK_STATS_PEERS = 0x0102,
32 TASK_STATS_SLASH24S = 0x0103,
33 TASK_STATS_TOP10 = 0x0104,
34 TASK_STATS_TOP100 = 0x0105,
35 TASK_STATS_EVERYTHING = 0x0106,
36 TASK_STATS_WOODPECKERS = 0x0108,
37
38 TASK_FULLSCRAPE = 0x0200, /* Default mode */
39 TASK_FULLSCRAPE_TPB_BINARY = 0x0201,
40 TASK_FULLSCRAPE_TPB_ASCII = 0x0202,
41 TASK_FULLSCRAPE_TPB_ASCII_PLUS = 0x0203,
42 TASK_FULLSCRAPE_TPB_URLENCODED = 0x0204,
43 TASK_FULLSCRAPE_TRACKERSTATE = 0x0205,
44
45 TASK_DMEM = 0x0300,
46
47 TASK_DONE = 0x0f00,
48 TASK_DONE_PARTIAL = 0x0f01,
49
50 TASK_FLAG_GZIP = 0x1000,
51 TASK_FLAG_BZIP2 = 0x2000,
52 TASK_FLAG_ZSTD = 0x4000,
53 TASK_FLAG_CHUNKED = 0x8000,
54
55 TASK_TASK_MASK = 0x0fff,
56 TASK_CLASS_MASK = 0x0f00,
57 TASK_FLAGS_MASK = 0xf000
58} ot_tasktype;
59
60typedef unsigned long ot_taskid;
61
62int workqueue_pushtask(int64_t sock, ot_tasktype tasktype);
63void workqueue_canceltask(int64_t sock);
64void workqueue_pushsuccess(ot_taskid taskid);
65ot_taskid workqueue_poptask(ot_tasktype *tasktype);
66int workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovector);
67int workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec);
68int64_t workqueue_popresult(size_t *iovec_entries, struct iovec **iovector, int *is_partial);
69
70void workqueue_init(void);
71void workqueue_deinit(void);
72void workqueue_wakeall(void);
73
74#endif
diff --git a/src/proxy.c b/src/proxy.c
new file mode 100644
index 0000000..8589db5
--- /dev/null
+++ b/src/proxy.c
@@ -0,0 +1,871 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* This relays sync traffic between trackers, and between multicast domains
5 that have no route to each other, so that no tracker has to reach out to
6 the outside world by itself. Defunc. */
7
8/* System */
9#include <arpa/inet.h>
10#include <ctype.h>
11#include <errno.h>
12#include <pthread.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <unistd.h>
18
19/* Libowfat */
20#include "io.h"
21
22/* Opentracker */
23#include "ot_batch.h"
24#include "ot_fdstate.h"
25#include "ot_ip.h"
26#include "ot_mutex.h"
27#include "ot_random.h"
28#include "ot_scan.h"
29#include "ot_socket.h"
30#include "ot_stats.h"
31#include "ot_vector.h"
32#include "trackerlogic.h"
33
34#include "ot_livesync.h"
35
36ot_ip6 g_serverip;
37uint16_t g_serverport = 9009;
38uint32_t g_tracker_id;
39ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5};
40int g_self_pipe[2];
41
42/* If you have more than 10 peers, don't use this proxy
43 Use 20 slots for 10 peers to have room for 10 incoming connection slots
44 */
45#define MAX_PEERS 20
46
47#define LIVESYNC_INCOMING_BUFFSIZE (256 * 256)
48#define STREAMSYNC_OUTGOING_BUFFSIZE (256 * 256)
49
50#define LIVESYNC_OUTGOING_BUFFSIZE_PEERS 1480
51#define LIVESYNC_OUTGOING_WATERMARK_PEERS (sizeof(ot_peer) + sizeof(ot_hash))
52#define LIVESYNC_MAXDELAY 15 /* seconds */
53
54/* The amount of time a complete sync cycle should take */
55#define OT_SYNC_INTERVAL_MINUTES 2
56
57/* So after each bucket wait 1 / OT_BUCKET_COUNT intervals */
58#define OT_SYNC_SLEEP (((OT_SYNC_INTERVAL_MINUTES) * 60 * 1000000) / (OT_BUCKET_COUNT))
59
60enum { OT_SYNC_PEER4, OT_SYNC_PEER6 };
61enum { FLAG_SERVERSOCKET = 1 };
62
63/* For incoming packets */
64static int g_socket_in = -1;
65static uint8_t g_inbuffer[LIVESYNC_INCOMING_BUFFSIZE];
66
67/* For outgoing packets */
68static int g_socket_out = -1;
69static uint8_t g_peerbuffer_start[LIVESYNC_OUTGOING_BUFFSIZE_PEERS];
70static uint8_t *g_peerbuffer_pos;
71static uint8_t *g_peerbuffer_highwater = g_peerbuffer_start + LIVESYNC_OUTGOING_BUFFSIZE_PEERS - LIVESYNC_OUTGOING_WATERMARK_PEERS;
72static ot_time g_next_packet_time;
73
74static void *livesync_worker(void *args);
75static void *streamsync_worker(void *args);
76static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer);
77
78void exerr(const char *message) {
79 fprintf(stderr, "%s\n", message);
80 exit(111);
81}
82
83void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) {
84 (void)ws;
85 (void)event;
86 (void)proto;
87 (void)event_data;
88}
89
90void livesync_bind_mcast(ot_ip6 ip, uint16_t port) {
91 if (!ip_is_v4mapped(ip))
92 exerr("v6 mcast support not yet available.");
93
94 if (g_socket_in != -1)
95 exerr("Error: Livesync listen ip specified twice.");
96
97 if ((g_socket_in = socket_mcast_join(groupip_1, ip, port)) == -1)
98 exerr("Error: Cant bind live sync incoming socket.");
99
100 if ((g_socket_out = socket_mcast_bind(ip, port, 1)) == -1)
101 exerr("Error: Cant bind live sync outgoing socket.");
102}
103
104size_t add_peer_to_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size) {
105 int exactmatch;
106 ot_torrent *torrent;
107 ot_peerlist *peer_list;
108 ot_peer *peer_dest;
109 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash);
110 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
111
112 torrent = vector_find_or_insert(torrents_list, (void *)hash, sizeof(ot_torrent), compare_size, &exactmatch);
113 if (!torrent)
114 return -1;
115
116 if (!exactmatch) {
117 /* Create a new torrent entry, then */
118 memcpy(torrent->hash, hash, sizeof(ot_hash));
119
120 if (!(torrent->peer_list6 = malloc(sizeof(ot_peerlist))) || !(torrent->peer_list4 = malloc(sizeof(ot_peerlist)))) {
121 vector_remove_torrent(torrents_list, torrent);
122 mutex_bucket_unlock_by_hash(hash, 0);
123 return -1;
124 }
125
126 memset(torrent->peer_list6, 0, sizeof(ot_peerlist));
127 memset(torrent->peer_list4, 0, sizeof(ot_peerlist));
128 }
129
130 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
131
132 /* Check for peer in torrent */
133 peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer, peer_size, &exactmatch);
134 if (!peer_dest) {
135 mutex_bucket_unlock_by_hash(hash, 0);
136 return -1;
137 }
138 /* Tell peer that it's fresh */
139 OT_PEERTIME(peer, peer_size) = 0;
140
141 /* If we hadn't had a match create peer there */
142 if (!exactmatch) {
143 peer_list->peer_count++;
144 if (OT_PEERFLAG_D(peer, peer_size) & PEER_FLAG_SEEDING)
145 peer_list->seed_count++;
146 }
147 memcpy(peer_dest, peer, peer_size);
148 mutex_bucket_unlock_by_hash(hash, 0);
149 return 0;
150}
151
152size_t remove_peer_from_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size) {
153 int exactmatch;
154 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash);
155 ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
156
157 if (exactmatch) {
158 ot_peerlist *peer_list = peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
159 switch (vector_remove_peer(&peer_list->peers, peer, peer_size)) {
160 case 2:
161 peer_list->seed_count--; /* Intentional fallthrough */
162 case 1:
163 peer_list->peer_count--; /* Intentional fallthrough */
164 default:
165 break;
166 }
167 }
168
169 mutex_bucket_unlock_by_hash(hash, 0);
170 return 0;
171}
172
173void free_peerlist(ot_peerlist *peer_list) {
174 if (peer_list->peers.data) {
175 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
176 ot_vector *bucket_list = (ot_vector *)(peer_list->peers.data);
177
178 while (peer_list->peers.size--)
179 free(bucket_list++->data);
180 }
181 free(peer_list->peers.data);
182 }
183 free(peer_list);
184}
185
186static void livesync_handle_peersync(ssize_t datalen, size_t peer_size) {
187 int off = sizeof(g_tracker_id) + sizeof(uint32_t);
188
189 fprintf(stderr, ".");
190
191 while ((ssize_t)(off + sizeof(ot_hash) + peer_size) <= datalen) {
192 ot_peer *peer = (ot_peer *)(g_inbuffer + off + sizeof(ot_hash));
193 ot_hash *hash = (ot_hash *)(g_inbuffer + off);
194
195 if (OT_PEERFLAG_D(peer, peer_size) & PEER_FLAG_STOPPED)
196 remove_peer_from_torrent_proxy(*hash, peer, peer_size);
197 else
198 add_peer_to_torrent_proxy(*hash, peer, peer_size);
199
200 off += sizeof(ot_hash) + peer_size;
201 }
202}
203
204int usage(char *self) {
205 fprintf(stderr, "Usage: %s -L <livesync_iface_ip> -l <listenip>:<listenport> -c <connectip>:<connectport>\n", self);
206 return 0;
207}
208
209enum {
210 FLAG_OUTGOING = 0x80,
211
212 FLAG_DISCONNECTED = 0x00,
213 FLAG_CONNECTING = 0x01,
214 FLAG_WAITTRACKERID = 0x02,
215 FLAG_CONNECTED = 0x03,
216
217 FLAG_MASK = 0x07
218};
219
220#define PROXYPEER_NEEDSCONNECT(flag) ((flag) == FLAG_OUTGOING)
221#define PROXYPEER_ISCONNECTED(flag) (((flag) & FLAG_MASK) == FLAG_CONNECTED)
222#define PROXYPEER_SETDISCONNECTED(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_DISCONNECTED)
223#define PROXYPEER_SETCONNECTING(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_CONNECTING)
224#define PROXYPEER_SETWAITTRACKERID(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_WAITTRACKERID)
225#define PROXYPEER_SETCONNECTED(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_CONNECTED)
226
227typedef struct {
228 int state; /* Whether we want to connect, how far our handshake is, etc. */
229 ot_ip6 ip; /* The peer to connect to */
230 uint16_t port; /* The peers port */
231 uint8_t indata[8192 * 16]; /* Any data not processed yet */
232 size_t indata_length; /* Length of unprocessed data */
233 uint32_t tracker_id; /* How the other end greeted */
234 int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */
235 ot_batch outdata; /* The batch containing our sync data */
236
237 size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */
238 uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */
239 uint8_t packet_type; /* Type of current packet */
240 uint32_t packet_tid; /* Tracker id for current packet */
241
242} proxy_peer;
243static void process_indata(proxy_peer *peer);
244
245void reset_info_block(proxy_peer *peer) {
246 peer->indata_length = 0;
247 peer->tracker_id = 0;
248 peer->fd = -1;
249 peer->packet_tcount = 0;
250 batch_reset(&peer->outdata);
251 PROXYPEER_SETDISCONNECTED(peer->state);
252}
253
254/* Number of connections to peers
255 * If a peer's IP is set, we try to reconnect, when the connection drops
256 * If we already have a connected tracker_id in our records for an _incoming_ connection, drop it
257 * Multiple connections to/from the same ip are okay, if tracker_id doesn't match
258 * Reconnect attempts occur only twice a minute
259 */
260static size_t g_connection_count;
261static ot_time g_connection_reconn;
262static proxy_peer g_connections[MAX_PEERS];
263
264static void handle_reconnects(void) {
265 size_t i;
266 for (i = 0; i < g_connection_count; ++i)
267 if (PROXYPEER_NEEDSCONNECT(g_connections[i].state)) {
268 int64_t newfd = socket_tcp_connect(g_serverip, g_serverport, g_connections[i].ip, g_connections[i].port);
269 fprintf(stderr, "(Re)connecting to peer...");
270 if (newfd == -1)
271 continue; /* No socket for you */
272 io_fd(newfd);
273 io_wantwrite(newfd); /* So we will be informed when it is connected */
274 fdstate_open(newfd, g_connections + i, 0);
275
276 /* Prepare connection info block */
277 reset_info_block(g_connections + i);
278 g_connections[i].fd = newfd;
279 PROXYPEER_SETCONNECTING(g_connections[i].state);
280 }
281 g_connection_reconn = time(NULL) + 30;
282}
283
284/* Handle incoming connection requests, check against whitelist */
285static void handle_accept(int64_t serversocket) {
286 int64_t newfd;
287 ot_ip6 ip;
288 uint16_t port;
289
290 while ((newfd = socket_accept(serversocket, ip, &port)) != -1) {
291
292 /* XXX some access control */
293
294 /* Put fd into a non-blocking mode */
295 io_nonblock(newfd);
296
297 if (!io_fd(newfd))
298 io_close(newfd);
299 else {
300 /* Find a new home for our incoming connection */
301 int i;
302 for (i = 0; i < MAX_PEERS; ++i)
303 if (g_connections[i].state == FLAG_DISCONNECTED)
304 break;
305 if (i == MAX_PEERS) {
306 fprintf(stderr, "No room for incoming connection.");
307 close(newfd);
308 continue;
309 }
310
311 /* Prepare connection info block */
312 reset_info_block(g_connections + i);
313 PROXYPEER_SETCONNECTING(g_connections[i].state);
314 g_connections[i].port = port;
315 g_connections[i].fd = newfd;
316
317 fdstate_open(newfd, g_connections + i, 0);
318
319 /* We expect the connecting side to begin with its tracker_id */
320 io_wantread(newfd);
321 }
322 }
323
324 return;
325}
326
327/* New sync data on the stream */
328static void handle_read(int64_t peersocket) {
329 int i;
330 int64_t datalen;
331 uint32_t tracker_id;
332 proxy_peer *peer = fdstate_cookie(peersocket);
333
334 if (!peer) {
335 /* Can't happen ;) */
336 io_close(peersocket);
337 return;
338 }
339 switch (peer->state & FLAG_MASK) {
340 case FLAG_DISCONNECTED:
341 io_close(peersocket);
342 break; /* Shouldnt happen */
343 case FLAG_CONNECTING:
344 case FLAG_WAITTRACKERID:
345 /* We want at least the first four bytes to come at once, to avoid keeping extra states (for now)
346 This also catches 0 bytes reads == EOF and negative values, denoting connection errors */
347 if (io_tryread(peersocket, (void *)&tracker_id, sizeof(tracker_id)) != sizeof(tracker_id))
348 goto close_socket;
349
350 /* See, if we already have a connection to that peer */
351 for (i = 0; i < MAX_PEERS; ++i)
352 if ((g_connections[i].state & FLAG_MASK) == FLAG_CONNECTED && g_connections[i].tracker_id == tracker_id) {
353 fprintf(stderr, "Peer already connected. Closing connection.\n");
354 goto close_socket;
355 }
356
357 /* Also no need for soliloquy */
358 if (tracker_id == g_tracker_id)
359 goto close_socket;
360
361 /* The new connection is good, send our tracker_id on incoming connections */
362 if (peer->state == FLAG_CONNECTING)
363 if (io_trywrite(peersocket, (void *)&g_tracker_id, sizeof(g_tracker_id)) != sizeof(g_tracker_id))
364 goto close_socket;
365
366 peer->tracker_id = tracker_id;
367 PROXYPEER_SETCONNECTED(peer->state);
368
369 if (peer->state & FLAG_OUTGOING)
370 fprintf(stderr, "succeeded.\n");
371 else
372 fprintf(stderr, "Incoming connection successful.\n");
373
374 break;
375 close_socket:
376 fprintf(stderr, "Handshake incomplete, closing socket\n");
377 io_close(peersocket);
378 reset_info_block(peer);
379 break;
380 case FLAG_CONNECTED:
381 /* Here we acutally expect data from peer
382 indata_length should be less than 20+256*7 bytes, for incomplete torrent entries */
383 datalen = io_tryread(peersocket, (void *)(peer->indata + peer->indata_length), sizeof(peer->indata) - peer->indata_length);
384 if (!datalen || datalen < -1) {
385 fprintf(stderr, "Connection closed by remote peer.\n");
386 io_close(peersocket);
387 reset_info_block(peer);
388 } else if (datalen > 0) {
389 peer->indata_length += datalen;
390 process_indata(peer);
391 }
392 break;
393 }
394}
395
396/* Can write new sync data to the stream */
397static void handle_write(int64_t peersocket) {
398 proxy_peer *peer = fdstate_cookie(peersocket);
399
400 if (!peer) {
401 /* Can't happen ;) */
402 io_close(peersocket);
403 return;
404 }
405
406 switch (peer->state & FLAG_MASK) {
407 case FLAG_DISCONNECTED:
408 default: /* Should not happen */
409 io_close(peersocket);
410 break;
411 case FLAG_CONNECTING:
412 /* Ensure that the connection is established and handle connection error */
413 if (peer->state & FLAG_OUTGOING && !socket_is_connected(peersocket)) {
414 fprintf(stderr, "failed\n");
415 reset_info_block(peer);
416 io_close(peersocket);
417 break;
418 }
419
420 if (io_trywrite(peersocket, (void *)&g_tracker_id, sizeof(g_tracker_id)) == sizeof(g_tracker_id)) {
421 PROXYPEER_SETWAITTRACKERID(peer->state);
422 io_dontwantwrite(peersocket);
423 io_wantread(peersocket);
424 } else {
425 fprintf(stderr, "Handshake incomplete, closing socket\n");
426 io_close(peersocket);
427 reset_info_block(peer);
428 }
429 break;
430 case FLAG_CONNECTED:
431 switch (batch_send(peersocket, &peer->outdata)) {
432 case BATCH_DONE:
433 io_dontwantwrite(peersocket);
434 break;
435 case BATCH_FAILED:
436 io_close(peersocket);
437 reset_info_block(peer);
438 break;
439 default: /* Normal operation or eagain */
440 break;
441 }
442 break;
443 }
444
445 return;
446}
447
448static void server_mainloop() {
449 int64_t sock;
450 uint32_t packet_type;
451
452 /* inlined livesync_init() */
453 memset(g_peerbuffer_start, 0, sizeof(g_peerbuffer_start));
454 g_peerbuffer_pos = g_peerbuffer_start;
455 memcpy(g_peerbuffer_pos, &g_tracker_id, sizeof(g_tracker_id));
456 packet_type = htonl(OT_SYNC_PEER);
457 memcpy(g_peerbuffer_pos + sizeof(g_tracker_id), &packet_type, sizeof(packet_type));
458 g_peerbuffer_pos += sizeof(g_tracker_id) + sizeof(uint32_t);
459 g_next_packet_time = time(NULL) + LIVESYNC_MAXDELAY;
460
461 while (1) {
462 /* See if we need to connect to anyone */
463 if (time(NULL) > g_connection_reconn)
464 handle_reconnects();
465
466 /* Wait for io events until next approx reconn check time */
467 io_waituntil2(30 * 1000);
468
469 /* Loop over readable sockets */
470 while ((sock = io_canread()) != -1) {
471 const void *cookie = fdstate_cookie(sock);
472 if ((uintptr_t)cookie == FLAG_SERVERSOCKET)
473 handle_accept(sock);
474 else
475 handle_read(sock);
476 }
477
478 /* Loop over writable sockets */
479 while ((sock = io_canwrite()) != -1)
480 handle_write(sock);
481
482 livesync_ticker();
483 }
484}
485
486static void panic(const char *routine) {
487 fprintf(stderr, "%s: %s\n", routine, strerror(errno));
488 exit(111);
489}
490
491static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) {
492 int64_t sock = socket_tcp_listen(ip, port, SOCKET_EXCLUSIVE);
493
494 if (sock == -1)
495 panic("socket_tcp_listen");
496
497 if (!io_fd(sock))
498 panic("io_fd");
499
500 fdstate_open(sock, (void *)FLAG_SERVERSOCKET, 0);
501 io_wantread(sock);
502 return sock;
503}
504
505static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
506 const char *s = src;
507 uint64_t value;
508 int off, bracket = 0;
509 while (isspace(*s))
510 ++s;
511 if (*s == '[')
512 ++s, ++bracket; /* for v6 style notation */
513 if (!(off = ip_parse(s, ip)))
514 return 0;
515 s += off;
516 if (*s == 0 || isspace(*s))
517 return s - src;
518 if (*s == ']' && bracket)
519 ++s;
520 if (!ip_is_v4mapped(ip)) {
521 if ((bracket && *(s) != ':') || (*(s) != '.'))
522 return 0;
523 s++;
524 } else {
525 if (*(s++) != ':')
526 return 0;
527 }
528 if (!(off = scan_uint(s, &value, 65535)))
529 return 0;
530 *port = (uint16_t)value;
531 return off + s - src;
532}
533
534int main(int argc, char **argv) {
535 static pthread_t sync_in_thread_id;
536 static pthread_t sync_out_thread_id;
537 ot_ip6 serverip;
538 uint16_t tmpport;
539 int scanon = 1, lbound = 0, sbound = 0;
540
541 random_init();
542 if (fdstate_init() == -1)
543 panic("fdstate_init");
544 g_tracker_id = random_uint32();
545
546 while (scanon) {
547 switch (getopt(argc, argv, ":l:c:L:h")) {
548 case -1:
549 scanon = 0;
550 break;
551 case 'l':
552 tmpport = 0;
553 if (!scan_ip6_port(optarg, serverip, &tmpport) || !tmpport) {
554 usage(argv[0]);
555 exit(1);
556 }
557 ot_try_bind(serverip, tmpport);
558 ++sbound;
559 break;
560 case 'c':
561 if (g_connection_count > MAX_PEERS / 2)
562 exerr("Connection limit exceeded.\n");
563 tmpport = 0;
564 if (!scan_ip6_port(optarg, g_connections[g_connection_count].ip, &g_connections[g_connection_count].port) || !g_connections[g_connection_count].port) {
565 usage(argv[0]);
566 exit(1);
567 }
568 g_connections[g_connection_count++].state = FLAG_OUTGOING;
569 break;
570 case 'L':
571 tmpport = 9696;
572 if (!scan_ip6_port(optarg, serverip, &tmpport) || !tmpport) {
573 usage(argv[0]);
574 exit(1);
575 }
576 livesync_bind_mcast(serverip, tmpport);
577 ++lbound;
578 break;
579 default:
580 case '?':
581 usage(argv[0]);
582 exit(1);
583 }
584 }
585
586 if (!lbound)
587 exerr("No livesync port bound.");
588 if (!g_connection_count && !sbound)
589 exerr("No streamsync port bound.");
590 pthread_create(&sync_in_thread_id, NULL, livesync_worker, NULL);
591 pthread_create(&sync_out_thread_id, NULL, streamsync_worker, NULL);
592
593 server_mainloop();
594 return 0;
595}
596
597static void *streamsync_worker(void *args) {
598 (void)args;
599 while (1) {
600 size_t bucket;
601 /* For each bucket... */
602 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
603 /* Get exclusive access to that bucket */
604 ot_vector *torrents_list = mutex_bucket_lock(bucket);
605 size_t tor_offset, count_def = 0, count_one = 0, count_two = 0, count_peers = 0;
606 size_t mem, mem_a = 0, mem_b = 0;
607 uint8_t *ptr = 0, *ptr_a, *ptr_b, *ptr_c;
608
609 if (!torrents_list->size)
610 goto unlock_continue;
611
612 /* For each torrent in this bucket.. */
613 for (tor_offset = 0; tor_offset < torrents_list->size; ++tor_offset) {
614 /* Address torrents members */
615 ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[tor_offset]).peer_list;
616 switch (peer_list->peer_count) {
617 case 2:
618 count_two++;
619 break;
620 case 1:
621 count_one++;
622 break;
623 case 0:
624 break;
625 default:
626 count_def++;
627 count_peers += peer_list->peer_count;
628 }
629 }
630
631 /* Maximal memory requirement: max 3 blocks, max torrents * 20 + max peers * 7 */
632 mem = 3 * (1 + 1 + 2) + (count_one + count_two) * (19 + 1) + count_def * (19 + 8) + (count_one + 2 * count_two + count_peers) * 7;
633
634 fprintf(stderr, "Mem: %zd\n", mem);
635
636 ptr = ptr_a = ptr_b = ptr_c = malloc(mem);
637 if (!ptr)
638 goto unlock_continue;
639
640 if (count_one > 4 || !count_def) {
641 mem_a = 1 + 1 + 2 + count_one * (19 + 7);
642 ptr_b += mem_a;
643 ptr_c += mem_a;
644 ptr_a[0] = 1; /* Offset 0: packet type 1 */
645 ptr_a[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS; /* Offset 1: the shared prefix */
646 ptr_a[2] = count_one >> 8;
647 ptr_a[3] = count_one & 255;
648 ptr_a += 4;
649 } else
650 count_def += count_one;
651
652 if (count_two > 4 || !count_def) {
653 mem_b = 1 + 1 + 2 + count_two * (19 + 14);
654 ptr_c += mem_b;
655 ptr_b[0] = 2; /* Offset 0: packet type 2 */
656 ptr_b[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS; /* Offset 1: the shared prefix */
657 ptr_b[2] = count_two >> 8;
658 ptr_b[3] = count_two & 255;
659 ptr_b += 4;
660 } else
661 count_def += count_two;
662
663 if (count_def) {
664 ptr_c[0] = 0; /* Offset 0: packet type 0 */
665 ptr_c[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS; /* Offset 1: the shared prefix */
666 ptr_c[2] = count_def >> 8;
667 ptr_c[3] = count_def & 255;
668 ptr_c += 4;
669 }
670
671 /* For each torrent in this bucket.. */
672 for (tor_offset = 0; tor_offset < torrents_list->size; ++tor_offset) {
673 /* Address torrents members */
674 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + tor_offset;
675 ot_peerlist *peer_list = torrent->peer_list;
676 ot_peer *peers = (ot_peer *)(peer_list->peers.data);
677 uint8_t **dst;
678
679 /* Determine destination slot */
680 count_peers = peer_list->peer_count;
681 switch (count_peers) {
682 case 0:
683 continue;
684 case 1:
685 dst = mem_a ? &ptr_a : &ptr_c;
686 break;
687 case 2:
688 dst = mem_b ? &ptr_b : &ptr_c;
689 break;
690 default:
691 dst = &ptr_c;
692 break;
693 }
694
695 /* Copy tail of info_hash, advance pointer */
696 memcpy(*dst, ((uint8_t *)torrent->hash) + 1, sizeof(ot_hash) - 1);
697 *dst += sizeof(ot_hash) - 1;
698
699 /* Encode peer count */
700 if (dst == &ptr_c)
701 while (count_peers) {
702 if (count_peers <= 0x7f)
703 *(*dst)++ = count_peers;
704 else
705 *(*dst)++ = 0x80 | (count_peers & 0x7f);
706 count_peers >>= 7;
707 }
708
709 /* Copy peers */
710 count_peers = peer_list->peer_count;
711 while (count_peers--) {
712 memcpy(*dst, peers++, OT_IP_SIZE + 3);
713 *dst += OT_IP_SIZE + 3;
714 }
715 free_peerlist(peer_list);
716 }
717
718 free(torrents_list->data);
719 memset(torrents_list, 0, sizeof(*torrents_list));
720 unlock_continue:
721 mutex_bucket_unlock(bucket, 0);
722
723 if (ptr) {
724 int i;
725
726 if (ptr_b > ptr_c)
727 ptr_c = ptr_b;
728 if (ptr_a > ptr_c)
729 ptr_c = ptr_a;
730 mem = ptr_c - ptr;
731
732 for (i = 0; i < MAX_PEERS; ++i) {
733 if (PROXYPEER_ISCONNECTED(g_connections[i].state)) {
734 void *tmp = malloc(mem);
735 if (tmp) {
736 memcpy(tmp, ptr, mem);
737 batch_add(&g_connections[i].outdata, tmp, mem);
738 io_wantwrite(g_connections[i].fd);
739 }
740 }
741 }
742
743 free(ptr);
744 }
745 usleep(OT_SYNC_SLEEP);
746 }
747 }
748 return 0;
749}
750
751static void livesync_issue_peersync() {
752 socket_mcast_send(g_socket_out, g_peerbuffer_start, (size_t)(g_peerbuffer_pos - g_peerbuffer_start), groupip_1, LIVESYNC_PORT);
753 g_peerbuffer_pos = g_peerbuffer_start + sizeof(g_tracker_id) + sizeof(uint32_t);
754 g_next_packet_time = time(NULL) + LIVESYNC_MAXDELAY;
755}
756
757void livesync_ticker() {
758 /* livesync_issue_peersync sets g_next_packet_time */
759 if (time(NULL) > g_next_packet_time && g_peerbuffer_pos > g_peerbuffer_start + sizeof(g_tracker_id))
760 livesync_issue_peersync();
761}
762
763static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer) {
764 // unsigned int i;
765
766 *g_peerbuffer_pos = prefix;
767 memcpy(g_peerbuffer_pos + 1, info_hash, sizeof(ot_hash) - 1);
768 memcpy(g_peerbuffer_pos + sizeof(ot_hash), peer, sizeof(ot_peer) - 1);
769
770#if 0
771 /* Dump info_hash */
772 for( i=0; i<sizeof(ot_hash); ++i )
773 printf( "%02X", g_peerbuffer_pos[i] );
774 putchar( ':' );
775#endif
776 g_peerbuffer_pos += sizeof(ot_hash);
777#if 0
778 printf( "%hhu.%hhu.%hhu.%hhu:%hu (%02X %02X)\n", g_peerbuffer_pos[0], g_peerbuffer_pos[1], g_peerbuffer_pos[2], g_peerbuffer_pos[3],
779 g_peerbuffer_pos[4] | ( g_peerbuffer_pos[5] << 8 ), g_peerbuffer_pos[6], g_peerbuffer_pos[7] );
780#endif
781 g_peerbuffer_pos += sizeof(ot_peer);
782
783 if (g_peerbuffer_pos >= g_peerbuffer_highwater)
784 livesync_issue_peersync();
785}
786
787static void process_indata(proxy_peer *peer) {
788 size_t consumed, peers;
789 uint8_t *data = peer->indata, *hash;
790 uint8_t *dataend = data + peer->indata_length;
791
792 while (1) {
793 /* If we're not inside of a packet, make a new one */
794 if (!peer->packet_tcount) {
795 /* Ensure the header is complete or postpone processing */
796 if (data + 4 > dataend)
797 break;
798 peer->packet_type = data[0];
799 peer->packet_tprefix = data[1];
800 peer->packet_tcount = data[2] * 256 + data[3];
801 data += 4;
802 printf("type: %hhu, prefix: %02X, torrentcount: %zd\n", peer->packet_type, peer->packet_tprefix, peer->packet_tcount);
803 }
804
805 /* Ensure size for a minimal torrent block */
806 if (data + sizeof(ot_hash) + OT_IP_SIZE + 3 > dataend)
807 break;
808
809 /* Advance pointer to peer count or peers */
810 hash = data;
811 data += sizeof(ot_hash) - 1;
812
813 /* Type 0 has peer count encoded before each peers */
814 peers = peer->packet_type;
815 if (!peers) {
816 int shift = 0;
817 do
818 peers |= (0x7f & *data) << (7 * shift);
819 while (*(data++) & 0x80 && shift++ < 6);
820 }
821#if 0
822printf( "peers: %zd\n", peers );
823#endif
824 /* Ensure enough data being read to hold all peers */
825 if (data + (OT_IP_SIZE + 3) * peers > dataend) {
826 data = hash;
827 break;
828 }
829 while (peers--) {
830 livesync_proxytell(peer->packet_tprefix, hash, data);
831 data += OT_IP_SIZE + 3;
832 }
833 --peer->packet_tcount;
834 }
835
836 consumed = data - peer->indata;
837 memmove(peer->indata, data, peer->indata_length - consumed);
838 peer->indata_length -= consumed;
839}
840
841static void *livesync_worker(void *args) {
842 (void)args;
843 while (1) {
844 ot_ip6 in_ip;
845 uint16_t in_port;
846 uint32_t packet_type;
847 ssize_t datalen = socket_recv(g_socket_in, g_inbuffer, LIVESYNC_INCOMING_BUFFSIZE, in_ip, &in_port);
848
849 /* Expect at least tracker id and packet type */
850 if (datalen <= (ssize_t)(sizeof(g_tracker_id) + sizeof(uint32_t)))
851 continue;
852 if (!memcmp(g_inbuffer, &g_tracker_id, sizeof(g_tracker_id))) {
853 /* drop packet coming from ourselves */
854 continue;
855 }
856 memcpy(&packet_type, g_inbuffer + sizeof(g_tracker_id), sizeof(packet_type));
857
858 switch (ntohl(packet_type)) {
859 case OT_SYNC_PEER4:
860 livesync_handle_peersync(datalen, OT_PEER_SIZE4);
861 break;
862 case OT_SYNC_PEER6:
863 livesync_handle_peersync(datalen, OT_PEER_SIZE6);
864 break;
865 default:
866 // fprintf( stderr, "Received an unknown live sync packet type %u.\n", ntohl( packet_type ) );
867 break;
868 }
869 }
870 return 0;
871}
diff --git a/src/trackerlogic.c b/src/trackerlogic.c
new file mode 100644
index 0000000..9565090
--- /dev/null
+++ b/src/trackerlogic.c
@@ -0,0 +1,628 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <arpa/inet.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11
12/* Opentracker */
13#include "ot_accesslist.h"
14#include "ot_clean.h"
15#include "ot_fmt.h"
16#include "ot_fullscrape.h"
17#include "ot_http.h"
18#include "ot_ip.h"
19#include "ot_livesync.h"
20#include "ot_mutex.h"
21#include "ot_workqueue.h"
22#include "ot_random.h"
23#include "ot_stats.h"
24#include "ot_udp.h"
25#include "ot_vector.h"
26#include "trackerlogic.h"
27
28/* Forward declaration */
29size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto);
30
31void free_peerlist(ot_peerlist *peer_list) {
32 if (peer_list && peer_list->peers.data) {
33 if (OT_PEERLIST_HASBUCKETS(peer_list))
34 vector_clean_list((ot_vector *)peer_list->peers.data, peer_list->peers.size);
35 else
36 free(peer_list->peers.data);
37 }
38 free(peer_list);
39}
40
41void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count) {
42 int exactmatch;
43 ot_torrent *torrent;
44 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash);
45
46 if (!accesslist_hashisvalid(hash)) {
47 mutex_bucket_unlock_by_hash(hash, 0);
48 return;
49 }
50
51 torrent = vector_find_or_insert(torrents_list, (void *)hash, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
52 if (!torrent || exactmatch) {
53 mutex_bucket_unlock_by_hash(hash, 0);
54 return;
55 }
56
57 /* Create a new torrent entry, then */
58 memset(torrent, 0, sizeof(ot_torrent));
59 memcpy(torrent->hash, hash, sizeof(ot_hash));
60
61 if (!(torrent->peer_list6 = malloc(sizeof(ot_peerlist))) || !(torrent->peer_list4 = malloc(sizeof(ot_peerlist)))) {
62 vector_remove_torrent(torrents_list, torrent);
63 mutex_bucket_unlock_by_hash(hash, 0);
64 return;
65 }
66
67 memset(torrent->peer_list6, 0, sizeof(ot_peerlist));
68 memset(torrent->peer_list4, 0, sizeof(ot_peerlist));
69 torrent->peer_list6->base = base;
70 torrent->peer_list4->base = base;
71 torrent->peer_list6->down_count = down_count;
72 torrent->peer_list4->down_count = down_count;
73
74 mutex_bucket_unlock_by_hash(hash, 1);
75}
76
77size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount) {
78 int exactmatch, delta_torrentcount = 0;
79 ot_torrent *torrent;
80 ot_peer *peer_dest;
81 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash);
82 ot_peerlist *peer_list;
83 size_t peer_size; /* initialized in next line */
84 ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size);
85
86 if (!accesslist_hashisvalid(*ws->hash)) {
87 mutex_bucket_unlock_by_hash(*ws->hash, 0);
88 if (proto == FLAG_TCP) {
89 const char invalid_hash[] = "d14:failure reason63:Requested download is not authorized for use with this tracker.e";
90 memcpy(ws->reply, invalid_hash, strlen(invalid_hash));
91 return strlen(invalid_hash);
92 }
93 return 0;
94 }
95
96 torrent = vector_find_or_insert(torrents_list, (void *)ws->hash, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
97 if (!torrent) {
98 mutex_bucket_unlock_by_hash(*ws->hash, 0);
99 return 0;
100 }
101
102 if (!exactmatch) {
103 /* Create a new torrent entry, then */
104 memset(torrent, 0, sizeof(ot_torrent));
105 memcpy(torrent->hash, *ws->hash, sizeof(ot_hash));
106
107 if (!(torrent->peer_list6 = malloc(sizeof(ot_peerlist))) || !(torrent->peer_list4 = malloc(sizeof(ot_peerlist)))) {
108 vector_remove_torrent(torrents_list, torrent);
109 mutex_bucket_unlock_by_hash(*ws->hash, 0);
110 return 0;
111 }
112
113 memset(torrent->peer_list6, 0, sizeof(ot_peerlist));
114 memset(torrent->peer_list4, 0, sizeof(ot_peerlist));
115 delta_torrentcount = 1;
116 } else
117 clean_single_torrent(torrent);
118
119 torrent->peer_list6->base = time_now_minutes();
120 torrent->peer_list4->base = time_now_minutes();
121
122 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
123
124 /* Check for peer in torrent */
125 peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer_src, peer_size, &exactmatch);
126 if (!peer_dest) {
127 mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount);
128 return 0;
129 }
130
131 /* Tell peer that it's fresh */
132 OT_PEERTIME(ws->peer, OT_PEER_SIZE6) = 0;
133
134 /* Sanitize flags: Whoever claims to have completed download, must be a seeder */
135 if ((OT_PEERFLAG(ws->peer) & (PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING)) == PEER_FLAG_COMPLETED)
136 OT_PEERFLAG(ws->peer) ^= PEER_FLAG_COMPLETED;
137
138 /* If we hadn't had a match create peer there */
139 if (!exactmatch) {
140
141#ifdef WANT_SYNC_LIVE
142 if (proto == FLAG_MCA)
143 OT_PEERFLAG(ws->peer) |= PEER_FLAG_FROM_SYNC;
144 else
145 livesync_tell(ws);
146#endif
147
148 peer_list->peer_count++;
149 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED) {
150 peer_list->down_count++;
151 stats_issue_event(ws, EVENT_COMPLETED, 0, 0);
152 }
153 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING)
154 peer_list->seed_count++;
155
156 } else {
157 stats_issue_event(ws, EVENT_RENEW, 0, OT_PEERTIME(peer_dest, peer_size));
158#ifdef WANT_SPOT_WOODPECKER
159 if ((OT_PEERTIME(peer_dest, peer_size) > 0) && (OT_PEERTIME(peer_dest, peer_size) < 20))
160 stats_issue_event(ws, EVENT_WOODPECKER, 0, (uintptr_t)&ws->peer);
161#endif
162#ifdef WANT_SYNC_LIVE
163 /* Won't live sync peers that come back too fast. Only exception:
164 fresh "completed" reports */
165 if (proto != FLAG_MCA) {
166 if (OT_PEERTIME(peer_dest, peer_size) > OT_CLIENT_SYNC_RENEW_BOUNDARY ||
167 (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED)))
168 livesync_tell(ws);
169 }
170#endif
171
172 if ((OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_SEEDING) && !(OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING))
173 peer_list->seed_count--;
174 if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_SEEDING) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING))
175 peer_list->seed_count++;
176 if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED)) {
177 peer_list->down_count++;
178 stats_issue_event(ws, EVENT_COMPLETED, 0, 0);
179 }
180 if (OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED)
181 OT_PEERFLAG(ws->peer) |= PEER_FLAG_COMPLETED;
182 }
183
184 memcpy(peer_dest, peer_src, peer_size);
185#ifdef WANT_SYNC
186 if (proto == FLAG_MCA) {
187 mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount);
188 return 0;
189 }
190#endif
191
192 ws->reply_size = return_peers_for_torrent(ws, torrent, amount, ws->reply, proto);
193 mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount);
194 return ws->reply_size;
195}
196
197static size_t return_peers_all(ot_peerlist *peer_list, size_t peer_size, char *reply) {
198 size_t bucket, num_buckets = 1;
199 ot_vector *bucket_list = &peer_list->peers;
200 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
201 size_t result = compare_size * peer_list->peer_count;
202 char *r_end = reply + result;
203
204 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
205 num_buckets = bucket_list->size;
206 bucket_list = (ot_vector *)bucket_list->data;
207 }
208
209 for (bucket = 0; bucket < num_buckets; ++bucket) {
210 ot_peer *peers = bucket_list[bucket].data;
211 size_t peer_count = bucket_list[bucket].size;
212 while (peer_count--) {
213 if (OT_PEERFLAG_D(peers, peer_size) & PEER_FLAG_SEEDING) {
214 r_end -= compare_size;
215 memcpy(r_end, peers, compare_size);
216 } else {
217 memcpy(reply, peers, compare_size);
218 reply += compare_size;
219 }
220 peers += peer_size;
221 }
222 }
223 return result;
224}
225
226static size_t return_peers_selection(struct ot_workstruct *ws, ot_peerlist *peer_list, size_t peer_size, size_t amount, char *reply) {
227 size_t bucket_offset, bucket_index = 0, num_buckets = 1;
228 ot_vector *bucket_list = &peer_list->peers;
229 unsigned int shifted_pc = peer_list->peer_count;
230 unsigned int shifted_step = 0;
231 unsigned int shift = 0;
232 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
233 size_t result = compare_size * amount;
234 char *r_end = reply + result;
235
236 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
237 num_buckets = bucket_list->size;
238 bucket_list = (ot_vector *)bucket_list->data;
239 }
240
241 /* Make fixpoint arithmetic as exact as possible */
242#define MAXPRECBIT (1 << (8 * sizeof(int) - 3))
243 while (!(shifted_pc & MAXPRECBIT)) {
244 shifted_pc <<= 1;
245 shift++;
246 }
247 shifted_step = shifted_pc / amount;
248#undef MAXPRECBIT
249
250 /* Initialize somewhere in the middle of peers so that
251 fixpoint's aliasing doesn't alway miss the same peers */
252 bucket_offset = nrand48(ws->rand48_state) % peer_list->peer_count;
253
254 while (amount--) {
255 ot_peer *peer;
256
257 /* This is the aliased, non shifted range, next value may fall into */
258 unsigned int diff = (((amount + 1) * shifted_step) >> shift) - ((amount * shifted_step) >> shift);
259 bucket_offset += 1 + nrand48(ws->rand48_state) % diff;
260
261 while (bucket_offset >= bucket_list[bucket_index].size) {
262 bucket_offset -= bucket_list[bucket_index].size;
263 bucket_index = (bucket_index + 1) % num_buckets;
264 }
265 peer = (ot_peer*)bucket_list[bucket_index].data + peer_size * bucket_offset;
266 if (OT_PEERFLAG_D(peer, peer_size) & PEER_FLAG_SEEDING) {
267 r_end -= compare_size;
268 memcpy(r_end, peer, compare_size);
269 } else {
270 memcpy(reply, peer, compare_size);
271 reply += compare_size;
272 }
273 }
274 return result;
275}
276
277static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) {
278 char *r = reply;
279 size_t peer_size = peer_size_from_peer6(&ws->peer);
280 ot_peerlist *peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
281 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
282 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
283
284 if (amount > peer_list->peer_count)
285 amount = peer_list->peer_count;
286
287 *(uint32_t *)(r + 0) = htonl(OT_CLIENT_REQUEST_INTERVAL_RANDOM);
288 *(uint32_t *)(r + 4) = htonl(peer_count - seed_count);
289 *(uint32_t *)(r + 8) = htonl(seed_count);
290 r += 12;
291
292 if (amount) {
293 if (amount == peer_list->peer_count)
294 r += return_peers_all(peer_list, peer_size, r);
295 else
296 r += return_peers_selection(ws, peer_list, peer_size, amount, r);
297 }
298 return r - reply;
299}
300
301static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) {
302 char *r = reply;
303 const char *r_end = ws->outbuf + G_OUTBUF_SIZE;
304 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
305 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
306 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
307 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count;
308
309 /* Simple case: amount of peers in both lists is less than requested, here we return all results */
310 size_t amount_v4 = torrent->peer_list4->peer_count;
311 size_t amount_v6 = torrent->peer_list6->peer_count;
312
313 /* Complex case: both lists have more than enough entries and we need to split between v4 and v6 clients */
314 if (amount_v4 + amount_v6 > amount) {
315 size_t amount_left, percent_v6 = 0, percent_v4 = 0, left_v6, left_v4;
316 const size_t SCALE = 1024;
317
318 /* If possible, fill at least a quarter of peer from each family */
319 if (amount / 4 <= amount_v4)
320 amount_v4 = amount / 4;
321 if (amount / 4 <= amount_v6)
322 amount_v6 = amount / 4;
323
324 /* Fill the rest according to which family's pool provides more peers */
325 amount_left = amount - (amount_v4 + amount_v6);
326
327 left_v4 = torrent->peer_list4->peer_count - amount_v4;
328 left_v6 = torrent->peer_list6->peer_count - amount_v6;
329
330 if (left_v4 + left_v6) {
331 percent_v4 = (SCALE * left_v4) / (left_v4 + left_v6);
332 percent_v6 = (SCALE * left_v6) / (left_v4 + left_v6);
333 }
334
335 amount_v4 += (amount_left * percent_v4) / SCALE;
336 amount_v6 += (amount_left * percent_v6) / SCALE;
337
338 /* Integer division rounding can leave out a peer */
339 if (amount_v4 + amount_v6 < amount && amount_v6 < torrent->peer_list6->peer_count)
340 ++amount_v6;
341 if (amount_v4 + amount_v6 < amount && amount_v4 < torrent->peer_list4->peer_count)
342 ++amount_v4;
343 }
344
345 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval,
346 erval / 2);
347
348 if (amount_v4) {
349 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4);
350 if (amount_v4 == torrent->peer_list4->peer_count)
351 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r);
352 else
353 r += return_peers_selection(ws, torrent->peer_list4, OT_PEER_SIZE4, amount_v4, r);
354 }
355
356 if (amount_v6) {
357 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6);
358 if (amount_v6 == torrent->peer_list6->peer_count)
359 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r);
360 else
361 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r);
362 }
363
364 fmt_append(&r, r_end, "e");
365
366 return r - reply;
367}
368
369/* Compiles a list of random peers for a torrent
370 * Reply must have enough space to hold:
371 * 92 + 6 * amount bytes for TCP/IPv4
372 * 92 + 18 * amount bytes for TCP/IPv6
373 * 12 + 6 * amount bytes for UDP/IPv4
374 * 12 + 18 * amount bytes for UDP/IPv6
375 * Does not yet check not to return self
376 */
377size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto) {
378 return proto == FLAG_TCP ? return_peers_for_torrent_tcp(ws, torrent, amount, reply) : return_peers_for_torrent_udp(ws, torrent, amount, reply);
379}
380
381/* Fetches scrape info for a specific torrent */
382size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
383 int exactmatch, delta_torrentcount = 0;
384 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash);
385 ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
386
387 if (!exactmatch) {
388 memset(reply, 0, 12);
389 } else {
390 uint32_t *r = (uint32_t *)reply;
391
392 if (clean_single_torrent(torrent)) {
393 vector_remove_torrent(torrents_list, torrent);
394 memset(reply, 0, 12);
395 delta_torrentcount = -1;
396 } else {
397 r[0] = htonl(torrent->peer_list6->seed_count + torrent->peer_list4->seed_count);
398 r[1] = htonl(torrent->peer_list6->down_count + torrent->peer_list4->down_count);
399 r[2] = htonl(torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
400 }
401 }
402 mutex_bucket_unlock_by_hash(hash, delta_torrentcount);
403 return 12;
404}
405
406/* Fetches scrape info for a specific torrent */
407void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) {
408 int exactmatch;
409 size_t i;
410
411 fmt_append(reply, reply_end, "d5:filesd");
412
413 for (i = 0; i < amount; ++i) {
414 int delta_torrentcount = 0;
415 ot_hash const *hash = hash_list + i;
416 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*hash);
417 ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
418
419 if (exactmatch) {
420 if (clean_single_torrent(torrent)) {
421 vector_remove_torrent(torrents_list, torrent);
422 delta_torrentcount = -1;
423 } else {
424 fmt_append(reply, reply_end, "20:");
425 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash));
426 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee",
427 torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count,
428 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
429 }
430 }
431 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount);
432 }
433
434 fmt_append(reply, reply_end, "ee");
435}
436
437static ot_peerlist dummy_list;
438size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
439 int exactmatch;
440 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash);
441 ot_torrent *torrent = vector_binary_search(ws->hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
442 ot_peerlist *peer_list = &dummy_list;
443 size_t peer_size; /* initialized in next line */
444 ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size);
445 size_t peer_count = 0, seed_count = 0;
446
447#ifdef WANT_SYNC_LIVE
448 if (proto != FLAG_MCA) {
449 OT_PEERFLAG(ws->peer) |= PEER_FLAG_STOPPED;
450 livesync_tell(ws);
451 }
452#endif
453
454 if (exactmatch) {
455 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
456 switch (vector_remove_peer(&peer_list->peers, peer_src, peer_size)) {
457 case 2:
458 peer_list->seed_count--; /* Intentional fallthrough */
459 case 1:
460 peer_list->peer_count--; /* Intentional fallthrough */
461 default:
462 break;
463 }
464
465 peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
466 seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
467 }
468
469 if (proto == FLAG_TCP) {
470 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
471 char *r = ws->reply;
472
473 fmt_append(&r, ws->outbuf + G_OUTBUF_SIZE, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count,
474 erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4);
475 ws->reply_size = r - ws->reply;
476 }
477
478 /* Handle UDP reply */
479 if (proto == FLAG_UDP) {
480 ((uint32_t *)ws->reply)[2] = htonl(OT_CLIENT_REQUEST_INTERVAL_RANDOM);
481 ((uint32_t *)ws->reply)[3] = htonl(peer_count - seed_count);
482 ((uint32_t *)ws->reply)[4] = htonl(seed_count);
483 ws->reply_size = 20;
484 }
485
486 mutex_bucket_unlock_by_hash(*ws->hash, 0);
487 return ws->reply_size;
488}
489
490void iterate_all_torrents(int (*for_each)(ot_torrent *torrent, uintptr_t data), uintptr_t data) {
491 size_t bucket, j;
492
493 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
494 ot_vector *torrents_list = mutex_bucket_lock(bucket);
495 ot_torrent *torrents = (ot_torrent *)(torrents_list->data);
496
497 for (j = 0; j < torrents_list->size; ++j)
498 if (for_each(torrents + j, data))
499 break;
500
501 mutex_bucket_unlock(bucket, 0);
502 if (!g_opentracker_running)
503 return;
504 }
505}
506
507ot_peer *peer_from_peer6(ot_peer6 *peer, size_t *peer_size) {
508 ot_ip6 *ip = (ot_ip6 *)peer;
509 if (!ip_is_v4mapped(ip)) {
510 *peer_size = OT_PEER_SIZE6;
511 return (ot_peer *)peer;
512 }
513 *peer_size = OT_PEER_SIZE4;
514 return (ot_peer *)(((uint8_t *)peer) + 12);
515}
516
517size_t peer_size_from_peer6(ot_peer6 *peer) {
518 ot_ip6 *ip = (ot_ip6 *)peer;
519 if (!ip_is_v4mapped(ip))
520 return OT_PEER_SIZE6;
521 return OT_PEER_SIZE4;
522}
523
524#ifdef _DEBUG_RANDOMTORRENTS
525void trackerlogic_add_random_torrents(size_t amount) {
526 struct ot_workstruct ws;
527 memset(&ws, 0, sizeof(ws));
528
529 ws.inbuf = malloc(G_INBUF_SIZE);
530 ws.outbuf = malloc(G_OUTBUF_SIZE);
531 ws.reply = ws.outbuf;
532 ws.hash = (ot_hash *)ws.inbuf;
533
534 while (amount--) {
535 random_bytes(ws.hash, sizeof(ot_hash));
536 random_bytes(&ws.peer, sizeof(ws.peer));
537
538 OT_PEERFLAG(ws.peer) &= PEER_FLAG_SEEDING | PEER_FLAG_COMPLETED | PEER_FLAG_STOPPED;
539
540 add_peer_to_torrent_and_return_peers(FLAG_TCP, &ws, 1);
541 }
542
543 free(ws.inbuf);
544 free(ws.outbuf);
545}
546#endif
547
548void exerr(const char *message) {
549 fprintf(stderr, "%s\n", message);
550 exit(111);
551}
552
553void trackerlogic_init(void) {
554 g_tracker_id = random_uint32();
555
556 if (!g_stats_path)
557 g_stats_path = "stats";
558 g_stats_path_len = strlen(g_stats_path);
559
560 /* Initialise background worker threads */
561 mutex_init();
562 workqueue_init();
563 clean_init();
564 fullscrape_init();
565 accesslist_init();
566 livesync_init();
567 stats_init();
568}
569
570int workstruct_init(struct ot_workstruct *ws) {
571 memset(ws, 0, sizeof(*ws));
572 ws->inbuf = malloc(G_INBUF_SIZE);
573 ws->outbuf = malloc(G_OUTBUF_SIZE);
574#ifdef _DEBUG_HTTPERROR
575 ws->debugbuf = malloc(G_DEBUGBUF_SIZE);
576#endif
577 if (!ws->inbuf || !ws->outbuf)
578 return -1;
579
580 /* Picking which peers to hand out starts here, so two workers seeded alike
581 would hand out alike */
582 random_bytes(ws->rand48_state, sizeof(ws->rand48_state));
583
584 stats_counters_register(ws);
585 return 0;
586}
587
588void workstruct_deinit(struct ot_workstruct *ws) {
589 /* The tally goes to the global before this struct goes out of scope */
590 stats_counters_retire(ws);
591
592 free(ws->inbuf);
593 free(ws->outbuf);
594#ifdef _DEBUG_HTTPERROR
595 free(ws->debugbuf);
596#endif
597}
598
599void trackerlogic_deinit(void) {
600 int delta_torrentcount = 0;
601 size_t bucket, j;
602
603 /* Stop the background workers first */
604 stats_deinit();
605 livesync_deinit();
606 accesslist_deinit();
607 fullscrape_deinit();
608 clean_deinit();
609
610 /* Free all torrents... */
611 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
612 ot_vector *torrents_list = mutex_bucket_lock(bucket);
613 if (torrents_list->size) {
614 for (j = 0; j < torrents_list->size; ++j) {
615 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + j;
616 free_peerlist(torrent->peer_list6);
617 free_peerlist(torrent->peer_list4);
618 delta_torrentcount -= 1;
619 }
620 free(torrents_list->data);
621 }
622 mutex_bucket_unlock(bucket, delta_torrentcount);
623 }
624
625 /* Release mutexes */
626 workqueue_deinit();
627 mutex_deinit();
628}
diff --git a/src/trackerlogic.h b/src/trackerlogic.h
new file mode 100644
index 0000000..7292d36
--- /dev/null
+++ b/src/trackerlogic.h
@@ -0,0 +1,283 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_TRACKERLOGIC_H__
5#define OT_TRACKERLOGIC_H__
6
7#include <signal.h>
8#include <stdatomic.h>
9#include <stdint.h>
10#include <stdlib.h>
11#include <sys/time.h>
12#include <sys/types.h>
13#include <time.h>
14
15typedef uint8_t ot_hash[20];
16typedef time_t ot_time;
17typedef char ot_ip6[16];
18typedef struct {
19 ot_ip6 address;
20 int bits;
21} ot_net;
22/* List of peers should fit in a single UDP packet (around 1200 bytes) */
23#define OT_MAX_PEERS_UDP6 66
24#define OT_MAX_PEERS_UDP4 200
25
26#define OT_IP_SIZE6 16
27#define OT_IP_SIZE4 4
28#define OT_PORT_SIZE 2
29#define OT_FLAG_SIZE 1
30#define OT_TIME_SIZE 1
31
32/* Some tracker behaviour tunable */
33#ifndef OT_CLIENT_TIMEOUT
34#define OT_CLIENT_TIMEOUT 30
35#endif
36#ifndef OT_CLIENT_TIMEOUT_CHECKINTERVAL
37#define OT_CLIENT_TIMEOUT_CHECKINTERVAL 10
38#endif
39#define OT_CLIENT_TIMEOUT_SEND (60 * 15)
40#define OT_CLIENT_REQUEST_INTERVAL (60 * 30)
41#define OT_CLIENT_REQUEST_VARIATION (60 * 6)
42
43#define OT_TORRENT_TIMEOUT_HOURS 24
44#define OT_TORRENT_TIMEOUT (60 * OT_TORRENT_TIMEOUT_HOURS)
45
46#define OT_CLIENT_REQUEST_INTERVAL_RANDOM \
47 (OT_CLIENT_REQUEST_INTERVAL - OT_CLIENT_REQUEST_VARIATION / 2 + (int)(nrand48(ws->rand48_state) % OT_CLIENT_REQUEST_VARIATION))
48
49/* If WANT_MODEST_FULLSCRAPES is on, ip addresses may not
50 fullscrape more frequently than this amount in seconds */
51#define OT_MODEST_PEER_TIMEOUT (60 * 5)
52
53/* If peers come back before 10 minutes, don't live sync them */
54#define OT_CLIENT_SYNC_RENEW_BOUNDARY 10
55
56/* Number of tracker admin ip addresses allowed */
57#define OT_ADMINIP_MAX 64
58#define OT_MAX_THREADS 64
59
60/* Number of minutes after announce before peer is removed */
61#define OT_PEER_TIMEOUT 45
62
63/* We maintain a list of 1024 pointers to sorted list of ot_torrent structs
64 Sort key is, of course, its hash */
65/* Set from the Makefile, 8 to 16. More buckets means shorter vectors and a
66 cheaper insert, which only pays for a tracker carrying millions of
67 torrents; see DESIGN. */
68#ifndef OT_BUCKET_COUNT_BITS
69#define OT_BUCKET_COUNT_BITS 10
70#endif
71
72#define OT_BUCKET_COUNT (1 << OT_BUCKET_COUNT_BITS)
73#define OT_BUCKET_COUNT_SHIFT (32 - OT_BUCKET_COUNT_BITS)
74
75/* if _DEBUG_RANDOMTORRENTS is set, this is the amount of torrents to create
76 on startup */
77#define RANDOMTORRENTS (1024 * 1024 * 1)
78
79#if defined(CLOCK_REALTIME_COARSE)
80#define OT_CLOCK_CHEAP CLOCK_REALTIME_COARSE /* Linux */
81#elif defined(CLOCK_REALTIME_FAST)
82#define OT_CLOCK_CHEAP CLOCK_REALTIME_FAST /* the BSDs */
83#else
84#define OT_CLOCK_CHEAP CLOCK_REALTIME /* macOS and whatever else */
85#endif
86
87static inline ot_time time_now_seconds(void) {
88 struct timespec now;
89 clock_gettime(OT_CLOCK_CHEAP, &now);
90 return (ot_time)now.tv_sec;
91}
92
93static inline ot_time time_now_minutes(void) { return time_now_seconds() / 60; }
94
95/* For the deadlines the tracker sets on itself, which read better as the
96 span they allow than as an addition to the current second */
97static inline ot_time time_future_seconds(int span) { return time_now_seconds() + span; }
98/* Cleared from the interrupt handler and read by every worker */
99extern _Atomic int g_opentracker_running;
100
101/* Sent to a thread parked in a blocking read */
102#define OT_SIGNAL_WAKE SIGUSR1
103
104
105extern uint32_t g_tracker_id;
106typedef enum { FLAG_TCP, FLAG_UDP, FLAG_MCA, FLAG_SELFPIPE } PROTO_FLAG;
107
108#define OT_PEER_COMPARE_SIZE6 ((OT_IP_SIZE6) + (OT_PORT_SIZE))
109#define OT_PEER_COMPARE_SIZE4 ((OT_IP_SIZE4) + (OT_PORT_SIZE))
110#define OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(PEER_SIZE) ((PEER_SIZE) - (OT_TIME_SIZE) - (OT_FLAG_SIZE))
111
112#define OT_PEER_SIZE6 ((OT_TIME_SIZE) + (OT_FLAG_SIZE) + (OT_PEER_COMPARE_SIZE6))
113#define OT_PEER_SIZE4 ((OT_TIME_SIZE) + (OT_FLAG_SIZE) + (OT_PEER_COMPARE_SIZE4))
114
115typedef uint8_t ot_peer; /* Generic pointer to a v6 or v4 peer */
116typedef uint8_t ot_peer6[OT_PEER_SIZE6];
117typedef uint8_t ot_peer4[OT_PEER_SIZE4];
118static const uint8_t PEER_FLAG_SEEDING = 0x80;
119static const uint8_t PEER_FLAG_COMPLETED = 0x40;
120static const uint8_t PEER_FLAG_STOPPED = 0x20;
121static const uint8_t PEER_FLAG_FROM_SYNC = 0x10;
122static const uint8_t PEER_FLAG_LEECHING = 0x00;
123
124/* Takes an ot_peer6 and returns the proper pointer to the peer and sets peer_size */
125ot_peer *peer_from_peer6(ot_peer6 *peer, size_t *peer_size);
126size_t peer_size_from_peer6(ot_peer6 *peer);
127
128/* New style */
129#define OT_SETIP(peer, ip) memcpy((uint8_t *)(peer), (ip), OT_IP_SIZE6)
130#define OT_SETPORT(peer, port) memcpy(((uint8_t *)(peer)) + (OT_IP_SIZE6), (port), 2)
131#define OT_PEERFLAG(peer) (((uint8_t *)(peer))[(OT_IP_SIZE6) + 2])
132#define OT_PEERFLAG_D(peer, peersize) (((uint8_t *)(peer))[(peersize) - 2])
133#define OT_PEERTIME(peer, peersize) (((uint8_t *)(peer))[(peersize) - 1])
134
135#define PEERS_BENCODED6 "6:peers6"
136#define PEERS_BENCODED4 "5:peers"
137
138#define OT_HASH_COMPARE_SIZE (sizeof(ot_hash))
139
140struct ot_peerlist;
141typedef struct ot_peerlist ot_peerlist;
142typedef struct {
143 ot_hash hash;
144 ot_peerlist *peer_list6;
145 ot_peerlist *peer_list4;
146} ot_torrent;
147
148#include "ot_vector.h"
149
150struct ot_peerlist {
151 ot_time base;
152 size_t seed_count;
153 size_t peer_count;
154 size_t down_count;
155 /* normal peers vector or
156 pointer to ot_vector[32] buckets if data != NULL and space == 0
157 */
158 ot_vector peers;
159};
160#define OT_PEERLIST_HASBUCKETS(peer_list) ((peer_list)->peers.size > (peer_list)->peers.space)
161
162/* The http replies the tracker counts when it has to refuse a request. */
163enum {
164 CODE_HTTPERROR_302,
165 CODE_HTTPERROR_400,
166 CODE_HTTPERROR_400_PARAM,
167 CODE_HTTPERROR_400_COMPACT,
168 CODE_HTTPERROR_402_NOTMODEST,
169 CODE_HTTPERROR_402_PAYMENT_REQUIRED,
170 CODE_HTTPERROR_403_IP,
171 CODE_HTTPERROR_404,
172 CODE_HTTPERROR_500,
173
174 CODE_HTTPERROR_COUNT
175};
176
177/* One statistic. Used when workers report stats to their workstruct */
178typedef _Atomic unsigned long long ot_counter;
179
180/* What each thread counts for itself. The two runs at the end are indexed by
181 the http error code and by the age in minutes of a renewed peer */
182typedef enum {
183 OT_COUNTER_TCP_CONNECTIONS,
184 OT_COUNTER_UDP_CONNECTIONS,
185 OT_COUNTER_TCP_ANNOUNCES,
186 OT_COUNTER_UDP_ANNOUNCES,
187 OT_COUNTER_TCP_SCRAPES,
188 OT_COUNTER_UDP_SCRAPES,
189 OT_COUNTER_TCP_CONNECTS,
190 OT_COUNTER_UDP_CONNECTS,
191 OT_COUNTER_UDP_CONNID_MISSMATCH,
192 OT_COUNTER_COMPLETED,
193 OT_COUNTER_FULLSCRAPES,
194 OT_COUNTER_FULLSCRAPE_REQUESTS,
195 OT_COUNTER_FULLSCRAPE_BYTES,
196 OT_COUNTER_SYNCS,
197 OT_COUNTER_STALLS,
198
199 OT_COUNTER_FAILED, /* CODE_HTTPERROR_COUNT of them */
200 OT_COUNTER_RENEWED = OT_COUNTER_FAILED + CODE_HTTPERROR_COUNT, /* OT_PEER_TIMEOUT of them */
201
202 OT_COUNTER_COUNT = OT_COUNTER_RENEWED + OT_PEER_TIMEOUT
203} ot_counter_index;
204
205struct ot_workstruct {
206 /* Thread specific, static */
207
208 /* This thread's own statistics, and the link ot_stats keeps them on */
209 ot_counter counters[OT_COUNTER_COUNT];
210 struct ot_workstruct *counters_next;
211
212 char *inbuf;
213#define G_INBUF_SIZE 8192
214 char *outbuf;
215#define G_OUTBUF_SIZE 8192
216#ifdef _DEBUG_HTTPERROR
217 char *debugbuf;
218#define G_DEBUGBUF_SIZE 8192
219#endif
220
221 /* The peer currently in the working */
222 ot_peer6 peer; /* Can fit v6 and v4 peers */
223
224 /* Pointers into the request buffer */
225 ot_hash *hash;
226 char *peer_id;
227
228 /* HTTP specific, non static */
229 char *request;
230 ssize_t request_size;
231 ssize_t header_size;
232 char *reply;
233 ssize_t reply_size;
234
235 /* Entropy state for rand48 function so that threads don't need to acquire mutexes for
236 global random() or arc4random() state, which causes heavy load on linuxes */
237 uint16_t rand48_state[3];
238
239 int keep_alive;
240};
241
242/* Everything a thread that serves requests needs for itself. Set up and torn
243 down in one place so that a new kind of worker cannot miss a piece.
244 Returns -1 if the buffers could not be had. */
245int workstruct_init(struct ot_workstruct *ws);
246void workstruct_deinit(struct ot_workstruct *ws);
247
248/*
249 Exported functions
250*/
251
252#ifdef WANT_SYNC_LIVE
253#define WANT_SYNC
254#endif
255
256#ifdef WANT_SYNC
257#define WANT_SYNC_PARAM(param) , param
258#else
259#define WANT_SYNC_PARAM(param)
260#endif
261
262void trackerlogic_init(void);
263void trackerlogic_deinit(void);
264void exerr(const char *message);
265
266/* add_peer_to_torrent does only release the torrent bucket if from_sync is set,
267 otherwise it is released in return_peers_for_torrent */
268size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount);
269size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws);
270void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end);
271size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply);
272void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count);
273#ifdef _DEBUG_RANDOMTORRENTS
274void trackerlogic_add_random_torrents(size_t amount);
275#endif
276
277/* torrent iterator */
278void iterate_all_torrents(int (*for_each)(ot_torrent *torrent, uintptr_t data), uintptr_t data);
279
280/* Helper, before it moves to its own object */
281void free_peerlist(ot_peerlist *peer_list);
282
283#endif