summary refs log tree commit diff
path: root/src/libnm-platform/nm-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2023-08-09 21:55:35 +0200
committerMichael Biebl <biebl@debian.org>2023-08-09 21:55:35 +0200
commit05e4a733f2141995181a551854d5df929f084adf (patch)
tree83bb937740a6667525ba0df046748ecaa829c269 /src/libnm-platform/nm-platform.c
parent14b0f3a9dc9ea90d60a3b057350fd4d637dc021a (diff)
New upstream version 1.44.0 upstream/1.44.0
Diffstat (limited to 'src/libnm-platform/nm-platform.c')
-rw-r--r--src/libnm-platform/nm-platform.c482
1 files changed, 231 insertions, 251 deletions
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index 1c495d60..041354cf 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -1276,7 +1276,7 @@ _link_add_check_existing(NMPlatform            *self,
  * @type: Interface type
  * @name: Interface name
  * @parent: the IFLA_LINK parameter or 0.
- * @address: (allow-none): set the mac address of the link
+ * @address: (nullable): set the mac address of the link
  * @address_len: the length of the @address
  * @extra_data: depending on @type, additional data.
  * @out_link: on success, the link object
@@ -2060,6 +2060,29 @@ nm_platform_link_set_bridge_vlans(NMPlatform                        *self,
     return klass->link_set_bridge_vlans(self, ifindex, on_master, vlans);
 }
 
+gboolean
+nm_platform_link_set_bridge_info(NMPlatform                            *self,
+                                 int                                    ifindex,
+                                 const NMPlatformLinkSetBridgeInfoData *bridge_info)
+{
+    _CHECK_SELF(self, klass, FALSE);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (_LOGD_ENABLED()) {
+        if (bridge_info->vlan_filtering_has) {
+            _LOG3D("link: setting bridge vlan-filtering %s",
+                   bridge_info->vlan_filtering_val ? "on" : "off");
+        }
+
+        if (bridge_info->vlan_default_pvid_has) {
+            _LOG3D("link: setting bridge vlan-default-pvid %d", bridge_info->vlan_default_pvid_val);
+        }
+    }
+
+    return klass->link_set_bridge_info(self, ifindex, bridge_info);
+}
+
 /**
  * nm_platform_link_change_flags_full:
  * @self: platform instance
@@ -2146,7 +2169,11 @@ nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name)
 }
 
 gboolean
-nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *bond_port)
+nm_platform_link_change(NMPlatform               *self,
+                        int                       ifindex,
+                        NMPlatformLinkProps      *props,
+                        NMPlatformLinkBondPort   *bond_port,
+                        NMPlatformLinkChangeFlags flags)
 {
     char sbuf_prio[100];
 
@@ -2154,9 +2181,24 @@ nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *b
 
     g_return_val_if_fail(ifindex >= 0, FALSE);
 
+    nm_assert(!NM_FLAGS_ANY(flags,
+                            NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH
+                                | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE
+                                | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS
+                                | NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE)
+              || props);
+
     if (_LOGD_ENABLED()) {
         nm_auto_free_gstring GString *str = g_string_new("");
 
+        if (flags & NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH)
+            g_string_append_printf(str, "tx-queue-length %u ", props->tx_queue_length);
+        if (flags & NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE)
+            g_string_append_printf(str, "gso_max_size %u ", props->gso_max_size);
+        if (flags & NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS)
+            g_string_append_printf(str, "gso_max_segments %u ", props->gso_max_segments);
+        if (flags & NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE)
+            g_string_append_printf(str, "gro_max_size %u ", props->gro_max_size);
         if (bond_port) {
             nm_assert(bond_port->prio_has || bond_port->prio == 0);
             g_string_append_printf(str,
@@ -2178,8 +2220,10 @@ nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *b
 
     return klass->link_change(self,
                               ifindex,
+                              props,
                               bond_port ? NM_PORT_KIND_BOND : NM_PORT_KIND_NONE,
-                              (const NMPlatformLinkPortData *) bond_port);
+                              (const NMPlatformLinkPortData *) bond_port,
+                              flags);
 }
 
 /**
@@ -2362,7 +2406,7 @@ nm_platform_link_can_assume(NMPlatform *self, int ifindex)
  * @self: the platform instance
  * @ifindex: the link ifindex to lookup
  * @link_type: filter by link-type.
- * @out_link: (allow-none): returns the platform link instance
+ * @out_link: (out) (optional) (nullable): returns the platform link instance
  *
  * If the function returns %NULL, that could mean that no such ifindex
  * exists, of that the link has no lnk data. You can find that out
@@ -2373,7 +2417,7 @@ nm_platform_link_can_assume(NMPlatform *self, int ifindex)
  * object if it is present. If you set link-type, you can be sure
  * that only a link type of the matching type is returned (or %NULL).
  *
- * Returns: the internal link lnk object. The returned object
+ * Returns: (nullable): the internal link lnk object. The returned object
  * is owned by the platform cache and must not be modified. Note
  * however, that the object is guaranteed to be immutable, so
  * you can safely take a reference and keep it for yourself
@@ -2631,7 +2675,7 @@ nm_platform_link_wireguard_change(NMPlatform                               *self
  * @vnet_hdr: whether to set the IFF_VNET_HDR flag
  * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
  * @out_link: on success, the link object
- * @out_fd: (allow-none): if give, return the file descriptor for the
+ * @out_fd: (out) (optional): if give, return the file descriptor for the
  *   created device. Note that when creating a non-persistent device,
  *   this argument is mandatory, otherwise it makes no sense
  *   to create such an interface.
@@ -2727,7 +2771,7 @@ link_set_option(NMPlatform *self,
     path =
         nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option);
     return nm_platform_sysctl_set(self,
-                                  NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path),
+                                  NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname_verified, path),
                                   value);
 }
 
@@ -2748,7 +2792,7 @@ link_get_option(NMPlatform *self, int ifindex, const char *category, const char
     path =
         nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option);
     return nm_platform_sysctl_get(self,
-                                  NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path));
+                                  NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname_verified, path));
 }
 
 static const char *
@@ -2955,7 +2999,7 @@ _infiniband_add_add_or_delete(NMPlatform            *self,
     if (parent_link->type != NM_LINK_TYPE_INFINIBAND)
         return -NME_PL_WRONG_TYPE;
 
-    nmp_utils_new_infiniband_name(name, parent_link->name, p_key);
+    nm_net_devname_infiniband(name, parent_link->name, p_key);
 
     if (add) {
         r = _link_add_check_existing(self, name, NM_LINK_TYPE_INFINIBAND, out_link);
@@ -3029,7 +3073,7 @@ nm_platform_link_infiniband_get_properties(NMPlatform  *self,
         return FALSE;
 
     contents =
-        nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode"));
+        nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "mode"));
     if (!contents)
         return FALSE;
     if (strstr(contents, "datagram"))
@@ -3040,13 +3084,13 @@ nm_platform_link_infiniband_get_properties(NMPlatform  *self,
         mode = NULL;
     g_free(contents);
 
-    p_key =
-        nm_platform_sysctl_get_int_checked(self,
-                                           NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "pkey"),
-                                           16,
-                                           0,
-                                           0xFFFF,
-                                           -1);
+    p_key = nm_platform_sysctl_get_int_checked(
+        self,
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "pkey"),
+        16,
+        0,
+        0xFFFF,
+        -1);
     if (p_key < 0)
         return FALSE;
 
@@ -3093,7 +3137,7 @@ nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_pee
  * nm_platform_link_tun_get_properties:
  * @self: the #NMPlatform instance
  * @ifindex: the ifindex to look up
- * @out_properties: (out) (allow-none): return the read properties
+ * @out_properties: (out) (optional): return the read properties
  *
  * Only recent versions of kernel export tun properties via netlink.
  * So, if that's the case, then we have the NMPlatformLnkTun instance
@@ -3148,31 +3192,33 @@ nm_platform_link_tun_get_properties(NMPlatform *self, int ifindex, NMPlatformLnk
         if (dirfd < 0)
             return FALSE;
 
-        owner = nm_platform_sysctl_get_int_checked(self,
-                                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "owner"),
-                                                   10,
-                                                   -1,
-                                                   G_MAXUINT32,
-                                                   -2);
+        owner =
+            nm_platform_sysctl_get_int_checked(self,
+                                               NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "owner"),
+                                               10,
+                                               -1,
+                                               G_MAXUINT32,
+                                               -2);
         if (owner == -2)
             return FALSE;
 
-        group = nm_platform_sysctl_get_int_checked(self,
-                                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "group"),
-                                                   10,
-                                                   -1,
-                                                   G_MAXUINT32,
-                                                   -2);
+        group =
+            nm_platform_sysctl_get_int_checked(self,
+                                               NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "group"),
+                                               10,
+                                               -1,
+                                               G_MAXUINT32,
+                                               -2);
         if (group == -2)
             return FALSE;
 
-        flags =
-            nm_platform_sysctl_get_int_checked(self,
-                                               NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "tun_flags"),
-                                               16,
-                                               0,
-                                               G_MAXINT64,
-                                               -1);
+        flags = nm_platform_sysctl_get_int_checked(
+            self,
+            NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "tun_flags"),
+            16,
+            0,
+            G_MAXINT64,
+            -1);
         if (flags == -1)
             return FALSE;
     }
@@ -3634,7 +3680,8 @@ nm_platform_ip4_address_add(NMPlatform *self,
                             guint32     lifetime,
                             guint32     preferred,
                             guint32     flags,
-                            const char *label)
+                            const char *label,
+                            char      **out_extack_msg)
 {
     _CHECK_SELF(self, klass, FALSE);
 
@@ -3644,6 +3691,7 @@ nm_platform_ip4_address_add(NMPlatform *self,
     g_return_val_if_fail(preferred <= lifetime, FALSE);
     g_return_val_if_fail(!label || strlen(label) < sizeof(((NMPlatformIP4Address *) NULL)->label),
                          FALSE);
+    nm_assert(!out_extack_msg || !*out_extack_msg);
 
     if (_LOGD_ENABLED()) {
         char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
@@ -3676,7 +3724,8 @@ nm_platform_ip4_address_add(NMPlatform *self,
                                   lifetime,
                                   preferred,
                                   flags,
-                                  label);
+                                  label,
+                                  out_extack_msg);
 }
 
 gboolean
@@ -3687,7 +3736,8 @@ nm_platform_ip6_address_add(NMPlatform     *self,
                             struct in6_addr peer_address,
                             guint32         lifetime,
                             guint32         preferred,
-                            guint32         flags)
+                            guint32         flags,
+                            char          **out_extack_msg)
 {
     _CHECK_SELF(self, klass, FALSE);
 
@@ -3695,6 +3745,7 @@ nm_platform_ip6_address_add(NMPlatform     *self,
     g_return_val_if_fail(plen <= 128, FALSE);
     g_return_val_if_fail(lifetime > 0, FALSE);
     g_return_val_if_fail(preferred <= lifetime, FALSE);
+    nm_assert(!out_extack_msg || !*out_extack_msg);
 
     if (_LOGD_ENABLED()) {
         char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
@@ -3715,8 +3766,15 @@ nm_platform_ip6_address_add(NMPlatform     *self,
 
     nm_platform_ip6_dadfailed_set(self, ifindex, &address, FALSE);
 
-    return klass
-        ->ip6_address_add(self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
+    return klass->ip6_address_add(self,
+                                  ifindex,
+                                  address,
+                                  plen,
+                                  peer_address,
+                                  lifetime,
+                                  preferred,
+                                  flags,
+                                  out_extack_msg);
 }
 
 gboolean
@@ -4102,7 +4160,7 @@ ip6_address_scope_cmp_descending(gconstpointer p_a, gconstpointer p_b, gpointer
  * @known_addresses: List of addresses. The list will be modified and
  *   expired addresses will be cleared (by calling nmp_object_unref()
  *   on the array element).
- * @addresses_prune: (allow-none): the list of addresses to delete.
+ * @addresses_prune: (nullable): the list of addresses to delete.
  *   If platform has such an address configured, it will be deleted
  *   at the beginning of the sync. Note that the array will be modified
  *   by the function.
@@ -4539,7 +4597,8 @@ next_plat:;
                     NM_FLAGS_HAS(flags, NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE)
                         ? IFA_F_NOPREFIXROUTE
                         : 0,
-                    known_address->a4.label))
+                    known_address->a4.label,
+                    NULL))
                 success = FALSE;
         } else {
             if (!nm_platform_ip6_address_add(
@@ -4553,7 +4612,8 @@ next_plat:;
                     (NM_FLAGS_HAS(flags, NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE)
                          ? IFA_F_NOPREFIXROUTE
                          : 0)
-                        | known_address->a6.n_ifa_flags))
+                        | known_address->a6.n_ifa_flags,
+                    NULL))
                 success = FALSE;
         }
     }
@@ -4594,42 +4654,6 @@ nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex)
 
 /*****************************************************************************/
 
-static gboolean
-_err_inval_due_to_ipv6_tentative_pref_src(NMPlatform *self, const NMPObject *obj)
-{
-    const NMPlatformIP6Route   *r;
-    const NMPlatformIP6Address *a;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(NMP_OBJECT_IS_VALID(obj));
-
-    /* trying to add an IPv6 route with pref-src fails, if the address is
-     * still tentative (rh#1452684). We need to hack around that.
-     *
-     * Detect it, by guessing whether that's the case. */
-
-    if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP6_ROUTE)
-        return FALSE;
-
-    r = NMP_OBJECT_CAST_IP6_ROUTE(obj);
-
-    /* we only allow this workaround for routes added manually by the user. */
-    if (r->rt_source != NM_IP_CONFIG_SOURCE_USER)
-        return FALSE;
-
-    if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src))
-        return FALSE;
-
-    a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src);
-    if (!a)
-        return FALSE;
-    if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE)
-        || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED))
-        return FALSE;
-
-    return TRUE;
-}
-
 static guint
 _ipv6_temporary_addr_prefixes_keep_hash(gconstpointer ptr)
 {
@@ -4951,15 +4975,15 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
  * @self: the #NMPlatform instance.
  * @addr_family: AF_INET or AF_INET6.
  * @ifindex: the @ifindex for which the routes are to be added.
- * @routes: (allow-none): a list of routes to configure. Must contain
+ * @routes: (nullable): a list of routes to configure. Must contain
  *   NMPObject instances of routes, according to @addr_family.
- * @routes_prune: (allow-none): the list of routes to delete.
+ * @routes_prune: (nullable): the list of routes to delete.
  *   If platform has such a route configured, it will be deleted
  *   at the end of the operation. Note that if @routes contains
  *   the same route, then it will not be deleted. @routes overrules
  *   @routes_prune list.
- * @out_temporary_not_available: (allow-none) (out): routes that could
- *   currently not be synced. The caller shall keep them and try later again.
+ * @out_routes_failed: (out) (optional) (nullable): routes that could
+ *   not be synced/added.
  *
  * Returns: %TRUE on success.
  */
@@ -4969,7 +4993,7 @@ nm_platform_ip_route_sync(NMPlatform *self,
                           int         ifindex,
                           GPtrArray  *routes,
                           GPtrArray  *routes_prune,
-                          GPtrArray **out_temporary_not_available)
+                          GPtrArray **out_routes_failed)
 {
     const int                      IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMPlatformVTableRoute   *vt;
@@ -4989,8 +5013,8 @@ nm_platform_ip_route_sync(NMPlatform *self,
 
     for (i_type = 0; routes && i_type < 2; i_type++) {
         for (i = 0; i < routes->len; i++) {
-            int      r, r2;
-            gboolean gateway_route_added = FALSE;
+            gs_free char *extack_msg = NULL;
+            int           r;
 
             conf_o = routes->pdata[i];
 
@@ -5046,143 +5070,59 @@ nm_platform_ip_route_sync(NMPlatform *self,
                 }
             }
 
-sync_route_add:
             r = nm_platform_ip_route_add(self,
                                          NMP_NLM_FLAG_APPEND
                                              | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-                                         conf_o);
-            if (r < 0) {
-                if (r == -EEXIST) {
-                    /* Don't fail for EEXIST. It's not clear that the existing route
-                     * is identical to the one that we were about to add. However,
-                     * above we should have deleted conflicting (non-identical) routes. */
-                    if (_LOGD_ENABLED()) {
-                        plat_entry =
-                            nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
-                        if (!plat_entry) {
-                            _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
-                                   "cannot find such a route",
-                                   nmp_object_to_string(conf_o,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf1,
-                                                        sizeof(sbuf1)));
-                        } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
-                                                 NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
-                                                 NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-                                   != 0) {
-                            _LOG3D("route-sync: adding route %s failed due to existing "
-                                   "(different!) route %s",
-                                   nmp_object_to_string(conf_o,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf1,
-                                                        sizeof(sbuf1)),
-                                   nmp_object_to_string(plat_entry->obj,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf2,
-                                                        sizeof(sbuf2)));
-                        }
-                    }
-                } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
-                    _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                } else if (r == -EINVAL && out_temporary_not_available
-                           && _err_inval_due_to_ipv6_tentative_pref_src(self, conf_o)) {
-                    _LOG3D("route-sync: ignore failure to add IPv6 route with tentative IPv6 "
-                           "pref-src: %s: %s",
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                    if (!*out_temporary_not_available)
-                        *out_temporary_not_available =
-                            g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref);
-                    g_ptr_array_add(*out_temporary_not_available,
-                                    (gpointer) nmp_object_ref(conf_o));
-                } else if (!gateway_route_added
-                           && ((r == -ENETUNREACH && vt->is_ip4
-                                && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway)
-                               || (r == -EHOSTUNREACH && !vt->is_ip4
-                                   && !IN6_IS_ADDR_UNSPECIFIED(
-                                       &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) {
-                    NMPObject oo;
-
-                    if (vt->is_ip4) {
-                        const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o);
-
-                        nmp_object_stackinit(
-                            &oo,
-                            NMP_OBJECT_TYPE_IP4_ROUTE,
-                            &((NMPlatformIP4Route){
-                                .ifindex       = rt->ifindex,
-                                .network       = rt->gateway,
-                                .plen          = 32,
-                                .metric        = nm_platform_ip4_route_get_effective_metric(rt),
-                                .rt_source     = rt->rt_source,
-                                .table_coerced = nm_platform_ip_route_get_effective_table(
-                                    NM_PLATFORM_IP_ROUTE_CAST(rt)),
-                            }));
-                    } else {
-                        const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o);
-
-                        nmp_object_stackinit(
-                            &oo,
-                            NMP_OBJECT_TYPE_IP6_ROUTE,
-                            &((NMPlatformIP6Route){
-                                .ifindex       = rt->ifindex,
-                                .network       = rt->gateway,
-                                .plen          = 128,
-                                .metric        = nm_platform_ip6_route_get_effective_metric(rt),
-                                .rt_source     = rt->rt_source,
-                                .table_coerced = nm_platform_ip_route_get_effective_table(
-                                    NM_PLATFORM_IP_ROUTE_CAST(rt)),
-                            }));
-                    }
-
-                    _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct "
-                           "route to gateway %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r),
-                           nmp_object_to_string(&oo,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf2,
-                                                sizeof(sbuf2)));
-
-                    r2 = nm_platform_ip_route_add(self,
-                                                  NMP_NLM_FLAG_APPEND
-                                                      | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-                                                  &oo);
-
-                    if (r2 < 0) {
-                        _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s",
-                               vt->is_ip4 ? '4' : '6',
+                                         conf_o,
+                                         &extack_msg);
+            if (r == 0) {
+                /* success */
+            } else if (r == -EEXIST) {
+                /* Don't fail for EEXIST. It's not clear that the existing route
+                 * is identical to the one that we were about to add. However,
+                 * above we should have deleted conflicting (non-identical) routes. */
+                if (_LOGD_ENABLED()) {
+                    plat_entry =
+                        nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
+                    if (!plat_entry) {
+                        _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
+                               "cannot find such a route",
+                               nmp_object_to_string(conf_o,
+                                                    NMP_OBJECT_TO_STRING_PUBLIC,
+                                                    sbuf1,
+                                                    sizeof(sbuf1)));
+                    } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
+                                             NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
+                                             NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+                               != 0) {
+                        _LOG3D("route-sync: adding route %s failed due to existing "
+                               "(different!) route %s",
                                nmp_object_to_string(conf_o,
                                                     NMP_OBJECT_TO_STRING_PUBLIC,
                                                     sbuf1,
                                                     sizeof(sbuf1)),
-                               nm_strerror(r2));
+                               nmp_object_to_string(plat_entry->obj,
+                                                    NMP_OBJECT_TO_STRING_PUBLIC,
+                                                    sbuf2,
+                                                    sizeof(sbuf2)));
                     }
+                }
+            } else {
+                _LOG3D(
+                    "route-sync: failure to add IPv%c route: %s: %s%s%s%s",
+                    vt->is_ip4 ? '4' : '6',
+                    nmp_object_to_string(conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof(sbuf1)),
+                    nm_strerror(r),
+                    NM_PRINT_FMT_QUOTED(extack_msg, " (", extack_msg, ")", ""));
 
-                    gateway_route_added = TRUE;
-                    goto sync_route_add;
-                } else {
-                    _LOG3W("route-sync: failure to add IPv%c route: %s: %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                    success = FALSE;
+                success = FALSE;
+
+                if (out_routes_failed) {
+                    if (!*out_routes_failed) {
+                        *out_routes_failed =
+                            g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+                    }
+                    g_ptr_array_add(*out_routes_failed, (gpointer) nmp_object_ref(conf_o));
                 }
             }
         }
@@ -5325,12 +5265,12 @@ nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route)
 }
 
 static int
-_ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack)
+_ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg)
 {
     char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     int  ifindex;
 
-    _CHECK_SELF(self, klass, FALSE);
+    _CHECK_SELF(self, klass, -NME_BUG);
 
     /* The caller already ensures that this is a stack allocated copy, that
      * - stays alive for the duration of the call.
@@ -5341,6 +5281,7 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack)
     nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_stack),
                         NMP_OBJECT_TYPE_IP4_ROUTE,
                         NMP_OBJECT_TYPE_IP6_ROUTE));
+    nm_assert(!out_extack_msg || !*out_extack_msg);
 
     nm_assert(NMP_OBJECT_GET_TYPE(obj_stack) != NMP_OBJECT_TYPE_IP4_ROUTE
               || obj_stack->ip4_route.n_nexthops <= 1u || obj_stack->_ip4_route.extra_nexthops);
@@ -5362,11 +5303,14 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack)
      *   is stack allocated (and the potential "extra_nexthops" array is
      *   guaranteed to stay alive too).
      */
-    return klass->ip_route_add(self, flags, obj_stack);
+    return klass->ip_route_add(self, flags, obj_stack, out_extack_msg);
 }
 
 int
-nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *obj)
+nm_platform_ip_route_add(NMPlatform      *self,
+                         NMPNlmFlags      flags,
+                         const NMPObject *obj,
+                         char           **out_extack_msg)
 {
     nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
     NMPObject                       obj_stack;
@@ -5384,7 +5328,7 @@ nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *o
         obj_stack._ip4_route.extra_nexthops = obj->_ip4_route.extra_nexthops;
     }
 
-    return _ip_route_add(self, flags, &obj_stack);
+    return _ip_route_add(self, flags, &obj_stack, out_extack_msg);
 }
 
 int
@@ -5416,7 +5360,7 @@ nm_platform_ip4_route_add(NMPlatform                   *self,
                               &extra_nexthops_free);
     }
 
-    return _ip_route_add(self, flags, &obj);
+    return _ip_route_add(self, flags, &obj, NULL);
 }
 
 int
@@ -5425,7 +5369,7 @@ nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformI
     NMPObject obj;
 
     nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ROUTE, (const NMPlatformObject *) route);
-    return _ip_route_add(self, flags, &obj);
+    return _ip_route_add(self, flags, &obj, NULL);
 }
 
 gboolean
@@ -5951,7 +5895,7 @@ nm_platform_vlan_qos_mapping_to_string(const char             *name,
 /**
  * nm_platform_link_to_string:
  * @route: pointer to NMPlatformLink address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting an link struct into a string representation.
@@ -6039,6 +5983,10 @@ nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len)
         "%s%s"    /* inet6_token */
         "%s%s"    /* driver */
         "%s%s"    /* port_data */
+        " tx-queue-len %u"
+        " gso-max-size %u"
+        " gso-max-segs %u"
+        " gro-max-size %u"
         " rx:%" G_GUINT64_FORMAT ",%" G_GUINT64_FORMAT " tx:%" G_GUINT64_FORMAT
         ",%" G_GUINT64_FORMAT,
         link->ifindex,
@@ -6072,6 +6020,10 @@ nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len)
         link->driver ? " driver " : "",
         link->driver ?: "",
         NM_PRINT_FMT_QUOTED2(str_port_data[0] != '\0', " ", str_port_data, ""),
+        link->link_props.tx_queue_length,
+        link->link_props.gso_max_size,
+        link->link_props.gso_max_segments,
+        link->link_props.gro_max_size,
         link->rx_packets,
         link->rx_bytes,
         link->tx_packets,
@@ -6167,6 +6119,7 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
     char sbuf_miimon[30];
     char sbuf_updelay[30];
     char sbuf_downdelay[30];
+    char sbuf_lacp_active[30];
     char sbuf_peer_notif_delay[60];
     char sbuf_resend_igmp[30];
     char sbuf_lp_interval[30];
@@ -6201,7 +6154,9 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
         " xmit_hash_policy %u"
         " num_gray_arp %u"
         " all_ports_active %u"
+        " arp_missed_max %u"
         " lacp_rate %u"
+        "%s" /* lacp_active */
         " ad_select %u"
         " use_carrier %d"
         "%s" /* tlb_dynamic_lb */,
@@ -6251,7 +6206,14 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
         lnk->xmit_hash_policy,
         lnk->num_grat_arp,
         lnk->all_ports_active,
+        lnk->arp_missed_max,
         lnk->lacp_rate,
+        lnk->lacp_active_has || lnk->lacp_active != 0
+            ? nm_sprintf_buf(sbuf_lacp_active,
+                             " lacp_active%s %u",
+                             !lnk->lacp_active_has ? "?" : "",
+                             lnk->lacp_active)
+            : "",
         lnk->ad_select,
         (int) lnk->use_carrier,
         lnk->tlb_dynamic_lb_has ? nm_sprintf_buf(sbuf_tlb_dynamic_lb,
@@ -6269,6 +6231,15 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
             nm_strbuf_append_str(&buf, &len, nm_inet4_ntop(lnk->arp_ip_target[i], target));
         }
     }
+    if (lnk->ns_ip6_targets_num > 0) {
+        nm_strbuf_append_str(&buf, &len, " ns_ip6_target");
+        for (i = 0; i < lnk->ns_ip6_targets_num; i++) {
+            char target[INET6_ADDRSTRLEN];
+
+            nm_strbuf_append_c(&buf, &len, ' ');
+            nm_strbuf_append_str(&buf, &len, nm_inet6_ntop(&lnk->ns_ip6_target[i], target));
+        }
+    }
     return buf;
 }
 
@@ -6920,11 +6891,11 @@ _rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags)
 /**
  * nm_platform_ip4_route_to_string:
  * @route: pointer to NMPlatformIP4Route route structure
- * @extra_nexthops: (allow-none): the route might be a ECMP multihop route
+ * @extra_nexthops: (nullable): the route might be a ECMP multihop route
  *   (with n_nexthops > 1). In that case, provide the list of extra hops
  *   to print too. It is allowed for a multihop route to omit the extra hops
  *   by passing NULL.
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting a route struct into a string representation.
@@ -6998,7 +6969,6 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route     *route,
         "%s"         /* rto_min */
         "%s"         /* quickack */
         "%s"         /* mtu */
-        "%s"         /* r_force_commit */
         "",
         nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
                                               str_type),
@@ -7065,8 +7035,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route     *route,
                                                        " mtu %s%" G_GUINT32_FORMAT,
                                                        route->lock_mtu ? "lock " : "",
                                                        route->mtu)
-                                      : "",
-        route->r_force_commit ? " force-commit" : "");
+                                      : "");
 
     if ((n_nexthops == 1 && route->ifindex > 0) || n_nexthops == 0) {
         /* A plain single hop route. Nothing extra to remark. */
@@ -7116,7 +7085,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route     *route,
 /**
  * nm_platform_ip6_route_to_string:
  * @route: pointer to NMPlatformIP6Route route structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting a route struct into a string representation.
@@ -7186,7 +7155,6 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
         "%s"         /* quickack */
         "%s"         /* mtu */
         "%s"         /* pref */
-        "%s"         /* r_force_commit */
         "",
         nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
                                               str_type),
@@ -7253,8 +7221,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
             str_pref,
             " pref %s",
             nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2)))
-                       : "",
-        route->r_force_commit ? " force-commit" : "");
+                       : "");
 
     return buf;
 }
@@ -7904,6 +7871,10 @@ nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h)
                         obj->arptype,
                         obj->inet6_addr_gen_mode_inv,
                         obj->inet6_token,
+                        obj->link_props.tx_queue_length,
+                        obj->link_props.gso_max_size,
+                        obj->link_props.gso_max_segments,
+                        obj->link_props.gro_max_size,
                         obj->port_kind,
                         obj->rx_packets,
                         obj->rx_bytes,
@@ -7966,6 +7937,10 @@ nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b)
     if (a->l_broadcast.len)
         NM_CMP_FIELD_MEMCMP_LEN(a, b, l_broadcast.data, a->l_broadcast.len);
     NM_CMP_FIELD_MEMCMP(a, b, inet6_token);
+    NM_CMP_FIELD(a, b, link_props.tx_queue_length);
+    NM_CMP_FIELD(a, b, link_props.gso_max_size);
+    NM_CMP_FIELD(a, b, link_props.gso_max_segments);
+    NM_CMP_FIELD(a, b, link_props.gro_max_size);
     NM_CMP_FIELD(a, b, port_kind);
     switch (a->port_kind) {
     case NM_PORT_KIND_NONE:
@@ -8032,15 +8007,19 @@ nm_platform_lnk_bond_hash_update(const NMPlatformLnkBond *obj, NMHashState *h)
                         obj->ad_actor_system,
                         obj->ad_select,
                         obj->all_ports_active,
+                        obj->arp_missed_max,
                         obj->arp_ip_targets_num,
                         obj->fail_over_mac,
                         obj->lacp_rate,
+                        obj->lacp_active,
+                        obj->ns_ip6_targets_num,
                         obj->num_grat_arp,
                         obj->mode,
                         obj->primary_reselect,
                         obj->xmit_hash_policy,
                         NM_HASH_COMBINE_BOOLS(guint16,
                                               obj->downdelay_has,
+                                              obj->lacp_active_has,
                                               obj->lp_interval_has,
                                               obj->miimon_has,
                                               obj->peer_notif_delay_has,
@@ -8051,6 +8030,7 @@ nm_platform_lnk_bond_hash_update(const NMPlatformLnkBond *obj, NMHashState *h)
                                               obj->use_carrier));
 
     nm_hash_update(h, obj->arp_ip_target, obj->arp_ip_targets_num * sizeof(obj->arp_ip_target[0]));
+    nm_hash_update(h, obj->ns_ip6_target, obj->ns_ip6_targets_num * sizeof(obj->ns_ip6_target[0]));
 }
 
 int
@@ -8069,6 +8049,11 @@ nm_platform_lnk_bond_cmp(const NMPlatformLnkBond *a, const NMPlatformLnkBond *b)
 {
     NM_CMP_SELF(a, b);
     NM_CMP_FIELD(a, b, arp_ip_targets_num);
+    NM_CMP_FIELD(a, b, ns_ip6_targets_num);
+    NM_CMP_FIELD_MEMCMP_LEN(a,
+                            b,
+                            ns_ip6_target,
+                            a->ns_ip6_targets_num * sizeof(a->ns_ip6_target[0]));
     NM_CMP_FIELD_MEMCMP_LEN(a,
                             b,
                             arp_ip_target,
@@ -8090,13 +8075,16 @@ nm_platform_lnk_bond_cmp(const NMPlatformLnkBond *a, const NMPlatformLnkBond *b)
     NM_CMP_FIELD_MEMCMP(a, b, ad_actor_system);
     NM_CMP_FIELD(a, b, ad_select);
     NM_CMP_FIELD(a, b, all_ports_active);
+    NM_CMP_FIELD(a, b, arp_missed_max);
     NM_CMP_FIELD(a, b, fail_over_mac);
     NM_CMP_FIELD(a, b, lacp_rate);
+    NM_CMP_FIELD(a, b, lacp_active);
     NM_CMP_FIELD(a, b, num_grat_arp);
     NM_CMP_FIELD(a, b, mode);
     NM_CMP_FIELD(a, b, primary_reselect);
     NM_CMP_FIELD(a, b, xmit_hash_policy);
     NM_CMP_FIELD_BOOL(a, b, downdelay_has);
+    NM_CMP_FIELD_BOOL(a, b, lacp_active_has);
     NM_CMP_FIELD_BOOL(a, b, lp_interval_has);
     NM_CMP_FIELD_BOOL(a, b, miimon_has);
     NM_CMP_FIELD_BOOL(a, b, peer_notif_delay_has);
@@ -8654,8 +8642,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
                                                   obj->lock_initcwnd,
                                                   obj->lock_initrwnd,
                                                   obj->lock_mtu,
-                                                  obj->lock_mss,
-                                                  obj->r_force_commit));
+                                                  obj->lock_mss));
         break;
     }
 }
@@ -8798,8 +8785,6 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
         NM_CMP_FIELD(a, b, initrwnd);
         NM_CMP_FIELD(a, b, mtu);
         NM_CMP_FIELD(a, b, rto_min);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
-            NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
         break;
     }
     return 0;
@@ -8899,8 +8884,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
                                                   obj->lock_initcwnd,
                                                   obj->lock_initrwnd,
                                                   obj->lock_mtu,
-                                                  obj->lock_mss,
-                                                  obj->r_force_commit),
+                                                  obj->lock_mss),
                             obj->window,
                             obj->cwnd,
                             obj->initcwnd,
@@ -8990,8 +8974,6 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
             NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref));
         else
             NM_CMP_FIELD(a, b, rt_pref);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
-            NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
         break;
     }
     return 0;
@@ -9313,7 +9295,6 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
                                               int                         ifindex,
                                               guint32                     route_table,
                                               guint32                     route_metric,
-                                              gboolean                    force_commit,
                                               NMPlatformIP4Route         *dst)
 {
     in_addr_t network_4;
@@ -9343,15 +9324,14 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
     }
 
     *dst = (NMPlatformIP4Route){
-        .ifindex        = ifindex,
-        .rt_source      = NM_IP_CONFIG_SOURCE_KERNEL,
-        .network        = network_4,
-        .plen           = addr->plen,
-        .pref_src       = addr->address,
-        .table_coerced  = nm_platform_route_table_coerce(route_table),
-        .metric         = route_metric,
-        .scope_inv      = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
-        .r_force_commit = force_commit,
+        .ifindex       = ifindex,
+        .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
+        .network       = network_4,
+        .plen          = addr->plen,
+        .pref_src      = addr->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(AF_INET, (NMPlatformIPRoute *) dst);