public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH nf-next 0/2] Update netdev stats with offloaded flows
@ 2026-03-17 23:48 Ahmed Zaki
  2026-03-17 23:48 ` [PATCH nf-next 1/2] net: treewide: pass number of pkts to dev_sw_netstats_rx_add() Ahmed Zaki
  2026-03-17 23:48 ` [PATCH nf-next 2/2] netfilter: flowtable: update netdev stats with HW_OFFLOAD flows Ahmed Zaki
  0 siblings, 2 replies; 5+ messages in thread
From: Ahmed Zaki @ 2026-03-17 23:48 UTC (permalink / raw)
  To: netfilter-devel, pablo, fw; +Cc: coreteam, netdev

Let's allow SNMP-based tools to accurately report the Tx/Rx stats
on devices implementing hardware flow offloads. Without this, its is very
confusing since these devices are reporting very-low stats compared to
others that do not do hardware-offloading.

First patch is prep work, change the prototype of dev_sw_netstats_rx_add()
to pass "packets" instead of the implied "1". Second patch updates the
netdev stats.

Ahmed Zaki (2):
  net: treewide: pass number of pkts to dev_sw_netstats_rx_add()
  netfilter: flowtable: update netdev stats with HW_OFFLOAD flows

 drivers/infiniband/hw/hfi1/driver.c           |  2 +-
 drivers/net/amt.c                             |  6 +-
 .../net/ethernet/hisilicon/hibmcge/hbg_txrx.c |  2 +-
 drivers/net/ethernet/litex/litex_liteeth.c    |  2 +-
 drivers/net/ethernet/realtek/r8169_main.c     |  2 +-
 .../net/ethernet/realtek/rtase/rtase_main.c   |  2 +-
 drivers/net/ethernet/ti/am65-cpsw-nuss.c      |  6 +-
 drivers/net/ethernet/ti/icssg/icssg_common.c  |  4 +-
 drivers/net/gtp.c                             |  2 +-
 drivers/net/macsec.c                          |  2 +-
 drivers/net/netkit.c                          |  2 +-
 drivers/net/ppp/ppp_generic.c                 |  2 +-
 drivers/net/tun.c                             |  8 +--
 drivers/net/usb/qmi_wwan.c                    |  2 +-
 drivers/net/wireguard/receive.c               |  2 +-
 .../quantenna/qtnfmac/pcie/pearl_pcie.c       |  2 +-
 .../quantenna/qtnfmac/pcie/topaz_pcie.c       |  2 +-
 include/linux/netdevice.h                     |  6 +-
 net/bridge/br_input.c                         |  2 +-
 net/core/filter.c                             |  2 +-
 net/dsa/tag.c                                 |  2 +-
 net/ipv4/ip_tunnel.c                          |  2 +-
 net/ipv4/ip_vti.c                             |  2 +-
 net/ipv6/ip6_tunnel.c                         |  2 +-
 net/ipv6/ip6_vti.c                            |  2 +-
 net/ipv6/sit.c                                |  2 +-
 net/mac80211/rx.c                             |  8 +--
 net/netfilter/nf_flow_table_offload.c         | 59 +++++++++++++++++--
 net/openvswitch/vport-internal_dev.c          |  2 +-
 net/xfrm/xfrm_interface_core.c                |  2 +-
 30 files changed, 96 insertions(+), 47 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH nf-next 1/2] net: treewide: pass number of pkts to dev_sw_netstats_rx_add()
  2026-03-17 23:48 [PATCH nf-next 0/2] Update netdev stats with offloaded flows Ahmed Zaki
