diff options
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 | ||
