about summary refs log tree commit diff
path: root/src/core/nm-l3-config-data.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/nm-l3-config-data.c')
-rw-r--r--src/core/nm-l3-config-data.c701
1 files changed, 428 insertions, 273 deletions
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index f2ec040b..033c8942 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -131,6 +131,14 @@ struct _NML3ConfigData {
     guint32 ndisc_reachable_time_msec_val;
     guint32 ndisc_retrans_timer_msec_val;
 
+    union {
+        struct {
+            NMOptionBool never_default_6;
+            NMOptionBool never_default_4;
+        };
+        NMOptionBool never_default_x[2];
+    };
+
     NMTernary metered : 3;
 
     NMTernary proxy_browser_only : 3;
@@ -323,7 +331,7 @@ _strv_ptrarray_merge(GPtrArray **p_dst, const GPtrArray *src)
         const char *s = src->pdata[i];
 
         if (dst_initial_len > 0
-            && nm_utils_strv_find_first((char **) ((*p_dst)->pdata), dst_initial_len, s) >= 0)
+            && nm_strv_find_first((const char *const *) ((*p_dst)->pdata), dst_initial_len, s) >= 0)
             continue;
 
         g_ptr_array_add(*p_dst, g_strdup(s));
@@ -439,27 +447,23 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                 const char *s_prefix = "ndisc: ";
 
                 if (self->ndisc_hop_limit_set) {
-                    nm_utils_strbuf_append(&p,
-                                           &l,
-                                           "%shop-limit=%d",
-                                           s_prefix,
-                                           self->ndisc_hop_limit_val);
+                    nm_strbuf_append(&p, &l, "%shop-limit=%d", s_prefix, self->ndisc_hop_limit_val);
                     s_prefix = ", ";
                 }
                 if (self->ndisc_reachable_time_msec_set) {
-                    nm_utils_strbuf_append(&p,
-                                           &l,
-                                           "%sreachable-time-msec=%u",
-                                           s_prefix,
-                                           self->ndisc_reachable_time_msec_val);
+                    nm_strbuf_append(&p,
+                                     &l,
+                                     "%sreachable-time-msec=%u",
+                                     s_prefix,
+                                     self->ndisc_reachable_time_msec_val);
                     s_prefix = ", ";
                 }
                 if (self->ndisc_retrans_timer_msec_set) {
-                    nm_utils_strbuf_append(&p,
-                                           &l,
-                                           "%sretrans-timer-msec=%u",
-                                           s_prefix,
-                                           self->ndisc_retrans_timer_msec_val);
+                    nm_strbuf_append(&p,
+                                     &l,
+                                     "%sretrans-timer-msec=%u",
+                                     s_prefix,
+                                     self->ndisc_retrans_timer_msec_val);
                     s_prefix = ", ";
                 }
                 _L("%s", sbuf);
@@ -541,6 +545,9 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                    options[i].value_str);
             }
         }
+
+        if (self->never_default_x[IS_IPv4] != NM_OPTION_BOOL_DEFAULT)
+            _L("never-default: %s", self->never_default_x[IS_IPv4] ? "yes" : "no");
     }
 
     if (self->mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
@@ -643,12 +650,14 @@ _idx_type_init(DedupMultiIdxType *idx_type, NMPObjectType obj_type)
 }
 
 NML3ConfigData *
-nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex)
+nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSource source)
 {
     NML3ConfigData *self;
 
     nm_assert(multi_idx);
     nm_assert(ifindex > 0);
+    nm_assert(source == NM_IP_CONFIG_SOURCE_UNKNOWN
+              || (source >= NM_IP_CONFIG_SOURCE_KERNEL && source <= NM_IP_CONFIG_SOURCE_USER));
 
     self  = g_slice_new(NML3ConfigData);
     *self = (NML3ConfigData){
@@ -663,7 +672,9 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex)
         .proxy_method                  = NM_PROXY_CONFIG_METHOD_UNKNOWN,
         .route_table_sync_4            = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
         .route_table_sync_6            = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
-        .source                        = NM_IP_CONFIG_SOURCE_UNKNOWN,
+        .never_default_6               = NM_OPTION_BOOL_DEFAULT,
+        .never_default_4               = NM_OPTION_BOOL_DEFAULT,
+        .source                        = source,
         .ip6_privacy                   = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
         .ndisc_hop_limit_set           = FALSE,
         .ndisc_reachable_time_msec_set = FALSE,
@@ -691,17 +702,21 @@ nm_l3_config_data_ref(const NML3ConfigData *self)
 const NML3ConfigData *
 nm_l3_config_data_ref_and_seal(const NML3ConfigData *self)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
-    ((NML3ConfigData *) self)->is_sealed = TRUE;
-    ((NML3ConfigData *) self)->ref_count++;
+    if (self) {
+        nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+        ((NML3ConfigData *) self)->is_sealed = TRUE;
+        ((NML3ConfigData *) self)->ref_count++;
+    }
     return self;
 }
 
 const NML3ConfigData *
 nm_l3_config_data_seal(const NML3ConfigData *self)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
-    ((NML3ConfigData *) self)->is_sealed = TRUE;
+    if (self) {
+        nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+        ((NML3ConfigData *) self)->is_sealed = TRUE;
+    }
     return self;
 }
 
@@ -871,6 +886,26 @@ nm_l3_config_data_lookup_obj(const NML3ConfigData *self, const NMPObject *obj)
     return nm_dedup_multi_index_lookup_obj(self->multi_idx, idx, obj);
 }
 
