about summary refs log tree commit diff
path: root/src/core/ndisc
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/ndisc')
-rw-r--r--src/core/ndisc/nm-fake-ndisc.c90
-rw-r--r--src/core/ndisc/nm-fake-ndisc.h18
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.c112
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.h13
-rw-r--r--src/core/ndisc/nm-ndisc-private.h2
-rw-r--r--src/core/ndisc/nm-ndisc.c587
-rw-r--r--src/core/ndisc/nm-ndisc.h66
-rw-r--r--src/core/ndisc/tests/test-ndisc-fake.c138
-rw-r--r--src/core/ndisc/tests/test-ndisc-linux.c55
9 files changed, 516 insertions, 565 deletions
diff --git a/src/core/ndisc/nm-fake-ndisc.c b/src/core/ndisc/nm-fake-ndisc.c
index ffe07ab6..5aea273e 100644
--- a/src/core/ndisc/nm-fake-ndisc.c
+++ b/src/core/ndisc/nm-fake-ndisc.c
@@ -10,6 +10,7 @@
 #include <arpa/inet.h>
 
 #include "nm-ndisc-private.h"
+#include "nm-l3cfg.h"
 
 #define _NMLOG_PREFIX_NAME "ndisc-fake"
 
@@ -20,10 +21,10 @@ typedef struct {
     guint when;
 
     NMNDiscDHCPLevel dhcp_level;
-    GArray *         gateways;
-    GArray *         prefixes;
-    GArray *         dns_servers;
-    GArray *         dns_domains;
+    GArray          *gateways;
+    GArray          *prefixes;
+    GArray          *dns_servers;
+    GArray          *dns_domains;
     int              hop_limit;
     guint32          mtu;
 } FakeRa;
@@ -97,7 +98,7 @@ find_ra(GSList *ras, guint id)
 }
 
 guint
