Skip to contentSkip to conversation

iavf: fix VLAN filter state machine races

PATCH In review Petr Oros 4 participants 9 messages Apr 17 – Apr 28

Subsystems (3)
lore
Share & tools
lore

Petr Oros

author
#0 lore raw

v2 cover iavf: fix VLAN filter state machine races

The iavf VLAN filter state machine has several design issues that lead to race conditions between userspace add/del calls and the watchdog task's virtchnl processing. Filters can get lost or leak HW resources, especially during interface down/up cycles and namespace moves.

The root problems:

1) On interface down, all VLAN filters are sent as DEL to PF and re-added on interface up. This is unnecessary and creates multiple race windows (details below).

2) The DELETE path immediately frees the filter struct after sending the DEL message, without waiting for PF confirmation. If the PF rejects the DEL, the filter remains in HW but the driver lost its tracking structure. Race conditions between a pending DEL and add/reset operations cannot be resolved because the struct is gone.

3) VIRTCHNL_OP_ADD_VLAN (V1) had no success completion handler, so filters stayed in IS_NEW state permanently.

Why removing VLAN filters on down/up is unnecessary:

Unlike MAC filters, which need to be re-evaluated on up because the PF can administratively change the MAC address during down, VLAN filters are purely user-controlled. The PF cannot change them while the VF is down. When the VF goes down, VIRTCHNL_OP_DISABLE_QUEUES stops all traffic; VLAN filters sitting in PF HW are harmless because no packets flow through the disabled queues.

Compare with other filter types in iavf_down():
- MAC filters: only the current MAC is removed (it gets re-read from PF on up in case it was administratively changed)
- Cloud filters: left as-is across down/up
- FDIR filters: left as-is across down/up

VLAN filters were the only type going through a full DEL+ADD cycle, and this caused real problems:

- With spoofcheck enabled, the PF activates TX VLAN anti-spoof on the first non-zero VLAN ADD. During the re-add phase after up, the filter list is transiently incomplete; traffic for VLANs not yet re-added gets dropped by anti-spoof.

- Rapid down/up can overlap with pending DEL messages. The old code used DISABLE/INACTIVE states to track this, but the DISABLE state could overwrite a concurrent REMOVE from userspace, causing the filter to be restored instead of deleted.

- Namespace moves trigger implicit ndo_vlan_rx_kill_vid() calls concurrent with the down/up sequence. The DEL from the namespace teardown races with the DISABLE from iavf_down(), and the filter can end up leaked in num_vlan_filters with no associated netdev.

After reset, VF-configured VLAN filters are properly re-added via the VIRTCHNL_OP_GET_VF_RESOURCES / GET_OFFLOAD_VLAN_V2_CAPS response handlers, which unconditionally set all filters to ADD state. This path is unaffected by these changes.

This series addresses all three issues:

Patch 1 renames IS_NEW to ADDING for clarity.

Patch 2 removes the DISABLE/INACTIVE state machinery so VLAN filters stay ACTIVE across down/up cycles. This is the core behavioral change; VLAN filters are no longer sent as DEL to PF on interface down, and iavf_restore_filters() is removed since there is nothing to restore.

Patch 3 adds a REMOVING state to make the DELETE path symmetric with ADD; filters are only freed after PF confirms the deletion. If the PF rejects the DEL, the filter reverts to ACTIVE instead of being lost.

Patch 4 hardens the remaining race windows: adds V1 ADD success handler and prevents redundant DEL on filters already in REMOVING state.

v2: Retarget from iwl-next to iwl-net; these are bug fixes.
Rebase on current net tree (conflict resolved).

Petr Oros (4):
iavf: rename IAVF_VLAN_IS_NEW to IAVF_VLAN_ADDING
iavf: stop removing VLAN filters from PF on interface down
iavf: wait for PF confirmation before removing VLAN filters
iavf: add VIRTCHNL_OP_ADD_VLAN to success completion handler

 drivers/net/ethernet/intel/iavf/iavf.h        |  9 +--
 drivers/net/ethernet/intel/iavf/iavf_main.c   | 52 +++----------
 .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 76 +++++++++----------
 3 files changed, 52 insertions(+), 85 deletions(-)
Signature · 1 line
2.52.0
author
#1 lore raw

PATCH v2 1/4 iavf: rename IAVF_VLAN_IS_NEW to IAVF_VLAN_ADDING

Rename the IAVF_VLAN_IS_NEW state to IAVF_VLAN_ADDING to better describe what the state represents: an ADD request has been sent to the PF and is waiting for a response.

This is a pure rename with no behavioral change, preparing for a cleanup of the VLAN filter state machine.

---
 drivers/net/ethernet/intel/iavf/iavf.h          | 2 +-
 drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)
drivers/net/ethernet/intel/iavf/iavf.h +1 -1
index e9fb0a0919e376..47a862ca5e2c3f 100644
@@ -158,7 +158,7 @@ struct iavf_vlan {
 enum iavf_vlan_state_t {
 	IAVF_VLAN_INVALID,
 	IAVF_VLAN_ADD,		/* filter needs to be added */
-	IAVF_VLAN_IS_NEW,	/* filter is new, wait for PF answer */
+	IAVF_VLAN_ADDING,	/* ADD sent to PF, waiting for response */
 	IAVF_VLAN_ACTIVE,	/* filter is accepted by PF */
 	IAVF_VLAN_DISABLE,	/* filter needs to be deleted by PF, then marked INACTIVE */
 	IAVF_VLAN_INACTIVE,	/* filter is inactive, we are in IFF_DOWN */
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +4 -4
index a52c100dcbc56d..6b06ae872a0cdf 100644
@@ -746,7 +746,7 @@ static void iavf_vlan_add_reject(struct iavf_adapter *adapter)
 
 	spin_lock_bh(&adapter->mac_vlan_list_lock);
 	list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
-		if (f->state == IAVF_VLAN_IS_NEW) {
+		if (f->state == IAVF_VLAN_ADDING) {
 			list_del(&f->list);
 			kfree(f);
 			adapter->num_vlan_filters--;
@@ -812,7 +812,7 @@ void iavf_add_vlans(struct iavf_adapter *adapter)
 			if (f->state == IAVF_VLAN_ADD) {
 				vvfl->vlan_id[i] = f->vlan.vid;
 				i++;
-				f->state = IAVF_VLAN_IS_NEW;
+				f->state = IAVF_VLAN_ADDING;
 				if (i == count)
 					break;
 			}
@@ -874,7 +874,7 @@ void iavf_add_vlans(struct iavf_adapter *adapter)
 				vlan->tpid = f->vlan.tpid;
 
 				i++;
-				f->state = IAVF_VLAN_IS_NEW;
+				f->state = IAVF_VLAN_ADDING;
 			}
 		}
 
@@ -2910,7 +2910,7 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
 
 		spin_lock_bh(&adapter->mac_vlan_list_lock);
 		list_for_each_entry(f, &adapter->vlan_filter_list, list) {
-			if (f->state == IAVF_VLAN_IS_NEW)
+			if (f->state == IAVF_VLAN_ADDING)
 				f->state = IAVF_VLAN_ACTIVE;
 		}
 		spin_unlock_bh(&adapter->mac_vlan_list_lock);
Signature · 1 line
2.52.0
author
#2 lore raw

PATCH v2 2/4 iavf: stop removing VLAN filters from PF on interface down

In reply to Petr Oros

When a VF goes down, the driver currently sends DEL_VLAN to the PF for every VLAN filter (ACTIVE -> DISABLE -> send DEL -> INACTIVE), then re-adds them all on UP (INACTIVE -> ADD -> send ADD -> ADDING -> ACTIVE). This round-trip is unnecessary because:

1. The PF disables the VF's queues via VIRTCHNL_OP_DISABLE_QUEUES, which already prevents all RX/TX traffic regardless of VLAN filter state.

2. The VLAN filters remaining in PF HW while the VF is down is harmless - packets matching those filters have nowhere to go with queues disabled.

3. The DEL+ADD cycle during down/up creates race windows where the VLAN filter list is incomplete. With spoofcheck enabled, the PF enables TX VLAN filtering on the first non-zero VLAN add, blocking traffic for any VLANs not yet re-added.

Remove the entire DISABLE/INACTIVE state machinery:
- Remove IAVF_VLAN_DISABLE and IAVF_VLAN_INACTIVE enum values
- Remove iavf_restore_filters() and its call from iavf_open()
- Remove VLAN filter handling from iavf_clear_mac_vlan_filters(), rename it to iavf_clear_mac_filters()
- Remove DEL_VLAN_FILTER scheduling from iavf_down()
- Remove all DISABLE/INACTIVE handling from iavf_del_vlans()

VLAN filters now stay ACTIVE across down/up cycles. Only explicit user removal (ndo_vlan_rx_kill_vid) or PF/VF reset triggers VLAN filter deletion/re-addition.

Fixes ed1f5b58ea01 ("i40evf: remove VLAN filters on close")
---
 drivers/net/ethernet/intel/iavf/iavf.h        |  6 +--
 drivers/net/ethernet/intel/iavf/iavf_main.c   | 39 ++-----------------
 .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 33 +++-------------
 3 files changed, 12 insertions(+), 66 deletions(-)
drivers/net/ethernet/intel/iavf/iavf.h +2 -4
index 47a862ca5e2c3f..5765715914d6b2 100644
@@ -159,10 +159,8 @@ enum iavf_vlan_state_t {
 	IAVF_VLAN_INVALID,
 	IAVF_VLAN_ADD,		/* filter needs to be added */
 	IAVF_VLAN_ADDING,	/* ADD sent to PF, waiting for response */
-	IAVF_VLAN_ACTIVE,	/* filter is accepted by PF */
-	IAVF_VLAN_DISABLE,	/* filter needs to be deleted by PF, then marked INACTIVE */
-	IAVF_VLAN_INACTIVE,	/* filter is inactive, we are in IFF_DOWN */
-	IAVF_VLAN_REMOVE,	/* filter needs to be removed from list */
+	IAVF_VLAN_ACTIVE,	/* PF confirmed, filter is in HW */
+	IAVF_VLAN_REMOVE,	/* filter queued for DEL from PF */
 };
 
 struct iavf_vlan_filter {
drivers/net/ethernet/intel/iavf/iavf_main.c +4 -35
index dad001abc9086b..12e102506011a6 100644
@@ -801,27 +801,6 @@ static void iavf_del_vlan(struct iavf_adapter *adapter, struct iavf_vlan vlan)
 	spin_unlock_bh(&adapter->mac_vlan_list_lock);
 }
 
-/**
- * iavf_restore_filters
- * @adapter: board private structure
- *
- * Restore existing non MAC filters when VF netdev comes back up
- **/
-static void iavf_restore_filters(struct iavf_adapter *adapter)
-{
-	struct iavf_vlan_filter *f;
-
-	/* re-add all VLAN filters */
-	spin_lock_bh(&adapter->mac_vlan_list_lock);
-
-	list_for_each_entry(f, &adapter->vlan_filter_list, list) {
-		if (f->state == IAVF_VLAN_INACTIVE)
-			f->state = IAVF_VLAN_ADD;
-	}
-
-	spin_unlock_bh(&adapter->mac_vlan_list_lock);
-	adapter->aq_required |= IAVF_FLAG_AQ_ADD_VLAN_FILTER;
-}
 
 /**
  * iavf_get_num_vlans_added - get number of VLANs added
@@ -1240,13 +1219,12 @@ static void iavf_up_complete(struct iavf_adapter *adapter)
 }
 
 /**
- * iavf_clear_mac_vlan_filters - Remove mac and vlan filters not sent to PF
- * yet and mark other to be removed.
+ * iavf_clear_mac_filters - Remove MAC filters not sent to PF yet and mark
+ * others to be removed.
  * @adapter: board private structure
  **/
-static void iavf_clear_mac_vlan_filters(struct iavf_adapter *adapter)
+static void iavf_clear_mac_filters(struct iavf_adapter *adapter)
 {
-	struct iavf_vlan_filter *vlf, *vlftmp;
 	struct iavf_mac_filter *f, *ftmp;
 
 	spin_lock_bh(&adapter->mac_vlan_list_lock);
@@ -1265,11 +1243,6 @@ static void iavf_clear_mac_vlan_filters(struct iavf_adapter *adapter)
 		}
 	}
 
-	/* disable all VLAN filters */
-	list_for_each_entry_safe(vlf, vlftmp, &adapter->vlan_filter_list,
-				 list)
-		vlf->state = IAVF_VLAN_DISABLE;
-
 	spin_unlock_bh(&adapter->mac_vlan_list_lock);
 }
 