+const NMPlatformIP4Address *
+nm_l3_config_data_lookup_address_4(const NML3ConfigData *self,
+                                   in_addr_t             addr,
+                                   guint8                plen,
+                                   in_addr_t             peer_addr)
+{
+    const NMDedupMultiEntry *head;
+    NMPObject                obj_stack;
+
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    nmp_object_stackinit_id_ip4_address(&obj_stack, self->ifindex, addr, plen, peer_addr);
+
+    head = nm_l3_config_data_lookup_obj(self, &obj_stack);
+    if (!head)
+        return NULL;
+
+    return NMP_OBJECT_CAST_IP4_ADDRESS(head->obj);
+}
+
 const NMPlatformIP6Address *
 nm_l3_config_data_lookup_address_6(const NML3ConfigData *self, const struct in6_addr *addr)
 {
@@ -879,7 +914,7 @@ nm_l3_config_data_lookup_address_6(const NML3ConfigData *self, const struct in6_
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
-    /* this works only, because the primary key for a Ipv6 address is the
+    /* this works only, because the primary key for a IPv6 address is the
      * ifindex and the "struct in6_addr". */
     nmp_object_stackinit_id_ip6_address(&obj_stack, self->ifindex, addr);
 
@@ -1104,6 +1139,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex *     multi_idx,
                     obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source;
                     modified                                 = TRUE;
                 }
+
+                /* OR assume_config_once flag */
+                if (obj_new->ip_address.a_assume_config_once
+                    && !obj_old->ip_address.a_assume_config_once) {
+                    obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new);
+                    obj_new_stackinit.ip_address.a_assume_config_once = TRUE;
+                    modified                                          = TRUE;
+                }
                 break;
             case NMP_OBJECT_TYPE_IP4_ROUTE:
             case NMP_OBJECT_TYPE_IP6_ROUTE:
@@ -1113,6 +1156,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex *     multi_idx,
                     obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source;
                     modified                             = TRUE;
                 }
+
+                /* OR assume_config_once flag */
+                if (obj_new->ip_route.r_assume_config_once
+                    && !obj_old->ip_route.r_assume_config_once) {
+                    obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new);
+                    obj_new_stackinit.ip_route.r_assume_config_once = TRUE;
+                    modified                                        = TRUE;
+                }
                 break;
             default:
                 nm_assert_not_reached();
@@ -1168,11 +1219,11 @@ _l3_config_best_default_route_find_better(const NMPObject *obj_cur, const NMPObj
                                NMP_OBJECT_TYPE_IP4_ROUTE,
                                NMP_OBJECT_TYPE_IP6_ROUTE))
               || NMP_OBJECT_GET_TYPE(obj_cur) == NMP_OBJECT_GET_TYPE(obj_cmp));
-    nm_assert(!obj_cur || nmp_object_ip_route_is_best_defaut_route(obj_cur));
+    nm_assert(!obj_cur || nmp_object_ip_route_is_best_default_route(obj_cur));
 
     /* assumes that @obj_cur is already the best default route (or NULL). It checks whether
      * @obj_cmp is also a default route and returns the best of both. */
