summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2025-07-15 08:31:19 +0200
committerMichael Biebl <biebl@debian.org>2025-07-15 08:31:19 +0200
commit582eb4472a0f3375042afb9026cbeb50e058a27d (patch)
tree65f78da0c024c43e669c6a9c23402e59b28a01ef /src/core
parent625e8ce60e826bde943487b8238884a9232e4562 (diff)
New upstream version 1.52.1 upstream/1.52.1
Diffstat (limited to 'src/core')
-rw-r--r--src/core/devices/nm-device-6lowpan.c9
-rw-r--r--src/core/devices/nm-device-ipvlan.c11
-rw-r--r--src/core/devices/nm-device-macsec.c9
-rw-r--r--src/core/devices/nm-device-macvlan.c11
-rw-r--r--src/core/devices/nm-device-vlan.c11
-rw-r--r--src/core/devices/nm-device-wireguard.c56
-rw-r--r--src/core/devices/nm-device.c43
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c26
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c100
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c27
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c300
-rw-r--r--src/core/devices/ovs/nm-ovsdb.h2
-rw-r--r--src/core/dns/nm-dns-dnsconfd.c50
-rw-r--r--src/core/nm-bond-manager.c3
-rw-r--r--src/core/nm-connectivity.c6
-rw-r--r--src/core/nm-firewall-utils.c424
-rw-r--r--src/core/nm-firewall-utils.h5
-rw-r--r--src/core/nm-ip-config.c2
-rw-r--r--src/core/tests/test-core.c1
-rw-r--r--src/core/vpn/nm-vpn-connection.c2
20 files changed, 702 insertions, 396 deletions
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index 3dabcb9b..61b1e4ee 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -138,14 +138,6 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 static gboolean
-is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-    if (!nm_device_parent_get_device(device))
-        return FALSE;
-    return NM_DEVICE_CLASS(nm_device_6lowpan_parent_class)->is_available(device, flags);
-}
-
-static gboolean
 complete_connection(NMDevice            *device,
                     NMConnection        *connection,
                     const char          *specific_object,
@@ -237,7 +229,6 @@ nm_device_6lowpan_class_init(NMDevice6LowpanClass *klass)
     device_class->get_generic_capabilities               = get_generic_capabilities;
     device_class->get_configured_mtu                     = nm_device_get_configured_mtu_for_wired;
     device_class->link_changed                           = link_changed;
-    device_class->is_available                           = is_available;
     device_class->parent_changed_notify                  = parent_changed_notify;
     device_class->update_connection                      = update_connection;
 }
diff --git a/src/core/devices/nm-device-ipvlan.c b/src/core/devices/nm-device-ipvlan.c
index 00a1b579..abeb82f7 100644
--- a/src/core/devices/nm-device-ipvlan.c
+++ b/src/core/devices/nm-device-ipvlan.c
@@ -222,16 +222,6 @@ get_generic_capabilities(NMDevice *device)
 /*****************************************************************************/
 
 static gboolean
-is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-    if (!nm_device_parent_get_device(device))
-        return FALSE;
-    return NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)->is_available(device, flags);
-}
-
-/*****************************************************************************/
-
-static gboolean
 check_connection_compatible(NMDevice     *device,
                             NMConnection *connection,
                             gboolean      check_properties,
@@ -376,7 +366,6 @@ nm_device_ipvlan_class_init(NMDeviceIpvlanClass *klass)
     device_class->check_connection_compatible = check_connection_compatible;
     device_class->create_and_realize          = create_and_realize;
     device_class->get_generic_capabilities    = get_generic_capabilities;
-    device_class->is_available                = is_available;
     device_class->link_changed                = link_changed;
     device_class->update_connection           = update_connection;
 
diff --git a/src/core/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c
index 89a06720..2ff1eeb3 100644
--- a/src/core/devices/nm-device-macsec.c
+++ b/src/core/devices/nm-device-macsec.c
@@ -684,14 +684,6 @@ get_generic_capabilities(NMDevice *dev)
 /******************************************************************/
 
 static gboolean
-is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-    if (!nm_device_parent_get_device(device))
-        return FALSE;
-    return NM_DEVICE_CLASS(nm_device_macsec_parent_class)->is_available(device, flags);
-}
-
-static gboolean
 create_and_realize(NMDevice              *device,
                    NMConnection          *connection,
                    NMDevice              *parent,
@@ -903,7 +895,6 @@ nm_device_macsec_class_init(NMDeviceMacsecClass *klass)
     device_class->deactivate               = deactivate;
     device_class->get_generic_capabilities = get_generic_capabilities;
     device_class->link_changed             = link_changed;
-    device_class->is_available             = is_available;
     device_class->parent_changed_notify    = parent_changed_notify;
     device_class->state_changed            = device_state_changed;
     device_class->get_configured_mtu       = nm_device_get_configured_mtu_wired_parent;
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index 9501e8f1..c5bcc91a 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -271,16 +271,6 @@ get_generic_capabilities(NMDevice *device)
 /*****************************************************************************/
 
 static gboolean
-is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-    if (!nm_device_parent_get_device(device))
-        return FALSE;
-    return NM_DEVICE_CLASS(nm_device_macvlan_parent_class)->is_available(device, flags);
-}
-
-/*****************************************************************************/
-
-static gboolean
 check_connection_compatible(NMDevice     *device,
                             NMConnection *connection,
                             gboolean      check_properties,
@@ -508,7 +498,6 @@ nm_device_macvlan_class_init(NMDeviceMacvlanClass *klass)
     device_class->create_and_realize                     = create_and_realize;
     device_class->get_generic_capabilities               = get_generic_capabilities;
     device_class->get_configured_mtu    = nm_device_get_configured_mtu_wired_parent;
-    device_class->is_available          = is_available;
     device_class->link_changed          = link_changed;
     device_class->parent_changed_notify = parent_changed_notify;
     device_class->update_connection     = update_connection;
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index 59a429ca..9d03e337 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -293,16 +293,6 @@ get_generic_capabilities(NMDevice *device)
 /*****************************************************************************/
 
 static gboolean
-is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-    if (!nm_device_parent_get_device(device))
-        return FALSE;
-    return NM_DEVICE_CLASS(nm_device_vlan_parent_class)->is_available(device, flags);
-}
-
-/*****************************************************************************/
-
-static gboolean
 check_connection_compatible(NMDevice     *device,
                             NMConnection *connection,
                             gboolean      check_properties,
@@ -561,7 +551,6 @@ nm_device_vlan_class_init(NMDeviceVlanClass *klass)
     device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
     device_class->act_stage1_prepare                     = act_stage1_prepare;
     device_class->get_configured_mtu    = nm_device_get_configured_mtu_wired_parent;
-    device_class->is_available          = is_available;
     device_class->parent_changed_notify = parent_changed_notify;
 
     device_class->check_connection_compatible = check_connection_compatible;
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index 4a08192e..299e3b30 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -23,6 +23,7 @@
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
 #include "dns/nm-dns-manager.h"
+#include "nm-firewall-utils.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceWireGuard
 #include "nm-device-logging.h"
@@ -1207,6 +1208,40 @@ skip:
     *out_allowed_ips_data = g_steal_pointer(&allowed_ips);
 }
 
+static void
+_configure_firewall(NMDeviceWireGuard *self, NMConnection *connection, int addr_family, gboolean up)
+{
+    NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    const char               *ip_iface;
+    NMSettingIPConfig        *ip_config;
+
+    ip_iface = nm_device_get_ip_iface(NM_DEVICE(self));
+
+    nm_assert(ip_iface);
+
+    switch (addr_family) {
+    case AF_INET:
+        if (!priv->auto_default_route_enabled_4)
+            return;
+
+        ip_config = nm_connection_get_setting_ip4_config(connection);
+        break;
+    case AF_INET6:
+        if (!priv->auto_default_route_enabled_6)
+            return;
+
+        ip_config = nm_connection_get_setting_ip6_config(connection);
+        break;
+    default:
+        nm_assert_not_reached();
+    }
+
+    nm_assert(ip_config);
+    nm_assert(priv->auto_default_route_fwmark);
+
+    nm_firewall_config_set_wg_rule(ip_iface, ip_config, priv->auto_default_route_fwmark, up);
+}
+
 /*****************************************************************************/
 
 static void
@@ -1300,6 +1335,18 @@ create_and_realize(NMDevice              *device,
     return TRUE;
 }
 
+static void
+deactivate(NMDevice *device)
+{
+    NMDeviceWireGuard *self       = NM_DEVICE_WIREGUARD(device);
+    NMConnection      *connection = nm_device_get_applied_connection(NM_DEVICE(self));
+
+    if (connection) {
+        _configure_firewall(self, connection, AF_INET, FALSE);
+        _configure_firewall(self, connection, AF_INET6, FALSE);
+    }
+}
+
 /*****************************************************************************/
 
 static void
@@ -1768,6 +1815,10 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
     nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
 
     l3cd = _get_dev2_ip_config(NM_DEVICE_WIREGUARD(device), addr_family);
+    _configure_firewall(NM_DEVICE_WIREGUARD(device),
+                        nm_device_get_applied_connection(device),
+                        addr_family,
+                        TRUE);
     nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd);
 }
 
@@ -1866,6 +1917,10 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
 
     if (state >= NM_DEVICE_STATE_CONFIG) {
         priv->auto_default_route_refresh = TRUE;
+
+        _configure_firewall(self, con_old, AF_INET, FALSE);
+        _configure_firewall(self, con_old, AF_INET6, FALSE);
+
         link_config(NM_DEVICE_WIREGUARD(device), "reapply", LINK_CONFIG_MODE_REAPPLY, NULL);
     }
 
@@ -2018,6 +2073,7 @@ nm_device_wireguard_class_init(NMDeviceWireGuardClass *klass)
 
     device_class->state_changed                                 = device_state_changed;
     device_class->create_and_realize                            = create_and_realize;
+    device_class->deactivate                                    = deactivate;
     device_class->act_stage2_config                             = act_stage2_config;
     device_class->act_stage2_config_also_for_external_or_assume = TRUE;
     device_class->act_stage3_ip_config                          = act_stage3_ip_config;
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index e310a9c6..2f2f25a5 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -603,6 +603,7 @@ typedef struct _NMDevicePrivate {
 
     bool is_attached : 1;
 
+    bool device_link_carrier_changed_down : 1;
     bool device_link_changed_down : 1;
 
     bool concheck_rp_filter_checked : 1;
@@ -2759,22 +2760,23 @@ _ethtool_fec_set(NMDevice         *self,
 
     g_hash_table_iter_init(&iter, hash);
     while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
-        NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
-
-        if (!nm_ethtool_id_is_fec(ethtool_id))
-            continue;
-
-        nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
-        fec_mode = g_variant_get_uint32(variant);
+        if (nm_ethtool_id_is_fec(nm_ethtool_id_get_by_name(name))) {
+            nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
+            fec_mode = g_variant_get_uint32(variant);
+            break;
+        }
     }
 
-    nm_platform_ethtool_get_fec_mode(platform, ethtool_state->ifindex, &old_fec_mode);
-
     /* The NM_SETTING_ETHTOOL_FEC_MODE_NONE is query only value, hence do nothing. */
     if (!fec_mode || fec_mode == NM_SETTING_ETHTOOL_FEC_MODE_NONE) {
         return;
     }
 
+    if (!nm_platform_ethtool_get_fec_mode(platform, ethtool_state->ifindex, &old_fec_mode)) {
+        _LOGW(LOGD_DEVICE, "ethtool: failure setting FEC %d: cannot get current value", fec_mode);
+        return;
+    }
+
     if (!nm_platform_ethtool_set_fec_mode(platform, ethtool_state->ifindex, fec_mode))
         _LOGW(LOGD_DEVICE, "ethtool: failure setting FEC %d", fec_mode);
     else {
@@ -7123,6 +7125,9 @@ nm_device_controller_release_port(NMDevice           *self,
                                      NM_UNMANAGED_IS_PORT,
                                      NM_UNMAN_FLAG_OP_FORGET,
                                      NM_DEVICE_STATE_REASON_REMOVED);
+
+    /* Once the port is detached, unmanaged-external-down might change */
+    _dev_unmanaged_check_external_down(self, FALSE, FALSE);
 }
 
 /*****************************************************************************/
@@ -7558,10 +7563,12 @@ device_link_changed(gpointer user_data)
     gboolean                        carrier_was_up;
     gboolean                        update_unmanaged_specs = FALSE;
     gboolean                        got_hw_addr            = FALSE, had_hw_addr;
+    gboolean                        carrier_seen_down      = priv->device_link_carrier_changed_down;
     gboolean                        seen_down              = priv->device_link_changed_down;
 
-    priv->device_link_changed_id   = 0;
-    priv->device_link_changed_down = FALSE;
+    priv->device_link_changed_id           = 0;
+    priv->device_link_changed_down         = FALSE;
+    priv->device_link_carrier_changed_down = FALSE;
 
     ifindex = nm_device_get_ifindex(self);
     if (ifindex <= 0)
@@ -7712,7 +7719,8 @@ device_link_changed(gpointer user_data)
         if (priv->state >= NM_DEVICE_STATE_IP_CONFIG && priv->state <= NM_DEVICE_STATE_ACTIVATED
             && !nm_device_managed_type_is_external(self))
             nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
-
+    }
+    if (priv->carrier && (!carrier_was_up || carrier_seen_down)) {
         /* If the device is active without a carrier (probably because it is
          * tagged for carrier ignore) ensure that when the carrier appears we
          * renew DHCP leases and such.
@@ -7803,6 +7811,8 @@ link_changed_cb(NMPlatform     *platform,
     priv = NM_DEVICE_GET_PRIVATE(self);
 
     if (ifindex == nm_device_get_ifindex(self)) {
+        if (!(pllink->n_ifi_flags & IFF_LOWER_UP))
+            priv->device_link_carrier_changed_down = TRUE;
         if (!(pllink->n_ifi_flags & IFF_UP))
             priv->device_link_changed_down = TRUE;
         if (!priv->device_link_changed_id) {
@@ -8814,6 +8824,9 @@ nm_device_controller_add_port(NMDevice *self, NMDevice *port, gboolean configure
     } else
         g_return_val_if_fail(port_priv->controller == self, FALSE);
 
+    /* Once the port is attached, unmanaged-external-down might change */
+    _dev_unmanaged_check_external_down(self, TRUE, FALSE);
+
     nm_device_queue_recheck_assume(self);
     nm_device_queue_recheck_assume(port);
 
@@ -13417,6 +13430,8 @@ _dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
         nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
 
         _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE);
+    } else {
+        _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_PD_6, NULL, FALSE);
     }
 
     _dev_ipsharedx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
@@ -15085,8 +15100,8 @@ respawn_ping_cb(gpointer user_data)
     nm_clear_g_source_inst(&ping_op->watch);
 
     if (!spawn_ping_for_operation(self, ping_op)) {
-        cleanup_ping_operation(ping_op);
         priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
+        cleanup_ping_operation(ping_op);
 
         if (g_list_length(priv->ping_operations) == 0) {
             ip_check_pre_up(self);
@@ -15129,7 +15144,6 @@ ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
 
     if (success) {
         if (ping_op->ping_addresses_require_all) {
-            cleanup_ping_operation(ping_op);
             priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
             if (g_list_length(priv->ping_operations) == 0) {
                 _LOGD(ping_op->log_domain,
@@ -15139,6 +15153,7 @@ ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
                     nm_clear_g_source_inst(&priv->ping_timeout);
                 ip_check_pre_up(self);
             }
+            cleanup_ping_operation(ping_op);
         } else {
             nm_assert(priv->ping_operations);
 
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index 09777669..c9b1f4c6 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -135,13 +135,36 @@ nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnec
     nm_ovsdb_set_reapply(nm_ovsdb_get(),
                          device_type,
                          nm_device_get_ip_iface(self),
-                         nm_connection_get_uuid(con_new),
+                         nm_simple_connection_new_clone(con_new),
                          _nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_EXTERNAL_IDS),
                          _nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_EXTERNAL_IDS),
                          _nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_OTHER_CONFIG),
                          _nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_OTHER_CONFIG));
 }
 
+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_bridge_parent_class);
+
+    if (nm_streq(setting_name, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) {
+        return nm_device_hash_check_invalid_keys(diffs,
+                                                 NM_SETTING_OVS_BRIDGE_SETTING_NAME,
+                                                 error,
+                                                 NM_SETTING_OVS_BRIDGE_FAIL_MODE,
+                                                 NM_SETTING_OVS_BRIDGE_MCAST_SNOOPING_ENABLE,
+                                                 NM_SETTING_OVS_BRIDGE_RSTP_ENABLE,
+                                                 NM_SETTING_OVS_BRIDGE_STP_ENABLE);
+    }
+
+    return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
+}
+
 /*****************************************************************************/
 
 static void
@@ -180,6 +203,7 @@ nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
     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;
 }
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 06a1da15..512aa5c3 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -26,16 +26,18 @@ typedef struct {
     NMOvsdb *ovsdb;
 
     struct {
-        /* The source for the idle handler to set the TUN ifindex */
-        GSource *tun_set_ifindex_idle_source;
-        /* The cloned MAC to set */
-        char *cloned_mac;
-        /* The id for the signal watching the TUN link to appear/change */
+        /* The signal id for the TUN link-changed event */
         gulong tun_link_signal_id;
-        /* The TUN ifindex to set in the idle handler */
+        /* The idle handler source for the TUN link-changed event */
+        GSource *tun_link_idle_source;
+        /* The ifindex for the TUN link-changed event */
         int tun_ifindex;
+
+        /* The cloned MAC to set */
+        char *cloned_mac;
         /* Whether we have determined the cloned MAC */
         bool cloned_mac_evaluated : 1;
+
         /* Whether we are waiting for the kernel link */
         bool waiting : 1;
     } wait_link;
@@ -261,39 +263,33 @@ ready_for_ip_config(NMDevice *device, gboolean is_manual)
 }
 
 static gboolean
-_set_ip_ifindex_tun(gpointer user_data)
+_netdev_tun_link_cb_in_idle(gpointer user_data)
 {
     NMDevice                    *device = user_data;
     NMDeviceOvsInterface        *self   = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv   = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
-    _LOGT(LOGD_CORE,
-          "ovs-wait-link: setting ip-ifindex %d from tun interface",
-          priv->wait_link.tun_ifindex);
-
-    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
-
-    nm_device_set_ip_ifindex(device, priv->wait_link.tun_ifindex);
-
-    if (check_waiting_for_link(device, "set-ip-ifindex-tun")) {
-        /* If the link is not ready, it means the MAC is not set yet. We don't have
-         * a convenient way to monitor for ip-ifindex changes other than listening
-         * for platform events again.*/
-        nm_assert(!priv->wait_link.tun_link_signal_id);
-        priv->wait_link.tun_link_signal_id = g_signal_connect(nm_device_get_platform(device),
-                                                              NM_PLATFORM_SIGNAL_LINK_CHANGED,
-                                                              G_CALLBACK(_netdev_tun_link_cb),
-                                                              self);
-        return G_SOURCE_CONTINUE;
+    if (nm_device_get_ip_ifindex(device) <= 0) {
+        _LOGT(LOGD_CORE,
+              "ovs-wait-link: setting ip-ifindex %d from tun link",
+              priv->wait_link.tun_ifindex);
+        nm_device_set_ip_ifindex(device, priv->wait_link.tun_ifindex);
     }
 
-    _LOGT(LOGD_CORE, "tun link is ready");
+    if (check_waiting_for_link(device, "tun-link-changed")) {
+        nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
+        return G_SOURCE_CONTINUE;
+    }
 
+    _LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready");
     nm_device_link_properties_set(device, FALSE);
+    nm_device_bring_up(device);
 
     nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
     nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
     nm_device_activate_schedule_stage3_ip_config(device, FALSE);
+    nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
+    nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
 
     return G_SOURCE_CONTINUE;
 }
@@ -309,40 +305,28 @@ _netdev_tun_link_cb(NMPlatform     *platform,
     const NMPlatformSignalChangeType change_type = change_type_i;
     NMDeviceOvsInterface            *self        = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate     *priv        = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
-    int                              ip_ifindex;
 
-    if (pllink->type != NM_LINK_TYPE_TUN || !nm_streq0(pllink->name, nm_device_get_iface(device)))
+    /* This is the handler for the link-changed platform events. It is triggered for all
+     * link changes. Keep only the ones matching our device. */
+    if (!NM_IN_SET(change_type, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED))
         return;
-
-    ip_ifindex = nm_device_get_ip_ifindex(device);
-    if (ip_ifindex > 0) {
-        /* When we have an ifindex, we are only waiting for the MAC to settle */
-        if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
-            return;
-
-        if (!check_waiting_for_link(device, "tun-link-changed")) {
-            _LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready, cloned MAC is set");
-
-            nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id);
-            nm_device_link_properties_set(device, FALSE);
-
-            nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
-            nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
-            nm_device_activate_schedule_stage3_ip_config(device, FALSE);
-        }
-        return;
-    }
-
-    /* No ip-ifindex on the device, set it when the link appears */
-    if (change_type != NM_PLATFORM_SIGNAL_ADDED)
+    if (pllink->type != NM_LINK_TYPE_TUN || !nm_streq0(pllink->name, nm_device_get_iface(device)))
         return;
 
     _LOGT(LOGD_CORE,
-          "ovs-wait-link: found matching tun interface, schedule set-ip-ifindex(%d)",
+          "ovs-wait-link: got platform event \'%s\' for ifindex %d, scheduling idle handler",
+          change_type == NM_PLATFORM_SIGNAL_ADDED ? "added" : "changed",
           ifindex);
-    nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id);
-    priv->wait_link.tun_ifindex                 = ifindex;
-    priv->wait_link.tun_set_ifindex_idle_source = nm_g_idle_add_source(_set_ip_ifindex_tun, device);
+
+    /* The handler is invoked by the platform synchronously in the netlink receive loop.
+     * We can't perform other platform operations (like bringing the interface up) since
+     * the code there is not re-entrant. Schedule an idle handler. */
+    nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
+    priv->wait_link.tun_link_idle_source =
+        nm_g_idle_add_source(_netdev_tun_link_cb_in_idle, device);
+    priv->wait_link.tun_ifindex = ifindex;
+
+    return;
 }
 
 static gboolean
@@ -464,7 +448,7 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
         nm_device_activate_schedule_stage3_ip_config(device, TRUE);
         return;
     }
-    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
+    nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
     nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
 
     nm_device_link_properties_set(device, FALSE);
@@ -488,7 +472,7 @@ deactivate(NMDevice *device)
     priv->wait_link.cloned_mac_evaluated = FALSE;
     nm_clear_g_free(&priv->wait_link.cloned_mac);
     nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
-    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
+    nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
 }
 
 typedef struct {
@@ -581,7 +565,7 @@ deactivate_async(NMDevice                  *device,
     _LOGT(LOGD_CORE, "deactivate: start async");
 
     nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
-    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
+    nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
     priv->wait_link.tun_ifindex          = -1;
     priv->wait_link.cloned_mac_evaluated = FALSE;
     nm_clear_g_free(&priv->wait_link.cloned_mac);
@@ -682,7 +666,7 @@ dispose(GObject *object)
 
     nm_assert(!priv->wait_link.waiting);
     nm_assert(priv->wait_link.tun_link_signal_id == 0);
-    nm_assert(!priv->wait_link.tun_set_ifindex_idle_source);
+    nm_assert(!priv->wait_link.tun_link_idle_source);
 
     if (priv->ovsdb) {
         g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self);
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index 7eacedb8..e9928548 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -256,6 +256,32 @@ detach_port(NMDevice                  *device,
     return ret;
 }
 
+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_port_parent_class);
+
+    if (nm_streq(setting_name, NM_SETTING_OVS_PORT_SETTING_NAME)) {
+        return nm_device_hash_check_invalid_keys(diffs,
+                                                 NM_SETTING_OVS_PORT_SETTING_NAME,
+                                                 error,
+                                                 NM_SETTING_OVS_PORT_TAG,
+                                                 NM_SETTING_OVS_PORT_VLAN_MODE,
+                                                 NM_SETTING_OVS_PORT_BOND_UPDELAY,
+                                                 NM_SETTING_OVS_PORT_BOND_DOWNDELAY,
+                                                 NM_SETTING_OVS_PORT_LACP,
+                                                 NM_SETTING_OVS_PORT_BOND_MODE,
+                                                 NM_SETTING_OVS_PORT_TRUNKS);
+    }
+
+    return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
+}
+
 /*****************************************************************************/
 
 static void
@@ -293,6 +319,7 @@ nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
     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;
 }
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 528d44d8..2dbc842a 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -101,13 +101,13 @@ typedef union {
         guint32 mtu;
     } set_interface_mtu;
     struct {
-        NMDeviceType device_type;
-        char        *ifname;
-        char        *connection_uuid;
-        GHashTable  *external_ids_old;
-        GHashTable  *external_ids_new;
-        GHashTable  *other_config_old;
-        GHashTable  *other_config_new;
+        NMDeviceType  device_type;
+        char         *ifname;
+        NMConnection *connection;
+        GHashTable   *external_ids_old;
+        GHashTable   *external_ids_new;
+        GHashTable   *other_config_old;
+        GHashTable   *other_config_new;
     } set_reapply;
 } OvsdbMethodPayload;
 
@@ -242,24 +242,21 @@ static void     cleanup_check_ready(NMOvsdb *self);
             },                                                    \
     }))
 
-#define OVSDB_METHOD_PAYLOAD_SET_REAPPLY(xdevice_type,                               \
-                                         xifname,                                    \
-                                         xconnection_uuid,                           \
-                                         xexternal_ids_old,                          \
-                                         xexternal_ids_new,                          \
-                                         xother_config_old,                          \
-                                         xother_config_new)                          \
-    (&((const OvsdbMethodPayload) {                                                  \
-        .set_reapply =                                                               \
-            {                                                                        \
-                .device_type      = xdevice_type,                                    \
-                .ifname           = (char *) NM_CONSTCAST(char, (xifname)),          \
-                .connection_uuid  = (char *) NM_CONSTCAST(char, (xconnection_uuid)), \
-                .external_ids_old = (xexternal_ids_old),                             \
-                .external_ids_new = (xexternal_ids_new),                             \
-                .other_config_old = (xother_config_old),                             \
-                .other_config_new = (xother_config_new),                             \
-            },                                                                       \
+#define OVSDB_METHOD_PAYLOAD_SET_REAPPLY(xdevice_type,                              \
+                                         xifname,                                   \
+                                         xconnection,                               \
+                                         xexternal_ids_old,                         \
+                                         xexternal_ids_new,                         \
+                                         xother_config_old,                         \
+                                         xother_config_new)                         \
+    (&((const OvsdbMethodPayload) {                                                 \
+        .set_reapply = {.device_type      = xdevice_type,                           \
+                        .ifname           = (char *) NM_CONSTCAST(char, (xifname)), \
+                        .connection       = (xconnection),                          \
+                        .external_ids_old = (xexternal_ids_old),                    \
+                        .external_ids_new = (xexternal_ids_new),                    \
+                        .other_config_old = (xother_config_old),                    \
+                        .other_config_new = (xother_config_new)},                   \
     }))
 
 /*****************************************************************************/
@@ -316,7 +313,7 @@ _call_complete(OvsdbMethodCall *call, json_t *response, GError *error)
         break;
     case OVSDB_SET_REAPPLY:
         nm_clear_g_free(&call->payload.set_reapply.ifname);
-        nm_clear_g_free(&call->payload.set_reapply.connection_uuid);
+        nm_clear_g_object(&call->payload.set_reapply.connection);
         nm_clear_pointer(&call->payload.set_reapply.external_ids_old, g_hash_table_destroy);
         nm_clear_pointer(&call->payload.set_reapply.external_ids_new, g_hash_table_destroy);
         nm_clear_pointer(&call->payload.set_reapply.other_config_old, g_hash_table_destroy);
@@ -476,9 +473,9 @@ ovsdb_call_method(NMOvsdb                  *self,
                    call->payload.set_interface_mtu.mtu);
         break;
     case OVSDB_SET_REAPPLY:
-        call->payload.set_reapply.device_type     = payload->set_reapply.device_type;
-        call->payload.set_reapply.ifname          = g_strdup(payload->set_reapply.ifname);
-        call->payload.set_reapply.connection_uuid = g_strdup(payload->set_reapply.connection_uuid);
+        call->payload.set_reapply.device_type = payload->set_reapply.device_type;
+        call->payload.set_reapply.ifname      = g_strdup(payload->set_reapply.ifname);
+        call->payload.set_reapply.connection  = payload->set_reapply.connection;
         call->payload.set_reapply.external_ids_old =
             nm_g_hash_table_ref(payload->set_reapply.external_ids_old);
         call->payload.set_reapply.external_ids_new =
@@ -488,8 +485,8 @@ ovsdb_call_method(NMOvsdb                  *self,
         call->payload.set_reapply.other_config_new =
             nm_g_hash_table_ref(payload->set_reapply.other_config_new);
         _LOGT_call(call,
-                   "new: set external-ids/other-config con-uuid=%s, interface=%s",
-                   call->payload.set_reapply.connection_uuid,
+                   "new: reapply con-uuid=%s, interface=%s",
+                   nm_connection_get_uuid(payload->set_reapply.connection),
                    call->payload.set_reapply.ifname);
         break;
     }
@@ -976,65 +973,79 @@ _insert_interface(json_t       *params,
                                     "rowInterface"));
 }
 
-/**
- * _insert_port:
- *
- * Returns an commands that adds new port from a given connection.
- */
 static void
-_insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
+ovsdb_row_set_string_or_null(json_t *row, const char *key, const char *str)
+{
+    /* ovsdb represents a NULL string (no value) as an empty set */
+    if (str) {
+        json_object_set_new(row, key, json_string(str));
+    } else {
+        json_object_set_new(row, key, json_pack("[s, []]", "set"));
+    }
+}
+
+static json_t *
+create_port_row_object(NMConnection *connection)
 {
     NMSettingOvsPort *s_ovs_port;
-    const char       *vlan_mode      = NULL;
-    json_t           *trunks         = NULL;
-    guint             tag            = 0;
-    const char       *lacp           = NULL;
-    const char       *bond_mode      = NULL;
-    guint             bond_updelay   = 0;
-    guint             bond_downdelay = 0;
     json_t           *row;
+    const char       *s;
+    guint             u;
 
-    s_ovs_port = nm_connection_get_setting_ovs_port(port);
+    s_ovs_port = nm_connection_get_setting_ovs_port(connection);
+    nm_assert(s_ovs_port);
 
     row = json_object();
 
-    if (s_ovs_port) {
+    s = nm_setting_ovs_port_get_vlan_mode(s_ovs_port);
+    ovsdb_row_set_string_or_null(row, "vlan_mode", s);
+
+    u = nm_setting_ovs_port_get_tag(s_ovs_port);
+    json_object_set_new(row, "tag", u != 0 ? json_integer(u) : json_pack("[s, []]", "set"));
+
+    u = nm_setting_ovs_port_get_bond_updelay(s_ovs_port);
+    json_object_set_new(row, "bond_updelay", json_integer(u));
+
+    u = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port);
+    json_object_set_new(row, "bond_downdelay", json_integer(u));
+
+    s = nm_setting_ovs_port_get_lacp(s_ovs_port);
+    ovsdb_row_set_string_or_null(row, "lacp", s);
+
+    s = nm_setting_ovs_port_get_bond_mode(s_ovs_port);
+    ovsdb_row_set_string_or_null(row, "bond_mode", s);
+
+    {
         const GPtrArray *ranges;
-        guint            i;
+        json_t          *trunks = json_array();
         guint64          start;
         guint64          end;
-
-        vlan_mode      = nm_setting_ovs_port_get_vlan_mode(s_ovs_port);
-        tag            = nm_setting_ovs_port_get_tag(s_ovs_port);
-        lacp           = nm_setting_ovs_port_get_lacp(s_ovs_port);
-        bond_mode      = nm_setting_ovs_port_get_bond_mode(s_ovs_port);
-        bond_updelay   = nm_setting_ovs_port_get_bond_updelay(s_ovs_port);
-        bond_downdelay = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port);
+        guint            i;
 
         ranges = _nm_setting_ovs_port_get_trunks_arr(s_ovs_port);
         for (i = 0; i < ranges->len; i++) {
-            if (!trunks)
-                trunks = json_array();
             nm_range_get_range(ranges->pdata[i], &start, &end);
             for (; start <= end; start++)
                 json_array_append_new(trunks, json_integer(start));
         }
-    }
 
-    if (vlan_mode)
-        json_object_set_new(row, "vlan_mode", json_string(vlan_mode));
-    if (tag)
-        json_object_set_new(row, "tag", json_integer(tag));
-    if (trunks)
         json_object_set_new(row, "trunks", json_pack("[s, o]", "set", trunks));
-    if (lacp)
-        json_object_set_new(row, "lacp", json_string(lacp));
-    if (bond_mode)
-        json_object_set_new(row, "bond_mode", json_string(bond_mode));
-    if (bond_updelay)
-        json_object_set_new(row, "bond_updelay", json_integer(bond_updelay));
-    if (bond_downdelay)
-        json_object_set_new(row, "bond_downdelay", json_integer(bond_downdelay));
+    }
+
+    return row;
+}
+
+/**
+ * _insert_port:
+ *
+ * Returns a command that adds new port from a given connection.
+ */
+static void
+_insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
+{
+    json_t *row;
+
+    row = create_port_row_object(port);
 
     json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(port)));
     json_object_set_new(row, "interfaces", json_pack("[s, O]", "set", new_interfaces));
@@ -1058,10 +1069,50 @@ _insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
                                     "rowPort"));
 }
 
+static json_t *
+create_bridge_row_object(NMConnection *connection, gboolean is_reapply)
+{
+    NMSettingOvsBridge *s_ovs_bridge;
+    json_t             *row;
+    gboolean            b;
+    const char         *s;
+
+    s_ovs_bridge = nm_connection_get_setting_ovs_bridge(connection);
+    nm_assert(s_ovs_bridge);
+
+    row = json_object();
+
+    b = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge);
+    json_object_set_new(row, "mcast_snooping_enable", json_boolean(b));
+
+    b = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge);
+    json_object_set_new(row, "rstp_enable", json_boolean(b));
+
+    b = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge);
+    json_object_set_new(row, "stp_enable", json_boolean(b));
+
+    s = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge);
+    ovsdb_row_set_string_or_null(row, "fail_mode", s);
+
+    if (!is_reapply) {
+        /* The datapath type can't be reapplied because after changing it,
+         * ovs removes the existing ovs-interface and creates a tun one (or
+         * vice-versa). */
+        s = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge);
+        if (s) {
+            /* Cannot use ovsdb_row_set_string_or_null() here as the column
+             * is a set and must not be empty. */
+            json_object_set_new(row, "datapath_type", json_string(s));
+        }
+    }
+
+    return row;
+}
+
 /**
  * _insert_bridge:
  *
- * Returns an commands that adds new bridge from a given connection.
+ * Returns a command that adds new bridge from a given connection.
  */
 static void
 _insert_bridge(json_t       *params,
@@ -1070,36 +1121,9 @@ _insert_bridge(json_t       *params,
                json_t       *new_ports,
                const char   *cloned_mac)
 {
-    NMSettingOvsBridge *s_ovs_bridge;
-    const char         *fail_mode             = NULL;
-    gboolean            mcast_snooping_enable = FALSE;
-    gboolean            rstp_enable           = FALSE;
-    gboolean            stp_enable            = FALSE;
-    const char         *datapath_type         = NULL;
-    json_t             *row;
-
-    s_ovs_bridge = nm_connection_get_setting_ovs_bridge(bridge);
-
-    row = json_object();
-
-    if (s_ovs_bridge) {
-        fail_mode             = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge);
-        mcast_snooping_enable = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge);
-        rstp_enable           = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge);
-        stp_enable            = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge);
-        datapath_type         = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge);
-    }
+    json_t *row;
 
-    if (fail_mode)
-        json_object_set_new(row, "fail_mode", json_string(fail_mode));
-    if (mcast_snooping_enable)
-        json_object_set_new(row, "mcast_snooping_enable", json_boolean(mcast_snooping_enable));
-    if (rstp_enable)
-        json_object_set_new(row, "rstp_enable", json_boolean(rstp_enable));
-    if (stp_enable)
-        json_object_set_new(row, "stp_enable", json_boolean(stp_enable));
-    if (datapath_type)
-        json_object_set_new(row, "datapath_type", json_string(datapath_type));
+    row = create_bridge_row_object(bridge, FALSE);
 
     json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(bridge)));
     json_object_set_new(row, "ports", json_pack("[s, O]", "set", new_ports));
@@ -1541,13 +1565,47 @@ ovsdb_next_command(NMOvsdb *self)
             break;
         case OVSDB_SET_REAPPLY:
         {
-            json_t *mutations;
+            NMConnection *connection;
+            json_t       *mutations;
+            json_t       *row;
+            const char   *table;
+
+            connection = call->payload.set_reapply.connection;
+            table      = _device_type_to_table(call->payload.set_reapply.device_type);
+
+            /* Reapply device properties */
+            switch (call->payload.set_reapply.device_type) {
+            case NM_DEVICE_TYPE_OVS_BRIDGE:
+                row = create_bridge_row_object(connection, TRUE);
+                break;
+            case NM_DEVICE_TYPE_OVS_PORT:
+                row = create_port_row_object(connection);
+                break;
+            default:
+                row = NULL;
+                break;
+            }
 
-            mutations = json_array();
+            if (row) {
+                json_array_append_new(params,
+                                      json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
+                                                "op",
+                                                "update",
+                                                "table",
+                                                table,
+                                                "row",
+                                                row,
+                                                "where",
+                                                "name",
+                                                "==",
+                                                call->payload.set_reapply.ifname));
+            }
 
+            /* Reapply external-ids and other-config */
+            mutations = json_array();
             _j_create_strv_array_update(mutations,
                                         STRDICT_TYPE_EXTERNAL_IDS,
-                                        call->payload.set_reapply.connection_uuid,
+                                        nm_connection_get_uuid(connection),
                                         call->payload.set_reapply.external_ids_old,
                                         call->payload.set_reapply.external_ids_new);
             _j_create_strv_array_update(mutations,
@@ -1556,19 +1614,18 @@ ovsdb_next_command(NMOvsdb *self)
                                         call->payload.set_reapply.other_config_old,
                                         call->payload.set_reapply.other_config_new);
 
-            json_array_append_new(
-                params,
-                json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
-                          "op",
-                          "mutate",
-                          "table",
-                          _device_type_to_table(call->payload.set_reapply.device_type),
-                          "mutations",
-                          mutations,
-                          "where",
-                          "name",
-                          "==",
-                          call->payload.set_reapply.ifname));
+            json_array_append_new(params,
+                                  json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
+                                            "op",
+                                            "mutate",
+                                            "table",
+                                            table,
+                                            "mutations",
+                                            mutations,
+                                            "where",
+                                            "name",
+                                            "==",
+                                            call->payload.set_reapply.ifname));
             break;
         }
 
@@ -2411,7 +2468,7 @@ again:
                  * content is broken (_json_read_msg() cannot extract any data) and
                  * we disconnect. */
                 priv->input_timeout_source =
-                    nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, NULL);
+                    nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, self);
             }
             return;
         }
@@ -2975,7 +3032,7 @@ void
 nm_ovsdb_set_reapply(NMOvsdb                 *self,
                      NMDeviceType             device_type,
                      const char              *ifname,
-                     const char              *connection_uuid,
+                     NMConnection            *connection_take,
                      NMSettingOvsExternalIDs *s_external_ids_old,
                      NMSettingOvsExternalIDs *s_external_ids_new,
                      NMSettingOvsOtherConfig *s_other_config_old,
@@ -2986,6 +3043,11 @@ nm_ovsdb_set_reapply(NMOvsdb                 *self,
     gs_unref_hashtable GHashTable *other_config_old = NULL;
     gs_unref_hashtable GHashTable *other_config_new = NULL;
 
+    nm_assert(NM_IN_SET(device_type,
+                        NM_DEVICE_TYPE_OVS_BRIDGE,
+                        NM_DEVICE_TYPE_OVS_PORT,
+                        NM_DEVICE_TYPE_OVS_INTERFACE));
+
     external_ids_old =
         s_external_ids_old
             ? nm_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_external_ids_old))
@@ -3011,7 +3073,7 @@ nm_ovsdb_set_reapply(NMOvsdb                 *self,
                       OVSDB_SET_REAPPLY,
                       OVSDB_METHOD_PAYLOAD_SET_REAPPLY(device_type,
                                                        ifname,
-                                                       connection_uuid,
+                                                       connection_take,
                                                        external_ids_old,
                                                        external_ids_new,
                                                        other_config_old,
diff --git a/src/core/devices/ovs/nm-ovsdb.h b/src/core/devices/ovs/nm-ovsdb.h
index a022ff00..9b3fb8f9 100644
--- a/src/core/devices/ovs/nm-ovsdb.h
+++ b/src/core/devices/ovs/nm-ovsdb.h
@@ -50,7 +50,7 @@ void nm_ovsdb_set_interface_mtu(NMOvsdb        *self,
 void nm_ovsdb_set_reapply(NMOvsdb                 *self,
                           NMDeviceType             device_type,
                           const char              *ifname,
-                          const char              *connection_uuid,
+                          NMConnection            *connection_take,
                           NMSettingOvsExternalIDs *s_external_ids_old,
                           NMSettingOvsExternalIDs *s_external_ids_new,
                           NMSettingOvsOtherConfig *s_other_config_old,
diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c
index 63b3060f..c17fb9cf 100644
--- a/src/core/dns/nm-dns-dnsconfd.c
+++ b/src/core/dns/nm-dns-dnsconfd.c
@@ -72,18 +72,25 @@ typedef enum {
 /*****************************************************************************/
 
 static void
+dnsconfd_change_plugin_state(NMDnsDnsconfd *self, DnsconfdPluginState new_state)
+{
+    NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self);
+
+    priv->plugin_state = new_state;
+    _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
+}
+
+static void
 dnsconfd_serial_changed(NMDnsDnsconfd *self, guint new_serial)
 {
     NMDnsDnsconfdPrivate *priv         = NM_DNS_DNSCONFD_GET_PRIVATE(self);
     priv->present_configuration_serial = new_serial;
     if (priv->plugin_state == DNSCONFD_PLUGIN_WAIT_SERIAL
         && priv->awaited_configuration_serial == new_serial) {
-        priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
         /* Update finished, serials match */
         _LOGT("serials match, update finished");
     }
-
-    _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
 }
 
 static void
@@ -132,6 +139,12 @@ dnsconfd_serial_retrieval_done(GObject *source_object, GAsyncResult *res, gpoint
     self = user_data;
     priv = NM_DNS_DNSCONFD_GET_PRIVATE(self);
 
+    if (!response) {
+        _LOGW("dnsconfd serial retrieval failed: %s", error->message);
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
+        return;
+    }
+
     nm_clear_g_cancellable(&priv->serial_cancellable);
 
     g_variant_get(response, "(v)", &new_serial_variant);
@@ -201,8 +214,11 @@ dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_da
 
     nm_clear_g_cancellable(&priv->update_cancellable);
 
-    if (!response)
+    if (!response) {
         _LOGW("dnsconfd update failed: %s", error->message);
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
+        return;
+    }
 
     /* By using &s we will get pointer to char data contained
      * in variant and thus no freing of dnsconfd_message is required */
@@ -210,8 +226,7 @@ dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_da
 
     if (!awaited_serial) {
         _LOGW("dnsconfd refused update: %s", dnsconfd_message);
-        priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
-        _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
         return;
     }
 
@@ -220,14 +235,12 @@ dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_da
 
     if (priv->awaited_configuration_serial == priv->present_configuration_serial) {
         /* Serials match, update finished */
-        priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
         _LOGT("after update serials match");
     } else {
-        priv->plugin_state = DNSCONFD_PLUGIN_WAIT_SERIAL;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_SERIAL);
         _LOGT("after update serials don't match, waiting");
     }
-
-    _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
 }
 
 static gboolean
@@ -478,8 +491,7 @@ name_owner_changed(NMDnsDnsconfd *self, const char *name_owner)
             || priv->plugin_state == DNSCONFD_PLUGIN_WAIT_SERIAL) {
             /* We were waiting for either serial or confirmation of update and name
              * disappeared, thus we need to retransmit */
-            priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
-            _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
+            dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_CONNECT);
         }
         return;
     }
@@ -490,15 +502,13 @@ name_owner_changed(NMDnsDnsconfd *self, const char *name_owner)
     if (!subscribe_serial(self)) {
         /* This means that in time between new name and subscribe serial call
          * we lost the name again thus wait again */
-        priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_CONNECT);
         _LOGT("subscription failed, waiting to connect");
     } else {
-        priv->plugin_state = DNSCONFD_PLUGIN_WAIT_UPDATE_DONE;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_UPDATE_DONE);
         _LOGT("sending update and waiting for its finish");
         send_dnsconfd_update(self);
     }
-
-    _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
 }
 
 static void
@@ -695,18 +705,16 @@ update(NMDnsPlugin             *plugin,
     /* We need to consider only whether we are connected, because newer update call
      * overrides the old one */
     if (all_connected == CONNECTION_FAIL) {
-        priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
         _LOGT("failed to connect");
     } else if (all_connected == CONNECTION_WAIT) {
-        priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_CONNECT);
         _LOGT("not connected, waiting to connect");
     } else {
-        priv->plugin_state = DNSCONFD_PLUGIN_WAIT_UPDATE_DONE;
+        dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_UPDATE_DONE);
         _LOGT("connected, waiting for update to finish");
     }
 
-    _nm_dns_plugin_update_pending_maybe_changed(plugin);
-
     if (all_connected == CONNECTION_FAIL) {
         nm_utils_error_set(error,
                            NM_UTILS_ERROR_UNKNOWN,
diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c
index 2f7fe36c..116cb737 100644
--- a/src/core/nm-bond-manager.c
+++ b/src/core/nm-bond-manager.c
@@ -882,7 +882,7 @@ nm_bond_manager_send_arp(int                 bond_ifindex,
         .sll_protocol = htons(ETH_P_ARP),
         .sll_ifindex  = bond_ifindex,
     };
-    ARPPacket         data;
+    ARPPacket         data = {0};
     const guint8     *hwaddr;
     gsize             hwaddrlen    = 0;
     nm_auto_close int sockfd       = -1;
@@ -940,6 +940,7 @@ nm_bond_manager_send_arp(int                 bond_ifindex,
         data.op = htons(ARP_OP_GARP);
         memcpy(data.s_addr, hwaddr, hwaddrlen);
         memcpy(data.s_hw_addr, hwaddr, hwaddrlen);
+        memset(data.d_hw_addr, 0xff, ETH_ALEN);
         for (int i = 0; i < addrs_len; i++) {
             const in_addr_t tmp_addr = addrs_array[i];
 
diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c
index 22e6c0d5..2aa22331 100644
--- a/src/core/nm-connectivity.c
+++ b/src/core/nm-connectivity.c
@@ -25,6 +25,8 @@
 
 #define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
 
+#define SD_RESOLVED_DNS ((guint64) (1LL << 0))
+
 /*****************************************************************************/
 
 static NM_UTILS_LOOKUP_STR_DEFINE(_state_to_string,
@@ -950,9 +952,6 @@ systemd_resolved_resolve_cb(GObject *object, GAsyncResult *res, gpointer user_da
 
     do_curl_request(cb_data, nm_str_buf_get_str(&strbuf_hosts));
 }
-#endif
-
-#define SD_RESOLVED_DNS ((guint64) (1LL << 0))
 
 static NMConnectivityState
 check_platform_config(NMConnectivity *self,
@@ -1013,6 +1012,7 @@ check_platform_config(NMConnectivity *self,
     NM_SET_OUT(reason, NULL);
     return NM_CONNECTIVITY_UNKNOWN;
 }
+#endif
 
 NMConnectivityCheckHandle *
 nm_connectivity_check_start(NMConnectivity             *self,
diff --git a/src/core/nm-firewall-utils.c b/src/core/nm-firewall-utils.c
index b4960612..60f40228 100644
--- a/src/core/nm-firewall-utils.c
+++ b/src/core/nm-firewall-utils.c
@@ -8,6 +8,7 @@
 
 #include "nm-firewall-utils.h"
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-glib-aux/nm-str-buf.h"
 #include "libnm-glib-aux/nm-io-utils.h"
 #include "libnm-platform/nm-platform.h"
@@ -127,7 +128,7 @@ _share_iptables_subnet_to_str(char      buf[static _SHARE_IPTABLES_SUBNET_TO_STR
 }
 
 static char *
-_share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const char *ip_iface)
+_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const char *ip_iface)
 {
     NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, FALSE);
     gsize    ip_iface_len;
@@ -179,13 +180,13 @@ _share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const c
 /*****************************************************************************/
 
 static gboolean
-_share_iptables_call_v(const char *const *argv)
+_iptables_call_v(const char *const *argv)
 {
     gs_free_error GError *error    = NULL;
     gs_free char         *argv_str = NULL;
     int                   status;
 
-    nm_log_dbg(LOGD_SHARING, "iptables: %s", (argv_str = g_strjoinv(" ", (char **) argv)));
+    nm_log_dbg(LOGD_FIREWALL, "iptables: %s", (argv_str = g_strjoinv(" ", (char **) argv)));
 
     if (!g_spawn_sync("/",
                       (char **) argv,
@@ -197,7 +198,7 @@ _share_iptables_call_v(const char *const *argv)
                       NULL,
                       &status,
                       &error)) {
-        nm_log_warn(LOGD_SHARING,
+        nm_log_warn(LOGD_FIREWALL,
                     "iptables: error executing command %s: %s",
                     argv[0],
                     error->message);
@@ -205,20 +206,24 @@ _share_iptables_call_v(const char *const *argv)
     }
 
     if (!g_spawn_check_exit_status(status, &error)) {
-        nm_log_warn(LOGD_SHARING, "iptables: command %s failed: %s", argv[0], error->message);
+        nm_log_warn(LOGD_FIREWALL, "iptables: command %s failed: %s", argv[0], error->message);
         return FALSE;
     }
 
     return TRUE;
 }
 
-#define _share_iptables_call(...) \
-    _share_iptables_call_v(NM_MAKE_STRV("" IPTABLES_PATH "", "--wait", "2", __VA_ARGS__))
+#define _ipxtables_call(family, ...)                                                   \
+    _iptables_call_v(                                                                  \
+        NM_MAKE_STRV((family == AF_INET ? "" IPTABLES_PATH "" : "" IP6TABLES_PATH ""), \
+                     "--wait",                                                         \
+                     "2",                                                              \
+                     __VA_ARGS__))
 
 static gboolean
 _share_iptables_chain_op(const char *table, const char *chain, const char *op)
 {
-    return _share_iptables_call("--table", table, op, chain);
+    return _ipxtables_call(AF_INET, "--table", table, op, chain);
 }
 
 static gboolean
@@ -244,24 +249,25 @@ _share_iptables_set_masquerade_sync(gboolean up, const char *ip_iface, in_addr_t
     char          str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
     gs_free char *comment_name = NULL;
 
-    comment_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
+    comment_name = _iptables_get_name(FALSE, "nm-shared", ip_iface);
 
     _share_iptables_subnet_to_str(str_subnet, addr, plen);
-    _share_iptables_call("--table",
-                         "nat",
-                         up ? "--insert" : "--delete",
-                         "POSTROUTING",
-                         "--source",
-                         str_subnet,
-                         "!",
-                         "--destination",
-                         str_subnet,
-                         "--jump",
-                         "MASQUERADE",
-                         "-m",
-                         "comment",
-                         "--comment",
-                         comment_name);
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "nat",
+                    up ? "--insert" : "--delete",
+                    "POSTROUTING",
+                    "--source",
+                    str_subnet,
+                    "!",
+                    "--destination",
+                    str_subnet,
+                    "--jump",
+                    "MASQUERADE",
+                    "-m",
+                    "comment",
+                    "--comment",
+                    comment_name);
 }
 
 static void
@@ -297,70 +303,76 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
     _share_iptables_chain_add("filter", chain_input);
 
     for (i = 0; i < (int) G_N_ELEMENTS(input_params); i++) {
-        _share_iptables_call("--table",
-                             "filter",
-                             "--append",
-                             chain_input,
-                             "--protocol",
-                             input_params[i][0],
-                             "--destination-port",
-                             input_params[i][1],
-                             "--jump",
-                             "ACCEPT");
+        _ipxtables_call(AF_INET,
+                        "--table",
+                        "filter",
+                        "--append",
+                        chain_input,
+                        "--protocol",
+                        input_params[i][0],
+                        "--destination-port",
+                        input_params[i][1],
+                        "--jump",
+                        "ACCEPT");
     }
 
     _share_iptables_chain_add("filter", chain_forward);
 
-    _share_iptables_call("--table",
-                         "filter",
-                         "--append",
-                         chain_forward,
-                         "--destination",
-                         str_subnet,
-                         "--out-interface",
-                         ip_iface,
-                         "--match",
-                         "state",
-                         "--state",
-                         "ESTABLISHED,RELATED",
-                         "--jump",
-                         "ACCEPT");
-    _share_iptables_call("--table",
-                         "filter",
-                         "--append",
-                         chain_forward,
-                         "--source",
-                         str_subnet,
-                         "--in-interface",
-                         ip_iface,
-                         "--jump",
-                         "ACCEPT");
-    _share_iptables_call("--table",
-                         "filter",
-                         "--append",
-                         chain_forward,
-                         "--in-interface",
-                         ip_iface,
-                         "--out-interface",
-                         ip_iface,
-                         "--jump",
-                         "ACCEPT");
-    _share_iptables_call("--table",
-                         "filter",
-                         "--append",
-                         chain_forward,
-                         "--out-interface",
-                         ip_iface,
-                         "--jump",
-                         "REJECT");
-    _share_iptables_call("--table",
-                         "filter",
-                         "--append",
-                         chain_forward,
-                         "--in-interface",
-                         ip_iface,
-                         "--jump",
-                         "REJECT");
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    "--append",
+                    chain_forward,
+                    "--destination",
+                    str_subnet,
+                    "--out-interface",
+                    ip_iface,
+                    "--match",
+                    "state",
+                    "--state",
+                    "ESTABLISHED,RELATED",
+                    "--jump",
+                    "ACCEPT");
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    "--append",
+                    chain_forward,
+                    "--source",
+                    str_subnet,
+                    "--in-interface",
+                    ip_iface,
+                    "--jump",
+                    "ACCEPT");
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    "--append",
+                    chain_forward,
+                    "--in-interface",
+                    ip_iface,
+                    "--out-interface",
+                    ip_iface,
+                    "--jump",
+                    "ACCEPT");
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    "--append",
+                    chain_forward,
+                    "--out-interface",
+                    ip_iface,
+                    "--jump",
+                    "REJECT");
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    "--append",
+                    chain_forward,
+                    "--in-interface",
+                    ip_iface,
+                    "--jump",
+                    "REJECT");
 }
 
 static void
@@ -377,36 +389,38 @@ _share_iptables_set_shared_sync(gboolean up, const char *ip_iface, in_addr_t add
     gs_free char *chain_input   = NULL;
     gs_free char *chain_forward = NULL;
 
-    comment_name  = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
-    chain_input   = _share_iptables_get_name(TRUE, "nm-sh-in", ip_iface);
-    chain_forward = _share_iptables_get_name(TRUE, "nm-sh-fw", ip_iface);
+    comment_name  = _iptables_get_name(FALSE, "nm-shared", ip_iface);
+    chain_input   = _iptables_get_name(TRUE, "nm-sh-in", ip_iface);
+    chain_forward = _iptables_get_name(TRUE, "nm-sh-fw", ip_iface);
 
     if (up)
         _share_iptables_set_shared_chains_add(chain_input, chain_forward, ip_iface, addr, plen);
 
-    _share_iptables_call("--table",
-                         "filter",
-                         up ? "--insert" : "--delete",
-                         "INPUT",
-                         "--in-interface",
-                         ip_iface,
-                         "--jump",
-                         chain_input,
-                         "-m",
-                         "comment",
-                         "--comment",
-                         comment_name);
-
-    _share_iptables_call("--table",
-                         "filter",
-                         up ? "--insert" : "--delete",
-                         "FORWARD",
-                         "--jump",
-                         chain_forward,
-                         "-m",
-                         "comment",
-                         "--comment",
-                         comment_name);
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    up ? "--insert" : "--delete",
+                    "INPUT",
+                    "--in-interface",
+                    ip_iface,
+                    "--jump",
+                    chain_input,
+                    "-m",
+                    "comment",
+                    "--comment",
+                    comment_name);
+
+    _ipxtables_call(AF_INET,
+                    "--table",
+                    "filter",
+                    up ? "--insert" : "--delete",
+                    "FORWARD",
+                    "--jump",
+                    chain_forward,
+                    "-m",
+                    "comment",
+                    "--comment",
+                    comment_name);
 
     if (!up)
         _share_iptables_set_shared_chains_delete(chain_input, chain_forward);
@@ -460,19 +474,19 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
         /* on any error, the process might still be running. We need to abort it in
          * the background... */
         if (!nm_utils_error_is_cancelled(error)) {
-            nm_log_dbg(LOGD_SHARING,
+            nm_log_dbg(LOGD_FIREWALL,
                        "firewall: nft[%s]: communication failed: %s. Kill process",
                        call_data->identifier,
                        error->message);
         } else if (!call_data->timeout_source) {
-            nm_log_dbg(LOGD_SHARING,
-                       "firewall: ntf[%s]: communication timed out. Kill process",
+            nm_log_dbg(LOGD_FIREWALL,
+                       "firewall: nft[%s]: communication timed out. Kill process",
                        call_data->identifier);
             nm_clear_error(&error);
             nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "timeout communicating with nft");
         } else {
-            nm_log_dbg(LOGD_SHARING,
-                       "firewall: ntf[%s]: communication cancelled. Kill process",
+            nm_log_dbg(LOGD_FIREWALL,
+                       "firewall: nft[%s]: communication cancelled. Kill process",
                        call_data->identifier);
         }
 
@@ -485,7 +499,7 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
             nm_g_subprocess_terminate_in_background(call_data->subprocess, 200);
         }
     } else if (g_subprocess_get_successful(call_data->subprocess)) {
-        nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: command successful", call_data->identifier);
+        nm_log_dbg(LOGD_FIREWALL, "firewall: nft[%s]: command successful", call_data->identifier);
     } else {
         char          buf[NM_UTILS_GET_PROCESS_EXIT_STATUS_BUF_LEN];
         gs_free char *ss_stdout    = NULL;
@@ -498,7 +512,7 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
 
         nm_utils_get_process_exit_status_desc_buf(status, buf, sizeof(buf));
 
-        nm_log_warn(LOGD_SHARING,
+        nm_log_warn(LOGD_FIREWALL,
                     "firewall: nft[%s]: command %s:%s%s%s%s%s%s%s",
                     call_data->identifier,
                     buf,
@@ -534,7 +548,7 @@ _fw_nft_call_cancelled_cb(GCancellable *cancellable, gpointer user_data)
     if (call_data->cancellable_id == 0)
         return;
 
-    nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: operation cancelled", call_data->identifier);
+    nm_log_dbg(LOGD_FIREWALL, "firewall: nft[%s]: operation cancelled", call_data->identifier);
 
     nm_clear_g_signal_handler(g_task_get_cancellable(call_data->task), &call_data->cancellable_id);
     nm_clear_g_cancellable(&call_data->intern_cancellable);
@@ -546,7 +560,7 @@ _fw_nft_call_timeout_cb(gpointer user_data)
     FwNftCallData *call_data = user_data;
 
     nm_clear_g_source_inst(&call_data->timeout_source);
-    nm_log_dbg(LOGD_SHARING,
+    nm_log_dbg(LOGD_FIREWALL,
                "firewall: nft[%s]: cancel operation after timeout",
                call_data->identifier);
 
@@ -573,7 +587,7 @@ nm_firewall_nft_call(GBytes             *stdin_buf,
         .timeout_source = NULL,
     };
 
-    nm_log_trace(LOGD_SHARING,
+    nm_log_trace(LOGD_FIREWALL,
                  "firewall: nft: call command: [ '%s' ]",
                  nm_utils_buf_utf8safe_escape_bytes(stdin_buf,
                                                     NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
@@ -585,7 +599,7 @@ nm_firewall_nft_call(GBytes             *stdin_buf,
                                                           call_data,
                                                           NULL);
         if (call_data->cancellable_id == 0) {
-            nm_log_dbg(LOGD_SHARING, "firewall: nft: already cancelled");
+            nm_log_dbg(LOGD_FIREWALL, "firewall: nft: already cancelled");
             nm_utils_error_set_cancelled(&error, FALSE, NULL);
             _fw_nft_call_data_free(call_data, g_steal_pointer(&error));
             return;
@@ -602,14 +616,14 @@ nm_firewall_nft_call(GBytes             *stdin_buf,
                                                          &error);
 
     if (!call_data->subprocess) {
-        nm_log_dbg(LOGD_SHARING, "firewall: nft: spawning nft failed: %s", error->message);
+        nm_log_dbg(LOGD_FIREWALL, "firewall: nft: spawning nft failed: %s", error->message);
         _fw_nft_call_data_free(call_data, g_steal_pointer(&error));
         return;
     }
 
     call_data->identifier = g_strdup(g_subprocess_get_identifier(call_data->subprocess));
 
-    nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: communicate with nft", call_data->identifier);
+    nm_log_dbg(LOGD_FIREWALL, "firewall: nft[%s]: communicate with nft", call_data->identifier);
 
     nm_shutdown_wait_obj_register_object(call_data->task, "nft-call");
 
@@ -691,7 +705,7 @@ _fw_nft_set_shared_construct(gboolean up, const char *ip_iface, in_addr_t addr,
     gs_free char            *table_name = NULL;
     char                     str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
 
-    table_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
+    table_name = _iptables_get_name(FALSE, "nm-shared", ip_iface);
 
     _share_iptables_subnet_to_str(str_subnet, addr, plen);
 
@@ -756,6 +770,141 @@ _fw_nft_set_shared_construct(gboolean up, const char *ip_iface, in_addr_t addr,
     return nm_str_buf_finalize_to_gbytes(&strbuf);
 }
 
+static GBytes *
+_fw_nft_wg_default_construct(const char        *ip_iface,
+                             NMSettingIPConfig *ip_config,
+                             int                fwmark,
+                             gboolean           up)
+{
+    nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+    gs_free char            *table_name = NULL;
+    const char              *family_str;
+
+    table_name = _iptables_get_name(FALSE, "nm-wg", ip_iface);
+    family_str = nm_setting_ip_config_get_addr_family(ip_config) == AF_INET ? "ip" : "ip6";
+
+    _fw_nft_append_cmd_table(&strbuf, family_str, table_name, up);
+
+    if (up) {
+        guint n_addresses = nm_setting_ip_config_get_num_addresses(ip_config);
+
+        if (n_addresses) {
+            _append(&strbuf, "add chain %s %s preraw {", family_str, table_name);
+
+            for (guint i = 0; i < n_addresses; i++) {
+                NMIPAddress *addr = nm_setting_ip_config_get_address(ip_config, i);
+
+                _append(&strbuf,
+                        " iifname != \"%s\" "
+                        " %s daddr %s "
+                        " fib saddr type != local "
+                        "drop;",
+                        ip_iface,
+                        family_str,
+                        nm_ip_address_get_address(addr));
+            }
+
+            _append(&strbuf, "};");
+        }
+
+        _append(&strbuf,
+                "add chain %s %s premangle {"
+                " type filter hook prerouting priority mangle; policy accept; "
+                " meta l4proto udp meta mark set ct mark; "
+                "};",
+                family_str,
+                table_name);
+
+        _append(&strbuf,
+                "add chain %s %s postmangle {"
+                " type filter hook postrouting priority mangle; policy accept; "
+                " meta l4proto udp mark 0x%08x ct mark set meta mark; "
+                "};",
+                family_str,
+                table_name,
+                fwmark);
+    }
+
+    return nm_str_buf_finalize_to_gbytes(&strbuf);
+}
+
+static void
+_fw_iptables_wg_configure(const char        *ip_iface,
+                          NMSettingIPConfig *ip_config,
+                          int                fwmark,
+                          gboolean           up)
+{
+    gs_free char *comment_name = NULL;
+    char          fwmark_str[11];
+    int           family      = nm_setting_ip_config_get_addr_family(ip_config);
+    guint         n_addresses = nm_setting_ip_config_get_num_addresses(ip_config);
+
+    comment_name = _iptables_get_name(FALSE, "nm-wg", ip_iface);
+    g_snprintf(fwmark_str, sizeof(fwmark_str), "%" G_GUINT32_FORMAT, fwmark);
+
+    nm_assert(strlen(fwmark_str) > 0);
+
+    for (guint i = 0; i < n_addresses; i++) {
+        NMIPAddress *addr = nm_setting_ip_config_get_address(ip_config, i);
+
+        _ipxtables_call(family,
+                        "--table",
+                        "raw",
+                        up ? "--insert" : "--delete",
+                        "PREROUTING",
+                        "!",
+                        "--in-interface",
+                        ip_iface,
+                        "--destination",
+                        nm_ip_address_get_address(addr),
+                        "--match",
+                        "addrtype",
+                        "!",
+                        "--src-type",
+                        "LOCAL",
+                        "-j",
+                        "DROP",
+                        "-m",
+                        "comment",
+                        "--comment",
+                        comment_name);
+    }
+
+    _ipxtables_call(family,
+                    "--table",
+                    "mangle",
+                    up ? "--insert" : "--delete",
+                    "POSTROUTING",
+                    "--match",
+                    "mark",
+                    "--mark",
+                    fwmark_str,
+                    "--protocol",
+                    "udp",
+                    "--jump",
+                    "CONNMARK",
+                    "--save-mark",
+                    "-m",
+                    "comment",
+                    "--comment",
+                    comment_name);
+
+    _ipxtables_call(family,
+                    "--table",
+                    "mangle",
+                    up ? "--insert" : "--delete",
+                    "PREROUTING",
+                    "--protocol",
+                    "udp",
+                    "--jump",
+                    "CONNMARK",
+                    "--restore-mark",
+                    "-m",
+                    "comment",
+                    "--comment",
+                    comment_name);
+}
+
 /*****************************************************************************/
 
 GBytes *
@@ -1046,6 +1195,31 @@ nm_firewall_config_free(NMFirewallConfig *self)
 }
 
 /*****************************************************************************/
+void
+nm_firewall_config_set_wg_rule(const char        *ifname,
+                               NMSettingIPConfig *ip_config,
+                               int                fwmark,
+                               gboolean           up)
+{
+    switch (nm_firewall_utils_get_backend()) {
+    case NM_FIREWALL_BACKEND_NFTABLES:
+    {
+        gs_unref_bytes GBytes *stdin_buf = NULL;
+
+        stdin_buf = _fw_nft_wg_default_construct(ifname, ip_config, fwmark, up);
+        _fw_nft_call_sync(stdin_buf, NULL);
+        break;
+    }
+    case NM_FIREWALL_BACKEND_IPTABLES:
+        _fw_iptables_wg_configure(ifname, ip_config, fwmark, up);
+        break;
+    case NM_FIREWALL_BACKEND_NONE:
+        break;
+    default:
+        nm_assert_not_reached();
+        break;
+    }
+}
 
 void
 nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up)
@@ -1124,7 +1298,7 @@ again:
         if (!g_atomic_int_compare_and_exchange(&backend, NM_FIREWALL_BACKEND_UNKNOWN, b))
             goto again;
 
-        nm_log_dbg(LOGD_SHARING,
+        nm_log_dbg(LOGD_FIREWALL,
                    "firewall: use %s backend%s%s%s%s%s%s%s",
                    FirewallBackends[b - 1].name,
                    NM_PRINT_FMT_QUOTED(FirewallBackends[b - 1].path,
diff --git a/src/core/nm-firewall-utils.h b/src/core/nm-firewall-utils.h
index 9f13a512..4df33d11 100644
--- a/src/core/nm-firewall-utils.h
+++ b/src/core/nm-firewall-utils.h
@@ -24,6 +24,11 @@ NMFirewallConfig *nm_firewall_config_new_shared(const char *ip_iface, in_addr_t
 
 void nm_firewall_config_free(NMFirewallConfig *self);
 
+void nm_firewall_config_set_wg_rule(const char        *ifname,
+                                    NMSettingIPConfig *ip_config,
+                                    int                fwmark,
+                                    gboolean           up);
+
 void nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up);
 
 /*****************************************************************************/
diff --git a/src/core/nm-ip-config.c b/src/core/nm-ip-config.c
index eb0ec9aa..975ae20d 100644
--- a/src/core/nm-ip-config.c
+++ b/src/core/nm-ip-config.c
@@ -826,7 +826,7 @@ _handle_l3cd_changed(NMIPConfig *self, const NML3ConfigData *l3cd)
     if (v_i != v_i_old)
         changed_params[n_changed_params++] = obj_properties_ip[PROP_IP_DNS_PRIORITY];
 
-    strarr_old = nm_l3_config_data_get_dns_options(l3cd_old, addr_family, &len);
+    strarr_old = nm_l3_config_data_get_dns_options(l3cd_old, addr_family, &len_old);
     strarr     = nm_l3_config_data_get_dns_options(priv->l3cd, addr_family, &len);
     if (!nm_strv_equal_n(strarr, len, strarr_old, len_old))
         changed_params[n_changed_params++] = obj_properties_ip[PROP_IP_DNS_OPTIONS];
diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c
index 2ff41f00..e08296c2 100644
--- a/src/core/tests/test-core.c
+++ b/src/core/tests/test-core.c
@@ -36,6 +36,7 @@ test_config_h(void)
     G_STMT_END
 
     ABSOLUTE_PATH(IPTABLES_PATH);
+    ABSOLUTE_PATH(IP6TABLES_PATH);
     ABSOLUTE_PATH(NFT_PATH);
 }
 
diff --git a/src/core/vpn/nm-vpn-connection.c b/src/core/vpn/nm-vpn-connection.c
index d0607160..22364ef9 100644
--- a/src/core/vpn/nm-vpn-connection.c
+++ b/src/core/vpn/nm-vpn-connection.c
@@ -1899,7 +1899,7 @@ _dbus_signal_config_cb(NMVpnConnection *self, GVariant *dict)
     _LOGD("config: reply received (IPv4:%s(%s), IPv6:%s(%s))",
           priv->ip_data_4.enabled ? "on" : "off",
           priv->ip_data_4.method_auto ? "auto" : "disabled",
-          priv->ip_data_4.enabled ? "on" : "off",
+          priv->ip_data_6.enabled ? "on" : "off",
           priv->ip_data_6.method_auto ? "auto" : "disabled");
 
     if (!priv->ip_data_4.method_auto)