</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>UsePREF64=</varname></term>
+ <listitem>
+ <para>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 <command>networkctl</command> status output per-link.
+ See <ulink url="https://tools.ietf.org/html/rfc8781">RFC 8781</ulink>. Defaults to false.</para>
+
+ <xi:include href="version-info.xml" xpointer="v255"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>UseAutonomousPrefix=</varname></term>
<listitem>
'icmp6-util.c',
'lldp-neighbor.c',
'lldp-network.c',
+ 'ndisc-protocol.c',
'ndisc-router.c',
'network-common.c',
'network-internal.c',
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
#include "memory-util.h"
#include "missing_network.h"
#include "ndisc-internal.h"
+#include "ndisc-protocol.h"
#include "ndisc-router.h"
#include "strv.h"
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;
"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;
}
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;
+}
#include "sd-radv.h"
#include "list.h"
+#include "ndisc-protocol.h"
#include "network-common.h"
#include "sparse-endian.h"
#include "time-util.h"
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;
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;
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;
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;
Set *ndisc_rdnss;
Set *ndisc_dnssl;
Set *ndisc_captive_portals;
+ Set *ndisc_pref64;
unsigned ndisc_messages;
bool ndisc_configured:1;
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;
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;
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] = {
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));
}
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)
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;
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 */
/* 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);