summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/NetworkManagerUtils.c13
-rw-r--r--src/core/devices/nm-device-bond.c16
-rw-r--r--src/core/devices/nm-device-bridge.c5
-rw-r--r--src/core/devices/nm-device-hsr.c4
-rw-r--r--src/core/devices/nm-device-tun.c8
-rw-r--r--src/core/devices/nm-device-vrf.c2
-rw-r--r--src/core/devices/nm-device.c177
-rw-r--r--src/core/devices/nm-device.h12
-rw-r--r--src/core/devices/nm-lldp-listener.c2
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c28
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c58
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c28
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c27
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c80
-rw-r--r--src/core/dhcp/nm-dhcp-options.c2
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c2
-rw-r--r--src/core/dhcp/nm-dhcp-utils.c2
-rw-r--r--src/core/dhcp/tests/test-dhcp-utils.c70
-rw-r--r--src/core/dns/nm-dns-dnsconfd.c24
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.c20
-rw-r--r--src/core/ndisc/nm-ndisc.c4
-rw-r--r--src/core/nm-bond-manager.c10
-rw-r--r--src/core/nm-core-utils.c10
-rw-r--r--src/core/nm-l3-config-data.c3
-rw-r--r--src/core/nm-l3cfg.c9
-rw-r--r--src/core/nm-manager.c19
-rw-r--r--src/core/nm-netns.c2
-rw-r--r--src/core/nm-policy.c272
-rw-r--r--src/core/platform/nm-fake-platform.c2
-rw-r--r--src/core/platform/tests/test-common.c4
-rw-r--r--src/core/platform/tests/test-link.c75
-rw-r--r--src/core/platform/tests/test-route.c33
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c14
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c2
-rw-r--r--src/core/supplicant/nm-supplicant-interface.c2
37 files changed, 785 insertions, 260 deletions
diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c
index 6c9e2f3d..32df3d6d 100644
--- a/src/core/NetworkManagerUtils.c
+++ b/src/core/NetworkManagerUtils.c
@@ -1509,7 +1509,6 @@ nm_utils_ip_route_attribute_to_platform(int                addr_family,
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_INITCWND, r->initcwnd, UINT32, uint32, 0);
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_INITRWND, r->initrwnd, UINT32, uint32, 0);
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_MTU, r->mtu, UINT32, uint32, 0);
-    GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_RTO_MIN, r->rto_min, UINT32, uint32, 0);
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_QUICKACK, r->quickack, BOOLEAN, boolean, FALSE);
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, r->lock_window, BOOLEAN, boolean, FALSE);
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, r->lock_cwnd, BOOLEAN, boolean, FALSE);
@@ -1518,6 +1517,18 @@ nm_utils_ip_route_attribute_to_platform(int                addr_family,
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, r->lock_mtu, BOOLEAN, boolean, FALSE);
     GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_LOCK_ADVMSS, r->lock_mss, BOOLEAN, boolean, FALSE);
 
+    {
+        GVariant *_variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_RTO_MIN);
+
+        if (_variant && g_variant_is_of_type(_variant, G_VARIANT_TYPE_UINT32)) {
+            r->rto_min     = g_variant_get_uint32(_variant);
+            r->rto_min_set = TRUE;
+        } else {
+            r->rto_min     = 0;
+            r->rto_min_set = FALSE;
+        }
+    }
+
     if ((variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_SRC))
         && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) {
         if (inet_pton(addr_family, g_variant_get_string(variant, NULL), &addr) == 1) {
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 53b32466..673d2361 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -137,13 +137,13 @@ _set_bond_attr(NMDevice *device, const char *attr, const char *value)
     return ret;
 }
 
-#define _set_bond_attr_take(device, attr, value)                            \
-    G_STMT_START                                                            \
-    {                                                                       \
-        gs_free char *_tmp = (value);                                       \
-                                                                            \
-        _set_bond_attr(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, _tmp); \
-    }                                                                       \
+#define _set_bond_attr_take(device, attr, value) \
+    G_STMT_START                                 \
+    {                                            \
+        gs_free char *_tmp = (value);            \
+                                                 \
+        _set_bond_attr(device, attr, _tmp);      \
+    }                                            \
     G_STMT_END
 
 #define _set_bond_attr_printf(device, attr, fmt, ...) \
@@ -902,7 +902,7 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
     mode  = _nm_setting_bond_mode_from_string(value);
     g_return_if_fail(mode != NM_BOND_MODE_UNKNOWN);
 
-    /* Below we set only the bond options that kernel allows to modify
+    /* Below we set only the bond options that the kernel allows modifying
      * while keeping the bond interface up */
 
     set_bond_arp_ip_targets(device, s_bond);
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index 7c34fde0..45cdd83f 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -735,6 +735,11 @@ merge_bridge_vlan_default_pvid(NMPlatformBridgeVlan *vlans, guint *num_vlans, gu
     gboolean              has_pvid = FALSE;
     guint                 i;
 
+    if (default_pvid == 0) {
+        /* default_pvid=0 means that the default PVID is disabled. No need to merge it. */
+        return vlans;
+    }
+
     for (i = 0; i < *num_vlans; i++) {
         if (vlans[i].pvid) {
             has_pvid = TRUE;
diff --git a/src/core/devices/nm-device-hsr.c b/src/core/devices/nm-device-hsr.c
index 8156fa77..59454ee3 100644
--- a/src/core/devices/nm-device-hsr.c
+++ b/src/core/devices/nm-device-hsr.c
@@ -94,8 +94,10 @@ update_properties(NMDevice *device)
     CHECK_PROPERTY_CHANGED(multicast_spec, PROP_MULTICAST_SPEC);
     CHECK_PROPERTY_CHANGED(prp, PROP_PRP);
 
-    if (!nm_ether_addr_equal(&priv->props.supervision_address, &props->supervision_address))
+    if (!nm_ether_addr_equal(&priv->props.supervision_address, &props->supervision_address)) {
+        priv->props.supervision_address = props->supervision_address;
         _notify(self, PROP_SUPERVISION_ADDRESS);
+    }
 
     g_object_thaw_notify((GObject *) device);
 }
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index faab86d0..b277c378 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -242,12 +242,14 @@ create_and_realize(NMDevice              *device,
         g_return_val_if_reached(FALSE);
     }
 
-    owner = _nm_utils_ascii_str_to_int64(nm_setting_tun_get_owner(s_tun), 10, 0, G_MAXINT32, -1);
+    owner =
+        _nm_utils_ascii_str_to_int64(nm_setting_tun_get_owner(s_tun), 10, 0, G_MAXUINT32 - 1, -1);
     if (owner != -1) {
         props.owner_valid = TRUE;
         props.owner       = owner;
     }
-    group = _nm_utils_ascii_str_to_int64(nm_setting_tun_get_group(s_tun), 10, 0, G_MAXINT32, -1);
+    group =
+        _nm_utils_ascii_str_to_int64(nm_setting_tun_get_group(s_tun), 10, 0, G_MAXUINT32 - 1, -1);
     if (group != -1) {
         props.group_valid = TRUE;
         props.group       = group;
@@ -278,7 +280,7 @@ _same_og(const char *str, gboolean og_valid, guint32 og_num)
 {
     gint64 v;
 
-    v = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXINT32, -1);
+    v = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXUINT32 - 1, -1);
     return (!og_valid && (v == (gint64) -1)) || (og_valid && (((guint32) v) == og_num));
 }
 
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index 7dfd6504..6d272a95 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -235,7 +235,7 @@ attach_port(NMDevice                  *device,
 
         _LOGI(LOGD_DEVICE, "attached VRF port %s", port_iface);
     } else
-        _LOGI(LOGD_BOND, "VRF port %s was attached", port_iface);
+        _LOGI(LOGD_DEVICE, "VRF port %s was attached", port_iface);
 
     return TRUE;
 }
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 2f2f25a5..f6057e52 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -779,6 +779,7 @@ typedef struct _NMDevicePrivate {
     char     *prop_ip_iface; /* IP interface D-Bus property */
     GList    *ping_operations;
     GSource  *ping_timeout;
+    bool      refresh_forwarding_done : 1;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -880,6 +881,7 @@ static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_chang
 static gboolean device_link_changed(gpointer user_data);
 static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
 static void     deactivate_ready(NMDevice *self, NMDeviceStateReason reason);
+static void     carrier_disconnected_action_cancel(NMDevice *self);
 
 /*****************************************************************************/
 
@@ -1414,6 +1416,26 @@ _prop_get_connection_mdns(NMDevice *self)
                                                        NM_SETTING_CONNECTION_MDNS_DEFAULT);
 }
 
+static gboolean
+_prop_get_sriov_preserve_on_down(NMDevice *self, NMSettingSriov *s_sriov)
+{
+    NMSriovPreserveOnDown preserve;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
+    g_return_val_if_fail(NM_IS_SETTING_SRIOV(s_sriov), FALSE);
+
+    preserve = nm_setting_sriov_get_preserve_on_down(s_sriov);
+    if (NM_IN_SET(preserve, NM_SRIOV_PRESERVE_ON_DOWN_NO, NM_SRIOV_PRESERVE_ON_DOWN_YES))
+        return preserve;
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       NM_CON_DEFAULT("sriov.preserve-on-down"),
+                                                       self,
+                                                       NM_SRIOV_PRESERVE_ON_DOWN_NO,
+                                                       NM_SRIOV_PRESERVE_ON_DOWN_YES,
+                                                       NM_SRIOV_PRESERVE_ON_DOWN_NO);
+}
+
 static NMSettingConnectionLlmnr
 _prop_get_connection_llmnr(NMDevice *self)
 {
@@ -2109,6 +2131,33 @@ _prop_get_ipvx_dhcp_send_hostname(NMDevice *self, int addr_family)
     return send_hostname_v2;
 }
 
+NMSettingIPConfigForwarding
+nm_device_get_ipv4_forwarding(NMDevice *self)
+{
+    NMSettingIPConfig          *s_ip;
+    NMSettingIPConfigForwarding forwarding;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_IP_CONFIG_FORWARDING_AUTO);
+
+    s_ip = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
+    if (s_ip)
+        forwarding = nm_setting_ip_config_get_forwarding(s_ip);
+    else
+        forwarding = NM_SETTING_IP_CONFIG_FORWARDING_DEFAULT;
+
+    if (forwarding == NM_SETTING_IP_CONFIG_FORWARDING_DEFAULT) {
+        forwarding =
+            nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                        NM_CON_DEFAULT("ipv4.forwarding"),
+                                                        self,
+                                                        NM_SETTING_IP_CONFIG_FORWARDING_NO,
+                                                        NM_SETTING_IP_CONFIG_FORWARDING_AUTO,
+                                                        NM_SETTING_IP_CONFIG_FORWARDING_AUTO);
+    }
+
+    return forwarding;
+}
+
 static gboolean
 _prop_get_connection_ip_ping_addresses_require_all(NMDevice *self, NMSettingConnection *s_con)
 {
@@ -2717,7 +2766,7 @@ _ethtool_features_set(NMDevice         *self,
     if (nm_setting_ethtool_init_features(s_ethtool, ethtool_state->requested) == 0)
         return;
 
-    features = nm_platform_ethtool_get_link_features(platform, ethtool_state->ifindex);
+    features = nm_platform_ethtool_get_features(platform, ethtool_state->ifindex);
     if (!features) {
         _LOGW(LOGD_DEVICE, "ethtool: failure setting offload features (cannot read features)");
         return;
@@ -2836,9 +2885,9 @@ _ethtool_coalesce_set(NMDevice         *self,
             continue;
 
         if (!has_old) {
-            if (!nm_platform_ethtool_get_link_coalesce(platform,
-                                                       ethtool_state->ifindex,
-                                                       &coalesce_old)) {
+            if (!nm_platform_ethtool_get_coalesce(platform,
+                                                  ethtool_state->ifindex,
+                                                  &coalesce_old)) {
                 _LOGW(LOGD_DEVICE, "ethtool: failure getting coalesce settings (cannot read)");
                 return;
             }
@@ -2917,7 +2966,7 @@ _ethtool_ring_set(NMDevice         *self,
         nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
 
         if (!has_old) {
-            if (!nm_platform_ethtool_get_link_ring(platform, ethtool_state->ifindex, &ring_old)) {
+            if (!nm_platform_ethtool_get_ring(platform, ethtool_state->ifindex, &ring_old)) {
                 _LOGW(LOGD_DEVICE,
                       "ethtool: failure setting ring options (cannot read existing setting)");
                 return;
@@ -3013,9 +3062,9 @@ _ethtool_channels_set(NMDevice         *self,
         nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
 
         if (!has_old) {
-            if (!nm_platform_ethtool_get_link_channels(platform,
-                                                       ethtool_state->ifindex,
-                                                       &channels_old)) {
+            if (!nm_platform_ethtool_get_channels(platform,
+                                                  ethtool_state->ifindex,
+                                                  &channels_old)) {
                 _LOGW(LOGD_DEVICE,
                       "ethtool: failure setting channels options (cannot read existing setting)");
                 return;
@@ -3132,7 +3181,7 @@ _ethtool_pause_set(NMDevice         *self,
         nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN));
 
         if (!has_old) {
-            if (!nm_platform_ethtool_get_link_pause(platform, ethtool_state->ifindex, &pause_old)) {
+            if (!nm_platform_ethtool_get_pause(platform, ethtool_state->ifindex, &pause_old)) {
                 _LOGW(LOGD_DEVICE,
                       "ethtool: failure setting pause options (cannot read "
                       "existing setting)");
@@ -3218,7 +3267,7 @@ _ethtool_eee_set(NMDevice         *self,
         nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN));
 
         if (!has_old) {
-            if (!nm_platform_ethtool_get_link_eee(platform, ethtool_state->ifindex, &eee_old)) {
+            if (!nm_platform_ethtool_get_eee(platform, ethtool_state->ifindex, &eee_old)) {
                 _LOGW(LOGD_DEVICE,
                       "ethtool: failure setting eee options (cannot read "
                       "existing setting)");
@@ -3729,7 +3778,7 @@ nm_device_assume_state_reset(NMDevice *self)
 
 /*****************************************************************************/
 
-static char *
+char *
 nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property)
 {
     const char *ifname;
@@ -4993,6 +5042,10 @@ _set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex)
 
     ip_ifindex_new = nm_device_get_ip_ifindex(self);
 
+    /* the ifindex changed; forget about any carrier change event for
+     * the previous ifindex */
+    carrier_disconnected_action_cancel(self);
+
     if (priv->l3cfg) {
         if (ip_ifindex_new <= 0 || ip_ifindex_new != nm_l3cfg_get_ifindex(priv->l3cfg)) {
             const NML3ConfigData *l3cd_old;
@@ -6584,11 +6637,15 @@ concheck_update_state(NMDevice           *self,
 
     _notify(self, IS_IPv4 ? PROP_IP4_CONNECTIVITY : PROP_IP6_CONNECTIVITY);
 
-    if (priv->state == NM_DEVICE_STATE_ACTIVATED && !nm_device_managed_type_is_external(self))
+    /* State change could've affected the route metrics (removed the penalty
+     * once FULL connectivity is reached), redo the L3 configuration. */
+    if (priv->state > NM_DEVICE_STATE_IP_CONFIG && priv->state < NM_DEVICE_STATE_DEACTIVATING
+        && !nm_device_managed_type_is_external(self)) {
         _dev_l3_register_l3cds(self, priv->l3cfg, TRUE, NM_TERNARY_DEFAULT);
+    }
 }
 
-static const char *
+const char *
 nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family)
 {
     NMDeviceClass *klass;
@@ -7725,9 +7782,7 @@ device_link_changed(gpointer user_data)
          * tagged for carrier ignore) ensure that when the carrier appears we
          * renew DHCP leases and such.
          */
-        if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
-            nm_device_update_dynamic_ip_setup(self, "interface got carrier");
-        }
+        nm_device_update_dynamic_ip_setup(self, "interface got carrier");
     }
 
     if (update_unmanaged_specs)
@@ -8974,7 +9029,7 @@ nm_device_port_notify_attach_as_port(NMDevice *self, gboolean success)
 
             priv->is_attached = TRUE;
 
-            _notify(priv->controller, PROP_CONTROLLER);
+            _notify(self, PROP_CONTROLLER);
 
             nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->controller)->ports_variant,
                              g_variant_unref);
@@ -9053,7 +9108,7 @@ nm_device_port_notify_release(NMDevice           *self,
 
     priv->is_attached = FALSE;
 
-    _notify(priv->controller, PROP_CONTROLLER);
+    _notify(self, PROP_CONTROLLER);
 
     nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->controller)->ports_variant, g_variant_unref);
     nm_gobject_notify_together(priv->controller, PROP_PORTS, PROP_SLAVES);
@@ -9521,7 +9576,7 @@ nm_device_generate_connection(NMDevice *self,
                  NM_SETTING_CONNECTION_ID,
                  ifname,
                  NM_SETTING_CONNECTION_AUTOCONNECT,
-                 FALSE,
+                 TRUE,
                  NM_SETTING_CONNECTION_INTERFACE_NAME,
                  ifname,
                  NM_SETTING_CONNECTION_TIMESTAMP,
@@ -11335,6 +11390,13 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d
     switch (notify_data->notify_type) {
     case NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED:
         nm_assert(!IS_IPv4);
+        if (notify_data->prefix_delegated.prefix->plen == 0
+            || notify_data->prefix_delegated.prefix->plen > 64) {
+            _LOGW_ipdhcp(addr_family,
+                         "ignoring invalid prefix-delegation with length %u",
+                         notify_data->prefix_delegated.prefix->plen);
+            return;
+        }
         /* Just re-emit. The device just contributes the prefix to the
          * pool in NMPolicy, which decides about subnet allocation
          * on the shared devices. */
@@ -13073,6 +13135,13 @@ activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family)
         goto out_devip;
 
     if (IS_IPv4) {
+        NMSettingIPConfigForwarding ipv4_forwarding = nm_device_get_ipv4_forwarding(self);
+
+        if (NM_IN_SET(ipv4_forwarding,
+                      NM_SETTING_IP_CONFIG_FORWARDING_NO,
+                      NM_SETTING_IP_CONFIG_FORWARDING_YES)) {
+            nm_device_sysctl_ip_conf_set(self, AF_INET, "forwarding", ipv4_forwarding ? "1" : "0");
+        }
         priv->ipll_data_4.v4.mode = _prop_get_ipv4_link_local(self);
         if (priv->ipll_data_4.v4.mode == NM_SETTING_IP4_LL_ENABLED)
             _dev_ipll4_start(self);
@@ -13369,7 +13438,8 @@ activate_stage3_ip_config(NMDevice *self)
              * IPv6LL if this is not an assumed connection, since assumed connections
              * will already have IPv6 set up.
              */
-            if (!nm_device_managed_type_is_external_or_assume(self))
+            if ((priv->state <= NM_DEVICE_STATE_IP_CONFIG || priv->ip_data_6.do_reapply)
+                && !nm_device_managed_type_is_external_or_assume(self))
                 _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
 
             /* Re-enable IPv6 on the interface */
@@ -13505,19 +13575,6 @@ _dev_ipshared4_init(NMDevice *self)
     }
 
     if (nm_platform_sysctl_get_int32(nm_device_get_platform(self),
-                                     NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
-                                     -1)
-        == 1) {
-        /* nothing to do. */
-    } else if (!nm_platform_sysctl_set(nm_device_get_platform(self),
-                                       NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
-                                       "1")) {
-        errsv = errno;
-        _LOGW_ipshared(AF_INET, "error enabling IPv4 forwarding: %s", nm_strerror_native(errsv));
-        return FALSE;
-    }
-
-    if (nm_platform_sysctl_get_int32(nm_device_get_platform(self),
                                      NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_dynaddr"),
                                      -1)
         == 1) {
@@ -13994,13 +14051,18 @@ can_reapply_change(NMDevice   *self,
         goto out_fail;
     }
 
-    if (NM_IN_STRSET(setting_name,
-                     NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME,
-                     NM_SETTING_OVS_OTHER_CONFIG_SETTING_NAME)
-        && NM_DEVICE_GET_CLASS(self)->can_reapply_change_ovs_external_ids) {
-        /* TODO: this means, you cannot reapply changes to the external-ids for
-         * OVS system interfaces. */
-        return TRUE;
+    if (nm_streq(setting_name, NM_SETTING_BRIDGE_PORT_SETTING_NAME)) {
+        return nm_device_hash_check_invalid_keys(diffs,
+                                                 NM_SETTING_BRIDGE_PORT_SETTING_NAME,
+                                                 error,
+                                                 NM_SETTING_BRIDGE_PORT_VLANS);
+    }
+
+    if (nm_streq(setting_name, NM_SETTING_SRIOV_SETTING_NAME)) {
+        return nm_device_hash_check_invalid_keys(diffs,
+                                                 NM_SETTING_SRIOV_SETTING_NAME,
+                                                 error,
+                                                 NM_SETTING_SRIOV_PRESERVE_ON_DOWN);
     }
 
 out_fail:
@@ -16851,6 +16913,8 @@ _cleanup_generic_post(NMDevice *self, NMDeviceStateReason reason, CleanupType cl
     priv->v4_route_table_all_sync_before = FALSE;
     priv->v6_route_table_all_sync_before = FALSE;
 
+    priv->refresh_forwarding_done = FALSE;
+
     priv->mtu_force_set_done = FALSE;
 
     priv->needs_ip6_subnet = FALSE;
@@ -16896,6 +16960,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
     NMDevicePrivate *priv;
     NMDeviceClass   *klass = NM_DEVICE_GET_CLASS(self);
     int              ifindex;
+    gint32           default_forwarding_v4;
 
     g_return_if_fail(NM_IS_DEVICE(self));
 
@@ -16918,6 +16983,17 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
         nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0");
     }
 
+    /* Restoring the device's forwarding to the sysctl default is necessary because
+     * `refresh_forwarding()` only updates forwarding on activated devices. */
+    default_forwarding_v4 = nm_platform_sysctl_get_int32(
+        nm_device_get_platform(self),
+        NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/conf/default/forwarding"),
+        0);
+    nm_device_sysctl_ip_conf_set(self,
+                                 AF_INET,
+                                 "forwarding",
+                                 default_forwarding_v4 == 1 ? "1" : "0");
+
     /* Call device type-specific deactivation */
     if (klass->deactivate)
         klass->deactivate(self);
@@ -17437,7 +17513,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
             }
 
             if (priv->ifindex > 0
-                && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
+                && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))
+                && (!_prop_get_sriov_preserve_on_down(self, s_sriov))) {
                 priv->sriov_reset_pending++;
                 sriov_op_queue(self,
                                0,
@@ -17492,7 +17569,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
             nm_settings_connection_update_timestamp(sett_conn, (guint64) 0);
 
         if (priv->ifindex > 0
-            && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
+            && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))
+            && (!_prop_get_sriov_preserve_on_down(self, s_sriov))) {
             priv->sriov_reset_pending++;
             sriov_op_queue(self,
                            0,
@@ -18613,7 +18691,7 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use
         gboolean valid;
 
         resolver->hostname = g_steal_pointer(&output);
-        valid              = nm_utils_validate_hostname(resolver->hostname);
+        valid              = nm_sd_dns_name_is_valid(resolver->hostname);
 
         _LOGD(LOGD_DNS,
               "hostname-from-dns: ipv%c resolver %s: lookup successful for %s, result %s%s%s%s",
@@ -18858,6 +18936,19 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     return nm_assert_unreachable_val(NULL);
 }
 
+gboolean
+nm_device_get_refresh_forwarding_done(NMDevice *self)
+{
+    return NM_DEVICE_GET_PRIVATE(self)->refresh_forwarding_done;
+}
+
+void
+nm_device_set_refresh_forwarding_done(NMDevice *self, gboolean is_refresh_forwarding_done)
+{
+    NMDevicePrivate *priv         = NM_DEVICE_GET_PRIVATE(self);
+    priv->refresh_forwarding_done = is_refresh_forwarding_done;
+}
+
 /*****************************************************************************/
 
 static const char *
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index 9b051ab4..9a663c2a 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -209,8 +209,6 @@ typedef struct _NMDeviceClass {
 
     bool act_stage1_prepare_set_hwaddr_ethernet : 1;
 
-    bool can_reapply_change_ovs_external_ids : 1;
-
     bool allow_autoconnect_on_external : 1;
 
     NMRfkillType rfkill_type : 4;
@@ -854,4 +852,14 @@ void nm_routing_rules_sync(NMConnection *applied_connection,
                            NMDevice *self,
                            NMNetns  *netns);
 
+NMSettingIPConfigForwarding nm_device_get_ipv4_forwarding(NMDevice *self);
+
+const char *nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family);
+
+char *nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property);
+
+gboolean nm_device_get_refresh_forwarding_done(NMDevice *self);
+
+void nm_device_set_refresh_forwarding_done(NMDevice *self, gboolean is_refresh_forwarding_done);
+
 #endif /* __NETWORKMANAGER_DEVICE_H__ */
diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c
index e08b379a..5c90911f 100644
--- a/src/core/devices/nm-lldp-listener.c
+++ b/src/core/devices/nm-lldp-listener.c
@@ -310,7 +310,7 @@ format_string(const guint8 *data, gsize len, gboolean allow_trim, char **out_to_
     if (len == 0)
         return NULL;
 
-    if (memchr(data, len, '\0'))
+    if (memchr(data, '\0', len))
         return NULL;
 
     return nm_utils_buf_utf8safe_escape(data,
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index c9b1f4c6..c9736eeb 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -162,6 +162,11 @@ can_reapply_change(NMDevice   *device,
                                                  NM_SETTING_OVS_BRIDGE_STP_ENABLE);
     }
 
+    if (NM_IN_STRSET(setting_name,
+                     NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME,
+                     NM_SETTING_OVS_OTHER_CONFIG_SETTING_NAME))
+        return TRUE;
+
     return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
 }
 
@@ -194,16 +199,15 @@ nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
     device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES();
 
-    device_class->is_controller                       = TRUE;
-    device_class->get_type_description                = get_type_description;
-    device_class->create_and_realize                  = create_and_realize;
-    device_class->unrealize                           = unrealize;
-    device_class->get_generic_capabilities            = get_generic_capabilities;
-    device_class->act_stage3_ip_config                = act_stage3_ip_config;
-    device_class->ready_for_ip_config                 = ready_for_ip_config;
-    device_class->attach_port                         = attach_port;
-    device_class->detach_port                         = detach_port;
-    device_class->can_reapply_change                  = can_reapply_change;
-    device_class->can_reapply_change_ovs_external_ids = TRUE;
-    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
+    device_class->is_controller            = TRUE;
+    device_class->get_type_description     = get_type_description;
+    device_class->create_and_realize       = create_and_realize;
+    device_class->unrealize                = unrealize;
+    device_class->get_generic_capabilities = get_generic_capabilities;
+    device_class->act_stage3_ip_config     = act_stage3_ip_config;
+    device_class->ready_for_ip_config      = ready_for_ip_config;
+    device_class->attach_port              = attach_port;
+    device_class->detach_port              = detach_port;
+    device_class->can_reapply_change       = can_reapply_change;
+    device_class->reapply_connection       = nm_device_ovs_reapply_connection;
 }
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 512aa5c3..922ed345 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -16,6 +16,8 @@
 #include "nm-setting-ovs-bridge.h"
 #include "nm-setting-ovs-interface.h"
 #include "nm-setting-ovs-port.h"
+#include "nm-setting-ovs-external-ids.h"
+#include "nm-setting-ovs-other-config.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceOvsInterface
 #include "devices/nm-device-logging.h"
@@ -631,6 +633,28 @@ can_update_from_platform_link(NMDevice *device, const NMPlatformLink *plink)
     return !plink || nm_device_get_state(device) != NM_DEVICE_STATE_DEACTIVATING;
 }
 
+static gboolean
+can_reapply_change(NMDevice   *device,
+                   const char *setting_name,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
+                   GHashTable *diffs,
+                   GError    **error)
+{
+    NMDeviceClass *device_class = NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class);
+
+    if (NM_IN_STRSET(setting_name,
+                     NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME,
+                     NM_SETTING_OVS_OTHER_CONFIG_SETTING_NAME)) {
+        /* TODO: it's currently not possible to reapply those settings on OVS
+         * system interfaces because they have type != "ovs-interface" (e.g.
+         * "ethernet") */
+        return TRUE;
+    }
+
+    return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
+}
+
 /*****************************************************************************/
 
 static void
@@ -696,21 +720,21 @@ nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
     device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH);
 
-    device_class->can_auto_connect                    = can_auto_connect;
-    device_class->can_update_from_platform_link       = can_update_from_platform_link;
-    device_class->deactivate                          = deactivate;
-    device_class->deactivate_async                    = deactivate_async;
-    device_class->get_type_description                = get_type_description;
-    device_class->create_and_realize                  = create_and_realize;
-    device_class->get_generic_capabilities            = get_generic_capabilities;
-    device_class->is_available                        = is_available;
-    device_class->check_connection_compatible         = check_connection_compatible;
-    device_class->link_changed                        = link_changed;
-    device_class->act_stage3_ip_config                = act_stage3_ip_config;
-    device_class->ready_for_ip_config                 = ready_for_ip_config;
-    device_class->can_unmanaged_external_down         = can_unmanaged_external_down;
-    device_class->set_platform_mtu                    = set_platform_mtu;
-    device_class->get_configured_mtu                  = nm_device_get_configured_mtu_for_wired;
-    device_class->can_reapply_change_ovs_external_ids = TRUE;
-    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
+    device_class->can_auto_connect              = can_auto_connect;
+    device_class->can_update_from_platform_link = can_update_from_platform_link;
+    device_class->deactivate                    = deactivate;
+    device_class->deactivate_async              = deactivate_async;
+    device_class->get_type_description          = get_type_description;
+    device_class->create_and_realize            = create_and_realize;
+    device_class->get_generic_capabilities      = get_generic_capabilities;
+    device_class->is_available                  = is_available;
+    device_class->check_connection_compatible   = check_connection_compatible;
+    device_class->link_changed                  = link_changed;
+    device_class->act_stage3_ip_config          = act_stage3_ip_config;
+    device_class->ready_for_ip_config           = ready_for_ip_config;
+    device_class->can_unmanaged_external_down   = can_unmanaged_external_down;
+    device_class->set_platform_mtu              = set_platform_mtu;
+    device_class->get_configured_mtu            = nm_device_get_configured_mtu_for_wired;
+    device_class->can_reapply_change            = can_reapply_change;
+    device_class->reapply_connection            = nm_device_ovs_reapply_connection;
 }
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index e9928548..0e3ef92c 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -16,6 +16,8 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-ovs-port.h"
 #include "nm-setting-ovs-interface.h"
+#include "nm-setting-ovs-external-ids.h"
+#include "nm-setting-ovs-other-config.h"
 #include "nm-setting-wired.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceOvsPort
@@ -279,6 +281,11 @@ can_reapply_change(NMDevice   *device,
                                                  NM_SETTING_OVS_PORT_TRUNKS);
     }
 
+    if (NM_IN_STRSET(setting_name,
+                     NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME,
+                     NM_SETTING_OVS_OTHER_CONFIG_SETTING_NAME))
+        return TRUE;
+
     return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
 }
 
@@ -311,15 +318,14 @@ nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_PORT_SETTING_NAME;
     device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES();
 
-    device_class->is_controller                       = TRUE;
-    device_class->get_type_description                = get_type_description;
-    device_class->create_and_realize                  = create_and_realize;
-    device_class->get_generic_capabilities            = get_generic_capabilities;
-    device_class->act_stage3_ip_config                = act_stage3_ip_config;
-    device_class->ready_for_ip_config                 = ready_for_ip_config;
-    device_class->attach_port                         = attach_port;
-    device_class->detach_port                         = detach_port;
-    device_class->can_reapply_change                  = can_reapply_change;
-    device_class->can_reapply_change_ovs_external_ids = TRUE;
-    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
+    device_class->is_controller            = TRUE;
+    device_class->get_type_description     = get_type_description;
+    device_class->create_and_realize       = create_and_realize;
+    device_class->get_generic_capabilities = get_generic_capabilities;
+    device_class->act_stage3_ip_config     = act_stage3_ip_config;
+    device_class->ready_for_ip_config      = ready_for_ip_config;
+    device_class->attach_port              = attach_port;
+    device_class->detach_port              = detach_port;
+    device_class->can_reapply_change       = can_reapply_change;
+    device_class->reapply_connection       = nm_device_ovs_reapply_connection;
 }
diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
index 2ca1a0b5..4084356f 100644
--- a/src/core/devices/ovs/nm-ovs-factory.c
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -75,9 +75,6 @@ new_device_from_type(const char *name, NMDeviceType device_type)
     const char *type_desc;
     NMLinkType  link_type = NM_LINK_TYPE_NONE;
 
-    if (nm_manager_get_device(NM_MANAGER_GET, name, device_type))
-        return NULL;
-
     if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE) {
         type      = NM_TYPE_DEVICE_OVS_INTERFACE;
         type_desc = "Open vSwitch Interface";
@@ -89,6 +86,7 @@ new_device_from_type(const char *name, NMDeviceType device_type)
         type      = NM_TYPE_DEVICE_OVS_BRIDGE;
         type_desc = "Open vSwitch Bridge";
     } else {
+        _LOGT(name, NULL, "Unrecognized link type: %d", device_type);
         return NULL;
     }
 
@@ -125,6 +123,11 @@ ovsdb_device_added(NMOvsdb         *ovsdb,
         return;
     }
 
+    if (nm_manager_get_device(NM_MANAGER_GET, name, device_type)) {
+        _LOGT(name, NULL, "Device already registered with manager, skipping.");
+        return;
+    }
+
     device = new_device_from_type(name, device_type);
     if (!device)
         return;
@@ -288,23 +291,31 @@ create_device(NMDeviceFactory      *self,
 {
     NMDeviceType device_type     = NM_DEVICE_TYPE_UNKNOWN;
     const char  *connection_type = NULL;
+    const char  *connection_uuid = NULL;
 
     if (g_strcmp0(iface, "ovs-system") == 0) {
         *out_ignore = TRUE;
         return NULL;
     }
 
-    if (connection)
+    if (connection) {
         connection_type = nm_connection_get_connection_type(connection);
+        connection_uuid = nm_connection_get_uuid(connection);
+    }
 
-    if (plink)
+    if (plink) {
+        _LOGD(iface, connection_uuid, "creating OVS interface (from a platform link)");
         device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
-    else if (g_strcmp0(connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0)
+    } else if (nm_streq0(connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME)) {
+        _LOGD(iface, connection_uuid, "creating OVS interface (from setting)");
         device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
-    else if (g_strcmp0(connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0)
+    } else if (nm_streq0(connection_type, NM_SETTING_OVS_PORT_SETTING_NAME)) {
+        _LOGD(iface, connection_uuid, "creating OVS port (from setting)");
         device_type = NM_DEVICE_TYPE_OVS_PORT;
-    else if (g_strcmp0(connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME) == 0)
+    } else if (nm_streq0(connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) {
+        _LOGD(iface, connection_uuid, "creating OVS bridge (from setting)");
         device_type = NM_DEVICE_TYPE_OVS_BRIDGE;
+    }
 
     return new_device_from_type(iface, device_type);
 }
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 2dbc842a..164bfd35 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -900,15 +900,17 @@ _insert_interface(json_t       *params,
         s_ovs_patch = nm_connection_get_setting_ovs_patch(interface);
 
     if (s_ovs_dpdk) {
-        const char *devargs;
-        guint32     n_rxq;
-        guint32     n_rxq_desc;
-        guint32     n_txq_desc;
+        const char                  *devargs;
+        guint32                      n_rxq;
+        guint32                      n_rxq_desc;
+        guint32                      n_txq_desc;
+        NMSettingOvsDpdkLscInterrupt lsc_int;
 
         devargs    = nm_setting_ovs_dpdk_get_devargs(s_ovs_dpdk);
         n_rxq      = nm_setting_ovs_dpdk_get_n_rxq(s_ovs_dpdk);
         n_rxq_desc = nm_setting_ovs_dpdk_get_n_rxq_desc(s_ovs_dpdk);
         n_txq_desc = nm_setting_ovs_dpdk_get_n_txq_desc(s_ovs_dpdk);
+        lsc_int    = nm_setting_ovs_dpdk_get_lsc_interrupt(s_ovs_dpdk);
 
         dpdk_array = json_array();
 
@@ -930,6 +932,17 @@ _insert_interface(json_t       *params,
                 json_pack("[s,s]", "n_txq_desc", nm_sprintf_buf(sbuf, "%u", n_txq_desc)));
         }
 
+        switch (lsc_int) {
+        case NM_SETTING_OVS_DPDK_LSC_INTERRUPT_IGNORE:
+            break;
+        case NM_SETTING_OVS_DPDK_LSC_INTERRUPT_ENABLED:
+            json_array_append_new(dpdk_array, json_pack("[s,s]", "dpdk-lsc-interrupt", "true"));
+            break;
+        case NM_SETTING_OVS_DPDK_LSC_INTERRUPT_DISABLED:
+            json_array_append_new(dpdk_array, json_pack("[s,s]", "dpdk-lsc-interrupt", "false"));
+            break;
+        }
+
         json_array_append_new(options, dpdk_array);
 
     } else if (s_ovs_patch) {
@@ -1374,88 +1387,113 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
     nm_auto_decref_json json_t *bridges     = NULL;
     nm_auto_decref_json json_t *new_bridges = NULL;
     gboolean                    bridges_changed;
-    gboolean                    ports_changed;
-    gboolean                    interfaces_changed;
-    int                         pi;
-    int                         ii;
 
     bridges         = json_array();
     new_bridges     = json_array();
     bridges_changed = FALSE;
 
+    /* Loop over all bridges */
     g_hash_table_iter_init(&iter, priv->bridges);
     while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) {
-        nm_auto_decref_json json_t *ports     = NULL;
-        nm_auto_decref_json json_t *new_ports = NULL;
+        nm_auto_decref_json json_t *ports         = NULL;
+        nm_auto_decref_json json_t *new_ports     = NULL;
+        guint                       num_nm_ports  = 0;
+        gboolean                    ports_changed = FALSE;
+        int                         pi;
 
-        ports         = json_array();
-        new_ports     = json_array();
-        ports_changed = FALSE;
+        ports     = json_array();
+        new_ports = json_array();
 
+        /* Add the bridge UUID to the list of known bridges for the "expect" condition */
         json_array_append_new(bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
 
+        if (!ovs_bridge->connection_uuid) {
+            /* Externally created, don't touch it */
+            json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
+            continue;
+        }
+
+        /* Loop over all bridge's ports */
         for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
-            nm_auto_decref_json json_t *interfaces     = NULL;
-            nm_auto_decref_json json_t *new_interfaces = NULL;
+            nm_auto_decref_json json_t *interfaces         = NULL;
+            nm_auto_decref_json json_t *new_interfaces     = NULL;
+            guint                       num_nm_interfaces  = 0;
+            gboolean                    interfaces_changed = FALSE;
+            int                         ii;
 
             interfaces     = json_array();
             new_interfaces = json_array();
             port_uuid      = g_ptr_array_index(ovs_bridge->ports, pi);
             ovs_port       = g_hash_table_lookup(priv->ports, &port_uuid);
 
+            /* Add the port UUID to the list of known bridge port for the "expect" condition */
             json_array_append_new(ports, json_pack("[s,s]", "uuid", port_uuid));
 
-            interfaces_changed = FALSE;
-
             if (!ovs_port) {
                 /* This would be a violation of ovsdb's reference integrity (a bug). */
                 _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid);
                 continue;
             }
 
+            /* Loop over all port's interfaces */
             for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
                 interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii);
                 ovs_interface  = g_hash_table_lookup(priv->interfaces, &interface_uuid);
 
+                /* Add the interface UUID to the list of known port interfaces for the "expect" condition */
                 json_array_append_new(interfaces, json_pack("[s,s]", "uuid", interface_uuid));
 
                 if (ovs_interface) {
                     if (nm_streq(ovs_interface->name, ifname)) {
-                        /* skip the interface */
+                        /* We are deleting this interface, don't count it */
                         interfaces_changed = TRUE;
                         continue;
                     }
+                    if (ovs_interface->connection_uuid)
+                        num_nm_interfaces++;
                 } else {
                     /* This would be a violation of ovsdb's reference integrity (a bug). */
                     _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
                 }
 
+                /* Add the interface to the list of new interfaces to set on the port */
                 json_array_append_new(new_interfaces, json_pack("[s,s]", "uuid", interface_uuid));
             }
 
-            if (json_array_size(new_interfaces) == 0) {
+            if (num_nm_interfaces == 0) {
+                /* The port no longer has any NM interface. Don't add it to "new_ports" and set
+                 * ports_changed=TRUE, so that it will be deleted. */
                 ports_changed = TRUE;
             } else {
                 if (interfaces_changed) {
+                    /* An interface needs to be deleted from this port */
                     _expect_port_interfaces(params, ovs_port->name, interfaces);
                     _set_port_interfaces(params, ovs_port->name, new_interfaces);
                 }
+                /* The port is still alive */
                 json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid));
+                if (ovs_port->connection_uuid)
+                    num_nm_ports++;
             }
         }
 
-        if (json_array_size(new_ports) == 0) {
+        if (num_nm_ports == 0) {
+            /* The bridge no longer has any NM port. Don't add it to "new_bridges" and set
+             * bridges_changed=TRUE, so that it will be deleted. */
             bridges_changed = TRUE;
         } else {
             if (ports_changed) {
+                /* A port needs to be deleted from this bridge */
                 _expect_bridge_ports(params, ovs_bridge->name, ports);
                 _set_bridge_ports(params, ovs_bridge->name, new_ports);
             }
+            /* The bridge is still alive */
             json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
         }
     }
 
     if (bridges_changed) {
+        /* A port needs to be deleted from this bridge */
         _expect_ovs_bridges(params, priv->db_uuid, bridges);
         _set_ovs_bridges(params, priv->db_uuid, new_bridges);
     }
@@ -2211,7 +2249,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
                                            ovs_bridge->connection_uuid,
                                            ""),
                       (strtmp1 = _strdict_to_string(ovs_bridge->external_ids)),
-                      (strtmp2 = _strdict_to_string(ovs_bridge->external_ids)));
+                      (strtmp2 = _strdict_to_string(ovs_bridge->other_config)));
             }
         } else {
             gs_free char *strtmp1 = NULL;
diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c
index ce03c607..30cfd8e6 100644
--- a/src/core/dhcp/nm-dhcp-options.c
+++ b/src/core/dhcp/nm-dhcp-options.c
@@ -68,7 +68,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
     REQ(NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT, "arp_cache_timeout", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION, "ieee802_3_encapsulation", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL, "default_tcp_ttl", FALSE),
-    REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL, "tcp_keepalive_internal", FALSE),
+    REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL, "tcp_keepalive_interval", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE, "tcp_keepalive_garbage", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, "vendor_encapsulated_options", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, "netbios_name_servers", FALSE),
diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index e1761523..b570f7e5 100644
--- a/src/core/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -395,7 +395,7 @@ ip6_start(NMDhcpClient *client, const struct in6_addr *ll_addr, GError **error)
     prefix_delegation = FALSE;
     if (client_config->v6.needed_prefixes > 0) {
         if (client_config->v6.needed_prefixes > 1) {
-            /* FIXME: systemd-networkd API only allows to request a
+            /* FIXME: systemd-networkd API only allows requesting a
              * single prefix */
             _LOGW("dhcp-client6: only one prefix request is supported");
         }
diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c
index 15293fa3..949d8720 100644
--- a/src/core/dhcp/nm-dhcp-utils.c
+++ b/src/core/dhcp/nm-dhcp-utils.c
@@ -1224,7 +1224,7 @@ lease_option_print_domain_name(const uint8_t  *cache,
         }
         case 0xC0: /* back pointer */
         {
-            size_t offset = (c & 0x3F) << 16;
+            size_t offset = (c & 0x3F) << 8;
 
             /*
              * The offset is given as two bytes (in big endian), where the
diff --git a/src/core/dhcp/tests/test-dhcp-utils.c b/src/core/dhcp/tests/test-dhcp-utils.c
index b81523e1..de1be653 100644
--- a/src/core/dhcp/tests/test-dhcp-utils.c
+++ b/src/core/dhcp/tests/test-dhcp-utils.c
@@ -239,6 +239,76 @@ test_parse_search_list(void)
     g_assert_cmpint(g_strv_length(domains), ==, 1);
     g_assert_cmpstr(domains[0], ==, "okay");
     g_strfreev(domains);
+
+    /* Test that the message compression works when the offset uses both bytes */
+    data = (guint8[]) {
+        /* clang-format off */
+         /* offset 0 */
+         0x3e,
+        'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
+        'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
+        'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a',
+        'a','a','a','a','a','a','a','a','a','a','a','a','a','a',
+        0x00,
+        /* offset 0x40 */
+        0x3e,
+        'b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b',
+        'b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b',
+        'b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b',
+        'b','b','b','b','b','b','b','b','b','b','b','b','b','b',
+        0x00,
+        /* offset 0x80 */
+        0x3e,
+        'c','c','c','c','c','c','c','c','c','c','c','c','c','c','c','c',
+        'c','c','c','c','c','c','c','c','c','c','c','c','c','c','c','c',
+        'c','c','c','c','c','c','c','c','c','c','c','c','c','c','c','c',
+        'c','c','c','c','c','c','c','c','c','c','c','c','c','c',
+        0x00,
+        /* offset 0xc0 */
+        0x3e,
+        'd','d','d','d','d','d','d','d','d','d','d','d','d','d','d','d',
+        'd','d','d','d','d','d','d','d','d','d','d','d','d','d','d','d',
+        'd','d','d','d','d','d','d','d','d','d','d','d','d','d','d','d',
+        'd','d','d','d','d','d','d','d','d','d','d','d','d','d',
+        0x00,
+        /* offset 0x100 */
+        0x3e,
+        'e','e','e','e','e','e','e','e','e','e','e','e','e','e','e','e',
+        'e','e','e','e','e','e','e','e','e','e','e','e','e','e','e','e',
+        'e','e','e','e','e','e','e','e','e','e','e','e','e','e','e','e',
+        'e','e','e','e','e','e','e','e','e','e','e','e','e','e',
+        0x00,
+        /* offset 0x140 */
+        0x06, 'f','o','o','b','a','r', 0x03, 'c', 'o', 'm', 0x00,
+        0x04, 't', 'e', 's', 't', 0xc1, 0x40, /* back pointer to offset 0x140*/
+        /* clang-format on */
+    };
+
+    domains = nm_dhcp_lease_data_parse_search_list(data,
+                                                   0x153,
+                                                   "eth0",
+                                                   AF_INET,
+                                                   NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST);
+    g_assert(domains);
+    g_assert_cmpint(g_strv_length(domains), ==, 7);
+    g_assert_cmpstr(domains[0],
+                    ==,
+                    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+    g_assert_cmpstr(domains[1],
+                    ==,
+                    "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
+    g_assert_cmpstr(domains[2],
+                    ==,
+                    "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc");
+    g_assert_cmpstr(domains[3],
+                    ==,
+                    "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
+    g_assert_cmpstr(domains[4],
+                    ==,
+                    "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
+    g_assert_cmpstr(domains[5], ==, "foobar.com");
+    g_assert_cmpstr(domains[6], ==, "test.foobar.com");
+    g_strfreev(domains);
 }
 
 static void
diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c
index c17fb9cf..3789caf8 100644
--- a/src/core/dns/nm-dns-dnsconfd.c
+++ b/src/core/dns/nm-dns-dnsconfd.c
@@ -368,7 +368,8 @@ server_builder_append_base(GVariantBuilder   *argument_builder,
                            const char        *address_string,
                            const char *const *routing_domains,
                            const char *const *search_domains,
-                           const char        *ca)
+                           const char        *ca,
+                           int                priority)
 {
     NMDnsServer dns_server;
     gsize       addr_size;
@@ -392,6 +393,12 @@ server_builder_append_base(GVariantBuilder   *argument_builder,
                               "{sv}",
                               "name",
                               g_variant_new("s", dns_server.servername));
+    if (dns_server.port != NM_DNS_PORT_UNDEFINED) {
+        g_variant_builder_add(argument_builder,
+                              "{sv}",
+                              "port",
+                              g_variant_new("i", dns_server.port));
+    }
     if (routing_domains) {
         g_variant_builder_add(argument_builder,
                               "{sv}",
@@ -407,6 +414,9 @@ server_builder_append_base(GVariantBuilder   *argument_builder,
     if (ca) {
         g_variant_builder_add(argument_builder, "{sv}", "ca", g_variant_new("s", ca));
     }
+    /* dnsconfd defines priority as bigger number equals bigger priority, while NM
+     * uses the exact opposite, thus use -priority */
+    g_variant_builder_add(argument_builder, "{sv}", "priority", g_variant_new("i", -priority));
     return TRUE;
 }
 
@@ -443,7 +453,8 @@ parse_global_config(const NMGlobalDnsConfig *global_config,
                                            servers[j],
                                            routing_domains,
                                            searches,
-                                           *ca)) {
+                                           *ca,
+                                           NM_DNS_PRIORITY_DEFAULT_NORMAL)) {
                 g_variant_builder_close(argument_builder);
             }
         }
@@ -560,7 +571,7 @@ dnsconfd_start_done(GObject *source_object, GAsyncResult *res, gpointer user_dat
         g_dbus_error_strip_remote_error(error);
         _LOGW("failed to start Dnsconfd %s", error->message);
     } else {
-        _LOGT("succesfully started Dnsconfd");
+        _LOGT("successfully started Dnsconfd");
     }
 
     /* No update maybe changed or state change, as this is handled by the name owner callbacks
@@ -628,6 +639,7 @@ parse_all_interface_config(GVariantBuilder *argument_builder,
     NMDnsConfigIPData *ip_data;
     const char *const *dns_server_strings;
     guint              nameserver_count;
+    int                priority;
     const char        *ifname;
     gboolean           explicit_default = is_default_interface_explicit(ip_data_lst_head);
 
@@ -648,6 +660,9 @@ parse_all_interface_config(GVariantBuilder *argument_builder,
 
         gather_interface_domains(ip_data, explicit_default, &routing_domains, &search_domains);
         get_networks(ip_data, &networks);
+        if (!nm_l3_config_data_get_dns_priority(ip_data->l3cd, ip_data->addr_family, &priority)) {
+            priority = NM_DNS_PRIORITY_DEFAULT_NORMAL;
+        }
 
         for (guint i = 0; i < nameserver_count; i++) {
             if (server_builder_append_base(argument_builder,
@@ -655,7 +670,8 @@ parse_all_interface_config(GVariantBuilder *argument_builder,
                                            dns_server_strings[i],
                                            routing_domains,
                                            search_domains,
-                                           ca)) {
+                                           ca,
+                                           priority)) {
                 server_builder_append_interface_info(argument_builder, ifname, networks);
             }
         }
diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c
index 33e9ebf3..f0de2fd5 100644
--- a/src/core/ndisc/nm-lndp-ndisc.c
+++ b/src/core/ndisc/nm-lndp-ndisc.c
@@ -218,10 +218,22 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
         /* Address */
         if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) {
-            const guint32 valid_time = ndp_msg_opt_prefix_valid_time(msg, offset);
-            const guint32 preferred_time =
-                NM_MIN(ndp_msg_opt_prefix_preferred_time(msg, offset), valid_time);
-            const NMNDiscAddress address = {
+            const guint32  valid_time     = ndp_msg_opt_prefix_valid_time(msg, offset);
+            const guint32  preferred_time = ndp_msg_opt_prefix_preferred_time(msg, offset);
+            NMNDiscAddress address;
+
+            /*
+             * RFC 4862 Section 5.5.3 states:
+             * c)  If the preferred lifetime is greater than the valid lifetime,
+             * silently ignore the Prefix Information option. A node MAY wish to
+             * log a system management error in this case.
+             */
+            if (preferred_time > valid_time) {
+                _LOGW("skipping PIO - preferred lifetime > valid lifetime");
+                continue;
+            }
+
+            address = (NMNDiscAddress) {
                 .address               = r_network,
                 .expiry_msec           = _nm_ndisc_lifetime_to_expiry(now_msec, valid_time),
                 .expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred_time),
diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c
index ad2edd8d..1a2bf480 100644
--- a/src/core/ndisc/nm-ndisc.c
+++ b/src/core/ndisc/nm-ndisc.c
@@ -1490,7 +1490,7 @@ clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint
         g_array_set_size(rdata->addresses, j);
     }
 
-    if (_array_set_size_max(rdata->gateways, priv->config.max_addresses))
+    if (_array_set_size_max(rdata->addresses, priv->config.max_addresses))
         *changed |= NM_NDISC_CONFIG_ADDRESSES;
 }
 
@@ -1830,7 +1830,7 @@ _config_init(NMNDiscConfig *config, const NMNDiscConfig *src)
     nm_assert(config);
     g_return_if_fail(src);
 
-    /* we only allow to set @config if it was cleared (or is not yet initialized). */
+    /* we only allow one to set @config if it was cleared (or is not yet initialized). */
     nm_assert(!config->l3cfg);
     nm_assert(!config->ifname);
     nm_assert(!config->network_id);
diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c
index 116cb737..abaab7d4 100644
--- a/src/core/nm-bond-manager.c
+++ b/src/core/nm-bond-manager.c
@@ -916,21 +916,21 @@ nm_bond_manager_send_arp(int                 bond_ifindex,
     if (announce_fdb) {
         /* if we are announcing the FDB we do a RARP, we don't set the
          * source/dest IPv4 address */
-        int                   ifindexes[] = {bridge_ifindex, bond_ifindex};
-        int                   i;
-        gs_free NMEtherAddr **fdb_addrs = NULL;
+        int                         ifindexes[] = {bridge_ifindex, bond_ifindex};
+        int                         i;
+        nm_auto_freev NMEtherAddr **fdb_addrs = NULL;
 
-        fdb_addrs = nm_linux_platform_get_link_fdb_table(platform, ifindexes, 2);
+        fdb_addrs = nm_linux_platform_get_bridge_fdb(platform, ifindexes, 2);
         /* we want to send a Reverse ARP (RARP) packet */
         data.op = htons(ARP_OP_RARP);
 
         i = 0;
         while (fdb_addrs[i] != NULL) {
             NMEtherAddr *tmp_hwaddr = fdb_addrs[i];
+
             memcpy(data.s_hw_addr, tmp_hwaddr, ETH_ALEN);
             memcpy(data.d_hw_addr, tmp_hwaddr, ETH_ALEN);
             memcpy(data.s_addr, tmp_hwaddr, ETH_ALEN);
-            g_free(tmp_hwaddr);
             if (sendto(sockfd, &data, sizeof(data), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0)
                 return FALSE;
             i++;
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 895a9917..d3c2eb21 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -4642,13 +4642,13 @@ get_max_rate_vht_80_ss3(int mcs)
     case 5:
         return 702000000;
     case 6:
-        return 0;
+        return 0; /* invalid */
     case 7:
         return 877500000;
     case 8:
-        return 105300000;
+        return 1053000000;
     case 9:
-        return 117000000;
+        return 1170000000;
     }
     return 0;
 }
@@ -4732,7 +4732,7 @@ get_max_rate_vht_160_ss3(int mcs)
     case 8:
         return 2106000000;
     case 9:
-        return 0;
+        return 0; /* invalid */
     }
     return 0;
 }
@@ -5417,7 +5417,7 @@ again:
     if ((g = g_atomic_int_get(&g_static)) == -1) {
         gid_t g2;
 
-        g2 = geteuid();
+        g2 = getegid();
         g  = g2;
         nm_assert(g == g2);
         nm_assert(g >= 0);
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 70867ff7..20623145 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -1419,6 +1419,9 @@ _check_and_add_domain(GPtrArray **p_arr, const char *domain)
     if (domain[0] == '.' || strstr(domain, ".."))
         return FALSE;
 
+    if (!g_utf8_validate(domain, -1, NULL))
+        return FALSE;
+
     len = strlen(domain);
     if (domain[len - 1] == '.') {
         copy   = g_strndup(domain, len - 1);
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c
index a9aa506d..1797cd0f 100644
--- a/src/core/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -2294,7 +2294,7 @@ _l3_acd_data_timeout_schedule(AcdData *acd_data, gint64 timeout_msec)
      * expect timeouts in certain states.
      *
      * That means, scheduling a timeout is only correct if we are in a certain
-     * state, which allows to handle timeouts. This assert checks for that to
+     * state, which allows one to handle timeouts. This assert checks for that to
      * ensure we don't call a timeout in an unexpected state. */
     nm_assert(NM_IN_SET(acd_data->info.state,
                         NM_L3_ACD_ADDR_STATE_PROBING,
@@ -2751,9 +2751,8 @@ handle_init:
                     goto handle_start_defending;
                 }
 
-                acd_data->probing_timestamp_msec = (*p_now_msec);
-                acd_data->probing_timeout_msec   = acd_timeout_msec;
-                log_reason                       = "retry probing on timeout";
+                acd_data->probing_timeout_msec = acd_timeout_msec;
+                log_reason                     = "retry probing on timeout";
                 goto handle_start_probing;
             }
 
@@ -3050,7 +3049,7 @@ handle_start_probing:
         if (!acd_data->nacd_probe) {
             _LOGT_acd(acd_data,
                       "probing currently %snot possible (timeout %u msec; %s, %s)",
-                      orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : " still",
+                      orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : "still ",
                       acd_data->probing_timeout_msec,
                       failure_reason,
                       log_reason);
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index a861b2fe..fce37071 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -478,7 +478,9 @@ _version_info_get(void)
          * Each of the array's elements has 32 bits. This means that capabilities
          * with index 0-31 goes to element #1, with index 32-63 to element #2,
          * with index 64-95 to element #3 and so on. */
-        1 << NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE,
+        (1 << NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE)
+            | (1 << NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING)
+            | (1 << NM_VERSION_INFO_CAPABILITY_SRIOV_PRESERVE_ON_DOWN),
     };
 
     return nm_g_variant_new_au(arr, G_N_ELEMENTS(arr));
@@ -4250,6 +4252,7 @@ add:
         case NM_LINK_TYPE_OLPC_MESH:
         case NM_LINK_TYPE_TEAM:
         case NM_LINK_TYPE_WIFI:
+        case NM_LINK_TYPE_OPENVSWITCH:
             _LOGI(LOGD_PLATFORM,
                   "(%s): '%s' plugin not available; creating generic device",
                   plink->name,
@@ -4689,16 +4692,10 @@ found_better:
         if (nm_g_hash_table_contains(exclude_devices, device))
             continue;
 
-        /* During startup, NM performs a cleanup of the ovsdb to remove previous entries.
-         * Before the device is suitable for the connection, it must have ovsdb->ready set
-         * to TRUE. Performing this check in all kind of interfaces is too agressive and leads
-         * to race conditions, e.g when a non-virtual bond port gets a carrier, preventing the
-         * device to be a good candidate for the connection. */
-        if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE
-            && !nm_device_is_available(device,
-                                       for_user_request
-                                           ? NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST
-                                           : NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
+        if (!nm_device_is_available(device,
+                                    for_user_request
+                                        ? NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST
+                                        : NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
             continue;
 
         /* determine the priority of this device. Currently, this priority is independent
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c
index 57dbe9c5..f481bc36 100644
--- a/src/core/nm-netns.c
+++ b/src/core/nm-netns.c
@@ -75,7 +75,7 @@ typedef struct {
     /* Indexes the watcher handles. */
     GHashTable *watcher_idx;
 
-    /* An index of WatcherByTag. It allows to lookup watcher handles by tag.
+    /* An index of WatcherByTag. It allows one to lookup watcher handles by tag.
      * Handles without tag are not indexed. */
     GHashTable *watcher_by_tag_idx;
 
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index 33073e4a..fbcee40d 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -18,6 +18,7 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-factory.h"
+#include "devices/nm-device-private.h"
 #include "dns/nm-dns-manager.h"
 #include "nm-act-request.h"
 #include "nm-auth-utils.h"
@@ -97,7 +98,6 @@ typedef struct {
     bool updating_dns : 1;
 
     GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */
-
 } NMPolicyPrivate;
 
 struct _NMPolicy {
@@ -155,17 +155,15 @@ static gboolean  hostname_retry_cb(gpointer user_data);
 
 typedef struct {
     NMPlatformIP6Address prefix;
-    NMDevice            *device;      /* The requesting ("uplink") device */
-    guint64              next_subnet; /* Cache of the next subnet number to be
-                                       * assigned from this prefix */
-    GHashTable          *subnets;     /* ifindex -> NMPlatformIP6Address */
+    NMDevice            *device;                   /* The requesting ("uplink") device */
+    GHashTable          *map_subnet_id_to_ifindex; /* (guint64 *) subnet_id -> int ifindex */
+    GHashTable          *map_ifindex_to_subnet; /* int ifindex -> (NMPlatformIP6Address *) prefix */
 } IP6PrefixDelegation;
 
 static void
-_clear_ip6_subnet(gpointer key, gpointer value, gpointer user_data)
+clear_ip6_subnet(int ifindex, NMPlatformIP6Address *subnet)
 {
-    NMPlatformIP6Address *subnet = value;
-    NMDevice *device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, GPOINTER_TO_INT(key));
+    NMDevice *device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, ifindex);
 
     if (device) {
         /* We can not remove a subnet we already started announcing.
@@ -177,6 +175,12 @@ _clear_ip6_subnet(gpointer key, gpointer value, gpointer user_data)
 }
 
 static void
+clear_ip6_subnet_entry(gpointer key, gpointer value, gpointer user_data)
+{
+    clear_ip6_subnet(GPOINTER_TO_INT(key), value);
+}
+
+static void
 clear_ip6_prefix_delegation(gpointer data)
 {
     IP6PrefixDelegation *delegation = data;
@@ -187,8 +191,9 @@ clear_ip6_prefix_delegation(gpointer data)
           nm_inet6_ntop(&delegation->prefix.address, sbuf),
           delegation->prefix.plen);
 
-    g_hash_table_foreach(delegation->subnets, _clear_ip6_subnet, NULL);
-    g_hash_table_destroy(delegation->subnets);
+    g_hash_table_foreach(delegation->map_ifindex_to_subnet, clear_ip6_subnet_entry, NULL);
+    g_hash_table_destroy(delegation->map_ifindex_to_subnet);
+    g_hash_table_destroy(delegation->map_subnet_id_to_ifindex);
 }
 
 static void
@@ -215,46 +220,112 @@ expire_ip6_delegations(NMPolicy *self)
 static gboolean
 ip6_subnet_from_delegation(IP6PrefixDelegation *delegation, NMDevice *device)
 {
-    NMPlatformIP6Address *subnet;
-    int                   ifindex = nm_device_get_ifindex(device);
-    char                  sbuf[NM_INET_ADDRSTRLEN];
-
-    subnet = g_hash_table_lookup(delegation->subnets, GINT_TO_POINTER(ifindex));
-    if (!subnet) {
-        /* Check for out-of-prefixes condition. */
-        if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) {
-            _LOGD(LOGD_IP6,
-                  "ipv6-pd: no more prefixes in %s/%d",
-                  nm_inet6_ntop(&delegation->prefix.address, sbuf),
-                  delegation->prefix.plen);
-            return FALSE;
+    NMPlatformIP6Address      *subnet;
+    int                        ifindex = nm_device_get_ifindex(device);
+    char                       sbuf[NM_INET_ADDRSTRLEN];
+    NMSettingPrefixDelegation *s_pd;
+    gint64                     wanted_subnet_id = -1;
+    guint64                    num_subnets;
+    guint64                    old_subnet_id;
+
+    nm_assert(delegation->prefix.plen > 0 && delegation->prefix.plen <= 64);
+
+    s_pd = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PREFIX_DELEGATION);
+    if (s_pd) {
+        wanted_subnet_id = nm_setting_prefix_delegation_get_subnet_id(s_pd);
+    }
+
+    /* Try to use the cached subnet assigned to the interface */
+    subnet = g_hash_table_lookup(delegation->map_ifindex_to_subnet, GINT_TO_POINTER(ifindex));
+    if (subnet) {
+        old_subnet_id = nm_ip6_addr_get_subnet_id(&subnet->address, delegation->prefix.plen);
+        if (wanted_subnet_id != -1 && wanted_subnet_id != old_subnet_id) {
+            /* The device had a subnet assigned before, but now wants a
+             * different subnet-id. Release the old subnet and continue below
+             * to get a new one. */
+            clear_ip6_subnet(ifindex, subnet);
+            subnet = NULL;
+            g_hash_table_remove(delegation->map_ifindex_to_subnet, GINT_TO_POINTER(ifindex));
+            g_hash_table_remove(delegation->map_subnet_id_to_ifindex, &old_subnet_id);
+        } else {
+            goto subnet_found;
         }
+    }
 
-        /* Allocate a new subnet. */
-        subnet = g_slice_new0(NMPlatformIP6Address);
-        g_hash_table_insert(delegation->subnets, GINT_TO_POINTER(ifindex), subnet);
+    /* Check for out-of-prefixes condition */
+    num_subnets = 1 << (64 - delegation->prefix.plen);
+    if (nm_g_hash_table_size(delegation->map_subnet_id_to_ifindex) >= num_subnets) {
+        _LOGD(LOGD_IP6,
+              "ipv6-pd: no more prefixes in %s/%u",
+              nm_inet6_ntop(&delegation->prefix.address, sbuf),
+              delegation->prefix.plen);
+        return FALSE;
+    }
+
+    /* Try to honor the "prefix-delegation.subnet-id" property */
+    if (wanted_subnet_id >= 0) {
+        gpointer  value;
+        NMDevice *other_device;
+
+        if (g_hash_table_lookup_extended(delegation->map_subnet_id_to_ifindex,
+                                         &wanted_subnet_id,
+                                         NULL,
+                                         &value)) {
+            other_device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, GPOINTER_TO_INT(value));
+            _LOGW(LOGD_IP6,
+                  "ipv6-pd: subnet-id 0x%" G_GINT64_MODIFIER
+                  "x wanted by device %s is already in use by "
+                  "device %s (ifindex %d)",
+                  (guint64) wanted_subnet_id,
+                  nm_device_get_iface(device),
+                  other_device ? nm_device_get_ip_iface(other_device) : NULL,
+                  GPOINTER_TO_INT(value));
+            wanted_subnet_id = -1;
+        }
+    }
+
+    /* If we don't have a subnet-id yet, find the first one available */
+    if (wanted_subnet_id < 0) {
+        guint64 i;
 
-        subnet->plen = 64;
-        subnet->address.s6_addr32[0] =
-            delegation->prefix.address.s6_addr32[0] | htonl(delegation->next_subnet >> 32);
-        subnet->address.s6_addr32[1] =
-            delegation->prefix.address.s6_addr32[1] | htonl(delegation->next_subnet);
+        for (i = 0; i < num_subnets; i++) {
+            if (!g_hash_table_lookup_extended(delegation->map_subnet_id_to_ifindex,
+                                              &i,
+                                              NULL,
+                                              NULL)) {
+                wanted_subnet_id = (gint64) i;
+                break;
+            }
+        }
 
-        /* Out subnet pool management is pretty unsophisticated. We only add
-         * the subnets and index them by ifindex. That keeps the implementation
-         * simple and the dead entries make it easy to reuse the same subnet on
-         * subsequent activations. On the other hand they may waste the subnet
-         * space. */
-        delegation->next_subnet++;
+        if (wanted_subnet_id < 0) {
+            /* We already verified that there are available subnets, this should not happen */
+            return nm_assert_unreachable_val(FALSE);
+        }
     }
 
+    /* Allocate a new subnet */
+    subnet = g_slice_new0(NMPlatformIP6Address);
+    g_hash_table_insert(delegation->map_ifindex_to_subnet, GINT_TO_POINTER(ifindex), subnet);
+    g_hash_table_insert(delegation->map_subnet_id_to_ifindex,
+                        nm_memdup(&wanted_subnet_id, sizeof(guint64)),
+                        GINT_TO_POINTER(ifindex));
+
+    subnet->plen = 64;
+    subnet->address.s6_addr32[0] =
+        delegation->prefix.address.s6_addr32[0] | htonl(wanted_subnet_id >> 32);
+    subnet->address.s6_addr32[1] =
+        delegation->prefix.address.s6_addr32[1] | htonl(wanted_subnet_id);
+
+subnet_found:
     subnet->timestamp = delegation->prefix.timestamp;
     subnet->lifetime  = delegation->prefix.lifetime;
     subnet->preferred = delegation->prefix.preferred;
 
     _LOGD(LOGD_IP6,
-          "ipv6-pd: %s allocated from a /%d prefix on %s",
+          "ipv6-pd: %s/64 (subnet-id 0x%" G_GINT64_MODIFIER "x) allocated from a /%d prefix on %s",
           nm_inet6_ntop(&subnet->address, sbuf),
+          (guint64) wanted_subnet_id,
           delegation->prefix.plen,
           nm_device_get_iface(device));
 
@@ -345,8 +416,9 @@ device_ip6_prefix_delegated(NMDevice                   *device,
     if (i == priv->ip6_prefix_delegations->len) {
         /* Allocate a delegation for new prefix. */
         delegation = nm_g_array_append_new(priv->ip6_prefix_delegations, IP6PrefixDelegation);
-        delegation->subnets     = g_hash_table_new(nm_direct_hash, NULL);
-        delegation->next_subnet = 0;
+        delegation->map_subnet_id_to_ifindex =
+            g_hash_table_new_full(nm_puint64_hash, nm_puint64_equal, g_free, NULL);
+        delegation->map_ifindex_to_subnet = g_hash_table_new(nm_direct_hash, NULL);
     }
 
     delegation->device = device;
@@ -960,7 +1032,7 @@ update_system_hostname(NMPolicy *self, const char *msg, gboolean reset_retry_int
 
     /* Try a persistent hostname first */
     configured_hostname = nm_hostname_manager_get_static_hostname(priv->hostname_manager);
-    if (configured_hostname && nm_utils_is_specific_hostname(configured_hostname)) {
+    if (configured_hostname && nm_utils_is_not_empty_hostname(configured_hostname)) {
         _set_hostname(self, configured_hostname, "from system configuration", FALSE);
         priv->dhcp_hostname = FALSE;
         return;
@@ -2012,6 +2084,65 @@ unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection
 }
 
 static void
+refresh_forwarding(NMPolicy *self, NMDevice *device, gboolean is_activated_shared_device)
+{
+    NMActiveConnection *ac;
+    NMDevice           *tmp_device;
+    NMPolicyPrivate    *priv = NM_POLICY_GET_PRIVATE(self);
+    const CList        *tmp_lst;
+    gboolean            any_shared_active = false;
+    gint32              default_forwarding_v4;
+    const char         *new_value = NULL;
+
+    /* FIXME: This implementation is still inefficient because refresh_forwarding()
+     * is called every time a device goes up or down, requiring a full scan of all
+     * active connections to determine if any shared connection is active. */
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_lst) {
+        NMSettingIPConfig *s_ip;
+        NMDevice          *to_device = nm_active_connection_get_device(ac);
+
+        if (to_device) {
+            s_ip = nm_device_get_applied_setting(to_device, NM_TYPE_SETTING_IP4_CONFIG);
+            if (s_ip) {
+                if (nm_streq0(nm_device_get_effective_ip_config_method(to_device, AF_INET),
+                              NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+                    any_shared_active = true;
+                    break;
+                }
+            }
+        }
+    }
+
+    default_forwarding_v4 = nm_platform_sysctl_get_int32(
+        NM_PLATFORM_GET,
+        NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/conf/default/forwarding"),
+        0);
+
+    new_value = any_shared_active ? "1" : (default_forwarding_v4 ? "1" : "0");
+
+    nm_manager_for_each_device (priv->manager, tmp_device, tmp_lst) {
+        NMDeviceState               state;
+        NMSettingIPConfigForwarding ipv4_forwarding;
+
+        state = nm_device_get_state(tmp_device);
+        if (state != NM_DEVICE_STATE_ACTIVATED)
+            continue;
+
+        ipv4_forwarding = nm_device_get_ipv4_forwarding(tmp_device);
+
+        if (ipv4_forwarding == NM_SETTING_IP_CONFIG_FORWARDING_AUTO
+            || (device == tmp_device && is_activated_shared_device)) {
+            gs_free char *sysctl_value = NULL;
+
+            sysctl_value = nm_device_sysctl_ip_conf_get(tmp_device, AF_INET, "forwarding");
+
+            if (!nm_streq0(sysctl_value, new_value))
+                nm_device_sysctl_ip_conf_set(tmp_device, AF_INET, "forwarding", new_value);
+        }
+    }
+}
+
+static void
 activate_port_or_children_connections(NMPolicy *self,
                                       NMDevice *device,
                                       gboolean  activate_children_connections_only)
@@ -2155,8 +2286,9 @@ device_state_changed(NMDevice           *device,
     NMPolicyPrivate      *priv = user_data;
     NMPolicy             *self = _PRIV_TO_SELF(priv);
     NMActiveConnection   *ac;
-    NMSettingsConnection *sett_conn = nm_device_get_settings_connection(device);
-    NMSettingConnection  *s_con     = NULL;
+    NMSettingsConnection *sett_conn                  = nm_device_get_settings_connection(device);
+    NMSettingConnection  *s_con                      = NULL;
+    gboolean              is_activated_shared_device = FALSE;
 
     switch (nm_device_state_reason_check(reason)) {
     case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
@@ -2211,8 +2343,10 @@ device_state_changed(NMDevice           *device,
                 con_v = nm_settings_connection_get_last_secret_agent_version_id(sett_conn);
                 if (con_v == 0 || con_v == nm_agent_manager_get_agent_version_id(priv->agent_mgr)) {
                     _LOGD(LOGD_DEVICE,
-                          "block-autoconnect: connection '%s' now blocked from autoconnect due to "
-                          "no secrets",
+                          "block-autoconnect: connection[" NM_HASH_OBFUSCATE_PTR_FMT
+                          "] (%s) now blocked from "
+                          "autoconnect due to no secrets",
+                          NM_HASH_OBFUSCATE_PTR(sett_conn),
                           nm_settings_connection_get_id(sett_conn));
                     nm_settings_connection_autoconnect_blocked_reason_set(
                         sett_conn,
@@ -2230,10 +2364,10 @@ device_state_changed(NMDevice           *device,
                  * be marked as blocked when it fails with dependency-failed.
                  */
                 _LOGD(LOGD_DEVICE,
-                      "block-autoconnect: connection[%p] (%s) now blocked from autoconnect due to "
-                      "failed "
-                      "dependency",
-                      sett_conn,
+                      "block-autoconnect: connection[" NM_HASH_OBFUSCATE_PTR_FMT
+                      "] (%s) now blocked "
+                      "from autoconnect due to failed dependency",
+                      NM_HASH_OBFUSCATE_PTR(sett_conn),
                       nm_settings_connection_get_id(sett_conn));
                 nm_manager_devcon_autoconnect_blocked_reason_set(
                     priv->manager,
@@ -2255,20 +2389,25 @@ device_state_changed(NMDevice           *device,
                     /* blocked */
                 } else if (tries != NM_AUTOCONNECT_RETRIES_FOREVER) {
                     _LOGD(LOGD_DEVICE,
-                          "autoconnect: connection[%p] (%s): failed to autoconnect; %u tries left",
-                          sett_conn,
+                          "autoconnect: connection[" NM_HASH_OBFUSCATE_PTR_FMT "] (%s): failed to "
+                          "autoconnect; %u tries left",
+                          NM_HASH_OBFUSCATE_PTR(sett_conn),
                           nm_settings_connection_get_id(sett_conn),
                           tries - 1u);
                     _connection_autoconnect_retries_set(self, device, sett_conn, tries - 1u);
                 } else {
                     _LOGD(LOGD_DEVICE,
-                          "autoconnect: connection[%p] (%s) failed to autoconnect; infinite tries "
-                          "left",
-                          sett_conn,
+                          "autoconnect: connection[" NM_HASH_OBFUSCATE_PTR_FMT "] (%s) failed to "
+                          "autoconnect; infinite tries left",
+                          NM_HASH_OBFUSCATE_PTR(sett_conn),
                           nm_settings_connection_get_id(sett_conn));
                 }
             }
         }
+        if (!nm_device_get_refresh_forwarding_done(device)) {
+            refresh_forwarding(self, device, FALSE);
+            nm_device_set_refresh_forwarding_done(device, TRUE);
+        }
         break;
     case NM_DEVICE_STATE_ACTIVATED:
         if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE) {
@@ -2301,11 +2440,20 @@ device_state_changed(NMDevice           *device,
         update_system_hostname(self, "routing and dns", TRUE);
         nm_dns_manager_end_updates(priv->dns_manager, __func__);
 
+        is_activated_shared_device =
+            nm_streq0(nm_device_get_effective_ip_config_method(device, AF_INET),
+                      NM_SETTING_IP4_CONFIG_METHOD_SHARED);
+        refresh_forwarding(self, device, is_activated_shared_device);
+        nm_device_set_refresh_forwarding_done(device, FALSE);
         break;
     case NM_DEVICE_STATE_UNMANAGED:
     case NM_DEVICE_STATE_UNAVAILABLE:
         if (old_state > NM_DEVICE_STATE_DISCONNECTED)
             update_routing_and_dns(self, FALSE, device);
+        if (!nm_device_get_refresh_forwarding_done(device)) {
+            refresh_forwarding(self, device, FALSE);
+            nm_device_set_refresh_forwarding_done(device, TRUE);
+        }
         break;
     case NM_DEVICE_STATE_DEACTIVATING:
         if (sett_conn) {
@@ -2341,6 +2489,10 @@ device_state_changed(NMDevice           *device,
             }
         }
         ip6_remove_device_prefix_delegations(self, device);
+        if (!nm_device_get_refresh_forwarding_done(device)) {
+            refresh_forwarding(self, device, FALSE);
+            nm_device_set_refresh_forwarding_done(device, TRUE);
+        }
         break;
     case NM_DEVICE_STATE_DISCONNECTED:
         g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
@@ -2357,6 +2509,10 @@ device_state_changed(NMDevice           *device,
 
         /* Device is now available for auto-activation */
         nm_policy_device_recheck_auto_activate_schedule(self, device);
+        if (!nm_device_get_refresh_forwarding_done(device)) {
+            refresh_forwarding(self, device, FALSE);
+            nm_device_set_refresh_forwarding_done(device, TRUE);
+        }
         break;
 
     case NM_DEVICE_STATE_PREPARE:
@@ -2372,6 +2528,10 @@ device_state_changed(NMDevice           *device,
             g_object_weak_unref(G_OBJECT(ac), pending_ac_gone, self);
             g_object_unref(self);
         }
+        if (!nm_device_get_refresh_forwarding_done(device)) {
+            refresh_forwarding(self, device, FALSE);
+            nm_device_set_refresh_forwarding_done(device, TRUE);
+        }
         break;
     case NM_DEVICE_STATE_IP_CONFIG:
         /* We must have secrets if we got here. */
@@ -2382,6 +2542,10 @@ device_state_changed(NMDevice           *device,
                 sett_conn,
                 NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 FALSE);
+        if (!nm_device_get_refresh_forwarding_done(device)) {
+            refresh_forwarding(self, device, FALSE);
+            nm_device_set_refresh_forwarding_done(device, TRUE);
+        }
         break;
     case NM_DEVICE_STATE_SECONDARIES:
         if (sett_conn)
diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c
index 10c69f63..e0d51b70 100644
--- a/src/core/platform/nm-fake-platform.c
+++ b/src/core/platform/nm-fake-platform.c
@@ -117,7 +117,7 @@ ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guin
         const int         _dirfd  = (dirfd);                                                     \
         const char *const _path   = (path);                                                      \
                                                                                                  \
-        g_assert(_path &&_path[0]);                                                              \
+        g_assert(_path && _path[0]);                                                             \
         g_assert(!strstr(_path, "/../"));                                                        \
         if (_dirfd < 0) {                                                                        \
             g_assert(!_pathid);                                                                  \
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index 26f8b26b..91445b30 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -1356,7 +1356,7 @@ nmtstp_check_platform_full(NMPlatform *platform, guint32 obj_type_flags, gboolea
                     /* For IPv4, it also does not reliably always work. This may
                      * be a bug we want to fix. For now, ignore the check.
                      *
-                     * a) Kernel can wrongly allow to configure the same route twice.
+                     * a) Kernel can wrongly allow one to configure the same route twice.
                      * That means, the same route is visible in `ip route` output,
                      * meaning, it would be added twice to the platform cache.
                      * At least due to that problem, may the weak-id not be properly sorted.
@@ -1364,7 +1364,7 @@ nmtstp_check_platform_full(NMPlatform *platform, guint32 obj_type_flags, gboolea
                      * a bug of kernel allowing to configure the exact same route twice.
                      *
                      * b) See https://bugzilla.redhat.com/show_bug.cgi?id=2162315 which is
-                     * a bug where kernel does allow to configure single-hop routes that differ by
+                     * a bug where kernel does allow one to configure single-hop routes that differ by
                      * their next-hop weight, but on the netlink API those routes look the same.
                      *
                      * Due to a) and b), the platform cache may contain only one instance
diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index 4cadfe4d..fab6bd2e 100644
--- a/src/core/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -15,6 +15,7 @@
 #include "libnm-base/nm-ethtool-base.h"
 #include "libnm-platform/nmp-object.h"
 #include "libnm-platform/nmp-netns.h"
+#include "libnm-platform/nmp-ethtool-ioctl.h"
 #include "libnm-platform/nm-platform-utils.h"
 
 #include "test-common.h"
@@ -2725,6 +2726,66 @@ test_link_set_properties(void)
 /*****************************************************************************/
 
 static void
+test_link_get_bridge_fdb(void)
+{
+    const NMPlatformLink       *link;
+    nm_auto_freev NMEtherAddr **addrs = NULL;
+    int                         ifindex[2];
+    guint8                      expected[][6] = {
+        {0x00, 0x99, 0x00, 0x00, 0x00, 0x01},
+        {0x00, 0x99, 0x00, 0x00, 0x00, 0x02},
+        {0x00, 0x99, 0x00, 0x00, 0x00, 0x03},
+        {0x00, 0x99, 0x00, 0x00, 0x00, 0x05},
+    };
+    guint i;
+    guint j;
+
+    ifindex[0] =
+        nmtstp_link_bridge_add(NULL, -1, "br-test-1", &nm_platform_lnk_bridge_default)->ifindex;
+    ifindex[1] =
+        nmtstp_link_bridge_add(NULL, -1, "br-test-2", &nm_platform_lnk_bridge_default)->ifindex;
+
+    link = nmtstp_link_get(NULL, ifindex[0], "br-test-1");
+    g_assert(link);
+    link = nmtstp_link_get(NULL, ifindex[1], "br-test-2");
+    g_assert(link);
+
+    nmtstp_run_command_check("bridge fdb add dev br-test-1 00:99:00:00:00:01");
+    nmtstp_run_command_check("bridge fdb add dev br-test-1 00:99:00:00:00:02");
+    nmtstp_run_command_check("bridge fdb add dev br-test-1 00:99:00:00:00:03");
+    nmtstp_run_command_check("bridge fdb add dev br-test-2 00:99:00:00:00:01");
+    nmtstp_run_command_check("bridge fdb add dev br-test-2 00:99:00:00:00:05");
+
+    addrs = nm_linux_platform_get_bridge_fdb(NM_PLATFORM_GET, ifindex, 2);
+    g_assert(addrs);
+
+    /* Check for expected entries */
+    for (i = 0; i < G_N_ELEMENTS(expected); i++) {
+        gboolean found = FALSE;
+
+        for (j = 0; addrs[j]; j++) {
+            if (memcmp(addrs[j], expected[i], ETH_ALEN) == 0) {
+                found = TRUE;
+                break;
+            }
+        }
+        g_assert(found);
+    }
+
+    /* No dupes */
+    for (i = 0; addrs[i]; i++) {
+        for (j = i + 1; addrs[j]; j++) {
+            g_assert_cmpint(memcmp(addrs[i], addrs[j], ETH_ALEN), !=, 0);
+        }
+    }
+
+    nmtstp_link_delete(NULL, -1, ifindex[0], "br-test-1", TRUE);
+    nmtstp_link_delete(NULL, -1, ifindex[1], "br-test-2", TRUE);
+}
+
+/*****************************************************************************/
+
+static void
 test_create_many_links_do(guint n_devices)
 {
     gint64                 time, start_time = nm_utils_get_monotonic_timestamp_nsec();
@@ -3183,10 +3244,10 @@ test_netns_general(gpointer fixture, gconstpointer test_data)
      * Work around that and skip asserts that are known to fail. */
     ethtool_support = nmtstp_run_command("ethtool -i dummy1_ > /dev/null") == 0;
     if (ethtool_support) {
-        g_assert(nmp_utils_ethtool_get_driver_info(
+        g_assert(nmp_ethtool_ioctl_get_driver_info(
             nmtstp_link_get_typed(platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex,
             &driver_info));
-        g_assert(nmp_utils_ethtool_get_driver_info(
+        g_assert(nmp_ethtool_ioctl_get_driver_info(
             nmtstp_link_get_typed(platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex,
             &driver_info));
         g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0);
@@ -3197,10 +3258,10 @@ test_netns_general(gpointer fixture, gconstpointer test_data)
     g_assert(nm_platform_netns_push(platform_2, &netns_tmp));
 
     if (ethtool_support) {
-        g_assert(nmp_utils_ethtool_get_driver_info(
+        g_assert(nmp_ethtool_ioctl_get_driver_info(
             nmtstp_link_get_typed(platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex,
             &driver_info));
-        g_assert(nmp_utils_ethtool_get_driver_info(
+        g_assert(nmp_ethtool_ioctl_get_driver_info(
             nmtstp_link_get_typed(platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex,
             &driver_info));
         g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0);
@@ -4031,7 +4092,7 @@ test_ethtool_features_get(void)
 
         _LOGT(">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset");
 
-        features = nmp_utils_ethtool_get_features(IFINDEX);
+        features = nmp_ethtool_ioctl_get_features(IFINDEX);
         g_ptr_array_add(gfree_keeper, features);
 
         ethtool_features_dump(features);
@@ -4044,7 +4105,7 @@ test_ethtool_features_get(void)
             features  = gfree_keeper->pdata[i_run * 2 - 1];
         }
 
-        nmp_utils_ethtool_set_features(IFINDEX, features, requested, do_set);
+        nmp_ethtool_ioctl_set_features(IFINDEX, features, requested, do_set);
     }
 }
 
@@ -4105,6 +4166,8 @@ _nmtstp_setup_tests(void)
         test_software_detect_add("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1);
         test_software_detect_add("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2);
 
+        g_test_add_func("/link/get-bridge-fdb", test_link_get_bridge_fdb);
+
         g_test_add_func("/link/software/vlan/set-xgress", test_vlan_set_xgress);
 
         g_test_add_func("/link/set-properties", test_link_set_properties);
diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index a05362f2..f8f7070b 100644
--- a/src/core/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -640,22 +640,23 @@ test_ip4_route_options(gconstpointer test_data)
     switch (TEST_IDX) {
     case 1:
         rts_add[rts_n++] = ((NMPlatformIP4Route) {
-            .ifindex    = IFINDEX,
-            .rt_source  = NM_IP_CONFIG_SOURCE_USER,
-            .network    = nmtst_inet4_from_string("172.16.1.0"),
-            .plen       = 24,
-            .metric     = 20,
-            .tos        = 0x28,
-            .window     = 10000,
-            .cwnd       = 16,
-            .initcwnd   = 30,
-            .initrwnd   = 50,
-            .mtu        = 1350,
-            .lock_cwnd  = TRUE,
-            .mss        = 1300,
-            .quickack   = TRUE,
-            .rto_min    = 1000,
-            .n_nexthops = 1,
+            .ifindex     = IFINDEX,
+            .rt_source   = NM_IP_CONFIG_SOURCE_USER,
+            .network     = nmtst_inet4_from_string("172.16.1.0"),
+            .plen        = 24,
+            .metric      = 20,
+            .tos         = 0x28,
+            .window      = 10000,
+            .cwnd        = 16,
+            .initcwnd    = 30,
+            .initrwnd    = 50,
+            .mtu         = 1350,
+            .lock_cwnd   = TRUE,
+            .mss         = 1300,
+            .quickack    = TRUE,
+            .rto_min     = 1000,
+            .rto_min_set = TRUE,
+            .n_nexthops  = 1,
         });
         break;
     case 2:
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index d64052cb..1e2e2d92 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -1439,7 +1439,7 @@ next:;
                         : ""));
             break;
         case PARSE_LINE_TYPE_FLAG:
-            /* NOTE: the flag (for "onlink") only allows to explicitly set "TRUE".
+            /* NOTE: the flag (for "onlink") only allows setting "TRUE" explicitly.
              * There is no way to express an explicit "FALSE" setting
              * of this attribute, hence, the file format cannot encode
              * that configuration. */
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 6e0411c6..39cbddc5 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -3769,7 +3769,7 @@ nms_ifcfg_rh_writer_write_connection(NMConnection                   *connection,
      *
      * FIXME: a much better solution might be, to re-read the connection only based
      * on the in-memory representation of what we collected above. But the reader
-     * does not yet allow to inject the configuration. */
+     * does not yet allow one to inject the configuration. */
     if (out_reread || out_reread_same) {
         gs_unref_object NMConnection *reread      = NULL;
         gboolean                      reread_same = FALSE;
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index ea978a98..2f3035ef 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -255,8 +255,8 @@ _assert_expected_content(NMConnection *connection, const char *filename, const c
         GError        *_error           = NULL;                           \
         gboolean       _success;                                          \
                                                                           \
-        g_assert(_ifcfg_dir &&_ifcfg_dir[0]);                             \
-        g_assert(_filename &&_filename[0]);                               \
+        g_assert(_ifcfg_dir && _ifcfg_dir[0]);                            \
+        g_assert(_filename && _filename[0]);                              \
                                                                           \
         _success = nms_ifcfg_rh_writer_write_connection(_connection,      \
                                                         _ifcfg_dir,       \
@@ -9533,12 +9533,10 @@ test_svUnescape(void)
         .exp        = v_expected,    \
         .can_concat = !!v_expected,  \
     }
-#define V2(v_value, v_expected)                 \
-    {                                           \
-        .val                   = "" v_value "", \
-        .exp                   = v_expected,    \
-        .can_concat            = TRUE,          \
-        .needs_ascii_separator = TRUE,          \
+#define V2(v_value, v_expected)                                      \
+    {                                                                \
+        .val = "" v_value "", .exp = v_expected, .can_concat = TRUE, \
+        .needs_ascii_separator = TRUE,                               \
     }
     const UnescapeTestData data_full[] = {
         V1("", ""),
diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
index 6b6913c5..df5dfd40 100644
--- a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -61,7 +61,7 @@ check_ip_route(NMSettingIPConfig *config,
         gs_free_error GError *_error = NULL;                                              \
         NMConnection         *_connection;                                                \
                                                                                           \
-        g_assert(full_filename &&full_filename[0] == '/');                                \
+        g_assert(full_filename && full_filename[0] == '/');                               \
                                                                                           \
         _connection =                                                                     \
             nms_keyfile_reader_from_file(full_filename,                                   \
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index d5f56dcd..514b7c0d 100644
--- a/src/core/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -3082,7 +3082,7 @@ _signal_handle(NMSupplicantInterface *self,
             const char           *status;
             const char           *parameter;
 
-            if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(ss)")))
+            if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(ss)")))
                 return;
 
             g_variant_get(parameters, "(&s&s)", &status, &parameter);