-    if (obj_cmp && nmp_object_ip_route_is_best_defaut_route(obj_cmp)) {
+    if (obj_cmp && nmp_object_ip_route_is_best_default_route(obj_cmp)) {
         guint32 metric_cur, metric_cmp;
 
         if (!obj_cur)
@@ -1297,9 +1348,16 @@ const NMPObject *
 nm_l3_config_data_get_best_default_route(const NML3ConfigData *self, int addr_family)
 {
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
-    nm_assert_addr_family(addr_family);
 
-    return self->best_default_route_x[NM_IS_IPv4(addr_family)];
+    switch (addr_family) {
+    case AF_INET:
+        return self->best_default_route_4;
+    case AF_INET6:
+        return self->best_default_route_6;
+    case AF_UNSPEC:
+        return self->best_default_route_4 ?: self->best_default_route_6;
+    }
+    return nm_assert_unreachable_val(NULL);
 }
 
 /*****************************************************************************/
@@ -1336,10 +1394,15 @@ _check_and_add_domain(GPtrArray **p_arr, const char *domain)
 gconstpointer
 nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return _garray_inaddr_get(self->nameservers_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1372,7 +1435,13 @@ nm_l3_config_data_clear_nameservers(NML3ConfigData *self, int addr_family)
 const in_addr_t *
 nm_l3_config_data_get_wins(const NML3ConfigData *self, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(out_len);
+
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
 
     return _garray_inaddr_get(self->wins, out_len);
 }
@@ -1420,10 +1489,15 @@ nm_l3_config_data_set_nis_domain(NML3ConfigData *self, const char *nis_domain)
 const char *const *
 nm_l3_config_data_get_domains(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return nm_strv_ptrarray_get_unsafe(self->domains_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1439,10 +1513,15 @@ nm_l3_config_data_add_domain(NML3ConfigData *self, int addr_family, const char *
 const char *const *
 nm_l3_config_data_get_searches(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return nm_strv_ptrarray_get_unsafe(self->searches_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1492,10 +1571,15 @@ nm_l3_config_data_add_dns_option(NML3ConfigData *self, int addr_family, const ch
 const char *const *
 nm_l3_config_data_get_dns_options(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return nm_strv_ptrarray_get_unsafe(self->dns_options_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1622,6 +1706,29 @@ nm_l3_config_data_set_route_table_sync(NML3ConfigData *       self,
 }
 
 NMTernary
+nm_l3_config_data_get_never_default(const NML3ConfigData *self, int addr_family)
+{
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    return NM_TERNARY_FROM_OPTION_BOOL(self->never_default_x[NM_IS_IPv4(addr_family)]);
+}
+
+gboolean
+nm_l3_config_data_set_never_default(NML3ConfigData *self, int addr_family, NMTernary never_default)
+{
+    const int          IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMOptionBool v       = NM_TERNARY_TO_OPTION_BOOL(never_default);
+
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+
+    if (self->never_default_x[IS_IPv4] == v)
+        return FALSE;
+
+    self->never_default_x[IS_IPv4] = v;
+    return TRUE;
+}
+
+NMTernary
 nm_l3_config_data_get_metered(const NML3ConfigData *self)
 {
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
@@ -1682,22 +1789,13 @@ nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu)
     return TRUE;
 }
 
-gboolean
-nm_l3_config_data_set_source(NML3ConfigData *self, NMIPConfigSource source)
-{
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
-
-    if (self->source == source)
-        return FALSE;
-
-    self->source = source;
-    return TRUE;
-}
-
 NMSettingIP6ConfigPrivacy
 nm_l3_config_data_get_ip6_privacy(const NML3ConfigData *self)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    if (!self)
+        return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 
     return self->ip6_privacy;
 }
@@ -1799,7 +1897,7 @@ nm_l3_config_data_set_proxy_pac_script(NML3ConfigData *self, const char *value)
 gboolean
 nm_l3_config_data_get_ndisc_hop_limit(const NML3ConfigData *self, int *out_val)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     if (!self->ndisc_hop_limit_set) {
         NM_SET_OUT(out_val, 0);
@@ -1822,7 +1920,7 @@ nm_l3_config_data_set_ndisc_hop_limit(NML3ConfigData *self, int val)
 gboolean
 nm_l3_config_data_get_ndisc_reachable_time_msec(const NML3ConfigData *self, guint32 *out_val)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     if (!self->ndisc_reachable_time_msec_set) {
         NM_SET_OUT(out_val, 0);
@@ -1845,7 +1943,7 @@ nm_l3_config_data_set_ndisc_reachable_time_msec(NML3ConfigData *self, guint32 va
 gboolean
 nm_l3_config_data_get_ndisc_retrans_timer_msec(const NML3ConfigData *self, guint32 *out_val)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     if (!self->ndisc_retrans_timer_msec_set) {
         NM_SET_OUT(out_val, 0);
@@ -1922,14 +2020,26 @@ nm_l3_config_data_set_dhcp_lease_from_options(NML3ConfigData *self,
 /*****************************************************************************/
 
 static int
-_dedup_multi_index_cmp(const NML3ConfigData *a, const NML3ConfigData *b, NMPObjectType obj_type)
+_dedup_multi_index_cmp(const NML3ConfigData *a,
+                       const NML3ConfigData *b,
+                       NMPObjectType         obj_type,
+                       gboolean              ignore_ifindex)
 {
     const NMDedupMultiHeadEntry *h_a = nm_l3_config_data_lookup_objs(a, obj_type);
     const NMDedupMultiHeadEntry *h_b = nm_l3_config_data_lookup_objs(b, obj_type);
     NMDedupMultiIter             iter_a;
     NMDedupMultiIter             iter_b;
 
+    /* We handle ignore_index via NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX flag, which is
+     * only implemented for certain types. */
+    nm_assert(NM_IN_SET(obj_type,
+                        NMP_OBJECT_TYPE_IP4_ADDRESS,
+                        NMP_OBJECT_TYPE_IP6_ADDRESS,
+                        NMP_OBJECT_TYPE_IP4_ROUTE,
+                        NMP_OBJECT_TYPE_IP6_ROUTE));
+
     NM_CMP_SELF(h_a, h_b);
+
     NM_CMP_DIRECT(h_a->len, h_b->len);
 
     nm_assert(h_a->len > 0);
@@ -1952,7 +2062,10 @@ _dedup_multi_index_cmp(const NML3ConfigData *a, const NML3ConfigData *b, NMPObje
         have_b = nm_platform_dedup_multi_iter_next_obj(&iter_b, &obj_b, obj_type);
         nm_assert(have_b);
 
-        NM_CMP_RETURN(nmp_object_cmp(obj_a, obj_b));
+        NM_CMP_RETURN(nmp_object_cmp_full(obj_a,
+                                          obj_b,
+                                          ignore_ifindex ? NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX
+                                                         : NMP_OBJECT_CMP_FLAGS_NONE));
     }
 }
 
@@ -1961,25 +2074,30 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
                            const NML3ConfigData *b,
                            NML3ConfigCmpFlags    cmp_flags)
 {
-    int IS_IPv4;
+    int      IS_IPv4;
+    gboolean ignore_ifindex;
 
     NM_CMP_SELF(a, b);
 
-    if (!NM_FLAGS_HAS(cmp_flags, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX))
+    ignore_ifindex = NM_FLAGS_HAS(cmp_flags, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX);
+
+    if (!ignore_ifindex)
         NM_CMP_DIRECT(a->ifindex, b->ifindex);
 
     NM_CMP_DIRECT(a->flags, b->flags);
 
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ADDRESS));
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ADDRESS));
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ROUTE));
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ROUTE));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ADDRESS, ignore_ifindex));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ADDRESS, ignore_ifindex));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ROUTE, ignore_ifindex));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ROUTE, ignore_ifindex));
 
     for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
         const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
 
-        NM_CMP_RETURN(
-            nmp_object_cmp(a->best_default_route_x[IS_IPv4], b->best_default_route_x[IS_IPv4]));
+        NM_CMP_RETURN(nmp_object_cmp_full(a->best_default_route_x[IS_IPv4],
+                                          b->best_default_route_x[IS_IPv4],
+                                          ignore_ifindex ? NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX
+                                                         : NMP_OBJECT_CMP_FLAGS_NONE));
 
         NM_CMP_RETURN(
             _garray_inaddr_cmp(a->nameservers_x[IS_IPv4], b->nameservers_x[IS_IPv4], addr_family));
