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