network: sd-radv - Introduce pref64 support (RFC8781)

Implements: https://datatracker.ietf.org/doc/html/rfc8781

```

[IPv6PREF64Prefix]
Prefix=2003:da8:1:0::/64
ValidLifetimeSec=30m

Frame 16: 126 bytes on wire (1008 bits), 126 bytes captured (1008 bits) on interface veth99, id 0
Ethernet II, Src: 06:c7:41:95:1d:7f (06:c7:41:95:1d:7f), Dst: IPv6mcast_01 (33:33:00:00:00:01)
Internet Protocol Version 6, Src: fe80::4c7:41ff:fe95:1d7f, Dst: ff02::1
Internet Control Message Protocol v6
    Type: Router Advertisement (134)
    Code: 0
    Checksum: 0x0ca0 [correct]
    [Checksum Status: Good]
    Cur hop limit: 0
    Flags: 0x00, Prf (Default Router Preference): Medium
    Router lifetime (s): 1800
    Reachable time (ms): 0
    Retrans timer (ms): 0
    ICMPv6 Option (Source link-layer address : 06:c7:41:95:1d:7f)
    ICMPv6 Option (Prefix information : 2002:da8:1::/64)
    ICMPv6 Option (PREF64 Option)
        Type: PREF64 Option (38)
        Length: 2 (16 bytes)
        0000 0111 0000 1... = Scaled Lifetime: 225
        .... .... .... .001 = PLC (Prefix Length Code): 64 bits prefix length (0x1)
        Prefix: 64:ff9b::

```
This commit is contained in:
Susant Sahani 2023-07-28 22:51:50 +05:30 committed by Daan De Meyer
parent ebbc92405b
commit 1925f829ab
9 changed files with 424 additions and 0 deletions

View file

@ -3249,6 +3249,31 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
</variablelist>
</refsect1>
<refsect1>
<title>[IPv6PREF64Prefix] Section Options</title>
<para>One or more [IPv6PREF64Prefix] sections contain the IPv6 PREF64 (or NAT64) prefixes that are announced via Router
Advertisements. See <ulink url="https://tools.ietf.org/html/rfc8781">RFC 8781</ulink> for further
details.</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>Prefix=</varname></term>
<listitem><para>The IPv6 PREF64 (or NAT64) prefix that is to be distributed to hosts. The setting holds
an IPv6 prefix that should be set up for NAT64 translation (PLAT) to allow 464XLAT on the network segment.
Use multiple [IPv6PREF64Prefix] sections to configure multiple IPv6 prefixes since prefix lifetime may differ
from one prefix to another. The prefix is an address with a prefix length, separated by a slash
<literal>/</literal> character. Valid NAT64 prefix length are 96, 64, 56, 48, 40, and 32 bits.</para></listitem></varlistentry>
<varlistentry>
<term><varname>LifetimeSec=</varname></term>
<listitem><para>Lifetime for the prefix measured in seconds. Should be greater than or equal to <varname>RouterLifetimeSec=</varname>.
<varname>LifetimeSec=</varname> defaults to 1800 seconds.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[Bridge] Section Options</title>
<para>The [Bridge] section accepts the following keys:</para>

View file

