]> git.ipfire.org Git - people/ms/libloc.git/blame - src/network.c
network: Support parsing IPv4 addresses and map them to IPv6
[people/ms/libloc.git] / src / network.c
CommitLineData
3b5f4af2
MT
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>
f3e02bc5 19#include <endian.h>
3b5f4af2 20#include <errno.h>
71ff3e69 21#include <stdio.h>
3b5f4af2
MT
22#include <stdlib.h>
23#include <string.h>
24
25#include <loc/libloc.h>
26#include <loc/network.h>
9fc7f001 27#include <loc/private.h>
3b5f4af2
MT
28
29struct loc_network {
30 struct loc_ctx* ctx;
31 int refcount;
32
33 struct in6_addr start_address;
34 unsigned int prefix;
35
36 char country_code[3];
71ff3e69 37 uint32_t asn;
3b5f4af2
MT
38};
39
40LOC_EXPORT int loc_network_new(struct loc_ctx* ctx, struct loc_network** network,
41 struct in6_addr start_address, unsigned int prefix) {
42 // Address cannot be unspecified
43 if (IN6_IS_ADDR_UNSPECIFIED(&start_address)) {
44 DEBUG(ctx, "Start address is unspecified\n");
45 return -EINVAL;
46 }
47
48 // Address cannot be loopback
49 if (IN6_IS_ADDR_LOOPBACK(&start_address)) {
50 DEBUG(ctx, "Start address is loopback address\n");
51 return -EINVAL;
52 }
53
54 // Address cannot be link-local
55 if (IN6_IS_ADDR_LINKLOCAL(&start_address)) {
56 DEBUG(ctx, "Start address cannot be link-local\n");
57 return -EINVAL;
58 }
59
60 // Address cannot be site-local
61 if (IN6_IS_ADDR_SITELOCAL(&start_address)) {
62 DEBUG(ctx, "Start address cannot be site-local\n");
63 return -EINVAL;
64 }
65
66 struct loc_network* n = calloc(1, sizeof(*n));
67 if (!n)
68 return -ENOMEM;
69
70 n->ctx = loc_ref(ctx);
71 n->refcount = 1;
72
73 n->start_address = start_address;
74 n->prefix = prefix;
75
76 DEBUG(n->ctx, "Network allocated at %p\n", n);
77 *network = n;
78 return 0;
79}
80
d3409788
MT
81static int loc_network_address_family(struct loc_network* network) {
82 if (IN6_IS_ADDR_V4MAPPED(&network->start_address))
83 return AF_INET;
84
85 return AF_INET6;
86}
87
88static int parse_address(struct loc_ctx* ctx, const char* string, struct in6_addr* address) {
89 DEBUG(ctx, "Paring IP address %s\n", string);
90
91 // Try parsing this as an IPv6 address
92 int r = inet_pton(AF_INET6, string, address);
93
94 // If inet_pton returns one it has been successful
95 if (r == 1) {
96 DEBUG(ctx, "%s is an IPv6 address\n", string);
97 return 0;
98 }
99
100 // Try parsing this as an IPv4 address
101 struct in_addr ipv4_address;
102 r = inet_pton(AF_INET, string, &ipv4_address);
103 if (r == 1) {
104 DEBUG(ctx, "%s is an IPv4 address\n", string);
105
106 // Convert to IPv6-mapped address
107 address->s6_addr32[0] = htonl(0x0000);
108 address->s6_addr32[1] = htonl(0x0000);
109 address->s6_addr32[2] = htonl(0xffff);
110 address->s6_addr32[3] = ipv4_address.s_addr;
111
112 return 0;
113 }
114
115 DEBUG(ctx, "%s is not an valid IP address\n", string);
116 return 1;
117}
118
3b5f4af2
MT
119LOC_EXPORT int loc_network_new_from_string(struct loc_ctx* ctx, struct loc_network** network,
120 const char* address_string) {
121 struct in6_addr start_address;
122 char* prefix_string;
123
124 // Make a copy of the string to work on it
125 char* buffer = strdup(address_string);
126 address_string = prefix_string = buffer;
127
128 // Split address and prefix
129 address_string = strsep(&prefix_string, "/");
130
131 // Convert prefix to integer
132 unsigned int prefix = strtol(prefix_string, NULL, 10);
133
134 // Parse the address
d3409788 135 int r = parse_address(ctx, address_string, &start_address);
3b5f4af2
MT
136
137 // Free temporary buffer
138 free(buffer);
139
d3409788 140 if (r == 0) {
3b5f4af2
MT
141 r = loc_network_new(ctx, network, start_address, prefix);
142 }
143
144 return r;
145}
146
147LOC_EXPORT struct loc_network* loc_network_ref(struct loc_network* network) {
148 network->refcount++;
149
150 return network;
151}
152
153static void loc_network_free(struct loc_network* network) {
154 DEBUG(network->ctx, "Releasing network at %p\n", network);
155
3b5f4af2
MT
156 loc_unref(network->ctx);
157 free(network);
158}
159
160LOC_EXPORT struct loc_network* loc_network_unref(struct loc_network* network) {
161 if (--network->refcount > 0)
162 return network;
163
164 loc_network_free(network);
165 return NULL;
166}
167
d3409788
MT
168static int format_ipv6_address(struct loc_network* network, char* string, size_t length) {
169 const char* ret = inet_ntop(AF_INET6, &network->start_address, string, length);
170 if (!ret)
171 return -1;
172
173 return 0;
174}
175
176static int format_ipv4_address(struct loc_network* network, char* string, size_t length) {
177 struct in_addr ipv4_address;
178 ipv4_address.s_addr = network->start_address.s6_addr32[3];
179
180 const char* ret = inet_ntop(AF_INET, &ipv4_address, string, length);
181 if (!ret)
182 return -1;
183
184 return 0;
185}
186
3b5f4af2 187LOC_EXPORT char* loc_network_str(struct loc_network* network) {
d3409788
MT
188 int r;
189 const size_t length = INET6_ADDRSTRLEN + 4;
3b5f4af2 190
d3409788 191 char* string = malloc(length);
3b5f4af2
MT
192 if (!string)
193 return NULL;
194
d3409788
MT
195 int family = loc_network_address_family(network);
196 switch (family) {
197 case AF_INET6:
198 r = format_ipv6_address(network, string, length);
199 break;
3b5f4af2 200
d3409788
MT
201 case AF_INET:
202 r = format_ipv4_address(network, string, length);
203 break;
204
205 default:
206 r = -1;
207 break;
208 }
209
210 if (r) {
211 ERROR(network->ctx, "Could not convert network to string: %s\n", strerror(errno));
3b5f4af2 212 free(string);
d3409788 213
3b5f4af2
MT
214 return NULL;
215 }
216
217 // Append prefix
218 sprintf(string + strlen(string), "/%u", network->prefix);
219
220 return string;
221}
222
223LOC_EXPORT const char* loc_network_get_country_code(struct loc_network* network) {
224 return network->country_code;
225}
226
227LOC_EXPORT int loc_network_set_country_code(struct loc_network* network, const char* country_code) {
228 // Country codes must be two characters
229 if (strlen(country_code) != 2)
230 return -EINVAL;
231
232 for (unsigned int i = 0; i < 3; i++) {
233 network->country_code[i] = country_code[i];
234 }
235
236 return 0;
237}
238
71ff3e69
MT
239LOC_EXPORT uint32_t loc_network_get_asn(struct loc_network* network) {
240 return network->asn;
241}
242
243LOC_EXPORT int loc_network_set_asn(struct loc_network* network, uint32_t asn) {
244 network->asn = asn;
245
246 return 0;
247}
248
f3e02bc5
MT
249LOC_EXPORT int loc_network_to_database_v0(struct loc_network* network, struct loc_database_network_v0* dbobj) {
250 dbobj->prefix = htobe16(network->prefix);
251
252 // Add country code
253 for (unsigned int i = 0; i < 2; i++) {
254 dbobj->country_code[i] = network->country_code ? network->country_code[i] : '\0';
255 }
256
257 // Add ASN
71ff3e69 258 dbobj->asn = htobe32(network->asn);
f3e02bc5
MT
259
260 return 0;
261}
262
3b5f4af2
MT
263struct loc_network_tree {
264 struct loc_ctx* ctx;
265 int refcount;
266
267 struct loc_network_tree_node* root;
268};
269
270struct loc_network_tree_node {
271 struct loc_network_tree_node* zero;
272 struct loc_network_tree_node* one;
273
274 struct loc_network* network;
275};
276
277LOC_EXPORT int loc_network_tree_new(struct loc_ctx* ctx, struct loc_network_tree** tree) {
278 struct loc_network_tree* t = calloc(1, sizeof(*t));
279 if (!t)
280 return -ENOMEM;
281
282 t->ctx = loc_ref(ctx);
283 t->refcount = 1;
284
285 // Create the root node
286 t->root = calloc(1, sizeof(*t->root));
287
288 DEBUG(t->ctx, "Network tree allocated at %p\n", t);
289 *tree = t;
290 return 0;
291}
292
293static int loc_network_tree_node_new(struct loc_network_tree_node** node) {
294 struct loc_network_tree_node* n = calloc(1, sizeof(*n));
295 if (!n)
296 return -ENOMEM;
297
298 n->zero = n->one = NULL;
299
300 *node = n;
301 return 0;
302}
303
304static struct loc_network_tree_node* loc_network_tree_get_node(struct loc_network_tree_node* node, int path) {
305 struct loc_network_tree_node** n;
306
307 if (path)
308 n = &node->one;
309 else
310 n = &node->zero;
311
312 // If the desired node doesn't exist, yet, we will create it
313 if (*n == NULL) {
314 int r = loc_network_tree_node_new(n);
315 if (r)
316 return NULL;
317 }
318
319 return *n;
320}
321
322static struct loc_network_tree_node* loc_network_tree_get_path(struct loc_network_tree* tree, const struct in6_addr* address) {
323 struct loc_network_tree_node* node = tree->root;
324
325 for (unsigned int i = 127; i > 0; i--) {
326 // Check if the ith bit is one or zero
327 node = loc_network_tree_get_node(node, ((address->s6_addr32[i / 32] & (1 << (i % 32))) == 0));
328 }
329
330 return node;
331}
332
333static int __loc_network_tree_walk(struct loc_ctx* ctx, struct loc_network_tree_node* node,
f3e02bc5
MT
334 int(*filter_callback)(struct loc_network* network, void* data),
335 int(*callback)(struct loc_network* network, void* data), void* data) {
3b5f4af2
MT
336 int r;
337
338 // Finding a network ends the walk here
339 if (node->network) {
340 if (filter_callback) {
f3e02bc5 341 int f = filter_callback(node->network, data);
3b5f4af2
MT
342 if (f < 0)
343 return f;
344
345 // Skip network if filter function returns value greater than zero
346 if (f > 0)
347 return 0;
348 }
349
f3e02bc5 350 r = callback(node->network, data);
3b5f4af2
MT
351 if (r)
352 return r;
353 }
354
355 // Walk down on the left side of the tree first
356 if (node->zero) {
f3e02bc5 357 r = __loc_network_tree_walk(ctx, node->zero, filter_callback, callback, data);
3b5f4af2
MT
358 if (r)
359 return r;
360 }
361
362 // Then walk on the other side
363 if (node->one) {
f3e02bc5 364 r = __loc_network_tree_walk(ctx, node->one, filter_callback, callback, data);
3b5f4af2
MT
365 if (r)
366 return r;
367 }
368
369 return 0;
370}
371
f3e02bc5
MT
372LOC_EXPORT int loc_network_tree_walk(struct loc_network_tree* tree,
373 int(*filter_callback)(struct loc_network* network, void* data),
374 int(*callback)(struct loc_network* network, void* data), void* data) {
375 return __loc_network_tree_walk(tree->ctx, tree->root, filter_callback, callback, data);
376}
377
3b5f4af2
MT
378static void loc_network_tree_free_subtree(struct loc_network_tree_node* node) {
379 if (node->network)
380 loc_network_unref(node->network);
381
382 if (node->zero)
383 loc_network_tree_free_subtree(node->zero);
384
385 if (node->one)
386 loc_network_tree_free_subtree(node->one);
387
388 free(node);
389}
390
391static void loc_network_tree_free(struct loc_network_tree* tree) {
392 DEBUG(tree->ctx, "Releasing network tree at %p\n", tree);
393
394 loc_network_tree_free_subtree(tree->root);
395
396 loc_unref(tree->ctx);
397 free(tree);
398}
399
400LOC_EXPORT struct loc_network_tree* loc_network_tree_unref(struct loc_network_tree* tree) {
401 if (--tree->refcount > 0)
402 return tree;
403
404 loc_network_tree_free(tree);
405 return NULL;
406}
407
f3e02bc5 408int __loc_network_tree_dump(struct loc_network* network, void* data) {
3b5f4af2
MT
409 DEBUG(network->ctx, "Dumping network at %p\n", network);
410
411 char* s = loc_network_str(network);
412 if (!s)
413 return 1;
414
415 INFO(network->ctx, "%s\n", s);
416 free(s);
417
418 return 0;
419}
420
421LOC_EXPORT int loc_network_tree_dump(struct loc_network_tree* tree) {
422 DEBUG(tree->ctx, "Dumping network tree at %p\n", tree);
423
f3e02bc5 424 return loc_network_tree_walk(tree, NULL, __loc_network_tree_dump, NULL);
3b5f4af2
MT
425}
426
427LOC_EXPORT int loc_network_tree_add_network(struct loc_network_tree* tree, struct loc_network* network) {
428 DEBUG(tree->ctx, "Adding network %p to tree %p\n", network, tree);
429
430 struct loc_network_tree_node* node = loc_network_tree_get_path(tree, &network->start_address);
431 if (!node) {
432 ERROR(tree->ctx, "Could not find a node\n");
433 return -ENOMEM;
434 }
435
436 // Check if node has not been set before
437 if (node->network) {
438 DEBUG(tree->ctx, "There is already a network at this path\n");
439 return 1;
440 }
441
442 // Point node to the network
443 node->network = loc_network_ref(network);
444
445 return 0;
446}
f3e02bc5
MT
447
448static int __loc_network_tree_count(struct loc_network* network, void* data) {
449 size_t* counter = (size_t*)data;
450
451 // Increase the counter for each network
452 counter++;
453
454 return 0;
455}
456
457LOC_EXPORT size_t loc_network_tree_count_networks(struct loc_network_tree* tree) {
458 size_t counter = 0;
459
460 int r = loc_network_tree_walk(tree, NULL, __loc_network_tree_count, &counter);
461 if (r)
462 return r;
463
464 return counter;
465}