@ 2026-03-17 23:48 ` Ahmed Zaki
  2026-03-19  2:12   ` Jakub Kicinski
  2026-03-17 23:48 ` [PATCH nf-next 2/2] netfilter: flowtable: update netdev stats with HW_OFFLOAD flows Ahmed Zaki
  1 sibling, 1 reply; 5+ messages in thread
From: Ahmed Zaki @ 2026-03-17 23:48 UTC (permalink / raw)
  To: netfilter-devel, pablo, fw; +Cc: coreteam, netdev

Allow callers of dev_sw_netstats_rx_add() to update the netdev stats
with multiple packets.

Signed-off-by: Ahmed Zaki <anzaki@gmail.com>
---
 drivers/infiniband/hw/hfi1/driver.c                      | 2 +-
 drivers/net/amt.c                                        | 6 +++---
 drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c        | 2 +-
 drivers/net/ethernet/litex/litex_liteeth.c               | 2 +-
 drivers/net/ethernet/realtek/r8169_main.c                | 2 +-
 drivers/net/ethernet/realtek/rtase/rtase_main.c          | 2 +-
 drivers/net/ethernet/ti/am65-cpsw-nuss.c                 | 6 +++---
 drivers/net/ethernet/ti/icssg/icssg_common.c             | 4 ++--
 drivers/net/gtp.c                                        | 2 +-
 drivers/net/macsec.c                                     | 2 +-
 drivers/net/netkit.c                                     | 2 +-
 drivers/net/ppp/ppp_generic.c                            | 2 +-
 drivers/net/tun.c                                        | 8 ++++----
 drivers/net/usb/qmi_wwan.c                               | 2 +-
 drivers/net/wireguard/receive.c                          | 2 +-
 drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c | 2 +-
 drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c | 2 +-
 include/linux/netdevice.h                                | 6 ++++--
 net/bridge/br_input.c                                    | 2 +-
 net/core/filter.c                                        | 2 +-
 net/dsa/tag.c                                            | 2 +-
 net/ipv4/ip_tunnel.c                                     | 2 +-
 net/ipv4/ip_vti.c                                        | 2 +-
 net/ipv6/ip6_tunnel.c                                    | 2 +-
 net/ipv6/ip6_vti.c                                       | 2 +-
 net/ipv6/sit.c                                           | 2 +-
 net/mac80211/rx.c                                        | 8 ++++----
 net/openvswitch/vport-internal_dev.c                     | 2 +-
 net/xfrm/xfrm_interface_core.c                           | 2 +-
 29 files changed, 43 insertions(+), 41 deletions(-)

diff --git a/drivers/infiniband/hw/hfi1/driver.c b/drivers/infiniband/hw/hfi1/driver.c
index 06487e20f723..a5e91ae7619e 100644
--- a/drivers/infiniband/hw/hfi1/driver.c
+++ b/drivers/infiniband/hw/hfi1/driver.c
@@ -1689,7 +1689,7 @@ static void hfi1_ipoib_ib_rcv(struct hfi1_packet *packet)
 	if (unlikely(!skb))
 		goto drop;
 
-	dev_sw_netstats_rx_add(netdev, skb->len);
+	dev_sw_netstats_rx_add(netdev, 1, skb->len);
 
 	skb->dev = netdev;
 	skb->pkt_type = PACKET_HOST;
diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index f2f3139e38a5..11b4d0ffc8a1 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -2340,7 +2340,7 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)
 	len = skb->len;
 	err = gro_cells_receive(&amt->gro_cells, skb);
 	if (likely(err == NET_RX_SUCCESS))
-		dev_sw_netstats_rx_add(amt->dev, len);
+		dev_sw_netstats_rx_add(amt->dev, 1, len);
 	else
 		amt->dev->stats.rx_dropped++;
 
@@ -2439,7 +2439,7 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 	local_bh_disable();
 	if (__netif_rx(skb) == NET_RX_SUCCESS) {
 		amt_update_gw_status(amt, AMT_STATUS_RECEIVED_QUERY, true);
-		dev_sw_netstats_rx_add(amt->dev, len);
+		dev_sw_netstats_rx_add(amt->dev, 1, len);
 	} else {
 		amt->dev->stats.rx_dropped++;
 	}
@@ -2541,7 +2541,7 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
 	if (__netif_rx(skb) == NET_RX_SUCCESS) {
 		amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_UPDATE,
 					true);
-		dev_sw_netstats_rx_add(amt->dev, len);
+		dev_sw_netstats_rx_add(amt->dev, 1, len);
 	} else {
 		amt->dev->stats.rx_dropped++;
 	}
diff --git a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c
index a4ea92c31c2f..bc1104b9f27d 100644
--- a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c
+++ b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c
@@ -516,7 +516,7 @@ static int hbg_napi_rx_poll(struct napi_struct *napi, int budget)
 		skb_put(buffer->skb, pkt_len);
 		buffer->skb->protocol = eth_type_trans(buffer->skb,
 						       priv->netdev);
-		dev_sw_netstats_rx_add(priv->netdev, pkt_len);
+		dev_sw_netstats_rx_add(priv->netdev, 1, pkt_len);
 		napi_gro_receive(napi, buffer->skb);
 		buffer->skb = NULL;
 		buffer->page = NULL;
diff --git a/drivers/net/ethernet/litex/litex_liteeth.c b/drivers/net/ethernet/litex/litex_liteeth.c
index 108d0a0db206..375a049fb3ad 100644
--- a/drivers/net/ethernet/litex/litex_liteeth.c
+++ b/drivers/net/ethernet/litex/litex_liteeth.c
@@ -78,7 +78,7 @@ static int liteeth_rx(struct net_device *netdev)
 	memcpy_fromio(data, priv->rx_base + rx_slot * priv->slot_size, len);
 	skb->protocol = eth_type_trans(skb, netdev);
 
-	dev_sw_netstats_rx_add(netdev, len);
+	dev_sw_netstats_rx_add(netdev, 1, len);
 
 	return netif_rx(skb);
 
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 791277e750ba..0cbeaa1b7d4e 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -4846,7 +4846,7 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
 
 		napi_gro_receive(&tp->napi, skb);
 
-		dev_sw_netstats_rx_add(dev, pkt_size);
+		dev_sw_netstats_rx_add(dev, 1, pkt_size);
 release_descriptor:
 		rtl8169_mark_to_asic(desc);
 	}
diff --git a/drivers/net/ethernet/realtek/rtase/rtase_main.c b/drivers/net/ethernet/realtek/rtase/rtase_main.c
index ef13109c49cf..40186d6a926a 100644
--- a/drivers/net/ethernet/realtek/rtase/rtase_main.c
+++ b/drivers/net/ethernet/realtek/rtase/rtase_main.c
@@ -568,7 +568,7 @@ static int rx_handler(struct rtase_ring *ring, int budget)
 		rtase_rx_vlan_skb(desc, skb);
 		rtase_rx_skb(ring, skb);
 
-		dev_sw_netstats_rx_add(dev, pkt_size);
+		dev_sw_netstats_rx_add(dev, 1, pkt_size);
 
 skip_process_pkt:
 		workdone++;
diff --git a/drivers/net/ethernet/ti/am65-cpsw-nuss.c b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
index a38bf7f4f434..1d92c3d27946 100644
--- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
+++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
@@ -1210,13 +1210,13 @@ static int am65_cpsw_run_xdp(struct am65_cpsw_rx_flow *flow,
 		if (err)
 			goto drop;
 
-		dev_sw_netstats_rx_add(ndev, pkt_len);
+		dev_sw_netstats_rx_add(ndev, 1, pkt_len);
 		return AM65_CPSW_XDP_TX;
 	case XDP_REDIRECT:
 		if (unlikely(xdp_do_redirect(ndev, xdp, prog)))
 			goto drop;
 
-		dev_sw_netstats_rx_add(ndev, pkt_len);
+		dev_sw_netstats_rx_add(ndev, 1, pkt_len);
 		return AM65_CPSW_XDP_REDIRECT;
 	default:
 		bpf_warn_invalid_xdp_action(ndev, prog, act);
@@ -1358,7 +1358,7 @@ static int am65_cpsw_nuss_rx_packets(struct am65_cpsw_rx_flow *flow,
 	am65_cpsw_nuss_rx_csum(skb, csum_info);
 	napi_gro_receive(&flow->napi_rx, skb);
 
-	dev_sw_netstats_rx_add(ndev, pkt_len);
+	dev_sw_netstats_rx_add(ndev, 1, pkt_len);
 
 allocate:
 	new_page = page_pool_dev_alloc_pages(flow->page_pool);
diff --git a/drivers/net/ethernet/ti/icssg/icssg_common.c b/drivers/net/ethernet/ti/icssg/icssg_common.c
index 0cf9dfe0fa36..60cac4d0a936 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_common.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_common.c
@@ -811,14 +811,14 @@ static u32 emac_run_xdp(struct prueth_emac *emac, struct xdp_buff *xdp, u32 *len
 			goto drop;
 		}
 
-		dev_sw_netstats_rx_add(ndev, xdpf->len);
+		dev_sw_netstats_rx_add(ndev, 1, xdpf->len);
 		return result;
 	case XDP_REDIRECT:
 		err = xdp_do_redirect(emac->ndev, xdp, xdp_prog);
 		if (err)
 			goto drop;
 
-		dev_sw_netstats_rx_add(ndev, pkt_len);
+		dev_sw_netstats_rx_add(ndev, 1, pkt_len);
 		return ICSSG_XDP_REDIR;
 	default:
 		bpf_warn_invalid_xdp_action(emac->ndev, xdp_prog, act);
diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index e8949f556209..be1f884de218 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -335,7 +335,7 @@ static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb,
 
 	skb->dev = pctx->dev;
 
-	dev_sw_netstats_rx_add(pctx->dev, skb->len);
+	dev_sw_netstats_rx_add(pctx->dev, 1, skb->len);
 
 	__netif_rx(skb);
 	return 0;
diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c
index f6cad0746a02..807939aa8c64 100644
--- a/drivers/net/macsec.c
+++ b/drivers/net/macsec.c
@@ -836,7 +836,7 @@ static void macsec_finalize_skb(struct sk_buff *skb, u8 icv_len, u8 hdr_len)
 
 static void count_rx(struct net_device *dev, int len)
 {
-	dev_sw_netstats_rx_add(dev, len);
+	dev_sw_netstats_rx_add(dev, 1, len);
 }
 
 static void macsec_decrypt_done(void *data, int err)
diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
index 5c0e01396e06..993ea62336a3 100644
--- a/drivers/net/netkit.c
+++ b/drivers/net/netkit.c
@@ -107,7 +107,7 @@ static netdev_tx_t netkit_xmit(struct sk_buff *skb, struct net_device *dev)
 		skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
 		if (likely(__netif_rx(skb) == NET_RX_SUCCESS)) {
 			dev_sw_netstats_tx_add(dev, 1, len);
-			dev_sw_netstats_rx_add(peer, len);
+			dev_sw_netstats_rx_add(peer, 1, len);
 		} else {
 			goto drop_stats;
 		}
diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
index 6344c5eb0f98..c9f33b588fab 100644
--- a/drivers/net/ppp/ppp_generic.c
+++ b/drivers/net/ppp/ppp_generic.c
@@ -2507,7 +2507,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb)
 		break;
 	}
 
-	dev_sw_netstats_rx_add(ppp->dev, skb->len - PPP_PROTO_LEN);
+	dev_sw_netstats_rx_add(ppp->dev, 1, skb->len - PPP_PROTO_LEN);
 
 	npi = proto_to_npindex(proto);
 	if (npi < 0) {
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index c492fda6fc15..2d6547d0453d 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -1567,7 +1567,7 @@ static int tun_xdp_act(struct tun_struct *tun, struct bpf_prog *xdp_prog,
 			dev_core_stats_rx_dropped_inc(tun->dev);
 			return err;
 		}
-		dev_sw_netstats_rx_add(tun->dev, xdp->data_end - xdp->data);
+		dev_sw_netstats_rx_add(tun->dev, 1, xdp->data_end - xdp->data);
 		break;
 	case XDP_TX:
 		err = tun_xdp_tx(tun->dev, xdp);
@@ -1575,7 +1575,7 @@ static int tun_xdp_act(struct tun_struct *tun, struct bpf_prog *xdp_prog,
 			dev_core_stats_rx_dropped_inc(tun->dev);
 			return err;
 		}
-		dev_sw_netstats_rx_add(tun->dev, xdp->data_end - xdp->data);
+		dev_sw_netstats_rx_add(tun->dev, 1, xdp->data_end - xdp->data);
 		break;
 	case XDP_PASS:
 		break;
@@ -1957,7 +1957,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
 	rcu_read_unlock();
 
 	preempt_disable();
-	dev_sw_netstats_rx_add(tun->dev, len);
+	dev_sw_netstats_rx_add(tun->dev, 1, len);
 	preempt_enable();
 
 	if (rxhash)
@@ -2497,7 +2497,7 @@ static int tun_xdp_one(struct tun_struct *tun,
 	/* No need to disable preemption here since this function is
 	 * always called with bh disabled
 	 */
-	dev_sw_netstats_rx_add(tun->dev, datasize);
+	dev_sw_netstats_rx_add(tun->dev, 1, datasize);
 
 	if (rxhash)
 		tun_flow_update(tun, rxhash, tfile);
diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index 3a4985b582cb..bf3979a048a7 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -217,7 +217,7 @@ static int qmimux_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
 			net->stats.rx_errors++;
 			return 0;
 		} else {
-			dev_sw_netstats_rx_add(net, pkt_len);
+			dev_sw_netstats_rx_add(net, 1, pkt_len);
 		}
 
 skip:
diff --git a/drivers/net/wireguard/receive.c b/drivers/net/wireguard/receive.c
index eb8851113654..61486b57b6ce 100644
--- a/drivers/net/wireguard/receive.c
+++ b/drivers/net/wireguard/receive.c
@@ -19,7 +19,7 @@
 /* Must be called with bh disabled. */
 static void update_rx_stats(struct wg_peer *peer, size_t len)
 {
-	dev_sw_netstats_rx_add(peer->device->dev, len);
+	dev_sw_netstats_rx_add(peer->device->dev, 1, len);
 	peer->rx_bytes += len;
 }
 
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c
index c1a53e1ba3be..01ff00f7a447 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pearl_pcie.c
@@ -756,7 +756,7 @@ static int qtnf_pcie_pearl_rx_poll(struct napi_struct *napi, int budget)
 			skb_put(skb, psize);
 			ndev = qtnf_classify_skb(bus, skb);
 			if (likely(ndev)) {
-				dev_sw_netstats_rx_add(ndev, skb->len);
+				dev_sw_netstats_rx_add(ndev, 1, skb->len);
 				skb->protocol = eth_type_trans(skb, ndev);
 				napi_gro_receive(napi, skb);
 			} else {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
index ef5c069542d4..81367451e096 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
@@ -662,7 +662,7 @@ static int qtnf_topaz_rx_poll(struct napi_struct *napi, int budget)
 			skb_put(skb, psize);
 			ndev = qtnf_classify_skb(bus, skb);
 			if (likely(ndev)) {
-				dev_sw_netstats_rx_add(ndev, skb->len);
+				dev_sw_netstats_rx_add(ndev, 1, skb->len);
 				skb->protocol = eth_type_trans(skb, ndev);
 				netif_receive_skb(skb);
 			} else {
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 67e25f6d15a4..351d8c4950e5 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2992,13 +2992,15 @@ struct pcpu_lstats {
 
 void dev_lstats_read(struct net_device *dev, u64 *packets, u64 *bytes);
 
-static inline void dev_sw_netstats_rx_add(struct net_device *dev, unsigned int len)
+static inline void dev_sw_netstats_rx_add(struct net_device *dev,
+					  unsigned int packets,
+					  unsigned int len)
 {
 	struct pcpu_sw_netstats *tstats = this_cpu_ptr(dev->tstats);
 
 	u64_stats_update_begin(&tstats->syncp);
 	u64_stats_add(&tstats->rx_bytes, len);
-	u64_stats_inc(&tstats->rx_packets);
+	u64_stats_add(&tstats->rx_packets, packets);
 	u64_stats_update_end(&tstats->syncp);
 }
 
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 2cbae0f9ae1f..adfb1861a8dc 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -36,7 +36,7 @@ static int br_pass_frame_up(struct sk_buff *skb, bool promisc)
 	struct net_bridge *br = netdev_priv(brdev);
 	struct net_bridge_vlan_group *vg;
 
-	dev_sw_netstats_rx_add(brdev, skb->len);
+	dev_sw_netstats_rx_add(brdev, 1, skb->len);
 
 	vg = br_vlan_group_rcu(br);
 
diff --git a/net/core/filter.c b/net/core/filter.c
index a77d23fe2359..c3b99c19a22d 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -2526,7 +2526,7 @@ int skb_do_redirect(struct sk_buff *skb)
 			     net_eq(net, dev_net(dev))))
 			goto out_drop;
 		skb->dev = dev;
-		dev_sw_netstats_rx_add(dev, skb->len);
+		dev_sw_netstats_rx_add(dev, 1, skb->len);
 		skb_scrub_packet(skb, false);
 		return -EAGAIN;
 	}
diff --git a/net/dsa/tag.c b/net/dsa/tag.c
index 79ad105902d9..92455cb848ca 100644
--- a/net/dsa/tag.c
+++ b/net/dsa/tag.c
@@ -115,7 +115,7 @@ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
 		skb = nskb;
 	}
 
-	dev_sw_netstats_rx_add(skb->dev, skb->len + ETH_HLEN);
+	dev_sw_netstats_rx_add(skb->dev, 1, skb->len + ETH_HLEN);
 
 	if (dsa_skb_defer_rx_timestamp(p, skb))
 		return 0;
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 50d0f5fe4e4c..88ac5e5fd68c 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -432,7 +432,7 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
 		}
 	}
 
-	dev_sw_netstats_rx_add(tunnel->dev, skb->len);
+	dev_sw_netstats_rx_add(tunnel->dev, 1, skb->len);
 	skb_scrub_packet(skb, !net_eq(tunnel->net, dev_net(tunnel->dev)));
 
 	if (tunnel->dev->type == ARPHRD_ETHER) {
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index 95b6bb78fcd2..54a789ec201f 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -140,7 +140,7 @@ static int vti_rcv_cb(struct sk_buff *skb, int err)
 
 	skb_scrub_packet(skb, !net_eq(tunnel->net, dev_net(skb->dev)));
 	skb->dev = dev;
-	dev_sw_netstats_rx_add(dev, skb->len);
+	dev_sw_netstats_rx_add(dev, 1, skb->len);
 
 	return 0;
 }
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 4c29aa94e86e..90cb0e769a06 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -870,7 +870,7 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
 		}
 	}
 
-	dev_sw_netstats_rx_add(tunnel->dev, skb->len);
+	dev_sw_netstats_rx_add(tunnel->dev, 1, skb->len);
 
 	skb_scrub_packet(skb, !net_eq(tunnel->net, dev_net(tunnel->dev)));
 
diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c
index ad5290be4dd6..adfe05c16c5b 100644
--- a/net/ipv6/ip6_vti.c
+++ b/net/ipv6/ip6_vti.c
@@ -384,7 +384,7 @@ static int vti6_rcv_cb(struct sk_buff *skb, int err)
 
 	skb_scrub_packet(skb, !net_eq(t->net, dev_net(skb->dev)));
 	skb->dev = dev;
-	dev_sw_netstats_rx_add(dev, skb->len);
+	dev_sw_netstats_rx_add(dev, 1, skb->len);
 
 	return 0;
 }
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index ef2e5111fb3a..aaec43451b20 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -724,7 +724,7 @@ static int ipip6_rcv(struct sk_buff *skb)
 			}
 		}
 
-		dev_sw_netstats_rx_add(tunnel->dev, skb->len);
+		dev_sw_netstats_rx_add(tunnel->dev, 1, skb->len);
 
 		netif_rx(skb);
 
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 6c4b549444c6..424a1d393753 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -956,7 +956,7 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 			continue;
 
 		skb->dev = prev_sdata->dev;
-		dev_sw_netstats_rx_add(skb->dev, skb->len);
+		dev_sw_netstats_rx_add(skb->dev, 1, skb->len);
 		netif_receive_skb(skb);
 		prev_sdata = sdata;
 	}
@@ -970,7 +970,7 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 							 only_monitor);
 		if (skb) {
 			skb->dev = prev_sdata->dev;
-			dev_sw_netstats_rx_add(skb->dev, skb->len);
+			dev_sw_netstats_rx_add(skb->dev, 1, skb->len);
 			netif_receive_skb(skb);
 		}
 	}
@@ -2776,7 +2776,7 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx)
 	skb = rx->skb;
 	xmit_skb = NULL;
 
-	dev_sw_netstats_rx_add(dev, skb->len);
+	dev_sw_netstats_rx_add(dev, 1, skb->len);
 
 	if (rx->sta) {
 		/* The seqno index has the same property as needed
@@ -4878,7 +4878,7 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx,
 
 	skb->dev = fast_rx->dev;
 
-	dev_sw_netstats_rx_add(fast_rx->dev, skb->len);
+	dev_sw_netstats_rx_add(fast_rx->dev, 1, skb->len);
 
 	/* The seqno index has the same property as needed
 	 * for the rx_msdu field, i.e. it is IEEE80211_NUM_TIDS
diff --git a/net/openvswitch/vport-internal_dev.c b/net/openvswitch/vport-internal_dev.c
index 125d310871e9..f1c4ed1bad33 100644
--- a/net/openvswitch/vport-internal_dev.c
+++ b/net/openvswitch/vport-internal_dev.c
@@ -199,7 +199,7 @@ static int internal_dev_recv(struct sk_buff *skb)
 	skb->pkt_type = PACKET_HOST;
 	skb->protocol = eth_type_trans(skb, netdev);
 	skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
-	dev_sw_netstats_rx_add(netdev, skb->len);
+	dev_sw_netstats_rx_add(netdev, 1, skb->len);
 
 	netif_rx(skb);
 	return NETDEV_TX_OK;
diff --git a/net/xfrm/xfrm_interface_core.c b/net/xfrm/xfrm_interface_core.c
index 330a05286a56..f700adbe26a9 100644
--- a/net/xfrm/xfrm_interface_core.c
+++ b/net/xfrm/xfrm_interface_core.c
@@ -415,7 +415,7 @@ static int xfrmi_rcv_cb(struct sk_buff *skb, int err)
 		md_dst->u.xfrm_info.link = link;
 		skb_dst_set(skb, (struct dst_entry *)md_dst);
 	}
-	dev_sw_netstats_rx_add(dev, skb->len);
+	dev_sw_netstats_rx_add(dev, 1, skb->len);
 
 	return 0;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH nf-next 2/2] netfilter: flowtable: update netdev stats with HW_OFFLOAD flows
  2026-03-17 23:48 [PATCH nf-next 0/2] Update netdev stats with offloaded flows Ahmed Zaki
  2026-03-17 23:48 ` [PATCH nf-next 1/2] net: treewide: pass number of pkts to dev_sw_netstats_rx_add() Ahmed Zaki