@ -55,10 +55,15 @@
#define RADV_MAX_FINAL_RTR_ADVERTISEMENTS 3
#define RADV_MIN_DELAY_BETWEEN_RAS 3
#define RADV_MAX_RA_DELAY_TIME_USEC (500 * USEC_PER_MSEC)
/* From RFC 8781 section 4.1
* By default, the value of the Scaled Lifetime field SHOULD be set to the lesser of 3 x MaxRtrAdvInterval */
#define RADV_DEFAULT_PRE64_LIFETIME_USEC (3 * RADV_DEFAULT_MAX_TIMEOUT_USEC)
#define RADV_OPT_ROUTE_INFORMATION 24
#define RADV_OPT_RDNSS 25
#define RADV_OPT_DNSSL 31
/* Pref64 option type (RFC8781, section 4) */
#define RADV_OPT_PREF64 38
enum RAdvState {
RADV_STATE_IDLE = 0,
@ -101,6 +106,9 @@ struct sd_radv {
unsigned n_route_prefixes;
LIST_HEAD(sd_radv_route_prefix, route_prefixes);
unsigned n_pref64_prefixes;
LIST_HEAD(sd_radv_pref64_prefix, pref64_prefixes);
size_t n_rdnss;
struct sd_radv_opt_dns *rdnss;
struct sd_radv_opt_dns *dnssl;
@ -172,6 +180,32 @@ 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 in6_addr in6_addr;
uint8_t prefixlen;
usec_t lifetime_usec;
LIST_FIELDS(struct sd_radv_pref64_prefix, prefix);
};
#define log_radv_errno(radv, error, fmt, ...) \
log_interface_prefix_full_errno( \
"RADV: ", \

View file

@ -25,6 +25,7 @@
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
#include "unaligned.h"
int sd_radv_new(sd_radv **ret) {
_cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
@ -220,6 +221,9 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us
iov[msg.msg_iovlen++] = IOVEC_MAKE(&rt->opt, sizeof(rt->opt));
}
LIST_FOREACH(prefix, p, ra->pref64_prefixes)
iov[msg.msg_iovlen++] = IOVEC_MAKE(&p->opt, sizeof(p->opt));
if (ra->rdnss)
iov[msg.msg_iovlen++] = IOVEC_MAKE(ra->rdnss, ra->rdnss->length * 8);
@ -738,6 +742,78 @@ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) {
return 0;
}
int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) {
sd_radv_pref64_prefix *found = NULL;
int r;
assert_return(ra, -EINVAL);
assert_return(p, -EINVAL);
const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen);
LIST_FOREACH(prefix, cur, ra->pref64_prefixes) {
r = in_addr_prefix_intersect(AF_INET6,
(const union in_addr_union*) &cur->in6_addr,
cur->prefixlen,
(const union in_addr_union*) &p->in6_addr,
p->prefixlen);
if (r < 0)
return r;
if (r == 0)
continue;
if (cur->prefixlen == p->prefixlen) {
found = cur;
break;
}
return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
"IPv6 PREF64 prefix %s conflicts with %s, ignoring.",
addr_p,
IN6_ADDR_PREFIX_TO_STRING(&cur->in6_addr, cur->prefixlen));
}
if (found) {
/* p and cur may be equivalent. First increment the reference counter. */
sd_radv_pref64_prefix_ref(p);
/* Then, remove the old entry. */
LIST_REMOVE(prefix, ra->pref64_prefixes, found);
sd_radv_pref64_prefix_unref(found);
/* Finally, add the new entry. */
LIST_APPEND(prefix, ra->pref64_prefixes, p);
log_radv(ra, "Updated/replaced IPv6 PREF64 prefix %s (lifetime: %s)",
strna(addr_p),
FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC));
} else {
/* The route prefix is new. Let's simply add it. */
sd_radv_pref64_prefix_ref(p);
LIST_APPEND(prefix, ra->pref64_prefixes, p);
ra->n_pref64_prefixes++;
log_radv(ra, "Added PREF64 prefix %s", strna(addr_p));
}
if (ra->state == RADV_STATE_IDLE)
return 0;
if (ra->ra_sent == 0)
return 0;
/* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
r = radv_send(ra, NULL, ra->lifetime_usec);
if (r < 0)
log_radv_errno(ra, r, "Unable to send Router Advertisement for added PREF64 prefix %s, ignoring: %m",
strna(addr_p));
else
log_radv(ra, "Sent Router Advertisement for added PREF64 prefix %s.", strna(addr_p));
return 0;
}
int sd_radv_set_rdnss(
sd_radv *ra,
uint32_t lifetime,
@ -983,3 +1059,77 @@ int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint64_t lifetime
return 0;
}
int sd_radv_pref64_prefix_new(sd_radv_pref64_prefix **ret) {
sd_radv_pref64_prefix *p;
assert_return(ret, -EINVAL);
p = new(sd_radv_pref64_prefix, 1);
if (!p)
return -ENOMEM;
*p = (sd_radv_pref64_prefix) {
.n_ref = 1,
.opt.type = RADV_OPT_PREF64,
.opt.length = 2,
};
*ret = p;
return 0;
}
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_pref64_prefix, sd_radv_pref64_prefix, mfree);
int sd_radv_pref64_prefix_set_prefix(
sd_radv_pref64_prefix *p,
const struct in6_addr *prefix,
uint8_t prefixlen,
uint64_t lifetime_usec) {
uint16_t pref64_lifetime;
uint8_t prefixlen_code;
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;
}
if (lifetime_usec == USEC_INFINITY || DIV_ROUND_UP(lifetime_usec, 8 * USEC_PER_SEC) >= UINT64_C(1) << 13)
return -EINVAL;
/* RFC 8781 - 4.1 rounding up lifetime to multiply of 8 */
pref64_lifetime = DIV_ROUND_UP(lifetime_usec, 8 * USEC_PER_SEC) << 3;
pref64_lifetime |= prefixlen_code;
unaligned_write_be16(&p->opt.lifetime_and_plc, pref64_lifetime);
memcpy(&p->opt.prefix, prefix, sizeof(p->opt.prefix));
p->in6_addr = *prefix;
p->prefixlen = prefixlen;
return 0;
}

View file

@ -389,6 +389,8 @@ IPv6Prefix.RouteMetric, config_parse_prefix_metric,
IPv6Prefix.Token, config_parse_prefix_token, 0, 0
IPv6RoutePrefix.Route, config_parse_route_prefix, 0, 0
IPv6RoutePrefix.LifetimeSec, config_parse_route_prefix_lifetime, 0, 0
IPv6PREF64Prefix.Prefix, config_parse_pref64_prefix, 0, 0
IPv6PREF64Prefix.LifetimeSec, config_parse_pref64_prefix_lifetime, 0, 0
LLDP.MUDURL, config_parse_mud_url, 0, offsetof(Network, lldp_mudurl)
CAN.BitRate, config_parse_can_bitrate, 0, offsetof(Network, can_bitrate)
CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)

View file

@ -529,6 +529,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
"IPv6PrefixDelegation\0"
"IPv6Prefix\0"
"IPv6RoutePrefix\0"
"IPv6PREF64Prefix\0"
"LLDP\0"
"TrafficControlQueueingDiscipline\0"
"CAN\0"
@ -779,6 +780,7 @@ static Network *network_free(Network *network) {
hashmap_free_with_destructor(network->address_labels_by_section, address_label_free);
hashmap_free_with_destructor(network->prefixes_by_section, prefix_free);
hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free);
hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free);
hashmap_free_with_destructor(network->rules_by_section, routing_policy_rule_free);
hashmap_free_with_destructor(network->dhcp_static_leases_by_section, dhcp_static_lease_free);
ordered_hashmap_free_with_destructor(network->sr_iov_by_section, sr_iov_free);
@ -844,6 +846,9 @@ bool network_has_static_ipv6_configurations(Network *network) {
if (!hashmap_isempty(network->route_prefixes_by_section))
return true;
if (!hashmap_isempty(network->pref64_prefixes_by_section))
return true;
return false;
}

View file

