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.c539
1 files changed, 387 insertions, 152 deletions
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 033c8942..03593ea2 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -117,8 +117,10 @@ struct _NML3ConfigData {
         NMIPRouteTableSyncMode route_table_sync_x[2];
     };
 
-    NMSettingConnectionMdns  mdns;
-    NMSettingConnectionLlmnr llmnr;
+    NMSettingConnectionMdns       mdns;
+    NMSettingConnectionLlmnr      llmnr;
+    NMSettingConnectionDnsOverTls dns_over_tls;
+    NMUtilsIPv6IfaceId            ip6_token;
 
     NML3ConfigDatFlags flags;
 
@@ -177,7 +179,7 @@ static GArray *
 _garray_inaddr_clone(const GArray *src, int addr_family)
 {
     const gsize elt_size = nm_utils_addr_family_to_size(addr_family);
-    GArray *    dst;
+    GArray     *dst;
 
     nm_assert_addr_family(addr_family);
 
@@ -226,7 +228,7 @@ next:;
 }
 
 static gssize
-_garray_inaddr_find(GArray *                           arr,
+_garray_inaddr_find(GArray                            *arr,
                     int                                addr_family,
                     gconstpointer                      needle,
                     /* (const NMIPAddr **) */ gpointer out_addr)
@@ -342,8 +344,8 @@ _strv_ptrarray_merge(GPtrArray **p_dst, const GPtrArray *src)
 
 void
 nm_l3_config_data_log(const NML3ConfigData *self,
-                      const char *          title,
-                      const char *          prefix,
+                      const char           *title,
+                      const char           *prefix,
                       NMLogLevel            log_level,
                       NMLogDomain           log_domain)
 {
@@ -443,7 +445,7 @@ nm_l3_config_data_log(const NML3ConfigData *self,
             if (self->ndisc_hop_limit_set || self->ndisc_reachable_time_msec_set
                 || self->ndisc_retrans_timer_msec_set) {
                 gsize       l        = sizeof(sbuf);
-                char *      p        = sbuf;
+                char       *p        = sbuf;
                 const char *s_prefix = "ndisc: ";
 
                 if (self->ndisc_hop_limit_set) {
@@ -524,7 +526,7 @@ nm_l3_config_data_log(const NML3ConfigData *self,
         if (self->dhcp_lease_x[IS_IPv4]) {
             gs_free NMUtilsNamedValue *options_free = NULL;
             NMUtilsNamedValue          options_buffer[30];
-            NMUtilsNamedValue *        options;
+            NMUtilsNamedValue         *options;
             guint                      options_len;
 
             options = nm_utils_named_values_from_strdict(
@@ -570,6 +572,21 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                                            NULL)));
     }
 
+    if (self->dns_over_tls != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT) {
+        gs_free char *s = NULL;
+
+        _L("dns-over-tls: %s",
+           (s = _nm_utils_enum_to_str_full(nm_setting_connection_dns_over_tls_get_type(),
+                                           self->dns_over_tls,
+                                           " ",
+                                           NULL)));
+    }
+
+    if (self->ip6_token.id != 0) {
+        _L("ipv6-token: %s",
+           nm_utils_inet6_interface_identifier_to_token(&self->ip6_token, sbuf_addr));
+    }
+
     if (self->metered != NM_TERNARY_DEFAULT)
         _L("metered: %s", self->metered ? "yes" : "no");
 
@@ -617,22 +634,22 @@ _route_valid(int addr_family, gconstpointer r)
 static gboolean
 _NM_IS_L3_CONFIG_DATA(const NML3ConfigData *self, gboolean allow_sealed)
 {
-    nm_assert(!self || (self->ifindex > 0 && self->multi_idx && self->ref_count > 0));
+    nm_assert(!self || (self->ifindex >= 0 && self->multi_idx && self->ref_count > 0));
     return self && self->ref_count > 0 && (allow_sealed || !self->is_sealed);
 }
 
 static void
 _idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type,
-                        const NMDedupMultiObj *    obj,
-                        NMHashState *              h)
+                        const NMDedupMultiObj     *obj,
+                        NMHashState               *h)
 {
     nmp_object_id_hash_update((NMPObject *) obj, h);
 }
 
 static gboolean
 _idx_obj_id_equal(const NMDedupMultiIdxType *idx_type,
-                  const NMDedupMultiObj *    obj_a,
-                  const NMDedupMultiObj *    obj_b)
+                  const NMDedupMultiObj     *obj_a,
+                  const NMDedupMultiObj     *obj_b)
 {
     return nmp_object_id_equal((NMPObject *) obj_a, (NMPObject *) obj_b);
 }
@@ -666,6 +683,7 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSourc
         .multi_idx                     = nm_dedup_multi_index_ref(multi_idx),
         .mdns                          = NM_SETTING_CONNECTION_MDNS_DEFAULT,
         .llmnr                         = NM_SETTING_CONNECTION_LLMNR_DEFAULT,
+        .dns_over_tls                  = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT,
         .flags                         = NM_L3_CONFIG_DAT_FLAGS_NONE,
         .metered                       = NM_TERNARY_DEFAULT,
         .proxy_browser_only            = NM_TERNARY_DEFAULT,
@@ -788,7 +806,7 @@ nm_l3_config_data_unref(const NML3ConfigData *self)
 static const NMDedupMultiEntry *
 _lookup_route(const NMDedupMultiIndex *multi_idx,
               const DedupMultiIdxType *idx_type,
-              const NMPObject *        needle)
+              const NMPObject         *needle)
 {
     const NMDedupMultiEntry *entry;
 
@@ -828,7 +846,7 @@ nm_l3_config_data_lookup_route_obj(const NML3ConfigData *self, const NMPObject *
 }
 
 const NMDedupMultiEntry *
-nm_l3_config_data_lookup_route(const NML3ConfigData *   self,
+nm_l3_config_data_lookup_route(const NML3ConfigData    *self,
                                int                      addr_family,
                                const NMPlatformIPRoute *needle)
 {
@@ -952,7 +970,7 @@ gboolean
 nm_l3_config_data_has_routes_with_type_local(const NML3ConfigData *self, int addr_family)
 {
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    NML3ConfigData * self_mutable;
+    NML3ConfigData  *self_mutable;
     NMDedupMultiIter iter;
     const NMPObject *obj;
     gboolean         val;
@@ -1020,7 +1038,7 @@ nm_l3_config_data_get_flags(const NML3ConfigData *self)
 }
 
 void
-nm_l3_config_data_set_flags_full(NML3ConfigData *   self,
+nm_l3_config_data_set_flags_full(NML3ConfigData    *self,
                                  NML3ConfigDatFlags flags,
                                  NML3ConfigDatFlags mask)
 {
@@ -1053,14 +1071,14 @@ nm_l3_config_data_get_first_obj(const NML3ConfigData *self,
 /*****************************************************************************/
 
 static gboolean
-_l3_config_data_add_obj(NMDedupMultiIndex *     multi_idx,
-                        DedupMultiIdxType *     idx_type,
+_l3_config_data_add_obj(NMDedupMultiIndex      *multi_idx,
+                        DedupMultiIdxType      *idx_type,
                         int                     ifindex,
-                        const NMPObject *       obj_new,
+                        const NMPObject        *obj_new,
                         const NMPlatformObject *pl_new,
                         NML3ConfigAddFlags      add_flags,
-                        const NMPObject **      out_obj_old /* returns a reference! */,
-                        const NMPObject **      out_obj_new /* does not return a reference */)
+                        const NMPObject       **out_obj_old /* returns a reference! */,
+                        const NMPObject       **out_obj_new /* does not return a reference */)
 {
     NMPObject                obj_new_stackinit;
     const NMDedupMultiEntry *entry_old;
@@ -1076,8 +1094,22 @@ _l3_config_data_add_obj(NMDedupMultiIndex *     multi_idx,
                         NMP_OBJECT_TYPE_IP4_ROUTE,
                         NMP_OBJECT_TYPE_IP6_ADDRESS,
                         NMP_OBJECT_TYPE_IP6_ROUTE));
+    nm_assert((!!obj_new) != (!!pl_new));
+
+    if (NM_IN_SET(idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)) {
+        const NMPlatformIPRoute *r;
+
+        r = obj_new ? NMP_OBJECT_CAST_IP_ROUTE(obj_new) : (NMPlatformIPRoute *) pl_new;
+
+        if (nm_platform_route_type_is_nodev(nm_platform_route_type_uncoerce(r->type_coerced))) {
+            /* such routes don't have a device/next-hop. We track them without ifindex. */
+            ifindex = 0;
+        }
+    }
+
     /* we go through extra lengths to accept a full obj_new object. That one,
-     * can be reused by increasing the ref-count. */
+     * can be reused by increasing the ref-count. We thus accept any ifindex, and
+     * set it here. */
     if (!obj_new) {
         nm_assert(pl_new);
         obj_new = nmp_object_stackinit(&obj_new_stackinit, idx_type->obj_type, pl_new);
@@ -1258,12 +1290,12 @@ _l3_config_best_default_route_find_better(const NMPObject *obj_cur, const NMPObj
 }
 
 gboolean
-nm_l3_config_data_add_address_full(NML3ConfigData *           self,
+nm_l3_config_data_add_address_full(NML3ConfigData            *self,
                                    int                        addr_family,
-                                   const NMPObject *          obj_new,
+                                   const NMPObject           *obj_new,
                                    const NMPlatformIPAddress *pl_new,
                                    NML3ConfigAddFlags         add_flags,
-                                   const NMPObject **         out_obj_new)
+                                   const NMPObject          **out_obj_new)
 {
     const NMPObject *new;
     gboolean changed;
@@ -1287,24 +1319,24 @@ nm_l3_config_data_add_address_full(NML3ConfigData *           self,
 
 static gboolean
 _l3_config_best_default_route_merge(const NMPObject **best_default_route,
-                                    const NMPObject * new_candidate)
+                                    const NMPObject  *new_candidate)
 {
     new_candidate = _l3_config_best_default_route_find_better(*best_default_route, new_candidate);
     return nmp_object_ref_set(best_default_route, new_candidate);
 }
 
 gboolean
-nm_l3_config_data_add_route_full(NML3ConfigData *         self,
+nm_l3_config_data_add_route_full(NML3ConfigData          *self,
                                  int                      addr_family,
-                                 const NMPObject *        obj_new,
+                                 const NMPObject         *obj_new,
                                  const NMPlatformIPRoute *pl_new,
                                  NML3ConfigAddFlags       add_flags,
-                                 const NMPObject **       out_obj_new,
-                                 gboolean *               out_changed_best_default_route)
+                                 const NMPObject        **out_obj_new,
+                                 gboolean                *out_changed_best_default_route)
 {
-    const int            IS_IPv4            = NM_IS_IPv4(addr_family);
+    const int                       IS_IPv4 = NM_IS_IPv4(addr_family);
     nm_auto_nmpobj const NMPObject *obj_old = NULL;
-    const NMPObject *               obj_new_2;
+    const NMPObject                *obj_new_2;
     gboolean                        changed                    = FALSE;
     gboolean                        changed_best_default_route = FALSE;
 
@@ -1407,7 +1439,7 @@ nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, g
 }
 
 gboolean
-nm_l3_config_data_add_nameserver(NML3ConfigData *                       self,
+nm_l3_config_data_add_nameserver(NML3ConfigData                        *self,
                                  int                                    addr_family,
                                  gconstpointer /* (const NMIPAddr *) */ nameserver)
 {
@@ -1679,6 +1711,26 @@ nm_l3_config_data_set_llmnr(NML3ConfigData *self, NMSettingConnectionLlmnr llmnr
     return TRUE;
 }
 
+NMSettingConnectionDnsOverTls
+nm_l3_config_data_get_dns_over_tls(const NML3ConfigData *self)
+{
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    return self->dns_over_tls;
+}
+
+gboolean
+nm_l3_config_data_set_dns_over_tls(NML3ConfigData *self, NMSettingConnectionDnsOverTls dns_over_tls)
+{
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+
+    if (self->dns_over_tls == dns_over_tls)
+        return FALSE;
+
+    self->dns_over_tls = dns_over_tls;
+    return TRUE;
+}
+
 NMIPRouteTableSyncMode
 nm_l3_config_data_get_route_table_sync(const NML3ConfigData *self, int addr_family)
 {
@@ -1689,7 +1741,7 @@ nm_l3_config_data_get_route_table_sync(const NML3ConfigData *self, int addr_fami
 }
 
 gboolean
-nm_l3_config_data_set_route_table_sync(NML3ConfigData *       self,
+nm_l3_config_data_set_route_table_sync(NML3ConfigData        *self,
                                        int                    addr_family,
                                        NMIPRouteTableSyncMode route_table_sync)
 {
@@ -1817,6 +1869,26 @@ nm_l3_config_data_set_ip6_privacy(NML3ConfigData *self, NMSettingIP6ConfigPrivac
     return TRUE;
 }
 
+NMUtilsIPv6IfaceId
+nm_l3_config_data_get_ip6_token(const NML3ConfigData *self)
+{
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    return self->ip6_token;
+}
+
+gboolean
+nm_l3_config_data_set_ip6_token(NML3ConfigData *self, NMUtilsIPv6IfaceId ipv6_token)
+{
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+
+    if (self->ip6_token.id == ipv6_token.id)
+        return FALSE;
+
+    self->ip6_token.id = ipv6_token.id;
+    return TRUE;
+}
+
 NMProxyConfigMethod
 nm_l3_config_data_get_proxy_method(const NML3ConfigData *self)
 {
@@ -1977,7 +2049,7 @@ gboolean
 nm_l3_config_data_set_dhcp_lease(NML3ConfigData *self, int addr_family, NMDhcpLease *lease)
 {
     nm_auto_unref_dhcplease NMDhcpLease *lease_old = NULL;
-    NMDhcpLease **                       p_lease;
+    NMDhcpLease                        **p_lease;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
 
@@ -1996,11 +2068,11 @@ nm_l3_config_data_set_dhcp_lease(NML3ConfigData *self, int addr_family, NMDhcpLe
 gboolean
 nm_l3_config_data_set_dhcp_lease_from_options(NML3ConfigData *self,
                                               int             addr_family,
-                                              GHashTable *    options_take)
+                                              GHashTable     *options_take)
 {
     nm_auto_unref_dhcplease NMDhcpLease *lease     = NULL;
     nm_auto_unref_dhcplease NMDhcpLease *lease_old = NULL;
-    NMDhcpLease **                       p_lease;
+    NMDhcpLease                        **p_lease;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
 
@@ -2023,7 +2095,8 @@ static int
 _dedup_multi_index_cmp(const NML3ConfigData *a,
                        const NML3ConfigData *b,
                        NMPObjectType         obj_type,
-                       gboolean              ignore_ifindex)
+                       gboolean              cmp_ifindex,
+                       gboolean              cmp_full)
 {
     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);
@@ -2062,91 +2135,221 @@ _dedup_multi_index_cmp(const NML3ConfigData *a,
         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_full(obj_a,
-                                          obj_b,
-                                          ignore_ifindex ? NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX
-                                                         : NMP_OBJECT_CMP_FLAGS_NONE));
+        if (cmp_full) {
+            NM_CMP_RETURN(nmp_object_cmp_full(obj_a,
+                                              obj_b,
+                                              cmp_ifindex ? NMP_OBJECT_CMP_FLAGS_NONE
+                                                          : NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX));
+        } else {
+            if (cmp_ifindex) {
+                NM_CMP_DIRECT(obj_a->obj_with_ifindex.ifindex, obj_b->obj_with_ifindex.ifindex);
+            }
+
+            switch (obj_type) {
+            case NMP_OBJECT_TYPE_IP4_ADDRESS:
+                NM_CMP_DIRECT(obj_a->ip4_address.plen, obj_b->ip4_address.plen);
+                NM_CMP_DIRECT(obj_b->ip4_address.address, obj_b->ip4_address.address);
+                NM_CMP_DIRECT(obj_b->ip4_address.peer_address, obj_b->ip4_address.peer_address);
+                break;
+            case NMP_OBJECT_TYPE_IP6_ADDRESS:
+                NM_CMP_DIRECT(obj_a->ip6_address.plen, obj_b->ip6_address.plen);
+                NM_CMP_DIRECT_IN6ADDR(&obj_a->ip6_address.address, &obj_b->ip6_address.address);
+                break;
+            case NMP_OBJECT_TYPE_IP4_ROUTE:
+            {
+                NMPlatformIP4Route ra = obj_a->ip4_route;
+                NMPlatformIP4Route rb = obj_b->ip4_route;
+
+                NM_CMP_DIRECT(ra.metric, rb.metric);
+                NM_CMP_DIRECT(ra.plen, rb.plen);
+                NM_CMP_RETURN_DIRECT(
+                    nm_utils_ip4_address_same_prefix_cmp(ra.network, rb.network, ra.plen));
+                break;
+            }
+            case NMP_OBJECT_TYPE_IP6_ROUTE:
+            {
+                NMPlatformIP6Route ra = obj_a->ip6_route;
+                NMPlatformIP6Route rb = obj_b->ip6_route;
+
+                NM_CMP_DIRECT(ra.metric, rb.metric);
+                NM_CMP_DIRECT(ra.plen, rb.plen);
+                NM_CMP_RETURN_DIRECT(
+                    nm_utils_ip6_address_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
+                break;
+            }
+            default:
+                nm_assert_not_reached();
+            }
+        }
+    }
+}
+
+static const NML3ConfigData *
+get_empty_l3cd(void)
+{
+    static NML3ConfigData *empty_l3cd;
+
+    if (!empty_l3cd) {
+        empty_l3cd =
+            nm_l3_config_data_new(nm_dedup_multi_index_new(), 1, NM_IP_CONFIG_SOURCE_UNKNOWN);
+        empty_l3cd->ifindex = 0;
     }
+
+    return empty_l3cd;
 }
 
 int
 nm_l3_config_data_cmp_full(const NML3ConfigData *a,
                            const NML3ConfigData *b,
-                           NML3ConfigCmpFlags    cmp_flags)
+                           NML3ConfigCmpFlags    flags)
 {
-    int      IS_IPv4;
-    gboolean ignore_ifindex;
+    int IS_IPv4;
+
+    if (a == b)
+        return 0;
 
-    NM_CMP_SELF(a, b);
+    if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ADDRESSES))
+        flags |= NM_L3_CONFIG_CMP_FLAGS_ADDRESSES_ID;
+    if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES))
+        flags |= NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID;
 
-    ignore_ifindex = NM_FLAGS_HAS(cmp_flags, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX);
+    if (!a)
+        a = get_empty_l3cd();
+    if (!b)
+        b = get_empty_l3cd();
 
-    if (!ignore_ifindex)
+    if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX))
         NM_CMP_DIRECT(a->ifindex, b->ifindex);
 
-    NM_CMP_DIRECT(a->flags, b->flags);
+    if (NM_FLAGS_ANY(flags,
+                     NM_L3_CONFIG_CMP_FLAGS_ADDRESSES | NM_L3_CONFIG_CMP_FLAGS_ADDRESSES_ID)) {
+        NM_CMP_RETURN(
+            _dedup_multi_index_cmp(a,
+                                   b,
+                                   NMP_OBJECT_TYPE_IP4_ADDRESS,
+                                   NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX),
+                                   NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ADDRESSES)));
+        NM_CMP_RETURN(
+            _dedup_multi_index_cmp(a,
+                                   b,
+                                   NMP_OBJECT_TYPE_IP6_ADDRESS,
+                                   NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX),
+                                   NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ADDRESSES)));
+    }
 
-    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));
+    if (NM_FLAGS_ANY(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES | NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)) {
+        NM_CMP_RETURN(_dedup_multi_index_cmp(a,
+                                             b,
+                                             NMP_OBJECT_TYPE_IP4_ROUTE,
+                                             NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX),
+                                             NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)));
+        NM_CMP_RETURN(_dedup_multi_index_cmp(a,
+                                             b,
+                                             NMP_OBJECT_TYPE_IP6_ROUTE,
+                                             NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX),
+                                             NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)));
+    }
 
     for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
-        const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        const int        addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        const NMPObject *def_route_a = a->best_default_route_x[IS_IPv4];
+        const NMPObject *def_route_b = b->best_default_route_x[IS_IPv4];
+
+        NM_CMP_SELF(def_route_a, def_route_b);
+
+        if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)) {
+            NM_CMP_RETURN(nmp_object_cmp_full(def_route_a,
+                                              def_route_b,
+                                              NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)
+                                                  ? NMP_OBJECT_CMP_FLAGS_NONE
+                                                  : NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX));
+        } else if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)) {
+            if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)) {
+                NM_CMP_DIRECT(def_route_a->obj_with_ifindex.ifindex,
+                              def_route_b->obj_with_ifindex.ifindex);
+            }
 
-        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));
+            if (IS_IPv4) {
+                NMPlatformIP4Route ra = def_route_a->ip4_route;
+                NMPlatformIP4Route rb = def_route_b->ip4_route;
+
+                NM_CMP_DIRECT(ra.metric, rb.metric);
+                NM_CMP_DIRECT(ra.plen, rb.plen);
+                NM_CMP_RETURN_DIRECT(
+                    nm_utils_ip4_address_same_prefix_cmp(ra.network, rb.network, ra.plen));
+            } else {
+                NMPlatformIP6Route ra = def_route_a->ip6_route;
+                NMPlatformIP6Route rb = def_route_b->ip6_route;
+
+                NM_CMP_DIRECT(ra.metric, rb.metric);
+                NM_CMP_DIRECT(ra.plen, rb.plen);
+                NM_CMP_RETURN_DIRECT(
+                    nm_utils_ip6_address_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
+            }
+        }
 
-        NM_CMP_RETURN(
-            _garray_inaddr_cmp(a->nameservers_x[IS_IPv4], b->nameservers_x[IS_IPv4], addr_family));
-
-        NM_CMP_RETURN(nm_utils_hashtable_cmp(nm_dhcp_lease_get_options(a->dhcp_lease_x[IS_IPv4]),
-                                             nm_dhcp_lease_get_options(b->dhcp_lease_x[IS_IPv4]),
-                                             TRUE,
-                                             nm_strcmp_with_data,
-                                             nm_strcmp_with_data,
-                                             NULL));
-
-        NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->domains_x[IS_IPv4], b->domains_x[IS_IPv4]));
-        NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->searches_x[IS_IPv4], b->searches_x[IS_IPv4]));
-        NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->dns_options_x[IS_IPv4], b->dns_options_x[IS_IPv4]));
-
-        if (NM_FLAGS_ANY(a->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4)))
-            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));
-    NM_CMP_RETURN(_garray_inaddr_cmp(a->nis_servers, b->nis_servers, AF_INET));
-    NM_CMP_DIRECT_REF_STRING(a->nis_domain, b->nis_domain);
-    NM_CMP_DIRECT(a->mdns, b->mdns);
-    NM_CMP_DIRECT(a->llmnr, b->llmnr);
-    NM_CMP_DIRECT(a->mtu, b->mtu);
-    NM_CMP_DIRECT(a->ip6_mtu, b->ip6_mtu);
-    NM_CMP_DIRECT_UNSAFE(a->metered, b->metered);
-    NM_CMP_DIRECT_UNSAFE(a->proxy_browser_only, b->proxy_browser_only);
-    NM_CMP_DIRECT_UNSAFE(a->proxy_method, b->proxy_method);
-    NM_CMP_DIRECT_REF_STRING(a->proxy_pac_url, b->proxy_pac_url);
-    NM_CMP_DIRECT_REF_STRING(a->proxy_pac_script, b->proxy_pac_script);
-    NM_CMP_DIRECT_UNSAFE(a->ip6_privacy, b->ip6_privacy);
-
-    NM_CMP_DIRECT_UNSAFE(a->ndisc_hop_limit_set, b->ndisc_hop_limit_set);
-    if (a->ndisc_hop_limit_set)
-        NM_CMP_DIRECT(a->ndisc_hop_limit_val, b->ndisc_hop_limit_val);
-
-    NM_CMP_DIRECT_UNSAFE(a->ndisc_reachable_time_msec_set, b->ndisc_reachable_time_msec_set);
-    if (a->ndisc_reachable_time_msec_set)
-        NM_CMP_DIRECT(a->ndisc_reachable_time_msec_val, b->ndisc_reachable_time_msec_val);
-
-    NM_CMP_DIRECT_UNSAFE(a->ndisc_retrans_timer_msec_set, b->ndisc_retrans_timer_msec_set);
-    if (a->ndisc_retrans_timer_msec_set)
-        NM_CMP_DIRECT(a->ndisc_retrans_timer_msec_val, b->ndisc_retrans_timer_msec_val);
-
-    NM_CMP_FIELD(a, b, source);
+        if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_DNS)) {
+            NM_CMP_RETURN(_garray_inaddr_cmp(a->nameservers_x[IS_IPv4],
+                                             b->nameservers_x[IS_IPv4],
+                                             addr_family));
+            NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->domains_x[IS_IPv4], b->domains_x[IS_IPv4]));
+            NM_CMP_RETURN(nm_strv_ptrarray_cmp(a->searches_x[IS_IPv4], b->searches_x[IS_IPv4]));
+            NM_CMP_RETURN(
+                nm_strv_ptrarray_cmp(a->dns_options_x[IS_IPv4], b->dns_options_x[IS_IPv4]));
+
+            if (NM_FLAGS_ANY(a->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4)))
+                NM_CMP_DIRECT(a->dns_priority_x[IS_IPv4], b->dns_priority_x[IS_IPv4]);
+        }
+
+        if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_OTHER)) {
+            NM_CMP_RETURN(
+                nm_utils_hashtable_cmp(nm_dhcp_lease_get_options(a->dhcp_lease_x[IS_IPv4]),
+                                       nm_dhcp_lease_get_options(b->dhcp_lease_x[IS_IPv4]),
+                                       TRUE,
+                                       nm_strcmp_with_data,
+                                       nm_strcmp_with_data,
+                                       NULL));
+
+            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]);
+        }
+    }
+
+    if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_DNS)) {
+        NM_CMP_RETURN(_garray_inaddr_cmp(a->wins, b->wins, AF_INET));
+        NM_CMP_RETURN(_garray_inaddr_cmp(a->nis_servers, b->nis_servers, AF_INET));
+        NM_CMP_DIRECT_REF_STRING(a->nis_domain, b->nis_domain);
+        NM_CMP_DIRECT(a->mdns, b->mdns);
+        NM_CMP_DIRECT(a->llmnr, b->llmnr);
+        NM_CMP_DIRECT(a->dns_over_tls, b->dns_over_tls);
+    }
+
+    if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_OTHER)) {
+        NM_CMP_DIRECT(a->flags, b->flags);
+        NM_CMP_DIRECT(a->ip6_token.id, b->ip6_token.id);
+        NM_CMP_DIRECT(a->mtu, b->mtu);
+        NM_CMP_DIRECT(a->ip6_mtu, b->ip6_mtu);
+        NM_CMP_DIRECT_UNSAFE(a->metered, b->metered);
+        NM_CMP_DIRECT_UNSAFE(a->proxy_browser_only, b->proxy_browser_only);
+        NM_CMP_DIRECT_UNSAFE(a->proxy_method, b->proxy_method);
+        NM_CMP_DIRECT_REF_STRING(a->proxy_pac_url, b->proxy_pac_url);
+        NM_CMP_DIRECT_REF_STRING(a->proxy_pac_script, b->proxy_pac_script);
+        NM_CMP_DIRECT_UNSAFE(a->ip6_privacy, b->ip6_privacy);
+
+        NM_CMP_DIRECT_UNSAFE(a->ndisc_hop_limit_set, b->ndisc_hop_limit_set);
+        if (a->ndisc_hop_limit_set)
+            NM_CMP_DIRECT(a->ndisc_hop_limit_val, b->ndisc_hop_limit_val);
+
+        NM_CMP_DIRECT_UNSAFE(a->ndisc_reachable_time_msec_set, b->ndisc_reachable_time_msec_set);
+        if (a->ndisc_reachable_time_msec_set)
+            NM_CMP_DIRECT(a->ndisc_reachable_time_msec_val, b->ndisc_reachable_time_msec_val);
+
+        NM_CMP_DIRECT_UNSAFE(a->ndisc_retrans_timer_msec_set, b->ndisc_retrans_timer_msec_set);
+        if (a->ndisc_retrans_timer_msec_set)
+            NM_CMP_DIRECT(a->ndisc_retrans_timer_msec_val, b->ndisc_retrans_timer_msec_val);
+
+        NM_CMP_FIELD(a, b, source);
+    }
 
     /* these fields are not considered by cmp():
      *
@@ -2167,9 +2370,9 @@ _data_get_direct_route_for_host(const NML3ConfigData *self,
                                 guint32               route_table)
 {
     const int                 IS_IPv4        = NM_IS_IPv4(addr_family);
-    const NMPObject *         best_route_obj = NULL;
+    const NMPObject          *best_route_obj = NULL;
     const NMPlatformIPXRoute *best_route     = NULL;
-    const NMPObject *         item_obj;
+    const NMPObject          *item_obj;
     NMDedupMultiIter          ipconf_iter;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
@@ -2230,7 +2433,7 @@ GPtrArray *
 nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolean is_vrf)
 {
     gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
-    const NMPObject *            my_addr_obj;
+    const NMPObject             *my_addr_obj;
     NMDedupMultiIter             iter;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
@@ -2295,7 +2498,7 @@ void
 nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_family)
 {
     gs_unref_ptrarray GPtrArray *extra_onlink_routes = NULL;
-    const NMPObject *            obj_src;
+    const NMPObject             *obj_src;
     NMDedupMultiIter             iter;
     int                          IS_IPv4;
     guint                        i;
@@ -2316,16 +2519,17 @@ nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_fam
 
     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;
+        NMPObject                *new_route;
+        NMPlatformIPXRoute       *new_r;
+        const NMIPAddr           *p_gateway;
 
         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 (_data_get_direct_route_for_host(
+        if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route_src)
+            || _data_get_direct_route_for_host(
                 self,
                 addr_family,
                 p_gateway,
@@ -2363,14 +2567,15 @@ nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_fam
 }
 
 void
-nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
+nm_l3_config_data_add_dependent_device_routes(NML3ConfigData       *self,
                                               int                   addr_family,
                                               guint32               route_table,
                                               guint32               route_metric,
+                                              gboolean              force_commit,
                                               const NML3ConfigData *source)
 {
     const int          IS_IPv4 = NM_IS_IPv4(addr_family);
-    const NMPObject *  obj_src;
+    const NMPObject   *obj_src;
     NMPlatformIPXRoute rx;
     NMDedupMultiIter   iter;
 
@@ -2393,7 +2598,7 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
                                          &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;
+        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)));
@@ -2411,6 +2616,7 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
                                                                                     self->ifindex,
                                                                                     route_table,
                                                                                     route_metric,
+                                                                                    force_commit,
                                                                                     &r_stack.r4);
             if (r)
                 nm_l3_config_data_add_route(self, addr_family, NULL, r);
@@ -2449,13 +2655,15 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
                 }
 
                 rx.r6 = (NMPlatformIP6Route){
-                    .ifindex       = self->ifindex,
-                    .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
-                    .table_coerced = nm_platform_route_table_coerce(route_table),
-                    .metric        = route_metric,
-                    .network       = *a6,
-                    .plen          = plen,
+                    .ifindex        = self->ifindex,
+                    .rt_source      = NM_IP_CONFIG_SOURCE_KERNEL,
+                    .table_coerced  = nm_platform_route_table_coerce(route_table),
+                    .metric         = route_metric,
+                    .network        = *a6,
+                    .plen           = plen,
+                    .r_force_commit = force_commit,
                 };
+
                 nm_platform_ip_route_normalize(addr_family, &rx.rx);
                 nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
             }
@@ -2475,7 +2683,7 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
     guint              nroutes;
     guint              nnameservers;
     guint              nsearches;
-    const char *       gateway_str;
+    const char        *gateway_str;
     NMIPAddr           gateway_bin;
     guint              i;
     int                idx;
@@ -2521,10 +2729,10 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
 
     naddresses = nm_setting_ip_config_get_num_addresses(s_ip);
     for (i = 0; i < naddresses; i++) {
-        NMIPAddress *        s_addr = nm_setting_ip_config_get_address(s_ip, i);
+        NMIPAddress         *s_addr = nm_setting_ip_config_get_address(s_ip, i);
         NMPlatformIPXAddress a;
         NMIPAddr             addr_bin;
-        GVariant *           label;
+        GVariant            *label;
 
         nm_assert(nm_ip_address_get_family(s_addr) == addr_family);
 
@@ -2561,7 +2769,7 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
 
     nroutes = nm_setting_ip_config_get_num_routes(s_ip);
     for (i = 0; i < nroutes; i++) {
-        NMIPRoute *        s_route = nm_setting_ip_config_get_route(s_ip, i);
+        NMIPRoute         *s_route = nm_setting_ip_config_get_route(s_ip, i);
         NMPlatformIPXRoute r;
         NMIPAddr           network_bin;
         NMIPAddr           next_hop_bin;
@@ -2656,7 +2864,7 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
 NML3ConfigData *
 nm_l3_config_data_new_from_connection(NMDedupMultiIndex *multi_idx,
                                       int                ifindex,
-                                      NMConnection *     connection)
+                                      NMConnection      *connection)
 {
     NML3ConfigData *self;
     NMSettingProxy *s_proxy;
@@ -2716,14 +2924,14 @@ sort_captured_addresses_6(const CList *lst_a, const CList *lst_b, gconstpointer
 }
 
 static void
-_init_from_platform(NML3ConfigData *          self,
+_init_from_platform(NML3ConfigData           *self,
                     int                       addr_family,
-                    NMPlatform *              platform,
+                    NMPlatform               *platform,
                     NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941)
 {
     const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMDedupMultiHeadEntry *head_entry;
-    const NMPObject *            plobj = NULL;
+    const NMPObject             *plobj = NULL;
     NMDedupMultiIter             iter;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
@@ -2766,9 +2974,9 @@ _init_from_platform(NML3ConfigData *          self,
 }
 
 NML3ConfigData *
-nm_l3_config_data_new_from_platform(NMDedupMultiIndex *       multi_idx,
+nm_l3_config_data_new_from_platform(NMDedupMultiIndex        *multi_idx,
                                     int                       ifindex,
-                                    NMPlatform *              platform,
+                                    NMPlatform               *platform,
                                     NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941)
 {
     NML3ConfigData *self;
@@ -2791,31 +2999,39 @@ nm_l3_config_data_new_from_platform(NMDedupMultiIndex *       multi_idx,
 /*****************************************************************************/
 
 void
