</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>
#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,
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;
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: ", \
#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;
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);
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,
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;
+}
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)
"IPv6PrefixDelegation\0"
"IPv6Prefix\0"
"IPv6RoutePrefix\0"
+ "IPv6PREF64Prefix\0"
"LLDP\0"
"TrafficControlQueueingDiscipline\0"
"CAN\0"
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);
if (!hashmap_isempty(network->route_prefixes_by_section))
return true;
+ if (!hashmap_isempty(network->pref64_prefixes_by_section))
+ return true;
+
return false;
}
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;
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);
}
}
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;
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;
static int radv_configure(Link *link) {
Link *uplink = NULL;
RoutePrefix *q;
+ pref64Prefix *n;
Prefix *p;
int r;
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);
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,
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,
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);
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);
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);
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);
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;