@ -358,6 +358,7 @@ struct Network {
Hashmap *address_labels_by_section;
Hashmap *prefixes_by_section;
Hashmap *route_prefixes_by_section;
Hashmap *pref64_prefixes_by_section;
Hashmap *rules_by_section;
Hashmap *dhcp_static_leases_by_section;
Hashmap *qdiscs_by_section;

View file

@ -48,6 +48,7 @@ void network_adjust_radv(Network *network) {
if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) {
network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free);
network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free);
network->pref64_prefixes_by_section = hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free);
}
}
@ -177,6 +178,61 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig
return 0;
}
pref64Prefix *pref64_prefix_free(pref64Prefix *prefix) {
if (!prefix)
return NULL;
if (prefix->network) {
assert(prefix->section);
hashmap_remove(prefix->network->pref64_prefixes_by_section, prefix->section);
}
config_section_free(prefix->section);
return mfree(prefix);
}
DEFINE_SECTION_CLEANUP_FUNCTIONS(pref64Prefix, pref64_prefix_free);
static int pref64_prefix_new_static(Network *network, const char *filename, unsigned section_line, pref64Prefix **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
_cleanup_(pref64_prefix_freep) pref64Prefix *prefix = NULL;
int r;
assert(network);
assert(ret);
assert(filename);
assert(section_line > 0);
r = config_section_new(filename, section_line, &n);
if (r < 0)
return r;
prefix = hashmap_get(network->pref64_prefixes_by_section, n);
if (prefix) {
*ret = TAKE_PTR(prefix);
return 0;
}
prefix = new(pref64Prefix, 1);
if (!prefix)
return -ENOMEM;
*prefix = (pref64Prefix) {
.network = network,
.section = TAKE_PTR(n),
.lifetime = RADV_DEFAULT_PRE64_LIFETIME_USEC,
};
r = hashmap_ensure_put(&network->pref64_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix);
if (r < 0)
return r;
*ret = TAKE_PTR(prefix);
return 0;
}
int link_request_radv_addresses(Link *link) {
Prefix *p;
int r;
@ -294,6 +350,25 @@ static int radv_set_route_prefix(Link *link, RoutePrefix *prefix) {
return sd_radv_add_route_prefix(link->radv, p);
}
static int radv_set_pref64_prefix(Link *link, pref64Prefix *prefix) {
_cleanup_(sd_radv_pref64_prefix_unrefp) sd_radv_pref64_prefix *p = NULL;
int r;
assert(link);
assert(link->radv);
assert(prefix);
r = sd_radv_pref64_prefix_new(&p);
if (r < 0)
return r;
r = sd_radv_pref64_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen, prefix->lifetime);
if (r < 0)
return r;
return sd_radv_add_pref64_prefix(link->radv, p);
}
static int network_get_ipv6_dns(Network *network, struct in6_addr **ret_addresses, size_t *ret_size) {
_cleanup_free_ struct in6_addr *addresses = NULL;
size_t n_addresses = 0;
@ -441,6 +516,7 @@ static int radv_find_uplink(Link *link, Link **ret) {
static int radv_configure(Link *link) {
Link *uplink = NULL;
RoutePrefix *q;
pref64Prefix *n;
Prefix *p;
int r;
@ -508,6 +584,12 @@ static int radv_configure(Link *link) {
return r;
}
HASHMAP_FOREACH(n, link->network->pref64_prefixes_by_section) {
r = radv_set_pref64_prefix(link, n);
if (r < 0 && r != -EEXIST)
return r;
}
(void) radv_find_uplink(link, &uplink);
r = radv_set_dns(link, uplink);
@ -792,6 +874,16 @@ void network_drop_invalid_route_prefixes(Network *network) {
route_prefix_free(p);
}
void network_drop_invalid_pref64_prefixes(Network *network) {
pref64Prefix *p;
assert(network);
HASHMAP_FOREACH(p, network->pref64_prefixes_by_section)
if (section_is_invalid(p->section))
pref64_prefix_free(p);
}
int config_parse_prefix(
const char *unit,
const char *filename,
@ -1083,6 +1175,99 @@ int config_parse_route_prefix_lifetime(
return 0;
}
int config_parse_pref64_prefix(
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) {
_cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL;
Network *network = ASSERT_PTR(userdata);
union in_addr_union a;
uint8_t prefixlen;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
r = pref64_prefix_new_static(network, filename, section_line, &p);
if (r < 0)
return log_oom();
r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &prefixlen);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"PREF64 prefix is invalid, ignoring assignment: %s", rvalue);
return 0;
}
if (!IN_SET(prefixlen, 96, 64, 56, 48, 40, 32)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"PREF64 prefixlen is invalid, ignoring assignment: %s", rvalue);
return 0;
}
(void) in6_addr_mask(&a.in6,prefixlen);
p->prefix = a.in6;
p->prefixlen = prefixlen;
TAKE_PTR(p);
return 0;
}
int config_parse_pref64_prefix_lifetime(
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) {
_cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL;
Network *network = ASSERT_PTR(userdata);
usec_t usec;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
r = pref64_prefix_new_static(network, filename, section_line, &p);
if (r < 0)
return log_oom();
r = parse_sec(rvalue, &usec);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"PREF64 lifetime is invalid, ignoring assignment: %s", rvalue);
return 0;
}
if (usec == USEC_INFINITY || DIV_ROUND_UP(usec, 8 * USEC_PER_SEC) >= UINT64_C(1) << 13) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"PREF64 lifetime is too long, ignoring assignment: %s", rvalue);
return 0;
}
p->lifetime = usec;
TAKE_PTR(p);
return 0;
}
int config_parse_radv_dns(
const char *unit,
const char *filename,

View file

@ -52,11 +52,22 @@ typedef struct RoutePrefix {
usec_t lifetime;
} RoutePrefix;
typedef struct pref64Prefix {
Network *network;
ConfigSection *section;
struct in6_addr prefix;
uint8_t prefixlen;
usec_t lifetime;
} pref64Prefix;
Prefix *prefix_free(Prefix *prefix);
RoutePrefix *route_prefix_free(RoutePrefix *prefix);
pref64Prefix *pref64_prefix_free(pref64Prefix *prefix);
void network_drop_invalid_prefixes(Network *network);
void network_drop_invalid_route_prefixes(Network *network);
void network_drop_invalid_pref64_prefixes(Network *network);
void network_adjust_radv(Network *network);
int link_request_radv_addresses(Link *link);
@ -85,3 +96,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_radv_dns);
CONFIG_PARSER_PROTOTYPE(config_parse_radv_search_domains);
CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix);
CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix_lifetime);
CONFIG_PARSER_PROTOTYPE(config_parse_pref64_prefix);
CONFIG_PARSER_PROTOTYPE(config_parse_pref64_prefix_lifetime);

View file

@ -33,6 +33,7 @@ _SD_BEGIN_DECLARATIONS;
typedef struct sd_radv sd_radv;
typedef struct sd_radv_prefix sd_radv_prefix;
typedef struct sd_radv_route_prefix sd_radv_route_prefix;
typedef struct sd_radv_pref64_prefix sd_radv_pref64_prefix;
/* Router Advertisement */
int sd_radv_new(sd_radv **ret);
@ -60,6 +61,7 @@ int sd_radv_set_other_information(sd_radv *ra, int other);
int sd_radv_set_preference(sd_radv *ra, unsigned preference);
int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p);
int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p);
int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p);
void sd_radv_remove_prefix(sd_radv *ra, const struct in6_addr *prefix, unsigned char prefixlen);
int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime,
const struct in6_addr *dns, size_t n_dns);
@ -87,9 +89,16 @@ sd_radv_route_prefix *sd_radv_route_prefix_unref(sd_radv_route_prefix *ra);
int sd_radv_route_prefix_set_prefix(sd_radv_route_prefix *p, const struct in6_addr *in6_addr, unsigned char prefixlen);
int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint64_t lifetime_usec, uint64_t valid_until);
int sd_radv_pref64_prefix_new(sd_radv_pref64_prefix **ret);
int sd_radv_pref64_prefix_set_prefix(sd_radv_pref64_prefix *p, const struct in6_addr *prefix,
uint8_t prefixlen, uint64_t lifetime_usec);
sd_radv_pref64_prefix *sd_radv_pref64_prefix_ref(sd_radv_pref64_prefix *ra);
sd_radv_pref64_prefix *sd_radv_pref64_prefix_unref(sd_radv_pref64_prefix *ra);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv, sd_radv_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_prefix, sd_radv_prefix_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_route_prefix, sd_radv_route_prefix_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_pref64_prefix, sd_radv_pref64_prefix_unref);
_SD_END_DECLARATIONS;