1 /* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
13 #ifdef WANT_DYNAMIC_ACCESSLIST
14 #include <sys/types.h>
27 #include "trackerlogic.h"
28 #include "ot_accesslist.h"
29 #include "ot_vector.h"
31 /* GLOBAL VARIABLES */
32 #ifdef WANT_ACCESSLIST
33 char *g_accesslist_filename
= NULL
;
34 #ifdef WANT_DYNAMIC_ACCESSLIST
35 char *g_accesslist_pipe_add
= NULL
;
36 char *g_accesslist_pipe_delete
= NULL
;
38 static pthread_mutex_t g_accesslist_mutex
;
40 /* Accesslists are lock free linked lists. We can not make them locking, because every announce
41 would try to acquire the mutex, making it the most contested mutex in the whole of opentracker,
42 basically creating a central performance choke point.
44 The idea is that updating the list heads happens under the g_accesslist_mutex guard and is
45 done atomically, while consumers might potentially still hold pointers deeper inside the list.
47 Consumers (for now only via accesslist_hashisvalid) will always fetch the list head pointer
48 that is guaranteed to live for at least five minutes. This should be many orders of magnitudes
49 more than how long it will be needed by the bsearch done on the list. */
51 typedef struct ot_accesslist ot_accesslist
;
52 struct ot_accesslist
{
58 static ot_accesslist
* _Atomic g_accesslist
= NULL
;
59 #ifdef WANT_DYNAMIC_ACCESSLIST
60 static ot_accesslist
* _Atomic g_accesslist_add
= NULL
;
61 static ot_accesslist
* _Atomic g_accesslist_delete
= NULL
;
64 /* Helpers to work on access lists */
65 static int vector_compare_hash(const void *hash1
, const void *hash2
) {
66 return memcmp( hash1
, hash2
, OT_HASH_COMPARE_SIZE
);
69 static ot_accesslist
* accesslist_free(ot_accesslist
*accesslist
) {
71 ot_accesslist
* this_accesslist
= accesslist
;
72 accesslist
= this_accesslist
->next
;
73 free(this_accesslist
->list
);
74 free(this_accesslist
);
79 static ot_accesslist
* accesslist_make(ot_accesslist
*next
, size_t size
) {
80 ot_accesslist
* accesslist_new
= malloc(sizeof(ot_accesslist
));
82 accesslist_new
->list
= size
? malloc(sizeof(ot_hash
) * size
) : NULL
;
83 accesslist_new
->size
= size
;
84 accesslist_new
->base
= g_now_minutes
;
85 accesslist_new
->next
= next
;
86 if (size
&& !accesslist_new
->list
) {
88 accesslist_new
= NULL
;
91 return accesslist_new
;
94 /* This must be called with g_accesslist_mutex held.
95 This will never delete head, because that might still be in use. */
96 static void accesslist_clean(ot_accesslist
*accesslist
) {
97 while (accesslist
&& accesslist
->next
) {
98 if (accesslist
->next
->base
+ 5 < g_now_minutes
)
99 accesslist
->next
= accesslist_free(accesslist
->next
);
100 accesslist
= accesslist
->next
;
104 /* Read initial access list */
105 static void accesslist_readfile( void ) {
106 ot_accesslist
* accesslist_new
;
108 const char *map
, *map_end
, *read_offs
;
111 if( ( map
= mmap_read( g_accesslist_filename
, &maplen
) ) == NULL
) {
112 char *wd
= getcwd( NULL
, 0 );
113 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
);
118 /* You need at least 41 bytes to pass an info_hash, make enough room
119 for the maximum amount of them */
120 accesslist_new
= accesslist_make(g_accesslist
, maplen
/ 41);
121 if( !accesslist_new
) {
122 fprintf( stderr
, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", ( maplen
/ 41 ) * 20 );
123 mmap_unmap( map
, maplen
);
126 info_hash
= accesslist_new
->list
;
128 /* No use to scan if there's not enough room for another full info_hash */
129 map_end
= map
+ maplen
- 40;
132 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*" */
133 while( read_offs
<= map_end
) {
135 for( i
=0; i
<(int)sizeof(ot_hash
); ++i
) {
136 int eger1
= scan_fromhex( (unsigned char)read_offs
[ 2*i
] );
137 int eger2
= scan_fromhex( (unsigned char)read_offs
[ 1 + 2*i
] );
138 if( eger1
< 0 || eger2
< 0 )
140 (*info_hash
)[i
] = (uint8_t)(eger1
* 16 + eger2
);
143 if( i
== sizeof(ot_hash
) ) {
146 /* Append accesslist to accesslist vector */
147 if( read_offs
== map_end
|| scan_fromhex( (unsigned char)*read_offs
) < 0 )
151 /* Find start of next line */
152 while( read_offs
<= map_end
&& *(read_offs
++) != '\n' );
155 fprintf( stderr
, "Added %zd info_hashes to accesslist\n", (size_t)(info_hash
- accesslist_new
->list
) );
158 mmap_unmap( map
, maplen
);
160 qsort( accesslist_new
->list
, info_hash
- accesslist_new
->list
, sizeof( *info_hash
), vector_compare_hash
);
161 accesslist_new
->size
= info_hash
- accesslist_new
->list
;
163 /* Now exchange the accesslist vector in the least race condition prone way */
164 pthread_mutex_lock(&g_accesslist_mutex
);
165 accesslist_new
->next
= g_accesslist
;
166 g_accesslist
= accesslist_new
; /* Only now set a new list */
168 #ifdef WANT_DYNAMIC_ACCESSLIST
169 /* If we have dynamic accesslists, reloading a new one will always void the add/delete lists.
170 Insert empty ones at the list head */
171 if (g_accesslist_add
&& (accesslist_new
= accesslist_make(g_accesslist_add
, 0)) != NULL
)
172 g_accesslist_add
= accesslist_new
;
173 if (g_accesslist_delete
&& (accesslist_new
= accesslist_make(g_accesslist_delete
, 0)) != NULL
)
174 g_accesslist_delete
= accesslist_new
;
177 accesslist_clean(g_accesslist
);
179 pthread_mutex_unlock(&g_accesslist_mutex
);
182 int accesslist_hashisvalid( ot_hash hash
) {
183 /* Get working copy of current access list */
184 ot_accesslist
* accesslist
= g_accesslist
;
185 #ifdef WANT_DYNAMIC_ACCESSLIST
186 ot_accesslist
* accesslist_add
, * accesslist_delete
;
188 void * exactmatch
= NULL
;
191 exactmatch
= bsearch( hash
, accesslist
->list
, accesslist
->size
, OT_HASH_COMPARE_SIZE
, vector_compare_hash
);
193 #ifdef WANT_DYNAMIC_ACCESSLIST
194 /* If we had no match on the main list, scan the list of dynamically added hashes */
195 accesslist_add
= g_accesslist_add
;
196 if ((exactmatch
== NULL
) && accesslist_add
)
197 exactmatch
= bsearch( hash
, accesslist_add
->list
, accesslist_add
->size
, OT_HASH_COMPARE_SIZE
, vector_compare_hash
);
199 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */
200 accesslist_delete
= g_accesslist_delete
;
201 if ((exactmatch
!= NULL
) && accesslist_delete
&& bsearch( hash
, accesslist_add
->list
, accesslist_add
->size
, OT_HASH_COMPARE_SIZE
, vector_compare_hash
))
205 #ifdef WANT_ACCESSLIST_BLACK
206 return exactmatch
== NULL
;
208 return exactmatch
!= NULL
;
212 static void * accesslist_worker( void * args
) {
214 sigset_t signal_mask
;
216 sigemptyset(&signal_mask
);
217 sigaddset(&signal_mask
, SIGHUP
);
222 if (!g_opentracker_running
)
225 /* Initial attempt to read accesslist */
226 accesslist_readfile( );
228 /* Wait for signals */
229 while( sigwait (&signal_mask
, &sig
) != 0 && sig
!= SIGHUP
);
234 #ifdef WANT_DYNAMIC_ACCESSLIST
235 static pthread_t thread_adder_id
, thread_deleter_id
;
236 static void * accesslist_adddel_worker(char * fifoname
, ot_accesslist
* _Atomic
* adding_to
, ot_accesslist
* _Atomic
* removing_from
) {
239 if (!stat(fifoname
, &st
)) {
240 if (!S_ISFIFO(st
.st_mode
)) {
241 fprintf(stderr
, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname
);
245 int error
= mkfifo(fifoname
, 0755);
246 if (error
&& error
!= EEXIST
) {
247 fprintf(stderr
, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname
, strerror(errno
));
252 while (g_opentracker_running
) {
253 FILE * fifo
= fopen(fifoname
, "r");
259 fprintf(stderr
, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname
, strerror(errno
));
263 while ((linelen
= getline(&line
, &linecap
, fifo
)) > 0) {
267 printf("Got line %*s", (int)linelen
, line
);
268 /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*"
269 If there's not enough characters for an info_hash in the line, skip it. */
273 for( i
=0; i
<(int)sizeof(ot_hash
); ++i
) {
274 int eger1
= scan_fromhex( (unsigned char)line
[ 2*i
] );
275 int eger2
= scan_fromhex( (unsigned char)line
[ 1 + 2*i
] );
276 if( eger1
< 0 || eger2
< 0 )
278 ((uint8_t*)info_hash
)[i
] = (uint8_t)(eger1
* 16 + eger2
);
280 printf("parsed info_hash %20s\n", info_hash
);
281 if( i
!= sizeof(ot_hash
) )
284 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
285 other worker threads from doing the same */
286 pthread_mutex_lock(&g_accesslist_mutex
);
288 /* If the info hash is in the removing_from list, create a new head without that entry */
289 if (*removing_from
&& (*removing_from
)->list
) {
290 ot_hash
* exactmatch
= bsearch( info_hash
, (*removing_from
)->list
, (*removing_from
)->size
, OT_HASH_COMPARE_SIZE
, vector_compare_hash
);
292 ptrdiff_t off
= exactmatch
- (*removing_from
)->list
;
293 ot_accesslist
* accesslist_new
= accesslist_make(*removing_from
, (*removing_from
)->size
- 1);
294 if (accesslist_new
) {
295 memcpy(accesslist_new
->list
, (*removing_from
)->list
, sizeof(ot_hash
) * off
);
296 memcpy(accesslist_new
->list
+ off
, (*removing_from
)->list
+ off
+ 1, (*removing_from
)->size
- off
- 1);
297 *removing_from
= accesslist_new
;
302 /* Simple case: there's no adding_to list yet, create one with one member */
304 ot_accesslist
* accesslist_new
= accesslist_make(NULL
, 1);
305 if (accesslist_new
) {
306 memcpy(accesslist_new
->list
, info_hash
, sizeof(ot_hash
));
307 *adding_to
= accesslist_new
;
311 ot_hash
* insert_point
= binary_search( info_hash
, (*adding_to
)->list
, (*adding_to
)->size
, OT_HASH_COMPARE_SIZE
, sizeof(ot_hash
), &exactmatch
);
313 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
315 ot_accesslist
* accesslist_new
= accesslist_make(*adding_to
, (*adding_to
)->size
+ 1);
316 ptrdiff_t off
= insert_point
- (*adding_to
)->list
;
317 if (accesslist_new
) {
318 memcpy(accesslist_new
->list
, (*adding_to
)->list
, sizeof(ot_hash
) * off
);
319 memcpy(accesslist_new
->list
+ off
, info_hash
, sizeof(info_hash
));
320 memcpy(accesslist_new
->list
+ off
+ 1, (*adding_to
)->list
+ off
, (*adding_to
)->size
- off
);
321 *adding_to
= accesslist_new
;
326 pthread_mutex_unlock(&g_accesslist_mutex
);
334 static void * accesslist_adder_worker( void * args
) {
336 return accesslist_adddel_worker(g_accesslist_pipe_add
, &g_accesslist_add
, &g_accesslist_delete
);
338 static void * accesslist_deleter_worker( void * args
) {
340 return accesslist_adddel_worker(g_accesslist_pipe_delete
, &g_accesslist_delete
, &g_accesslist_add
);
344 static pthread_t thread_id
;
345 void accesslist_init( ) {
346 pthread_mutex_init(&g_accesslist_mutex
, NULL
);
347 pthread_create( &thread_id
, NULL
, accesslist_worker
, NULL
);
348 #ifdef WANT_DYNAMIC_ACCESSLIST
349 if (g_accesslist_pipe_add
)
350 pthread_create( &thread_adder_id
, NULL
, accesslist_adder_worker
, NULL
);
351 if (g_accesslist_pipe_delete
)
352 pthread_create( &thread_deleter_id
, NULL
, accesslist_deleter_worker
, NULL
);
356 void accesslist_deinit( void ) {
357 /* Wake up sleeping worker */
358 pthread_kill(thread_id
, SIGHUP
);
360 pthread_mutex_lock(&g_accesslist_mutex
);
362 g_accesslist
= accesslist_free(g_accesslist
);
364 #ifdef WANT_DYNAMIC_ACCESSLIST
365 g_accesslist_add
= accesslist_free(g_accesslist_add
);
366 g_accesslist_delete
= accesslist_free(g_accesslist_delete
);
369 pthread_mutex_unlock(&g_accesslist_mutex
);
370 pthread_cancel( thread_id
);
371 pthread_mutex_destroy(&g_accesslist_mutex
);
374 void accesslist_cleanup( void ) {
375 pthread_mutex_lock(&g_accesslist_mutex
);
377 accesslist_clean(g_accesslist
);
378 #if WANT_DYNAMIC_ACCESSLIST
379 accesslist_clean(g_accesslist_add
);
380 accesslist_clean(g_accesslist_delete
);
383 pthread_mutex_unlock(&g_accesslist_mutex
);
387 int address_in_net( const ot_ip6 address
, const ot_net
*net
) {
388 int bits
= net
->bits
, checkbits
= ( 0x7f00 >> ( bits
& 7 ));
389 int result
= memcmp( address
, &net
->address
, bits
>> 3 );
390 if( !result
&& ( bits
& 7 ) )
391 result
= ( checkbits
& address
[bits
>>3] ) - ( checkbits
& net
->address
[bits
>>3]);
395 void *set_value_for_net( const ot_net
*net
, ot_vector
*vector
, const void *value
, const size_t member_size
) {
399 /* Caller must have a concept of ot_net in it's member */
400 if( member_size
< sizeof(ot_net
) )
403 /* Check each net in vector for overlap */
404 uint8_t *member
= ((uint8_t*)vector
->data
);
405 for( i
=0; i
<vector
->size
; ++i
) {
406 if( address_in_net( *(ot_ip6
*)member
, net
) ||
407 address_in_net( net
->address
, (ot_net
*)member
) )
409 member
+= member_size
;
412 member
= vector_find_or_insert( vector
, (void*)net
, member_size
, sizeof(ot_net
), &exactmatch
);
414 memcpy( member
, net
, sizeof(ot_net
));
415 memcpy( member
+ sizeof(ot_net
), value
, member_size
- sizeof(ot_net
));
421 /* Takes a vector filled with { ot_net net, uint8_t[x] value };
422 Returns value associated with the net, or NULL if not found */
423 void *get_value_for_net( const ot_ip6 address
, const ot_vector
*vector
, const size_t member_size
) {
425 /* This binary search will return a pointer to the first non-containing network... */
426 ot_net
*net
= binary_search( address
, vector
->data
, vector
->size
, member_size
, sizeof(ot_ip6
), &exactmatch
);
429 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
430 if( !exactmatch
&& ( (void*)net
> vector
->data
) )
432 if( !address_in_net( address
, net
) )
437 #ifdef WANT_FULLLOG_NETWORKS
438 static ot_vector g_lognets_list
;
439 ot_log
*g_logchain_first
, *g_logchain_last
;
441 static pthread_mutex_t g_lognets_list_mutex
= PTHREAD_MUTEX_INITIALIZER
;
442 void loglist_add_network( const ot_net
*net
) {
443 pthread_mutex_lock(&g_lognets_list_mutex
);
444 set_value_for_net( net
, &g_lognets_list
, NULL
, sizeof(ot_net
));
445 pthread_mutex_unlock(&g_lognets_list_mutex
);
448 void loglist_reset( ) {
449 pthread_mutex_lock(&g_lognets_list_mutex
);
450 free( g_lognets_list
.data
);
451 g_lognets_list
.data
= 0;
452 g_lognets_list
.size
= g_lognets_list
.space
= 0;
453 pthread_mutex_unlock(&g_lognets_list_mutex
);
456 int loglist_check_address( const ot_ip6 address
) {
458 pthread_mutex_lock(&g_lognets_list_mutex
);
459 result
= ( NULL
!= get_value_for_net( address
, &g_lognets_list
, sizeof(ot_net
)) );
460 pthread_mutex_unlock(&g_lognets_list_mutex
);
465 #ifdef WANT_IP_FROM_PROXY
471 static ot_vector g_proxies_list
;
472 static pthread_mutex_t g_proxies_list_mutex
= PTHREAD_MUTEX_INITIALIZER
;
474 int proxylist_add_network( const ot_net
*proxy
, const ot_net
*net
) {
476 int exactmatch
, result
= 1;
477 pthread_mutex_lock(&g_proxies_list_mutex
);
479 /* If we have a direct hit, use and extend the vector there */
480 map
= binary_search( proxy
, g_proxies_list
.data
, g_proxies_list
.size
, sizeof(ot_proxymap
), sizeof(ot_net
), &exactmatch
);
482 if( !map
|| !exactmatch
) {
483 /* else see, if we've got overlapping networks
484 and get a new empty vector if not */
486 memset( &empty
, 0, sizeof( ot_vector
) );
487 map
= set_value_for_net( proxy
, &g_proxies_list
, &empty
, sizeof(ot_proxymap
));
490 if( map
&& set_value_for_net( net
, &map
->networks
, NULL
, sizeof(ot_net
) ) )
493 pthread_mutex_unlock(&g_proxies_list_mutex
);
497 int proxylist_check_proxy( const ot_ip6 proxy
, const ot_ip6 address
) {
501 pthread_mutex_lock(&g_proxies_list_mutex
);
503 if( ( map
= get_value_for_net( proxy
, &g_proxies_list
, sizeof(ot_proxymap
) ) ) )
504 if( !address
|| get_value_for_net( address
, &map
->networks
, sizeof(ot_net
) ) )
507 pthread_mutex_unlock(&g_proxies_list_mutex
);
513 static ot_net g_admin_nets
[OT_ADMINIP_MAX
];
514 static ot_permissions g_admin_nets_permissions
[OT_ADMINIP_MAX
];
515 static unsigned int g_admin_nets_count
= 0;
517 int accesslist_bless_net( ot_net
*net
, ot_permissions permissions
) {
518 if( g_admin_nets_count
>= OT_ADMINIP_MAX
)
521 memcpy(g_admin_nets
+ g_admin_nets_count
, net
, sizeof(ot_net
));
522 g_admin_nets_permissions
[ g_admin_nets_count
++ ] = permissions
;
527 int off
= snprintf( _debug
, sizeof(_debug
), "Blessing ip net " );
528 off
+= fmt_ip6c(_debug
+off
, net
->address
);
529 if( net
->bits
< 128) {
531 if( ip6_isv4mapped(net
->address
) )
532 off
+= fmt_long(_debug
+off
, net
->bits
-96);
534 off
+= fmt_long(_debug
+off
, net
->bits
);
537 if( permissions
& OT_PERMISSION_MAY_STAT
) off
+= snprintf( _debug
+off
, 512-off
, " may_fetch_stats" );
538 if( permissions
& OT_PERMISSION_MAY_LIVESYNC
) off
+= snprintf( _debug
+off
, 512-off
, " may_sync_live" );
539 if( permissions
& OT_PERMISSION_MAY_FULLSCRAPE
) off
+= snprintf( _debug
+off
, 512-off
, " may_fetch_fullscrapes" );
540 if( permissions
& OT_PERMISSION_MAY_PROXY
) off
+= snprintf( _debug
+off
, 512-off
, " may_proxy" );
541 if( !permissions
) off
+= snprintf( _debug
+off
, sizeof(_debug
)-off
, " nothing" );
543 _debug
[off
++] = '\n';
544 (void)write( 2, _debug
, off
);
551 int accesslist_is_blessed( ot_ip6 ip
, ot_permissions permissions
) {
553 for( i
=0; i
<g_admin_nets_count
; ++i
)
554 if( address_in_net(ip
, g_admin_nets
+ i
) && (g_admin_nets_permissions
[ i
] & permissions
))
559 const char *g_version_accesslist_c
= "$Source$: $Revision$\n";