| /* 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 = ASSERT_PTR(data); |
| int r; |
| |
| 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(); |
| } |
| } |
| } |