@ 2026-03-17 23:48 ` Ahmed Zaki
  2026-03-18  0:32   ` bot+bpf-ci
  1 sibling, 1 reply; 5+ messages in thread
From: Ahmed Zaki @ 2026-03-17 23:48 UTC (permalink / raw)
  To: netfilter-devel, pablo, fw; +Cc: coreteam, netdev

SNMP-based network monitoring systems (and maybe other tools) rely on
netdev stats to report the network traffic. We currently do not update
the netdev stats with the offloaded flows stats which creates
discrepancies in the stats with some of these devices inside a bigger
network.

Update the nedev stats with the hardware offloaded flows' stats. The
stats are updated periodically in flow_offload_work_stats() and also
once in flow_offload_work_del() before the flow is deleted. For this,
flow_offload_work_del() had to be moved below flow_offload_tuple_stats()

Signed-off-by: Ahmed Zaki <anzaki@gmail.com>
---
 net/netfilter/nf_flow_table_offload.c | 59 ++++++++++++++++++++++++---
 1 file changed, 53 insertions(+), 6 deletions(-)

diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index b2e4fb6fa011..fb325d4a1131 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -925,13 +925,41 @@ static void flow_offload_work_add(struct flow_offload_work *offload)
 	nf_flow_offload_destroy(flow_rule);
 }
 
