/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #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; }