summaryrefslogtreecommitdiff
path: root/src/ot_accesslist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_accesslist.c')
-rw-r--r--src/ot_accesslist.c601
1 files changed, 601 insertions, 0 deletions
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}