From: Susant Sahani Date: Mon, 28 Aug 2023 18:12:39 +0000 (+0530) Subject: network: ndisc - Allow to parse PREF64 prefix X-Git-Tag: v255-rc1~592 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6e8f5e4c1f5fcdb3b63343268936f9c269b6a209;p=thirdparty%2Fsystemd.git network: ndisc - Allow to parse PREF64 prefix --- diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 5ece7b747cd..c52b80f2852 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -3122,6 +3122,17 @@ Token=prefixstable:2002:da8:1:: + + UsePREF64= + + When true, the IPv6 PREF64 (or NAT64) prefixes received in the Router Advertisement will be recorded + and made available to client programs and displayed in the networkctl status output per-link. + See RFC 8781. Defaults to false. + + + + + UseAutonomousPrefix= diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 0b35eeec348..80762a72c07 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -13,6 +13,7 @@ sources = files( 'icmp6-util.c', 'lldp-neighbor.c', 'lldp-network.c', + 'ndisc-protocol.c', 'ndisc-router.c', 'network-common.c', 'network-internal.c', diff --git a/src/libsystemd-network/ndisc-protocol.c b/src/libsystemd-network/ndisc-protocol.c new file mode 100644 index 00000000000..fae4a583ad8 --- /dev/null +++ b/src/libsystemd-network/ndisc-protocol.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ndisc-protocol.h" + +static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = { + [PREFIX_LENGTH_CODE_96] = 96, + [PREFIX_LENGTH_CODE_64] = 64, + [PREFIX_LENGTH_CODE_56] = 56, + [PREFIX_LENGTH_CODE_48] = 48, + [PREFIX_LENGTH_CODE_40] = 40, + [PREFIX_LENGTH_CODE_32] = 32, +}; + +int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret) { + plc &= PREF64_PLC_MASK; + if (plc >= _PREFIX_LENGTH_CODE_MAX) + return -EINVAL; + + if (ret) + *ret = prefix_length_code_to_prefix_length[plc]; + return 0; +} + +int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) { + assert(ret); + + for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++) + if (prefix_length_code_to_prefix_length[i] == prefixlen) { + *ret = i; + return 0; + } + + return -EINVAL; +} diff --git a/src/libsystemd-network/ndisc-protocol.h b/src/libsystemd-network/ndisc-protocol.h new file mode 100644 index 00000000000..8e403e34255 --- /dev/null +++ b/src/libsystemd-network/ndisc-protocol.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "time-util.h" + +/* RFC 8781: PREF64 or (NAT64 prefix) */ +#define PREF64_SCALED_LIFETIME_MASK 0xfff8 +#define PREF64_PLC_MASK 0x0007 +#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC) + +typedef enum PrefixLengthCode { + PREFIX_LENGTH_CODE_96, + PREFIX_LENGTH_CODE_64, + PREFIX_LENGTH_CODE_56, + PREFIX_LENGTH_CODE_48, + PREFIX_LENGTH_CODE_40, + PREFIX_LENGTH_CODE_32, + _PREFIX_LENGTH_CODE_MAX, + _PREFIX_LENGTH_CODE_INVALID = -EINVAL, +} PrefixLengthCode; + +/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */ +struct nd_opt_prefix64_info { + uint8_t type; + uint8_t length; + uint16_t lifetime_and_plc; + uint8_t prefix[12]; +} __attribute__((__packed__)); + +int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret); +int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret); diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c index 86f8ca0364d..fe8d26ebd4b 100644 --- a/src/libsystemd-network/ndisc-router.c +++ b/src/libsystemd-network/ndisc-router.c @@ -13,6 +13,7 @@ #include "memory-util.h" #include "missing_network.h" #include "ndisc-internal.h" +#include "ndisc-protocol.h" #include "ndisc-router.h" #include "strv.h" @@ -69,6 +70,21 @@ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) return 0; } +static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t length) { + uint16_t lifetime_and_plc; + + assert(p); + + if (length != sizeof(struct nd_opt_prefix64_info)) + return false; + + lifetime_and_plc = be16toh(p->lifetime_and_plc); + if (pref64_plc_to_prefix_length(lifetime_and_plc, NULL) < 0) + return false; + + return true; +} + int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { struct nd_router_advert *a; const uint8_t *p; @@ -205,7 +221,12 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { "DNSSL option has invalid size."); break; - } + case SD_NDISC_OPTION_PREF64: { + if (!pref64_option_verify((struct nd_opt_prefix64_info *) p, length)) + log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "PREF64 prefix has invalid prefix length."); + break; + }} p += length, left -= length; } @@ -766,3 +787,94 @@ int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret return 0; } + +static int get_pref64_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix64_info **ret) { + struct nd_opt_prefix64_info *ri; + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREF64); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length != sizeof(struct nd_opt_prefix64_info)) + return -EBADMSG; + + ri = (struct nd_opt_prefix64_info *) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); + if (!pref64_option_verify(ri, length)) + return -EBADMSG; + + *ret = ri; + return 0; +} + +int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + struct nd_opt_prefix64_info *pi; + struct in6_addr a = {}; + unsigned prefixlen; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + r = get_pref64_prefix_info(rt, &pi); + if (r < 0) + return r; + + r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefixlen); + if (r < 0) + return r; + + memcpy(&a, pi->prefix, sizeof(pi->prefix)); + in6_addr_mask(&a, prefixlen); + /* extra safety check for refusing malformed prefix. */ + if (memcmp(&a, pi->prefix, sizeof(pi->prefix)) != 0) + return -EBADMSG; + + *ret_addr = a; + return 0; +} + +int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { + struct nd_opt_prefix64_info *pi; + uint16_t lifetime_prefix_len; + uint8_t prefix_len; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_pref64_prefix_info(rt, &pi); + if (r < 0) + return r; + + lifetime_prefix_len = be16toh(pi->lifetime_and_plc); + pref64_plc_to_prefix_length(lifetime_prefix_len, &prefix_len); + + *ret = prefix_len; + return 0; +} + +int sd_ndisc_router_prefix64_get_lifetime_sec(sd_ndisc_router *rt, uint16_t *ret) { + struct nd_opt_prefix64_info *pi; + uint16_t lifetime_prefix_len; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_pref64_prefix_info(rt, &pi); + if (r < 0) + return r; + + lifetime_prefix_len = be16toh(pi->lifetime_and_plc); + + *ret = lifetime_prefix_len & PREF64_SCALED_LIFETIME_MASK; + return 0; +} diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 0667376d886..af0be7a5b7c 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -10,6 +10,7 @@ #include "sd-radv.h" #include "list.h" +#include "ndisc-protocol.h" #include "network-common.h" #include "sparse-endian.h" #include "time-util.h" @@ -190,23 +191,10 @@ struct sd_radv_route_prefix { usec_t valid_until; }; -/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PLC (Prefix Length Code): 3-bit unsigned integer */ -#define radv_pref64_prefix_opt__contents { \ - uint8_t type; \ - uint8_t length; \ - uint16_t lifetime_and_plc; \ - uint8_t prefix[12]; \ -} - -struct radv_pref64_prefix_opt radv_pref64_prefix_opt__contents; - -struct radv_pref64_prefix_opt__packed radv_pref64_prefix_opt__contents _packed_; -assert_cc(sizeof(struct radv_pref64_prefix_opt) == sizeof(struct radv_pref64_prefix_opt__packed)); - struct sd_radv_pref64_prefix { unsigned n_ref; - struct radv_pref64_prefix_opt opt; + struct nd_opt_prefix64_info opt; struct in6_addr in6_addr; uint8_t prefixlen; diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index f1256f4725b..c99b2b2313c 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -1135,33 +1135,14 @@ int sd_radv_pref64_prefix_set_prefix( uint16_t pref64_lifetime; uint8_t prefixlen_code; + int r; assert_return(p, -EINVAL); assert_return(prefix, -EINVAL); - switch (prefixlen) { - case 96: - prefixlen_code = 0; - break; - case 64: - prefixlen_code = 1; - break; - case 56: - prefixlen_code = 2; - break; - case 48: - prefixlen_code = 3; - break; - case 40: - prefixlen_code = 4; - break; - case 32: - prefixlen_code = 5; - break; - default: - log_radv(NULL, "Unsupported PREF64 prefix length %u. Valid lengths are 32, 40, 48, 56, 64 and 96", prefixlen); - return -EINVAL; - } + r = pref64_prefix_length_to_plc(prefixlen, &prefixlen_code); + if (r < 0) + return log_radv_errno(NULL, r, "Unsupported PREF64 prefix length %u. Valid lengths are 32, 40, 48, 56, 64 and 96", prefixlen); if (lifetime_usec == USEC_INFINITY || DIV_ROUND_UP(lifetime_usec, 8 * USEC_PER_SEC) >= UINT64_C(1) << 13) return -EINVAL; diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index a9e33ac627e..c6756a87389 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -921,6 +921,34 @@ static int captive_portal_append_json(Link *link, JsonVariant **v) { return json_variant_merge_objectb(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("CaptivePortal", captive_portal))); } +static int pref64_append_json(Link *link, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL, *w = NULL; + NDiscPREF64 *i; + int r; + + assert(link); + assert(v); + + if (!link->network || !link->network->ipv6_accept_ra_use_pref64) + return 0; + + SET_FOREACH(i, link->ndisc_pref64) { + r = json_build(&array, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("Prefix", &i->prefix), + JSON_BUILD_PAIR_UNSIGNED("PrefixLength", i->prefix_len), + JSON_BUILD_PAIR_FINITE_USEC("LifetimeUSec", i->lifetime_usec), + JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("ConfigProvider", &i->router))); + if (r < 0) + return r; + } + + r = json_append_one(&w, "PREF64", array); + if (r < 0) + return r; + + return json_append_one(v, "NDisc", w); +} + static int dhcp_server_offered_leases_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; DHCPLease *lease; @@ -1145,6 +1173,10 @@ int link_build_json(Link *link, JsonVariant **ret) { if (r < 0) return r; + r = pref64_append_json(link, &v); + if (r < 0) + return r; + r = addresses_append_json(link->addresses, &v); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index e46e5f0c617..8ced02f3c1a 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -157,6 +157,7 @@ typedef struct Link { Set *ndisc_rdnss; Set *ndisc_dnssl; Set *ndisc_captive_portals; + Set *ndisc_pref64; unsigned ndisc_messages; bool ndisc_configured:1; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 8e66d83fbb1..e8606506916 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -965,6 +965,113 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) return 1; } +static void ndisc_pref64_hash_func(const NDiscPREF64 *x, struct siphash *state) { + assert(x); + + siphash24_compress(&x->prefix_len, sizeof(x->prefix_len), state); + siphash24_compress(&x->prefix, sizeof(x->prefix), state); +} + +static int ndisc_pref64_compare_func(const NDiscPREF64 *a, const NDiscPREF64 *b) { + int r; + + assert(a); + assert(b); + + r = CMP(a->prefix_len, b->prefix_len); + if (r != 0) + return r; + + return memcmp(&a->prefix, &b->prefix, sizeof(a->prefix)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_pref64_hash_ops, + NDiscPREF64, + ndisc_pref64_hash_func, + ndisc_pref64_compare_func, + mfree); + +static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { + _cleanup_free_ NDiscPREF64 *new_entry = NULL; + usec_t lifetime_usec, timestamp_usec; + struct in6_addr a, router; + uint16_t lifetime_sec; + unsigned prefix_len; + NDiscPREF64 *exist; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ipv6_accept_ra_use_pref64) + return 0; + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); + + r = sd_ndisc_router_prefix64_get_prefix(rt, &a); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get pref64 prefix: %m"); + + r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefix_len); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get pref64 prefix length: %m"); + + r = sd_ndisc_router_prefix64_get_lifetime_sec(rt, &lifetime_sec); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get pref64 prefix lifetime: %m"); + + r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); + + lifetime_usec = sec16_to_usec(lifetime_sec, timestamp_usec); + + if (lifetime_usec == 0) { + free(set_remove(link->ndisc_pref64, + &(NDiscPREF64) { + .prefix = a, + .prefix_len = prefix_len + })); + return 0; + } + + exist = set_get(link->ndisc_pref64, + &(NDiscPREF64) { + .prefix = a, + .prefix_len = prefix_len + }); + if (exist) { + /* update existing entry */ + exist->router = router; + exist->lifetime_usec = lifetime_usec; + return 0; + } + + new_entry = new(NDiscPREF64, 1); + if (!new_entry) + return log_oom(); + + *new_entry = (NDiscPREF64) { + .router = router, + .lifetime_usec = lifetime_usec, + .prefix = a, + .prefix_len = prefix_len, + }; + + r = set_ensure_put(&link->ndisc_pref64, &ndisc_pref64_hash_ops, new_entry); + if (r < 0) + return log_oom(); + + assert(r > 0); + TAKE_PTR(new_entry); + + return 0; +} + static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { size_t n_captive_portal = 0; int r; @@ -1013,6 +1120,9 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { if (r > 0) n_captive_portal++; break; + case SD_NDISC_OPTION_PREF64: + r = ndisc_router_process_pref64(link, rt); + break; } if (r < 0 && r != -EBADMSG) return r; @@ -1413,6 +1523,7 @@ void ndisc_flush(Link *link) { link->ndisc_rdnss = set_free(link->ndisc_rdnss); link->ndisc_dnssl = set_free(link->ndisc_dnssl); link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); + link->ndisc_pref64 = set_free(link->ndisc_pref64); } static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index 267f7d4a020..a463f42b522 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -39,6 +39,15 @@ typedef struct NDiscCaptivePortal { char *captive_portal; } NDiscCaptivePortal; +typedef struct NDiscPREF64 { + struct in6_addr router; + /* This is an absolute point in time, and NOT a timespan/duration. + * Must be specified with CLOCK_BOOTTIME. */ + usec_t lifetime_usec; + uint8_t prefix_len; + struct in6_addr prefix; +} NDiscPREF64; + static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); } diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index fefc84a7d94..bbb87e00bd2 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -285,6 +285,7 @@ IPv6AcceptRA.UseGateway, config_parse_bool, IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix) IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix) IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix) +IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_pref64) IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) IPv6AcceptRA.UseDomains, config_parse_ipv6_accept_ra_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_mtu) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index a7d1f9cd280..3e1110e6d7d 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -333,6 +333,7 @@ struct Network { bool ipv6_accept_ra_use_icmp6_ratelimit; bool ipv6_accept_ra_quickack; bool ipv6_accept_ra_use_captive_portal; + bool ipv6_accept_ra_use_pref64; bool active_slave; bool primary_slave; DHCPUseDomains ipv6_accept_ra_use_domains; diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h index be702f68a52..89b6413ef9b 100644 --- a/src/systemd/sd-ndisc.h +++ b/src/systemd/sd-ndisc.h @@ -42,7 +42,8 @@ enum { SD_NDISC_OPTION_RDNSS = 25, SD_NDISC_OPTION_FLAGS_EXTENSION = 26, SD_NDISC_OPTION_DNSSL = 31, - SD_NDISC_OPTION_CAPTIVE_PORTAL = 37 + SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, + SD_NDISC_OPTION_PREF64 = 38 }; /* Route preference, RFC 4191, Section 2.1 */ @@ -127,6 +128,11 @@ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); /* Specific option access: SD_NDISC_OPTION_CAPTIVE_PORTAL */ int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **uri, size_t *size); +/* Specific option access: SD_NDISC_OPTION_PREF64 */ +int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_prefix64_get_lifetime_sec(sd_ndisc_router *rt, uint16_t *ret); + _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref);