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