Files

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
*/