@@ -1999,6 +2117,7 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
             NM_CMP_DIRECT(a->dns_priority_x[IS_IPv4], b->dns_priority_x[IS_IPv4]);
 
         NM_CMP_DIRECT(a->route_table_sync_x[IS_IPv4], b->route_table_sync_x[IS_IPv4]);
+        NM_CMP_DIRECT(a->never_default_x[IS_IPv4], b->never_default_x[IS_IPv4]);
     }
 
     NM_CMP_RETURN(_garray_inaddr_cmp(a->wins, b->wins, AF_INET));
@@ -2066,8 +2185,8 @@ _data_get_direct_route_for_host(const NML3ConfigData *self,
                                          NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
         const NMPlatformIPXRoute *item = NMP_OBJECT_CAST_IPX_ROUTE(item_obj);
 
-        if (nm_ip_addr_is_null(addr_family,
-                               nm_platform_ip_route_get_gateway(addr_family, &item->rx)))
+        if (!nm_ip_addr_is_null(addr_family,
+                                nm_platform_ip_route_get_gateway(addr_family, &item->rx)))
             continue;
 
         if (best_route && best_route->rx.plen > item->rx.plen)
@@ -2114,7 +2233,7 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
     const NMPObject *            my_addr_obj;
     NMDedupMultiIter             iter;
 
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     /* For IPv6 slaac, we explicitly add the device-routes (onlink).
      * As we don't do that for IPv4 and manual IPv6 addresses. Add them here
@@ -2125,9 +2244,6 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
         in_addr_t                         network_4;
         NMPlatformIPXRoute                rx;
 
-        if (my_addr->external)
-            continue;
-
         nm_assert(my_addr->plen <= 32);
         if (my_addr->plen == 0)
             continue;
@@ -2176,120 +2292,137 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
 /*****************************************************************************/
 
 void
-nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
-                                       int             addr_family,
-                                       guint32         route_table,
-                                       guint32         route_metric,
-                                       gboolean        is_vrf)
+nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_family)
 {
-    const int         IS_IPv4                        = NM_IS_IPv4(addr_family);
     gs_unref_ptrarray GPtrArray *extra_onlink_routes = NULL;
-    const NMPObject *            my_addr_obj;
-    const NMPObject *            my_route_obj;
-    NMPlatformIPXRoute           rx;
+    const NMPObject *            obj_src;
     NMDedupMultiIter             iter;
-    in_addr_t                    network_4 = 0;
+    int                          IS_IPv4;
     guint                        i;
 
+    if (addr_family == AF_UNSPEC) {
+        nm_l3_config_data_add_dependent_onlink_routes(self, AF_INET);
+        nm_l3_config_data_add_dependent_onlink_routes(self, AF_INET6);
+        return;
+    }
+
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
-    nm_assert_addr_family(addr_family);
 
     /* For IPv6 slaac, we explicitly add the device-routes (onlink).
      * As we don't do that for IPv4 and manual IPv6 addresses. Add them here
      * as dependent routes. */
 
-    if (!IS_IPv4) {
-        /* Pre-generate multicast route */
-        rx.r6 = (NMPlatformIP6Route){
-            .ifindex            = self->ifindex,
-            .network.s6_addr[0] = 0xffu,
-            .plen               = 8,
-            .table_coerced      = nm_platform_route_table_coerce(RT_TABLE_LOCAL),
-            .type_coerced       = nm_platform_route_type_coerce(RTN_UNICAST),
-            .metric             = 256,
-        };
-        nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
-    }
+    nm_l3_config_data_iter_obj_for_each (&iter, self, &obj_src, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
+        const NMPlatformIPXRoute *route_src = NMP_OBJECT_CAST_IPX_ROUTE(obj_src);
+        NMPObject *               new_route;
+        NMPlatformIPXRoute *      new_r;
+        const NMIPAddr *          p_gateway;
 
-    nm_l3_config_data_iter_obj_for_each (&iter,
-                                         self,
-                                         &my_addr_obj,
-                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
-        const NMPlatformIPXAddress *const my_addr = NMP_OBJECT_CAST_IPX_ADDRESS(my_addr_obj);
+        p_gateway = nm_platform_ip_route_get_gateway(addr_family, &route_src->rx);
+
+        if (nm_ip_addr_is_null(addr_family, p_gateway))
+            continue;
 
-        if (my_addr->ax.external)
+        if (_data_get_direct_route_for_host(
+                self,
+                addr_family,
+                p_gateway,
+                nm_platform_route_table_uncoerce(route_src->rx.table_coerced, TRUE)))
             continue;
 
+        new_route = nmp_object_clone(obj_src, FALSE);
+        new_r     = NMP_OBJECT_CAST_IPX_ROUTE(new_route);
         if (IS_IPv4) {
-            nm_assert(my_addr->a4.plen <= 32);
-            if (my_addr->a4.plen == 0)
-                continue;
+            new_r->r4.network = route_src->r4.gateway;
+            new_r->r4.plen    = 32;
+            new_r->r4.gateway = 0;
+        } else {
+            new_r->r6.network = route_src->r6.gateway;
+            new_r->r6.plen    = 128;
+            new_r->r6.gateway = in6addr_any;
         }
 
-        if (IS_IPv4) {
-            rx.r4 = (NMPlatformIP4Route){
-                .ifindex      = self->ifindex,
-                .rt_source    = NM_IP_CONFIG_SOURCE_KERNEL,
-                .network      = my_addr->a4.address,
-                .plen         = 32,
-                .pref_src     = my_addr->a4.address,
-                .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL),
-                .scope_inv    = nm_platform_route_scope_inv(RT_SCOPE_HOST),
-                .table_coerced =
-                    nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL),
-            };
-        } else {
-            rx.r6 = (NMPlatformIP6Route){
-                .ifindex      = self->ifindex,
-                .network      = my_addr->a6.address,
-                .plen         = 128,
-                .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL),
-                .metric       = 0,
-                .table_coerced =
-                    nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL),
-            };
+        /* we cannot add the route right away, because that invalidates the iteration. */
+        if (!extra_onlink_routes)
+            extra_onlink_routes = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+        g_ptr_array_add(extra_onlink_routes, new_route);
+    }
+    if (extra_onlink_routes) {
+        for (i = 0; i < extra_onlink_routes->len; i++) {
+            nm_l3_config_data_add_route_full(self,
+                                             addr_family,
+                                             extra_onlink_routes->pdata[i],
+                                             NULL,
+                                             NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
+                                             NULL,
+                                             NULL);
         }
-        nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
+    }
+}
 
-        if (my_addr->ax.plen == 0)
+void
+nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
+                                              int                   addr_family,
+                                              guint32               route_table,
+                                              guint32               route_metric,
+                                              const NML3ConfigData *source)
+{
+    const int          IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMPObject *  obj_src;
+    NMPlatformIPXRoute rx;
+    NMDedupMultiIter   iter;
+
+    nm_assert_addr_family(addr_family);
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(source, TRUE));
+    nm_assert(self != source);
+
+    /* For IPv6 slaac, we explicitly add the device-routes (onlink) and track them
+     * as regular routes in NML3ConfigData.
+     *
+     * For IPv4 and for manual IPv6 addresses we don't do that. Instead, add those
+     * routes automatically afterwards.
+     *
+     * As route-table/metric is associated with the source l3cd, we need to process
+     * all source l3cds separately. */
+
+    nm_l3_config_data_iter_obj_for_each (&iter,
+                                         source,
+                                         &obj_src,
+                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
+        const NMPlatformIPXAddress *const addr_src = NMP_OBJECT_CAST_IPX_ADDRESS(obj_src);
+        const NMPlatformIPXAddress *      addr_dst;
+
+        addr_dst = NMP_OBJECT_CAST_IPX_ADDRESS(
+            nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj(self, obj_src)));
+        if (!addr_dst)
             continue;
 
         if (IS_IPv4) {
-            network_4 =
-                nm_utils_ip4_address_clear_host_address(my_addr->a4.peer_address, my_addr->a4.plen);
+            NMPlatformIPXRoute       r_stack;
+            const NMPlatformIPRoute *r;
 
-            if (nm_utils_ip4_address_is_zeronet(network_4)) {
-                /* Kernel doesn't add device-routes for destinations that
-                 * start with 0.x.y.z. Skip them. */
+            if (addr_dst->a4.a_acd_not_ready)
                 continue;
-            }
 
-            if (my_addr->a4.plen == 32 && my_addr->a4.address == my_addr->a4.peer_address) {
-                /* Kernel doesn't add device-routes for /32 addresses unless
-                 * they have a peer. */
-                continue;
-            }
+            r = (NMPlatformIPRoute *) nm_platform_ip4_address_generate_device_route(&addr_src->a4,
+                                                                                    self->ifindex,
+                                                                                    route_table,
+                                                                                    route_metric,
+                                                                                    &r_stack.r4);
+            if (r)
+                nm_l3_config_data_add_route(self, addr_family, NULL, r);
         } else {
-            if (NM_FLAGS_HAS(my_addr->a6.n_ifa_flags, IFA_F_NOPREFIXROUTE))
+            const gboolean has_peer = !IN6_IS_ADDR_UNSPECIFIED(&addr_src->a6.peer_address);
+            int            routes_i;
+
+            if (addr_src->ax.plen == 0)
                 continue;
-        }
 
-        if (IS_IPv4) {
-            rx.r4 = (NMPlatformIP4Route){
-                .ifindex       = self->ifindex,
-                .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
-                .network       = network_4,
-                .plen          = my_addr->a4.plen,
-                .pref_src      = my_addr->a4.address,
-                .table_coerced = nm_platform_route_table_coerce(route_table),
-                .metric        = route_metric,
-                .scope_inv     = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
-            };
-            nm_platform_ip_route_normalize(addr_family, &rx.rx);
-            nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
-        } else {
-            const gboolean has_peer = !IN6_IS_ADDR_UNSPECIFIED(&my_addr->a6.peer_address);
-            int            routes_i;
+            if (NM_FLAGS_HAS(addr_src->a6.n_ifa_flags, IFA_F_NOPREFIXROUTE))
+                continue;
 
             /* If we have an IPv6 peer, we add two /128 routes
              * (unless, both addresses are identical). */
@@ -2299,20 +2432,20 @@ nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
                 guint8                 plen;
 
                 if (routes_i == 1 && has_peer
-                    && IN6_ARE_ADDR_EQUAL(&my_addr->a6.address, &my_addr->a6.peer_address))
+                    && IN6_ARE_ADDR_EQUAL(&addr_src->a6.address, &addr_src->a6.peer_address))
                     break;
 
                 if (has_peer) {
                     if (routes_i == 0)
-                        a6 = &my_addr->a6.address;
+                        a6 = &addr_src->a6.address;
                     else
-                        a6 = &my_addr->a6.peer_address;
+                        a6 = &addr_src->a6.peer_address;
                     plen = 128;
                 } else {
                     a6   = nm_utils_ip6_address_clear_host_address(&a6_stack,
-                                                                 &my_addr->a6.address,
-                                                                 my_addr->a6.plen);
-                    plen = my_addr->a6.plen;
+                                                                 &addr_src->a6.address,
+                                                                 addr_src->a6.plen);
+                    plen = addr_src->a6.plen;
                 }
 
                 rx.r6 = (NMPlatformIP6Route){
@@ -2328,73 +2461,16 @@ nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
             }
         }
     }
-
-    nm_l3_config_data_iter_obj_for_each (&iter,
-                                         self,
-                                         &my_route_obj,
-                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
-        const NMPlatformIPXRoute *my_route = NMP_OBJECT_CAST_IPX_ROUTE(my_route_obj);
-        NMPObject *               new_route;
-        NMPlatformIPXRoute *      new_r;
-        const NMIPAddr *          p_gateway;
-
-        if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(my_route)
-            || NM_IS_IP_CONFIG_SOURCE_RTPROT(my_route->rx.rt_source))
-            continue;
-
-        p_gateway = nm_platform_ip_route_get_gateway(addr_family, &my_route->rx);
-
-        if (nm_ip_addr_is_null(addr_family, p_gateway))
-            continue;
-
-        if (_data_get_direct_route_for_host(
-                self,
-                addr_family,
-                p_gateway,
-                nm_platform_route_table_uncoerce(my_route->rx.table_coerced, TRUE)))
-            continue;
-
-        new_route = nmp_object_clone(my_route_obj, FALSE);
-        new_r     = NMP_OBJECT_CAST_IPX_ROUTE(new_route);
-        if (IS_IPv4) {
-            new_r->r4.network = my_route->r4.gateway;
-            new_r->r4.plen    = 32;
-            new_r->r4.gateway = 0;
-        } else {
-            new_r->r6.network = my_route->r6.gateway;
-            new_r->r6.plen    = 128;
-            new_r->r6.gateway = in6addr_any;
-        }
-
-        /* we cannot add the route right away, because that invalidates the iteration. */
-        if (!extra_onlink_routes)
-            extra_onlink_routes = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
-        g_ptr_array_add(extra_onlink_routes, new_route);
-    }
-    if (extra_onlink_routes) {
-        for (i = 0; i < extra_onlink_routes->len; i++) {
-            nm_l3_config_data_add_route_full(self,
-                                             addr_family,
-                                             extra_onlink_routes->pdata[i],
-                                             NULL,
-                                             NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
-                                             NULL,
-                                             NULL);
-        }
-    }
 }
 
 /*****************************************************************************/
 
 static void