-static void flow_offload_work_del(struct flow_offload_work *offload)
+static void flow_offload_netdev_update(struct flow_offload_work *offload,
+				       struct flow_stats *stats)
 {
-	clear_bit(IPS_HW_OFFLOAD_BIT, &offload->flow->ct->status);
-	flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL);
-	if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags))
-		flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY);
-	set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags);
+	const struct flow_offload_tuple *tuple;
+	struct net_device *indev, *outdev;
+	struct net *net;
+
+	rcu_read_lock();
+	net = read_pnet(&offload->flowtable->net);
+	if (stats[0].pkts) {
+		tuple = &offload->flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple;
+		indev  = dev_get_by_index_rcu(net, tuple->iifidx);
+		if (indev)
+			dev_sw_netstats_rx_add(indev,
+					       stats[0].pkts, stats[0].bytes);
+
+		outdev = dev_get_by_index_rcu(net, tuple->out.ifidx);
+		if (outdev)
+			dev_sw_netstats_tx_add(outdev,
+					       stats[0].pkts, stats[0].bytes);
+	}
+
+	if (stats[1].pkts) {
+		tuple = &offload->flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple;
+		indev  = dev_get_by_index_rcu(net, tuple->iifidx);
+		if (indev)
+			dev_sw_netstats_rx_add(indev,
+					       stats[1].pkts, stats[1].bytes);
+
+		outdev = dev_get_by_index_rcu(net, tuple->out.ifidx);
+		if (outdev)
+			dev_sw_netstats_tx_add(outdev,
+					       stats[1].pkts, stats[1].bytes);
+	}
+	rcu_read_unlock();
 }
 
 static void flow_offload_tuple_stats(struct flow_offload_work *offload,
@@ -968,6 +996,25 @@ static void flow_offload_work_stats(struct flow_offload_work *offload)
 				       FLOW_OFFLOAD_DIR_REPLY,
 				       stats[1].pkts, stats[1].bytes);
 	}
