diff options
Diffstat (limited to '')
-rw-r--r-- | src/basic/ether-addr-util.c | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c new file mode 100644 index 0000000..0a6a54f --- /dev/null +++ b/src/basic/ether-addr-util.c @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <inttypes.h> +#include <net/ethernet.h> +#include <stdio.h> +#include <sys/types.h> + +#include "ether-addr-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "string-util.h" + +char *hw_addr_to_string_full( + const struct hw_addr_data *addr, + HardwareAddressToStringFlags flags, + char buffer[static HW_ADDR_TO_STRING_MAX]) { + + assert(addr); + assert(buffer); + assert(addr->length <= HW_ADDR_MAX_SIZE); + + for (size_t i = 0, j = 0; i < addr->length; i++) { + buffer[j++] = hexchar(addr->bytes[i] >> 4); + buffer[j++] = hexchar(addr->bytes[i] & 0x0f); + if (!FLAGS_SET(flags, HW_ADDR_TO_STRING_NO_COLON)) + buffer[j++] = ':'; + } + + buffer[addr->length == 0 || FLAGS_SET(flags, HW_ADDR_TO_STRING_NO_COLON) ? + addr->length * 2 : + addr->length * 3 - 1] = '\0'; + return buffer; +} + +struct hw_addr_data *hw_addr_set(struct hw_addr_data *addr, const uint8_t *bytes, size_t length) { + assert(addr); + assert(length <= HW_ADDR_MAX_SIZE); + + addr->length = length; + memcpy_safe(addr->bytes, bytes, length); + return addr; +} + +int hw_addr_compare(const struct hw_addr_data *a, const struct hw_addr_data *b) { + int r; + + assert(a); + assert(b); + + r = CMP(a->length, b->length); + if (r != 0) + return r; + + return memcmp(a->bytes, b->bytes, a->length); +} + +void hw_addr_hash_func(const struct hw_addr_data *p, struct siphash *state) { + assert(p); + assert(state); + + siphash24_compress(&p->length, sizeof(p->length), state); + siphash24_compress(p->bytes, p->length, state); +} + +DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(hw_addr_hash_ops_free, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare, free); + +char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { + assert(addr); + assert(buffer); + + /* Like ether_ntoa() but uses %02x instead of %x to print + * ethernet addresses, which makes them look less funny. Also, + * doesn't use a static buffer. */ + + sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", + addr->ether_addr_octet[0], + addr->ether_addr_octet[1], + addr->ether_addr_octet[2], + addr->ether_addr_octet[3], + addr->ether_addr_octet[4], + addr->ether_addr_octet[5]); + + return buffer; +} + +int ether_addr_to_string_alloc(const struct ether_addr *addr, char **ret) { + char *buf; + + assert(addr); + assert(ret); + + buf = new(char, ETHER_ADDR_TO_STRING_MAX); + if (!buf) + return -ENOMEM; + + ether_addr_to_string(addr, buf); + + *ret = buf; + return 0; +} + +int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) { + return memcmp(a, b, ETH_ALEN); +} + +static void ether_addr_hash_func(const struct ether_addr *p, struct siphash *state) { + siphash24_compress(p, sizeof(struct ether_addr), state); +} + +DEFINE_HASH_OPS(ether_addr_hash_ops, struct ether_addr, ether_addr_hash_func, ether_addr_compare); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(ether_addr_hash_ops_free, struct ether_addr, ether_addr_hash_func, ether_addr_compare, free); + +static int parse_hw_addr_one_field(const char **s, char sep, size_t len, uint8_t *buf) { + const char *hex = HEXDIGITS, *p; + uint16_t data = 0; + bool cont; + + assert(s); + assert(*s); + assert(IN_SET(len, 1, 2)); + assert(buf); + + p = *s; + + for (size_t i = 0; i < len * 2; i++) { + const char *hexoff; + size_t x; + + if (*p == '\0' || *p == sep) { + if (i == 0) + return -EINVAL; + break; + } + + hexoff = strchr(hex, *p); + if (!hexoff) + return -EINVAL; + + assert(hexoff >= hex); + x = hexoff - hex; + if (x >= 16) + x -= 6; /* A-F */ + + assert(x < 16); + data <<= 4; + data += x; + + p++; + } + + if (*p != '\0' && *p != sep) + return -EINVAL; + + switch (len) { + case 1: + buf[0] = data; + break; + case 2: + buf[0] = (data & 0xff00) >> 8; + buf[1] = data & 0xff; + break; + default: + assert_not_reached(); + } + + cont = *p == sep; + *s = p + cont; + return cont; +} + +int parse_hw_addr_full(const char *s, size_t expected_len, struct hw_addr_data *ret) { + size_t field_size, max_len, len = 0; + uint8_t bytes[HW_ADDR_MAX_SIZE]; + char sep; + int r; + + assert(s); + assert(expected_len <= HW_ADDR_MAX_SIZE || expected_len == SIZE_MAX); + assert(ret); + + /* This accepts the following formats: + * + * Dot separated 2 bytes format: xxyy.zzaa.bbcc + * Colon separated 1 bytes format: xx:yy:zz:aa:bb:cc + * Hyphen separated 1 bytes format: xx-yy-zz-aa-bb-cc + * + * Moreover, if expected_len == 0, 4, or 16, this also accepts: + * + * IPv4 format: used by IPv4 tunnel, e.g. ipgre + * IPv6 format: used by IPv6 tunnel, e.g. ip6gre + * + * The expected_len argument controls the length of acceptable addresses: + * + * 0: accepts 4 (AF_INET), 16 (AF_INET6), 6 (ETH_ALEN), or 20 (INFINIBAND_ALEN). + * SIZE_MAX: accepts arbitrary length, but at least one separator must be included. + * Otherwise: accepts addresses with matching length. + */ + + if (IN_SET(expected_len, 0, sizeof(struct in_addr), sizeof(struct in6_addr))) { + union in_addr_union a; + int family; + + if (expected_len == 0) + r = in_addr_from_string_auto(s, &family, &a); + else { + family = expected_len == sizeof(struct in_addr) ? AF_INET : AF_INET6; + r = in_addr_from_string(family, s, &a); + } + if (r >= 0) { + ret->length = FAMILY_ADDRESS_SIZE(family); + memcpy(ret->bytes, a.bytes, ret->length); + return 0; + } + } + + max_len = + expected_len == 0 ? INFINIBAND_ALEN : + expected_len == SIZE_MAX ? HW_ADDR_MAX_SIZE : expected_len; + sep = s[strspn(s, HEXDIGITS)]; + + if (sep == '.') + field_size = 2; + else if (IN_SET(sep, ':', '-')) + field_size = 1; + else + return -EINVAL; + + if (max_len % field_size != 0) + return -EINVAL; + + for (size_t i = 0; i < max_len / field_size; i++) { + r = parse_hw_addr_one_field(&s, sep, field_size, bytes + i * field_size); + if (r < 0) + return r; + if (r == 0) { + len = (i + 1) * field_size; + break; + } + } + + if (len == 0) + return -EINVAL; + + if (expected_len == 0) { + if (!IN_SET(len, 4, 16, ETH_ALEN, INFINIBAND_ALEN)) + return -EINVAL; + } else if (expected_len != SIZE_MAX) { + if (len != expected_len) + return -EINVAL; + } + + ret->length = len; + memcpy(ret->bytes, bytes, ret->length); + return 0; +} + +int parse_ether_addr(const char *s, struct ether_addr *ret) { + struct hw_addr_data a; + int r; + + assert(s); + assert(ret); + + r = parse_hw_addr_full(s, ETH_ALEN, &a); + if (r < 0) + return r; + + *ret = a.ether; + return 0; +} |