-nm_l3_config_data_merge(NML3ConfigData *      self,
+nm_l3_config_data_merge(NML3ConfigData       *self,
                         const NML3ConfigData *src,
                         NML3ConfigMergeFlags  merge_flags,
-                        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 */,
-                        const int *           default_dns_priority_x /* length 2, for IS_IPv4 */,
+                        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 */,
+                        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};
+    guint32              default_route_table_coerced_x[2];
     NMDedupMultiIter     iter;
-    const NMPObject *    obj;
+    const NMPObject     *obj;
     int                  IS_IPv4;
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
     nm_assert(_NM_IS_L3_CONFIG_DATA(src, TRUE));
 
-    if (!default_route_table_x)
-        default_route_table_x = x_default_route_table_x;
+    if (default_route_table_x) {
+        default_route_table_coerced_x[0] = nm_platform_route_table_coerce(default_route_table_x[0]);
+        default_route_table_coerced_x[1] = nm_platform_route_table_coerce(default_route_table_x[1]);
+    } else {
+        default_route_table_coerced_x[0] = nm_platform_route_table_coerce(RT_TABLE_MAIN);
+        default_route_table_coerced_x[1] = nm_platform_route_table_coerce(RT_TABLE_MAIN);
+    }
+    nm_assert(nm_platform_route_table_uncoerce(default_route_table_coerced_x[0], FALSE) != 0);
+    nm_assert(nm_platform_route_table_uncoerce(default_route_table_coerced_x[1], FALSE) != 0);
+
     if (!default_route_metric_x)
         default_route_metric_x = x_default_route_metric_x;
     if (!default_route_penalty_x)
@@ -2823,8 +3039,6 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
     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);
     nm_assert(default_route_metric_x[0] != 0); /* IPv6 route metric cannot be zero. */
 
     nm_assert(!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)
