network: Support parsing IPv4 addresses and map them to IPv6
[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 <endian.h>
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <loc/libloc.h>
26 #include <loc/network.h>
27 #include <loc/private.h>
28
29 struct 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];
37         uint32_t asn;
38 };
39
40 LOC_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
81 static 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
88 static 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
119 LOC_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
135         int r = parse_address(ctx, address_string, &start_address);
136
137         // Free temporary buffer
138         free(buffer);
139
140         if (r == 0) {
141                 r = loc_network_new(ctx, network, start_address, prefix);
142         }
143
144         return r;
145 }
146
147 LOC_EXPORT struct loc_network* loc_network_ref(struct loc_network* network) {
148         network->refcount++;
149
150         return network;
151 }
152
153 static void loc_network_free(struct loc_network* network) {
154         DEBUG(network->ctx, "Releasing network at %p\n", network);
155
156         loc_unref(network->ctx);
157         free(network);
158 }
159
160 LOC_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
168 static 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
176 static 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
187 LOC_EXPORT char* loc_network_str(struct loc_network* network) {
188         int r;
189         const size_t length = INET6_ADDRSTRLEN + 4;
190
191         char* string = malloc(length);
192         if (!string)
193                 return NULL;
194
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;
200
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));
212                 free(string);
213
214                 return NULL;
215         }
216
217         // Append prefix
218         sprintf(string + strlen(string), "/%u", network->prefix);
219
220         return string;
221 }
222
223 LOC_EXPORT const char* loc_network_get_country_code(struct loc_network* network) {
224         return network->country_code;
225 }
226
227 LOC_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
239 LOC_EXPORT uint32_t loc_network_get_asn(struct loc_network* network) {
240         return network->asn;
241 }
242
243 LOC_EXPORT int loc_network_set_asn(struct loc_network* network, uint32_t asn) {
244         network->asn = asn;
245
246         return 0;
247 }
248
249 LOC_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
258         dbobj->asn = htobe32(network->asn);
259
260         return 0;
261 }
262
263 struct loc_network_tree {
264         struct loc_ctx* ctx;
265         int refcount;
266
267         struct loc_network_tree_node* root;
268 };
269
270 struct 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
277 LOC_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
293 static 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
304 static 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
322 static 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
333 static int __loc_network_tree_walk(struct loc_ctx* ctx, struct loc_network_tree_node* node,
334                 int(*filter_callback)(struct loc_network* network, void* data),
335                 int(*callback)(struct loc_network* network, void* data), void* data) {
336         int r;
337
338         // Finding a network ends the walk here
339         if (node->network) {
340                 if (filter_callback) {
341                         int f = filter_callback(node->network, data);
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
350                 r = callback(node->network, data);
351                 if (r)
352                         return r;
353         }
354
355         // Walk down on the left side of the tree first
356         if (node->zero) {
357                 r = __loc_network_tree_walk(ctx, node->zero, filter_callback, callback, data);
358                 if (r)
359                         return r;
360         }
361
362         // Then walk on the other side
363         if (node->one) {
364                 r = __loc_network_tree_walk(ctx, node->one, filter_callback, callback, data);
365                 if (r)
366                         return r;
367         }
368
369         return 0;
370 }
371
372 LOC_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
378 static 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
391 static 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
400 LOC_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
408 int __loc_network_tree_dump(struct loc_network* network, void* data) {
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
421 LOC_EXPORT int loc_network_tree_dump(struct loc_network_tree* tree) {
422         DEBUG(tree->ctx, "Dumping network tree at %p\n", tree);
423
424         return loc_network_tree_walk(tree, NULL, __loc_network_tree_dump, NULL);
425 }
426
427 LOC_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 }
447
448 static 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
457 LOC_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 }