1 /* AirScan (a.k.a. eSCL) backend for SANE
2 *
3 * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
4 * See LICENSE for license terms and conditions
5 *
6 * Utility function for IP addresses
7 */
8
9 #include "airscan.h"
10
11 #include <string.h>
12 #include <stdint.h>
13 #include <stdlib.h>
14 #include <arpa/inet.h>
15 #include <sys/un.h>
16
17 #if defined(OS_HAVE_ENDIAN_H)
18 # include <endian.h>
19 #elif defined(OS_HAVE_SYS_ENDIAN_H)
20 # include <sys/endian.h>
21 #endif
22
23 /* Format ip_straddr from IP address (struct in_addr or struct in6_addr)
24 * af must be AF_INET or AF_INET6
25 */
26 ip_straddr
ip_straddr_from_ip(int af,const void * addr)27 ip_straddr_from_ip (int af, const void *addr)
28 {
29 ip_straddr straddr = {""};
30 inet_ntop(af, addr, straddr.text, sizeof(straddr.text));
31 return straddr;
32 }
33
34 /* Format ip_straddr from struct sockaddr.
35 * AF_INET, AF_INET6, and AF_UNIX are supported
36 *
37 * If `withzone' is true, zone suffix will be appended, when appropriate
38 */
39 ip_straddr
ip_straddr_from_sockaddr(const struct sockaddr * addr,bool withzone)40 ip_straddr_from_sockaddr (const struct sockaddr *addr, bool withzone)
41 {
42 return ip_straddr_from_sockaddr_dport(addr, -1, withzone);
43 }
44
45 /* Format ip_straddr from struct sockaddr.
46 * AF_INET, AF_INET6, and AF_UNIX are supported
47 *
48 * Port will not be appended, if it matches provided default port
49 * If `withzone' is true, zone suffix will be appended, when appropriate
50 */
51 ip_straddr
ip_straddr_from_sockaddr_dport(const struct sockaddr * addr,int dport,bool withzone)52 ip_straddr_from_sockaddr_dport (const struct sockaddr *addr,
53 int dport, bool withzone)
54 {
55 ip_straddr straddr = {""};
56 struct sockaddr_in *addr_in = (struct sockaddr_in*) addr;
57 struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6*) addr;
58 struct sockaddr_un *addr_un = (struct sockaddr_un*) addr;
59 uint16_t port = 0;
60
61 switch (addr->sa_family) {
62 case AF_INET:
63 inet_ntop(AF_INET, &addr_in->sin_addr,
64 straddr.text, sizeof(straddr.text));
65 port = addr_in->sin_port;
66 break;
67 case AF_INET6:
68 straddr.text[0] = '[';
69 inet_ntop(AF_INET6, &addr_in6->sin6_addr,
70 straddr.text + 1, sizeof(straddr.text) - 2);
71 if (withzone && addr_in6->sin6_scope_id != 0) {
72 sprintf(straddr.text + strlen(straddr.text), "%%%d",
73 addr_in6->sin6_scope_id);
74 }
75 strcat(straddr.text, "]");
76 port = addr_in6->sin6_port;
77 break;
78 case AF_UNIX:
79 strncpy(straddr.text, addr_un->sun_path, sizeof(straddr.text) - 1);
80 straddr.text[sizeof(straddr.text)-1] = '\0';
81 break;
82 }
83
84 port = htons(port);
85 if (port != dport && addr->sa_family != AF_UNIX) {
86 sprintf(straddr.text + strlen(straddr.text), ":%d", port);
87 }
88
89 return straddr;
90 }
91
92 /* Check if address is link-local
93 * af must be AF_INET or AF_INET6
94 */
95 bool
ip_is_linklocal(int af,const void * addr)96 ip_is_linklocal (int af, const void *addr)
97 {
98 if (af == AF_INET) {
99 /* 169.254.0.0/16 */
100 const uint32_t *a = addr;
101 return (ntohl(*a) & 0xffff0000) == 0xa9fe0000;
102 } else {
103 const uint8_t *a = addr;
104 return a[0] == 0xfe && (a[1] & 0xc0) == 0x80;
105 }
106 }
107
108 /* Check if sockaddr is link-local
109 */
110 bool
ip_sockaddr_is_linklocal(const struct sockaddr * addr)111 ip_sockaddr_is_linklocal (const struct sockaddr *addr)
112 {
113 struct sockaddr_in *addr_in = (struct sockaddr_in*) addr;
114 struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6*) addr;
115
116 switch (addr->sa_family) {
117 case AF_INET:
118 return ip_is_linklocal(AF_INET, &addr_in->sin_addr);
119
120 case AF_INET6:
121 return ip_is_linklocal(AF_INET6, &addr_in6->sin6_addr);
122 }
123
124 return false;
125 }
126
127 /* Check if address is loopback
128 * af must be AF_INET or AF_INET6
129 */
130 bool
ip_is_loopback(int af,const void * addr)131 ip_is_loopback (int af, const void *addr)
132 {
133 if (af == AF_INET) {
134 /* 169.254.0.0/16 */
135 const uint32_t *a = addr;
136 return ntohl(*a) == 0x7f000001;
137 } else {
138 static const uint8_t loopback[16] = {
139 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
140 };
141
142 return !memcmp(addr, loopback, 16);
143 }
144 }
145
146 /* Format ip_addr into ip_straddr
147 */
148 ip_straddr
ip_addr_to_straddr(ip_addr addr,bool withzone)149 ip_addr_to_straddr (ip_addr addr, bool withzone)
150 {
151 ip_straddr straddr = {""};
152 struct sockaddr_in addr_in;
153 struct sockaddr_in6 addr_in6;
154 struct sockaddr *sockaddr = NULL;
155
156 switch (addr.af) {
157 case AF_INET:
158 memset(&addr_in, 0, sizeof(addr_in));
159 addr_in.sin_family = AF_INET;
160 addr_in.sin_addr = addr.ip.v4;
161 sockaddr = (struct sockaddr*) &addr_in;
162 break;
163
164 case AF_INET6:
165 memset(&addr_in6, 0, sizeof(addr_in6));
166 addr_in6.sin6_family = AF_INET6;
167 addr_in6.sin6_addr = addr.ip.v6;
168 addr_in6.sin6_scope_id = addr.ifindex;
169 sockaddr = (struct sockaddr*) &addr_in6;
170 break;
171 }
172
173 if (sockaddr != NULL) {
174 straddr = ip_straddr_from_sockaddr_dport(sockaddr, 0, withzone);
175 }
176
177 return straddr;
178 }
179
180 /* Format ip_network into ip_straddr
181 */
182 ip_straddr
ip_network_to_straddr(ip_network net)183 ip_network_to_straddr (ip_network net)
184 {
185 ip_straddr straddr = {""};
186 size_t len;
187
188 inet_ntop(net.addr.af, &net.addr.ip, straddr.text, sizeof(straddr.text));
189 len = strlen(straddr.text);
190 sprintf(straddr.text + len, "/%d", net.mask);
191
192 return straddr;
193 }
194
195 /* Check if ip_network contains ip_addr
196 */
197 bool
ip_network_contains(ip_network net,ip_addr addr)198 ip_network_contains (ip_network net, ip_addr addr)
199 {
200 struct in_addr a4, m4;
201 uint64_t a6[2], m6[2];
202
203 if (net.addr.af != addr.af) {
204 return false;
205 }
206
207 switch (net.addr.af) {
208 case AF_INET:
209 a4.s_addr = net.addr.ip.v4.s_addr ^ addr.ip.v4.s_addr;
210 m4.s_addr = htonl(0xffffffff << (32 - net.mask));
211 return (a4.s_addr & m4.s_addr) == 0;
212
213 case AF_INET6:
214 /* a6 = net.addr.ip.v6 ^ addr.ip.v6 */
215 memcpy(a6, &addr.ip.v6, 16);
216 memcpy(m6, &net.addr.ip.v6, 16);
217 a6[0] ^= m6[0];
218 a6[1] ^= m6[1];
219
220 /* Compute and apply netmask */
221 memset(m6, 0, 16);
222 if (net.mask <= 64) {
223 m6[0] = htobe64(UINT64_MAX << (64 - net.mask));
224 m6[1] = 0;
225 } else {
226 m6[0] = UINT64_MAX;
227 m6[1] = htobe64(UINT64_MAX << (128 - net.mask));
228 }
229
230 a6[0] &= m6[0];
231 a6[1] &= m6[1];
232
233 /* Check result */
234 return (a6[0] | a6[1]) == 0;
235 }
236
237 return false;
238 }
239
240 /* ip_addr_set represents a set of IP addresses
241 */
242 struct ip_addrset {
243 ip_addr *addrs; /* Addresses in the set */
244 };
245
246 /* Create new ip_addrset
247 */
248 ip_addrset*
ip_addrset_new(void)249 ip_addrset_new (void)
250 {
251 ip_addrset *addrset = mem_new(ip_addrset, 1);
252 addrset->addrs = mem_new(ip_addr, 0);
253 return addrset;
254 }
255
256 /* Free ip_addrset
257 */
258 void
ip_addrset_free(ip_addrset * addrset)259 ip_addrset_free (ip_addrset *addrset)
260 {
261 mem_free(addrset->addrs);
262 mem_free(addrset);
263 }
264
265 /* Find address index within a set. Returns -1 if address was not found
266 */
267 static int
ip_addrset_index(const ip_addrset * addrset,ip_addr addr)268 ip_addrset_index (const ip_addrset *addrset, ip_addr addr)
269 {
270 size_t i, len = mem_len(addrset->addrs);
271
272 for (i = 0; i < len; i ++) {
273 if (ip_addr_equal(addrset->addrs[i], addr)) {
274 return (int) i;
275 }
276 }
277
278 return -1;
279 }
280
281 /* Check if address is in set
282 */
283 bool
ip_addrset_lookup(const ip_addrset * addrset,ip_addr addr)284 ip_addrset_lookup (const ip_addrset *addrset, ip_addr addr)
285 {
286 return ip_addrset_index(addrset, addr) >= 0;
287 }
288
289 /* Add address to the set. Returns true, if address was
290 * actually added, false if it was already in the set
291 */
292 bool
ip_addrset_add(ip_addrset * addrset,ip_addr addr)293 ip_addrset_add (ip_addrset *addrset, ip_addr addr)
294 {
295 if (ip_addrset_lookup(addrset, addr)) {
296 return false;
297 }
298
299 ip_addrset_add_unsafe(addrset, addr);
300 return true;
301 }
302
303 /* Add address to the set without checking for duplicates
304 */
305 void
ip_addrset_add_unsafe(ip_addrset * addrset,ip_addr addr)306 ip_addrset_add_unsafe (ip_addrset *addrset, ip_addr addr)
307 {
308 size_t len = mem_len(addrset->addrs);
309
310 addrset->addrs = mem_resize(addrset->addrs, len + 1, 0);
311 addrset->addrs[len] = addr;
312 }
313
314 /* Del address from the set.
315 */
316 void
ip_addrset_del(ip_addrset * addrset,ip_addr addr)317 ip_addrset_del (ip_addrset *addrset, ip_addr addr)
318 {
319 int i = ip_addrset_index(addrset, addr);
320
321 if (i >= 0) {
322 size_t len = mem_len(addrset->addrs);
323 size_t tail = len - (size_t) i - 1;
324 if (tail != 0) {
325 tail *= sizeof(*addrset->addrs);
326 memmove(&addrset->addrs[i], &addrset->addrs[i + 1], tail);
327 }
328 mem_shrink(addrset->addrs, len - 1);
329 }
330 }
331
332 /* Delete all addresses from the set
333 */
334 void
ip_addrset_purge(ip_addrset * addrset)335 ip_addrset_purge (ip_addrset *addrset)
336 {
337 mem_shrink(addrset->addrs, 0);
338 }
339
340 /* Merge two sets:
341 * addrset += addrset2
342 */
343 void
ip_addrset_merge(ip_addrset * addrset,const ip_addrset * addrset2)344 ip_addrset_merge (ip_addrset *addrset, const ip_addrset *addrset2)
345 {
346 size_t i, len = mem_len(addrset2->addrs);
347
348 for (i = 0; i < len; i ++) {
349 ip_addrset_add(addrset, addrset2->addrs[i]);
350 }
351 }
352
353 /* Get access to array of addresses in the set
354 */
355 const ip_addr*
ip_addrset_addresses(const ip_addrset * addrset,size_t * count)356 ip_addrset_addresses (const ip_addrset *addrset, size_t *count)
357 {
358 *count = mem_len(addrset->addrs);
359 return addrset->addrs;
360 }
361
362 /* Check if two address sets are intersecting
363 */
364 bool
ip_addrset_is_intersect(const ip_addrset * set,const ip_addrset * set2)365 ip_addrset_is_intersect (const ip_addrset *set, const ip_addrset *set2)
366 {
367 size_t i, len = mem_len(set->addrs);
368
369 for (i = 0; i < len; i ++) {
370 if (ip_addrset_lookup(set2, set->addrs[i])) {
371 return true;
372 }
373 }
374
375 return false;
376 }
377
378 /* Check if some of addresses in the address set is on the
379 * given network
380 */
381 bool
ip_addrset_on_network(const ip_addrset * set,ip_network net)382 ip_addrset_on_network (const ip_addrset *set, ip_network net)
383 {
384 size_t i, len = mem_len(set->addrs);
385
386 for (i = 0; i < len; i ++) {
387 if (ip_network_contains(net, set->addrs[i])) {
388 return true;
389 }
390 }
391
392 return false;
393 }
394
395 /* Compare two ip_addrs, for sorting in ip_addrset_friendly_str()
396 */
397 static int
ip_addrset_friendly_sort_cmp(const void * p1,const void * p2)398 ip_addrset_friendly_sort_cmp (const void *p1, const void *p2)
399 {
400 const ip_addr *a1 = (const ip_addr*) p1;
401 const ip_addr *a2 = (const ip_addr*) p2;
402 bool ll1 = ip_is_linklocal(a1->af, &a1->ip);
403 bool ll2 = ip_is_linklocal(a2->af, &a2->ip);
404 ip_straddr s1, s2;
405
406 /* Prefer normal addresses, rather that link-local */
407 if (ll1 != ll2) {
408 return ll1 ? 1 : -1;
409 }
410
411 /* Put IP4 addresses first, they tell more to humans */
412 if (a1->af != a2->af) {
413 return a1->af == AF_INET6 ? 1 : -1;
414 }
415
416 /* Otherwise, sort lexicographically */
417 s1 = ip_addr_to_straddr(*a1, true);
418 s2 = ip_addr_to_straddr(*a2, true);
419
420 return strcmp(s1.text, s2.text);
421 }
422
423 /* Create user-friendly string out of set of addresses, containing
424 * in the ip_addrset:
425 * * addresses are sorted, IP4 addresses goes first
426 * * link-local addresses are skipped, if there are non-link-local ones
427 */
428 char*
ip_addrset_friendly_str(const ip_addrset * set,char * s)429 ip_addrset_friendly_str (const ip_addrset *set, char *s)
430 {
431 size_t i, j, len = mem_len(set->addrs);
432 ip_addr *addrs = alloca(sizeof(ip_addr) * len);
433
434 /* Gather addresses */
435 for (i = j = 0; i < len; i ++) {
436 ip_addr *addr = &set->addrs[i];
437 if (!ip_is_linklocal(addr->af, &addr->ip)) {
438 addrs[j ++] = *addr;
439 }
440 }
441
442 if (j != 0) {
443 len = j;
444 } else {
445 memcpy(addrs, set->addrs, sizeof(ip_addr) * len);
446 }
447
448 /* Sort addresses */
449 qsort(addrs, len, sizeof(ip_addr), ip_addrset_friendly_sort_cmp);
450
451 /* And now stringify */
452 for (i = 0; i < len; i ++) {
453 ip_straddr str = ip_addr_to_straddr(addrs[i], true);
454
455 if (i != 0) {
456 s = str_append(s, ", ");
457 }
458
459 if (str.text[0] != '[') {
460 s = str_append(s, str.text);
461 } else {
462 str.text[strlen(str.text) - 1] = '\0';
463 s = str_append(s, str.text + 1);
464 }
465 }
466
467 return s;
468 }
469
470 /* vim:ts=8:sw=4:et
471 */
472