summaryrefslogtreecommitdiff
path: root/ot_accesslist.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2022-11-24 04:20:06 +0100
committerDirk Engling <erdgeist@erdgeist.org>2022-11-24 04:20:06 +0100
commitbe825f57597b0e9dcf07d257e93f03e30935f7db (patch)
tree58059fe89c3e2faf8be5999b7cac010e03f1ae31 /ot_accesslist.c
parent110868ec4ebe60521d5a4ced63feca6a1cf0aa2a (diff)
Add support for dynamic accesslists
Diffstat (limited to 'ot_accesslist.c')
-rw-r--r--ot_accesslist.c273
1 files changed, 236 insertions, 37 deletions
diff --git a/ot_accesslist.c b/ot_accesslist.c
index a3a2049..078cebd 100644
--- a/ot_accesslist.c
+++ b/ot_accesslist.c
@@ -10,6 +10,11 @@
10#include <stdio.h> 10#include <stdio.h>
11#include <signal.h> 11#include <signal.h>
12#include <unistd.h> 12#include <unistd.h>
13#ifdef WANT_DYNAMIC_ACCESSLIST
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <errno.h>
17#endif
13 18
14/* Libowfat */ 19/* Libowfat */
15#include "byte.h" 20#include "byte.h"
@@ -24,23 +29,80 @@
24 29
25/* GLOBAL VARIABLES */ 30/* GLOBAL VARIABLES */
26#ifdef WANT_ACCESSLIST 31#ifdef WANT_ACCESSLIST
27 char *g_accesslist_filename; 32char *g_accesslist_filename = NULL;
33#ifdef WANT_DYNAMIC_ACCESSLIST
34char *g_accesslist_pipe_add = NULL;
35char *g_accesslist_pipe_delete = NULL;
36#endif
28static pthread_mutex_t g_accesslist_mutex; 37static pthread_mutex_t g_accesslist_mutex;
29 38
30typedef struct { 39/* Accesslists are lock free linked lists. We can not make them locking, because every announce
31 ot_hash *list; 40 would try to acquire the mutex, making it the most contested mutex in the whole of opentracker,
32 size_t size; 41 basically creating a central performance choke point.
33} ot_accesslist; 42
34ot_accesslist * g_accesslist = NULL; 43 The idea is that updating the list heads happens under the g_accesslist_mutex guard and is
35ot_accesslist * g_accesslist_old = NULL; 44 done atomically, while consumers might potentially still hold pointers deeper inside the list.
45
46 Consumers (for now only via accesslist_hashisvalid) will always fetch the list head pointer
47 that is guaranteed to live for at least five minutes. This should be many orders of magnitudes
48 more than how long it will be needed by the bsearch done on the list. */
49struct ot_accesslist;
50typedef struct ot_accesslist ot_accesslist;
51struct ot_accesslist {
52 ot_hash *list;
53 size_t size;
54 ot_time base;
55 ot_accesslist *next;
56};
57static ot_accesslist * g_accesslist = NULL;
58#ifdef WANT_DYNAMIC_ACCESSLIST
59static ot_accesslist * g_accesslist_add = NULL;
60static ot_accesslist * g_accesslist_delete = NULL;
61#endif
36 62
63/* Helpers to work on access lists */
37static int vector_compare_hash(const void *hash1, const void *hash2 ) { 64static int vector_compare_hash(const void *hash1, const void *hash2 ) {
38 return memcmp( hash1, hash2, OT_HASH_COMPARE_SIZE ); 65 return memcmp( hash1, hash2, OT_HASH_COMPARE_SIZE );
39} 66}
40 67
68static ot_accesslist * accesslist_free(ot_accesslist *accesslist) {
69 while (accesslist) {
70 ot_accesslist * this_accesslist = accesslist;
71 accesslist = this_accesslist->next;
72 free(this_accesslist->list);
73 free(this_accesslist);
74 }
75 return NULL;
76}
77
78static ot_accesslist * accesslist_make(ot_accesslist *next, size_t size) {
79 ot_accesslist * accesslist_new = malloc(sizeof(ot_accesslist));
80 if (accesslist_new) {
81 accesslist_new->list = size ? malloc(sizeof(ot_hash) * size) : NULL;
82 accesslist_new->size = size;
83 accesslist_new->base = g_now_minutes;
84 accesslist_new->next = next;
85 if (size && !accesslist_new->list) {
86 free(accesslist_new);
87 accesslist_new = NULL;
88 }
89 }
90 return accesslist_new;
91}
92
93/* This must be called with g_accesslist_mutex held.
94 This will never delete head, because that might still be in use. */
95static void accesslist_clean(ot_accesslist *accesslist) {
96 while (accesslist && accesslist->next) {
97 if (accesslist->next->base + 5 < g_now_minutes)
98 accesslist->next = accesslist_free(accesslist->next);
99 accesslist = accesslist->next;
100 }
101}
102
41/* Read initial access list */ 103/* Read initial access list */
42static void accesslist_readfile( void ) { 104static void accesslist_readfile( void ) {
43 ot_accesslist * accesslist_new = malloc(sizeof(ot_accesslist)); 105 ot_accesslist * accesslist_new;
44 ot_hash *info_hash; 106 ot_hash *info_hash;
45 const char *map, *map_end, *read_offs; 107 const char *map, *map_end, *read_offs;
46 size_t maplen; 108 size_t maplen;
@@ -54,14 +116,13 @@ static void accesslist_readfile( void ) {
54 116
55 /* You need at least 41 bytes to pass an info_hash, make enough room 117 /* You need at least 41 bytes to pass an info_hash, make enough room
56 for the maximum amount of them */ 118 for the maximum amount of them */
57 accesslist_new->size = 0; 119 accesslist_new = accesslist_make(g_accesslist, maplen / 41);
58 info_hash = accesslist_new->list = malloc( ( maplen / 41 ) * 20 ); 120 if( !accesslist_new ) {
59 if( !accesslist_new->list ) {
60 fprintf( stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", ( maplen / 41 ) * 20 ); 121 fprintf( stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", ( maplen / 41 ) * 20 );
61 mmap_unmap( map, maplen); 122 mmap_unmap( map, maplen);
62 free(accesslist_new);
63 return; 123 return;
64 } 124 }
125 info_hash = accesslist_new->list;
65 126
66 /* No use to scan if there's not enough room for another full info_hash */ 127 /* No use to scan if there's not enough room for another full info_hash */
67 map_end = map + maplen - 40; 128 map_end = map + maplen - 40;
@@ -71,18 +132,18 @@ static void accesslist_readfile( void ) {
71 while( read_offs <= map_end ) { 132 while( read_offs <= map_end ) {
72 int i; 133 int i;
73 for( i=0; i<(int)sizeof(ot_hash); ++i ) { 134 for( i=0; i<(int)sizeof(ot_hash); ++i ) {
74 int eger1 = scan_fromhex( read_offs[ 2*i ] ); 135 int eger1 = scan_fromhex( (unsigned char)read_offs[ 2*i ] );
75 int eger2 = scan_fromhex( read_offs[ 1 + 2*i ] ); 136 int eger2 = scan_fromhex( (unsigned char)read_offs[ 1 + 2*i ] );
76 if( eger1 < 0 || eger2 < 0 ) 137 if( eger1 < 0 || eger2 < 0 )
77 break; 138 break;
78 (*info_hash)[i] = eger1 * 16 + eger2; 139 (*info_hash)[i] = (uint8_t)(eger1 * 16 + eger2);
79 } 140 }
80 141
81 if( i == sizeof(ot_hash) ) { 142 if( i == sizeof(ot_hash) ) {
82 read_offs += 40; 143 read_offs += 40;
83 144
84 /* Append accesslist to accesslist vector */ 145 /* Append accesslist to accesslist vector */
85 if( read_offs == map_end || scan_fromhex( *read_offs ) < 0 ) 146 if( read_offs == map_end || scan_fromhex( (unsigned char)*read_offs ) < 0 )
86 ++info_hash; 147 ++info_hash;
87 } 148 }
88 149
@@ -100,14 +161,19 @@ static void accesslist_readfile( void ) {
100 161
101 /* Now exchange the accesslist vector in the least race condition prone way */ 162 /* Now exchange the accesslist vector in the least race condition prone way */
102 pthread_mutex_lock(&g_accesslist_mutex); 163 pthread_mutex_lock(&g_accesslist_mutex);
164 accesslist_new->next = g_accesslist;
165 g_accesslist = accesslist_new; /* Only now set a new list */
166
167#ifdef WANT_DYNAMIC_ACCESSLIST
168 /* If we have dynamic accesslists, reloading a new one will always void the add/delete lists.
169 Insert empty ones at the list head */
170 if (g_accesslist_add && (accesslist_new = accesslist_make(g_accesslist_add, 0)) != NULL)
171 g_accesslist_add = accesslist_new;
172 if (g_accesslist_delete && (accesslist_new = accesslist_make(g_accesslist_delete, 0)) != NULL)
173 g_accesslist_delete = accesslist_new;
174#endif
103 175
104 if (g_accesslist_old) { 176 accesslist_clean(g_accesslist);
105 free(g_accesslist_old->list);
106 free(g_accesslist_old);
107 }
108
109 g_accesslist_old = g_accesslist; /* Keep a copy for later free */
110 g_accesslist = accesslist_new; /* Only now set a new list */
111 177
112 pthread_mutex_unlock(&g_accesslist_mutex); 178 pthread_mutex_unlock(&g_accesslist_mutex);
113} 179}
@@ -115,11 +181,25 @@ static void accesslist_readfile( void ) {
115int accesslist_hashisvalid( ot_hash hash ) { 181int accesslist_hashisvalid( ot_hash hash ) {
116 /* Get working copy of current access list */ 182 /* Get working copy of current access list */
117 ot_accesslist * accesslist = g_accesslist; 183 ot_accesslist * accesslist = g_accesslist;
118 184#ifdef WANT_DYNAMIC_ACCESSLIST
185 ot_accesslist * accesslist_add, * accesslist_delete;
186#endif
119 void * exactmatch = NULL; 187 void * exactmatch = NULL;
120 188
121 if (accesslist) 189 if (accesslist)
122 exactmatch = bsearch( hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash ); 190 exactmatch = bsearch( hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
191
192#ifdef WANT_DYNAMIC_ACCESSLIST
193 /* If we had no match on the main list, scan the list of dynamically added hashes */
194 accesslist_add = g_accesslist_add;
195 if ((exactmatch == NULL) && accesslist_add)
196 exactmatch = bsearch( hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
197
198 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */
199 accesslist_delete = g_accesslist_delete;
200 if ((exactmatch != NULL) && accesslist_delete && bsearch( hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash ))
201 exactmatch = NULL;
202#endif
123 203
124#ifdef WANT_ACCESSLIST_BLACK 204#ifdef WANT_ACCESSLIST_BLACK
125 return exactmatch == NULL; 205 return exactmatch == NULL;
@@ -138,6 +218,8 @@ static void * accesslist_worker( void * args ) {
138 (void)args; 218 (void)args;
139 219
140 while( 1 ) { 220 while( 1 ) {
221 if (!g_opentracker_running)
222 return NULL;
141 223
142 /* Initial attempt to read accesslist */ 224 /* Initial attempt to read accesslist */
143 accesslist_readfile( ); 225 accesslist_readfile( );
@@ -148,27 +230,144 @@ static void * accesslist_worker( void * args ) {
148 return NULL; 230 return NULL;
149} 231}
150 232
233#ifdef WANT_DYNAMIC_ACCESSLIST
234static pthread_t thread_adder_id, thread_deleter_id;
235static void * accesslist_adddel_worker(char * fifoname, ot_accesslist ** adding_to, ot_accesslist ** removing_from) {
236 struct stat st;
237
238 if (!stat(fifoname, &st)) {
239 if (!S_ISFIFO(st.st_mode)) {
240 fprintf(stderr, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname);
241 return NULL;
242 }
243 } else {
244 int error = mkfifo(fifoname, 0755);
245 if (error && error != EEXIST) {
246 fprintf(stderr, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname, strerror(errno));
247 return NULL;
248 }
249 }
250
251 while (g_opentracker_running) {
252 FILE * fifo = fopen(fifoname, "r");
253 char *line = NULL;
254 size_t linecap = 0;
255 ssize_t linelen;
256
257 if (!fifo) {
258 fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno));
259 return NULL;
260 }
261
262 while ((linelen = getline(&line, &linecap, fifo)) > 0) {
263 ot_hash info_hash;
264 int i;
265
266 printf("Got line %*s", (int)linelen, line);
267 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*"
268 If there's not enough characters for an info_hash in the line, skip it. */
269 if (linelen < 41)
270 continue;
271
272 for( i=0; i<(int)sizeof(ot_hash); ++i ) {
273 int eger1 = scan_fromhex( (unsigned char)line[ 2*i ] );
274 int eger2 = scan_fromhex( (unsigned char)line[ 1 + 2*i ] );
275 if( eger1 < 0 || eger2 < 0 )
276 break;
277 ((uint8_t*)info_hash)[i] = (uint8_t)(eger1 * 16 + eger2);
278 }
279printf("parsed info_hash %20s\n", info_hash);
280 if( i != sizeof(ot_hash) )
281 continue;
282
283 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
284 other worker threads from doing the same */
285 pthread_mutex_lock(&g_accesslist_mutex);
286
287 /* If the info hash is in the removing_from list, create a new head without that entry */
288 if (*removing_from && (*removing_from)->list) {
289 ot_hash * exactmatch = bsearch( info_hash, (*removing_from)->list, (*removing_from)->size, OT_HASH_COMPARE_SIZE, vector_compare_hash );
290 if (exactmatch) {
291 ptrdiff_t off = exactmatch - (*removing_from)->list;
292 ot_accesslist * accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1);
293 if (accesslist_new) {
294 memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off);
295 memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, (*removing_from)->size - off - 1);
296 *removing_from = accesslist_new;
297 }
298 }
299 }
300 accesslist_clean(*removing_from);
301
302 /* Simple case: there's no adding_to list yet, create one with one member */
303 if (!*adding_to) {
304 *adding_to = accesslist_make(NULL, 1);
305 if (*adding_to)
306 memcpy((*adding_to)->list, info_hash, sizeof(ot_hash));
307 } else {
308 int exactmatch = 0;
309 ot_hash * insert_point = binary_search( info_hash, (*adding_to)->list, (*adding_to)->size, OT_HASH_COMPARE_SIZE, sizeof(ot_hash), &exactmatch );
310
311 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
312 if (!exactmatch) {
313 ot_accesslist * accesslist_new = accesslist_make(*adding_to, (*adding_to)->size + 1);
314 ptrdiff_t off = insert_point - (*adding_to)->list;
315 if (accesslist_new) {
316 memcpy(accesslist_new->list, (*adding_to)->list, sizeof(ot_hash) * off);
317 memcpy(accesslist_new->list + off, info_hash, sizeof(info_hash));
318 memcpy(accesslist_new->list + off + 1, (*adding_to)->list + off, (*adding_to)->size - off);
319 *adding_to = accesslist_new;
320 }
321 }
322 }
323 accesslist_clean(*adding_to);
324
325 pthread_mutex_unlock(&g_accesslist_mutex);
326 }
327
328 fclose(fifo);
329 }
330 return NULL;
331}
332
333static void * accesslist_adder_worker( void * args ) {
334 (void)args;
335 return accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete);
336}
337static void * accesslist_deleter_worker( void * args ) {
338 (void)args;
339 return accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add);
340}
341#endif
342
151static pthread_t thread_id; 343static pthread_t thread_id;
152void accesslist_init( ) { 344void accesslist_init( ) {
153 pthread_mutex_init(&g_accesslist_mutex, NULL); 345 pthread_mutex_init(&g_accesslist_mutex, NULL);
154 pthread_create( &thread_id, NULL, accesslist_worker, NULL ); 346 pthread_create( &thread_id, NULL, accesslist_worker, NULL );
347#ifdef WANT_DYNAMIC_ACCESSLIST
348 if (g_accesslist_pipe_add)
349 pthread_create( &thread_adder_id, NULL, accesslist_adder_worker, NULL );
350 if (g_accesslist_pipe_delete)
351 pthread_create( &thread_deleter_id, NULL, accesslist_deleter_worker, NULL );
352#endif
155} 353}
156 354
157void accesslist_deinit( void ) { 355void accesslist_deinit( void ) {
158 pthread_cancel( thread_id ); 356 /* Wake up sleeping worker */
159 pthread_mutex_destroy(&g_accesslist_mutex); 357 pthread_kill(thread_id, SIGHUP);
160 358
161 if (g_accesslist_old) { 359 pthread_mutex_lock(&g_accesslist_mutex);
162 free(g_accesslist_old->list);
163 free(g_accesslist_old);
164 g_accesslist_old = 0;
165 }
166 360
167 if (g_accesslist) { 361 g_accesslist = accesslist_free(g_accesslist);
168 free(g_accesslist->list); 362
169 free(g_accesslist); 363#ifdef WANT_DYNAMIC_ACCESSLIST
170 g_accesslist = 0; 364 g_accesslist_add = accesslist_free(g_accesslist_add);
171 } 365 g_accesslist_delete = accesslist_free(g_accesslist_delete);
366#endif
367
368 pthread_mutex_unlock(&g_accesslist_mutex);
369 pthread_cancel( thread_id );
370 pthread_mutex_destroy(&g_accesslist_mutex);
172} 371}
173#endif 372#endif
174 373