]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-radv.c
Merge pull request #33352 from YHNdnzj/freeconp-void
[thirdparty/systemd.git] / src / network / networkd-radv.c
index e6ef3974f288837993cd8722bd88541880d1f13a..652e7845fcea0b722c87dc512ee9c7abe4394dc7 100644 (file)
@@ -7,6 +7,7 @@
 #include <arpa/inet.h>
 
 #include "dns-domain.h"
+#include "ndisc-router-internal.h"
 #include "networkd-address-generation.h"
 #include "networkd-address.h"
 #include "networkd-dhcp-prefix-delegation.h"
 #include "string-table.h"
 #include "strv.h"
 
-void network_adjust_radv(Network *network) {
-        assert(network);
-
-        /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */
-
-        if (network->dhcp_pd < 0)
-                /* For backward compatibility. */
-                network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6);
-
-        if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) {
-                if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE)
-                        log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. "
-                                    "Disabling IPv6PrefixDelegation=.", network->filename);
-
-                network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE;
-        }
-
-        if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) {
-                network->n_router_dns = 0;
-                network->router_dns = mfree(network->router_dns);
-                network->router_search_domains = ordered_set_free(network->router_search_domains);
-        }
-
-        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);
-        }
-}
-
 bool link_radv_enabled(Link *link) {
         assert(link);
 
@@ -63,7 +35,7 @@ bool link_radv_enabled(Link *link) {
         return link->network->router_prefix_delegation;
 }
 
-Prefix *prefix_free(Prefix *prefix) {
+Prefixprefix_free(Prefix *prefix) {
         if (!prefix)
                 return NULL;
 
@@ -108,10 +80,11 @@ static int prefix_new_static(Network *network, const char *filename, unsigned se
                 .network = network,
                 .section = TAKE_PTR(n),
 
-                .preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC,
-                .valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC,
-                .onlink = true,
-                .address_auto_configuration = true,
+                .prefix.flags = ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO,
+                .prefix.valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC,
+                .prefix.preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC,
+                .prefix.valid_until = USEC_INFINITY,
+                .prefix.preferred_until = USEC_INFINITY,
         };
 
         r = hashmap_ensure_put(&network->prefixes_by_section, &config_section_hash_ops, prefix->section, prefix);
@@ -122,7 +95,7 @@ static int prefix_new_static(Network *network, const char *filename, unsigned se
         return 0;
 }
 
-RoutePrefix *route_prefix_free(RoutePrefix *prefix) {
+RoutePrefixroute_prefix_free(RoutePrefix *prefix) {
         if (!prefix)
                 return NULL;
 
@@ -166,7 +139,8 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig
                 .network = network,
                 .section = TAKE_PTR(n),
 
-                .lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC,
+                .route.lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC,
+                .route.valid_until = USEC_INFINITY,
         };
 
         r = hashmap_ensure_put(&network->route_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix);
@@ -177,6 +151,62 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig
         return 0;
 }
 
+Prefix64* prefix64_free(Prefix64 *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(Prefix64, prefix64_free);
+
+static int prefix64_new_static(Network *network, const char *filename, unsigned section_line, Prefix64 **ret) {
+        _cleanup_(config_section_freep) ConfigSection *n = NULL;
+        _cleanup_(prefix64_freep) Prefix64 *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(Prefix64, 1);
+        if (!prefix)
+                return -ENOMEM;
+
+        *prefix = (Prefix64) {
+                .network = network,
+                .section = TAKE_PTR(n),
+
+                .prefix64.lifetime = RADV_PREF64_DEFAULT_LIFETIME_USEC,
+                .prefix64.valid_until = USEC_INFINITY,
+        };
+
+        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;
@@ -187,22 +217,22 @@ int link_request_radv_addresses(Link *link) {
                 return 0;
 
         HASHMAP_FOREACH(p, link->network->prefixes_by_section) {
-                _cleanup_set_free_ Set *addresses = NULL;
-                struct in6_addr *a;
-
                 if (!p->assign)
                         continue;
 
                 /* radv_generate_addresses() below requires the prefix length <= 64. */
-                if (p->prefixlen > 64)
+                if (p->prefix.prefixlen > 64)
                         continue;
 
-                r = radv_generate_addresses(link, p->tokens, &p->prefix, p->prefixlen, &addresses);
+                _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL;
+                r = radv_generate_addresses(link, p->tokens, &p->prefix.address, p->prefix.prefixlen, &tokens_by_address);
                 if (r < 0)
                         return r;
 
-                SET_FOREACH(a, addresses) {
-                        _cleanup_(address_freep) Address *address = NULL;
+                IPv6Token *token;
+                struct in6_addr *a;
+                HASHMAP_FOREACH_KEY(token, a, tokens_by_address) {
+                        _cleanup_(address_unrefp) Address *address = NULL;
 
                         r = address_new(&address);
                         if (r < 0)
@@ -211,8 +241,9 @@ int link_request_radv_addresses(Link *link) {
                         address->source = NETWORK_CONFIG_SOURCE_STATIC;
                         address->family = AF_INET6;
                         address->in_addr.in6 = *a;
-                        address->prefixlen = p->prefixlen;
+                        address->prefixlen = p->prefix.prefixlen;
                         address->route_metric = p->route_metric;
+                        address->token = ipv6_token_ref(token);
 
                         r = link_request_static_address(link, address);
                         if (r < 0)
@@ -223,17 +254,27 @@ int link_request_radv_addresses(Link *link) {
         return 0;
 }
 
-static uint32_t usec_to_lifetime(usec_t usec) {
-        uint64_t t;
+int link_reconfigure_radv_address(Address *address, Link *link) {
+        int r;
+
+        assert(address);
+        assert(address->source == NETWORK_CONFIG_SOURCE_STATIC);
+        assert(link);
 
-        if (usec == USEC_INFINITY)
-                return UINT32_MAX;
+        r = regenerate_address(address, link);
+        if (r <= 0)
+                return r;
+
+        r = link_request_static_address(link, address);
+        if (r < 0)
+                return r;
 
-        t = DIV_ROUND_UP(usec, USEC_PER_SEC);
-        if (t >= UINT32_MAX)
-                return UINT32_MAX;
+        if (link->static_address_messages != 0) {
+                link->static_addresses_configured = false;
+                link_set_state(link, LINK_STATE_CONFIGURING);
+        }
 
-        return (uint32_t) t;
+        return 0;
 }
 
 static int radv_set_prefix(Link *link, Prefix *prefix) {
@@ -248,23 +289,23 @@ static int radv_set_prefix(Link *link, Prefix *prefix) {
         if (r < 0)
                 return r;
 
-        r = sd_radv_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen);
+        r = sd_radv_prefix_set_prefix(p, &prefix->prefix.address, prefix->prefix.prefixlen);
         if (r < 0)
                 return r;
 
-        r = sd_radv_prefix_set_preferred_lifetime(p, prefix->preferred_lifetime, USEC_INFINITY);
+        r = sd_radv_prefix_set_preferred_lifetime(p, prefix->prefix.preferred_lifetime, prefix->prefix.preferred_until);
         if (r < 0)
                 return r;
 
-        r = sd_radv_prefix_set_valid_lifetime(p, prefix->valid_lifetime, USEC_INFINITY);
+        r = sd_radv_prefix_set_valid_lifetime(p, prefix->prefix.valid_lifetime, prefix->prefix.valid_until);
         if (r < 0)
                 return r;
 
-        r = sd_radv_prefix_set_onlink(p, prefix->onlink);
+        r = sd_radv_prefix_set_onlink(p, FLAGS_SET(prefix->prefix.flags, ND_OPT_PI_FLAG_ONLINK));
         if (r < 0)
                 return r;
 
-        r = sd_radv_prefix_set_address_autoconfiguration(p, prefix->address_auto_configuration);
+        r = sd_radv_prefix_set_address_autoconfiguration(p, FLAGS_SET(prefix->prefix.flags, ND_OPT_PI_FLAG_AUTO));
         if (r < 0)
                 return r;
 
@@ -283,17 +324,36 @@ static int radv_set_route_prefix(Link *link, RoutePrefix *prefix) {
         if (r < 0)
                 return r;
 
-        r = sd_radv_route_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen);
+        r = sd_radv_route_prefix_set_prefix(p, &prefix->route.address, prefix->route.prefixlen);
         if (r < 0)
                 return r;
 
-        r = sd_radv_route_prefix_set_lifetime(p, prefix->lifetime, USEC_INFINITY);
+        r = sd_radv_route_prefix_set_lifetime(p, prefix->route.lifetime, prefix->route.valid_until);
         if (r < 0)
                 return r;
 
         return sd_radv_add_route_prefix(link->radv, p);
 }
 
+static int radv_set_pref64_prefix(Link *link, Prefix64 *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->prefix64.prefix, prefix->prefix64.prefixlen, prefix->prefix64.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;
@@ -371,7 +431,7 @@ static int radv_set_dns(Link *link, Link *uplink) {
 
 set_dns:
         return sd_radv_set_rdnss(link->radv,
-                                 usec_to_lifetime(link->network->router_dns_lifetime_usec),
+                                 link->network->router_dns_lifetime_usec,
                                  dns, n_dns);
 }
 
@@ -407,7 +467,7 @@ set_domains:
                 return log_oom();
 
         return sd_radv_set_dnssl(link->radv,
-                                 usec_to_lifetime(link->network->router_dns_lifetime_usec),
+                                 link->network->router_dns_lifetime_usec,
                                  s);
 
 }
@@ -440,8 +500,6 @@ static int radv_find_uplink(Link *link, Link **ret) {
 
 static int radv_configure(Link *link) {
         Link *uplink = NULL;
-        RoutePrefix *q;
-        Prefix *p;
         int r;
 
         assert(link);
@@ -480,30 +538,43 @@ static int radv_configure(Link *link) {
         if (r < 0)
                 return r;
 
-        if (link->network->router_lifetime_usec > 0) {
-                r = sd_radv_set_preference(link->radv, link->network->router_preference);
-                if (r < 0)
-                        return r;
-        }
+        r = sd_radv_set_hop_limit(link->radv, link->network->router_hop_limit);
+        if (r < 0)
+                return r;
 
-        if (link->network->router_retransmit_usec > 0) {
-                r = sd_radv_set_retransmit(link->radv, DIV_ROUND_UP(link->network->router_retransmit_usec, USEC_PER_MSEC));
-                if (r < 0)
-                        return r;
-        }
+        r = sd_radv_set_preference(link->radv, link->network->router_preference);
+        if (r < 0)
+                return r;
+
+        r = sd_radv_set_reachable_time(link->radv, link->network->router_reachable_usec);
+        if (r < 0)
+                return r;
+
+        r = sd_radv_set_retransmit(link->radv, link->network->router_retransmit_usec);
+        if (r < 0)
+                return r;
 
+        Prefix *p;
         HASHMAP_FOREACH(p, link->network->prefixes_by_section) {
                 r = radv_set_prefix(link, p);
                 if (r < 0 && r != -EEXIST)
                         return r;
         }
 
+        RoutePrefix *q;
         HASHMAP_FOREACH(q, link->network->route_prefixes_by_section) {
                 r = radv_set_route_prefix(link, q);
                 if (r < 0 && r != -EEXIST)
                         return r;
         }
 
+        Prefix64 *n;
+        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);
@@ -514,13 +585,22 @@ static int radv_configure(Link *link) {
         if (r < 0)
                 return log_link_debug_errno(link, r, "Could not set RA Domains: %m");
 
+        r = sd_radv_set_home_agent_information(link->radv, link->network->router_home_agent_information);
+        if (r < 0)
+                return r;
+
+        r = sd_radv_set_home_agent_preference(link->radv, link->network->router_home_agent_preference);
+        if (r < 0)
+                return r;
+
+        r = sd_radv_set_home_agent_lifetime(link->radv, link->network->home_agent_lifetime_usec);
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
 int radv_update_mac(Link *link) {
-        bool restart;
-        int r;
-
         assert(link);
 
         if (!link->radv)
@@ -529,23 +609,7 @@ int radv_update_mac(Link *link) {
         if (link->hw_addr.length != ETH_ALEN)
                 return 0;
 
-        restart = sd_radv_is_running(link->radv);
-
-        r = sd_radv_stop(link->radv);
-        if (r < 0)
-                return r;
-
-        r = sd_radv_set_mac(link->radv, &link->hw_addr.ether);
-        if (r < 0)
-                return r;
-
-        if (restart) {
-                r = sd_radv_start(link->radv);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
+        return sd_radv_set_mac(link->radv, &link->hw_addr.ether);
 }
 
 static int radv_is_ready_to_configure(Link *link) {
@@ -660,6 +724,10 @@ int radv_start(Link *link) {
                         return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m");
         }
 
+        r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address);
+        if (r < 0)
+                return r;
+
         log_link_debug(link, "Starting IPv6 Router Advertisements");
         return sd_radv_start(link->radv);
 }
@@ -696,9 +764,18 @@ int radv_add_prefix(
                 return r;
 
         r = sd_radv_add_prefix(link->radv, p);
-        if (r < 0 && r != -EEXIST)
+        if (r == -EEXIST)
+                return 0;
+        if (r < 0)
                 return r;
 
+        if (sd_radv_is_running(link->radv)) {
+                /* Announce updated prefixe now. */
+                r = sd_radv_send(link->radv);
+                if (r < 0)
+                        return r;
+        }
+
         return 0;
 }
 
@@ -708,84 +785,112 @@ static int prefix_section_verify(Prefix *p) {
         if (section_is_invalid(p->section))
                 return -EINVAL;
 
-        if (in6_addr_is_null(&p->prefix))
+        if (in6_addr_is_null(&p->prefix.address))
                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
                                          "%s: [IPv6Prefix] section without Prefix= field configured, "
                                          "or specified prefix is the null address. "
                                          "Ignoring [IPv6Prefix] section from line %u.",
                                          p->section->filename, p->section->line);
 
-        if (p->prefixlen < 3 || p->prefixlen > 128)
+        if (p->prefix.prefixlen < 3 || p->prefix.prefixlen > 128)
                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
                                          "%s: Invalid prefix length %u is specified in [IPv6Prefix] section. "
                                          "Valid range is 3…128. Ignoring [IPv6Prefix] section from line %u.",
-                                         p->section->filename, p->prefixlen, p->section->line);
+                                         p->section->filename, p->prefix.prefixlen, p->section->line);
 
-        if (p->prefixlen > 64) {
+        if (p->prefix.prefixlen > 64) {
                 log_info("%s:%u: Unusual prefix length %u (> 64) is specified in [IPv6Prefix] section from line %s%s.",
                          p->section->filename, p->section->line,
-                         p->prefixlen,
+                         p->prefix.prefixlen,
                          p->assign ? ", refusing to assign an address in " : "",
-                         p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix, p->prefixlen) : "");
+                         p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix.address, p->prefix.prefixlen) : "");
 
                 p->assign = false;
         }
 
-        if (p->valid_lifetime == 0)
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: The valid lifetime of prefix cannot be zero. "
-                                         "Ignoring [IPv6Prefix] section from line %u.",
-                                         p->section->filename, p->section->line);
-
-        if (p->preferred_lifetime > p->valid_lifetime)
+        if (p->prefix.preferred_lifetime > p->prefix.valid_lifetime)
                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
                                          "%s: The preferred lifetime %s is longer than the valid lifetime %s. "
                                          "Ignoring [IPv6Prefix] section from line %u.",
                                          p->section->filename,
-                                         FORMAT_TIMESPAN(p->preferred_lifetime, USEC_PER_SEC),
-                                         FORMAT_TIMESPAN(p->valid_lifetime, USEC_PER_SEC),
+                                         FORMAT_TIMESPAN(p->prefix.preferred_lifetime, USEC_PER_SEC),
+                                         FORMAT_TIMESPAN(p->prefix.valid_lifetime, USEC_PER_SEC),
                                          p->section->line);
 
         return 0;
 }
 
-void network_drop_invalid_prefixes(Network *network) {
-        Prefix *p;
-
-        assert(network);
-
-        HASHMAP_FOREACH(p, network->prefixes_by_section)
-                if (prefix_section_verify(p) < 0)
-                        prefix_free(p);
-}
-
 static int route_prefix_section_verify(RoutePrefix *p) {
         if (section_is_invalid(p->section))
                 return -EINVAL;
 
-        if (p->prefixlen > 128)
+        if (p->route.prefixlen > 128)
                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
                                          "%s: Invalid prefix length %u is specified in [IPv6RoutePrefix] section. "
                                          "Valid range is 0…128. Ignoring [IPv6RoutePrefix] section from line %u.",
-                                         p->section->filename, p->prefixlen, p->section->line);
-
-        if (p->lifetime == 0)
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: The lifetime of route cannot be zero. "
-                                         "Ignoring [IPv6RoutePrefix] section from line %u.",
-                                         p->section->filename, p->section->line);
+                                         p->section->filename, p->route.prefixlen, p->section->line);
 
         return 0;
 }
 
-void network_drop_invalid_route_prefixes(Network *network) {
-        RoutePrefix *p;
-
+void network_adjust_radv(Network *network) {
         assert(network);
 
-        HASHMAP_FOREACH(p, network->route_prefixes_by_section)
-                if (route_prefix_section_verify(p) < 0)
-                        route_prefix_free(p);
+        /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */
+
+        if (network->dhcp_pd < 0)
+                /* For backward compatibility. */
+                network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6);
+
+        if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) {
+                if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE)
+                        log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. "
+                                    "Disabling IPv6PrefixDelegation=.", network->filename);
+
+                network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE;
+        }
+
+        if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) {
+                network->n_router_dns = 0;
+                network->router_dns = mfree(network->router_dns);
+                network->router_search_domains = ordered_set_free(network->router_search_domains);
+        }
+
+        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, prefix64_free);
+        }
+
+        if (!network->router_prefix_delegation)
+                return;
+
+        /* Below, let's verify router settings, if enabled. */
+
+        if (network->router_lifetime_usec == 0 && network->router_preference != SD_NDISC_PREFERENCE_MEDIUM)
+                /* RFC 4191, Section 2.2,
+                 * If the Router Lifetime is zero, the preference value MUST be set to (00) by the sender.
+                 *
+                 * Note, radv_send_router() gracefully handle that. So, it is not necessary to refuse, but
+                 * let's warn about that. */
+                log_notice("%s: RouterPreference=%s specified with RouterLifetimeSec=0, ignoring RouterPreference= setting.",
+                           network->filename, ndisc_router_preference_to_string(network->router_preference));
+
+        Prefix *prefix;
+        HASHMAP_FOREACH(prefix, network->prefixes_by_section)
+                if (prefix_section_verify(prefix) < 0)
+                        prefix_free(prefix);
+
+
+        RoutePrefix *route;
+        HASHMAP_FOREACH(route, network->route_prefixes_by_section)
+                if (route_prefix_section_verify(route) < 0)
+                        route_prefix_free(route);
+
+        Prefix64 *pref64;
+        HASHMAP_FOREACH(pref64, network->pref64_prefixes_by_section)
+                 if (section_is_invalid(pref64->section))
+                         prefix64_free(pref64);
 }
 
 int config_parse_prefix(
@@ -814,15 +919,15 @@ int config_parse_prefix(
         if (r < 0)
                 return log_oom();
 
-        r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen);
+        r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefix.prefixlen);
         if (r < 0) {
                 log_syntax(unit, LOG_WARNING, filename, line, r,
                            "Prefix is invalid, ignoring assignment: %s", rvalue);
                 return 0;
         }
 
-        (void) in6_addr_mask(&a.in6, p->prefixlen);
-        p->prefix = a.in6;
+        (void) in6_addr_mask(&a.in6, p->prefix.prefixlen);
+        p->prefix.address = a.in6;
 
         TAKE_PTR(p);
         return 0;
@@ -860,14 +965,12 @@ int config_parse_prefix_boolean(
                 return 0;
         }
 
-        if (streq(lvalue, "OnLink"))
-                p->onlink = r;
-        else if (streq(lvalue, "AddressAutoconfiguration"))
-                p->address_auto_configuration = r;
-        else if (streq(lvalue, "Assign"))
+        if (ltype != 0)
+                SET_FLAG(p->prefix.flags, ltype, r);
+        else {
+                assert(streq(lvalue, "Assign"));
                 p->assign = r;
-        else
-                assert_not_reached();
+        }
 
         TAKE_PTR(p);
         return 0;
@@ -913,9 +1016,9 @@ int config_parse_prefix_lifetime(
         }
 
         if (streq(lvalue, "PreferredLifetimeSec"))
-                p->preferred_lifetime = usec;
+                p->prefix.preferred_lifetime = usec;
         else if (streq(lvalue, "ValidLifetimeSec"))
-                p->valid_lifetime = usec;
+                p->prefix.valid_lifetime = usec;
         else
                 assert_not_reached();
 
@@ -1020,15 +1123,15 @@ int config_parse_route_prefix(
         if (r < 0)
                 return log_oom();
 
-        r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen);
+        r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->route.prefixlen);
         if (r < 0) {
                 log_syntax(unit, LOG_WARNING, filename, line, r,
                            "Route prefix is invalid, ignoring assignment: %s", rvalue);
                 return 0;
         }
 
-        (void) in6_addr_mask(&a.in6, p->prefixlen);
-        p->prefix = a.in6;
+        (void) in6_addr_mask(&a.in6, p->route.prefixlen);
+        p->route.address = a.in6;
 
         TAKE_PTR(p);
         return 0;
@@ -1073,7 +1176,100 @@ int config_parse_route_prefix_lifetime(
                 return 0;
         }
 
-        p->lifetime = usec;
+        p->route.lifetime = usec;
+
+        TAKE_PTR(p);
+        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_(prefix64_free_or_set_invalidp) Prefix64 *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 = prefix64_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->prefix64.prefix = a.in6;
+        p->prefix64.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_(prefix64_free_or_set_invalidp) Prefix64 *p = NULL;
+        Network *network = ASSERT_PTR(userdata);
+        usec_t usec;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+
+        r = prefix64_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->prefix64.lifetime = usec;
 
         TAKE_PTR(p);
         return 0;
@@ -1311,7 +1507,7 @@ int config_parse_router_lifetime(
         return 0;
 }
 
-int config_parse_router_retransmit(
+int config_parse_router_uint32_msec_usec(
                 const char *unit,
                 const char *filename,
                 unsigned line,
@@ -1323,7 +1519,7 @@ int config_parse_router_retransmit(
                 void *data,
                 void *userdata) {
 
-        usec_t usec, *router_retransmit_usec = ASSERT_PTR(data);
+        usec_t usec, *router_usec = ASSERT_PTR(data);
         int r;
 
         assert(filename);
@@ -1332,7 +1528,7 @@ int config_parse_router_retransmit(
         assert(rvalue);
 
         if (isempty(rvalue)) {
-                *router_retransmit_usec = 0;
+                *router_usec = 0;
                 return 0;
         }
 
@@ -1344,13 +1540,13 @@ int config_parse_router_retransmit(
         }
 
         if (usec != USEC_INFINITY &&
-            DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) {
+            usec > RADV_MAX_UINT32_MSEC_USEC) {
                 log_syntax(unit, LOG_WARNING, filename, line, 0,
-                           "Invalid %s= must be in the range 0...%"PRIu32"Sec, ignoring: %s", lvalue, UINT32_MAX, rvalue);
+                           "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue);
                 return 0;
         }
 
-        *router_retransmit_usec = usec;
+        *router_usec = usec;
         return 0;
 }
 
@@ -1386,3 +1582,46 @@ int config_parse_router_preference(
 
         return 0;
 }
+
+int config_parse_router_home_agent_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) {
+
+        usec_t usec, *home_agent_lifetime_usec = ASSERT_PTR(data);
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                *home_agent_lifetime_usec = 0;
+                return 0;
+        }
+
+        r = parse_sec(rvalue, &usec);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        if (!timestamp_is_set(usec) ||
+            usec > RADV_HOME_AGENT_MAX_LIFETIME_USEC) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue);
+                return 0;
+        }
+
+        *home_agent_lifetime_usec = usec;
+        return 0;
+}