Skip to content

Commit

Permalink
network: sd-radv - Introduce pref64 support (RFC8781)
Browse files Browse the repository at this point in the history
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::

```
  • Loading branch information
ssahani authored and DaanDeMeyer committed Aug 25, 2023
1 parent ebbc924 commit 1925f82
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 0 deletions.
25 changes: 25 additions & 0 deletions man/systemd.network.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
34 changes: 34 additions & 0 deletions src/libsystemd-network/radv-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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: ", \
Expand Down
150 changes: 150 additions & 0 deletions src/libsystemd-network/sd-radv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 changes: 2 additions & 0 deletions src/network/networkd-network-gperf.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/network/networkd-network.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions src/network/networkd-network.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 1925f82

Please sign in to comment.