mirror of
https://github.com/openharmony/third_party_sane-airscan.git
synced 2026-06-30 21:17:55 -04:00
507 lines
12 KiB
C
507 lines
12 KiB
C
/* AirScan (a.k.a. eSCL) backend for SANE
|
|
*
|
|
* Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
|
|
* 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
|
|
*/
|