network: ndisc - Allow to parse PREF64 prefix

This commit is contained in:
Susant Sahani 2023-08-28 23:42:39 +05:30 committed by Yu Watanabe
parent 6a6d27bc5b
commit 6e8f5e4c1f
14 changed files with 358 additions and 39 deletions

View file

@ -3122,6 +3122,17 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
</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>

View file

@ -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',

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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, &timestamp_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] = {

View file

@ -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));
}

View file

@ -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)

View file

@ -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;

View file

@ -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);