]> git.ipfire.org Git - people/ms/libloc.git/blob - src/network.c
ad3989250e421e098068ed52e5f4b8c6d3bd2522
[people/ms/libloc.git] / src / network.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 <assert.h>
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23
24 #ifdef HAVE_ENDIAN_H
25 # include <endian.h>
26 #endif
27
28 #include <loc/libloc.h>
29 #include <loc/compat.h>
30 #include <loc/country.h>
31 #include <loc/network.h>
32 #include <loc/private.h>
33
34 struct loc_network {
35 struct loc_ctx* ctx;
36 int refcount;
37
38 struct in6_addr start_address;
39 unsigned int prefix;
40
41 char country_code[3];
42 uint32_t asn;
43 enum loc_network_flags flags;
44 };
45
46 static int valid_prefix(struct in6_addr* address, unsigned int prefix) {
47 // The prefix cannot be larger than 128 bits
48 if (prefix > 128)
49 return 1;
50
51 // And the prefix cannot be zero
52 if (prefix == 0)
53 return 1;
54
55 // For IPv4-mapped addresses the prefix has to be 96 or lager
56 if (IN6_IS_ADDR_V4MAPPED(address) && prefix <= 96)
57 return 1;
58
59 return 0;
60 }
61
62 static struct in6_addr prefix_to_bitmask(unsigned int prefix) {
63 struct in6_addr bitmask;
64
65 for (unsigned int i = 0; i < 16; i++)
66 bitmask.s6_addr[i] = 0;
67
68 for (int i = prefix, j = 0; i > 0; i -= 8, j++) {
69 if (i >= 8)
70 bitmask.s6_addr[j] = 0xff;
71 else
72 bitmask.s6_addr[j] = 0xff << (8 - i);
73 }
74
75 return bitmask;
76 }
77
78 static struct in6_addr make_start_address(const struct in6_addr* address, unsigned int prefix) {
79 struct in6_addr a;
80 struct in6_addr bitmask = prefix_to_bitmask(prefix);
81
82 // Perform bitwise AND
83 for (unsigned int i = 0; i < 4; i++)
84 a.s6_addr32[i] = address->s6_addr32[i] & bitmask.s6_addr32[i];
85
86 return a;
87 }
88
89 static struct in6_addr make_last_address(const struct in6_addr* address, unsigned int prefix) {
90 struct in6_addr a;
91 struct in6_addr bitmask = prefix_to_bitmask(prefix);
92
93 // Perform bitwise OR
94 for (unsigned int i = 0; i < 4; i++)
95 a.s6_addr32[i] = address->s6_addr32[i] | ~bitmask.s6_addr32[i];
96
97 return a;
98 }
99
100 LOC_EXPORT int loc_network_new(struct loc_ctx* ctx, struct loc_network** network,
101 struct in6_addr* address, unsigned int prefix) {
102 // Address cannot be unspecified
103 if (IN6_IS_ADDR_UNSPECIFIED(address)) {
104 DEBUG(ctx, "Start address is unspecified\n");
105 return -EINVAL;
106 }
107
108 // Address cannot be loopback
109 if (IN6_IS_ADDR_LOOPBACK(address)) {
110 DEBUG(ctx, "Start address is loopback address\n");
111 return -EINVAL;
112 }
113
114 // Address cannot be link-local
115 if (IN6_IS_ADDR_LINKLOCAL(address)) {
116 DEBUG(ctx, "Start address cannot be link-local\n");
117 return -EINVAL;
118 }
119
120 // Address cannot be site-local
121 if (IN6_IS_ADDR_SITELOCAL(address)) {
122 DEBUG(ctx, "Start address cannot be site-local\n");
123 return -EINVAL;
124 }
125
126 // Validate the prefix
127 if (valid_prefix(address, prefix) != 0) {
128 DEBUG(ctx, "Invalid prefix: %u\n", prefix);
129 return -EINVAL;
130 }
131
132 struct loc_network* n = calloc(1, sizeof(*n));
133 if (!n)
134 return -ENOMEM;
135
136 n->ctx = loc_ref(ctx);
137 n->refcount = 1;
138
139 // Store the first address in the network
140 n->start_address = make_start_address(address, prefix);
141 n->prefix = prefix;
142
143 DEBUG(n->ctx, "Network allocated at %p\n", n);
144 *network = n;
145 return 0;
146 }
147
148 static int loc_network_address_family(struct loc_network* network) {
149 if (IN6_IS_ADDR_V4MAPPED(&network->start_address))
150 return AF_INET;
151
152 return AF_INET6;
153 }
154
155 LOC_EXPORT int loc_network_new_from_string(struct loc_ctx* ctx, struct loc_network** network,
156 const char* address_string) {
157 struct in6_addr start_address;
158 unsigned int prefix = 0;
159 char* prefix_string;
160 int r = 1;
161
162 // Make a copy of the string to work on it
163 char* buffer = strdup(address_string);
164 address_string = prefix_string = buffer;
165
166 // Split address and prefix
167 address_string = strsep(&prefix_string, "/");
168
169 // Did we find a prefix?
170 if (prefix_string) {
171 // Convert prefix to integer
172 prefix = strtol(prefix_string, NULL, 10);
173
174 if (prefix) {
175 // Parse the address
176 r = loc_parse_address(ctx, address_string, &start_address);
177
178 // Map the prefix to IPv6 if needed
179 if (IN6_IS_ADDR_V4MAPPED(&start_address))
180 prefix += 96;
181 }
182 }
183
184 // Free temporary buffer
185 free(buffer);
186
187 if (r == 0) {
188 r = loc_network_new(ctx, network, &start_address, prefix);
189 }
190
191 return r;
192 }
193
194 LOC_EXPORT struct loc_network* loc_network_ref(struct loc_network* network) {
195 network->refcount++;
196
197 return network;
198 }
199
200 static void loc_network_free(struct loc_network* network) {
201 DEBUG(network->ctx, "Releasing network at %p\n", network);
202
203 loc_unref(network->ctx);
204 free(network);
205 }
206
207 LOC_EXPORT struct loc_network* loc_network_unref(struct loc_network* network) {
208 if (!network)
209 return NULL;
210
211 if (--network->refcount > 0)
212 return network;
213
214 loc_network_free(network);
215 return NULL;
216 }
217
218 static int format_ipv6_address(const struct in6_addr* address, char* string, size_t length) {
219 const char* ret = inet_ntop(AF_INET6, address, string, length);
220 if (!ret)
221 return -1;
222
223 return 0;
224 }
225
226 static int format_ipv4_address(const struct in6_addr* address, char* string, size_t length) {
227 struct in_addr ipv4_address;
228 ipv4_address.s_addr = address->s6_addr32[3];
229
230 const char* ret = inet_ntop(AF_INET, &ipv4_address, string, length);
231 if (!ret)
232 return -1;
233
234 return 0;
235 }
236
237 LOC_EXPORT char* loc_network_str(struct loc_network* network) {
238 int r;
239 const size_t length = INET6_ADDRSTRLEN + 4;
240
241 char* string = malloc(length);
242 if (!string)
243 return NULL;
244
245 unsigned int prefix = network->prefix;
246
247 int family = loc_network_address_family(network);
248 switch (family) {
249 case AF_INET6:
250 r = format_ipv6_address(&network->start_address, string, length);
251 break;
252
253 case AF_INET:
254 r = format_ipv4_address(&network->start_address, string, length);
255 prefix -= 96;
256 break;
257
258 default:
259 r = -1;
260 break;
261 }
262
263 if (r) {
264 ERROR(network->ctx, "Could not convert network to string: %s\n", strerror(errno));
265 free(string);
266
267 return NULL;
268 }
269
270 // Append prefix
271 sprintf(string + strlen(string), "/%u", prefix);
272
273 return string;
274 }
275
276 LOC_EXPORT int loc_network_match_address(struct loc_network* network, const struct in6_addr* address) {
277 // Address must be larger than the start address
278 if (in6_addr_cmp(&network->start_address, address) > 0)
279 return 1;
280
281 // Determine the last address in this network
282 struct in6_addr last_address = make_last_address(&network->start_address, network->prefix);
283
284 // Address must be smaller than the last address
285 if (in6_addr_cmp(address, &last_address) > 0)
286 return 1;
287
288 // The address is inside this network
289 return 0;
290 }
291
292 LOC_EXPORT const char* loc_network_get_country_code(struct loc_network* network) {
293 return network->country_code;
294 }
295
296 LOC_EXPORT int loc_network_set_country_code(struct loc_network* network, const char* country_code) {
297 // Set empty country code
298 if (!country_code || !*country_code) {
299 *network->country_code = '\0';
300 return 0;
301 }
302
303 // Check country code
304 if (!loc_country_code_is_valid(country_code))
305 return -EINVAL;
306
307 loc_country_code_copy(network->country_code, country_code);
308
309 return 0;
310 }
311
312 LOC_EXPORT int loc_network_match_country_code(struct loc_network* network, const char* country_code) {
313 // Check country code
314 if (!loc_country_code_is_valid(country_code))
315 return -EINVAL;
316
317 return (network->country_code[0] == country_code[0])
318 && (network->country_code[1] == country_code[1]);
319 }
320
321 LOC_EXPORT uint32_t loc_network_get_asn(struct loc_network* network) {
322 return network->asn;
323 }
324
325 LOC_EXPORT int loc_network_set_asn(struct loc_network* network, uint32_t asn) {
326 network->asn = asn;
327
328 return 0;
329 }
330
331 LOC_EXPORT int loc_network_match_asn(struct loc_network* network, uint32_t asn) {
332 return network->asn == asn;
333 }
334
335 LOC_EXPORT int loc_network_has_flag(struct loc_network* network, uint32_t flag) {
336 return network->flags & flag;
337 }
338
339 LOC_EXPORT int loc_network_set_flag(struct loc_network* network, uint32_t flag) {
340 network->flags |= flag;
341
342 return 0;
343 }
344
345 LOC_EXPORT int loc_network_match_flag(struct loc_network* network, uint32_t flag) {
346 return loc_network_has_flag(network, flag);
347 }
348
349 LOC_EXPORT int loc_network_to_database_v0(struct loc_network* network, struct loc_database_network_v0* dbobj) {
350 // Add country code
351 loc_country_code_copy(dbobj->country_code, network->country_code);
352
353 // Add ASN
354 dbobj->asn = htobe32(network->asn);
355
356 // Flags
357 dbobj->flags = htobe16(network->flags);
358
359 return 0;
360 }
361
362 LOC_EXPORT int loc_network_new_from_database_v0(struct loc_ctx* ctx, struct loc_network** network,
363 struct in6_addr* address, unsigned int prefix, const struct loc_database_network_v0* dbobj) {
364 char country_code[3] = "\0\0";
365
366 int r = loc_network_new(ctx, network, address, prefix);
367 if (r) {
368 ERROR(ctx, "Could not allocate a new network: %s", strerror(-r));
369 return r;
370 }
371
372 // Import country code
373 loc_country_code_copy(country_code, dbobj->country_code);
374
375 r = loc_network_set_country_code(*network, country_code);
376 if (r) {
377 ERROR(ctx, "Could not set country code: %s\n", country_code);
378 return r;
379 }
380
381 // Import ASN
382 uint32_t asn = be32toh(dbobj->asn);
383 r = loc_network_set_asn(*network, asn);
384 if (r) {
385 ERROR(ctx, "Could not set ASN: %d\n", asn);
386 return r;
387 }
388
389 // Import flags
390 int flags = be16toh(dbobj->flags);
391 r = loc_network_set_flag(*network, flags);
392 if (r) {
393 ERROR(ctx, "Could not set flags: %d\n", flags);
394 return r;
395 }
396
397 return 0;
398 }
399
400 struct loc_network_tree {
401 struct loc_ctx* ctx;
402 int refcount;
403
404 struct loc_network_tree_node* root;
405 };
406
407 struct loc_network_tree_node {
408 struct loc_ctx* ctx;
409 int refcount;
410
411 struct loc_network_tree_node* zero;
412 struct loc_network_tree_node* one;
413
414 struct loc_network* network;
415 };
416
417 LOC_EXPORT int loc_network_tree_new(struct loc_ctx* ctx, struct loc_network_tree** tree) {
418 struct loc_network_tree* t = calloc(1, sizeof(*t));
419 if (!t)
420 return -ENOMEM;
421
422 t->ctx = loc_ref(ctx);
423 t->refcount = 1;
424
425 // Create the root node
426 int r = loc_network_tree_node_new(ctx, &t->root);
427 if (r) {
428 loc_network_tree_unref(t);
429 return r;
430 }
431
432 DEBUG(t->ctx, "Network tree allocated at %p\n", t);
433 *tree = t;
434 return 0;
435 }
436
437 LOC_EXPORT struct loc_network_tree_node* loc_network_tree_get_root(struct loc_network_tree* tree) {
438 return loc_network_tree_node_ref(tree->root);
439 }
440
441 static struct loc_network_tree_node* loc_network_tree_get_node(struct loc_network_tree_node* node, int path) {
442 struct loc_network_tree_node** n;
443
444 if (path == 0)
445 n = &node->zero;
446 else
447 n = &node->one;
448
449 // If the desired node doesn't exist, yet, we will create it
450 if (*n == NULL) {
451 int r = loc_network_tree_node_new(node->ctx, n);
452 if (r)
453 return NULL;
454 }
455
456 return *n;
457 }
458
459 static struct loc_network_tree_node* loc_network_tree_get_path(struct loc_network_tree* tree, const struct in6_addr* address, unsigned int prefix) {
460 struct loc_network_tree_node* node = tree->root;
461
462 for (unsigned int i = 0; i < prefix; i++) {
463 // Check if the ith bit is one or zero
464 node = loc_network_tree_get_node(node, in6_addr_get_bit(address, i));
465 }
466
467 return node;
468 }
469
470 static int __loc_network_tree_walk(struct loc_ctx* ctx, struct loc_network_tree_node* node,
471 int(*filter_callback)(struct loc_network* network, void* data),
472 int(*callback)(struct loc_network* network, void* data), void* data) {
473 int r;
474
475 // Finding a network ends the walk here
476 if (node->network) {
477 if (filter_callback) {
478 int f = filter_callback(node->network, data);
479 if (f < 0)
480 return f;
481
482 // Skip network if filter function returns value greater than zero
483 if (f > 0)
484 return 0;
485 }
486
487 r = callback(node->network, data);
488 if (r)
489 return r;
490 }
491
492 // Walk down on the left side of the tree first
493 if (node->zero) {
494 r = __loc_network_tree_walk(ctx, node->zero, filter_callback, callback, data);
495 if (r)
496 return r;
497 }
498
499 // Then walk on the other side
500 if (node->one) {
501 r = __loc_network_tree_walk(ctx, node->one, filter_callback, callback, data);
502 if (r)
503 return r;
504 }
505
506 return 0;
507 }
508
509 LOC_EXPORT int loc_network_tree_walk(struct loc_network_tree* tree,
510 int(*filter_callback)(struct loc_network* network, void* data),
511 int(*callback)(struct loc_network* network, void* data), void* data) {
512 return __loc_network_tree_walk(tree->ctx, tree->root, filter_callback, callback, data);
513 }
514
515 static void loc_network_tree_free(struct loc_network_tree* tree) {
516 DEBUG(tree->ctx, "Releasing network tree at %p\n", tree);
517
518 loc_network_tree_node_unref(tree->root);
519
520 loc_unref(tree->ctx);
521 free(tree);
522 }
523
524 LOC_EXPORT struct loc_network_tree* loc_network_tree_unref(struct loc_network_tree* tree) {
525 if (--tree->refcount > 0)
526 return tree;
527
528 loc_network_tree_free(tree);
529 return NULL;
530 }
531
532 static int __loc_network_tree_dump(struct loc_network* network, void* data) {
533 DEBUG(network->ctx, "Dumping network at %p\n", network);
534
535 char* s = loc_network_str(network);
536 if (!s)
537 return 1;
538
539 INFO(network->ctx, "%s\n", s);
540 free(s);
541
542 return 0;
543 }
544
545 LOC_EXPORT int loc_network_tree_dump(struct loc_network_tree* tree) {
546 DEBUG(tree->ctx, "Dumping network tree at %p\n", tree);
547
548 return loc_network_tree_walk(tree, NULL, __loc_network_tree_dump, NULL);
549 }
550
551 LOC_EXPORT int loc_network_tree_add_network(struct loc_network_tree* tree, struct loc_network* network) {
552 DEBUG(tree->ctx, "Adding network %p to tree %p\n", network, tree);
553
554 struct loc_network_tree_node* node = loc_network_tree_get_path(tree,
555 &network->start_address, network->prefix);
556 if (!node) {
557 ERROR(tree->ctx, "Could not find a node\n");
558 return -ENOMEM;
559 }
560
561 // Check if node has not been set before
562 if (node->network) {
563 DEBUG(tree->ctx, "There is already a network at this path\n");
564 return -EBUSY;
565 }
566
567 // Point node to the network
568 node->network = loc_network_ref(network);
569
570 return 0;
571 }
572
573 static int __loc_network_tree_count(struct loc_network* network, void* data) {
574 size_t* counter = (size_t*)data;
575
576 // Increase the counter for each network
577 counter++;
578
579 return 0;
580 }
581
582 LOC_EXPORT size_t loc_network_tree_count_networks(struct loc_network_tree* tree) {
583 size_t counter = 0;
584
585 int r = loc_network_tree_walk(tree, NULL, __loc_network_tree_count, &counter);
586 if (r)
587 return r;
588
589 return counter;
590 }
591
592 static size_t __loc_network_tree_count_nodes(struct loc_network_tree_node* node) {
593 size_t counter = 1;
594
595 if (node->zero)
596 counter += __loc_network_tree_count_nodes(node->zero);
597
598 if (node->one)
599 counter += __loc_network_tree_count_nodes(node->one);
600
601 return counter;
602 }
603
604 LOC_EXPORT size_t loc_network_tree_count_nodes(struct loc_network_tree* tree) {
605 return __loc_network_tree_count_nodes(tree->root);
606 }
607
608 LOC_EXPORT int loc_network_tree_node_new(struct loc_ctx* ctx, struct loc_network_tree_node** node) {
609 struct loc_network_tree_node* n = calloc(1, sizeof(*n));
610 if (!n)
611 return -ENOMEM;
612
613 n->ctx = loc_ref(ctx);
614 n->refcount = 1;
615
616 n->zero = n->one = NULL;
617
618 DEBUG(n->ctx, "Network node allocated at %p\n", n);
619 *node = n;
620 return 0;
621 }
622
623 LOC_EXPORT struct loc_network_tree_node* loc_network_tree_node_ref(struct loc_network_tree_node* node) {
624 if (node)
625 node->refcount++;
626
627 return node;
628 }
629
630 static void loc_network_tree_node_free(struct loc_network_tree_node* node) {
631 DEBUG(node->ctx, "Releasing network node at %p\n", node);
632
633 if (node->network)
634 loc_network_unref(node->network);
635
636 if (node->zero)
637 loc_network_tree_node_unref(node->zero);
638
639 if (node->one)
640 loc_network_tree_node_unref(node->one);
641
642 loc_unref(node->ctx);
643 free(node);
644 }
645
646 LOC_EXPORT struct loc_network_tree_node* loc_network_tree_node_unref(struct loc_network_tree_node* node) {
647 if (!node)
648 return NULL;
649
650 if (--node->refcount > 0)
651 return node;
652
653 loc_network_tree_node_free(node);
654 return NULL;
655 }
656
657 LOC_EXPORT struct loc_network_tree_node* loc_network_tree_node_get(struct loc_network_tree_node* node, unsigned int index) {
658 if (index == 0)
659 node = node->zero;
660 else
661 node = node->one;
662
663 if (!node)
664 return NULL;
665
666 return loc_network_tree_node_ref(node);
667 }
668
669 LOC_EXPORT int loc_network_tree_node_is_leaf(struct loc_network_tree_node* node) {
670 return (!!node->network);
671 }
672
673 LOC_EXPORT struct loc_network* loc_network_tree_node_get_network(struct loc_network_tree_node* node) {
674 return loc_network_ref(node->network);
675 }