@@ -2842,8 +3056,9 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
             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,
+                 .ip4acd_not_ready   = NM_OPTION_BOOL_DEFAULT,
+                 .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+                 .force_commit       = NM_OPTION_BOOL_DEFAULT,
             };
 
 #define _ensure_a()                                       \
@@ -2882,6 +3097,12 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                 a.ax.a_assume_config_once = (!!hook_result.assume_config_once);
             }
 
+            if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT
+                && (!!hook_result.force_commit) != a_src->a_force_commit) {
+                _ensure_a();
+                a.ax.a_force_commit = (!!hook_result.force_commit);
+            }
+
             nm_l3_config_data_add_address_full(self,
                                                addr_family,
                                                a_src == &a.ax ? NULL : obj,
@@ -2897,11 +3118,12 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                                                  src,
                                                  &obj,
                                                  NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
-                const NMPlatformIPRoute * r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
+                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,
+                    .force_commit       = NM_OPTION_BOOL_DEFAULT,
                 };
 
 #define _ensure_r()                                     \
@@ -2933,11 +3155,17 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                     r.rx.r_assume_config_once = (!!hook_result.assume_config_once);
                 }
 
+                if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT
+                    && (!!hook_result.force_commit) != r_src->r_force_commit) {
+                    _ensure_r();
+                    r.rx.r_force_commit = (!!hook_result.force_commit);
+                }
+
                 if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
                     if (r_src->table_any) {
                         _ensure_r();
                         r.rx.table_any     = FALSE;
-                        r.rx.table_coerced = default_route_table_x[IS_IPv4];
+                        r.rx.table_coerced = default_route_table_coerced_x[IS_IPv4];
                     }
 
                     if (r_src->metric_any) {
@@ -3019,6 +3247,12 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
     if (self->llmnr == NM_SETTING_CONNECTION_LLMNR_DEFAULT)
         self->llmnr = src->llmnr;
 
+    if (self->dns_over_tls == NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT)
+        self->dns_over_tls = src->dns_over_tls;
+
+    if (self->ip6_token.id == 0)
+        self->ip6_token.id = src->ip6_token.id;
+
     self->metered = NM_MAX((NMTernary) self->metered, (NMTernary) src->metered);
 
     if (self->proxy_method == NM_PROXY_CONFIG_METHOD_UNKNOWN)
@@ -3095,9 +3329,10 @@ nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex)
 
     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)
+                                         src->ifindex == ifindex
+                                             ? NM_L3_CONFIG_CMP_FLAGS_ALL
+                                             : NM_L3_CONFIG_CMP_FLAGS_ALL
+                                                   & ~(NM_L3_CONFIG_CMP_FLAGS_IFINDEX))
               == 0);
     nm_assert(nm_l3_config_data_get_ifindex(self) == ifindex);