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