-_init_from_connection_ip(NML3ConfigData *self,
-                         int             addr_family,
-                         NMConnection *  connection,
-                         guint32         route_table,
-                         guint32         route_metric)
+_init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *connection)
 {
     const int          IS_IPv4 = NM_IS_IPv4(addr_family);
     NMSettingIPConfig *s_ip;
+    gboolean           never_default;
     guint              naddresses;
     guint              nroutes;
     guint              nnameservers;
@@ -2415,25 +2491,28 @@ _init_from_connection_ip(NML3ConfigData *self,
     if (!s_ip)
         return;
 
-    if (!nm_setting_ip_config_get_never_default(s_ip)
-        && (gateway_str = nm_setting_ip_config_get_gateway(s_ip))
+    never_default = nm_setting_ip_config_get_never_default(s_ip);
+
+    nm_l3_config_data_set_never_default(self, addr_family, !!never_default);
+
+    if (!never_default && (gateway_str = nm_setting_ip_config_get_gateway(s_ip))
         && inet_pton(addr_family, gateway_str, &gateway_bin) == 1
         && !nm_ip_addr_is_null(addr_family, &gateway_bin)) {
         NMPlatformIPXRoute r;
 
         if (IS_IPv4) {
             r.r4 = (NMPlatformIP4Route){
-                .rt_source     = NM_IP_CONFIG_SOURCE_USER,
-                .gateway       = gateway_bin.addr4,
-                .table_coerced = nm_platform_route_table_coerce(route_table),
-                .metric        = route_metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
+                .gateway    = gateway_bin.addr4,
+                .table_any  = TRUE,
+                .metric_any = TRUE,
             };
         } else {
             r.r6 = (NMPlatformIP6Route){
-                .rt_source     = NM_IP_CONFIG_SOURCE_USER,
-                .gateway       = gateway_bin.addr6,
-                .table_coerced = nm_platform_route_table_coerce(route_table),
-                .metric        = route_metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
+                .gateway    = gateway_bin.addr6,
+                .table_any  = TRUE,
+                .metric_any = TRUE,
             };
         }
 
@@ -2488,6 +2567,7 @@ _init_from_connection_ip(NML3ConfigData *self,
         NMIPAddr           next_hop_bin;
         gint64             metric64;
         guint32            metric;
+        gboolean           metric_any;
         guint              plen;
 
         nm_assert(nm_ip_route_get_family(s_route) == addr_family);
@@ -2496,11 +2576,14 @@ _init_from_connection_ip(NML3ConfigData *self,
         nm_ip_route_get_next_hop_binary(s_route, &next_hop_bin);
 
         metric64 = nm_ip_route_get_metric(s_route);
-        if (metric64 < 0)
-            metric = route_metric;
-        else
-            metric = metric64;
-        metric = nm_utils_ip_route_metric_normalize(addr_family, metric);
+        if (metric64 < 0) {
+            metric_any = TRUE;
+            metric     = 0;
+        } else {
+            metric_any = FALSE;
+            metric     = metric64;
+            metric     = nm_utils_ip_route_metric_normalize(addr_family, metric);
+        }
 
         plen = nm_ip_route_get_prefix(s_route);
 
@@ -2508,25 +2591,27 @@ _init_from_connection_ip(NML3ConfigData *self,
 
         if (IS_IPv4) {
             r.r4 = (NMPlatformIP4Route){
-                .network   = network_bin.addr4,
-                .plen      = nm_ip_route_get_prefix(s_route),
-                .gateway   = next_hop_bin.addr4,
-                .metric    = metric,
-                .rt_source = NM_IP_CONFIG_SOURCE_USER,
+                .network    = network_bin.addr4,
+                .plen       = nm_ip_route_get_prefix(s_route),
+                .gateway    = next_hop_bin.addr4,
+                .metric_any = metric_any,
+                .metric     = metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
             };
             nm_assert(r.r4.plen <= 32);
         } else {
             r.r6 = (NMPlatformIP6Route){
-                .network   = network_bin.addr6,
-                .plen      = nm_ip_route_get_prefix(s_route),
-                .gateway   = next_hop_bin.addr6,
-                .metric    = metric,
-                .rt_source = NM_IP_CONFIG_SOURCE_USER,
+                .network    = network_bin.addr6,
+                .plen       = nm_ip_route_get_prefix(s_route),
+                .gateway    = next_hop_bin.addr6,
+                .metric_any = metric_any,
+                .metric     = metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
             };
             nm_assert(r.r6.plen <= 128);
         }
 
-        nm_utils_ip_route_attribute_to_platform(addr_family, s_route, &r.rx, route_table);
+        nm_utils_ip_route_attribute_to_platform(addr_family, s_route, &r.rx, -1);
 
         nm_l3_config_data_add_route(self, addr_family, NULL, &r.rx);
     }
@@ -2550,34 +2635,36 @@ _init_from_connection_ip(NML3ConfigData *self,
     }
 
     idx = 0;
-    while ((idx = nm_setting_ip_config_next_valid_dns_option(s_ip, i)) >= 0) {
+    while ((idx = nm_setting_ip_config_next_valid_dns_option(s_ip, idx)) >= 0) {
         nm_l3_config_data_add_dns_option(self,
                                          addr_family,
-                                         nm_setting_ip_config_get_dns_option(s_ip, i));
+                                         nm_setting_ip_config_get_dns_option(s_ip, idx));
         idx++;
     }
 
     nm_l3_config_data_set_dns_priority(self,
                                        addr_family,
                                        nm_setting_ip_config_get_dns_priority(s_ip));
+
+    if (!IS_IPv4) {
+        nm_l3_config_data_set_ip6_privacy(
+            self,
+            nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip)));
+    }
 }
 
 NML3ConfigData *
 nm_l3_config_data_new_from_connection(NMDedupMultiIndex *multi_idx,
                                       int                ifindex,
-                                      NMConnection *     connection,
-                                      guint32            route_table_4,
-                                      guint32            route_table_6,
-                                      guint32            route_metric_4,
-                                      guint32            route_metric_6)
+                                      NMConnection *     connection)
 {
     NML3ConfigData *self;
     NMSettingProxy *s_proxy;
 
-    self = nm_l3_config_data_new(multi_idx, ifindex);
+    self = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_USER);
 
-    _init_from_connection_ip(self, AF_INET, connection, route_table_4, route_metric_4);
-    _init_from_connection_ip(self, AF_INET6, connection, route_table_6, route_metric_6);
+    _init_from_connection_ip(self, AF_INET, connection);
+    _init_from_connection_ip(self, AF_INET6, connection);
 
     s_proxy = _nm_connection_get_setting(connection, NM_TYPE_SETTING_PROXY);
     if (s_proxy) {
@@ -2693,7 +2780,7 @@ nm_l3_config_data_new_from_platform(NMDedupMultiIndex *       multi_idx,
     if (nm_platform_link_get_master(platform, ifindex) > 0)
         return NULL;
 
-    self = nm_l3_config_data_new(multi_idx, ifindex);
+    self = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_KERNEL);
 
     _init_from_platform(self, AF_INET, platform, ipv6_privacy_rfc4941);
     _init_from_platform(self, AF_INET6, platform, ipv6_privacy_rfc4941);
@@ -2710,13 +2797,16 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                         const guint32 *       default_route_table_x /* length 2, for IS_IPv4 */,
                         const guint32 *       default_route_metric_x /* length 2, for IS_IPv4 */,
                         const guint32 *       default_route_penalty_x /* length 2, for IS_IPv4 */,
-                        NML3ConfigMergeHookAddObj hook_add_addr,
+                        const int *           default_dns_priority_x /* length 2, for IS_IPv4 */,
+                        NML3ConfigMergeHookAddObj hook_add_obj,
                         gpointer                  hook_user_data)
 {
     static const guint32 x_default_route_table_x[2]   = {RT_TABLE_MAIN, RT_TABLE_MAIN};
     static const guint32 x_default_route_metric_x[2]  = {NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
                                                         NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4};
     static const guint32 x_default_route_penalty_x[2] = {0, 0};
+    static const int     x_default_dns_priority_x[2]  = {NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                                                    NM_DNS_PRIORITY_DEFAULT_NORMAL};
     NMDedupMultiIter     iter;
     const NMPObject *    obj;
     int                  IS_IPv4;
@@ -2730,6 +2820,8 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
         default_route_metric_x = x_default_route_metric_x;
     if (!default_route_penalty_x)
         default_route_penalty_x = x_default_route_penalty_x;
+    if (!default_dns_priority_x)
+        default_dns_priority_x = x_default_dns_priority_x;
 
     nm_assert(default_route_table_x[0] != 0);
     nm_assert(default_route_table_x[1] != 0);
@@ -2747,51 +2839,100 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                                              src,
                                              &obj,
                                              NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
-            NMPlatformIPXAddress       addr_stack;
-            const NMPlatformIPAddress *addr             = NULL;
-            NMTernary                  ip4acd_not_ready = NM_TERNARY_DEFAULT;
+            const NMPlatformIPAddress *a_src = NMP_OBJECT_CAST_IP_ADDRESS(obj);
+            NMPlatformIPXAddress       a;
+            NML3ConfigMergeHookResult  hook_result = {
+                .ip4acd_not_ready   = NM_OPTION_BOOL_DEFAULT,
+                .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+            };
+
+#define _ensure_a()                                       \
+    G_STMT_START                                          \
+    {                                                     \
+        if (a_src != &a.ax) {                             \
+            if (IS_IPv4)                                  \
+                a.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); \
+            else                                          \
+                a.a6 = *NMP_OBJECT_CAST_IP6_ADDRESS(obj); \
+            a_src = &a.ax;                                \
+        }                                                 \
+    }                                                     \
+    G_STMT_END
 
-            if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data))
+            if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data))
                 continue;
 