-nm_fake_ndisc_add_ra(NMFakeNDisc *    self,
+nm_fake_ndisc_add_ra(NMFakeNDisc     *self,
                      guint            seconds_after_previous,
                      NMNDiscDHCPLevel dhcp_level,
                      int              hop_limit,
@@ -105,7 +106,7 @@ nm_fake_ndisc_add_ra(NMFakeNDisc *    self,
 {
     NMFakeNDiscPrivate *priv    = NM_FAKE_NDISC_GET_PRIVATE(self);
     static guint        counter = 1;
-    FakeRa *            ra;
+    FakeRa             *ra;
 
     ra              = g_malloc0(sizeof(*ra));
     ra->id          = counter++;
@@ -124,15 +125,15 @@ nm_fake_ndisc_add_ra(NMFakeNDisc *    self,
 }
 
 void
-nm_fake_ndisc_add_gateway(NMFakeNDisc *      self,
+nm_fake_ndisc_add_gateway(NMFakeNDisc       *self,
                           guint              ra_id,
-                          const char *       addr,
+                          const char        *addr,
                           gint64             expiry_msec,
                           NMIcmpv6RouterPref preference)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
-    FakeRa *            ra   = find_ra(priv->ras, ra_id);
-    NMNDiscGateway *    gw;
+    FakeRa             *ra   = find_ra(priv->ras, ra_id);
+    NMNDiscGateway     *gw;
 
     g_assert(ra);
 
@@ -144,18 +145,18 @@ nm_fake_ndisc_add_gateway(NMFakeNDisc *      self,
 }
 
 void
-nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
+nm_fake_ndisc_add_prefix(NMFakeNDisc       *self,
                          guint              ra_id,
-                         const char *       network,
+                         const char        *network,
                          guint              plen,
-                         const char *       gateway,
+                         const char        *gateway,
                          gint64             expiry_msec,
                          gint64             expiry_preferred_msec,
                          NMIcmpv6RouterPref preference)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
-    FakeRa *            ra   = find_ra(priv->ras, ra_id);
-    FakePrefix *        prefix;
+    FakeRa             *ra   = find_ra(priv->ras, ra_id);
+    FakePrefix         *prefix;
 
     g_assert(ra);
 
@@ -175,12 +176,12 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
 void
 nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
                              guint        ra_id,
-                             const char * address,
+                             const char  *address,
                              gint64       expiry_msec)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
-    FakeRa *            ra   = find_ra(priv->ras, ra_id);
-    NMNDiscDNSServer *  dns;
+    FakeRa             *ra   = find_ra(priv->ras, ra_id);
+    NMNDiscDNSServer   *dns;
 
     g_assert(ra);
 
@@ -195,8 +196,8 @@ void
 nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, guint ra_id, const char *domain, gint64 expiry_msec)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
-    FakeRa *            ra   = find_ra(priv->ras, ra_id);
-    NMNDiscDNSDomain *  dns;
+    FakeRa             *ra   = find_ra(priv->ras, ra_id);
+    NMNDiscDNSDomain   *dns;
 
     g_assert(ra);
 
@@ -225,11 +226,11 @@ send_rs(NMNDisc *ndisc, GError **error)
 static gboolean
 receive_ra(gpointer user_data)
 {
-    NMFakeNDisc *        self     = user_data;
-    NMFakeNDiscPrivate * priv     = NM_FAKE_NDISC_GET_PRIVATE(self);
-    NMNDisc *            ndisc    = NM_NDISC(self);
+    NMFakeNDisc         *self     = user_data;
+    NMFakeNDiscPrivate  *priv     = NM_FAKE_NDISC_GET_PRIVATE(self);
+    NMNDisc             *ndisc    = NM_NDISC(self);
     NMNDiscDataInternal *rdata    = ndisc->rdata;
-    FakeRa *             ra       = priv->ras->data;
+    FakeRa              *ra       = priv->ras->data;
     NMNDiscConfigMap     changed  = 0;
     const gint64         now_msec = nm_utils_get_monotonic_timestamp_msec();
     guint                i;
@@ -253,7 +254,7 @@ receive_ra(gpointer user_data)
     }
 
     for (i = 0; i < ra->prefixes->len; i++) {
-        FakePrefix *       item  = &g_array_index(ra->prefixes, FakePrefix, i);
+        FakePrefix        *item  = &g_array_index(ra->prefixes, FakePrefix, i);
         const NMNDiscRoute route = {
             .network     = item->network,
             .plen        = item->plen,
@@ -322,7 +323,7 @@ static void
 start(NMNDisc *ndisc)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(ndisc);
-    FakeRa *            ra;
+    FakeRa             *ra;
 
     /* Queue up the first fake RA */
     g_assert(priv->ras);
@@ -354,28 +355,23 @@ nm_fake_ndisc_init(NMFakeNDisc *fake_ndisc)
 {}
 
 NMNDisc *
-nm_fake_ndisc_new(int ifindex, const char *ifname)
+nm_fake_ndisc_new(NML3Cfg *l3cfg)
 {
-    return g_object_new(NM_TYPE_FAKE_NDISC,
-                        NM_NDISC_IFINDEX,
-                        ifindex,
-                        NM_NDISC_IFNAME,
-                        ifname,
-                        NM_NDISC_NODE_TYPE,
-                        (int) NM_NDISC_NODE_TYPE_HOST,
-                        NM_NDISC_STABLE_TYPE,
-                        (int) NM_UTILS_STABLE_TYPE_UUID,
-                        NM_NDISC_NETWORK_ID,
-                        "fake",
-                        NM_NDISC_MAX_ADDRESSES,
-                        NM_NDISC_MAX_ADDRESSES_DEFAULT,
-                        NM_NDISC_ROUTER_SOLICITATIONS,
-                        NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT,
-                        NM_NDISC_ROUTER_SOLICITATION_INTERVAL,
-                        NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL,
-                        NM_NDISC_RA_TIMEOUT,
-                        30u,
-                        NULL);
+    const NMNDiscConfig config = {
+        .l3cfg                        = g_object_ref(NM_L3CFG(l3cfg)),
+        .ifname                       = nm_l3cfg_get_ifname(l3cfg, TRUE),
+        .node_type                    = NM_NDISC_NODE_TYPE_HOST,
+        .stable_type                  = NM_UTILS_STABLE_TYPE_UUID,
+        .network_id                   = "fake",
+        .max_addresses                = NM_NDISC_MAX_ADDRESSES_DEFAULT,
+        .router_solicitations         = NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT,
+        .router_solicitation_interval = NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL,
+        .ra_timeout                   = 30u,
+        .addr_gen_mode                = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+        .ip6_privacy                  = NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+    };
+
+    return g_object_new(NM_TYPE_FAKE_NDISC, NM_NDISC_CONFIG, &config, NULL);
 }
 
 static void
diff --git a/src/core/ndisc/nm-fake-ndisc.h b/src/core/ndisc/nm-fake-ndisc.h
index 677f15fc..91c2c21c 100644
--- a/src/core/ndisc/nm-fake-ndisc.h
+++ b/src/core/ndisc/nm-fake-ndisc.h
@@ -24,37 +24,37 @@ typedef struct _NMFakeRNDiscClass NMFakeNDiscClass;
 
 GType nm_fake_ndisc_get_type(void);
 
-NMNDisc *nm_fake_ndisc_new(int ifindex, const char *ifname);
+NMNDisc *nm_fake_ndisc_new(NML3Cfg *l3cfg);
 
-guint nm_fake_ndisc_add_ra(NMFakeNDisc *    self,
+guint nm_fake_ndisc_add_ra(NMFakeNDisc     *self,
                            guint            seconds,
                            NMNDiscDHCPLevel dhcp_level,
                            int              hop_limit,
                            guint32          mtu);
 
-void nm_fake_ndisc_add_gateway(NMFakeNDisc *      self,
+void nm_fake_ndisc_add_gateway(NMFakeNDisc       *self,
                                guint              ra_id,
-                               const char *       addr,
+                               const char        *addr,
                                gint64             expiry_msec,
                                NMIcmpv6RouterPref preference);
 
-void nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
+void nm_fake_ndisc_add_prefix(NMFakeNDisc       *self,
                               guint              ra_id,
-                              const char *       network,
+                              const char        *network,
                               guint              plen,
-                              const char *       gateway,
+                              const char        *gateway,
                               gint64             expiry_msec,
                               gint64             expiry_preferred_msec,
                               NMIcmpv6RouterPref preference);
 
 void nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
                                   guint        ra_id,
-                                  const char * address,
+                                  const char  *address,
                                   gint64       expiry_msec);
 
 void nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self,
                                   guint        ra_id,
-                                  const char * domain,
+                                  const char  *domain,
                                   gint64       expiry_msec);
 
 void nm_fake_ndisc_emit_new_ras(NMFakeNDisc *self);
diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c
index 552b0e4b..313756a6 100644
--- a/src/core/ndisc/nm-lndp-ndisc.c
+++ b/src/core/ndisc/nm-lndp-ndisc.c
@@ -9,16 +9,16 @@
 
 #include <arpa/inet.h>
 #include <netinet/icmp6.h>
-/* stdarg.h included because of a bug in ndp.h */
 #include <stdarg.h>
 #include <ndp.h>
 
-#include "libnm-glib-aux/nm-str-buf.h"
-#include "libnm-systemd-shared/nm-sd-utils-shared.h"
-#include "nm-ndisc-private.h"
 #include "NetworkManagerUtils.h"
+#include "libnm-glib-aux/nm-str-buf.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-netns.h"
+#include "libnm-systemd-shared/nm-sd-utils-shared.h"
+#include "nm-l3cfg.h"
+#include "nm-ndisc-private.h"
 
 #define _NMLOG_PREFIX_NAME "ndisc-lndp"
 
@@ -26,7 +26,7 @@
 
 typedef struct {
     struct ndp *ndp;
-    GSource *   event_source;
+    GSource    *event_source;
 } NMLndpNDiscPrivate;
 
 /*****************************************************************************/
@@ -53,7 +53,7 @@ static gboolean
 send_rs(NMNDisc *ndisc, GError **error)
 {
     NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
-    struct ndp_msg *    msg;
+    struct ndp_msg     *msg;
     int                 errsv;
 
     errsv = ndp_msg_new(&msg, NDP_MSG_RS);
@@ -112,10 +112,10 @@ _route_preference_coerce(enum ndp_route_preference pref)
 static int
 receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 {
-    NMNDisc *            ndisc   = (NMNDisc *) user_data;
+    NMNDisc             *ndisc   = (NMNDisc *) user_data;
     NMNDiscDataInternal *rdata   = ndisc->rdata;
     NMNDiscConfigMap     changed = 0;
-    struct ndp_msgra *   msgra   = ndp_msgra(msg);
+    struct ndp_msgra    *msgra   = ndp_msgra(msg);
     struct in6_addr      gateway_addr;
     const gint64         now_msec = nm_utils_get_monotonic_timestamp_msec();
     int                  offset;
@@ -386,11 +386,11 @@ G_STATIC_ASSERT(sizeof(NMLndpDnsslOption) == 8u);
 static gboolean
 send_ra(NMNDisc *ndisc, GError **error)
 {
-    NMLndpNDiscPrivate *     priv  = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
-    NMNDiscDataInternal *    rdata = ndisc->rdata;
+    NMLndpNDiscPrivate      *priv  = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscDataInternal     *rdata = ndisc->rdata;
     int                      errsv;
-    struct in6_addr *        addr;
-    struct ndp_msg *         msg;
+    struct in6_addr         *addr;
+    struct ndp_msg          *msg;
     guint                    i;
     nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE);
 
@@ -417,7 +417,7 @@ send_ra(NMNDisc *ndisc, GError **error)
     /* The device let us know about all addresses that the device got
      * whose prefixes are suitable for delegating. Let's announce them. */
     for (i = 0; i < rdata->addresses->len; i++) {
-        const NMNDiscAddress *     address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
+        const NMNDiscAddress      *address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
         struct nd_opt_prefix_info *prefix;
 
         prefix = _ndp_msg_add_option(msg, sizeof(*prefix));
@@ -573,9 +573,9 @@ receive_rs(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 static gboolean
 event_ready(int fd, GIOCondition condition, gpointer user_data)
 {
-    gs_unref_object NMNDisc *ndisc    = g_object_ref(NM_NDISC(user_data));
+    gs_unref_object NMNDisc    *ndisc = g_object_ref(NM_NDISC(user_data));
     nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMLndpNDiscPrivate *        priv  = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
+    NMLndpNDiscPrivate         *priv  = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
 
     _LOGD("processing libndp events");
 
@@ -669,72 +669,39 @@ nm_lndp_ndisc_init(NMLndpNDisc *lndp_ndisc)
 {}
 
 NMNDisc *
-nm_lndp_ndisc_new(NMPlatform *                  platform,
-                  int                           ifindex,
-                  const char *                  ifname,
-                  NMUtilsStableType             stable_type,
-                  const char *                  network_id,
-                  NMSettingIP6ConfigAddrGenMode addr_gen_mode,
-                  NMNDiscNodeType               node_type,
-                  int                           max_addresses,
-                  int                           router_solicitations,
-                  int                           router_solicitation_interval,
-                  guint32                       ra_timeout,
-                  GError **                     error)
+nm_lndp_ndisc_new(const NMNDiscConfig *config)
 {
     nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMNDisc *                   ndisc;
-    NMLndpNDiscPrivate *        priv;
+    gs_unref_object NMNDisc    *ndisc = NULL;
+    NMLndpNDiscPrivate         *priv;
     int                         errsv;
 
-    g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL);
-    g_return_val_if_fail(!error || !*error, NULL);
-    g_return_val_if_fail(network_id, NULL);
+    g_return_val_if_fail(config, NULL);
+    g_return_val_if_fail(NM_IS_L3CFG(config->l3cfg), NULL);
+    g_return_val_if_fail(config->network_id, NULL);
 
-    if (!nm_platform_netns_push(platform, &netns))
-        return NULL;
+    if (!nm_platform_netns_push(nm_l3cfg_get_platform(config->l3cfg), &netns)) {
+        /* The inability to change the name space is also considered
+         * a fatal error. We have a FD open to the file descriptor, and
+         * it's unclear how to handle (or recover from) a failure to setns(). */
+        g_return_val_if_reached(NULL);
+    }
 
-    ndisc = g_object_new(NM_TYPE_LNDP_NDISC,
-                         NM_NDISC_PLATFORM,
-                         platform,
-                         NM_NDISC_STABLE_TYPE,
-                         (int) stable_type,
-                         NM_NDISC_IFINDEX,
-                         ifindex,
-                         NM_NDISC_IFNAME,
-                         ifname,
-                         NM_NDISC_NETWORK_ID,
-                         network_id,
-                         NM_NDISC_ADDR_GEN_MODE,
-                         (int) addr_gen_mode,
-                         NM_NDISC_NODE_TYPE,
-                         (int) node_type,
-                         NM_NDISC_MAX_ADDRESSES,
-                         max_addresses,
-                         NM_NDISC_ROUTER_SOLICITATIONS,
-                         router_solicitations,
-                         NM_NDISC_ROUTER_SOLICITATION_INTERVAL,
-                         router_solicitation_interval,
-                         NM_NDISC_RA_TIMEOUT,
-                         (guint) ra_timeout,
-                         NULL);
+    ndisc = g_object_new(NM_TYPE_LNDP_NDISC, NM_NDISC_CONFIG, config, NULL);
 
     priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
 
     errsv = ndp_open(&priv->ndp);
 
     if (errsv != 0) {
-        errsv = nm_errno_native(errsv);
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "failure creating libndp socket: %s (%d)",
-                    nm_strerror_native(errsv),
-                    errsv);
-        g_object_unref(ndisc);
-        return NULL;
+        /* This is serious. It might be ENOMEM or the inability to open (or modify)
+         * a file descriptor. In all cases there is not much reason trying to recover
+         * from that. File descriptors are a basic resource, that we just require (just
+         * like memory). */
+        g_return_val_if_reached(NULL);
     }
-    return ndisc;
+
+    return g_steal_pointer(&ndisc);
 }
 
 static void
@@ -754,8 +721,9 @@ nm_lndp_ndisc_class_init(NMLndpNDiscClass *klass)
     NMNDiscClass *ndisc_class  = NM_NDISC_CLASS(klass);
 
     object_class->dispose = dispose;
-    ndisc_class->start    = start;
-    ndisc_class->stop     = stop;
-    ndisc_class->send_rs  = send_rs;
-    ndisc_class->send_ra  = send_ra;
+
+    ndisc_class->start   = start;
+    ndisc_class->stop    = stop;
+    ndisc_class->send_rs = send_rs;
+    ndisc_class->send_ra = send_ra;
 }
diff --git a/src/core/ndisc/nm-lndp-ndisc.h b/src/core/ndisc/nm-lndp-ndisc.h
index 151f4bf7..711a95e1 100644
--- a/src/core/ndisc/nm-lndp-ndisc.h
+++ b/src/core/ndisc/nm-lndp-ndisc.h
@@ -23,17 +23,6 @@ typedef struct _NMLndpNDiscClass NMLndpNDiscClass;
 
 GType nm_lndp_ndisc_get_type(void);
 
-NMNDisc *nm_lndp_ndisc_new(NMPlatform *                  platform,
-                           int                           ifindex,
-                           const char *                  ifname,
-                           NMUtilsStableType             stable_type,
-                           const char *                  network_id,
-                           NMSettingIP6ConfigAddrGenMode addr_gen_mode,
-                           NMNDiscNodeType               node_type,
-                           int                           max_addresses,
-                           int                           router_solicitations,
-                           int                           router_solicitation_interval,
-                           guint32                       ra_timeout,
-                           GError **                     error);
+NMNDisc *nm_lndp_ndisc_new(const NMNDiscConfig *config);
 
 #endif /* __NETWORKMANAGER_LNDP_NDISC_H__ */
diff --git a/src/core/ndisc/nm-ndisc-private.h b/src/core/ndisc/nm-ndisc-private.h
index 04739124..1479e566 100644
--- a/src/core/ndisc/nm-ndisc-private.h
+++ b/src/core/ndisc/nm-ndisc-private.h
@@ -45,7 +45,7 @@ gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_ite
         if (nm_logging_enabled(__level, __domain)) {                                  \
             NMNDisc *const __self = (self);                                           \
             char           __prefix[64];                                              \
-            const char *   __ifname = __self ? nm_ndisc_get_ifname(__self) : NULL;    \
+            const char    *__ifname = __self ? nm_ndisc_get_ifname(__self) : NULL;    \
                                                                                       \
             _nm_log(__level,                                                          \
                     __domain,                                                         \
diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c
index d7b1a2e1..969eacfa 100644
--- a/src/core/ndisc/nm-ndisc.c
+++ b/src/core/ndisc/nm-ndisc.c
@@ -7,16 +7,17 @@
 
 #include "nm-ndisc.h"
 
-#include <stdlib.h>
 #include <arpa/inet.h>
+#include <stdlib.h>
 
-#include "nm-setting-ip6-config.h"
-
-#include "nm-ndisc-private.h"
-#include "nm-utils.h"
+#include "libnm-platform/nm-platform-utils.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-netns.h"
 #include "nm-l3-config-data.h"
+#include "nm-l3cfg.h"
+#include "nm-ndisc-private.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-utils.h"
 
 #define _NMLOG_PREFIX_NAME "ndisc"
 
@@ -38,6 +39,8 @@ struct _NMNDiscPrivate {
     /* this *must* be the first field. */
     NMNDiscDataInternal rdata;
 
+    const NML3ConfigData *l3cd;
+
     char *last_error;
 
     GSource *ra_timeout_source;
@@ -54,38 +57,27 @@ struct _NMNDiscPrivate {
     GSource *timeout_expire_source;
 
     NMUtilsIPv6IfaceId iid;
+    gboolean           iid_is_token;
 
-    /* immutable values: */
-    int                           ifindex;
-    char *                        ifname;
-    char *                        network_id;
-    guint                         max_addresses;
-    NMSettingIP6ConfigAddrGenMode addr_gen_mode;
-    NMUtilsStableType             stable_type;
-    guint32                       ra_timeout;
-    gint32                        router_solicitations;
-    gint32                        router_solicitation_interval;
-    NMNDiscNodeType               node_type;
-
-    NMPlatform *platform;
-    NMPNetns *  netns;
+    /* immutable values from here on: */
+
+    union {
+        const NMNDiscConfig config;
+        NMNDiscConfig       config_;
+    };
+
+    NMPNetns *netns;
 };
 
 typedef struct _NMNDiscPrivate NMNDiscPrivate;
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM,
-                                  PROP_IFINDEX,
-                                  PROP_IFNAME,
-                                  PROP_STABLE_TYPE,
-                                  PROP_NETWORK_ID,
-                                  PROP_ADDR_GEN_MODE,
-                                  PROP_MAX_ADDRESSES,
-                                  PROP_RA_TIMEOUT,
-                                  PROP_ROUTER_SOLICITATIONS,
-                                  PROP_ROUTER_SOLICITATION_INTERVAL,
-                                  PROP_NODE_TYPE, );
+NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_CONFIG, );
 
-enum { CONFIG_RECEIVED, RA_TIMEOUT_SIGNAL, LAST_SIGNAL };
+enum {
+    CONFIG_RECEIVED,
+    RA_TIMEOUT_SIGNAL,
+    LAST_SIGNAL,
+};
 
 static guint signals[LAST_SIGNAL] = {0};
 
@@ -111,18 +103,14 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_ndisc_dhcp_level_to_string,
 /*****************************************************************************/
 
 NML3ConfigData *
-nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
+nm_ndisc_data_to_l3cd(NMDedupMultiIndex        *multi_idx,
                       int                       ifindex,
-                      const NMNDiscData *       rdata,
+                      const NMNDiscData        *rdata,
                       NMSettingIP6ConfigPrivacy ip6_privacy,
-                      guint32                   route_table,
-                      guint32                   route_metric,
-                      gboolean                  kernel_support_rta_pref,
-                      gboolean                  kernel_support_extended_ifa_flags)
+                      NMUtilsIPv6IfaceId       *token)
 {
     nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
     guint32                                 ifa_flags;
-    guint8                                  plen;
     guint                                   i;
     const gint32                            now_sec = nm_utils_get_monotonic_timestamp_sec();
 
@@ -130,20 +118,11 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
 
     nm_l3_config_data_set_ip6_privacy(l3cd, ip6_privacy);
 
-    /* Check, whether kernel is recent enough to help user space handling RA.
-     * If it's not supported, we have no ipv6-privacy and must add autoconf
-     * addresses as /128. The reason for the /128 is to prevent the kernel
-     * from adding a prefix route for this address. */
-    ifa_flags = 0;
-    if (kernel_support_extended_ifa_flags) {
-        ifa_flags |= IFA_F_NOPREFIXROUTE;
-        if (NM_IN_SET(ip6_privacy,
-                      NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
-                      NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
-            ifa_flags |= IFA_F_MANAGETEMPADDR;
-        plen = 64;
-    } else
-        plen = 128;
+    ifa_flags = IFA_F_NOPREFIXROUTE;
+    if (NM_IN_SET(ip6_privacy,
+                  NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+                  NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
+        ifa_flags |= IFA_F_MANAGETEMPADDR;
 
     for (i = 0; i < rdata->addresses_n; i++) {
         const NMNDiscAddress *ndisc_addr = &rdata->addresses[i];
@@ -152,7 +131,7 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
         a = (NMPlatformIP6Address){
             .ifindex   = ifindex,
             .address   = ndisc_addr->address,
-            .plen      = plen,
+            .plen      = 64,
             .timestamp = now_sec,
             .lifetime  = _nm_ndisc_lifetime_from_expiry(((gint64) now_sec) * 1000,
                                                        ndisc_addr->expiry_msec,
@@ -178,8 +157,10 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
             .plen          = ndisc_route->plen,
             .gateway       = ndisc_route->gateway,
             .rt_source     = NM_IP_CONFIG_SOURCE_NDISC,
-            .table_coerced = nm_platform_route_table_coerce(route_table),
-            .metric        = route_metric,
+            .table_any     = TRUE,
+            .table_coerced = 0,
+            .metric_any    = TRUE,
+            .metric        = 0,
             .rt_pref       = ndisc_route->preference,
         };
         nm_assert((NMIcmpv6RouterPref) r.rt_pref == ndisc_route->preference);
@@ -188,12 +169,13 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
     }
 
     if (rdata->gateways_n > 0) {
-        const NMIcmpv6RouterPref first_pref = rdata->gateways[0].preference;
-        NMPlatformIP6Route       r          = {
+        NMPlatformIP6Route r = {
             .rt_source     = NM_IP_CONFIG_SOURCE_NDISC,
             .ifindex       = ifindex,
-            .table_coerced = nm_platform_route_table_coerce(route_table),
-            .metric        = route_metric,
+            .table_any     = TRUE,
+            .table_coerced = 0,
+            .metric_any    = TRUE,
+            .metric        = 0,
         };
 
         for (i = 0; i < rdata->gateways_n; i++) {
@@ -201,13 +183,6 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
             r.rt_pref = rdata->gateways[i].preference;
             nm_assert((NMIcmpv6RouterPref) r.rt_pref == rdata->gateways[i].preference);
             nm_l3_config_data_add_route_6(l3cd, &r);
-
-            if (first_pref != rdata->gateways[i].preference && !kernel_support_rta_pref) {
-                /* We are unable to configure a router preference. Hence, we skip all gateways
-                 * with a different preference from the first gateway. Note, that the gateways
-                 * are sorted in order of highest to lowest preference. */
-                break;
-            }
         }
     }
 
@@ -222,6 +197,8 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
     nm_l3_config_data_set_ndisc_retrans_timer_msec(l3cd, rdata->retrans_timer_ms);
 
     nm_l3_config_data_set_ip6_mtu(l3cd, rdata->mtu);
+    if (token)
+        nm_l3_config_data_set_ip6_token(l3cd, *token);
 
     return g_steal_pointer(&l3cd);
 }
@@ -315,7 +292,7 @@ nm_ndisc_get_ifindex(NMNDisc *self)
 {
     g_return_val_if_fail(NM_IS_NDISC(self), 0);
 
-    return NM_NDISC_GET_PRIVATE(self)->ifindex;
+    return nm_l3cfg_get_ifindex(NM_NDISC_GET_PRIVATE(self)->config.l3cfg);
 }
 
 const char *
@@ -323,7 +300,7 @@ nm_ndisc_get_ifname(NMNDisc *self)
 {
     g_return_val_if_fail(NM_IS_NDISC(self), NULL);
 
-    return NM_NDISC_GET_PRIVATE(self)->ifname;
+    return NM_NDISC_GET_PRIVATE(self)->config.ifname;
 }
 
 NMNDiscNodeType
@@ -331,7 +308,7 @@ nm_ndisc_get_node_type(NMNDisc *self)
 {
     g_return_val_if_fail(NM_IS_NDISC(self), NM_NDISC_NODE_TYPE_INVALID);
 
-    return NM_NDISC_GET_PRIVATE(self)->node_type;
+    return NM_NDISC_GET_PRIVATE(self)->config.node_type;
 }
 
 /*****************************************************************************/
@@ -391,15 +368,28 @@ _data_complete(NMNDiscDataInternal *data)
     return &data->public;
 }
 
-void
+static void
 nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed)
 {
+    NMNDiscPrivate                          *priv = NM_NDISC_GET_PRIVATE(self);
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+    const NMNDiscData                       *rdata;
+
     _config_changed_log(self, changed);
-    g_signal_emit(self,
-                  signals[CONFIG_RECEIVED],
-                  0,
-                  _data_complete(&NM_NDISC_GET_PRIVATE(self)->rdata),
-                  (guint) changed);
+
+    rdata = _data_complete(&NM_NDISC_GET_PRIVATE(self)->rdata),
+
+    l3cd = nm_ndisc_data_to_l3cd(nm_l3cfg_get_multi_idx(priv->config.l3cfg),
+                                 nm_l3cfg_get_ifindex(priv->config.l3cfg),
+                                 rdata,
+                                 priv->config.ip6_privacy,
+                                 priv->iid_is_token ? &priv->iid : NULL);
+    l3cd = nm_l3_config_data_seal(l3cd);
+
+    if (!nm_l3_config_data_equal(priv->l3cd, l3cd))
+        NM_SWAP(&priv->l3cd, &l3cd);
+
+    g_signal_emit(self, signals[CONFIG_RECEIVED], 0, rdata, (guint) changed, priv->l3cd);
 }
 
 /*****************************************************************************/
@@ -474,16 +464,16 @@ static gboolean
 complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
 {
     NMNDiscPrivate *priv;
-    GError *        error = NULL;
+    GError         *error = NULL;
 
     g_return_val_if_fail(NM_IS_NDISC(ndisc), FALSE);
 
     priv = NM_NDISC_GET_PRIVATE(ndisc);
-    if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
-        if (!nm_utils_ipv6_addr_set_stable_privacy_may_fail(priv->stable_type,
+    if (priv->config.addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
+        if (!nm_utils_ipv6_addr_set_stable_privacy_may_fail(priv->config.stable_type,
                                                             &addr->address,
-                                                            priv->ifname,
-                                                            priv->network_id,
+                                                            priv->config.ifname,
+                                                            priv->config.network_id,
                                                             addr->dad_counter++,
                                                             &error)) {
             _LOGW("complete-address: failed to generate an stable-privacy address: %s",
@@ -511,15 +501,15 @@ complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
 }
 
 static gboolean
-nm_ndisc_add_address(NMNDisc *             ndisc,
+nm_ndisc_add_address(NMNDisc              *ndisc,
                      const NMNDiscAddress *new_item,
                      gint64                now_msec,
                      gboolean              from_ra)
 {
-    NMNDiscPrivate *     priv  = NM_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscPrivate      *priv  = NM_NDISC_GET_PRIVATE(ndisc);
     NMNDiscDataInternal *rdata = &priv->rdata;
-    NMNDiscAddress *     new2;
-    NMNDiscAddress *     existing = NULL;
+    NMNDiscAddress      *new2;
+    NMNDiscAddress      *existing = NULL;
     guint                i;
 
     nm_assert(new_item);
@@ -608,7 +598,7 @@ nm_ndisc_add_address(NMNDisc *             ndisc,
      * what the kernel does, because it considers *all* addresses (including
      * static and other temporary addresses).
      **/
-    if (rdata->addresses->len >= priv->max_addresses)
+    if (rdata->addresses->len >= priv->config.max_addresses)
         return FALSE;
 
     if (new_item->expiry_msec <= now_msec)
@@ -640,7 +630,7 @@ nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item
 gboolean
 nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec)
 {
-    NMNDiscPrivate *     priv;
+    NMNDiscPrivate      *priv;
     NMNDiscDataInternal *rdata;
     guint                i;
     guint                insert_idx = G_MAXUINT;
@@ -709,7 +699,7 @@ nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec
 gboolean
 nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec)
 {
-    NMNDiscPrivate *     priv;
+    NMNDiscPrivate      *priv;
     NMNDiscDataInternal *rdata;
     guint                i;
 
@@ -747,9 +737,9 @@ nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64
 gboolean
 nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec)
 {
-    NMNDiscPrivate *     priv;
+    NMNDiscPrivate      *priv;
     NMNDiscDataInternal *rdata;
-    NMNDiscDNSDomain *   item;
+    NMNDiscDNSDomain    *item;
     guint                i;
 
     priv  = NM_NDISC_GET_PRIVATE(ndisc);
@@ -824,13 +814,13 @@ solicit_retransmit_time_jitter(gint32 solicit_retransmit_time_msec)
 static gboolean
 solicit_timer_cb(gpointer user_data)
 {
-    const gint32      TIMEOUT_APPROX_THRESHOLD_SEC = 10000;
-    nm_auto_pop_netns NMPNetns *netns              = NULL;
-    NMNDisc *                   ndisc              = user_data;
-    NMNDiscClass *              klass              = NM_NDISC_GET_CLASS(ndisc);
-    NMNDiscPrivate *            priv               = NM_NDISC_GET_PRIVATE(ndisc);
-    gs_free_error GError *error                    = NULL;
-    gint32                timeout_msec;
+    const gint32                TIMEOUT_APPROX_THRESHOLD_SEC = 10000;
+    nm_auto_pop_netns NMPNetns *netns                        = NULL;
+    NMNDisc                    *ndisc                        = user_data;
+    NMNDiscClass               *klass                        = NM_NDISC_GET_CLASS(ndisc);
+    NMNDiscPrivate             *priv                         = NM_NDISC_GET_PRIVATE(ndisc);
+    gs_free_error GError       *error                        = NULL;
+    gint32                      timeout_msec;
 
     if (!nm_ndisc_netns_push(ndisc, &netns)) {
         nm_utils_error_set(&error,
@@ -899,7 +889,7 @@ solicit_timer_start(NMNDisc *ndisc)
         g_random_int() % ((guint32) (NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY * 1000 / 4));
 
     _LOGD("solicit: schedule sending first solicitation (of %d) in %.3f seconds",
-          priv->router_solicitations,
+          priv->config.router_solicitations,
           ((double) delay_msec) / 1000);
 
     priv->solicit_retransmit_time_msec = 0;
@@ -913,9 +903,9 @@ static gboolean
 announce_router(NMNDisc *ndisc)
 {
     nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMNDiscClass *              klass = NM_NDISC_GET_CLASS(ndisc);
-    NMNDiscPrivate *            priv  = NM_NDISC_GET_PRIVATE(ndisc);
-    GError *                    error = NULL;
+    NMNDiscClass               *klass = NM_NDISC_GET_CLASS(ndisc);
+    NMNDiscPrivate             *priv  = NM_NDISC_GET_PRIVATE(ndisc);
+    GError                     *error = NULL;
 
     if (!nm_ndisc_netns_push(ndisc, &netns))
         return G_SOURCE_REMOVE;
@@ -997,30 +987,82 @@ announce_router_solicited(NMNDisc *ndisc)
 /*****************************************************************************/
 
 void
-nm_ndisc_set_config(NMNDisc *     ndisc,
-                    const GArray *addresses,
-                    const GArray *dns_servers,
-                    const GArray *dns_domains)
+nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd)
 {
-    gboolean changed = FALSE;
-    guint    i;
+    gboolean               changed = FALSE;
+    const struct in6_addr *in6arr;
+    const char *const     *strvarr;
+    NMDedupMultiIter       iter;
+    const NMPObject       *obj;
+    guint                  len;
+    guint                  i;
+
+    nm_assert(NM_IS_NDISC(ndisc));
+    nm_assert(nm_ndisc_get_node_type(ndisc) == NM_NDISC_NODE_TYPE_ROUTER);
+
+    nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP6_ADDRESS) {
+        const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
+        guint32                     preferred;
+        guint32                     lifetime;
+        NMNDiscAddress              a;
+
+        if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address))
+            continue;
+
+        if (addr->n_ifa_flags & IFA_F_TENTATIVE || addr->n_ifa_flags & IFA_F_DADFAILED)
+            continue;
+
+        if (addr->plen != 64)
+            continue;
 
-    for (i = 0; i < addresses->len; i++) {
-        if (nm_ndisc_add_address(ndisc, &g_array_index(addresses, NMNDiscAddress, i), 0, FALSE))
+        lifetime = nmp_utils_lifetime_get(addr->timestamp,
+                                          addr->lifetime,
+                                          addr->preferred,
+                                          NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
+                                          &preferred);
+        if (!lifetime)
+            continue;
+
+        a = (NMNDiscAddress){
+            .address     = addr->address,
+            .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime),
+            .expiry_preferred_msec =
+                _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred),
+        };
+
+        if (nm_ndisc_add_address(ndisc, &a, 0, FALSE))
             changed = TRUE;
     }
 
-    for (i = 0; i < dns_servers->len; i++) {
-        if (nm_ndisc_add_dns_server(ndisc,
-                                    &g_array_index(dns_servers, NMNDiscDNSServer, i),
-                                    G_MININT64))
+    in6arr = NULL;
+    len    = 0;
+    if (l3cd)
+        in6arr = nm_l3_config_data_get_nameservers(l3cd, AF_INET6, &len);
+    for (i = 0; i < len; i++) {
+        NMNDiscDNSServer n;
+
+        n = (NMNDiscDNSServer){
+            .address     = in6arr[i],
+            .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
+                                                        NM_NDISC_ROUTER_LIFETIME),
+        };
+        if (nm_ndisc_add_dns_server(ndisc, &n, G_MININT64))
             changed = TRUE;
     }
 
-    for (i = 0; i < dns_domains->len; i++) {
-        if (nm_ndisc_add_dns_domain(ndisc,
-                                    &g_array_index(dns_domains, NMNDiscDNSDomain, i),
-                                    G_MININT64))
+    strvarr = NULL;
+    len     = 0;
+    if (l3cd)
+        strvarr = nm_l3_config_data_get_searches(l3cd, AF_INET6, &len);
+    for (i = 0; i < len; i++) {
+        NMNDiscDNSDomain n;
+
+        n = (NMNDiscDNSDomain){
+            .domain      = (char *) strvarr[i],
+            .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
+                                                        NM_NDISC_ROUTER_LIFETIME),
+        };
+        if (nm_ndisc_add_dns_domain(ndisc, &n, G_MININT64))
             changed = TRUE;
     }
 
@@ -1048,20 +1090,21 @@ nm_ndisc_set_config(NMNDisc *     ndisc,
  * Returns: %TRUE if addresses need to be regenerated, %FALSE otherwise.
  **/
 gboolean
-nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid)
+nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid, gboolean is_token)
 {
-    NMNDiscPrivate *     priv;
+    NMNDiscPrivate      *priv;
     NMNDiscDataInternal *rdata;
 
     g_return_val_if_fail(NM_IS_NDISC(ndisc), FALSE);
 
-    priv  = NM_NDISC_GET_PRIVATE(ndisc);
-    rdata = &priv->rdata;
+    priv               = NM_NDISC_GET_PRIVATE(ndisc);
+    priv->iid_is_token = is_token;
+    rdata              = &priv->rdata;
 
     if (priv->iid.id != iid.id) {
         priv->iid = iid;
 
-        if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY)
+        if (priv->config.addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY)
             return FALSE;
 
         if (rdata->addresses->len) {
@@ -1090,7 +1133,7 @@ void
 nm_ndisc_start(NMNDisc *ndisc)
 {
     nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMNDiscPrivate *            priv;
+    NMNDiscPrivate             *priv;
 
     g_return_if_fail(NM_IS_NDISC(ndisc));
 
@@ -1100,26 +1143,26 @@ nm_ndisc_start(NMNDisc *ndisc)
     nm_assert(!priv->ra_timeout_source);
 
     _LOGD("starting neighbor discovery for ifindex %d%s",
-          priv->ifindex,
-          priv->node_type == NM_NDISC_NODE_TYPE_HOST ? " (solicit)" : " (announce)");
+          nm_l3cfg_get_ifindex(priv->config.l3cfg),
+          priv->config.node_type == NM_NDISC_NODE_TYPE_HOST ? " (solicit)" : " (announce)");
 
     if (!nm_ndisc_netns_push(ndisc, &netns))
         return;
 
     NM_NDISC_GET_CLASS(ndisc)->start(ndisc);
 
-    if (priv->node_type == NM_NDISC_NODE_TYPE_HOST) {
+    if (priv->config.node_type == NM_NDISC_NODE_TYPE_HOST) {
         G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_DEFAULT == 0);
         G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_INFINITY == G_MAXINT32);
-        nm_assert(priv->ra_timeout > 0u);
-        nm_assert(priv->ra_timeout <= NM_RA_TIMEOUT_INFINITY);
+        nm_assert(priv->config.ra_timeout > 0u);
+        nm_assert(priv->config.ra_timeout <= NM_RA_TIMEOUT_INFINITY);
 
-        if (priv->ra_timeout < NM_RA_TIMEOUT_INFINITY) {
+        if (priv->config.ra_timeout < NM_RA_TIMEOUT_INFINITY) {
             guint timeout_msec;
 
-            _LOGD("scheduling RA timeout in %u seconds", priv->ra_timeout);
-            if (priv->ra_timeout < G_MAXUINT / 1000u)
-                timeout_msec = priv->ra_timeout * 1000u;
+            _LOGD("scheduling RA timeout in %u seconds", priv->config.ra_timeout);
+            if (priv->config.ra_timeout < G_MAXUINT / 1000u)
+                timeout_msec = priv->config.ra_timeout * 1000u;
             else
                 timeout_msec = G_MAXUINT;
             priv->ra_timeout_source = nm_g_timeout_add_source(timeout_msec, ra_timeout_cb, ndisc);
@@ -1129,8 +1172,8 @@ nm_ndisc_start(NMNDisc *ndisc)
         return;
     }
 
-    nm_assert(priv->ra_timeout == 0u);
-    nm_assert(priv->node_type == NM_NDISC_NODE_TYPE_ROUTER);
+    nm_assert(priv->config.ra_timeout == 0u);
+    nm_assert(priv->config.node_type == NM_NDISC_NODE_TYPE_ROUTER);
     announce_router_initial(ndisc);
 }
 
@@ -1138,8 +1181,8 @@ void
 nm_ndisc_stop(NMNDisc *ndisc)
 {
     nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMNDiscDataInternal *       rdata;
-    NMNDiscPrivate *            priv;
+    NMNDiscDataInternal        *rdata;
+    NMNDiscPrivate             *priv;
 
     g_return_if_fail(NM_IS_NDISC(ndisc));
 
@@ -1147,7 +1190,7 @@ nm_ndisc_stop(NMNDisc *ndisc)
 
     nm_assert(NM_NDISC_GET_CLASS(ndisc)->stop);
 
-    _LOGD("stopping neighbor discovery for ifindex %d", priv->ifindex);
+    _LOGD("stopping neighbor discovery for ifindex %d", nm_l3cfg_get_ifindex(priv->config.l3cfg));
 
     if (!nm_ndisc_netns_push(ndisc, &netns))
         return;
@@ -1230,7 +1273,7 @@ config_map_to_string(NMNDiscConfigMap map, char *p)
 static void
 _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
 {
-    NMNDiscPrivate *     priv;
+    NMNDiscPrivate      *priv;
     NMNDiscDataInternal *rdata;
     guint                i;
     char                 changedstr[CONFIG_MAP_MAX_STR];
@@ -1319,7 +1362,7 @@ static void
 clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
-    NMNDiscGateway *     arr;
+    NMNDiscGateway      *arr;
     guint                i;
     guint                j;
 
@@ -1350,9 +1393,9 @@ clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint6
 static void
 clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
-    NMNDiscPrivate *     priv  = NM_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscPrivate      *priv  = NM_NDISC_GET_PRIVATE(ndisc);
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
-    NMNDiscAddress *     arr;
+    NMNDiscAddress      *arr;
     guint                i;
     guint                j;
 
@@ -1374,7 +1417,7 @@ clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint
         g_array_set_size(rdata->addresses, j);
     }
 
-    if (_array_set_size_max(rdata->gateways, priv->max_addresses))
+    if (_array_set_size_max(rdata->gateways, priv->config.max_addresses))
         *changed |= NM_NDISC_CONFIG_ADDRESSES;
 }
 
@@ -1382,7 +1425,7 @@ static void
 clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
-    NMNDiscRoute *       arr;
+    NMNDiscRoute        *arr;
     guint                i;
     guint                j;
 
@@ -1412,7 +1455,7 @@ static void
 clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
-    NMNDiscDNSServer *   arr;
+    NMNDiscDNSServer    *arr;
     guint                i;
     guint                j;
 
@@ -1442,7 +1485,7 @@ static void
 clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
-    NMNDiscDNSDomain *   arr;
+    NMNDiscDNSDomain    *arr;
     guint                i;
     guint                j;
 
@@ -1549,7 +1592,7 @@ _calc_pre_expiry_rs_msec_worker(gint64 *earliest_expiry_msec,
 static gint64
 calc_pre_expiry_rs_msec(NMNDisc *ndisc)
 {
-    NMNDiscPrivate *     priv        = NM_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscPrivate      *priv        = NM_NDISC_GET_PRIVATE(ndisc);
     NMNDiscDataInternal *rdata       = &priv->rdata;
     gint64               expiry_msec = NM_NDISC_EXPIRY_INFINITY;
     guint                i;
@@ -1657,10 +1700,10 @@ ipv6_sysctl_get(NMPlatform *platform,
 void
 nm_ndisc_get_sysctl(NMPlatform *platform,
                     const char *ifname,
-                    int *       out_max_addresses,
-                    int *       out_router_solicitations,
-                    int *       out_router_solicitation_interval,
-                    guint32 *   out_default_ra_timeout)
+                    int        *out_max_addresses,
+                    int        *out_router_solicitations,
+                    int        *out_router_solicitation_interval,
+                    guint32    *out_default_ra_timeout)
 {
     int router_solicitation_interval = 0;
     int router_solicitations         = 0;
@@ -1700,85 +1743,87 @@ nm_ndisc_get_sysctl(NMPlatform *platform,
 /*****************************************************************************/
 
 static void
+_config_clear(NMNDiscConfig *config)
+{
+    g_clear_object(&config->l3cfg);
+    nm_clear_g_free((gpointer *) &config->ifname);
+    nm_clear_g_free((gpointer *) &config->network_id);
+}
+
+static void
+_config_init(NMNDiscConfig *config, const NMNDiscConfig *src)
+{
+    nm_assert(config);
+    g_return_if_fail(src);
+
+    /* we only allow to set @config if it was cleared (or is not yet initialized). */
+    nm_assert(!config->l3cfg);
+    nm_assert(!config->ifname);
+    nm_assert(!config->network_id);
+
+    g_return_if_fail(NM_IS_L3CFG(src->l3cfg));
+
+    *config = *src;
+
+    g_object_ref(config->l3cfg);
+    config->ifname     = g_strdup(config->ifname);
+    config->network_id = g_strdup(config->network_id);
+
+    if (config->max_addresses <= 0)
+        config->max_addresses = _SIZE_MAX_ADDRESSES;
+    else if (config->max_addresses > 3u * _SIZE_MAX_ADDRESSES)
+        config->max_addresses = 3u * _SIZE_MAX_ADDRESSES;
+
+    /* This setter is only used in specific circumstances, and in this case,
+     * we expect that @src only contains valid settings. We thus assert that to
+     * be the case.*/
+    g_return_if_fail(config->ifname && config->ifname[0]);
+    g_return_if_fail(config->network_id);
+    g_return_if_fail(config->stable_type >= NM_UTILS_STABLE_TYPE_UUID
+                     && config->stable_type <= NM_UTILS_STABLE_TYPE_RANDOM);
+    g_return_if_fail(config->addr_gen_mode >= NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64
+                     && config->addr_gen_mode
+                            <= NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY);
+    nm_assert(config->max_addresses >= 0 && config->max_addresses <= G_MAXINT32);
+    G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_RA_TIMEOUT_INFINITY);
+    g_return_if_fail(config->ra_timeout <= NM_RA_TIMEOUT_INFINITY);
+    g_return_if_fail(config->router_solicitations > 0
+                     && config->router_solicitations <= G_MAXINT32);
+    g_return_if_fail(config->router_solicitation_interval > 0
+                     && config->router_solicitation_interval <= G_MAXINT32);
+    g_return_if_fail(
+        NM_IN_SET(config->node_type, NM_NDISC_NODE_TYPE_HOST, NM_NDISC_NODE_TYPE_ROUTER));
+    g_return_if_fail(NM_IN_SET(config->ip6_privacy,
+                               NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED,
+                               NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR,
+                               NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR));
+}
+
+/*****************************************************************************/
+
+static void
 dns_domain_free(gpointer data)
 {
     g_free(((NMNDiscDNSDomain *) (data))->domain);
 }
 
+/*****************************************************************************/
+
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-    NMNDisc *       self = NM_NDISC(object);
+    NMNDisc        *self = NM_NDISC(object);
     NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(self);
-    int             i;
 
     switch (prop_id) {
-    case PROP_PLATFORM:
+    case PROP_CONFIG:
         /* construct-only */
-        priv->platform = g_value_get_object(value) ?: NM_PLATFORM_GET;
-        if (!priv->platform)
-            g_return_if_reached();
-
-        g_object_ref(priv->platform);
-
-        priv->netns = nm_platform_netns_get(priv->platform);
-        if (priv->netns)
-            g_object_ref(priv->netns);
+        _config_init(&priv->config_, g_value_get_pointer(value));
 
+        priv->netns =
+            nm_g_object_ref(nm_platform_netns_get(nm_l3cfg_get_platform(priv->config.l3cfg)));
         g_return_if_fail(!priv->netns || priv->netns == nmp_netns_get_current());
         break;
-    case PROP_IFINDEX:
-        /* construct-only */
-        priv->ifindex = g_value_get_int(value);
-        g_return_if_fail(priv->ifindex > 0);
-        break;
-    case PROP_IFNAME:
-        /* construct-only */
-        priv->ifname = g_value_dup_string(value);
-        g_return_if_fail(priv->ifname && priv->ifname[0]);
-        break;
-    case PROP_STABLE_TYPE:
-        /* construct-only */
-        priv->stable_type = g_value_get_int(value);
-        break;
-    case PROP_NETWORK_ID:
-        /* construct-only */
-        priv->network_id = g_value_dup_string(value);
-        g_return_if_fail(priv->network_id);
-        break;
-    case PROP_ADDR_GEN_MODE:
-        /* construct-only */
-        priv->addr_gen_mode = g_value_get_int(value);
-        break;
-    case PROP_MAX_ADDRESSES:
-        /* construct-only */
-        i = g_value_get_int(value);
-        nm_assert(i >= 0);
-        priv->max_addresses = i;
-
-        if (priv->max_addresses <= 0)
-            priv->max_addresses = _SIZE_MAX_ADDRESSES;
-        else if (priv->max_addresses > 3u * _SIZE_MAX_ADDRESSES)
-            priv->max_addresses = 3u * _SIZE_MAX_ADDRESSES;
-        break;
-    case PROP_RA_TIMEOUT:
-        /* construct-only */
-        priv->ra_timeout = g_value_get_uint(value);
-        nm_assert(priv->ra_timeout <= NM_RA_TIMEOUT_INFINITY);
-        break;
-    case PROP_ROUTER_SOLICITATIONS:
-        /* construct-only */
-        priv->router_solicitations = g_value_get_int(value);
-        break;
-    case PROP_ROUTER_SOLICITATION_INTERVAL:
-        /* construct-only */
-        priv->router_solicitation_interval = g_value_get_int(value);
-        break;
-    case PROP_NODE_TYPE:
-        /* construct-only */
-        priv->node_type = g_value_get_int(value);
-        nm_assert(NM_IN_SET(priv->node_type, NM_NDISC_NODE_TYPE_HOST, NM_NDISC_NODE_TYPE_ROUTER));
-        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
@@ -1788,7 +1833,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
 static void
 nm_ndisc_init(NMNDisc *ndisc)
 {
-    NMNDiscPrivate *     priv;
+    NMNDiscPrivate      *priv;
     NMNDiscDataInternal *rdata;
 
     priv         = G_TYPE_INSTANCE_GET_PRIVATE(ndisc, NM_TYPE_NDISC, NMNDiscPrivate);
@@ -1808,7 +1853,7 @@ nm_ndisc_init(NMNDisc *ndisc)
 static void
 dispose(GObject *object)
 {
-    NMNDisc *       ndisc = NM_NDISC(object);
+    NMNDisc        *ndisc = NM_NDISC(object);
     NMNDiscPrivate *priv  = NM_NDISC_GET_PRIVATE(ndisc);
 
     nm_clear_g_source_inst(&priv->ra_timeout_source);
@@ -1824,13 +1869,10 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMNDisc *            ndisc = NM_NDISC(object);
-    NMNDiscPrivate *     priv  = NM_NDISC_GET_PRIVATE(ndisc);
+    NMNDisc             *ndisc = NM_NDISC(object);
+    NMNDiscPrivate      *priv  = NM_NDISC_GET_PRIVATE(ndisc);
     NMNDiscDataInternal *rdata = &priv->rdata;
 
-    g_free(priv->ifname);
-    g_free(priv->network_id);
-
     g_array_unref(rdata->gateways);
     g_array_unref(rdata->addresses);
     g_array_unref(rdata->routes);
@@ -1838,7 +1880,10 @@ finalize(GObject *object)
     g_array_unref(rdata->dns_domains);
 
     g_clear_object(&priv->netns);
-    g_clear_object(&priv->platform);
+
+    _config_clear(&priv->config_);
+
+    nm_clear_l3cd(&priv->l3cd);
 
     G_OBJECT_CLASS(nm_ndisc_parent_class)->finalize(object);
 }
@@ -1854,89 +1899,12 @@ nm_ndisc_class_init(NMNDiscClass *klass)
     object_class->dispose      = dispose;
     object_class->finalize     = finalize;
 
-    obj_properties[PROP_PLATFORM] =
-        g_param_spec_object(NM_NDISC_PLATFORM,
-                            "",
-                            "",
-                            NM_TYPE_PLATFORM,
-                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_IFINDEX] =
-        g_param_spec_int(NM_NDISC_IFINDEX,
-                         "",
-                         "",
-                         0,
-                         G_MAXINT,
-                         0,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_IFNAME] =
-        g_param_spec_string(NM_NDISC_IFNAME,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_STABLE_TYPE] =
-        g_param_spec_int(NM_NDISC_STABLE_TYPE,
-                         "",
-                         "",
-                         NM_UTILS_STABLE_TYPE_UUID,
-                         NM_UTILS_STABLE_TYPE_RANDOM,
-                         NM_UTILS_STABLE_TYPE_UUID,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_NETWORK_ID] =
-        g_param_spec_string(NM_NDISC_NETWORK_ID,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_ADDR_GEN_MODE] =
-        g_param_spec_int(NM_NDISC_ADDR_GEN_MODE,
-                         "",
-                         "",
-                         NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
-                         NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY,
-                         NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_MAX_ADDRESSES] =
-        g_param_spec_int(NM_NDISC_MAX_ADDRESSES,
-                         "",
-                         "",
-                         0,
-                         G_MAXINT32,
-                         NM_NDISC_MAX_ADDRESSES_DEFAULT,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_RA_TIMEOUT_INFINITY);
-    obj_properties[PROP_RA_TIMEOUT] =
-        g_param_spec_uint(NM_NDISC_RA_TIMEOUT,
-                          "",
-                          "",
-                          0,
-                          G_MAXINT32,
-                          0,
-                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_ROUTER_SOLICITATIONS] =
-        g_param_spec_int(NM_NDISC_ROUTER_SOLICITATIONS,
-                         "",
-                         "",
-                         1,
-                         G_MAXINT32,
-                         NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_ROUTER_SOLICITATION_INTERVAL] =
-        g_param_spec_int(NM_NDISC_ROUTER_SOLICITATION_INTERVAL,
-                         "",
-                         "",
-                         1,
-                         G_MAXINT32,
-                         NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_NODE_TYPE] =
-        g_param_spec_int(NM_NDISC_NODE_TYPE,
-                         "",
-                         "",
-                         NM_NDISC_NODE_TYPE_INVALID,
-                         NM_NDISC_NODE_TYPE_ROUTER,
-                         NM_NDISC_NODE_TYPE_INVALID,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_CONFIG] =
+        g_param_spec_pointer(NM_NDISC_CONFIG,
+                             "",
+                             "",
+                             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
     signals[CONFIG_RECEIVED]   = g_signal_new(NM_NDISC_CONFIG_RECEIVED,
@@ -1947,9 +1915,14 @@ nm_ndisc_class_init(NMNDiscClass *klass)
                                             NULL,
                                             NULL,
                                             G_TYPE_NONE,
-                                            2,
-                                            G_TYPE_POINTER,
-                                            G_TYPE_UINT);
+                                            3,
+                                            G_TYPE_POINTER
+                                            /* (const NMNDiscData *)rdata */,
+                                            G_TYPE_UINT
+                                            /* (guint) changed_i */,
+                                            G_TYPE_POINTER
+                                            /* (const NML3ConfigData *) l3cd */
+    );
     signals[RA_TIMEOUT_SIGNAL] = g_signal_new(NM_NDISC_RA_TIMEOUT_SIGNAL,
                                               G_OBJECT_CLASS_TYPE(klass),
                                               G_SIGNAL_RUN_FIRST,
diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h
index 5b827528..079ec037 100644
--- a/src/core/ndisc/nm-ndisc.h
+++ b/src/core/ndisc/nm-ndisc.h
@@ -26,17 +26,7 @@
 #define NM_IS_NDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_NDISC))
 #define NM_NDISC_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_NDISC, NMNDiscClass))
 
-#define NM_NDISC_PLATFORM                     "platform"
-#define NM_NDISC_IFINDEX                      "ifindex"
-#define NM_NDISC_IFNAME                       "ifname"
-#define NM_NDISC_NETWORK_ID                   "network-id"
-#define NM_NDISC_ADDR_GEN_MODE                "addr-gen-mode"
-#define NM_NDISC_STABLE_TYPE                  "stable-type"
-#define NM_NDISC_NODE_TYPE                    "node-type"
-#define NM_NDISC_MAX_ADDRESSES                "max-addresses"
-#define NM_NDISC_RA_TIMEOUT                   "ra-timeout"
-#define NM_NDISC_ROUTER_SOLICITATIONS         "router-solicitations"
-#define NM_NDISC_ROUTER_SOLICITATION_INTERVAL "router-solicitation-interval"
+#define NM_NDISC_CONFIG "config"
 
 #define NM_NDISC_CONFIG_RECEIVED   "config-received"
 #define NM_NDISC_RA_TIMEOUT_SIGNAL "ra-timeout-signal"
@@ -133,7 +123,7 @@ typedef struct {
 } NMNDiscDNSServer;
 
 typedef struct {
-    char * domain;
+    char  *domain;
     gint64 expiry_msec;
 } NMNDiscDNSDomain;
 
@@ -169,6 +159,20 @@ typedef enum {
 #define NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL     600 /* RFC4861, MaxRtrAdvInterval default */
 #define NM_NDISC_ROUTER_LIFETIME                900 /* 1.5 * NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL */
 
+typedef struct {
+    NML3Cfg                      *l3cfg;
+    const char                   *ifname;
+    const char                   *network_id;
+    int                           max_addresses;
+    int                           router_solicitations;
+    int                           router_solicitation_interval;
+    guint32                       ra_timeout;
+    NMUtilsStableType             stable_type;
+    NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+    NMNDiscNodeType               node_type;
+    NMSettingIP6ConfigPrivacy     ip6_privacy;
+} NMNDiscConfig;
+
 struct _NMNDiscPrivate;
 struct _NMNDiscDataInternal;
 
@@ -185,9 +189,9 @@ typedef struct {
     guint dns_servers_n;
     guint dns_domains_n;
 
-    const NMNDiscGateway *  gateways;
-    const NMNDiscAddress *  addresses;
-    const NMNDiscRoute *    routes;
+    const NMNDiscGateway   *gateways;
+    const NMNDiscAddress   *addresses;
+    const NMNDiscRoute     *routes;
     const NMNDiscDNSServer *dns_servers;
     const NMNDiscDNSDomain *dns_domains;
 } NMNDiscData;
@@ -201,7 +205,7 @@ typedef struct {
 typedef struct {
     GObject parent;
     union {
-        struct _NMNDiscPrivate *     _priv;
+        struct _NMNDiscPrivate      *_priv;
         struct _NMNDiscDataInternal *rdata;
     };
 } NMNDisc;
@@ -217,24 +221,19 @@ typedef struct {
 
 GType nm_ndisc_get_type(void);
 
-void nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed);
-
 int             nm_ndisc_get_ifindex(NMNDisc *self);
-const char *    nm_ndisc_get_ifname(NMNDisc *self);
+const char     *nm_ndisc_get_ifname(NMNDisc *self);
 NMNDiscNodeType nm_ndisc_get_node_type(NMNDisc *self);
 
-gboolean nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid);
+gboolean nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid, gboolean is_token);
 void     nm_ndisc_start(NMNDisc *ndisc);
 void     nm_ndisc_stop(NMNDisc *ndisc);
 NMNDiscConfigMap
 nm_ndisc_dad_failed(NMNDisc *ndisc, const struct in6_addr *address, gboolean emit_changed_signal);
-void nm_ndisc_set_config(NMNDisc *     ndisc,
-                         const GArray *addresses,
-                         const GArray *dns_servers,
-                         const GArray *dns_domains);
+void nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd);
 
 NMPlatform *nm_ndisc_get_platform(NMNDisc *self);
-NMPNetns *  nm_ndisc_netns_get(NMNDisc *self);
+NMPNetns   *nm_ndisc_netns_get(NMNDisc *self);
 gboolean    nm_ndisc_netns_push(NMNDisc *self, NMPNetns **netns);
 
 static inline gboolean
@@ -268,22 +267,19 @@ nm_ndisc_dad_addr_is_fail_candidate(NMPlatform *platform, const NMPObject *obj)
 
 void nm_ndisc_get_sysctl(NMPlatform *platform,
                          const char *ifname,
-                         int *       out_max_addresses,
-                         int *       out_router_solicitations,
-                         int *       out_router_solicitation_interval,
-                         guint32 *   out_default_ra_timeout);
+                         int        *out_max_addresses,
+                         int        *out_router_solicitations,
+                         int        *out_router_solicitation_interval,
+                         guint32    *out_default_ra_timeout);
 
 /*****************************************************************************/
 
 struct _NML3ConfigData;
 
-struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
+struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex        *multi_idx,
                                               int                       ifindex,
-                                              const NMNDiscData *       rdata,
+                                              const NMNDiscData        *rdata,
                                               NMSettingIP6ConfigPrivacy ip6_privacy,
-                                              guint32                   route_table,
-                                              guint32                   route_metric,
-                                              gboolean                  kernel_support_rta_pref,
-                                              gboolean kernel_support_extended_ifa_flags);
+                                              NMUtilsIPv6IfaceId       *token);
 
 #endif /* __NETWORKMANAGER_NDISC_H__ */
diff --git a/src/core/ndisc/tests/test-ndisc-fake.c b/src/core/ndisc/tests/test-ndisc-fake.c
index f201bc56..0763b64e 100644
--- a/src/core/ndisc/tests/test-ndisc-fake.c
+++ b/src/core/ndisc/tests/test-ndisc-fake.c
@@ -11,24 +11,29 @@
 #include "ndisc/nm-fake-ndisc.h"
 
 #include "platform/nm-fake-platform.h"
+#include "nm-netns.h"
 
-#include "nm-test-utils-core.h"
+#include "platform/tests/test-common.h"
 
 /*****************************************************************************/
 
 static NMFakeNDisc *
 ndisc_new(void)
 {
-    NMNDisc *          ndisc;
-    const int          ifindex = 1;
-    const char *       ifname  = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex);
-    NMUtilsIPv6IfaceId iid     = {};
+    gs_unref_object NML3Cfg *l3cfg = NULL;
+    NMNDisc                 *ndisc;
+    const int                ifindex = 1;
+    NMUtilsIPv6IfaceId       iid;
 
-    ndisc        = nm_fake_ndisc_new(ifindex, ifname);
-    iid.id_u8[7] = 1;
-    nm_ndisc_set_iid(ndisc, iid);
+    l3cfg = nm_netns_l3cfg_acquire(NM_NETNS_GET, ifindex);
+
+    ndisc = nm_fake_ndisc_new(l3cfg);
     g_assert(ndisc);
 
+    memset(&iid, 0, sizeof(iid));
+    iid.id_u8[7] = 1;
+    nm_ndisc_set_iid(ndisc, iid, FALSE);
+
     return NM_FAKE_NDISC(ndisc);
 }
 
@@ -37,7 +42,7 @@ ndisc_new(void)
 static void
 match_gateway(const NMNDiscData *rdata,
               guint              idx,
-              const char *       addr,
+              const char        *addr,
               gint64             expiry_msec,
               NMIcmpv6RouterPref pref)
 {
@@ -57,7 +62,7 @@ match_gateway(const NMNDiscData *rdata,
 #define match_address(rdata, idx, addr, _expiry_msec, _expiry_preferred_msec)     \
     G_STMT_START                                                                  \
     {                                                                             \
-        const NMNDiscData *   _rdata = (rdata);                                   \
+        const NMNDiscData    *_rdata = (rdata);                                   \
         guint                 _idx   = (idx);                                     \
         const NMNDiscAddress *_a;                                                 \
                                                                                   \
@@ -76,7 +81,7 @@ match_gateway(const NMNDiscData *rdata,
 #define match_route(rdata, idx, nw, pl, gw, _expiry_msec, pref) \
     G_STMT_START                                                \
     {                                                           \
-        const NMNDiscData * _rdata = (rdata);                   \
+        const NMNDiscData  *_rdata = (rdata);                   \
         guint               _idx   = (idx);                     \
         const NMNDiscRoute *_r;                                 \
         int                 _plen = (pl);                       \
@@ -140,9 +145,15 @@ typedef struct {
 /*****************************************************************************/
 
 static void
-test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
+test_simple_changed(NMNDisc              *ndisc,
+                    const NMNDiscData    *rdata,
+                    guint                 changed_i,
+                    const NML3ConfigData *l3cd,
+                    TestData             *data)
 {
-    NMNDiscConfigMap changed = changed_int;
+    NMNDiscConfigMap changed = changed_i;
+
+    _LOGT("test_simple: callback (counter=%u)", data->counter);
 
     switch (data->counter++) {
     case 0:
@@ -180,12 +191,12 @@ test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int,
 static void
 test_simple(void)
 {
-    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
-    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
-    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
-    TestData                     data       = {
-        .loop             = loop,
-        .timestamp_msec_1 = now_msec,
+    nm_auto_unref_gmainloop GMainLoop *loop     = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc       *ndisc    = ndisc_new();
+    const gint64                       now_msec = nm_utils_get_monotonic_timestamp_msec();
+    TestData                           data     = {
+                                      .loop             = loop,
+                                      .timestamp_msec_1 = now_msec,
     };
     guint id;
 
@@ -206,9 +217,11 @@ test_simple(void)
 
     g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_simple_changed), &data);
 
+    _LOGT("test_simple: start");
     nm_ndisc_start(NM_NDISC(ndisc));
     nmtst_main_loop_run_assert(data.loop, 15000);
     g_assert_cmpint(data.counter, ==, 2);
+    _LOGT("test_simple: done");
 }
 
 /*****************************************************************************/
@@ -221,9 +234,13 @@ test_everything_rs_sent(NMNDisc *ndisc, TestData *data)
 }
 
 static void
-test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
+test_everything_changed(NMNDisc              *ndisc,
+                        const NMNDiscData    *rdata,
+                        guint                 changed_i,
+                        const NML3ConfigData *l3cd,
+                        TestData             *data)
 {
-    NMNDiscConfigMap changed = changed_int;
+    NMNDiscConfigMap changed = changed_i;
 
     if (data->counter == 0) {
         g_assert_cmpint(data->rs_counter, ==, 1);
@@ -288,12 +305,12 @@ test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_
 static void
 test_everything(void)
 {
-    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
-    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
-    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
-    TestData                     data       = {
-        .loop             = loop,
-        .timestamp_msec_1 = now_msec,
+    nm_auto_unref_gmainloop GMainLoop *loop     = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc       *ndisc    = ndisc_new();
+    const gint64                       now_msec = nm_utils_get_monotonic_timestamp_msec();
+    TestData                           data     = {
+                                      .loop             = loop,
+                                      .timestamp_msec_1 = now_msec,
     };
     guint id;
 
@@ -342,12 +359,13 @@ test_everything(void)
 }
 
 static void
-test_preference_order_cb(NMNDisc *          ndisc,
-                         const NMNDiscData *rdata,
-                         guint              changed_int,
-                         TestData *         data)
+test_preference_order_cb(NMNDisc              *ndisc,
+                         const NMNDiscData    *rdata,
+                         guint                 changed_i,
+                         const NML3ConfigData *l3cd,
+                         TestData             *data)
 {
-    NMNDiscConfigMap changed = changed_int;
+    NMNDiscConfigMap changed = changed_i;
 
     if (data->counter == 1) {
         g_assert_cmpint(changed,
@@ -391,12 +409,12 @@ test_preference_order_cb(NMNDisc *          ndisc,
 static void
 test_preference_order(void)
 {
-    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
-    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
-    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
-    TestData                     data       = {
-        .loop             = loop,
-        .timestamp_msec_1 = now_msec,
+    nm_auto_unref_gmainloop GMainLoop *loop     = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc       *ndisc    = ndisc_new();
+    const gint64                       now_msec = nm_utils_get_monotonic_timestamp_msec();
+    TestData                           data     = {
+                                      .loop             = loop,
+                                      .timestamp_msec_1 = now_msec,
     };
     guint id;
 
@@ -434,12 +452,13 @@ test_preference_order(void)
 }
 
 static void
-test_preference_changed_cb(NMNDisc *          ndisc,
-                           const NMNDiscData *rdata,
-                           guint              changed_int,
-                           TestData *         data)
+test_preference_changed_cb(NMNDisc              *ndisc,
+                           const NMNDiscData    *rdata,
+                           guint                 changed_i,
+                           const NML3ConfigData *l3cd,
+                           TestData             *data)
 {
-    NMNDiscConfigMap changed = changed_int;
+    NMNDiscConfigMap changed = changed_i;
 
     if (data->counter == 1) {
         g_assert_cmpint(changed,
@@ -513,12 +532,12 @@ test_preference_changed_cb(NMNDisc *          ndisc,
 static void
 test_preference_changed(void)
 {
-    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
-    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
-    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
-    TestData                     data       = {
-        .loop             = loop,
-        .timestamp_msec_1 = now_msec,
+    nm_auto_unref_gmainloop GMainLoop *loop     = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc       *ndisc    = ndisc_new();
+    const gint64                       now_msec = nm_utils_get_monotonic_timestamp_msec();
+    TestData                           data     = {
+                                      .loop             = loop,
+                                      .timestamp_msec_1 = now_msec,
     };
     guint id;
 
@@ -576,10 +595,11 @@ test_preference_changed(void)
 /*****************************************************************************/
 
 static void
-_test_dns_solicit_loop_changed(NMNDisc *          ndisc,
-                               const NMNDiscData *rdata,
-                               guint              changed_int,
-                               TestData *         data)
+_test_dns_solicit_loop_changed(NMNDisc              *ndisc,
+                               const NMNDiscData    *rdata,
+                               guint                 changed_i,
+                               const NML3ConfigData *l3cd,
+                               TestData             *data)
 {
     data->counter++;
 }
@@ -593,12 +613,12 @@ _test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
 static void
 test_dns_solicit_loop(void)
 {
-    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
-    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
-    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
-    TestData                     data       = {
-        .loop             = loop,
-        .timestamp_msec_1 = now_msec,
+    nm_auto_unref_gmainloop GMainLoop *loop     = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc       *ndisc    = ndisc_new();
+    const gint64                       now_msec = nm_utils_get_monotonic_timestamp_msec();
+    TestData                           data     = {
+                                      .loop             = loop,
+                                      .timestamp_msec_1 = now_msec,
     };
     guint id;
 
diff --git a/src/core/ndisc/tests/test-ndisc-linux.c b/src/core/ndisc/tests/test-ndisc-linux.c
index 57021606..66c7e157 100644
--- a/src/core/ndisc/tests/test-ndisc-linux.c
+++ b/src/core/ndisc/tests/test-ndisc-linux.c
@@ -11,6 +11,8 @@
 #include "ndisc/nm-lndp-ndisc.h"
 
 #include "libnm-platform/nm-linux-platform.h"
+#include "nm-netns.h"
+#include "nm-l3cfg.h"
 
 #include "nm-test-utils-core.h"
 
@@ -19,16 +21,18 @@ NMTST_DEFINE();
 int
 main(int argc, char **argv)
 {
-    GMainLoop *        loop;
-    NMNDisc *          ndisc;
-    int                ifindex = 1;
-    const char *       ifname;
-    NMUtilsIPv6IfaceId iid   = {};
-    GError *           error = NULL;
-    int                max_addresses;
-    int                router_solicitations;
-    int                router_solicitation_interval;
-    guint32            ra_timeout;
+    gs_unref_object NML3Cfg *l3cfg = NULL;
+    NMNDiscConfig            config;
+    GMainLoop               *loop;
+    NMNDisc                 *ndisc;
+    int                      ifindex = 1;
+    const char              *ifname;
+    NMUtilsIPv6IfaceId       iid   = {};
+    GError                  *error = NULL;
+    int                      max_addresses;
+    int                      router_solicitations;
+    int                      router_solicitation_interval;
+    guint32                  ra_timeout;
 
     nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT");
 
@@ -56,18 +60,23 @@ main(int argc, char **argv)
                         &router_solicitation_interval,
                         &ra_timeout);
 
-    ndisc = nm_lndp_ndisc_new(NM_PLATFORM_GET,
-                              ifindex,
-                              ifname,
-                              NM_UTILS_STABLE_TYPE_UUID,
-                              "8ce666e8-d34d-4fb1-b858-f15a7al28086",
-                              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
-                              NM_NDISC_NODE_TYPE_HOST,
-                              max_addresses,
-                              router_solicitations,
-                              router_solicitation_interval,
-                              ra_timeout,
-                              &error);
+    l3cfg = nm_netns_l3cfg_acquire(NM_NETNS_GET, ifindex);
+
+    config = (NMNDiscConfig){
+        .l3cfg                        = l3cfg,
+        .ifname                       = nm_l3cfg_get_ifname(l3cfg, TRUE),
+        .stable_type                  = NM_UTILS_STABLE_TYPE_UUID,
+        .network_id                   = "8ce666e8-d34d-4fb1-b858-f15a7al28086",
+        .addr_gen_mode                = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+        .node_type                    = NM_NDISC_NODE_TYPE_HOST,
+        .max_addresses                = max_addresses,
+        .router_solicitations         = router_solicitations,
+        .router_solicitation_interval = router_solicitation_interval,
+        .ra_timeout                   = ra_timeout,
+        .ip6_privacy                  = NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+    };
+
+    ndisc = nm_lndp_ndisc_new(&config);
     if (!ndisc) {
         g_print("Failed to create NMNDisc instance: %s\n", error->message);
         g_error_free(error);
@@ -75,7 +84,7 @@ main(int argc, char **argv)
     }
 
     iid.id_u8[7] = 1;
-    nm_ndisc_set_iid(ndisc, iid);
+    nm_ndisc_set_iid(ndisc, iid, FALSE);
     nm_ndisc_start(ndisc);
     g_main_loop_run(loop);