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-08-25 15:13:06 +02:00
committed by Daan De Meyer
parent ebbc92405b
commit 1925f829ab
9 changed files with 424 additions and 0 deletions
+34
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: ", \
+150
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;
}
+2
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)
+5
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;
}
+1
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;
+185
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,
+13
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);
+9
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;