@@ -1365,7 +1338,7 @@ void iavf_down(struct iavf_adapter *adapter)
 	iavf_napi_disable_all(adapter);
 	iavf_irq_disable(adapter);
 
-	iavf_clear_mac_vlan_filters(adapter);
+	iavf_clear_mac_filters(adapter);
 	iavf_clear_cloud_filters(adapter);
 	iavf_clear_fdir_filters(adapter);
 	iavf_clear_adv_rss_conf(adapter);
@@ -1382,8 +1355,6 @@ void iavf_down(struct iavf_adapter *adapter)
 		 */
 		if (!list_empty(&adapter->mac_filter_list))
 			adapter->aq_required |= IAVF_FLAG_AQ_DEL_MAC_FILTER;
-		if (!list_empty(&adapter->vlan_filter_list))
-			adapter->aq_required |= IAVF_FLAG_AQ_DEL_VLAN_FILTER;
 		if (!list_empty(&adapter->cloud_filter_list))
 			adapter->aq_required |= IAVF_FLAG_AQ_DEL_CLOUD_FILTER;
 		if (!list_empty(&adapter->fdir_list_head))
@@ -4488,8 +4459,6 @@ static int iavf_open(struct net_device *netdev)
 	iavf_add_filter(adapter, adapter->hw.mac.addr);
 	spin_unlock_bh(&adapter->mac_vlan_list_lock);
 
-	/* Restore filters that were removed with IFF_DOWN */
-	iavf_restore_filters(adapter);
 	iavf_restore_fdir_filters(adapter);
 
 	iavf_configure(adapter);
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +6 -27
index 6b06ae872a0cdf..4f197d908124e6 100644
@@ -911,22 +911,12 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 	spin_lock_bh(&adapter->mac_vlan_list_lock);
 
 	list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
-		/* since VLAN capabilities are not allowed, we dont want to send
-		 * a VLAN delete request because it will most likely fail and
-		 * create unnecessary errors/noise, so just free the VLAN
-		 * filters marked for removal to enable bailing out before
-		 * sending a virtchnl message
-		 */
 		if (f->state == IAVF_VLAN_REMOVE &&
 		    !VLAN_FILTERING_ALLOWED(adapter)) {
 			list_del(&f->list);
 			kfree(f);
 			adapter->num_vlan_filters--;
-		} else if (f->state == IAVF_VLAN_DISABLE &&
-		    !VLAN_FILTERING_ALLOWED(adapter)) {
-			f->state = IAVF_VLAN_INACTIVE;
-		} else if (f->state == IAVF_VLAN_REMOVE ||
-			   f->state == IAVF_VLAN_DISABLE) {
+		} else if (f->state == IAVF_VLAN_REMOVE) {
 			count++;
 		}
 	}
@@ -959,13 +949,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 		vvfl->vsi_id = adapter->vsi_res->vsi_id;
 		vvfl->num_elements = count;
 		list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
-			if (f->state == IAVF_VLAN_DISABLE) {
-				vvfl->vlan_id[i] = f->vlan.vid;
-				f->state = IAVF_VLAN_INACTIVE;
-				i++;
-				if (i == count)
-					break;
-			} else if (f->state == IAVF_VLAN_REMOVE) {
+			if (f->state == IAVF_VLAN_REMOVE) {
 				vvfl->vlan_id[i] = f->vlan.vid;
 				list_del(&f->list);
 				kfree(f);
@@ -1007,8 +991,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 		vvfl_v2->vport_id = adapter->vsi_res->vsi_id;
 		vvfl_v2->num_elements = count;
 		list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
-			if (f->state == IAVF_VLAN_DISABLE ||
-			    f->state == IAVF_VLAN_REMOVE) {
+			if (f->state == IAVF_VLAN_REMOVE) {
 				struct virtchnl_vlan_supported_caps *filtering_support =
 					&adapter->vlan_v2_caps.filtering.filtering_support;
 				struct virtchnl_vlan *vlan;
@@ -1022,13 +1005,9 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 				vlan->tci = f->vlan.vid;
 				vlan->tpid = f->vlan.tpid;
 
-				if (f->state == IAVF_VLAN_DISABLE) {
-					f->state = IAVF_VLAN_INACTIVE;
-				} else {
-					list_del(&f->list);
-					kfree(f);
-					adapter->num_vlan_filters--;
-				}
+				list_del(&f->list);
+				kfree(f);
+				adapter->num_vlan_filters--;
 				i++;
 				if (i == count)
 					break;
Signature · 1 line
2.52.0
author
#3 lore raw

PATCH v2 3/4 iavf: wait for PF confirmation before removing VLAN filters

In reply to Petr Oros

The VLAN filter DELETE path was asymmetric with the ADD path: ADD waits for PF confirmation (ADD -> ADDING -> ACTIVE), but DELETE immediately frees the filter struct after sending the DEL message without waiting for the PF response.

This is problematic because:
- If the PF rejects the DEL, the filter remains in HW but the driver has already freed the tracking structure, losing sync.
- Race conditions between DEL pending and other operations (add, reset) cannot be properly resolved if the filter struct is already gone.

Add IAVF_VLAN_REMOVING state to make the DELETE path symmetric:

REMOVE -> REMOVING (send DEL) -> PF confirms -> kfree

                                -> PF rejects  -> ACTIVE

In iavf_del_vlans(), transition filters from REMOVE to REMOVING instead of immediately freeing them. The new DEL completion handler in iavf_virtchnl_completion() frees filters on success or reverts them to ACTIVE on error.

Update iavf_add_vlan() to handle the REMOVING state: if a DEL is pending and the user re-adds the same VLAN, queue it for ADD so it gets re-programmed after the PF processes the DEL.

The !VLAN_FILTERING_ALLOWED early-exit path still frees filters directly since no PF message is sent in that case.

Also update iavf_del_vlan() to skip filters already in REMOVING
state: DEL has been sent to PF and the completion handler will free the filter when PF confirms. Without this guard, the sequence DEL(pending) -> user-del -> second DEL could cause the PF to return an error for the second DEL (filter already gone), causing the completion handler to incorrectly revert a deleted filter back to ACTIVE.

Fixes 968996c070ef ("iavf: Fix VLAN_V2 addition/rejection")
---
 drivers/net/ethernet/intel/iavf/iavf.h        |  1 +
 drivers/net/ethernet/intel/iavf/iavf_main.c   | 13 ++++---
 .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 37 +++++++++++++------
 3 files changed, 34 insertions(+), 17 deletions(-)
drivers/net/ethernet/intel/iavf/iavf.h +1 -0
index 5765715914d6b2..050f8241ef5e6b 100644
@@ -161,6 +161,7 @@ enum iavf_vlan_state_t {
 	IAVF_VLAN_ADDING,	/* ADD sent to PF, waiting for response */
 	IAVF_VLAN_ACTIVE,	/* PF confirmed, filter is in HW */
 	IAVF_VLAN_REMOVE,	/* filter queued for DEL from PF */
+	IAVF_VLAN_REMOVING,	/* DEL sent to PF, waiting for response */
 };
 
 struct iavf_vlan_filter {
drivers/net/ethernet/intel/iavf/iavf_main.c +8 -5
index 12e102506011a6..d373feee4c7e9c 100644
@@ -757,10 +757,10 @@ iavf_vlan_filter *iavf_add_vlan(struct iavf_adapter *adapter,
 		adapter->num_vlan_filters++;
 		iavf_schedule_aq_request(adapter, IAVF_FLAG_AQ_ADD_VLAN_FILTER);
 	} else if (f->state == IAVF_VLAN_REMOVE) {
-		/* Re-add the filter since we cannot tell whether the
-		 * pending delete has already been processed by the PF.
-		 * A duplicate add is harmless.
-		 */
+		/* DEL not yet sent to PF, cancel it */
+		f->state = IAVF_VLAN_ACTIVE;
+	} else if (f->state == IAVF_VLAN_REMOVING) {
+		/* DEL already sent to PF, re-add after completion */
 		f->state = IAVF_VLAN_ADD;
 		iavf_schedule_aq_request(adapter,
 					 IAVF_FLAG_AQ_ADD_VLAN_FILTER);
@@ -791,11 +791,14 @@ static void iavf_del_vlan(struct iavf_adapter *adapter, struct iavf_vlan vlan)
 			list_del(&f->list);
 			kfree(f);
 			adapter->num_vlan_filters--;
-		} else {
+		} else if (f->state != IAVF_VLAN_REMOVING) {
 			f->state = IAVF_VLAN_REMOVE;
 			iavf_schedule_aq_request(adapter,
 						 IAVF_FLAG_AQ_DEL_VLAN_FILTER);
 		}
+		/* If REMOVING, DEL is already sent to PF; completion
+		 * handler will free the filter when PF confirms.
+		 */
 	}
 
 	spin_unlock_bh(&adapter->mac_vlan_list_lock);
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +25 -12
index 4f197d908124e6..93ca79c3e3b535 100644
@@ -948,12 +948,10 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 
 		vvfl->vsi_id = adapter->vsi_res->vsi_id;
 		vvfl->num_elements = count;
-		list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
+		list_for_each_entry(f, &adapter->vlan_filter_list, list) {
 			if (f->state == IAVF_VLAN_REMOVE) {
 				vvfl->vlan_id[i] = f->vlan.vid;
-				list_del(&f->list);
-				kfree(f);
-				adapter->num_vlan_filters--;
+				f->state = IAVF_VLAN_REMOVING;
 				i++;
 				if (i == count)
 					break;
@@ -990,7 +988,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 
 		vvfl_v2->vport_id = adapter->vsi_res->vsi_id;
 		vvfl_v2->num_elements = count;
-		list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
+		list_for_each_entry(f, &adapter->vlan_filter_list, list) {
 			if (f->state == IAVF_VLAN_REMOVE) {
 				struct virtchnl_vlan_supported_caps *filtering_support =
 					&adapter->vlan_v2_caps.filtering.filtering_support;
@@ -1005,9 +1003,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
 				vlan->tci = f->vlan.vid;
 				vlan->tpid = f->vlan.tpid;
 
-				list_del(&f->list);
-				kfree(f);
-				adapter->num_vlan_filters--;
+				f->state = IAVF_VLAN_REMOVING;
 				i++;
 				if (i == count)
 					break;
@@ -2370,10 +2366,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
 			ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr);
 			wake_up(&adapter->vc_waitqueue);
 			break;
-		case VIRTCHNL_OP_DEL_VLAN:
-			dev_err(&adapter->pdev->dev, "Failed to delete VLAN filter, error %s\n",
-				iavf_stat_str(&adapter->hw, v_retval));
-			break;
 		case VIRTCHNL_OP_DEL_ETH_ADDR:
 			dev_err(&adapter->pdev->dev, "Failed to delete MAC filter, error %s\n",
 				iavf_stat_str(&adapter->hw, v_retval));
@@ -2895,6 +2887,27 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
 		spin_unlock_bh(&adapter->mac_vlan_list_lock);
 		}
 		break;
+	case VIRTCHNL_OP_DEL_VLAN:
+	case VIRTCHNL_OP_DEL_VLAN_V2: {
+		struct iavf_vlan_filter *f, *ftmp;
+
+		spin_lock_bh(&adapter->mac_vlan_list_lock);
+		list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list,
+					 list) {
+			if (f->state == IAVF_VLAN_REMOVING) {
+				if (v_retval) {
+					/* PF rejected DEL, keep filter */
+					f->state = IAVF_VLAN_ACTIVE;
+				} else {
+					list_del(&f->list);
+					kfree(f);
+					adapter->num_vlan_filters--;
+				}
+			}
+		}
+		spin_unlock_bh(&adapter->mac_vlan_list_lock);
+		}
+		break;
 	case VIRTCHNL_OP_ENABLE_VLAN_STRIPPING:
 		/* PF enabled vlan strip on this VF.
 		 * Update netdev->features if needed to be in sync with ethtool.
Signature · 1 line
2.52.0
author
#4 lore raw

PATCH v2 4/4 iavf: add VIRTCHNL_OP_ADD_VLAN to success completion handler

In reply to Petr Oros

The V1 ADD_VLAN opcode had no success handler; filters sent via V1 stayed in ADDING state permanently. Add a fallthrough case so V1 filters also transition ADDING -> ACTIVE on PF confirmation.

Critically, add an if (v_retval) break guard: the error switch in iavf_virtchnl_completion() does NOT return after handling errors, it falls through to the success switch. Without this guard, a PF-rejected ADD would incorrectly mark ADDING filters as ACTIVE, creating a driver/HW mismatch where the driver believes the filter is installed but the PF never accepted it.

For V2, this is harmless: iavf_vlan_add_reject() in the error block already kfree'd all ADDING filters, so the success handler finds nothing to transition.

Fixes 968996c070ef ("iavf: Fix VLAN_V2 addition/rejection")
---
 drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 4 ++++
 1 file changed, 4 insertions(+)
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +4 -0
index 93ca79c3e3b535..4f2defd2331b17 100644
@@ -2876,9 +2876,13 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
 		spin_unlock_bh(&adapter->adv_rss_lock);
 		}
 		break;
+	case VIRTCHNL_OP_ADD_VLAN:
 	case VIRTCHNL_OP_ADD_VLAN_V2: {
 		struct iavf_vlan_filter *f;
 
+		if (v_retval)
+			break;
+
 		spin_lock_bh(&adapter->mac_vlan_list_lock);
 		list_for_each_entry(f, &adapter->vlan_filter_list, list) {
 			if (f->state == IAVF_VLAN_ADDING)
Signature · 1 line
2.52.0
maintainer Reviewed-by on the v2 series
#5 lore raw
In reply to Petr Oros
Quoted 5 lines · Petr Oros

The iavf VLAN filter state machine has several design issues that lead to race conditions between userspace add/del calls and the watchdog task's virtchnl processing. Filters can get lost or leak HW resources, especially during interface down/up cycles and namespace moves.

[...]

Quoted 10 lines

v2: Retarget from iwl-next to iwl-net; these are bug fixes.
Rebase on current net tree (conflict resolved).

Petr Oros (4):
iavf: rename IAVF_VLAN_IS_NEW to IAVF_VLAN_ADDING
iavf: stop removing VLAN filters from PF on interface down
iavf: wait for PF confirmation before removing VLAN filters
iavf: add VIRTCHNL_OP_ADD_VLAN to success completion handler

Quoted 5 lines
  drivers/net/ethernet/intel/iavf/iavf.h        |  9 +--
  drivers/net/ethernet/intel/iavf/iavf_main.c   | 52 +++----------
  .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 76 +++++++++----------
  3 files changed, 52 insertions(+), 85 deletions(-)

Thank you for the series, it looks good.
Also thanks for the not obvious details, like changing list_for_each_entry_safe() -> list_for_each_entry() in places that no longer alter the list

for the series:

reviewer
#6 lore raw
In reply to Petr Oros
Quoted 4 lines · Petr Oros

The iavf VLAN filter state machine has several design issues that lead to race conditions between userspace add/del calls and the watchdog task's virtchnl processing. Filters can get lost or leak HW resources, especially during interface down/up cycles and namespace moves.

...

Hi Petr,

Sashiko has a bit to say about this patch.
I'd appreciate it if you could look over that.

In particular, the feedback on patches 2 and 3 may warrant some updates to this patchset, while I think 4 is more in the realm of possible future work.

#7 lore raw
Quoted 8 lines · Simon Horman
Quoted 4 lines · Petr Oros

The iavf VLAN filter state machine has several design issues that lead to race conditions between userspace add/del calls and the watchdog task's virtchnl processing. Filters can get lost or leak HW resources, especially during interface down/up cycles and namespace moves.

...

Quoted 8 lines

Hi Petr,

Sashiko has a bit to say about this patch.
I'd appreciate it if you could look over that.

In particular, the feedback on patches 2 and 3 may warrant some updates to this patchset, while I think 4 is more in the realm of possible future work.

@Petr,

Could you please review the Sashiko reports and clarify whether a new version will be needed?

The original series posted as a net-next was Tested-by, and it would be good to get this moving, but I don't want to queue it up for sending until certain it won't simply get rejected due to these unresolved comments.

Thanks,
Jake

author
#8 lore raw
Quoted 21 lines · Jacob Keller
Quoted 7 lines · Simon Horman
Quoted 4 lines · Petr Oros

The iavf VLAN filter state machine has several design issues that lead to race conditions between userspace add/del calls and the watchdog task's virtchnl processing. Filters can get lost or leak HW resources, especially during interface down/up cycles and namespace moves.

...

Quoted 8 lines

Hi Petr,

Sashiko has a bit to say about this patch.
I'd appreciate it if you could look over that.

In particular, the feedback on patches 2 and 3 may warrant some updates to this patchset, while I think 4 is more in the realm of possible future work.

@Petr,

Could you please review the Sashiko reports and clarify whether a new version will be needed?

Quoted 7 lines

The original series posted as a net-next was Tested-by, and it would be good to get this moving, but I don't want to queue it up for sending until certain it won't simply get rejected due to these unresolved comments.

Thanks,
Jake

Hi Jake,

The Sashiko review identified seven concerns across the four patches.

Five of them describe sub millisecond race windows. Rapid del and re add of a VLAN in IAVF_VLAN_ADDING state. Pending IAVF_VLAN_ADD lost across down and up before the watchdog ships the request. REMOVING combined with user re add and user re del state confusion. The reset path resurrecting filters that are in REMOVE or REMOVING state. Phantom ACTIVE after the PF rejects an ADD whose user side del raced through.

The remaining two are deterministic pre existing V1 bugs unrelated to this series. The V1 ADD_VLAN error path has never called iavf_vlan_add_reject(). The V2 path got it in 968996c070ef ("iavf: Fix VLAN_V2 addition/rejection") and V1 was missed. These manifest whenever the PF rejects an ADD on i40e for example a port VLAN conflict or an untrusted cap reached, and they belong in a separate fix.

The five race window findings require tight syscall sequencing via ip batch or sysfs FLR concurrent with del to reach. These patterns do not match how NetworkManager, systemd-networkd, libvirt or cloud-init configure VLANs. Those tools add VLANs once on VF setup and do not issue rapid del and re add or trigger FLR mid operation. The current version keeps the state machine minimal. Closing these windows requires per filter flag tracking that adds complexity disproportionate to the user visible benefit on real workloads.

Two larger problems are worth addressing in follow up work. The first is num_vlan_filters accounting on V2 under high churn. Post series, filters in REMOVING state count against iavf_get_max_vlans_allowed until the PF confirms the deletion. This can cause a transient EIO on rapid del then add when at the cap. Pre series this was avoided by immediate kfree. The trade off here is correctness (no HW resource leak on PF reject) at the cost of a transient userspace error.

The second is the i40e silent ADD reject. The i40e PF rejects over cap or untrusted VF VLAN ADDs by returning VIRTCHNL_STATUS_SUCCESS, so iavf cannot surface the failure to userspace. ip link add ... type vlan reports success while no filter exists in HW. V2 on ice avoids this via the client side cap. Closing this gap requires PF and driver ABI coordination.

The series has been tested across documented user workflows on both ice and i40e PFs in trusted and untrusted modes. The tested scenarios include interface up and down cycles, namespace migration, VF reset, VLAN add and remove sequences, parallel VLAN operations across two VFs, traffic verification via ping under spoofcheck, port VLAN, and multi VLAN configurations. The workflow scenarios pass on the patched kernel.

The small number of test failures observed were test framework artifacts (missing IP configuration on probe interfaces, settle time too short for PF round trip drainage, V1 PF reject classification) and not kernel regressions.

Regards,

Petr