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