]> git.ipfire.org Git - people/ms/libloc.git/blob - src/database.c
c41ce46aa7e5020bc2f3c28f9ca5ca9f5082db22
[people/ms/libloc.git] / src / database.c
1 /*
2 libloc - A library to determine the location of someone on the Internet
3
4 Copyright (C) 2017 IPFire Development Team <info@ipfire.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15 */
16
17 #include <arpa/inet.h>
18 #include <ctype.h>
19 #include <endian.h>
20 #include <errno.h>
21 #include <netinet/in.h>
22 #include <stddef.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/mman.h>
28 #include <sys/types.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #include <loc/libloc.h>
33 #include <loc/as.h>
34 #include <loc/database.h>
35 #include <loc/format.h>
36 #include <loc/network.h>
37 #include <loc/private.h>
38 #include <loc/stringpool.h>
39
40 struct loc_database {
41 struct loc_ctx* ctx;
42 int refcount;
43
44 unsigned int version;
45 time_t created_at;
46 off_t vendor;
47 off_t description;
48 off_t license;
49
50 // ASes in the database
51 struct loc_database_as_v0* as_v0;
52 size_t as_count;
53
54 // Network tree
55 struct loc_database_network_node_v0* network_nodes_v0;
56 size_t network_nodes_count;
57
58 // Networks
59 struct loc_database_network_v0* networks_v0;
60 size_t networks_count;
61
62 struct loc_stringpool* pool;
63 };
64
65 #define MAX_STACK_DEPTH 256
66
67 struct loc_node_stack {
68 off_t offset;
69 int i; // Is this node 0 or 1?
70 int depth;
71 };
72
73 struct loc_database_enumerator {
74 struct loc_ctx* ctx;
75 struct loc_database* db;
76 enum loc_database_enumerator_mode mode;
77 int refcount;
78
79 // Search string
80 char* string;
81 char country_code[3];
82 uint32_t asn;
83
84 // Index of the AS we are looking at
85 unsigned int as_index;
86
87 // Network state
88 struct in6_addr network_address;
89 struct loc_node_stack network_stack[MAX_STACK_DEPTH];
90 int network_stack_depth;
91 unsigned int* networks_visited;
92 };
93
94 static int loc_database_read_magic(struct loc_database* db, FILE* f) {
95 struct loc_database_magic magic;
96
97 // Read from file
98 size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
99
100 // Check if we have been able to read enough data
101 if (bytes_read < sizeof(magic)) {
102 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
103 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
104 return -ENOMSG;
105 }
106
107 // Compare magic bytes
108 if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
109 DEBUG(db->ctx, "Magic value matches\n");
110
111 // Parse version
112 db->version = be16toh(magic.version);
113 DEBUG(db->ctx, "Database version is %u\n", db->version);
114
115 return 0;
116 }
117
118 ERROR(db->ctx, "Database format is not compatible\n");
119
120 // Return an error
121 return 1;
122 }
123
124 static int loc_database_read_as_section_v0(struct loc_database* db,
125 FILE* f, const struct loc_database_header_v0* header) {
126 off_t as_offset = be32toh(header->as_offset);
127 size_t as_length = be32toh(header->as_length);
128
129 DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
130
131 if (as_length > 0) {
132 db->as_v0 = mmap(NULL, as_length, PROT_READ,
133 MAP_SHARED, fileno(f), as_offset);
134
135 if (db->as_v0 == MAP_FAILED)
136 return -errno;
137 }
138
139 db->as_count = as_length / sizeof(*db->as_v0);
140
141 INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
142
143 return 0;
144 }
145
146 static int loc_database_read_network_nodes_section_v0(struct loc_database* db,
147 FILE* f, const struct loc_database_header_v0* header) {
148 off_t network_nodes_offset = be32toh(header->network_tree_offset);
149 size_t network_nodes_length = be32toh(header->network_tree_length);
150
151 DEBUG(db->ctx, "Reading network nodes section from %jd (%zu bytes)\n",
152 network_nodes_offset, network_nodes_length);
153
154 if (network_nodes_length > 0) {
155 db->network_nodes_v0 = mmap(NULL, network_nodes_length, PROT_READ,
156 MAP_SHARED, fileno(f), network_nodes_offset);
157
158 if (db->network_nodes_v0 == MAP_FAILED)
159 return -errno;
160 }
161
162 db->network_nodes_count = network_nodes_length / sizeof(*db->network_nodes_v0);
163
164 INFO(db->ctx, "Read %zu network nodes from the database\n", db->network_nodes_count);
165
166 return 0;
167 }
168
169 static int loc_database_read_networks_section_v0(struct loc_database* db,
170 FILE* f, const struct loc_database_header_v0* header) {
171 off_t networks_offset = be32toh(header->network_data_offset);
172 size_t networks_length = be32toh(header->network_data_length);
173
174 DEBUG(db->ctx, "Reading networks section from %jd (%zu bytes)\n",
175 networks_offset, networks_length);
176
177 if (networks_length > 0) {
178 db->networks_v0 = mmap(NULL, networks_length, PROT_READ,
179 MAP_SHARED, fileno(f), networks_offset);
180
181 if (db->networks_v0 == MAP_FAILED)
182 return -errno;
183 }
184
185 db->networks_count = networks_length / sizeof(*db->networks_v0);
186
187 INFO(db->ctx, "Read %zu networks from the database\n", db->networks_count);
188
189 return 0;
190 }
191
192 static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
193 struct loc_database_header_v0 header;
194
195 // Read from file
196 size_t size = fread(&header, 1, sizeof(header), f);
197
198 if (size < sizeof(header)) {
199 ERROR(db->ctx, "Could not read enough data for header\n");
200 return -ENOMSG;
201 }
202
203 // Copy over data
204 db->created_at = be64toh(header.created_at);
205 db->vendor = be32toh(header.vendor);
206 db->description = be32toh(header.description);
207 db->license = be32toh(header.license);
208
209 // Open pool
210 off_t pool_offset = be32toh(header.pool_offset);
211 size_t pool_length = be32toh(header.pool_length);
212
213 int r = loc_stringpool_open(db->ctx, &db->pool,
214 f, pool_length, pool_offset);
215 if (r)
216 return r;
217
218 // AS section
219 r = loc_database_read_as_section_v0(db, f, &header);
220 if (r)
221 return r;
222
223 // Network Nodes
224 r = loc_database_read_network_nodes_section_v0(db, f, &header);
225 if (r)
226 return r;
227
228 // Networks
229 r = loc_database_read_networks_section_v0(db, f, &header);
230 if (r)
231 return r;
232
233 return 0;
234 }
235
236 static int loc_database_read_header(struct loc_database* db, FILE* f) {
237 switch (db->version) {
238 case 0:
239 return loc_database_read_header_v0(db, f);
240
241 default:
242 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
243 return 1;
244 }
245 }
246
247 static int loc_database_read(struct loc_database* db, FILE* f) {
248 clock_t start = clock();
249
250 // Read magic bytes
251 int r = loc_database_read_magic(db, f);
252 if (r)
253 return r;
254
255 // Read the header
256 r = loc_database_read_header(db, f);
257 if (r)
258 return r;
259
260 clock_t end = clock();
261
262 INFO(db->ctx, "Opened database in %.8fs\n",
263 (double)(end - start) / CLOCKS_PER_SEC);
264
265 return 0;
266 }
267
268 LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
269 // Fail on invalid file handle
270 if (!f)
271 return -EINVAL;
272
273 struct loc_database* db = calloc(1, sizeof(*db));
274 if (!db)
275 return -ENOMEM;
276
277 // Reference context
278 db->ctx = loc_ref(ctx);
279 db->refcount = 1;
280
281 DEBUG(db->ctx, "Database object allocated at %p\n", db);
282
283 int r = loc_database_read(db, f);
284 if (r) {
285 loc_database_unref(db);
286 return r;
287 }
288
289 *database = db;
290
291 return 0;
292 }
293
294 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
295 db->refcount++;
296
297 return db;
298 }
299
300 static void loc_database_free(struct loc_database* db) {
301 int r;
302
303 DEBUG(db->ctx, "Releasing database %p\n", db);
304
305 // Removing all ASes
306 if (db->as_v0) {
307 r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0));
308 if (r)
309 ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno));
310 }
311
312 // Remove mapped network sections
313 if (db->networks_v0) {
314 r = munmap(db->networks_v0, db->networks_count * sizeof(*db->networks_v0));
315 if (r)
316 ERROR(db->ctx, "Could not unmap networks section: %s\n", strerror(errno));
317 }
318
319 // Remove mapped network nodes section
320 if (db->network_nodes_v0) {
321 r = munmap(db->network_nodes_v0, db->network_nodes_count * sizeof(*db->network_nodes_v0));
322 if (r)
323 ERROR(db->ctx, "Could not unmap network nodes section: %s\n", strerror(errno));
324 }
325
326 if (db->pool)
327 loc_stringpool_unref(db->pool);
328
329 loc_unref(db->ctx);
330 free(db);
331 }
332
333 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
334 if (--db->refcount > 0)
335 return NULL;
336
337 loc_database_free(db);
338 return NULL;
339 }
340
341 LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
342 return db->created_at;
343 }
344
345 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
346 return loc_stringpool_get(db->pool, db->vendor);
347 }
348
349 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
350 return loc_stringpool_get(db->pool, db->description);
351 }
352
353 LOC_EXPORT const char* loc_database_get_license(struct loc_database* db) {
354 return loc_stringpool_get(db->pool, db->license);
355 }
356
357 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
358 return db->as_count;
359 }
360
361 // Returns the AS at position pos
362 static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) {
363 if ((size_t)pos >= db->as_count)
364 return -EINVAL;
365
366 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
367
368 int r;
369 switch (db->version) {
370 case 0:
371 r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos);
372 break;
373
374 default:
375 return -1;
376 }
377
378 if (r == 0) {
379 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
380 }
381
382 return r;
383 }
384
385 // Performs a binary search to find the AS in the list
386 LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) {
387 off_t lo = 0;
388 off_t hi = db->as_count - 1;
389
390 // Save start time
391 clock_t start = clock();
392
393 while (lo <= hi) {
394 off_t i = (lo + hi) / 2;
395
396 // Fetch AS in the middle between lo and hi
397 int r = loc_database_fetch_as(db, as, i);
398 if (r)
399 return r;
400
401 // Check if this is a match
402 uint32_t as_number = loc_as_get_number(*as);
403 if (as_number == number) {
404 clock_t end = clock();
405
406 // Log how fast this has been
407 DEBUG(db->ctx, "Found AS%u in %.8fs\n", as_number,
408 (double)(end - start) / CLOCKS_PER_SEC);
409
410 return 0;
411 }
412
413 // If it wasn't, we release the AS and
414 // adjust our search pointers
415 loc_as_unref(*as);
416
417 if (as_number < number) {
418 lo = i + 1;
419 } else
420 hi = i - 1;
421 }
422
423 // Nothing found
424 *as = NULL;
425
426 return 1;
427 }
428
429 // Returns the network at position pos
430 static int loc_database_fetch_network(struct loc_database* db, struct loc_network** network,
431 struct in6_addr* address, unsigned int prefix, off_t pos) {
432 if ((size_t)pos >= db->networks_count)
433 return -EINVAL;
434
435 DEBUG(db->ctx, "Fetching network at position %jd\n", pos);
436
437 int r;
438 switch (db->version) {
439 case 0:
440 r = loc_network_new_from_database_v0(db->ctx, network,
441 address, prefix, db->networks_v0 + pos);
442 break;
443
444 default:
445 return -1;
446 }
447
448 if (r == 0) {
449 char* string = loc_network_str(*network);
450 DEBUG(db->ctx, "Got network %s\n", string);
451 free(string);
452 }
453
454 return r;
455 }
456
457 static int __loc_database_node_is_leaf(const struct loc_database_network_node_v0* node) {
458 return (node->network != htobe32(0xffffffff));
459 }
460
461 static int __loc_database_lookup_handle_leaf(struct loc_database* db, const struct in6_addr* address,
462 struct loc_network** network, struct in6_addr* network_address, unsigned int prefix,
463 const struct loc_database_network_node_v0* node) {
464 off_t network_index = be32toh(node->network);
465
466 DEBUG(db->ctx, "Handling leaf node at %jd (%jd)\n", node - db->network_nodes_v0, network_index);
467
468 // Fetch the network
469 int r = loc_database_fetch_network(db, network,
470 network_address, prefix, network_index);
471 if (r) {
472 ERROR(db->ctx, "Could not fetch network %jd from database\n", network_index);
473 return r;
474 }
475
476 // Check if the given IP address is inside the network
477 r = loc_network_match_address(*network, address);
478 if (r) {
479 DEBUG(db->ctx, "Searched address is not part of the network\n");
480
481 loc_network_unref(*network);
482 *network = NULL;
483 return 1;
484 }
485
486 // A network was found and the IP address matches
487 return 0;
488 }
489
490 // Searches for an exact match along the path
491 static int __loc_database_lookup(struct loc_database* db, const struct in6_addr* address,
492 struct loc_network** network, struct in6_addr* network_address,
493 const struct loc_database_network_node_v0* node, unsigned int level) {
494 int r;
495 off_t node_index;
496
497 // Follow the path
498 int bit = in6_addr_get_bit(address, level);
499 in6_addr_set_bit(network_address, level, bit);
500
501 if (bit == 0)
502 node_index = be32toh(node->zero);
503 else
504 node_index = be32toh(node->one);
505
506 // If the node index is zero, the tree ends here
507 // and we cannot descend any further
508 if (node_index > 0) {
509 // Check boundaries
510 if ((size_t)node_index >= db->network_nodes_count)
511 return -EINVAL;
512
513 // Move on to the next node
514 r = __loc_database_lookup(db, address, network, network_address,
515 db->network_nodes_v0 + node_index, level + 1);
516
517 // End here if a result was found
518 if (r == 0)
519 return r;
520
521 // Raise any errors
522 else if (r < 0)
523 return r;
524
525 DEBUG(db->ctx, "No match found below level %u\n", level);
526 } else {
527 DEBUG(db->ctx, "Tree ended at level %u\n", level);
528 }
529
530 // If this node has a leaf, we will check if it matches
531 if (__loc_database_node_is_leaf(node)) {
532 r = __loc_database_lookup_handle_leaf(db, address, network, network_address, level, node);
533 if (r <= 0)
534 return r;
535 }
536
537 return 1;
538 }
539
540 LOC_EXPORT int loc_database_lookup(struct loc_database* db,
541 struct in6_addr* address, struct loc_network** network) {
542 struct in6_addr network_address;
543 memset(&network_address, 0, sizeof(network_address));
544
545 *network = NULL;
546
547 // Save start time
548 clock_t start = clock();
549
550 int r = __loc_database_lookup(db, address, network, &network_address,
551 db->network_nodes_v0, 0);
552
553 clock_t end = clock();
554
555 // Log how fast this has been
556 DEBUG(db->ctx, "Executed network search in %.8fs\n",
557 (double)(end - start) / CLOCKS_PER_SEC);
558
559 return r;
560 }
561
562 LOC_EXPORT int loc_database_lookup_from_string(struct loc_database* db,
563 const char* string, struct loc_network** network) {
564 struct in6_addr address;
565
566 int r = loc_parse_address(db->ctx, string, &address);
567 if (r)
568 return r;
569
570 return loc_database_lookup(db, &address, network);
571 }
572
573 // Enumerator
574
575 LOC_EXPORT int loc_database_enumerator_new(struct loc_database_enumerator** enumerator,
576 struct loc_database* db, enum loc_database_enumerator_mode mode) {
577 struct loc_database_enumerator* e = calloc(1, sizeof(*e));
578 if (!e)
579 return -ENOMEM;
580
581 // Reference context
582 e->ctx = loc_ref(db->ctx);
583 e->db = loc_database_ref(db);
584 e->mode = mode;
585 e->refcount = 1;
586
587 // Initialise graph search
588 //e->network_stack[++e->network_stack_depth] = 0;
589 e->network_stack_depth = 1;
590 e->networks_visited = calloc(db->network_nodes_count, sizeof(*e->networks_visited));
591
592 DEBUG(e->ctx, "Database enumerator object allocated at %p\n", e);
593
594 *enumerator = e;
595 return 0;
596 }
597
598 LOC_EXPORT struct loc_database_enumerator* loc_database_enumerator_ref(struct loc_database_enumerator* enumerator) {
599 enumerator->refcount++;
600
601 return enumerator;
602 }
603
604 static void loc_database_enumerator_free(struct loc_database_enumerator* enumerator) {
605 DEBUG(enumerator->ctx, "Releasing database enumerator %p\n", enumerator);
606
607 // Release all references
608 loc_database_unref(enumerator->db);
609 loc_unref(enumerator->ctx);
610
611 if (enumerator->string)
612 free(enumerator->string);
613
614 // Free network search
615 free(enumerator->networks_visited);
616
617 free(enumerator);
618 }
619
620 LOC_EXPORT struct loc_database_enumerator* loc_database_enumerator_unref(struct loc_database_enumerator* enumerator) {
621 if (!enumerator)
622 return NULL;
623
624 if (--enumerator->refcount > 0)
625 return enumerator;
626
627 loc_database_enumerator_free(enumerator);
628 return NULL;
629 }
630
631 LOC_EXPORT int loc_database_enumerator_set_string(struct loc_database_enumerator* enumerator, const char* string) {
632 enumerator->string = strdup(string);
633
634 // Make the string lowercase
635 for (char *p = enumerator->string; *p; p++)
636 *p = tolower(*p);
637
638 return 0;
639 }
640
641 LOC_EXPORT int loc_database_enumerator_set_country_code(struct loc_database_enumerator* enumerator, const char* country_code) {
642 // Set empty country code
643 if (!country_code || !*country_code) {
644 *enumerator->country_code = '\0';
645 return 0;
646 }
647
648 // Country codes must be two characters
649 if (strlen(country_code) != 2)
650 return -EINVAL;
651
652 for (unsigned int i = 0; i < 3; i++) {
653 enumerator->country_code[i] = country_code[i];
654 }
655
656 return 0;
657 }
658
659 LOC_EXPORT int loc_database_enumerator_set_asn(
660 struct loc_database_enumerator* enumerator, unsigned int asn) {
661 enumerator->asn = asn;
662
663 return 0;
664 }
665
666 LOC_EXPORT int loc_database_enumerator_next_as(
667 struct loc_database_enumerator* enumerator, struct loc_as** as) {
668 *as = NULL;
669
670 // Do not do anything if not in AS mode
671 if (enumerator->mode != LOC_DB_ENUMERATE_ASES)
672 return 0;
673
674 struct loc_database* db = enumerator->db;
675
676 while (enumerator->as_index < db->as_count) {
677 // Fetch the next AS
678 int r = loc_database_fetch_as(db, as, enumerator->as_index++);
679 if (r)
680 return r;
681
682 r = loc_as_match_string(*as, enumerator->string);
683 if (r == 1) {
684 DEBUG(enumerator->ctx, "AS%d (%s) matches %s\n",
685 loc_as_get_number(*as), loc_as_get_name(*as), enumerator->string);
686
687 return 0;
688 }
689
690 // No match
691 loc_as_unref(*as);
692 *as = NULL;
693 }
694
695 // Reset the index
696 enumerator->as_index = 0;
697
698 // We have searched through all of them
699 return 0;
700 }
701
702 static int loc_database_enumerator_stack_push_node(
703 struct loc_database_enumerator* e, off_t offset, int i, int depth) {
704 // Do not add empty nodes
705 if (!offset)
706 return 0;
707
708 // Check if there is any space left on the stack
709 if (e->network_stack_depth >= MAX_STACK_DEPTH) {
710 ERROR(e->ctx, "Maximum stack size reached: %d\n", e->network_stack_depth);
711 return -1;
712 }
713
714 // Increase stack size
715 int s = ++e->network_stack_depth;
716
717 DEBUG(e->ctx, "Added node %jd to stack (%d)\n", offset, depth);
718
719 e->network_stack[s].offset = offset;
720 e->network_stack[s].i = i;
721 e->network_stack[s].depth = depth;
722
723 return 0;
724 }
725
726 LOC_EXPORT int loc_database_enumerator_next_network(
727 struct loc_database_enumerator* enumerator, struct loc_network** network) {
728 // Reset network
729 *network = NULL;
730
731 // Do not do anything if not in network mode
732 if (enumerator->mode != LOC_DB_ENUMERATE_NETWORKS)
733 return 0;
734
735 int r;
736
737 DEBUG(enumerator->ctx, "Called with a stack of %u nodes\n",
738 enumerator->network_stack_depth);
739
740 // Perform DFS
741 while (enumerator->network_stack_depth > 0) {
742 DEBUG(enumerator->ctx, "Stack depth: %u\n", enumerator->network_stack_depth);
743
744 // Get object from top of the stack
745 struct loc_node_stack* node = &enumerator->network_stack[enumerator->network_stack_depth];
746
747 // Remove the node from the stack if we have already visited it
748 if (enumerator->networks_visited[node->offset]) {
749 enumerator->network_stack_depth--;
750 continue;
751 }
752
753 // Mark the bits on the path correctly
754 in6_addr_set_bit(&enumerator->network_address,
755 (node->depth > 0) ? node->depth - 1 : 0, node->i);
756
757 DEBUG(enumerator->ctx, "Looking at node %jd\n", node->offset);
758 enumerator->networks_visited[node->offset]++;
759
760 // Pop node from top of the stack
761 struct loc_database_network_node_v0* n =
762 enumerator->db->network_nodes_v0 + node->offset;
763
764 // Add edges to stack
765 r = loc_database_enumerator_stack_push_node(enumerator,
766 be32toh(n->one), 1, node->depth + 1);
767
768 if (r)
769 return r;
770
771 r = loc_database_enumerator_stack_push_node(enumerator,
772 be32toh(n->zero), 0, node->depth + 1);
773
774 if (r)
775 return r;
776
777 // Check if this node is a leaf and has a network object
778 if (__loc_database_node_is_leaf(n)) {
779 off_t network_index = be32toh(n->network);
780
781 DEBUG(enumerator->ctx, "Node has a network at %jd\n", network_index);
782
783 // Fetch the network object
784 r = loc_database_fetch_network(enumerator->db, network,
785 &enumerator->network_address, node->depth, network_index);
786
787 // Break on any errors
788 if (r)
789 return r;
790
791 // Check if we are interested in this network
792
793 // Skip if the country code does not match
794 if (enumerator->country_code &&
795 !loc_network_match_country_code(*network, enumerator->country_code)) {
796 loc_network_unref(*network);
797 *network = NULL;
798
799 continue;
800 }
801
802 // Skip if the ASN does not match
803 if (enumerator->asn &&
804 !loc_network_match_asn(*network, enumerator->asn)) {
805 loc_network_unref(*network);
806 *network = NULL;
807
808 continue;
809 }
810
811 return 0;
812 }
813 }
814
815 // Reached the end of the search
816
817 // Mark all nodes as non-visited
818 for (unsigned int i = 0; i < enumerator->db->network_nodes_count; i++)
819 enumerator->networks_visited[i] = 0;
820
821 return 0;
822 }