diff options
author | Dirk Engling <erdgeist@erdgeist.org> | 2022-11-24 04:20:06 +0100 |
---|---|---|
committer | Dirk Engling <erdgeist@erdgeist.org> | 2022-11-24 04:20:06 +0100 |
commit | be825f57597b0e9dcf07d257e93f03e30935f7db (patch) | |
tree | 58059fe89c3e2faf8be5999b7cac010e03f1ae31 /ot_accesslist.c | |
parent | 110868ec4ebe60521d5a4ced63feca6a1cf0aa2a (diff) |
Add support for dynamic accesslists
Diffstat (limited to 'ot_accesslist.c')
-rw-r--r-- | ot_accesslist.c | 273 |
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; | 32 | char *g_accesslist_filename = NULL; |
33 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
34 | char *g_accesslist_pipe_add = NULL; | ||
35 | char *g_accesslist_pipe_delete = NULL; | ||
36 | #endif | ||
28 | static pthread_mutex_t g_accesslist_mutex; | 37 | static pthread_mutex_t g_accesslist_mutex; |
29 | 38 | ||
30 | typedef 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 | |
34 | ot_accesslist * g_accesslist = NULL; | 43 | The idea is that updating the list heads happens under the g_accesslist_mutex guard and is |
35 | ot_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. */ | ||
49 | struct ot_accesslist; | ||
50 | typedef struct ot_accesslist ot_accesslist; | ||
51 | struct ot_accesslist { | ||
52 | ot_hash *list; | ||
53 | size_t size; | ||
54 | ot_time base; | ||
55 | ot_accesslist *next; | ||
56 | }; | ||
57 | static ot_accesslist * g_accesslist = NULL; | ||
58 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
59 | static ot_accesslist * g_accesslist_add = NULL; | ||
60 | static ot_accesslist * g_accesslist_delete = NULL; | ||
61 | #endif | ||
36 | 62 | ||
63 | /* Helpers to work on access lists */ | ||
37 | static int vector_compare_hash(const void *hash1, const void *hash2 ) { | 64 | static 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 | ||
68 | static 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 | |||
78 | static 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. */ | ||
95 | static 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 */ |
42 | static void accesslist_readfile( void ) { | 104 | static 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 ) { | |||
115 | int accesslist_hashisvalid( ot_hash hash ) { | 181 | int 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 | ||
234 | static pthread_t thread_adder_id, thread_deleter_id; | ||
235 | static 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 | } | ||
279 | printf("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 | |||
333 | static 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 | } | ||
337 | static 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 | |||
151 | static pthread_t thread_id; | 343 | static pthread_t thread_id; |
152 | void accesslist_init( ) { | 344 | void 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 | ||
157 | void accesslist_deinit( void ) { | 355 | void 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 | ||