/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" #include "extract-word.h" #include "hostname-util.h" #include "in-addr-prefix-util.h" #include "string-util.h" /* 0.0.0.0/0 */ #define IN_ADDR_PREFIX_IPV4_ANY ((struct in_addr_prefix) { .family = AF_INET }) /* ::/0 */ #define IN_ADDR_PREFIX_IPV6_ANY ((struct in_addr_prefix) { .family = AF_INET6 }) /* 127.0.0.0/8 */ #define IN_ADDR_PREFIX_IPV4_LOCALHOST \ ((struct in_addr_prefix) { \ .family = AF_INET, \ .address.in.s_addr = htobe32(UINT32_C(127) << 24), \ .prefixlen = 8, \ }) /* ::1/128 */ #define IN_ADDR_PREFIX_IPV6_LOCALHOST \ ((struct in_addr_prefix) { \ .family = AF_INET6, \ .address.in6 = IN6ADDR_LOOPBACK_INIT, \ .prefixlen = 128, \ }) /* 169.254.0.0/16 */ #define IN_ADDR_PREFIX_IPV4_LINKLOCAL \ ((struct in_addr_prefix) { \ .family = AF_INET, \ .address.in.s_addr = htobe32((UINT32_C(169) << 24) | \ (UINT32_C(254) << 16)), \ .prefixlen = 16, \ }) /* fe80::/64 */ #define IN_ADDR_PREFIX_IPV6_LINKLOCAL \ ((struct in_addr_prefix) { \ .family = AF_INET6, \ .address.in6.s6_addr[0] = 0xfe, \ .address.in6.s6_addr[1] = 0x80, \ .prefixlen = 64, \ }) /* 224.0.0.0/4 */ #define IN_ADDR_PREFIX_IPV4_MULTICAST \ ((struct in_addr_prefix) { \ .family = AF_INET, \ .address.in.s_addr = htobe32((UINT32_C(224) << 24)), \ .prefixlen = 4, \ }) /* ff00::/8 */ #define IN_ADDR_PREFIX_IPV6_MULTICAST \ ((struct in_addr_prefix) { \ .family = AF_INET6, \ .address.in6.s6_addr[0] = 0xff, \ .prefixlen = 8, \ }) static void in_addr_prefix_hash_func(const struct in_addr_prefix *a, struct siphash *state) { assert(a); assert(state); siphash24_compress(&a->family, sizeof(a->family), state); siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); } static int in_addr_prefix_compare_func(const struct in_addr_prefix *x, const struct in_addr_prefix *y) { int r; assert(x); assert(y); r = CMP(x->family, y->family); if (r != 0) return r; r = CMP(x->prefixlen, y->prefixlen); if (r != 0) return r; return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); } DEFINE_HASH_OPS(in_addr_prefix_hash_ops, struct in_addr_prefix, in_addr_prefix_hash_func, in_addr_prefix_compare_func); DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(in_addr_prefix_hash_ops_free, struct in_addr_prefix, in_addr_prefix_hash_func, in_addr_prefix_compare_func, free); int in_addr_prefix_add(Set **prefixes, const struct in_addr_prefix *prefix) { struct in_addr_prefix *copy; assert(prefixes); assert(prefix); assert(IN_SET(prefix->family, AF_INET, AF_INET6)); copy = newdup(struct in_addr_prefix, prefix, 1); if (!copy) return -ENOMEM; (void) in_addr_mask(copy->family, ©->address, copy->prefixlen); return set_ensure_consume(prefixes, &in_addr_prefix_hash_ops_free, copy); } int in_addr_prefixes_reduce(Set *prefixes) { uint32_t ipv4_prefixlen_bits = 0; uint64_t ipv6_prefixlen_bits[128 / sizeof(uint64_t)] = {}; uint8_t ipv4_prefixlens[32] = {}, ipv6_prefixlens[128] = {}; bool ipv4_has_any = false, ipv6_has_any = false; size_t ipv4_n_prefixlens = 0, ipv6_n_prefixlens = 0; struct in_addr_prefix *p; SET_FOREACH(p, prefixes) switch (p->family) { case AF_INET: assert(p->prefixlen <= 32); if (p->prefixlen == 0) ipv4_has_any = true; else ipv4_prefixlen_bits |= UINT32_C(1) << (p->prefixlen - 1); break; case AF_INET6: assert(p->prefixlen <= 128); if (p->prefixlen == 0) ipv6_has_any = true; else ipv6_prefixlen_bits[(p->prefixlen - 1) / sizeof(uint64_t)] |= UINT64_C(1) << ((p->prefixlen - 1) % sizeof(uint64_t)); break; default: assert_not_reached(); } if (!ipv4_has_any) for (size_t i = 0; i < 32; i++) if (ipv4_prefixlen_bits & (UINT32_C(1) << i)) ipv4_prefixlens[ipv4_n_prefixlens++] = i + 1; if (!ipv6_has_any) for (size_t i = 0; i < 128; i++) if (ipv6_prefixlen_bits[i / sizeof(uint64_t)] & (UINT64_C(1) << (i % sizeof(uint64_t)))) ipv6_prefixlens[ipv6_n_prefixlens++] = i + 1; SET_FOREACH(p, prefixes) { uint8_t *prefixlens; bool covered; size_t *n; if (p->prefixlen == 0) continue; switch (p->family) { case AF_INET: prefixlens = ipv4_prefixlens; n = &ipv4_n_prefixlens; covered = ipv4_has_any; break; case AF_INET6: prefixlens = ipv6_prefixlens; n = &ipv6_n_prefixlens; covered = ipv6_has_any; break; default: assert_not_reached(); } for (size_t i = 0; i < *n; i++) { struct in_addr_prefix tmp; if (covered) break; if (prefixlens[i] >= p->prefixlen) break; tmp = *p; tmp.prefixlen = prefixlens[i]; (void) in_addr_mask(tmp.family, &tmp.address, tmp.prefixlen); covered = set_contains(prefixes, &tmp); } if (covered) free(set_remove(prefixes, p)); } return 0; } int in_addr_prefixes_merge(Set **dest, Set *src) { struct in_addr_prefix *p; int r; assert(dest); SET_FOREACH(p, src) { r = in_addr_prefix_add(dest, p); if (r < 0) return r; } return 0; } bool in_addr_prefixes_is_any(Set *prefixes) { return set_contains(prefixes, &IN_ADDR_PREFIX_IPV4_ANY) && set_contains(prefixes, &IN_ADDR_PREFIX_IPV6_ANY); } int config_parse_in_addr_prefixes( const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { Set **prefixes = data; int r; assert(prefixes); assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); if (isempty(rvalue)) { *prefixes = set_free(*prefixes); return 0; } for (const char *p = rvalue;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, NULL, 0); if (r == -ENOMEM) return log_oom(); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); return 0; } if (r == 0) return 0; if (streq(word, "any")) { /* "any" is a shortcut for 0.0.0.0/0 and ::/0 */ if (ltype != AF_INET6) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV4_ANY); if (r < 0) return log_oom(); } if (ltype != AF_INET) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV6_ANY); if (r < 0) return log_oom(); } } else if (is_localhost(word)) { /* "localhost" is a shortcut for 127.0.0.0/8 and ::1/128 */ if (ltype != AF_INET6) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV4_LOCALHOST); if (r < 0) return log_oom(); } if (ltype != AF_INET) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV6_LOCALHOST); if (r < 0) return log_oom(); } } else if (streq(word, "link-local")) { /* "link-local" is a shortcut for 169.254.0.0/16 and fe80::/64 */ if (ltype != AF_INET6) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV4_LINKLOCAL); if (r < 0) return log_oom(); } if (ltype != AF_INET) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV6_LINKLOCAL); if (r < 0) return log_oom(); } } else if (streq(word, "multicast")) { /* "multicast" is a shortcut for 224.0.0.0/4 and ff00::/8 */ if (ltype != AF_INET6) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV4_MULTICAST); if (r < 0) return log_oom(); } if (ltype != AF_INET) { r = in_addr_prefix_add(prefixes, &IN_ADDR_PREFIX_IPV6_MULTICAST); if (r < 0) return log_oom(); } } else { struct in_addr_prefix a; if (ltype == AF_UNSPEC) r = in_addr_prefix_from_string_auto(word, &a.family, &a.address, &a.prefixlen); else { a.family = ltype; r = in_addr_prefix_from_string(word, a.family, &a.address, &a.prefixlen); } if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Address prefix is invalid, ignoring assignment: %s", word); continue; } r = in_addr_prefix_add(prefixes, &a); if (r < 0) return log_oom(); } } }