+
+	flow_offload_netdev_update(offload, stats);
+}
+
+static void flow_offload_work_del(struct flow_offload_work *offload)
+{
+	struct flow_stats stats[FLOW_OFFLOAD_DIR_MAX] = {};
+
+	flow_offload_tuple_stats(offload, FLOW_OFFLOAD_DIR_ORIGINAL, &stats[0]);
+	if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags))
+		flow_offload_tuple_stats(offload, FLOW_OFFLOAD_DIR_REPLY,
+					 &stats[1]);
+	flow_offload_netdev_update(offload, stats);
+
+	clear_bit(IPS_HW_OFFLOAD_BIT, &offload->flow->ct->status);
+	flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL);
+	if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags))
+		flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY);
+	set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags);
 }
 
 static void flow_offload_work_handler(struct work_struct *work)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH nf-next 2/2] netfilter: flowtable: update netdev stats with HW_OFFLOAD flows
  2026-03-17 23:48 ` [PATCH nf-next 2/2] netfilter: flowtable: update netdev stats with HW_OFFLOAD flows Ahmed Zaki
@ 2026-03-18  0:32   ` bot+bpf-ci
  0 siblings, 0 replies; 5+ messages in thread
From: bot+bpf-ci @ 2026-03-18  0:32 UTC (permalink / raw)
  To: anzaki, netfilter-devel, pablo, fw
  Cc: coreteam, netdev, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3169 bytes --]

> diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
> index 9b677e116487..d340d37589bf 100644
> --- a/net/netfilter/nf_flow_table_offload.c
> +++ b/net/netfilter/nf_flow_table_offload.c

[ ... ]

> +static void flow_offload_netdev_update(struct flow_offload_work *offload,
> +				       struct flow_stats *stats)
> +{
> +	const struct flow_offload_tuple *tuple;
> +	struct net_device *indev, *outdev;
> +	struct net *net;
> +
> +	rcu_read_lock();
> +	net = read_pnet(&offload->flowtable->net);
> +	if (stats[0].pkts) {
> +		tuple = &offload->flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple;
> +		indev  = dev_get_by_index_rcu(net, tuple->iifidx);
> +		if (indev)
> +			dev_sw_netstats_rx_add(indev,
> +					       stats[0].pkts, stats[0].bytes);

dev_sw_netstats_rx_add() and dev_sw_netstats_tx_add() both do
this_cpu_ptr(dev->tstats) internally to update per-cpu software
stats. Can this corrupt memory when the device does not have tstats
allocated?

Drivers that support flowtable hw offload (TC_SETUP_FT) are typically
physical NICs such as mlx5, which do not set pcpu_stat_type and do
not allocate dev->tstats. When dev->tstats is NULL,
this_cpu_ptr(NULL) computes the per-cpu base address rather than
faulting, and the subsequent writes would silently corrupt whatever
per-cpu data lives at offset zero.

All existing callers of dev_sw_netstats_rx_add() call it on device
types known to have tstats (bridges, tunnels, tun/tap, etc.), not
on devices obtained from arbitrary dev_get_by_index_rcu() lookups.

> +
> +		outdev = dev_get_by_index_rcu(net, tuple->out.ifidx);
> +		if (outdev)
> +			dev_sw_netstats_tx_add(outdev,
> +					       stats[0].pkts, stats[0].bytes);

For flows with xmit_type FLOW_OFFLOAD_XMIT_NEIGH (the common L3
routing case), does tuple->out.ifidx return the correct output
interface index?

Looking at the flow_offload_tuple union layout in
nf_flow_table.h and how flow_offload_fill_route() populates it:

For FLOW_OFFLOAD_XMIT_DIRECT:
    flow_tuple->out.ifidx = route->tuple[dir].out.ifindex;

For FLOW_OFFLOAD_XMIT_NEIGH:
    flow_tuple->dst_cache = dst;
    flow_tuple->ifidx = route->tuple[dir].out.ifindex;

The anonymous struct member ifidx sits at a different offset than
out.ifidx in the union -- dst_cache (a pointer, 8 bytes on 64-bit)
occupies offset 0, and the anonymous ifidx follows at offset 8.
Meanwhile out.ifidx is at offset 0.

So for NEIGH flows, reading tuple->out.ifidx actually reads the
low bytes of the dst_cache pointer rather than the output interface
index. dev_get_by_index_rcu() would return NULL for this bogus
value, and outdev tx stats would be silently skipped.

The existing software path in nf_flow_offload_ip_hook() handles
this correctly by checking tuple->xmit_type and using
tuple->out.ifidx only for DIRECT and tuple->ifidx (anonymous
member) only for NEIGH.

> +	}

[ ... ]

> +	rcu_read_unlock();
>  }

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/23222395400

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH nf-next 1/2] net: treewide: pass number of pkts to dev_sw_netstats_rx_add()
  2026-03-17 23:48 ` [PATCH nf-next 1/2] net: treewide: pass number of pkts to dev_sw_netstats_rx_add() Ahmed Zaki
@ 2026-03-19  2:12   ` Jakub Kicinski
  0 siblings, 0 replies; 5+ messages in thread
From: Jakub Kicinski @ 2026-03-19  2:12 UTC (permalink / raw)
  To: Ahmed Zaki; +Cc: netfilter-devel, pablo, fw, coreteam, netdev

On Tue, 17 Mar 2026 17:48:50 -0600 Ahmed Zaki wrote:
> Allow callers of dev_sw_netstats_rx_add() to update the netdev stats
> with multiple packets.

Please don't. If you have to change almost 30 users to add one you're
probably doing something wrong with the interface.

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-03-19  2:12 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-17 23:48 [PATCH nf-next 0/2] Update netdev stats with offloaded flows Ahmed Zaki
2026-03-17 23:48 ` [PATCH nf-next 1/2] net: treewide: pass number of pkts to dev_sw_netstats_rx_add() Ahmed Zaki
2026-03-19  2:12   ` Jakub Kicinski
2026-03-17 23:48 ` [PATCH nf-next 2/2] netfilter: flowtable: update netdev stats with HW_OFFLOAD flows Ahmed Zaki
2026-03-18  0:32   ` bot+bpf-ci

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox