]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: ndisc - Allow to parse PREF64 prefix
authorSusant Sahani <ssahani@gmail.com>
Mon, 28 Aug 2023 18:12:39 +0000 (23:42 +0530)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 4 Sep 2023 14:41:02 +0000 (23:41 +0900)
14 files changed:
man/systemd.network.xml
src/libsystemd-network/meson.build
src/libsystemd-network/ndisc-protocol.c [new file with mode: 0644]
src/libsystemd-network/ndisc-protocol.h [new file with mode: 0644]
src/libsystemd-network/ndisc-router.c
src/libsystemd-network/radv-internal.h
src/libsystemd-network/sd-radv.c
src/network/networkd-json.c
src/network/networkd-link.h
src/network/networkd-ndisc.c
src/network/networkd-ndisc.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.h
src/systemd/sd-ndisc.h

index 5ece7b747cd973881931add450f1a549fda0ada6..c52b80f2852979b9dd789d126ec2a06835ba92de 100644 (file)
@@ -3122,6 +3122,17 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>UsePREF64=</varname></term>
+        <listitem>
+          <para>When true, the IPv6 PREF64 (or NAT64) prefixes received in the Router Advertisement will be recorded
+          and made available to client programs and displayed in the <command>networkctl</command> status output per-link.
+          See <ulink url="https://tools.ietf.org/html/rfc8781">RFC 8781</ulink>. Defaults to false.</para>
+
+          <xi:include href="version-info.xml" xpointer="v255"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>UseAutonomousPrefix=</varname></term>
         <listitem>
index 0b35eeec3488a0915f6409bcd6b40bd090e540ea..80762a72c07ff774c31939f059fb3233d2d06288 100644 (file)
@@ -13,6 +13,7 @@ sources = files(
         'icmp6-util.c',
         'lldp-neighbor.c',
         'lldp-network.c',
+        'ndisc-protocol.c',
         'ndisc-router.c',
         'network-common.c',
         'network-internal.c',
diff --git a/src/libsystemd-network/ndisc-protocol.c b/src/libsystemd-network/ndisc-protocol.c
new file mode 100644 (file)
index 0000000..fae4a58
--- /dev/null
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "ndisc-protocol.h"
+
+static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = {
+        [PREFIX_LENGTH_CODE_96] = 96,
+        [PREFIX_LENGTH_CODE_64] = 64,
+        [PREFIX_LENGTH_CODE_56] = 56,
+        [PREFIX_LENGTH_CODE_48] = 48,
+        [PREFIX_LENGTH_CODE_40] = 40,
+        [PREFIX_LENGTH_CODE_32] = 32,
+};
+
+int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret) {
+        plc &= PREF64_PLC_MASK;
+        if (plc >= _PREFIX_LENGTH_CODE_MAX)
+                return -EINVAL;
+
+        if (ret)
+                *ret = prefix_length_code_to_prefix_length[plc];
+        return 0;
+}
+
+int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) {
+        assert(ret);
+
+        for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++)
+                if (prefix_length_code_to_prefix_length[i] == prefixlen) {
+                        *ret = i;
+                        return 0;
+                }
+
+        return -EINVAL;
+}
diff --git a/src/libsystemd-network/ndisc-protocol.h b/src/libsystemd-network/ndisc-protocol.h
new file mode 100644 (file)
index 0000000..8e403e3
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "time-util.h"
+
+/* RFC 8781: PREF64 or (NAT64 prefix) */
+#define PREF64_SCALED_LIFETIME_MASK      0xfff8
+#define PREF64_PLC_MASK                  0x0007
+#define PREF64_MAX_LIFETIME_USEC         (65528 * USEC_PER_SEC)
+
+typedef enum PrefixLengthCode {
+        PREFIX_LENGTH_CODE_96,
+        PREFIX_LENGTH_CODE_64,
+        PREFIX_LENGTH_CODE_56,
+        PREFIX_LENGTH_CODE_48,
+        PREFIX_LENGTH_CODE_40,
+        PREFIX_LENGTH_CODE_32,
+        _PREFIX_LENGTH_CODE_MAX,
+        _PREFIX_LENGTH_CODE_INVALID = -EINVAL,
+} PrefixLengthCode;
+
+/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */
+struct nd_opt_prefix64_info {
+        uint8_t type;
+        uint8_t length;
+        uint16_t lifetime_and_plc;
+        uint8_t prefix[12];
+} __attribute__((__packed__));
+
+int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret);
+int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret);
index 86f8ca0364d9bfb28693f7214e88596caf769acb..fe8d26ebd4b90853cbed9e3abdabc060678a3930 100644 (file)
@@ -13,6 +13,7 @@
 #include "memory-util.h"
 #include "missing_network.h"
 #include "ndisc-internal.h"
+#include "ndisc-protocol.h"
 #include "ndisc-router.h"
 #include "strv.h"
 
@@ -69,6 +70,21 @@ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size)
         return 0;
 }
 
+static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t length) {
+        uint16_t lifetime_and_plc;
+
+        assert(p);
+
+        if (length != sizeof(struct nd_opt_prefix64_info))
+                return false;
+
+        lifetime_and_plc = be16toh(p->lifetime_and_plc);
+        if (pref64_plc_to_prefix_length(lifetime_and_plc, NULL) < 0)
+                return false;
+
+        return true;
+}
+
 int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
         struct nd_router_advert *a;
         const uint8_t *p;
@@ -205,7 +221,12 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
                                                        "DNSSL option has invalid size.");
 
                         break;
-                }
+                case SD_NDISC_OPTION_PREF64: {
+                        if (!pref64_option_verify((struct nd_opt_prefix64_info *) p, length))
+                                log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+                                                "PREF64 prefix has invalid prefix length.");
+                        break;
+                }}
 
                 p += length, left -= length;
         }
@@ -766,3 +787,94 @@ int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret
 
         return 0;
 }
+
+static int get_pref64_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix64_info **ret) {
+        struct nd_opt_prefix64_info *ri;
+        size_t length;
+        int r;
+
+        assert(rt);
+        assert(ret);
+
+        r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREF64);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EMEDIUMTYPE;
+
+        length = NDISC_ROUTER_OPTION_LENGTH(rt);
+        if (length != sizeof(struct nd_opt_prefix64_info))
+                return -EBADMSG;
+
+        ri = (struct nd_opt_prefix64_info *) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
+        if (!pref64_option_verify(ri, length))
+                return -EBADMSG;
+
+        *ret = ri;
+        return 0;
+}
+
+int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+        struct nd_opt_prefix64_info *pi;
+        struct in6_addr a = {};
+        unsigned prefixlen;
+        int r;
+
+        assert_return(rt, -EINVAL);
+        assert_return(ret_addr, -EINVAL);
+
+        r = get_pref64_prefix_info(rt, &pi);
+        if (r < 0)
+                return r;
+
+        r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefixlen);
+        if (r < 0)
+                return r;
+
+        memcpy(&a, pi->prefix, sizeof(pi->prefix));
+        in6_addr_mask(&a, prefixlen);
+        /* extra safety check for refusing malformed prefix. */
+        if (memcmp(&a, pi->prefix, sizeof(pi->prefix)) != 0)
+                return -EBADMSG;
+
+        *ret_addr = a;
+        return 0;
+}
+
+int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
+        struct nd_opt_prefix64_info *pi;
+        uint16_t lifetime_prefix_len;
+        uint8_t prefix_len;
+        int r;
+
+        assert_return(rt, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        r = get_pref64_prefix_info(rt, &pi);
+        if (r < 0)
+              return r;
+
+        lifetime_prefix_len = be16toh(pi->lifetime_and_plc);
+        pref64_plc_to_prefix_length(lifetime_prefix_len, &prefix_len);
+
+        *ret = prefix_len;
+        return 0;
+}
+
+int sd_ndisc_router_prefix64_get_lifetime_sec(sd_ndisc_router *rt, uint16_t *ret) {
+        struct nd_opt_prefix64_info *pi;
+        uint16_t lifetime_prefix_len;
+        int r;
+
+        assert_return(rt, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        r = get_pref64_prefix_info(rt, &pi);
+        if (r < 0)
+                return r;
+
+        lifetime_prefix_len = be16toh(pi->lifetime_and_plc);
+
+        *ret = lifetime_prefix_len & PREF64_SCALED_LIFETIME_MASK;
+        return 0;
+}
index 0667376d8867866d827acd425afbe18a5439a3aa..af0be7a5b7c0330b1a819a77a3817a02d6d0ff77 100644 (file)
@@ -10,6 +10,7 @@
 #include "sd-radv.h"
 
 #include "list.h"
+#include "ndisc-protocol.h"
 #include "network-common.h"
 #include "sparse-endian.h"
 #include "time-util.h"
@@ -190,23 +191,10 @@ 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 nd_opt_prefix64_info opt;
 
         struct in6_addr in6_addr;
         uint8_t prefixlen;
index f1256f4725b6168b969776feb5c30ee089ec137a..c99b2b2313c25aca49ebb66594a6e9f209fefc79 100644 (file)
@@ -1135,33 +1135,14 @@ int sd_radv_pref64_prefix_set_prefix(
 
         uint16_t pref64_lifetime;
         uint8_t prefixlen_code;
+        int r;
 
         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;
-        }
+        r = pref64_prefix_length_to_plc(prefixlen, &prefixlen_code);
+        if (r < 0)
+                return log_radv_errno(NULL, r,  "Unsupported PREF64 prefix length %u. Valid lengths are 32, 40, 48, 56, 64 and 96", prefixlen);
 
         if (lifetime_usec == USEC_INFINITY || DIV_ROUND_UP(lifetime_usec, 8 * USEC_PER_SEC) >= UINT64_C(1) << 13)
                 return -EINVAL;
index a9e33ac627ec44b2069cd2d1994bf67bb3245be3..c6756a87389afd96e17003756c84c63b4b3a9b36 100644 (file)
@@ -921,6 +921,34 @@ static int captive_portal_append_json(Link *link, JsonVariant **v) {
         return json_variant_merge_objectb(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("CaptivePortal", captive_portal)));
 }
 
+static int pref64_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL, *w = NULL;
+        NDiscPREF64 *i;
+        int r;
+
+        assert(link);
+        assert(v);
+
+        if (!link->network || !link->network->ipv6_accept_ra_use_pref64)
+                return 0;
+
+        SET_FOREACH(i, link->ndisc_pref64) {
+                r = json_build(&array, JSON_BUILD_OBJECT(
+                                               JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("Prefix", &i->prefix),
+                                               JSON_BUILD_PAIR_UNSIGNED("PrefixLength", i->prefix_len),
+                                               JSON_BUILD_PAIR_FINITE_USEC("LifetimeUSec", i->lifetime_usec),
+                                               JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("ConfigProvider", &i->router)));
+                if (r < 0)
+                        return r;
+        }
+
+        r = json_append_one(&w, "PREF64", array);
+        if (r < 0)
+                return r;
+
+        return json_append_one(v, "NDisc", w);
+}
+
 static int dhcp_server_offered_leases_append_json(Link *link, JsonVariant **v) {
         _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         DHCPLease *lease;
@@ -1145,6 +1173,10 @@ int link_build_json(Link *link, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
+        r = pref64_append_json(link, &v);
+        if (r < 0)
+                return r;
+
         r = addresses_append_json(link->addresses, &v);
         if (r < 0)
                 return r;
index e46e5f0c617cfa8f1e441fa383fc2cde8c7aa622..8ced02f3c1aeb0872b2c4a8815bde18ffe53bf04 100644 (file)
@@ -157,6 +157,7 @@ typedef struct Link {
         Set *ndisc_rdnss;
         Set *ndisc_dnssl;
         Set *ndisc_captive_portals;
+        Set *ndisc_pref64;
         unsigned ndisc_messages;
         bool ndisc_configured:1;
 
index 8e66d83fbb1749c249c55b806ea72600253010fd..e8606506916e0697c535aca80b66bd546b2993ae 100644 (file)
@@ -965,6 +965,113 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt)
         return 1;
 }
 
+static void ndisc_pref64_hash_func(const NDiscPREF64 *x, struct siphash *state) {
+        assert(x);
+
+        siphash24_compress(&x->prefix_len, sizeof(x->prefix_len), state);
+        siphash24_compress(&x->prefix, sizeof(x->prefix), state);
+}
+
+static int ndisc_pref64_compare_func(const NDiscPREF64 *a, const NDiscPREF64 *b) {
+        int r;
+
+        assert(a);
+        assert(b);
+
+        r = CMP(a->prefix_len, b->prefix_len);
+        if (r != 0)
+                return r;
+
+        return memcmp(&a->prefix, &b->prefix, sizeof(a->prefix));
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+                ndisc_pref64_hash_ops,
+                NDiscPREF64,
+                ndisc_pref64_hash_func,
+                ndisc_pref64_compare_func,
+                mfree);
+
+static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) {
+        _cleanup_free_ NDiscPREF64 *new_entry = NULL;
+        usec_t lifetime_usec, timestamp_usec;
+        struct in6_addr a, router;
+        uint16_t lifetime_sec;
+        unsigned prefix_len;
+        NDiscPREF64 *exist;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(rt);
+
+        if (!link->network->ipv6_accept_ra_use_pref64)
+                return 0;
+
+        r = sd_ndisc_router_get_address(rt, &router);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to get router address from RA: %m");
+
+        r = sd_ndisc_router_prefix64_get_prefix(rt, &a);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to get pref64 prefix: %m");
+
+        r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefix_len);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to get pref64 prefix length: %m");
+
+        r = sd_ndisc_router_prefix64_get_lifetime_sec(rt, &lifetime_sec);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to get pref64 prefix lifetime: %m");
+
+        r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, &timestamp_usec);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+
+        lifetime_usec = sec16_to_usec(lifetime_sec, timestamp_usec);
+
+        if (lifetime_usec == 0) {
+                free(set_remove(link->ndisc_pref64,
+                                &(NDiscPREF64) {
+                                        .prefix = a,
+                                        .prefix_len = prefix_len
+                                }));
+                return 0;
+        }
+
+        exist = set_get(link->ndisc_pref64,
+                        &(NDiscPREF64) {
+                                .prefix = a,
+                                .prefix_len = prefix_len
+                });
+        if (exist) {
+                /* update existing entry */
+                exist->router = router;
+                exist->lifetime_usec = lifetime_usec;
+                return 0;
+        }
+
+        new_entry = new(NDiscPREF64, 1);
+        if (!new_entry)
+                return log_oom();
+
+        *new_entry = (NDiscPREF64) {
+                .router = router,
+                .lifetime_usec = lifetime_usec,
+                .prefix = a,
+                .prefix_len = prefix_len,
+        };
+
+        r = set_ensure_put(&link->ndisc_pref64, &ndisc_pref64_hash_ops, new_entry);
+        if (r < 0)
+                return log_oom();
+
+        assert(r > 0);
+        TAKE_PTR(new_entry);
+
+        return 0;
+}
+
 static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
         size_t n_captive_portal = 0;
         int r;
@@ -1013,6 +1120,9 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
                         if (r > 0)
                                 n_captive_portal++;
                         break;
+                case SD_NDISC_OPTION_PREF64:
+                        r = ndisc_router_process_pref64(link, rt);
+                        break;
                 }
                 if (r < 0 && r != -EBADMSG)
                         return r;
@@ -1413,6 +1523,7 @@ void ndisc_flush(Link *link) {
         link->ndisc_rdnss = set_free(link->ndisc_rdnss);
         link->ndisc_dnssl = set_free(link->ndisc_dnssl);
         link->ndisc_captive_portals = set_free(link->ndisc_captive_portals);
+        link->ndisc_pref64 = set_free(link->ndisc_pref64);
 }
 
 static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {
index 267f7d4a02057242d757b919d8fe6df1884dbe97..a463f42b52294ff3dca079d869d53f58793e130c 100644 (file)
@@ -39,6 +39,15 @@ typedef struct NDiscCaptivePortal {
         char *captive_portal;
 } NDiscCaptivePortal;
 
+typedef struct NDiscPREF64 {
+        struct in6_addr router;
+        /* This is an absolute point in time, and NOT a timespan/duration.
+         * Must be specified with CLOCK_BOOTTIME. */
+        usec_t lifetime_usec;
+        uint8_t prefix_len;
+        struct in6_addr prefix;
+} NDiscPREF64;
+
 static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) {
         return ((char*) n) + ALIGN(sizeof(NDiscDNSSL));
 }
index fefc84a7d94163e5930589a5c8330e7b26f7c545..bbb87e00bd22fc4c68f66e2776a7b9eead11797c 100644 (file)
@@ -285,6 +285,7 @@ IPv6AcceptRA.UseGateway,                     config_parse_bool,
 IPv6AcceptRA.UseRoutePrefix,                 config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_route_prefix)
 IPv6AcceptRA.UseAutonomousPrefix,            config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
 IPv6AcceptRA.UseOnLinkPrefix,                config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_onlink_prefix)
+IPv6AcceptRA.UsePREF64,                      config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_pref64)
 IPv6AcceptRA.UseDNS,                         config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_dns)
 IPv6AcceptRA.UseDomains,                     config_parse_ipv6_accept_ra_use_domains,                  0,                             offsetof(Network, ipv6_accept_ra_use_domains)
 IPv6AcceptRA.UseMTU,                         config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_mtu)
index a7d1f9cd28088e46b5178a8823f87687d6e21f83..3e1110e6d7d8e5fd2678bb6cebdcc99d78a75e16 100644 (file)
@@ -333,6 +333,7 @@ struct Network {
         bool ipv6_accept_ra_use_icmp6_ratelimit;
         bool ipv6_accept_ra_quickack;
         bool ipv6_accept_ra_use_captive_portal;
+        bool ipv6_accept_ra_use_pref64;
         bool active_slave;
         bool primary_slave;
         DHCPUseDomains ipv6_accept_ra_use_domains;
index be702f68a52c3c947540f3380193b4a41fe38faa..89b6413ef9bfec3164c6caa986010b1604c5d6bd 100644 (file)
@@ -42,7 +42,8 @@ enum {
         SD_NDISC_OPTION_RDNSS              = 25,
         SD_NDISC_OPTION_FLAGS_EXTENSION    = 26,
         SD_NDISC_OPTION_DNSSL              = 31,
-        SD_NDISC_OPTION_CAPTIVE_PORTAL     = 37
+        SD_NDISC_OPTION_CAPTIVE_PORTAL     = 37,
+        SD_NDISC_OPTION_PREF64             = 38
 };
 
 /* Route preference, RFC 4191, Section 2.1 */
@@ -127,6 +128,11 @@ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
 /* Specific option access: SD_NDISC_OPTION_CAPTIVE_PORTAL */
 int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **uri, size_t *size);
 
+/* Specific option access: SD_NDISC_OPTION_PREF64 */
+int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret);
+int sd_ndisc_router_prefix64_get_lifetime_sec(sd_ndisc_router *rt, uint16_t *ret);
+
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref);
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref);