summary refs log tree commit diff
path: root/src/core/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-10-18 11:56:48 +0200
committerMichael Biebl <biebl@debian.org>2022-10-18 11:56:48 +0200
commit9f101839d9e64df832e9e43c5181887369c46a7e (patch)
tree3e23589a9d4e9e7c32ce41b634ccf26af3f0d45e /src/core/devices
parentab0efddcdb48d800e2f938da54cfe3074640792e (diff)
New upstream version 1.40.2 upstream/1.40.2
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/nm-device-bond.c95
-rw-r--r--src/core/devices/nm-device-private.h5
-rw-r--r--src/core/devices/nm-device-veth.c4
-rw-r--r--src/core/devices/nm-device.c96
-rw-r--r--src/core/devices/nm-device.h5
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c155
6 files changed, 271 insertions, 89 deletions
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 44bb316d..3e083de4 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -395,61 +395,57 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
 {
     const char *opt_value;
 
+#define _v_fcn(fcn, s_bond, opt) (fcn(nm_setting_bond_get_option_normalized((s_bond), (opt))))
+#define _v_u8(s_bond, opt)       _nm_setting_bond_opt_value_as_u8((s_bond), (opt))
+#define _v_u16(s_bond, opt)      _nm_setting_bond_opt_value_as_u16((s_bond), (opt))
+#define _v_u32(s_bond, opt)      _nm_setting_bond_opt_value_as_u32((s_bond), (opt))
+#define _v_intbool(s_bond, opt)  _nm_setting_bond_opt_value_as_intbool((s_bond), (opt))
+
     *props = (NMPlatformLnkBond){
-        .mode = _nm_setting_bond_mode_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_MODE)),
+        .mode      = _v_fcn(_nm_setting_bond_mode_from_string, s_bond, NM_SETTING_BOND_OPTION_MODE),
         .primary   = _setting_bond_primary_opt_as_ifindex(s_bond),
-        .miimon    = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
-        .updelay   = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_UPDELAY),
-        .downdelay = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY),
-        .arp_interval =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL),
-        .resend_igmp =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP),
-        .min_links = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS),
-        .lp_interval =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL),
-        .packets_per_port =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE),
-        .peer_notif_delay =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY),
-        .arp_all_targets = _nm_setting_bond_arp_all_targets_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS)),
-        .arp_validate = _nm_setting_bond_arp_validate_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE)),
-        .ad_actor_sys_prio =
-            _nm_setting_bond_opt_value_as_u16(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO),
-        .ad_user_port_key =
-            _nm_setting_bond_opt_value_as_u16(s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY),
-        .primary_reselect = _nm_setting_bond_primary_reselect_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT)),
-        .fail_over_mac = _nm_setting_bond_fail_over_mac_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC)),
-        .xmit_hash_policy = _nm_setting_bond_xmit_hash_policy_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY)),
-        .num_grat_arp =
-            _nm_setting_bond_opt_value_as_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP),
-        .all_ports_active =
-            _nm_setting_bond_opt_value_as_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE),
-        .lacp_rate = _nm_setting_bond_lacp_rate_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_LACP_RATE)),
-        .ad_select = _nm_setting_bond_ad_select_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_AD_SELECT)),
+        .miimon    = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
+        .updelay   = _v_u32(s_bond, NM_SETTING_BOND_OPTION_UPDELAY),
+        .downdelay = _v_u32(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY),
+        .arp_interval      = _v_u32(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL),
+        .resend_igmp       = _v_u32(s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP),
+        .min_links         = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS),
+        .lp_interval       = _v_u32(s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL),
+        .packets_per_port  = _v_u32(s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE),
+        .peer_notif_delay  = _v_u32(s_bond, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY),
+        .arp_all_targets   = _v_fcn(_nm_setting_bond_arp_all_targets_from_string,
+                                  s_bond,
+                                  NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS),
+        .arp_validate      = _v_fcn(_nm_setting_bond_arp_validate_from_string,
+                               s_bond,
+                               NM_SETTING_BOND_OPTION_ARP_VALIDATE),
+        .ad_actor_sys_prio = _v_u16(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO),
+        .ad_user_port_key  = _v_u16(s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY),
+        .primary_reselect  = _v_fcn(_nm_setting_bond_primary_reselect_from_string,
+                                   s_bond,
+                                   NM_SETTING_BOND_OPTION_PRIMARY_RESELECT),
+        .fail_over_mac     = _v_fcn(_nm_setting_bond_fail_over_mac_from_string,
+                                s_bond,
+                                NM_SETTING_BOND_OPTION_FAIL_OVER_MAC),
+        .xmit_hash_policy  = _v_fcn(_nm_setting_bond_xmit_hash_policy_from_string,
+                                   s_bond,
+                                   NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY),
+        .num_grat_arp      = _v_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP),
+        .all_ports_active  = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE),
+        .lacp_rate         = _v_fcn(_nm_setting_bond_lacp_rate_from_string,
+                            s_bond,
+                            NM_SETTING_BOND_OPTION_LACP_RATE),
+        .ad_select         = _v_fcn(_nm_setting_bond_ad_select_from_string,
+                            s_bond,
+                            NM_SETTING_BOND_OPTION_AD_SELECT),
+        .use_carrier       = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER),
+        .tlb_dynamic_lb    = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB),
     };
 
     nm_ether_addr_from_string(
         &props->ad_actor_system,
         nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM));
 
-    opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER);
-    if (opt_value != NULL)
-        props->use_carrier = _nm_utils_ascii_str_to_bool(opt_value, FALSE);
-
-    opt_value =
-        nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB);
-    if (opt_value != NULL)
-        props->tlb_dynamic_lb = _nm_utils_ascii_str_to_bool(opt_value, FALSE);
-
     opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
     if (opt_value != NULL)
         props->arp_ip_targets_num =
@@ -459,7 +455,6 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
     props->updelay_has          = props->miimon_has && props->miimon;
     props->downdelay_has        = props->miimon_has && props->miimon;
     props->peer_notif_delay_has = (props->miimon || props->arp_interval) && props->peer_notif_delay;
-    props->arp_all_targets_has  = props->arp_interval && props->arp_all_targets;
     props->resend_igmp_has      = props->resend_igmp != 1;
     props->lp_interval_has      = props->lp_interval != 1;
     props->tlb_dynamic_lb_has   = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB);
@@ -493,6 +488,10 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE))
             ret = NM_ACT_STAGE_RETURN_FAILURE;
     }
+
+    /* This is a workaround because netlink do not support ifname as primary */
+    set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
+
     nm_device_bring_up(device, TRUE, NULL);
 
     return ret;
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index b54aed6a..31424d5c 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -89,7 +89,10 @@ nm_device_devip_set_state(NMDevice             *self,
     nm_assert(NM_IS_DEVICE(self));
     nm_assert_addr_family_or_unspec(addr_family);
     nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd));
-    nm_assert(NM_IN_SET(ip_state, NM_DEVICE_IP_STATE_PENDING, NM_DEVICE_IP_STATE_READY));
+    nm_assert(NM_IN_SET(ip_state,
+                        NM_DEVICE_IP_STATE_NONE,
+                        NM_DEVICE_IP_STATE_PENDING,
+                        NM_DEVICE_IP_STATE_READY));
 
     nm_device_devip_set_state_full(self, addr_family, ip_state, l3cd, NM_DEVICE_STATE_REASON_NONE);
 }
diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index 63dfd8bb..c3482e78 100644
--- a/src/core/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -101,8 +101,8 @@ create_and_realize(NMDevice              *device,
     peer        = nm_setting_veth_get_peer(s_veth);
     peer_device = nm_manager_get_device(NM_MANAGER_GET, peer, NM_DEVICE_TYPE_VETH);
     if (peer_device) {
-        /* The veth device and its peer already exist. No need to create it again. */
-        if (nm_streq0(nm_device_get_iface(nm_device_parent_get_device(peer_device)), iface))
+        if (nm_device_parent_get_device(peer_device))
+            /* The veth device and its peer already exist. No need to create it again. */
             return TRUE;
     }
 
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index d63b902b..6be9bc7c 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -96,6 +96,8 @@
 #define NM_DEVICE_AUTH_RETRIES_INFINITY -2
 #define NM_DEVICE_AUTH_RETRIES_DEFAULT  3
 
+#define AUTOCONNECT_RESET_RETRIES_TIMER 300
+
 /*****************************************************************************/
 
 typedef void (*ActivationHandleFunc)(NMDevice *self);
@@ -761,6 +763,9 @@ typedef struct _NMDevicePrivate {
 
     GVariant *ports_variant; /* Array of port devices D-Bus path */
     char     *prop_ip_iface; /* IP interface D-Bus property */
+
+    int    autoconnect_retries;
+    gint32 autoconnect_retries_blocked_until;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -778,7 +783,9 @@ static void _dev_l3_cfg_commit_type_reset(NMDevice *self);
 
 static gboolean nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure);
 static void     nm_device_slave_notify_enslave(NMDevice *self, gboolean success);
-static void     nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason);
+static void     nm_device_slave_notify_release(NMDevice           *self,
+                                               NMDeviceStateReason reason,
+                                               ReleaseSlaveType    release_type);
 
 static void _dev_ipll6_start(NMDevice *self);
 
@@ -6236,7 +6243,7 @@ nm_device_master_release_slave(NMDevice           *self,
                                                release_type >= RELEASE_SLAVE_TYPE_CONFIG);
 
     /* raise notifications about the release, including clearing is_enslaved. */
-    nm_device_slave_notify_release(slave, reason);
+    nm_device_slave_notify_release(slave, reason, release_type);
 
     /* keep both alive until the end of the function.
      * Transfers ownership from slave_priv->master.  */
@@ -6536,12 +6543,16 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
 
     g_return_if_fail(plink);
 
-    if (plink->master <= 0)
-        goto out;
-
-    master                  = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master);
-    plink_master            = nm_platform_link_get(nm_device_get_platform(self), plink->master);
-    plink_master_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink_master));
+    if (plink->master > 0) {
+        master                  = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master);
+        plink_master            = nm_platform_link_get(nm_device_get_platform(self), plink->master);
+        plink_master_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink_master));
+    } else {
+        if (priv->master_ifindex == 0)
+            goto out;
+        master       = NULL;
+        plink_master = NULL;
+    }
 
     if (master == NULL && plink_master
         && NM_IN_STRSET(plink_master->name, "ovs-system", "ovs-netdev")
@@ -8024,7 +8035,9 @@ nm_device_slave_notify_enslave(NMDevice *self, gboolean success)
  * Notifies a slave that it has been released, and why.
  */
 static void
-nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
+nm_device_slave_notify_release(NMDevice           *self,
+                               NMDeviceStateReason reason,
+                               ReleaseSlaveType    release_type)
 {
     NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
     NMConnection    *connection = nm_device_get_applied_connection(self);
@@ -8032,7 +8045,7 @@ nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
 
     g_return_if_fail(priv->master);
 
-    if (!priv->is_enslaved)
+    if (!priv->is_enslaved && release_type == RELEASE_SLAVE_TYPE_NO_CONFIG)
         return;
 
     if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state <= NM_DEVICE_STATE_ACTIVATED) {
@@ -8956,7 +8969,7 @@ nm_device_emit_recheck_assume(gpointer user_data)
     priv = NM_DEVICE_GET_PRIVATE(self);
 
     priv->recheck_assume_id = 0;
-    if (!nm_device_get_act_request(self))
+    if (!priv->queued_act_request && !nm_device_get_act_request(self))
         g_signal_emit(self, signals[RECHECK_ASSUME], 0);
 
     return G_SOURCE_REMOVE;
@@ -9954,6 +9967,7 @@ nm_device_devip_set_state_full(NMDevice             *self,
 
     nm_assert_addr_family_or_unspec(addr_family);
     nm_assert(NM_IN_SET(ip_state,
+                        NM_DEVICE_IP_STATE_NONE,
                         NM_DEVICE_IP_STATE_PENDING,
                         NM_DEVICE_IP_STATE_READY,
                         NM_DEVICE_IP_STATE_FAILED));
@@ -9961,7 +9975,7 @@ nm_device_devip_set_state_full(NMDevice             *self,
 
     nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED)
               == (failed_reason == NM_DEVICE_STATE_REASON_NONE));
-    nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED) || !l3cd);
+    nm_assert(NM_IN_SET(ip_state, NM_DEVICE_IP_STATE_PENDING, NM_DEVICE_IP_STATE_READY) || !l3cd);
 
     p = _dev_ipdev_data(self, addr_family);
 
@@ -12206,7 +12220,7 @@ _dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
         }
 
         if (priv->ipshared_data_4.v4.firewall_config) {
-            nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, FALSE);
+            nm_firewall_config_apply_sync(priv->ipshared_data_4.v4.firewall_config, FALSE);
             nm_clear_pointer(&priv->ipshared_data_4.v4.firewall_config, nm_firewall_config_free);
         }
 
@@ -12353,8 +12367,8 @@ _dev_ipshared4_start(NMDevice *self)
         goto out_fail;
 
     priv->ipshared_data_4.v4.firewall_config =
-        nm_firewall_config_new(ip_iface, ip4_addr.address, ip4_addr.plen);
-    nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, TRUE);
+        nm_firewall_config_new_shared(ip_iface, ip4_addr.address, ip4_addr.plen);
+    nm_firewall_config_apply_sync(priv->ipshared_data_4.v4.firewall_config, TRUE);
 
     priv->ipshared_data_4.v4.l3cd = nm_l3_config_data_ref(l3cd);
     _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, l3cd, FALSE);
@@ -16857,6 +16871,49 @@ nm_device_get_initial_hw_address(NMDevice *self)
     return NM_DEVICE_GET_PRIVATE(self)->hw_addr_initial;
 }
 
+void
+nm_device_set_autoconnect_retries(NMDevice *self, int tries)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (priv->autoconnect_retries != tries) {
+        _LOGT(LOGD_DEVICE, "autoconnect: retries set %d", tries);
+        priv->autoconnect_retries = tries;
+    }
+
+    if (tries)
+        priv->autoconnect_retries_blocked_until = 0; /* we are not blocked anymore */
+    else
+        priv->autoconnect_retries_blocked_until =
+            nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER;
+}
+
+int
+nm_device_get_autoconnect_retries(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    return priv->autoconnect_retries;
+}
+
+gint32
+nm_device_autoconnect_retries_blocked_until(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    return priv->autoconnect_retries_blocked_until;
+}
+
+void
+nm_device_autoconnect_retries_reset(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    /* default value, we will sync. with connection value when needed */
+    priv->autoconnect_retries               = -2;
+    priv->autoconnect_retries_blocked_until = 0;
+}
+
 /**
  * nm_device_spec_match_list:
  * @self: an #NMDevice
@@ -17161,6 +17218,13 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     /* If the device is not supposed to have addresses,
      * return an immediate empty result.*/
     if (!nm_device_get_applied_connection(self)) {
+        nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
+        NM_SET_OUT(out_wait, FALSE);
+        return NULL;
+    }
+
+    if (!priv->carrier) {
+        nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
         NM_SET_OUT(out_wait, FALSE);
         return NULL;
     }
@@ -17601,6 +17665,8 @@ nm_device_init(NMDevice *self)
     priv->sys_iface_state_      = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
 
     priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
+
+    priv->autoconnect_retries = -2;
 }
 
 static GObject *
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index de850e68..fea46bb7 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -464,6 +464,11 @@ const char *nm_device_get_permanent_hw_address_full(NMDevice *self,
                                                     gboolean *out_is_fake);
 const char *nm_device_get_initial_hw_address(NMDevice *dev);
 
+void   nm_device_set_autoconnect_retries(NMDevice *self, int tries);
+int    nm_device_get_autoconnect_retries(NMDevice *self);
+gint32 nm_device_autoconnect_retries_blocked_until(NMDevice *self);
+void   nm_device_autoconnect_retries_reset(NMDevice *self);
+
 NMDhcpConfig *nm_device_get_dhcp_config(NMDevice *dev, int addr_family);
 
 NML3Cfg *nm_device_get_l3cfg(NMDevice *self);
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index e7c96852..d3e858a1 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -18,6 +18,7 @@
 #include "nm-manager.h"
 #include "nm-setting-ovs-external-ids.h"
 #include "nm-priv-helper-call.h"
+#include "libnm-platform/nm-platform.h"
 
 /*****************************************************************************/
 
@@ -120,6 +121,7 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
+    NMPlatform        *platform;
     GSocketConnection *conn;
     GCancellable      *conn_cancellable;
     char               buf[4096]; /* Input buffer */
@@ -135,8 +137,14 @@ typedef struct {
     GHashTable *bridges;    /* bridge uuid => OpenvswitchBridge */
     char       *db_uuid;
     guint       num_failures;
-    guint       num_pending_deletions;
     bool        ready : 1;
+    struct {
+        GPtrArray *interfaces;      /* Interface names we are waiting to go away */
+        GSource   *timeout_source;  /* After all deletions complete, wait this
+                                    * timeout for interfaces to disappear */
+        gulong     link_changed_id; /* Platform link-changed signal handle */
+        guint      num_pending_del; /* Number of ovsdb deletions pending */
+    } cleanup;
 } NMOvsdbPrivate;
 
 struct _NMOvsdb {
@@ -161,6 +169,7 @@ static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposin
 static void ovsdb_read(NMOvsdb *self);
 static void ovsdb_write(NMOvsdb *self);
 static void ovsdb_next_command(NMOvsdb *self);
+static void cleanup_check_ready(NMOvsdb *self);
 
 /*****************************************************************************/
 
@@ -2283,21 +2292,114 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 }
 
 static void
-_check_ready(NMOvsdb *self)
+cleanup_emit_ready(NMOvsdb *self, const char *reason)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    _LOGT("cleanup: ready (%s)", reason);
+
+    nm_clear_pointer(&priv->cleanup.interfaces, g_ptr_array_unref);
+    nm_clear_g_source_inst(&priv->cleanup.timeout_source);
+    nm_clear_g_signal_handler(priv->platform, &priv->cleanup.link_changed_id);
+
+    priv->ready = TRUE;
+    g_signal_emit(self, signals[READY], 0);
+    nm_manager_unblock_failed_ovs_interfaces(nm_manager_get());
+}
+
+static gboolean
+cleanup_timeout(NMOvsdb *self)
+{
+    cleanup_emit_ready(self, "timeout");
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+cleanup_link_cb(NMPlatform     *platform,
+                int             obj_type_i,
+                int             ifindex,
+                NMPlatformLink *plink,
+                int             change_type_i,
+                gpointer        user_data)
+{
+    const NMPlatformSignalChangeType change_type = change_type_i;
+
+    if (change_type != NM_PLATFORM_SIGNAL_REMOVED)
+        return;
+
+    cleanup_check_ready(user_data);
+}
+
+static void
+cleanup_check_ready(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+    guint           i    = 0;
 
     nm_assert(!priv->ready);
 
-    if (priv->num_pending_deletions == 0) {
-        priv->ready = TRUE;
-        g_signal_emit(self, signals[READY], 0);
-        nm_manager_unblock_failed_ovs_interfaces(nm_manager_get());
+    if (priv->cleanup.num_pending_del > 0)
+        return;
+
+    /* After we have deleted an interface from ovsdb, the link will stay
+     * in platform until ovs-vswitch removes it. To avoid race conditions,
+     * we need to wait until the link goes away; otherwise, after adding the
+     * interface again, these race conditions can happen:
+     * 1) we see the link in platform, and proceed with activation. But after
+     *    that, ovs-vswitchd reads the updates from ovsdb-server and deletes/recreates
+     *    the link.
+     * 2) ovs-vswitch combines the delete/insert of the interface to a no-op. NM sees
+     *    the link staying in platform, but doesn't know whether the link is ready
+     *    or we are again in case 1)
+     * In other words, it's necessary to wait that the link goes away before inserting
+     * the interface again.
+     */
+    while (i < nm_g_ptr_array_len(priv->cleanup.interfaces)) {
+        const char                  *ifname;
+        const NMDedupMultiHeadEntry *pl_links_head_entry;
+        NMDedupMultiIter             pliter;
+        const NMPlatformLink        *link;
+        gboolean                     found = FALSE;
+
+        ifname              = priv->cleanup.interfaces->pdata[i];
+        pl_links_head_entry = nm_platform_lookup_link_by_ifname(priv->platform, ifname);
+        nmp_cache_iter_for_each_link (&pliter, pl_links_head_entry, &link) {
+            if (link->type == NM_LINK_TYPE_OPENVSWITCH
+                && nmp_object_is_visible(NMP_OBJECT_UP_CAST(link))) {
+                found = TRUE;
+                break;
+            }
+        }
+
+        if (!found) {
+            g_ptr_array_remove_index_fast(priv->cleanup.interfaces, i);
+            continue;
+        }
+        i++;
     }
+
+    if (nm_g_ptr_array_len(priv->cleanup.interfaces) == 0) {
+        cleanup_emit_ready(self, "all interfaces deleted");
+        return;
+    }
+
+    _LOGT("cleanup: still waiting for %d interfaces", priv->cleanup.interfaces->len);
+
+    if (priv->cleanup.timeout_source) {
+        /* We already registered the timeout/change-callback */
+        return;
+    }
+
+    priv->cleanup.timeout_source =
+        nm_g_timeout_add_seconds_source(6, G_SOURCE_FUNC(cleanup_timeout), self);
+    priv->cleanup.link_changed_id = g_signal_connect(priv->platform,
+                                                     NM_PLATFORM_SIGNAL_LINK_CHANGED,
+                                                     G_CALLBACK(cleanup_link_cb),
+                                                     self);
 }
 
 static void
-_del_initial_iface_cb(GError *error, gpointer user_data)
+cleanup_del_iface_cb(GError *error, gpointer user_data)
 {
     NMOvsdb        *self;
     gs_free char   *ifname = NULL;
@@ -2309,18 +2411,18 @@ _del_initial_iface_cb(GError *error, gpointer user_data)
         return;
 
     priv = NM_OVSDB_GET_PRIVATE(self);
-    nm_assert(priv->num_pending_deletions > 0);
-    priv->num_pending_deletions--;
+    nm_assert(priv->cleanup.num_pending_del > 0);
+    priv->cleanup.num_pending_del--;
 
-    _LOGD("delete initial interface '%s': %s %s%s%s, pending %u",
+    _LOGD("cleanup: deleted interface '%s': %s %s%s%s, pending %u",
           ifname,
           error ? "error" : "success",
           error ? "(" : "",
           error ? error->message : "",
           error ? ")" : "",
-          priv->num_pending_deletions);
+          priv->cleanup.num_pending_del);
 
-    _check_ready(self);
+    cleanup_check_ready(self);
 }
 
 static void
@@ -2331,7 +2433,7 @@ ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
     NMUtilsUserData            *data;
     GHashTableIter              iter;
 
-    if (priv->ready || priv->num_pending_deletions != 0)
+    if (priv->ready || priv->cleanup.num_pending_del > 0 || priv->cleanup.interfaces)
         return;
 
     /* Delete OVS interfaces added by NM. Bridges and ports and
@@ -2339,17 +2441,22 @@ ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
      * when no interface is present. */
     g_hash_table_iter_init(&iter, self->_priv.interfaces);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &interface)) {
-        if (interface->connection_uuid) {
-            priv->num_pending_deletions++;
-            _LOGD("deleting initial interface '%s' (pending: %u)",
-                  interface->name,
-                  priv->num_pending_deletions);
-            data = nm_utils_user_data_pack(self, g_strdup(interface->name));
-            nm_ovsdb_del_interface(self, interface->name, _del_initial_iface_cb, data);
+        if (!interface->connection_uuid) {
+            /* not created by NM, ignore */
+            continue;
         }
+
+        if (!priv->cleanup.interfaces)
+            priv->cleanup.interfaces = g_ptr_array_new_with_free_func(g_free);
+        g_ptr_array_add(priv->cleanup.interfaces, g_strdup(interface->name));
+
+        _LOGD("cleanup: deleting interface '%s'", interface->name);
+        priv->cleanup.num_pending_del++;
+        data = nm_utils_user_data_pack(self, g_strdup(interface->name));
+        nm_ovsdb_del_interface(self, interface->name, cleanup_del_iface_cb, data);
     }
 
-    _check_ready(self);
+    cleanup_check_ready(self);
 }
 
 static void
@@ -2622,8 +2729,9 @@ nm_ovsdb_init(NMOvsdb *self)
 
     c_list_init(&priv->calls_lst_head);
 
-    priv->input  = g_string_new(NULL);
-    priv->output = g_string_new(NULL);
+    priv->platform = g_object_ref(NM_PLATFORM_GET);
+    priv->input    = g_string_new(NULL);
+    priv->output   = g_string_new(NULL);
     priv->bridges =
         g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL);
     priv->ports =
@@ -2653,6 +2761,7 @@ dispose(GObject *object)
         priv->output = NULL;
     }
 
+    g_clear_object(&priv->platform);
     nm_clear_pointer(&priv->bridges, g_hash_table_destroy);
     nm_clear_pointer(&priv->ports, g_hash_table_destroy);
     nm_clear_pointer(&priv->interfaces, g_hash_table_destroy);