-            if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT
-                && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) {
-                addr_stack.a4                  = *NMP_OBJECT_CAST_IP4_ADDRESS(obj);
-                addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready);
-                addr                           = &addr_stack.ax;
-            } else
-                nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT);
+            nm_assert(IS_IPv4 || hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+
+            if (a_src->ifindex != self->ifindex) {
+                _ensure_a();
+                a.ax.ifindex = self->ifindex;
+            }
+
+            if (hook_result.ip4acd_not_ready != NM_OPTION_BOOL_DEFAULT && IS_IPv4
+                && (!!hook_result.ip4acd_not_ready)
+                       != ((const NMPlatformIP4Address *) a_src)->a_acd_not_ready) {
+                _ensure_a();
+                a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready);
+            }
+
+            if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT
+                && (!!hook_result.assume_config_once) != a_src->a_assume_config_once) {
+                _ensure_a();
+                a.ax.a_assume_config_once = (!!hook_result.assume_config_once);
+            }
 
             nm_l3_config_data_add_address_full(self,
                                                addr_family,
-                                               addr ? NULL : obj,
-                                               addr,
+                                               a_src == &a.ax ? NULL : obj,
+                                               a_src == &a.ax ? a_src : NULL,
                                                NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
                                                NULL);
         }
 
+#undef _ensure_a
+
         if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) {
             nm_l3_config_data_iter_obj_for_each (&iter,
                                                  src,
                                                  &obj,
                                                  NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
-                const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
-                NMPlatformIPXRoute       r;
+                const NMPlatformIPRoute * r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
+                NMPlatformIPXRoute        r;
+                NML3ConfigMergeHookResult hook_result = {
+                    .ip4acd_not_ready   = NM_OPTION_BOOL_DEFAULT,
+                    .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+                };
 
 #define _ensure_r()                                     \
     G_STMT_START                                        \
     {                                                   \
         if (r_src != &r.rx) {                           \
-            r_src = &r.rx;                              \
             if (IS_IPv4)                                \
                 r.r4 = *NMP_OBJECT_CAST_IP4_ROUTE(obj); \
             else                                        \
                 r.r6 = *NMP_OBJECT_CAST_IP6_ROUTE(obj); \
-            r.rx.ifindex = self->ifindex;               \
+            r_src = &r.rx;                              \
         }                                               \
     }                                                   \
     G_STMT_END
 
+                if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data))
+                    continue;
+
+                nm_assert(hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+
+                if (r_src->ifindex != self->ifindex) {
+                    _ensure_r();
+                    r.rx.ifindex = self->ifindex;
+                }
+
+                if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT
+                    && (!!hook_result.assume_config_once) != r_src->r_assume_config_once) {
+                    _ensure_r();
+                    r.rx.r_assume_config_once = (!!hook_result.assume_config_once);
+                }
+
                 if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
                     if (r_src->table_any) {
                         _ensure_r();
@@ -2848,12 +2989,20 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
 
         if (!NM_FLAGS_ANY(self->flags, has_dns_priority_flag)
             && NM_FLAGS_ANY(src->flags, has_dns_priority_flag)) {
-            self->dns_priority_x[IS_IPv4] = src->dns_priority_x[IS_IPv4];
+            int p = src->dns_priority_x[IS_IPv4];
+
+            if (p == 0 && !NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE))
+                p = default_dns_priority_x[IS_IPv4];
+
+            self->dns_priority_x[IS_IPv4] = p;
             self->flags |= has_dns_priority_flag;
         }
 
         if (self->route_table_sync_x[IS_IPv4] == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE)
             self->route_table_sync_x[IS_IPv4] = src->route_table_sync_x[IS_IPv4];
+
+        if (self->never_default_x[IS_IPv4] == NM_OPTION_BOOL_DEFAULT)
+            self->never_default_x[IS_IPv4] = src->never_default_x[IS_IPv4];
     }
 
     if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) {
@@ -2933,7 +3082,7 @@ nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex)
     if (ifindex <= 0)
         ifindex = src->ifindex;
 
-    self = nm_l3_config_data_new(src->multi_idx, ifindex);
+    self = nm_l3_config_data_new(src->multi_idx, ifindex, src->source);
     nm_l3_config_data_merge(self,
                             src,
                             NM_L3_CONFIG_MERGE_FLAGS_CLONE,
@@ -2941,9 +3090,15 @@ nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex)
                             NULL,
                             NULL,
                             NULL,
+                            NULL,
                             NULL);
 
-    nm_assert(nm_l3_config_data_cmp_full(src, self, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX) == 0);
+    nm_assert(nm_l3_config_data_cmp_full(src,
+                                         self,
+                                         src->ifindex != ifindex
+                                             ? NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX
+                                             : NM_L3_CONFIG_CMP_FLAGS_NONE)
+              == 0);
     nm_assert(nm_l3_config_data_get_ifindex(self) == ifindex);
 
     return self;