* [PATCH] net/af_packet: add capture direction option
@ 2026-10-04 4:22 Frank Dressler
2026-10-04 16:10 ` Stephen Hemminger
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
0 siblings, 2 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-04 4:22 UTC (permalink / raw)
To: dev; +Cc: Frank Dressler, Thomas Monjalon, Stephen Hemminger
By default, AF_PACKET sockets capture both incoming and outgoing
packets. This leads to unexpected behavior in the AF_PACKET PMD
since other PMDs only return actually received packets in
rte_eth_rx_burst() calls.
This patch adds an option capture_dir=<in|out|inout> that controls
which packets are received, similar to tcpdump's -Q option.
The PMD never sees its own TX on RX, but TX from other sources on
the same netdev is still seen. Use inout for tcpdump-like tools and
in for applications that send and receive.
The default is "inout" so that existing software keeps working as
before.
The filter checks the packet type in the struct sockaddr_ll that
comes with each packet. Each TPACKET_V2 packet is laid out as
struct tpacket2_hdr | struct sockaddr_ll | packet data.
The kernel sets sockaddr_ll::sll_pkttype to skb->pkt_type, which is
PACKET_OUTGOING for outgoing packets (set by dev_queue_xmit_nit())
and a different value otherwise. Libpcap's linux_check_direction()
uses the same check.
A unit test injects OUTGOING traffic from a second port on the same
TAP and checks in/out/inout filtering.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 101 ++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 2 +
doc/guides/rel_notes/release_26_11.rst | 4 +
drivers/net/af_packet/rte_eth_af_packet.c | 59 ++++++++++++-
5 files changed, 166 insertions(+), 1 deletion(-)
diff --git a/.mailmap b/.mailmap
index 2e348c3bce..a04fc553d4 100644
--- a/.mailmap
+++ b/.mailmap
@@ -505,6 +505,7 @@ Francis Kelly <fkelly@nvidia.com> <fkelly@mellanox.com>
Francis Racicot <francis.racicot@intel.com>
Franck Lenormand <franck.lenormand@nxp.com>
François-Frédéric Ozog <ff@ozog.com>
+Frank Dressler <frank@dressler.pro>
Frank Du <frank.du@intel.com>
Frank Zhao <frank.zhao@starfivetech.com>
Frederico Cadete <frederico.cadete-ext@oneaccess-net.com>
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index b668ca5b81..8c8c8f6612 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -913,6 +913,106 @@ test_af_packet_qdisc_bypass(void)
return TEST_SUCCESS;
}
+/*
+ * Test: capture_dir in/out/inout.
+ * Send packets; "in" must receive none, "out" and "inout" must receive them.
+ */
+static int
+test_af_packet_capture_dir(void)
+{
+ static const char * const modes[] = {"in", "out", "inout"};
+ static const char * const names[] = {
+ "net_af_packet_cap_in",
+ "net_af_packet_cap_out",
+ "net_af_packet_cap_inout",
+ };
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint16_t tx_port, rx_ports[RTE_DIM(modes)], nb_tx;
+ unsigned int rx[RTE_DIM(modes)] = {0};
+ unsigned int m, i, n_rx = 0, allocated;
+ uint64_t elapsed = 0;
+ const char *err = NULL;
+ char args[128];
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ /* qdisc_bypass=0 so the kernel TX tap sees the TX packets */
+ ret = create_af_packet_port("net_af_packet_cap_tx",
+ "iface=" TAP_DEV_NAME ",qdisc_bypass=0",
+ &tx_port);
+ TEST_ASSERT(ret == 0, "Failed to create TX af_packet port");
+ ret = configure_af_packet_port(tx_port, 1, 1);
+ if (ret != 0) {
+ err = "Failed to configure TX af_packet port";
+ goto out;
+ }
+
+ /* Create all three capture_dir variants: in, out, and inout */
+ for (m = 0; m < RTE_DIM(modes); m++) {
+ snprintf(args, sizeof(args), "iface=%s,capture_dir=%s",
+ TAP_DEV_NAME, modes[m]);
+ ret = create_af_packet_port(names[m], args, &rx_ports[m]);
+ if (ret != 0) {
+ err = "Failed to create capture_dir port";
+ goto out;
+ }
+ n_rx++;
+ ret = configure_af_packet_port(rx_ports[m], 1, 1);
+ if (ret != 0) {
+ err = "Failed to configure capture_dir port";
+ goto out;
+ }
+ }
+
+ /* Drain stale packets */
+ for (m = 0; m < n_rx; m++)
+ while (do_rx_burst(rx_ports[m], 0, bufs, BURST_SIZE) > 0)
+ ;
+
+ /* Inject packets */
+ allocated = alloc_tx_mbufs(bufs, 4);
+ nb_tx = do_tx_burst(tx_port, 0, bufs, allocated);
+ if (allocated == 0 || nb_tx == 0) {
+ err = "TX setup failed";
+ goto out;
+ }
+
+ while (elapsed < LOOPBACK_TIMEOUT_US) {
+ for (m = 0; m < n_rx; m++)
+ rx[m] += do_rx_burst(rx_ports[m], 0, bufs, BURST_SIZE);
+ if (rx[1] >= nb_tx && rx[2] >= nb_tx)
+ break;
+ rte_delay_us_block(STATS_POLL_INTERVAL_US);
+ elapsed += STATS_POLL_INTERVAL_US;
+ }
+
+out:
+ for (i = 0; i < n_rx; i++) {
+ rte_eth_dev_stop(rx_ports[i]);
+ rte_eth_dev_close(rx_ports[i]);
+ rte_vdev_uninit(names[i]);
+ }
+ rte_eth_dev_stop(tx_port);
+ rte_eth_dev_close(tx_port);
+ rte_vdev_uninit("net_af_packet_cap_tx");
+
+ TEST_ASSERT(err == NULL, "%s", err);
+
+ TEST_ASSERT(rx[0] == 0, "Expected no packets with capture_dir=in");
+ TEST_ASSERT(rx[1] > 0, "Expected packets with capture_dir=out");
+ TEST_ASSERT(rx[2] > 0, "Expected packets with capture_dir=inout");
+
+ ret = rte_vdev_init("net_af_packet_cap_bad",
+ "iface=" TAP_DEV_NAME ",capture_dir=bogus");
+ TEST_ASSERT(ret != 0, "Expected failure with capture_dir=bogus");
+
+ return TEST_SUCCESS;
+}
+
/*
* Test: Multiple queue pairs
*/
@@ -1107,6 +1207,7 @@ static struct unit_test_suite af_packet_test_suite = {
TEST_CASE(test_af_packet_invalid_qpairs),
TEST_CASE(test_af_packet_frame_config),
TEST_CASE(test_af_packet_qdisc_bypass),
+ TEST_CASE(test_af_packet_capture_dir),
TEST_CASE(test_af_packet_multi_queue),
TEST_CASES_END() /**< NULL terminate unit test array */
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 1505b98ff7..a10927e6e6 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -25,6 +25,8 @@ Some of these, in turn, will be used to configure the PACKET_MMAP settings.
disabled by default);
* ``fanout_mode`` - set fanout algorithm.
Possible choices: hash, lb, cpu, rollover, rnd, qm (optional, default hash);
+* ``capture_dir`` - select which packet directions to receive.
+ Possible choices: in, out, inout (optional, default inout);
* ``blocksz`` - PACKET_MMAP block size (optional, default 4096);
* ``framesz`` - PACKET_MMAP frame size (optional, default 2048B; Note: multiple
of 16B);
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 030bd84cea..769f55337c 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,10 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Updated af_packet net driver.**
+
+ * Added ``capture_dir`` option to select ingress, egress, or both.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index a93df97023..88cdca61f9 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -39,10 +39,18 @@
#define ETH_AF_PACKET_FRAMECOUNT_ARG "framecnt"
#define ETH_AF_PACKET_QDISC_BYPASS_ARG "qdisc_bypass"
#define ETH_AF_PACKET_FANOUT_MODE_ARG "fanout_mode"
+#define ETH_AF_PACKET_CAPTURE_DIR_ARG "capture_dir"
#define DFLT_FRAME_SIZE (1 << 11)
#define DFLT_FRAME_COUNT (1 << 9)
+enum rte_af_packet_capture_dir {
+ RTE_AF_PACKET_CAPTURE_DIR_INVALID = -1,
+ RTE_AF_PACKET_CAPTURE_DIR_IN,
+ RTE_AF_PACKET_CAPTURE_DIR_OUT,
+ RTE_AF_PACKET_CAPTURE_DIR_INOUT,
+};
+
static uint64_t timestamp_dynflag;
static int timestamp_dynfield_offset = -1;
@@ -59,6 +67,7 @@ struct __rte_cache_aligned pkt_rx_queue {
uint8_t vlan_strip;
uint8_t timestamp_offloading;
uint8_t scatter_enabled;
+ uint8_t capture_dir;
volatile unsigned long rx_pkts;
volatile unsigned long rx_bytes;
@@ -103,6 +112,7 @@ static const char *valid_arguments[] = {
ETH_AF_PACKET_FRAMECOUNT_ARG,
ETH_AF_PACKET_QDISC_BYPASS_ARG,
ETH_AF_PACKET_FANOUT_MODE_ARG,
+ ETH_AF_PACKET_CAPTURE_DIR_ARG,
NULL
};
@@ -166,6 +176,7 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
{
unsigned i;
struct tpacket2_hdr *ppd;
+ struct sockaddr_ll *sll;
struct rte_mbuf *mbuf;
uint8_t *pbuf;
struct pkt_rx_queue *pkt_q = queue;
@@ -189,6 +200,18 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
if ((ppd->tp_status & TP_STATUS_USER) == 0)
break;
+ /* drop frames that do not match capture_dir */
+ if (pkt_q->capture_dir != RTE_AF_PACKET_CAPTURE_DIR_INOUT) {
+ sll = (struct sockaddr_ll *)((char *)ppd + TPACKET_ALIGN(sizeof(*ppd)));
+ if (sll->sll_pkttype == PACKET_OUTGOING) {
+ if (pkt_q->capture_dir == RTE_AF_PACKET_CAPTURE_DIR_IN)
+ goto release_frame;
+ } else {
+ if (pkt_q->capture_dir == RTE_AF_PACKET_CAPTURE_DIR_OUT)
+ goto release_frame;
+ }
+ }
+
/* allocate the next mbuf */
mbuf = rte_pktmbuf_alloc(pkt_q->mb_pool);
if (unlikely(mbuf == NULL)) {
@@ -867,6 +890,20 @@ get_fanout(const char *fanout_mode, int if_index)
return PACKET_FANOUT_INVALID;
}
+static enum rte_af_packet_capture_dir
+get_capture_dir(const char *capture_dir)
+{
+ if (!capture_dir)
+ return RTE_AF_PACKET_CAPTURE_DIR_INOUT;
+ if (!strcmp(capture_dir, "in"))
+ return RTE_AF_PACKET_CAPTURE_DIR_IN;
+ if (!strcmp(capture_dir, "out"))
+ return RTE_AF_PACKET_CAPTURE_DIR_OUT;
+ if (!strcmp(capture_dir, "inout"))
+ return RTE_AF_PACKET_CAPTURE_DIR_INOUT;
+ return RTE_AF_PACKET_CAPTURE_DIR_INVALID;
+}
+
static int
rte_pmd_init_internals(struct rte_vdev_device *dev,
const int sockfd,
@@ -877,6 +914,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
unsigned int framecnt,
unsigned int qdisc_bypass,
const char *fanout_mode,
+ const char *capture_dir,
struct pmd_internals **internals,
struct rte_eth_dev **eth_dev,
struct rte_kvargs *kvlist)
@@ -896,6 +934,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
int qsockfd = -1;
unsigned int i, q, rdsize;
int fanout_arg;
+ enum rte_af_packet_capture_dir capture_arg;
for (k_idx = 0; k_idx < kvlist->count; k_idx++) {
pair = &kvlist->pairs[k_idx];
@@ -982,6 +1021,12 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
goto error;
}
+ capture_arg = get_capture_dir(capture_dir);
+ if (capture_arg == RTE_AF_PACKET_CAPTURE_DIR_INVALID) {
+ PMD_LOG(ERR, "Invalid capture_dir: %s", capture_dir);
+ goto error;
+ }
+
for (q = 0; q < nb_queues; q++) {
/* Open an AF_PACKET socket for this queue... */
qsockfd = socket(AF_PACKET, SOCK_RAW, 0);
@@ -1043,6 +1088,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
+ rx_queue->capture_dir = capture_arg;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
@@ -1206,6 +1252,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
unsigned int qpairs = 1;
unsigned int qdisc_bypass = 1;
const char *fanout_mode = NULL;
+ const char *capture_dir = NULL;
/* do some parameter checking */
if (*sockfd < 0)
@@ -1272,6 +1319,10 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
fanout_mode = pair->value;
continue;
}
+ if (strstr(pair->key, ETH_AF_PACKET_CAPTURE_DIR_ARG) != NULL) {
+ capture_dir = pair->value;
+ continue;
+ }
}
if (framesize > blocksize) {
@@ -1298,12 +1349,17 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, fanout_mode);
else
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, "default PACKET_FANOUT_HASH");
+ if (capture_dir)
+ PMD_LOG(DEBUG, "%s:\tcapture_dir %s", name, capture_dir);
+ else
+ PMD_LOG(DEBUG, "%s:\tcapture_dir %s", name, "default inout");
if (rte_pmd_init_internals(dev, *sockfd, qpairs,
blocksize, blockcount,
framesize, framecount,
qdisc_bypass,
fanout_mode,
+ capture_dir,
&internals, ð_dev,
kvlist) < 0)
return -1;
@@ -1401,4 +1457,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,
"framesz=<int> "
"framecnt=<int> "
"qdisc_bypass=<0|1> "
- "fanout_mode=<hash|lb|cpu|rollover|rnd|qm>");
+ "fanout_mode=<hash|lb|cpu|rollover|rnd|qm> "
+ "capture_dir=<in|out|inout>");
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH] net/af_packet: add capture direction option
2026-10-04 4:22 [PATCH] net/af_packet: add capture direction option Frank Dressler
@ 2026-10-04 16:10 ` Stephen Hemminger
2026-10-05 0:26 ` Frank Dressler
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
1 sibling, 1 reply; 23+ messages in thread
From: Stephen Hemminger @ 2026-10-04 16:10 UTC (permalink / raw)
To: Frank Dressler; +Cc: dev, Thomas Monjalon
On Sun, 4 Oct 2026 05:22:54 +0100
Frank Dressler <frank@dressler.pro> wrote:
> By default, AF_PACKET sockets capture both incoming and outgoing
> packets. This leads to unexpected behavior in the AF_PACKET PMD
> since other PMDs only return actually received packets in
> rte_eth_rx_burst() calls.
>
> This patch adds an option capture_dir=<in|out|inout> that controls
> which packets are received, similar to tcpdump's -Q option.
>
> The PMD never sees its own TX on RX, but TX from other sources on
> the same netdev is still seen. Use inout for tcpdump-like tools and
> in for applications that send and receive.
>
> The default is "inout" so that existing software keeps working as
> before.
>
> The filter checks the packet type in the struct sockaddr_ll that
> comes with each packet. Each TPACKET_V2 packet is laid out as
> struct tpacket2_hdr | struct sockaddr_ll | packet data.
> The kernel sets sockaddr_ll::sll_pkttype to skb->pkt_type, which is
> PACKET_OUTGOING for outgoing packets (set by dev_queue_xmit_nit())
> and a different value otherwise. Libpcap's linux_check_direction()
> uses the same check.
>
> A unit test injects OUTGOING traffic from a second port on the same
> TAP and checks in/out/inout filtering.
>
> Signed-off-by: Frank Dressler <frank@dressler.pro>
The AF_PACKET PMD is special case really intended for monitoring
applications, not for general use. You are probably better off using AF_XDP
for general use.
The kernel does have flags to filter by direction, so you could
avoid having it be done in the PMD.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH] net/af_packet: add capture direction option
2026-10-04 16:10 ` Stephen Hemminger
@ 2026-10-05 0:26 ` Frank Dressler
0 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-05 0:26 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, Thomas Monjalon
On 10/4/26 17:10, Stephen Hemminger wrote:
> The AF_PACKET PMD is special case really intended for monitoring
> applications, not for general use. You are probably better off using
AF_XDP
> for general use.
I agree there are cases in which AF_XDP is better, but it redirects
packets from the host stack and needs bpf()/XDP. The AF_PACKET approach
allows for passive monitoring without taking the traffic and works even
when there is already an XDP program attached or bpf() not allowed.
> The kernel does have flags to filter by direction, so you could
> avoid having it be done in the PMD.
Thanks for the hint. It made me find the PACKET_IGNORE_OUTGOING sockopt
(Linux 4.20+, fa788d986a3a). v2 drops the capture_dir=<in|out|inout> in
favor of ignore_outgoing=<0|1>.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2] net/af_packet: add option to ignore outgoing packets
2026-10-04 4:22 [PATCH] net/af_packet: add capture direction option Frank Dressler
2026-10-04 16:10 ` Stephen Hemminger
@ 2026-10-05 0:28 ` Frank Dressler
2026-10-05 16:41 ` Stephen Hemminger
` (3 more replies)
1 sibling, 4 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-05 0:28 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Stephen Hemminger
By default, AF_PACKET delivers both incoming and outgoing packets.
This might surprise applications that expect rte_eth_rx_burst() to
return only incoming traffic.
This patch adds the option ignore_outgoing=<0|1> to enable
PACKET_IGNORE_OUTGOING (Linux 4.20+). When set, the kernel drops
outgoing frames. The default is 0 to keep the existing behavior.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
v2:
- use PACKET_IGNORE_OUTGOING / ignore_outgoing= instead of capture_dir
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 97 +++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 3 +
doc/guides/rel_notes/release_26_11.rst | 6 ++
drivers/net/af_packet/rte_eth_af_packet.c | 27 ++++++-
5 files changed, 133 insertions(+), 1 deletion(-)
diff --git a/.mailmap b/.mailmap
index 2e348c3bce..a04fc553d4 100644
--- a/.mailmap
+++ b/.mailmap
@@ -505,6 +505,7 @@ Francis Kelly <fkelly@nvidia.com> <fkelly@mellanox.com>
Francis Racicot <francis.racicot@intel.com>
Franck Lenormand <franck.lenormand@nxp.com>
François-Frédéric Ozog <ff@ozog.com>
+Frank Dressler <frank@dressler.pro>
Frank Du <frank.du@intel.com>
Frank Zhao <frank.zhao@starfivetech.com>
Frederico Cadete <frederico.cadete-ext@oneaccess-net.com>
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index b668ca5b81..ee4a54d86a 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -913,6 +913,102 @@ test_af_packet_qdisc_bypass(void)
return TEST_SUCCESS;
}
+/*
+ * Test: Ignore outgoing packets configuration
+ * TX on the TAP with qdisc_bypass=0 produces PACKET_OUTGOING frames.
+ * A peer with ignore_outgoing=0 should see them; ignore_outgoing=1 must not.
+ */
+static int
+test_af_packet_ignore_outgoing(void)
+{
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint16_t tx_port, rx_port_ign_out_off, rx_port_ign_out_on, nb_tx;
+ unsigned int rx_off = 0, rx_on = 0, allocated;
+ uint64_t elapsed = 0;
+ const char *err = NULL;
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ ret = create_af_packet_port("net_af_packet_ign_on",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=1",
+ &rx_port_ign_out_on);
+ if (ret != 0) {
+ printf("SKIPPED: ignore_outgoing may not be supported\n");
+ return TEST_SKIPPED;
+ }
+
+ ret = create_af_packet_port("net_af_packet_ign_off",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=0",
+ &rx_port_ign_out_off);
+ if (ret != 0) {
+ err = "Failed to create ignore_outgoing=0 port";
+ goto out_rx_port_ign_out_on;
+ }
+
+ /* qdisc_bypass=0 so the kernel TX tap sees the TX packets */
+ ret = create_af_packet_port("net_af_packet_ign_tx",
+ "iface=" TAP_DEV_NAME ",qdisc_bypass=0", &tx_port);
+ if (ret != 0) {
+ err = "Failed to create TX af_packet port";
+ goto out_rx_port_ign_out_off;
+ }
+
+ if (configure_af_packet_port(rx_port_ign_out_on, 1, 1) != 0 ||
+ configure_af_packet_port(rx_port_ign_out_off, 1, 1) != 0 ||
+ configure_af_packet_port(tx_port, 1, 1) != 0) {
+ err = "Failed to configure ports";
+ goto out;
+ }
+
+ while (do_rx_burst(rx_port_ign_out_off, 0, bufs, BURST_SIZE) > 0)
+ ;
+ while (do_rx_burst(rx_port_ign_out_on, 0, bufs, BURST_SIZE) > 0)
+ ;
+
+ allocated = alloc_tx_mbufs(bufs, 4);
+ nb_tx = do_tx_burst(tx_port, 0, bufs, allocated);
+ if (allocated == 0 || nb_tx == 0) {
+ err = "TX setup failed";
+ goto out;
+ }
+
+ while (elapsed < LOOPBACK_TIMEOUT_US) {
+ rx_off += do_rx_burst(rx_port_ign_out_off, 0, bufs, BURST_SIZE);
+ rx_on += do_rx_burst(rx_port_ign_out_on, 0, bufs, BURST_SIZE);
+ if (rx_off >= nb_tx)
+ break;
+ rte_delay_us_block(STATS_POLL_INTERVAL_US);
+ elapsed += STATS_POLL_INTERVAL_US;
+ }
+
+out:
+ rte_eth_dev_stop(tx_port);
+ rte_eth_dev_close(tx_port);
+ rte_vdev_uninit("net_af_packet_ign_tx");
+out_rx_port_ign_out_off:
+ rte_eth_dev_stop(rx_port_ign_out_off);
+ rte_eth_dev_close(rx_port_ign_out_off);
+ rte_vdev_uninit("net_af_packet_ign_off");
+out_rx_port_ign_out_on:
+ rte_eth_dev_stop(rx_port_ign_out_on);
+ rte_eth_dev_close(rx_port_ign_out_on);
+ rte_vdev_uninit("net_af_packet_ign_on");
+
+ TEST_ASSERT(err == NULL, "%s", err);
+ TEST_ASSERT(rx_off > 0, "Expected packets with ignore_outgoing=0");
+ TEST_ASSERT(rx_on == 0, "Expected no packets with ignore_outgoing=1");
+
+ ret = rte_vdev_init("net_af_packet_ign_bad",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=2");
+ TEST_ASSERT(ret != 0, "Expected failure with ignore_outgoing=2");
+
+ return TEST_SUCCESS;
+}
+
/*
* Test: Multiple queue pairs
*/
@@ -1107,6 +1203,7 @@ static struct unit_test_suite af_packet_test_suite = {
TEST_CASE(test_af_packet_invalid_qpairs),
TEST_CASE(test_af_packet_frame_config),
TEST_CASE(test_af_packet_qdisc_bypass),
+ TEST_CASE(test_af_packet_ignore_outgoing),
TEST_CASE(test_af_packet_multi_queue),
TEST_CASES_END() /**< NULL terminate unit test array */
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 1505b98ff7..d1c04908dd 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -25,6 +25,9 @@ Some of these, in turn, will be used to configure the PACKET_MMAP settings.
disabled by default);
* ``fanout_mode`` - set fanout algorithm.
Possible choices: hash, lb, cpu, rollover, rnd, qm (optional, default hash);
+* ``ignore_outgoing`` - set PACKET_IGNORE_OUTGOING so the socket does not
+ receive packets transmitted by the host on the same interface (optional,
+ default 0; requires Linux kernel >= 4.20);
* ``blocksz`` - PACKET_MMAP block size (optional, default 4096);
* ``framesz`` - PACKET_MMAP frame size (optional, default 2048B; Note: multiple
of 16B);
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 030bd84cea..a7421cf9d4 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,12 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Updated AF_PACKET driver.**
+
+ Added ``ignore_outgoing`` vdev argument to enable ``PACKET_IGNORE_OUTGOING``,
+ so the PMD does not receive packets transmitted by the host on the same
+ interface. Requires Linux kernel >= 4.20.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index a93df97023..efb32ad8a6 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -39,6 +39,7 @@
#define ETH_AF_PACKET_FRAMECOUNT_ARG "framecnt"
#define ETH_AF_PACKET_QDISC_BYPASS_ARG "qdisc_bypass"
#define ETH_AF_PACKET_FANOUT_MODE_ARG "fanout_mode"
+#define ETH_AF_PACKET_IGNORE_OUTGOING_ARG "ignore_outgoing"
#define DFLT_FRAME_SIZE (1 << 11)
#define DFLT_FRAME_COUNT (1 << 9)
@@ -103,6 +104,7 @@ static const char *valid_arguments[] = {
ETH_AF_PACKET_FRAMECOUNT_ARG,
ETH_AF_PACKET_QDISC_BYPASS_ARG,
ETH_AF_PACKET_FANOUT_MODE_ARG,
+ ETH_AF_PACKET_IGNORE_OUTGOING_ARG,
NULL
};
@@ -877,6 +879,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
unsigned int framecnt,
unsigned int qdisc_bypass,
const char *fanout_mode,
+ unsigned int ignore_outgoing,
struct pmd_internals **internals,
struct rte_eth_dev **eth_dev,
struct rte_kvargs *kvlist)
@@ -1025,6 +1028,19 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
#endif
}
+ if (ignore_outgoing) {
+#if defined(PACKET_IGNORE_OUTGOING)
+ rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
+ &ignore_outgoing, sizeof(ignore_outgoing));
+ if (rc == -1) {
+ PMD_LOG_ERRNO(ERR,
+ "%s: could not set PACKET_IGNORE_OUTGOING on AF_PACKET socket for %s",
+ name, pair->value);
+ goto error;
+ }
+#endif
+ }
+
rc = setsockopt(qsockfd, SOL_PACKET, PACKET_RX_RING, req, sizeof(*req));
if (rc == -1) {
PMD_LOG_ERRNO(ERR,
@@ -1206,6 +1222,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
unsigned int qpairs = 1;
unsigned int qdisc_bypass = 1;
const char *fanout_mode = NULL;
+ unsigned int ignore_outgoing = 0;
/* do some parameter checking */
if (*sockfd < 0)
@@ -1272,6 +1289,11 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
fanout_mode = pair->value;
continue;
}
+ if (strstr(pair->key, ETH_AF_PACKET_IGNORE_OUTGOING_ARG) != NULL) {
+ if (parse_uint(pair->key, pair->value, &ignore_outgoing, 1) < 0)
+ return -1;
+ continue;
+ }
}
if (framesize > blocksize) {
@@ -1298,12 +1320,14 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, fanout_mode);
else
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, "default PACKET_FANOUT_HASH");
+ PMD_LOG(DEBUG, "%s:\tignore outgoing %d", name, ignore_outgoing);
if (rte_pmd_init_internals(dev, *sockfd, qpairs,
blocksize, blockcount,
framesize, framecount,
qdisc_bypass,
fanout_mode,
+ ignore_outgoing,
&internals, ð_dev,
kvlist) < 0)
return -1;
@@ -1401,4 +1425,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,
"framesz=<int> "
"framecnt=<int> "
"qdisc_bypass=<0|1> "
- "fanout_mode=<hash|lb|cpu|rollover|rnd|qm>");
+ "fanout_mode=<hash|lb|cpu|rollover|rnd|qm> "
+ "ignore_outgoing=<0|1>");
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v2] net/af_packet: add option to ignore outgoing packets
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
@ 2026-10-05 16:41 ` Stephen Hemminger
2026-10-06 6:42 ` [PATCH v3] " Frank Dressler
` (2 subsequent siblings)
3 siblings, 0 replies; 23+ messages in thread
From: Stephen Hemminger @ 2026-10-05 16:41 UTC (permalink / raw)
To: Frank Dressler; +Cc: dev, Thomas Monjalon
On Mon, 5 Oct 2026 01:28:09 +0100
Frank Dressler <frank@dressler.pro> wrote:
>
> + if (ignore_outgoing) {
> +#if defined(PACKET_IGNORE_OUTGOING)
> + rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
> + &ignore_outgoing, sizeof(ignore_outgoing));
> + if (rc == -1) {
> + PMD_LOG_ERRNO(ERR,
> + "%s: could not set PACKET_IGNORE_OUTGOING on AF_PACKET socket for %s",
> + name, pair->value);
> + goto error;
> + }
> +#endif
> + }
> +
Since PACKET_IGNORE_OUTGOING has been in kernel uapi since 4.19 the #ifdef here is not needed.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3] net/af_packet: add option to ignore outgoing packets
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
2026-10-05 16:41 ` Stephen Hemminger
@ 2026-10-06 6:42 ` Frank Dressler
2026-10-06 13:41 ` Stephen Hemminger
2026-10-09 15:02 ` [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
3 siblings, 1 reply; 23+ messages in thread
From: Frank Dressler @ 2026-10-06 6:42 UTC (permalink / raw)
To: Thomas Monjalon, Stephen Hemminger; +Cc: dev
By default, AF_PACKET delivers both incoming and outgoing packets.
This might surprise applications that expect rte_eth_rx_burst() to
return only incoming traffic.
This patch adds the devarg ignore_outgoing=<0|1> to enable
PACKET_IGNORE_OUTGOING. When set, the kernel drops outgoing frames.
The default is 0 to keep the existing behavior.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
v3:
- drop #ifdef PACKET_IGNORE_OUTGOING (uapi since 4.20; DPDK requires kernel >= 5.4)
- fail the test if ignore_outgoing=1 cannot create a port
- drop kernel >= 4.20 notes from docs and release notes
v2:
- use PACKET_IGNORE_OUTGOING / ignore_outgoing= instead of capture_dir
---
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 97 +++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 3 +
doc/guides/rel_notes/release_26_11.rst | 6 ++
drivers/net/af_packet/rte_eth_af_packet.c | 25 +++++-
5 files changed, 131 insertions(+), 1 deletion(-)
diff --git a/.mailmap b/.mailmap
index 2e348c3bce..a04fc553d4 100644
--- a/.mailmap
+++ b/.mailmap
@@ -505,6 +505,7 @@ Francis Kelly <fkelly@nvidia.com> <fkelly@mellanox.com>
Francis Racicot <francis.racicot@intel.com>
Franck Lenormand <franck.lenormand@nxp.com>
François-Frédéric Ozog <ff@ozog.com>
+Frank Dressler <frank@dressler.pro>
Frank Du <frank.du@intel.com>
Frank Zhao <frank.zhao@starfivetech.com>
Frederico Cadete <frederico.cadete-ext@oneaccess-net.com>
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index b668ca5b81..1ed1de8c02 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -913,6 +913,102 @@ test_af_packet_qdisc_bypass(void)
return TEST_SUCCESS;
}
+/*
+ * Test: Ignore outgoing packets configuration
+ * TX on the TAP with qdisc_bypass=0 produces PACKET_OUTGOING frames.
+ * A peer with ignore_outgoing=0 should see them; ignore_outgoing=1 must not.
+ */
+static int
+test_af_packet_ignore_outgoing(void)
+{
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint16_t tx_port, rx_port_ign_out_off, rx_port_ign_out_on, nb_tx;
+ unsigned int rx_off = 0, rx_on = 0, allocated;
+ uint64_t elapsed = 0;
+ const char *err = NULL;
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ ret = create_af_packet_port("net_af_packet_ign_on",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=1",
+ &rx_port_ign_out_on);
+ if (ret != 0) {
+ err = "Failed to create ignore_outgoing=1 port";
+ goto fail_rx_ign_on;
+ }
+
+ ret = create_af_packet_port("net_af_packet_ign_off",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=0",
+ &rx_port_ign_out_off);
+ if (ret != 0) {
+ err = "Failed to create ignore_outgoing=0 port";
+ goto fail_rx_ign_off;
+ }
+
+ /* qdisc_bypass=0 so the kernel TX tap sees the TX packets */
+ ret = create_af_packet_port("net_af_packet_ign_tx",
+ "iface=" TAP_DEV_NAME ",qdisc_bypass=0", &tx_port);
+ if (ret != 0) {
+ err = "Failed to create TX af_packet port";
+ goto fail_tx;
+ }
+
+ if (configure_af_packet_port(rx_port_ign_out_on, 1, 1) != 0 ||
+ configure_af_packet_port(rx_port_ign_out_off, 1, 1) != 0 ||
+ configure_af_packet_port(tx_port, 1, 1) != 0) {
+ err = "Failed to configure ports";
+ goto fail_setup;
+ }
+
+ while (do_rx_burst(rx_port_ign_out_off, 0, bufs, BURST_SIZE) > 0)
+ ;
+ while (do_rx_burst(rx_port_ign_out_on, 0, bufs, BURST_SIZE) > 0)
+ ;
+
+ allocated = alloc_tx_mbufs(bufs, 4);
+ nb_tx = do_tx_burst(tx_port, 0, bufs, allocated);
+ if (allocated == 0 || nb_tx == 0) {
+ err = "TX setup failed";
+ goto fail_setup;
+ }
+
+ while (elapsed < LOOPBACK_TIMEOUT_US) {
+ rx_off += do_rx_burst(rx_port_ign_out_off, 0, bufs, BURST_SIZE);
+ rx_on += do_rx_burst(rx_port_ign_out_on, 0, bufs, BURST_SIZE);
+ if (rx_off >= nb_tx)
+ break;
+ rte_delay_us_block(STATS_POLL_INTERVAL_US);
+ elapsed += STATS_POLL_INTERVAL_US;
+ }
+
+fail_setup:
+ rte_eth_dev_stop(tx_port);
+ rte_eth_dev_close(tx_port);
+ rte_vdev_uninit("net_af_packet_ign_tx");
+fail_tx:
+ rte_eth_dev_stop(rx_port_ign_out_off);
+ rte_eth_dev_close(rx_port_ign_out_off);
+ rte_vdev_uninit("net_af_packet_ign_off");
+fail_rx_ign_off:
+ rte_eth_dev_stop(rx_port_ign_out_on);
+ rte_eth_dev_close(rx_port_ign_out_on);
+ rte_vdev_uninit("net_af_packet_ign_on");
+fail_rx_ign_on:
+ TEST_ASSERT(err == NULL, "%s", err);
+ TEST_ASSERT(rx_off > 0, "Expected packets with ignore_outgoing=0");
+ TEST_ASSERT(rx_on == 0, "Expected no packets with ignore_outgoing=1");
+
+ ret = rte_vdev_init("net_af_packet_ign_bad",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=2");
+ TEST_ASSERT(ret != 0, "Expected failure with ignore_outgoing=2");
+
+ return TEST_SUCCESS;
+}
+
/*
* Test: Multiple queue pairs
*/
@@ -1107,6 +1203,7 @@ static struct unit_test_suite af_packet_test_suite = {
TEST_CASE(test_af_packet_invalid_qpairs),
TEST_CASE(test_af_packet_frame_config),
TEST_CASE(test_af_packet_qdisc_bypass),
+ TEST_CASE(test_af_packet_ignore_outgoing),
TEST_CASE(test_af_packet_multi_queue),
TEST_CASES_END() /**< NULL terminate unit test array */
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 1505b98ff7..11c9def583 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -25,6 +25,9 @@ Some of these, in turn, will be used to configure the PACKET_MMAP settings.
disabled by default);
* ``fanout_mode`` - set fanout algorithm.
Possible choices: hash, lb, cpu, rollover, rnd, qm (optional, default hash);
+* ``ignore_outgoing`` - set PACKET_IGNORE_OUTGOING so the socket does not
+ receive packets transmitted by the host on the same interface (optional,
+ default 0);
* ``blocksz`` - PACKET_MMAP block size (optional, default 4096);
* ``framesz`` - PACKET_MMAP frame size (optional, default 2048B; Note: multiple
of 16B);
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 030bd84cea..13598e1272 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,12 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Updated AF_PACKET driver.**
+
+ Added ``ignore_outgoing`` vdev argument to enable ``PACKET_IGNORE_OUTGOING``,
+ so the PMD does not receive packets transmitted by the host on the same
+ interface.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index a93df97023..66400073a7 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -39,6 +39,7 @@
#define ETH_AF_PACKET_FRAMECOUNT_ARG "framecnt"
#define ETH_AF_PACKET_QDISC_BYPASS_ARG "qdisc_bypass"
#define ETH_AF_PACKET_FANOUT_MODE_ARG "fanout_mode"
+#define ETH_AF_PACKET_IGNORE_OUTGOING_ARG "ignore_outgoing"
#define DFLT_FRAME_SIZE (1 << 11)
#define DFLT_FRAME_COUNT (1 << 9)
@@ -103,6 +104,7 @@ static const char *valid_arguments[] = {
ETH_AF_PACKET_FRAMECOUNT_ARG,
ETH_AF_PACKET_QDISC_BYPASS_ARG,
ETH_AF_PACKET_FANOUT_MODE_ARG,
+ ETH_AF_PACKET_IGNORE_OUTGOING_ARG,
NULL
};
@@ -877,6 +879,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
unsigned int framecnt,
unsigned int qdisc_bypass,
const char *fanout_mode,
+ unsigned int ignore_outgoing,
struct pmd_internals **internals,
struct rte_eth_dev **eth_dev,
struct rte_kvargs *kvlist)
@@ -1025,6 +1028,17 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
#endif
}
+ if (ignore_outgoing) {
+ rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
+ &ignore_outgoing, sizeof(ignore_outgoing));
+ if (rc == -1) {
+ PMD_LOG_ERRNO(ERR,
+ "%s: could not set PACKET_IGNORE_OUTGOING on AF_PACKET socket for %s",
+ name, pair->value);
+ goto error;
+ }
+ }
+
rc = setsockopt(qsockfd, SOL_PACKET, PACKET_RX_RING, req, sizeof(*req));
if (rc == -1) {
PMD_LOG_ERRNO(ERR,
@@ -1206,6 +1220,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
unsigned int qpairs = 1;
unsigned int qdisc_bypass = 1;
const char *fanout_mode = NULL;
+ unsigned int ignore_outgoing = 0;
/* do some parameter checking */
if (*sockfd < 0)
@@ -1272,6 +1287,11 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
fanout_mode = pair->value;
continue;
}
+ if (strstr(pair->key, ETH_AF_PACKET_IGNORE_OUTGOING_ARG) != NULL) {
+ if (parse_uint(pair->key, pair->value, &ignore_outgoing, 1) < 0)
+ return -1;
+ continue;
+ }
}
if (framesize > blocksize) {
@@ -1298,12 +1318,14 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, fanout_mode);
else
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, "default PACKET_FANOUT_HASH");
+ PMD_LOG(DEBUG, "%s:\tignore outgoing %d", name, ignore_outgoing);
if (rte_pmd_init_internals(dev, *sockfd, qpairs,
blocksize, blockcount,
framesize, framecount,
qdisc_bypass,
fanout_mode,
+ ignore_outgoing,
&internals, ð_dev,
kvlist) < 0)
return -1;
@@ -1401,4 +1423,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,
"framesz=<int> "
"framecnt=<int> "
"qdisc_bypass=<0|1> "
- "fanout_mode=<hash|lb|cpu|rollover|rnd|qm>");
+ "fanout_mode=<hash|lb|cpu|rollover|rnd|qm> "
+ "ignore_outgoing=<0|1>");
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v3] net/af_packet: add option to ignore outgoing packets
2026-10-06 6:42 ` [PATCH v3] " Frank Dressler
@ 2026-10-06 13:41 ` Stephen Hemminger
0 siblings, 0 replies; 23+ messages in thread
From: Stephen Hemminger @ 2026-10-06 13:41 UTC (permalink / raw)
To: Frank Dressler; +Cc: Thomas Monjalon, dev
On Tue, 6 Oct 2026 07:42:28 +0100
Frank Dressler <frank@dressler.pro> wrote:
> By default, AF_PACKET delivers both incoming and outgoing packets.
> This might surprise applications that expect rte_eth_rx_burst() to
> return only incoming traffic.
>
> This patch adds the devarg ignore_outgoing=<0|1> to enable
> PACKET_IGNORE_OUTGOING. When set, the kernel drops outgoing frames.
> The default is 0 to keep the existing behavior.
>
> Signed-off-by: Frank Dressler <frank@dressler.pro>
> ---
> v3:
> - drop #ifdef PACKET_IGNORE_OUTGOING (uapi since 4.20; DPDK requires kernel >= 5.4)
> - fail the test if ignore_outgoing=1 cannot create a port
> - drop kernel >= 4.20 notes from docs and release notes
> v2:
> - use PACKET_IGNORE_OUTGOING / ignore_outgoing= instead of capture_dir
>
> ---
AI spotted issue with multiple qpairs.
[PATCH v3] net/af_packet: add option to ignore outgoing packets
Builds clean with -Dwerror=true. Test not run.
Error
1. ignore_outgoing=1 has no effect when qpairs > 1.
rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
&ignore_outgoing, sizeof(ignore_outgoing));
With more than one queue every socket joins a fanout group.
fanout_add() removes the socket's own packet_type from ptype_all
and registers the group's instead, and dev_queue_xmit_nit() only
looks at the group's ignore_outgoing. The per-socket option is
accepted and silently does nothing. The group needs
PACKET_FANOUT_FLAG_IGNORE_OUTGOING (6.2) or'd into the flags half
of fanout_arg.
Kernels before 6.2 do not validate fanout flags; the bit is
accepted and ignored there. Either keep the sll_pkttype check in
eth_af_packet_rx() as a backstop, or document that
ignore_outgoing with qpairs > 1 needs 6.2. Distro headers older
than 6.2 lack the define, so provide a fallback #define.
Warning
2. Test only covers the single queue case and never sends an
incoming frame.
TEST_ASSERT(rx_on == 0, "Expected no packets with ignore_outgoing=1");
A port that receives nothing at all passes this. Write frames to
tap_fd as test_af_packet_loopback() does and check that the
ignore_outgoing=1 port still receives them. Run the same check
with qpairs=2; that would have caught item 1.
Info
3. Commit message:
When set, the kernel drops outgoing frames.
The frames are still transmitted; the socket just does not get a
copy. The doc text has it right, use that wording.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
2026-10-05 16:41 ` Stephen Hemminger
2026-10-06 6:42 ` [PATCH v3] " Frank Dressler
@ 2026-10-09 15:02 ` Frank Dressler
2026-10-09 15:02 ` [PATCH v4 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
` (2 more replies)
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
3 siblings, 3 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:02 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In contrast to other PMDs, the AF_PACKET PMD does not only return
incoming but also outgoing packets from rte_eth_rx_burst(). This is
fine for applications similar to tcpdump which usually want to see
both. But it is not always the desired behavior. For example, the
AF_PACKET PMD might be used in a testing environment in which no
regular ethernet PMD works. In such an environment, the AF_PACKET
PMD must behave like a regular one, i.e. not return sent packets
from rte_eth_rx_burst(). One could use the AF_XDP PMD for this but
it has different dependencies than the AF_PACKET PMD because it
requires the ``bpf()`` syscall, for example, which might not be
available.
For this reason, this series adds the ignore_outgoing=<0|1> devarg.
When set, the PMD ensures that rte_eth_rx_burst() only returns
incoming traffic. The default is 0 so that existing software works
as before.
The first patch does this filtering in the PMD because this works
in all supported kernel versions and for all queue counts.
The second patch adds PACKET_IGNORE_OUTGOING which moves the
filtering into the kernel. It is supported since v4.20 but only
works for single-queue AF_PACKET scenarios. Multi-queue AF_PACKET
is still filtered in the PMD.
The third patch adds PACKET_FANOUT_FLAG_IGNORE_OUTGOING which is
supported since kernel v6.2 and moves the filtering into the kernel
for the multi-queue scenarios. The kernel does not expose a direct
way to check whether it supports PACKET_FANOUT_FLAG_IGNORE_OUTGOING
but it also does not return an error when the sockopt is set on
kernels that do not support it. Because of that the sockopt is set
opportunistically. For kernels v6.2 the filtering will then happen
in the kernel and for earlier versions the PMD does the filtering
for the multi-queue scenario.
Overall we get the following:
| Single Queue | Multi-Queue
------------+--------------+-------------
Kernel 5.4+ | in kernel | in PMD
Kernel 6.2+ | in kernel | in kernel
I tested this on both, kernel 7.2.8 and kernel 5.4.0 with both single
and multiple queues. My results, strace'ing testpmd 7.2.8, with the
variants of ignore_outgoing=0|1 and single/multi-queue:
Kernel 5.4.0:
qpairs=1,ignore_outgoing=0: -
qpairs=1,ignore_outgoing=1: setsockopt(14, SOL_PACKET, 0x17 /* PACKET_??? */, [1], 4) = 0
// 0x17 is PACKET_IGNORE_OUTGOING
qpairs=2,ignore_outgoing=0: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-1878917532], 4) = 0
// -1878917532 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
qpairs=2,ignore_outgoing=1: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-805175699], 4) = 0
// -805175699 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
Kernel 7.2.8:
qpairs=1,ignore_outgoing=0: -
qpairs=1,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_IGNORE_OUTGOING, [1], 4) = 0
qpairs=2,ignore_outgoing=0: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-1878978899], 4) = 0
// -1878978899 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
qpairs=2,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-805236964], 4) = 0
// -805236964 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
They show that the setsockopt succeeeds even on the old kernel that
does not support PACKET_FANOUT_FLAG_IGNORE_OUTGOING, so setting it
opportunistically is fine. They also show that the code only tries to
set PACKET_FANOUT_FLAG_IGNORE_OUTGOING or PACKET_IGNORE_OUTGOING when
requested by ignore_outgoing=1. And it shows that
PACKET_IGNORE_OUTGOING is used for single and
PACKET_FANOUT_FLAG_IGNORE_OUTGOING for multi-queue.
I have also manually ran testpmd in all modes and both kernels to
verify that ignore_outgoing works as expected, sending packets and
checking what testpmd was receiving (``set verbose 1``).
v4:
- split into PMD filtering, PACKET_IGNORE_OUTGOING for the
single-queue scenario, and PACKET_FANOUT_FLAG_IGNORE_OUTGOING for
multiple queues
- set the fanout flag for all kernel versions. kernels that do not
support it silently ignore it and filtering falls back to the
PMD-based approach
- changed the test to test both single and multi-queue
v3:
- drop #ifdef PACKET_IGNORE_OUTGOING (uapi since 4.20; DPDK requires kernel >= 5.4)
- fail the test if ignore_outgoing=1 cannot create a port
- drop kernel >= 4.20 notes from docs and release notes
v2:
- use PACKET_IGNORE_OUTGOING / ignore_outgoing= instead of capture_dir
Frank Dressler (3):
net/af_packet: filter Rx of outgoing packets in the PMD
net/af_packet: use kernel to filter single-queue Rx
net/af_packet: use kernel to filter multi-queue Rx
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 150 ++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 2 +
doc/guides/rel_notes/release_26_11.rst | 6 +
drivers/net/af_packet/rte_eth_af_packet.c | 50 +++++++-
5 files changed, 208 insertions(+), 1 deletion(-)
--
2.56.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v4 1/3] net/af_packet: filter Rx of outgoing packets in the PMD
2026-10-09 15:02 ` [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
@ 2026-10-09 15:02 ` Frank Dressler
2026-10-09 15:02 ` [PATCH v4 2/3] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
2026-10-09 15:02 ` [PATCH v4 3/3] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
2 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:02 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In contrast to other PMDs, the AF_PACKET PMD does not only return
incoming but also outgoing packets from rte_eth_rx_burst(). This is
fine for applications similar to tcpdump which usually want to see
both.
But this is not always the desired behavior. For example, the
AF_PACKET PMD might be used in a testing environment in which no
regular ethernet PMD is available. In that testing environment, the
PMD must behave like a regular one, i.e. not return sent packets
from rte_eth_rx_burst().
For this reason, this change adds the ignore_outgoing=<0|1> devarg.
When set, the PMD ensures that rte_eth_rx_burst() only returns
incoming traffic. The default is 0 so that existing software works
as before.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 150 ++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 2 +
doc/guides/rel_notes/release_26_11.rst | 5 +
drivers/net/af_packet/rte_eth_af_packet.c | 25 +++-
5 files changed, 182 insertions(+), 1 deletion(-)
diff --git a/.mailmap b/.mailmap
index 2e348c3bce..a04fc553d4 100644
--- a/.mailmap
+++ b/.mailmap
@@ -505,6 +505,7 @@ Francis Kelly <fkelly@nvidia.com> <fkelly@mellanox.com>
Francis Racicot <francis.racicot@intel.com>
Franck Lenormand <franck.lenormand@nxp.com>
François-Frédéric Ozog <ff@ozog.com>
+Frank Dressler <frank@dressler.pro>
Frank Du <frank.du@intel.com>
Frank Zhao <frank.zhao@starfivetech.com>
Frederico Cadete <frederico.cadete-ext@oneaccess-net.com>
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index b668ca5b81..5dd72c35cf 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -1083,6 +1083,153 @@ test_af_packet_multithread(void)
return TEST_SUCCESS;
}
+/*
+ * Test: Ignore outgoing configuration
+ */
+static int
+test_af_packet_ignore_outgoing_queues(uint16_t nb_queues, bool ignore_outgoing)
+{
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint16_t tx_port, rx_port, nb_tx, q;
+ unsigned int rx_pkts = 0, allocated;
+ uint64_t elapsed = 0;
+ const char *err = NULL;
+ bool have_rx = false;
+ bool have_tx = false;
+ char args[128];
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ snprintf(args, sizeof(args),
+ "iface=%s,qpairs=%u,ignore_outgoing=%u",
+ TAP_DEV_NAME, nb_queues, ignore_outgoing ? 1 : 0);
+ ret = create_af_packet_port("net_af_packet_ign_rx", args, &rx_port);
+ if (ret != 0) {
+ err = "Failed to create RX af_packet port";
+ goto out;
+ }
+ have_rx = true;
+
+ /* qdisc_bypass=0 so the kernel TX path sees the TX packets */
+ ret = create_af_packet_port("net_af_packet_ign_tx",
+ "iface=" TAP_DEV_NAME ",qdisc_bypass=0", &tx_port);
+ if (ret != 0) {
+ err = "Failed to create TX af_packet port";
+ goto out;
+ }
+ have_tx = true;
+
+ if (configure_af_packet_port(rx_port, nb_queues, nb_queues) != 0 ||
+ configure_af_packet_port(tx_port, 1, 1) != 0) {
+ err = "Failed to configure ports";
+ goto out;
+ }
+
+ /* flush the RX port */
+ for (q = 0; q < nb_queues; q++)
+ while (do_rx_burst(rx_port, q, bufs, BURST_SIZE) > 0)
+ ;
+
+ /* send packets */
+ allocated = alloc_tx_mbufs(bufs, 4);
+ nb_tx = do_tx_burst(tx_port, 0, bufs, allocated);
+ if (allocated == 0 || nb_tx == 0) {
+ err = "TX setup failed";
+ goto out;
+ }
+
+ /* receive packets */
+ while (elapsed < LOOPBACK_TIMEOUT_US) {
+ for (q = 0; q < nb_queues; q++)
+ rx_pkts += do_rx_burst(rx_port, q, bufs, BURST_SIZE);
+ if (ignore_outgoing) {
+ if (rx_pkts != 0)
+ break;
+ } else if (rx_pkts >= nb_tx)
+ break;
+ rte_delay_us_block(STATS_POLL_INTERVAL_US);
+ elapsed += STATS_POLL_INTERVAL_US;
+ }
+
+out:
+ if (have_tx) {
+ rte_eth_dev_stop(tx_port);
+ rte_eth_dev_close(tx_port);
+ rte_vdev_uninit("net_af_packet_ign_tx");
+ }
+ if (have_rx) {
+ rte_eth_dev_stop(rx_port);
+ rte_eth_dev_close(rx_port);
+ rte_vdev_uninit("net_af_packet_ign_rx");
+ }
+
+ TEST_ASSERT(err == NULL, "%s", err);
+ if (ignore_outgoing)
+ TEST_ASSERT(rx_pkts == 0,
+ "ignore_outgoing port with %u queues saw outgoing packets",
+ nb_queues);
+ else
+ TEST_ASSERT(rx_pkts > 0,
+ "port with %u queues and ignore_outgoing=0 saw no packets",
+ nb_queues);
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Test: invalid ignore_outgoing value
+ */
+static int
+test_af_packet_invalid_ignore_outgoing(void)
+{
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ ret = rte_vdev_init("net_af_packet_ign_bad",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=2");
+ TEST_ASSERT(ret != 0, "Expected failure with ignore_outgoing=2");
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Test: Ignore outgoing for one queue
+ */
+static int
+test_af_packet_ignore_outgoing(void)
+{
+ int ret;
+
+ ret = test_af_packet_ignore_outgoing_queues(1, true);
+ if (ret != TEST_SUCCESS)
+ return ret;
+
+ return test_af_packet_ignore_outgoing_queues(1, false);
+}
+
+/*
+ * Test: Ignore outgoing for multiple queues
+ */
+static int
+test_af_packet_ignore_outgoing_multi_queue(void)
+{
+ int ret;
+
+ ret = test_af_packet_ignore_outgoing_queues(2, true);
+ if (ret != TEST_SUCCESS)
+ return ret;
+
+ return test_af_packet_ignore_outgoing_queues(2, false);
+}
+
static struct unit_test_suite af_packet_test_suite = {
.suite_name = "AF_PACKET PMD Unit Test Suite",
.setup = test_af_packet_setup,
@@ -1108,6 +1255,9 @@ static struct unit_test_suite af_packet_test_suite = {
TEST_CASE(test_af_packet_frame_config),
TEST_CASE(test_af_packet_qdisc_bypass),
TEST_CASE(test_af_packet_multi_queue),
+ TEST_CASE(test_af_packet_invalid_ignore_outgoing),
+ TEST_CASE(test_af_packet_ignore_outgoing),
+ TEST_CASE(test_af_packet_ignore_outgoing_multi_queue),
TEST_CASES_END() /**< NULL terminate unit test array */
}
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 1505b98ff7..fe42180d4e 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -25,6 +25,8 @@ Some of these, in turn, will be used to configure the PACKET_MMAP settings.
disabled by default);
* ``fanout_mode`` - set fanout algorithm.
Possible choices: hash, lb, cpu, rollover, rnd, qm (optional, default hash);
+* ``ignore_outgoing`` - prevent `rte_eth_rx_burst()` from returning transmitted
+ packets (optional, disabled by default);
* ``blocksz`` - PACKET_MMAP block size (optional, default 4096);
* ``framesz`` - PACKET_MMAP frame size (optional, default 2048B; Note: multiple
of 16B);
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 030bd84cea..daf5603a41 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,11 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Updated AF_PACKET driver.**
+
+ Added ``ignore_outgoing`` vdev argument to prevent `rte_eth_rx_burst()` from
+ returning transmitted packets.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index a93df97023..837da9a3af 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -39,6 +39,7 @@
#define ETH_AF_PACKET_FRAMECOUNT_ARG "framecnt"
#define ETH_AF_PACKET_QDISC_BYPASS_ARG "qdisc_bypass"
#define ETH_AF_PACKET_FANOUT_MODE_ARG "fanout_mode"
+#define ETH_AF_PACKET_IGNORE_OUTGOING_ARG "ignore_outgoing"
#define DFLT_FRAME_SIZE (1 << 11)
#define DFLT_FRAME_COUNT (1 << 9)
@@ -59,6 +60,7 @@ struct __rte_cache_aligned pkt_rx_queue {
uint8_t vlan_strip;
uint8_t timestamp_offloading;
uint8_t scatter_enabled;
+ uint8_t ignore_outgoing;
volatile unsigned long rx_pkts;
volatile unsigned long rx_bytes;
@@ -103,6 +105,7 @@ static const char *valid_arguments[] = {
ETH_AF_PACKET_FRAMECOUNT_ARG,
ETH_AF_PACKET_QDISC_BYPASS_ARG,
ETH_AF_PACKET_FANOUT_MODE_ARG,
+ ETH_AF_PACKET_IGNORE_OUTGOING_ARG,
NULL
};
@@ -166,6 +169,7 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
{
unsigned i;
struct tpacket2_hdr *ppd;
+ struct sockaddr_ll *sll;
struct rte_mbuf *mbuf;
uint8_t *pbuf;
struct pkt_rx_queue *pkt_q = queue;
@@ -189,6 +193,14 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
if ((ppd->tp_status & TP_STATUS_USER) == 0)
break;
+ /* skip outgoing frames if ignore_outgoing is set */
+ if (pkt_q->ignore_outgoing != 0) {
+ sll = (struct sockaddr_ll *)((uint8_t *)ppd +
+ TPACKET_ALIGN(sizeof(*ppd)));
+ if (sll->sll_pkttype == PACKET_OUTGOING)
+ goto release_frame;
+ }
+
/* allocate the next mbuf */
mbuf = rte_pktmbuf_alloc(pkt_q->mb_pool);
if (unlikely(mbuf == NULL)) {
@@ -877,6 +889,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
unsigned int framecnt,
unsigned int qdisc_bypass,
const char *fanout_mode,
+ unsigned int ignore_outgoing,
struct pmd_internals **internals,
struct rte_eth_dev **eth_dev,
struct rte_kvargs *kvlist)
@@ -1043,6 +1056,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
+ rx_queue->ignore_outgoing = ignore_outgoing != 0;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
@@ -1206,6 +1220,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
unsigned int qpairs = 1;
unsigned int qdisc_bypass = 1;
const char *fanout_mode = NULL;
+ unsigned int ignore_outgoing = 0;
/* do some parameter checking */
if (*sockfd < 0)
@@ -1272,6 +1287,11 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
fanout_mode = pair->value;
continue;
}
+ if (strstr(pair->key, ETH_AF_PACKET_IGNORE_OUTGOING_ARG) != NULL) {
+ if (parse_uint(pair->key, pair->value, &ignore_outgoing, 1) < 0)
+ return -1;
+ continue;
+ }
}
if (framesize > blocksize) {
@@ -1298,12 +1318,14 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, fanout_mode);
else
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, "default PACKET_FANOUT_HASH");
+ PMD_LOG(DEBUG, "%s:\tignore outgoing %d", name, ignore_outgoing);
if (rte_pmd_init_internals(dev, *sockfd, qpairs,
blocksize, blockcount,
framesize, framecount,
qdisc_bypass,
fanout_mode,
+ ignore_outgoing,
&internals, ð_dev,
kvlist) < 0)
return -1;
@@ -1401,4 +1423,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,
"framesz=<int> "
"framecnt=<int> "
"qdisc_bypass=<0|1> "
- "fanout_mode=<hash|lb|cpu|rollover|rnd|qm>");
+ "fanout_mode=<hash|lb|cpu|rollover|rnd|qm> "
+ "ignore_outgoing=<0|1>");
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v4 2/3] net/af_packet: use kernel to filter single-queue Rx
2026-10-09 15:02 ` [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:02 ` [PATCH v4 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
@ 2026-10-09 15:02 ` Frank Dressler
2026-10-09 15:02 ` [PATCH v4 3/3] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
2 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:02 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, Cursor
Currently, when the ignore_outgoing devarg is 1, the PMD still
receives outgoing packets from the kernel but skips over them so
that rte_eth_rx_burst() only returns incoming packets.
Since Linux kernel v4.20, the PACKET_IGNORE_OUTGOING socket option
prevents the kernel from delivering outgoing packets to an
AF_PACKET socket.
This change sets this socket option so that the PMD does not need to
filter out outgoing packets anymore. However, this socket option does
not work for packet sockets which are in a fanout group, so it
is only set in the single-queue scenario. When multiple queues
are used, the PMD-side filtering serves as a fallback.
The kernel has supported this flag since v4.20, and DPDK requires
at least v5.4, so no feature check has been added.
Signed-off-by: Frank Dressler <frank@dressler.pro>
Co-authored-by: Cursor <cursoragent@cursor.com>
---
doc/guides/rel_notes/release_26_11.rst | 3 ++-
drivers/net/af_packet/rte_eth_af_packet.c | 14 +++++++++++++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index daf5603a41..0cf7671cbb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,7 +67,8 @@ New Features
* **Updated AF_PACKET driver.**
Added ``ignore_outgoing`` vdev argument to prevent `rte_eth_rx_burst()` from
- returning transmitted packets.
+ returning transmitted packets. The single-queue scenario is accelerated,
+ as the kernel does the filtering.
* **Updated AF_XDP driver.**
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 837da9a3af..085bfa9af9 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -1038,6 +1038,18 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
#endif
}
+ if (ignore_outgoing != 0 && nb_queues == 1) {
+ rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
+ &ignore_outgoing, sizeof(ignore_outgoing));
+ if (rc == -1) {
+ PMD_LOG_ERRNO(ERR,
+ "%s: could not set PACKET_IGNORE_OUTGOING "
+ "on AF_PACKET socket for %s",
+ name, pair->value);
+ goto error;
+ }
+ }
+
rc = setsockopt(qsockfd, SOL_PACKET, PACKET_RX_RING, req, sizeof(*req));
if (rc == -1) {
PMD_LOG_ERRNO(ERR,
@@ -1056,7 +1068,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
- rx_queue->ignore_outgoing = ignore_outgoing != 0;
+ rx_queue->ignore_outgoing = ignore_outgoing != 0 && nb_queues > 1;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v4 3/3] net/af_packet: use kernel to filter multi-queue Rx
2026-10-09 15:02 ` [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:02 ` [PATCH v4 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
2026-10-09 15:02 ` [PATCH v4 2/3] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
@ 2026-10-09 15:02 ` Frank Dressler
2 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:02 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
The AF_PACKET PMD currently needs to filter out outgoing packets in
the PMD when the ignore_outgoing devarg is enabled and multiple queues
are used. Kernel-side filtering is only supported for the single-queue
scenario.
Since v6.2 the Linux kernel supports filtering outgoing packets for
AF_PACKET socket fanout groups via PACKET_FANOUT_FLAG_IGNORE_OUTGOING
which means that the PMD does not need to do this anymore.
Earlier kernels ignore this flag and have no direct way to detect
whether the flag is supported.
This change opportunistically sets this flag for all kernel versions
in multi-queue scenarios. Kernels that do not support this flag
silently ignore it. Filtering outgoing packets then falls back to the
PMD-based approach.
With this change, for all kernels v6.2 or newer, all the filtering of
outgoing packets will happen in the kernel.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
doc/guides/rel_notes/release_26_11.rst | 4 ++--
drivers/net/af_packet/rte_eth_af_packet.c | 13 +++++++++++++
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0cf7671cbb..c4ef38b362 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,8 +67,8 @@ New Features
* **Updated AF_PACKET driver.**
Added ``ignore_outgoing`` vdev argument to prevent `rte_eth_rx_burst()` from
- returning transmitted packets. The single-queue scenario is accelerated,
- as the kernel does the filtering.
+ returning transmitted packets. Single queue is always filtered in the kernel.
+ Multiple queues are filtered in the kernel since v6.2, otherwise in the PMD.
* **Updated AF_XDP driver.**
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 085bfa9af9..e3ed511b45 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -32,6 +32,11 @@
#include <unistd.h>
#include <poll.h>
+/* Added in Linux 6.2. Older kernels ignore it. */
+#ifndef PACKET_FANOUT_FLAG_IGNORE_OUTGOING
+#define PACKET_FANOUT_FLAG_IGNORE_OUTGOING 0x4000
+#endif
+
#define ETH_AF_PACKET_IFACE_ARG "iface"
#define ETH_AF_PACKET_NUM_Q_ARG "qpairs"
#define ETH_AF_PACKET_BLOCKSIZE_ARG "blocksz"
@@ -995,6 +1000,14 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
goto error;
}
+ /*
+ * Set the flag opportunistically if multiple queues are requested.
+ * It takes effect from Linux v6.2. Older kernels ignore it and the
+ * PMD filters instead.
+ */
+ if (ignore_outgoing != 0 && nb_queues > 1)
+ fanout_arg |= PACKET_FANOUT_FLAG_IGNORE_OUTGOING << 16;
+
for (q = 0; q < nb_queues; q++) {
/* Open an AF_PACKET socket for this queue... */
qsockfd = socket(AF_PACKET, SOCK_RAW, 0);
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
` (2 preceding siblings ...)
2026-10-09 15:02 ` [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
@ 2026-10-09 15:10 ` Frank Dressler
2026-10-09 15:10 ` [PATCH v5 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
` (4 more replies)
3 siblings, 5 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:10 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In contrast to other PMDs, the AF_PACKET PMD does not only return
incoming but also outgoing packets from rte_eth_rx_burst(). This is
fine for applications similar to tcpdump which usually want to see
both. But it is not always the desired behavior. For example, the
AF_PACKET PMD might be used in a testing environment in which no
regular ethernet PMD works. In such an environment, the AF_PACKET
PMD must behave like a regular one, i.e. not return sent packets
from rte_eth_rx_burst(). One could use the AF_XDP PMD for this but
it has different dependencies than the AF_PACKET PMD because it
requires the ``bpf()`` syscall, for example, which might not be
available.
For this reason, this series adds the ignore_outgoing=<0|1> devarg.
When set, the PMD ensures that rte_eth_rx_burst() only returns
incoming traffic. The default is 0 so that existing software works
as before.
The first patch does this filtering in the PMD because this works
in all supported kernel versions and for all queue counts.
The second patch adds PACKET_IGNORE_OUTGOING which moves the
filtering into the kernel. It is supported since v4.20 but only
works for single-queue AF_PACKET scenarios. Multi-queue AF_PACKET
is still filtered in the PMD.
The third patch adds PACKET_FANOUT_FLAG_IGNORE_OUTGOING which is
supported since kernel v6.2 and moves the filtering into the kernel
for the multi-queue scenarios. The kernel does not expose a direct
way to check whether it supports PACKET_FANOUT_FLAG_IGNORE_OUTGOING
but it also does not return an error when the sockopt is set on
kernels that do not support it. Because of that the sockopt is set
opportunistically. For kernels v6.2 the filtering will then happen
in the kernel and for earlier versions the PMD does the filtering
for the multi-queue scenario.
Overall we get the following:
| Single Queue | Multi-Queue
------------+--------------+-------------
Kernel 5.4+ | in kernel | in PMD
Kernel 6.2+ | in kernel | in kernel
I tested this on both, kernel 7.2.8 and kernel 5.4.0 with both single
and multiple queues. My results, strace'ing testpmd 7.2.8, with the
variants of ignore_outgoing=0|1 and single/multi-queue:
Kernel 5.4.0:
qpairs=1,ignore_outgoing=0: -
qpairs=1,ignore_outgoing=1: setsockopt(14, SOL_PACKET, 0x17 /* PACKET_??? */, [1], 4) = 0
// 0x17 is PACKET_IGNORE_OUTGOING
qpairs=2,ignore_outgoing=0: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-1878917532], 4) = 0
// -1878917532 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
qpairs=2,ignore_outgoing=1: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-805175699], 4) = 0
// -805175699 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
Kernel 7.2.8:
qpairs=1,ignore_outgoing=0: -
qpairs=1,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_IGNORE_OUTGOING, [1], 4) = 0
qpairs=2,ignore_outgoing=0: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-1878978899], 4) = 0
// -1878978899 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
qpairs=2,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-805236964], 4) = 0
// -805236964 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
They show that the setsockopt succeeeds even on the old kernel that
does not support PACKET_FANOUT_FLAG_IGNORE_OUTGOING, so setting it
opportunistically is fine. They also show that the code only tries to
set PACKET_FANOUT_FLAG_IGNORE_OUTGOING or PACKET_IGNORE_OUTGOING when
requested by ignore_outgoing=1. And it shows that
PACKET_IGNORE_OUTGOING is used for single and
PACKET_FANOUT_FLAG_IGNORE_OUTGOING for multi-queue.
I have also manually ran testpmd in all modes and both kernels to
verify that ignore_outgoing works as expected, sending packets and
checking what testpmd was receiving (``set verbose 1``).
v5:
- remove Co-Authored-By line in patch 2
v4:
- split into PMD filtering, PACKET_IGNORE_OUTGOING for the
single-queue scenario, and PACKET_FANOUT_FLAG_IGNORE_OUTGOING for
multiple queues
- set the fanout flag for all kernel versions. kernels that do not
support it silently ignore it and filtering falls back to the
PMD-based approach
- changed the test to test both single and multi-queue
v3:
- drop #ifdef PACKET_IGNORE_OUTGOING (uapi since 4.20; DPDK requires kernel >= 5.4)
- fail the test if ignore_outgoing=1 cannot create a port
- drop kernel >= 4.20 notes from docs and release notes
v2:
- use PACKET_IGNORE_OUTGOING / ignore_outgoing= instead of capture_dir
Frank Dressler (3):
net/af_packet: filter Rx of outgoing packets in the PMD
net/af_packet: use kernel to filter single-queue Rx
net/af_packet: use kernel to filter multi-queue Rx
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 150 ++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 2 +
doc/guides/rel_notes/release_26_11.rst | 6 +
drivers/net/af_packet/rte_eth_af_packet.c | 50 +++++++-
5 files changed, 208 insertions(+), 1 deletion(-)
--
2.56.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v5 1/3] net/af_packet: filter Rx of outgoing packets in the PMD
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
@ 2026-10-09 15:10 ` Frank Dressler
2026-10-09 15:10 ` [PATCH v5 2/3] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
` (3 subsequent siblings)
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:10 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In contrast to other PMDs, the AF_PACKET PMD does not only return
incoming but also outgoing packets from rte_eth_rx_burst(). This is
fine for applications similar to tcpdump which usually want to see
both.
But this is not always the desired behavior. For example, the
AF_PACKET PMD might be used in a testing environment in which no
regular ethernet PMD is available. In that testing environment, the
PMD must behave like a regular one, i.e. not return sent packets
from rte_eth_rx_burst().
For this reason, this change adds the ignore_outgoing=<0|1> devarg.
When set, the PMD ensures that rte_eth_rx_burst() only returns
incoming traffic. The default is 0 so that existing software works
as before.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 150 ++++++++++++++++++++++
doc/guides/nics/af_packet.rst | 2 +
doc/guides/rel_notes/release_26_11.rst | 5 +
drivers/net/af_packet/rte_eth_af_packet.c | 25 +++-
5 files changed, 182 insertions(+), 1 deletion(-)
diff --git a/.mailmap b/.mailmap
index 2e348c3bce..a04fc553d4 100644
--- a/.mailmap
+++ b/.mailmap
@@ -505,6 +505,7 @@ Francis Kelly <fkelly@nvidia.com> <fkelly@mellanox.com>
Francis Racicot <francis.racicot@intel.com>
Franck Lenormand <franck.lenormand@nxp.com>
François-Frédéric Ozog <ff@ozog.com>
+Frank Dressler <frank@dressler.pro>
Frank Du <frank.du@intel.com>
Frank Zhao <frank.zhao@starfivetech.com>
Frederico Cadete <frederico.cadete-ext@oneaccess-net.com>
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index b668ca5b81..5dd72c35cf 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -1083,6 +1083,153 @@ test_af_packet_multithread(void)
return TEST_SUCCESS;
}
+/*
+ * Test: Ignore outgoing configuration
+ */
+static int
+test_af_packet_ignore_outgoing_queues(uint16_t nb_queues, bool ignore_outgoing)
+{
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint16_t tx_port, rx_port, nb_tx, q;
+ unsigned int rx_pkts = 0, allocated;
+ uint64_t elapsed = 0;
+ const char *err = NULL;
+ bool have_rx = false;
+ bool have_tx = false;
+ char args[128];
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ snprintf(args, sizeof(args),
+ "iface=%s,qpairs=%u,ignore_outgoing=%u",
+ TAP_DEV_NAME, nb_queues, ignore_outgoing ? 1 : 0);
+ ret = create_af_packet_port("net_af_packet_ign_rx", args, &rx_port);
+ if (ret != 0) {
+ err = "Failed to create RX af_packet port";
+ goto out;
+ }
+ have_rx = true;
+
+ /* qdisc_bypass=0 so the kernel TX path sees the TX packets */
+ ret = create_af_packet_port("net_af_packet_ign_tx",
+ "iface=" TAP_DEV_NAME ",qdisc_bypass=0", &tx_port);
+ if (ret != 0) {
+ err = "Failed to create TX af_packet port";
+ goto out;
+ }
+ have_tx = true;
+
+ if (configure_af_packet_port(rx_port, nb_queues, nb_queues) != 0 ||
+ configure_af_packet_port(tx_port, 1, 1) != 0) {
+ err = "Failed to configure ports";
+ goto out;
+ }
+
+ /* flush the RX port */
+ for (q = 0; q < nb_queues; q++)
+ while (do_rx_burst(rx_port, q, bufs, BURST_SIZE) > 0)
+ ;
+
+ /* send packets */
+ allocated = alloc_tx_mbufs(bufs, 4);
+ nb_tx = do_tx_burst(tx_port, 0, bufs, allocated);
+ if (allocated == 0 || nb_tx == 0) {
+ err = "TX setup failed";
+ goto out;
+ }
+
+ /* receive packets */
+ while (elapsed < LOOPBACK_TIMEOUT_US) {
+ for (q = 0; q < nb_queues; q++)
+ rx_pkts += do_rx_burst(rx_port, q, bufs, BURST_SIZE);
+ if (ignore_outgoing) {
+ if (rx_pkts != 0)
+ break;
+ } else if (rx_pkts >= nb_tx)
+ break;
+ rte_delay_us_block(STATS_POLL_INTERVAL_US);
+ elapsed += STATS_POLL_INTERVAL_US;
+ }
+
+out:
+ if (have_tx) {
+ rte_eth_dev_stop(tx_port);
+ rte_eth_dev_close(tx_port);
+ rte_vdev_uninit("net_af_packet_ign_tx");
+ }
+ if (have_rx) {
+ rte_eth_dev_stop(rx_port);
+ rte_eth_dev_close(rx_port);
+ rte_vdev_uninit("net_af_packet_ign_rx");
+ }
+
+ TEST_ASSERT(err == NULL, "%s", err);
+ if (ignore_outgoing)
+ TEST_ASSERT(rx_pkts == 0,
+ "ignore_outgoing port with %u queues saw outgoing packets",
+ nb_queues);
+ else
+ TEST_ASSERT(rx_pkts > 0,
+ "port with %u queues and ignore_outgoing=0 saw no packets",
+ nb_queues);
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Test: invalid ignore_outgoing value
+ */
+static int
+test_af_packet_invalid_ignore_outgoing(void)
+{
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ ret = rte_vdev_init("net_af_packet_ign_bad",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=2");
+ TEST_ASSERT(ret != 0, "Expected failure with ignore_outgoing=2");
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Test: Ignore outgoing for one queue
+ */
+static int
+test_af_packet_ignore_outgoing(void)
+{
+ int ret;
+
+ ret = test_af_packet_ignore_outgoing_queues(1, true);
+ if (ret != TEST_SUCCESS)
+ return ret;
+
+ return test_af_packet_ignore_outgoing_queues(1, false);
+}
+
+/*
+ * Test: Ignore outgoing for multiple queues
+ */
+static int
+test_af_packet_ignore_outgoing_multi_queue(void)
+{
+ int ret;
+
+ ret = test_af_packet_ignore_outgoing_queues(2, true);
+ if (ret != TEST_SUCCESS)
+ return ret;
+
+ return test_af_packet_ignore_outgoing_queues(2, false);
+}
+
static struct unit_test_suite af_packet_test_suite = {
.suite_name = "AF_PACKET PMD Unit Test Suite",
.setup = test_af_packet_setup,
@@ -1108,6 +1255,9 @@ static struct unit_test_suite af_packet_test_suite = {
TEST_CASE(test_af_packet_frame_config),
TEST_CASE(test_af_packet_qdisc_bypass),
TEST_CASE(test_af_packet_multi_queue),
+ TEST_CASE(test_af_packet_invalid_ignore_outgoing),
+ TEST_CASE(test_af_packet_ignore_outgoing),
+ TEST_CASE(test_af_packet_ignore_outgoing_multi_queue),
TEST_CASES_END() /**< NULL terminate unit test array */
}
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 1505b98ff7..fe42180d4e 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -25,6 +25,8 @@ Some of these, in turn, will be used to configure the PACKET_MMAP settings.
disabled by default);
* ``fanout_mode`` - set fanout algorithm.
Possible choices: hash, lb, cpu, rollover, rnd, qm (optional, default hash);
+* ``ignore_outgoing`` - prevent `rte_eth_rx_burst()` from returning transmitted
+ packets (optional, disabled by default);
* ``blocksz`` - PACKET_MMAP block size (optional, default 4096);
* ``framesz`` - PACKET_MMAP frame size (optional, default 2048B; Note: multiple
of 16B);
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 030bd84cea..daf5603a41 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,11 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Updated AF_PACKET driver.**
+
+ Added ``ignore_outgoing`` vdev argument to prevent `rte_eth_rx_burst()` from
+ returning transmitted packets.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index a93df97023..837da9a3af 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -39,6 +39,7 @@
#define ETH_AF_PACKET_FRAMECOUNT_ARG "framecnt"
#define ETH_AF_PACKET_QDISC_BYPASS_ARG "qdisc_bypass"
#define ETH_AF_PACKET_FANOUT_MODE_ARG "fanout_mode"
+#define ETH_AF_PACKET_IGNORE_OUTGOING_ARG "ignore_outgoing"
#define DFLT_FRAME_SIZE (1 << 11)
#define DFLT_FRAME_COUNT (1 << 9)
@@ -59,6 +60,7 @@ struct __rte_cache_aligned pkt_rx_queue {
uint8_t vlan_strip;
uint8_t timestamp_offloading;
uint8_t scatter_enabled;
+ uint8_t ignore_outgoing;
volatile unsigned long rx_pkts;
volatile unsigned long rx_bytes;
@@ -103,6 +105,7 @@ static const char *valid_arguments[] = {
ETH_AF_PACKET_FRAMECOUNT_ARG,
ETH_AF_PACKET_QDISC_BYPASS_ARG,
ETH_AF_PACKET_FANOUT_MODE_ARG,
+ ETH_AF_PACKET_IGNORE_OUTGOING_ARG,
NULL
};
@@ -166,6 +169,7 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
{
unsigned i;
struct tpacket2_hdr *ppd;
+ struct sockaddr_ll *sll;
struct rte_mbuf *mbuf;
uint8_t *pbuf;
struct pkt_rx_queue *pkt_q = queue;
@@ -189,6 +193,14 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
if ((ppd->tp_status & TP_STATUS_USER) == 0)
break;
+ /* skip outgoing frames if ignore_outgoing is set */
+ if (pkt_q->ignore_outgoing != 0) {
+ sll = (struct sockaddr_ll *)((uint8_t *)ppd +
+ TPACKET_ALIGN(sizeof(*ppd)));
+ if (sll->sll_pkttype == PACKET_OUTGOING)
+ goto release_frame;
+ }
+
/* allocate the next mbuf */
mbuf = rte_pktmbuf_alloc(pkt_q->mb_pool);
if (unlikely(mbuf == NULL)) {
@@ -877,6 +889,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
unsigned int framecnt,
unsigned int qdisc_bypass,
const char *fanout_mode,
+ unsigned int ignore_outgoing,
struct pmd_internals **internals,
struct rte_eth_dev **eth_dev,
struct rte_kvargs *kvlist)
@@ -1043,6 +1056,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
+ rx_queue->ignore_outgoing = ignore_outgoing != 0;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
@@ -1206,6 +1220,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
unsigned int qpairs = 1;
unsigned int qdisc_bypass = 1;
const char *fanout_mode = NULL;
+ unsigned int ignore_outgoing = 0;
/* do some parameter checking */
if (*sockfd < 0)
@@ -1272,6 +1287,11 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
fanout_mode = pair->value;
continue;
}
+ if (strstr(pair->key, ETH_AF_PACKET_IGNORE_OUTGOING_ARG) != NULL) {
+ if (parse_uint(pair->key, pair->value, &ignore_outgoing, 1) < 0)
+ return -1;
+ continue;
+ }
}
if (framesize > blocksize) {
@@ -1298,12 +1318,14 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, fanout_mode);
else
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, "default PACKET_FANOUT_HASH");
+ PMD_LOG(DEBUG, "%s:\tignore outgoing %d", name, ignore_outgoing);
if (rte_pmd_init_internals(dev, *sockfd, qpairs,
blocksize, blockcount,
framesize, framecount,
qdisc_bypass,
fanout_mode,
+ ignore_outgoing,
&internals, ð_dev,
kvlist) < 0)
return -1;
@@ -1401,4 +1423,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,
"framesz=<int> "
"framecnt=<int> "
"qdisc_bypass=<0|1> "
- "fanout_mode=<hash|lb|cpu|rollover|rnd|qm>");
+ "fanout_mode=<hash|lb|cpu|rollover|rnd|qm> "
+ "ignore_outgoing=<0|1>");
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v5 2/3] net/af_packet: use kernel to filter single-queue Rx
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:10 ` [PATCH v5 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
@ 2026-10-09 15:10 ` Frank Dressler
2026-10-09 15:10 ` [PATCH v5 3/3] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
` (2 subsequent siblings)
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:10 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
Currently, when the ignore_outgoing devarg is 1, the PMD still
receives outgoing packets from the kernel but skips over them so
that rte_eth_rx_burst() only returns incoming packets.
Since Linux kernel v4.20, the PACKET_IGNORE_OUTGOING socket option
prevents the kernel from delivering outgoing packets to an
AF_PACKET socket.
This change sets this socket option so that the PMD does not need to
filter out outgoing packets anymore. However, this socket option does
not work for packet sockets which are in a fanout group, so it
is only set in the single-queue scenario. When multiple queues
are used, the PMD-side filtering serves as a fallback.
The kernel has supported this flag since v4.20, and DPDK requires
at least v5.4, so no feature check has been added.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
doc/guides/rel_notes/release_26_11.rst | 3 ++-
drivers/net/af_packet/rte_eth_af_packet.c | 14 +++++++++++++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index daf5603a41..0cf7671cbb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,7 +67,8 @@ New Features
* **Updated AF_PACKET driver.**
Added ``ignore_outgoing`` vdev argument to prevent `rte_eth_rx_burst()` from
- returning transmitted packets.
+ returning transmitted packets. The single-queue scenario is accelerated,
+ as the kernel does the filtering.
* **Updated AF_XDP driver.**
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 837da9a3af..085bfa9af9 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -1038,6 +1038,18 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
#endif
}
+ if (ignore_outgoing != 0 && nb_queues == 1) {
+ rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
+ &ignore_outgoing, sizeof(ignore_outgoing));
+ if (rc == -1) {
+ PMD_LOG_ERRNO(ERR,
+ "%s: could not set PACKET_IGNORE_OUTGOING "
+ "on AF_PACKET socket for %s",
+ name, pair->value);
+ goto error;
+ }
+ }
+
rc = setsockopt(qsockfd, SOL_PACKET, PACKET_RX_RING, req, sizeof(*req));
if (rc == -1) {
PMD_LOG_ERRNO(ERR,
@@ -1056,7 +1068,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
- rx_queue->ignore_outgoing = ignore_outgoing != 0;
+ rx_queue->ignore_outgoing = ignore_outgoing != 0 && nb_queues > 1;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v5 3/3] net/af_packet: use kernel to filter multi-queue Rx
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:10 ` [PATCH v5 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
2026-10-09 15:10 ` [PATCH v5 2/3] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
@ 2026-10-09 15:10 ` Frank Dressler
2026-10-09 20:03 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-09 15:10 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
The AF_PACKET PMD currently needs to filter out outgoing packets in
the PMD when the ignore_outgoing devarg is enabled and multiple queues
are used. Kernel-side filtering is only supported for the single-queue
scenario.
Since v6.2 the Linux kernel supports filtering outgoing packets for
AF_PACKET socket fanout groups via PACKET_FANOUT_FLAG_IGNORE_OUTGOING
which means that the PMD does not need to do this anymore.
Earlier kernels ignore this flag and have no direct way to detect
whether the flag is supported.
This change opportunistically sets this flag for all kernel versions
in multi-queue scenarios. Kernels that do not support this flag
silently ignore it. Filtering outgoing packets then falls back to the
PMD-based approach.
With this change, for all kernels v6.2 or newer, all the filtering of
outgoing packets will happen in the kernel.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
doc/guides/rel_notes/release_26_11.rst | 4 ++--
drivers/net/af_packet/rte_eth_af_packet.c | 13 +++++++++++++
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0cf7671cbb..c4ef38b362 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,8 +67,8 @@ New Features
* **Updated AF_PACKET driver.**
Added ``ignore_outgoing`` vdev argument to prevent `rte_eth_rx_burst()` from
- returning transmitted packets. The single-queue scenario is accelerated,
- as the kernel does the filtering.
+ returning transmitted packets. Single queue is always filtered in the kernel.
+ Multiple queues are filtered in the kernel since v6.2, otherwise in the PMD.
* **Updated AF_XDP driver.**
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 085bfa9af9..e3ed511b45 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -32,6 +32,11 @@
#include <unistd.h>
#include <poll.h>
+/* Added in Linux 6.2. Older kernels ignore it. */
+#ifndef PACKET_FANOUT_FLAG_IGNORE_OUTGOING
+#define PACKET_FANOUT_FLAG_IGNORE_OUTGOING 0x4000
+#endif
+
#define ETH_AF_PACKET_IFACE_ARG "iface"
#define ETH_AF_PACKET_NUM_Q_ARG "qpairs"
#define ETH_AF_PACKET_BLOCKSIZE_ARG "blocksz"
@@ -995,6 +1000,14 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
goto error;
}
+ /*
+ * Set the flag opportunistically if multiple queues are requested.
+ * It takes effect from Linux v6.2. Older kernels ignore it and the
+ * PMD filters instead.
+ */
+ if (ignore_outgoing != 0 && nb_queues > 1)
+ fanout_arg |= PACKET_FANOUT_FLAG_IGNORE_OUTGOING << 16;
+
for (q = 0; q < nb_queues; q++) {
/* Open an AF_PACKET socket for this queue... */
qsockfd = socket(AF_PACKET, SOCK_RAW, 0);
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
` (2 preceding siblings ...)
2026-10-09 15:10 ` [PATCH v5 3/3] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
@ 2026-10-09 20:03 ` Stephen Hemminger
2026-10-15 11:55 ` Frank Dressler
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
4 siblings, 1 reply; 23+ messages in thread
From: Stephen Hemminger @ 2026-10-09 20:03 UTC (permalink / raw)
To: Frank Dressler; +Cc: dev
On Fri, 9 Oct 2026 16:10:12 +0100
Frank Dressler <frank@dressler.pro> wrote:
> In contrast to other PMDs, the AF_PACKET PMD does not only return
> incoming but also outgoing packets from rte_eth_rx_burst(). This is
> fine for applications similar to tcpdump which usually want to see
> both. But it is not always the desired behavior. For example, the
> AF_PACKET PMD might be used in a testing environment in which no
> regular ethernet PMD works. In such an environment, the AF_PACKET
> PMD must behave like a regular one, i.e. not return sent packets
> from rte_eth_rx_burst(). One could use the AF_XDP PMD for this but
> it has different dependencies than the AF_PACKET PMD because it
> requires the ``bpf()`` syscall, for example, which might not be
> available.
>
> For this reason, this series adds the ignore_outgoing=<0|1> devarg.
> When set, the PMD ensures that rte_eth_rx_burst() only returns
> incoming traffic. The default is 0 so that existing software works
> as before.
>
> The first patch does this filtering in the PMD because this works
> in all supported kernel versions and for all queue counts.
>
> The second patch adds PACKET_IGNORE_OUTGOING which moves the
> filtering into the kernel. It is supported since v4.20 but only
> works for single-queue AF_PACKET scenarios. Multi-queue AF_PACKET
> is still filtered in the PMD.
>
> The third patch adds PACKET_FANOUT_FLAG_IGNORE_OUTGOING which is
> supported since kernel v6.2 and moves the filtering into the kernel
> for the multi-queue scenarios. The kernel does not expose a direct
> way to check whether it supports PACKET_FANOUT_FLAG_IGNORE_OUTGOING
> but it also does not return an error when the sockopt is set on
> kernels that do not support it. Because of that the sockopt is set
> opportunistically. For kernels v6.2 the filtering will then happen
> in the kernel and for earlier versions the PMD does the filtering
> for the multi-queue scenario.
>
> Overall we get the following:
>
> | Single Queue | Multi-Queue
> ------------+--------------+-------------
> Kernel 5.4+ | in kernel | in PMD
> Kernel 6.2+ | in kernel | in kernel
>
> I tested this on both, kernel 7.2.8 and kernel 5.4.0 with both single
> and multiple queues. My results, strace'ing testpmd 7.2.8, with the
> variants of ignore_outgoing=0|1 and single/multi-queue:
>
> Kernel 5.4.0:
> qpairs=1,ignore_outgoing=0: -
> qpairs=1,ignore_outgoing=1: setsockopt(14, SOL_PACKET, 0x17 /* PACKET_??? */, [1], 4) = 0
> // 0x17 is PACKET_IGNORE_OUTGOING
>
> qpairs=2,ignore_outgoing=0: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-1878917532], 4) = 0
> // -1878917532 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
> qpairs=2,ignore_outgoing=1: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-805175699], 4) = 0
> // -805175699 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
>
> Kernel 7.2.8:
> qpairs=1,ignore_outgoing=0: -
> qpairs=1,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_IGNORE_OUTGOING, [1], 4) = 0
>
> qpairs=2,ignore_outgoing=0: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-1878978899], 4) = 0
> // -1878978899 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
> qpairs=2,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-805236964], 4) = 0
> // -805236964 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
>
> They show that the setsockopt succeeeds even on the old kernel that
> does not support PACKET_FANOUT_FLAG_IGNORE_OUTGOING, so setting it
> opportunistically is fine. They also show that the code only tries to
> set PACKET_FANOUT_FLAG_IGNORE_OUTGOING or PACKET_IGNORE_OUTGOING when
> requested by ignore_outgoing=1. And it shows that
> PACKET_IGNORE_OUTGOING is used for single and
> PACKET_FANOUT_FLAG_IGNORE_OUTGOING for multi-queue.
>
> I have also manually ran testpmd in all modes and both kernels to
> verify that ignore_outgoing works as expected, sending packets and
> checking what testpmd was receiving (``set verbose 1``).
I see one warning from AI review worth fixing.
Series applies to main (1d020e1) and builds at each commit with
-Dwerror=true. Kernel filtering is right in both cases now: single
queue uses per-socket PACKET_IGNORE_OUTGOING (no fanout group), multi
queue sets PACKET_FANOUT_FLAG_IGNORE_OUTGOING on the group with the
sll_pkttype check as fallback for pre-6.2 kernels. The v3 fanout
problem is resolved.
The test still cannot tell a working filter from a dead port.
[PATCH v5 1/3] net/af_packet: filter Rx of outgoing packets in the PMD
Warning
1. ignore_outgoing=1 test passes if the port receives nothing.
if (ignore_outgoing)
TEST_ASSERT(rx_pkts == 0, ...
Only outgoing traffic is generated, so a queue that drops
everything passes. Echo the transmitted frames back through
tap_fd, as test_af_packet_loopback() does, so they arrive as
incoming. The kernel only ever transmits on the tap, so for
ignore_outgoing=1 the count is exact:
ignore_outgoing=1: rx_pkts == nb_tx (echoes only)
ignore_outgoing=0: rx_pkts >= 2 * nb_tx (sent + echoes)
After do_tx_burst(), with tap_fd set O_NONBLOCK:
while (echoed < nb_tx && elapsed < LOOPBACK_TIMEOUT_US) {
n = read(tap_fd, tap_buf, sizeof(tap_buf));
if (n > 0) {
if (write(tap_fd, tap_buf, n) == n)
echoed++;
continue;
}
rte_delay_us_block(STATS_POLL_INTERVAL_US);
elapsed += STATS_POLL_INTERVAL_US;
}
then poll all Rx queues and check the counts. With qpairs=2
this also covers the fanout path.
Info
2. Use ``rte_eth_rx_burst()`` (double backticks) in af_packet.rst
and release_26_11.rst. Single backticks are RST interpreted text,
not literal; both files use double elsewhere. Also in 2/3, 3/3.
[PATCH v5 3/3] net/af_packet: use kernel to filter multi-queue Rx
Info
3. Commit message says the PMD "does not need to do this anymore",
but rx_queue->ignore_outgoing stays set for nb_queues > 1, so the
sll_pkttype check runs per frame on 6.2+ as well. Keeping it is
correct (the flag cannot be probed, and sll is in the same cache
line as the tpacket2_hdr). Fix the text, not the code:
The PMD check is kept, since older kernels accept the flag
without effect and support cannot be detected.
4. Release note "filtered in the kernel since v6.2" reads as a DPDK
version. Say "Linux 6.2". The kernel version detail fits better
in af_packet.rst next to the devarg than in the release notes.
PS: AI review also mentioned pre-existing bug.
A separate, older bug turned up while checking fanout_add() in the kernel. get_fanout_mode() always ORs in PACKET_FANOUT_FLAG_ROLLOVER, and the kernel returns -EINVAL for PACKET_FANOUT_ROLLOVER combined with that flag. So fanout_mode=rollover with qpairs > 1 always fails setsockopt. It’s not part of this series, but it deserves its own Fixes patch.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 0/4] net/af_packet: do not return outgoing packets from rte_eth_rx_burst
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
` (3 preceding siblings ...)
2026-10-09 20:03 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
@ 2026-10-15 11:14 ` Frank Dressler
2026-10-15 11:14 ` [PATCH v6 1/4] test/af_packet: ignore stray packets in loopback test Frank Dressler
` (4 more replies)
4 siblings, 5 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-15 11:14 UTC (permalink / raw)
To: stephen; +Cc: dev
In contrast to other PMDs, the AF_PACKET PMD does not only return
incoming but also outgoing packets from rte_eth_rx_burst(). This is
fine for applications similar to tcpdump which usually want to see
both. But it is not always the desired behavior. For example, the
AF_PACKET PMD might be used in a testing environment in which no
regular ethernet PMD works. In such an environment, the AF_PACKET
PMD must behave like a regular one, i.e. not return sent packets
from rte_eth_rx_burst(). One could use the AF_XDP PMD for this but
it has different dependencies than the AF_PACKET PMD because it
requires the bpf() syscall, for example, which might not be
available.
For this reason, this series adds the ignore_outgoing=<0|1> devarg.
When set, the PMD ensures that rte_eth_rx_burst() only returns
incoming traffic. The default is 0 so that existing software works
as before.
The ignore_outgoing test tags the frames it injects so that it can
distinguish them from stray frames, which would otherwise be counted
as well and could lead to a wrong result. As the pre-existing
loopback test could suffer from stray frames as well, the first patch
changes it to use tagged frames.
The second patch adds filtering of outgoing packets in the PMD
because that works in all supported kernel versions and for all
queue counts. The test creates an AF_PACKET vdev and injects frames
at the fd end of the TAP device and via a second AF_PACKET vdev.
Frames from the fd end arrive as incoming traffic and frames from
the second vdev as outgoing traffic, so with ignore_outgoing=1 the
first vdev should return only the incoming frames. The frames from
the fd end are required to distinguish a dead device from one which
dropped the outgoing frames.
The third patch adds PACKET_IGNORE_OUTGOING which moves the
filtering into the kernel. It is supported since Linux 4.20 but only
works for single-queue AF_PACKET scenarios. Multi-queue AF_PACKET
is still filtered in the PMD.
The fourth patch adds PACKET_FANOUT_FLAG_IGNORE_OUTGOING which is
supported since Linux 6.2 and moves the filtering into the kernel
for the multi-queue scenarios. The kernel does not expose a direct
way to check whether it supports PACKET_FANOUT_FLAG_IGNORE_OUTGOING
but it also does not return an error when the sockopt is set on
kernels that do not support it. Because of that the sockopt is set
opportunistically. The PMD check is kept, since older kernels accept
the flag without effect and support cannot be detected.
Overall we get the following:
| Single Queue | Multi-Queue
------------+--------------+-------------
Kernel 5.4+ | in kernel | in PMD
Kernel 6.2+ | in kernel | in kernel
I tested this on both, kernel 7.2.8 and kernel 5.4.0 with both single
and multiple queues. My results, strace'ing testpmd 7.2.8, with the
variants of ignore_outgoing=0|1 and single/multi-queue:
Kernel 5.4.0:
qpairs=1,ignore_outgoing=0: -
qpairs=1,ignore_outgoing=1: setsockopt(14, SOL_PACKET, 0x17 /* PACKET_??? */, [1], 4) = 0
// 0x17 is PACKET_IGNORE_OUTGOING
qpairs=2,ignore_outgoing=0: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-1878917532], 4) = 0
// -1878917532 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
qpairs=2,ignore_outgoing=1: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-805175699], 4) = 0
// -805175699 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
Kernel 7.2.8:
qpairs=1,ignore_outgoing=0: -
qpairs=1,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_IGNORE_OUTGOING, [1], 4) = 0
qpairs=2,ignore_outgoing=0: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-1878978899], 4) = 0
// -1878978899 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
qpairs=2,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-805236964], 4) = 0
// -805236964 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
They show that the setsockopt succeeds even on the old kernel that
does not support PACKET_FANOUT_FLAG_IGNORE_OUTGOING, so setting it
opportunistically is fine. They also show that the code only tries to
set PACKET_FANOUT_FLAG_IGNORE_OUTGOING or PACKET_IGNORE_OUTGOING when
requested by ignore_outgoing=1. And it shows that
PACKET_IGNORE_OUTGOING is used for single and
PACKET_FANOUT_FLAG_IGNORE_OUTGOING for multi-queue.
I have also manually ran testpmd in all modes and both kernels to
verify that ignore_outgoing works as expected, sending packets and
checking what testpmd was receiving (``set verbose 1``).
v6:
- add tags to frames to distinguish injected ones from stray ones
- update the loopback and ignore_outgoing tests to count only tagged
frames
- inject frames as incoming to distinguish a dead port from a
working one in ignore_outgoing=1
- bound the receive flush so a continuous stream cannot hang the test
- quote rte_eth_rx_burst() with double backticks in the docs
- use Linux 6.2 instead of v6.2 throughout the code and the docs
- add a paragraph to af_packet.rst on single-queue and multi-queue
filtering
v5:
- remove Co-Authored-By line in patch 2
v4:
- split into PMD filtering, PACKET_IGNORE_OUTGOING for the
single-queue scenario, and PACKET_FANOUT_FLAG_IGNORE_OUTGOING for
multiple queues
- set the fanout flag for all kernel versions. kernels that do not
support it silently ignore it and filtering falls back to the
PMD-based approach
- changed the test to test both single and multi-queue
v3:
- drop #ifdef PACKET_IGNORE_OUTGOING (uapi since 4.20; DPDK requires kernel >= 5.4)
- fail the test if ignore_outgoing=1 cannot create a port
- drop kernel >= 4.20 notes from docs and release notes
v2:
- use PACKET_IGNORE_OUTGOING / ignore_outgoing= instead of capture_dir
Frank Dressler (4):
test/af_packet: ignore stray packets in loopback test
net/af_packet: filter Rx of outgoing packets in the PMD
net/af_packet: use kernel to filter single-queue Rx
net/af_packet: use kernel to filter multi-queue Rx
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 301 ++++++++++++++++++++--
doc/guides/nics/af_packet.rst | 8 +
doc/guides/rel_notes/release_26_11.rst | 7 +
drivers/net/af_packet/rte_eth_af_packet.c | 50 +++-
5 files changed, 345 insertions(+), 22 deletions(-)
--
2.56.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 1/4] test/af_packet: ignore stray packets in loopback test
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
@ 2026-10-15 11:14 ` Frank Dressler
2026-10-15 11:14 ` [PATCH v6 2/4] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
` (3 subsequent siblings)
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-15 11:14 UTC (permalink / raw)
To: stephen; +Cc: dev
The loopback test counts looped back frames. But there can be
unexpected frames among them such as IPv6 Neighbor Discovery
packets injected by the kernel which can lead to unexpected results.
Tag sent frames in the source MAC and count only matching ones.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
.mailmap | 1 +
app/test/test_pmd_af_packet.c | 58 ++++++++++++++++++++++++++---------
2 files changed, 44 insertions(+), 15 deletions(-)
diff --git a/.mailmap b/.mailmap
index 67ff247224..8c49fcb6f3 100644
--- a/.mailmap
+++ b/.mailmap
@@ -509,6 +509,7 @@ Francis Kelly <fkelly@nvidia.com> <fkelly@mellanox.com>
Francis Racicot <francis.racicot@intel.com>
Franck Lenormand <franck.lenormand@nxp.com>
François-Frédéric Ozog <ff@ozog.com>
+Frank Dressler <frank@dressler.pro>
Frank Du <frank.du@intel.com>
Frank Zhao <frank.zhao@starfivetech.com>
Frederico Cadete <frederico.cadete-ext@oneaccess-net.com>
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index b668ca5b81..74eb47f1a2 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -44,6 +44,14 @@ test_pmd_af_packet(void)
#define BURST_SIZE 32
#define RING_SIZE 256
+/* Length of Ethernet frames built by the tests */
+#define TEST_FRAME_LEN (sizeof(struct rte_ether_hdr) + 46)
+
+/* Source MAC used to recognise frames sent by this test */
+static const struct rte_ether_addr DEV_MAC_TAG = {
+ .addr_bytes = { 'P', 'A', 'C', 'K', 'E', 'T' },
+};
+
/* Polling and timeout constants */
#define STATS_POLL_INTERVAL_US 100
#define STATS_POLL_TIMEOUT_US 100000 /* 100ms overall timeout */
@@ -206,23 +214,39 @@ alloc_tx_mbufs(struct rte_mbuf **bufs, unsigned int count)
for (i = 0; i < count; i++) {
struct rte_ether_hdr *eth_hdr;
+
eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(bufs[i],
- sizeof(struct rte_ether_hdr) + 46);
+ TEST_FRAME_LEN);
if (eth_hdr == NULL) {
/* Free all allocated mbufs on failure */
rte_pktmbuf_free_bulk(bufs, count);
return 0;
}
- /* Set broadcast destination and zero source MAC */
+ /* Set broadcast destination and DEV_MAC_TAG source MAC */
memset(ð_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN);
- memset(ð_hdr->src_addr, 0x00, RTE_ETHER_ADDR_LEN);
+ rte_ether_addr_copy(&DEV_MAC_TAG, ð_hdr->src_addr);
eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
}
return count;
}
+/*
+ * Helper: Indicates whether the mbuf source MAC is DEV_MAC_TAG.
+ */
+static bool
+is_tagged_mbuf(struct rte_mbuf *buf)
+{
+ struct rte_ether_hdr *eth;
+
+ if (rte_pktmbuf_data_len(buf) < sizeof(*eth))
+ return false;
+
+ eth = rte_pktmbuf_mtod(buf, struct rte_ether_hdr *);
+ return rte_is_same_ether_addr(ð->src_addr, &DEV_MAC_TAG);
+}
+
/*
* Helper: Transmit packets and free unsent mbufs.
* Returns the number of packets successfully transmitted.
@@ -243,15 +267,22 @@ do_tx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
/*
* Helper: Receive packets and free received mbufs.
+ * If count_tagged is non-NULL, count DEV_MAC_TAG frames in it.
* Returns the number of packets received.
*/
static uint16_t
do_rx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
- unsigned int count)
+ unsigned int count, unsigned int *count_tagged)
{
- uint16_t nb_rx;
+ uint16_t i, nb_rx;
nb_rx = rte_eth_rx_burst(pid, queue_id, bufs, count);
+ if (count_tagged != NULL) {
+ for (i = 0; i < nb_rx; i++) {
+ if (is_tagged_mbuf(bufs[i]))
+ (*count_tagged)++;
+ }
+ }
rte_pktmbuf_free_bulk(bufs, nb_rx);
return nb_rx;
@@ -527,7 +558,7 @@ test_af_packet_rx(void)
}
/* Try to receive packets (non-blocking) using helper */
- do_rx_burst(port_id, 0, bufs, BURST_SIZE);
+ do_rx_burst(port_id, 0, bufs, BURST_SIZE, NULL);
/* RX from tap interface without external traffic will return 0 */
/* This test just verifies the RX path doesn't crash */
@@ -544,7 +575,8 @@ test_af_packet_loopback(void)
{
struct rte_mbuf *tx_bufs[BURST_SIZE];
struct rte_mbuf *rx_bufs[BURST_SIZE];
- uint16_t nb_tx, total_rx = 0;
+ uint16_t nb_tx;
+ unsigned int count_tagged = 0;
uint64_t elapsed = 0;
unsigned int allocated;
char tap_buf[2048];
@@ -588,14 +620,10 @@ test_af_packet_loopback(void)
}
/* Try to receive via af_packet RX */
- uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, rx_bufs, BURST_SIZE);
- if (nb_rx > 0) {
- total_rx += nb_rx;
- rte_pktmbuf_free_bulk(rx_bufs, nb_rx);
- }
+ do_rx_burst(port_id, 0, rx_bufs, BURST_SIZE, &count_tagged);
/* Check if we've received enough */
- if (total_rx >= nb_tx)
+ if (count_tagged >= nb_tx)
break;
rte_delay_us_block(STATS_POLL_INTERVAL_US);
@@ -609,7 +637,7 @@ test_af_packet_loopback(void)
* Note: We may not receive all packets due to timing, but receiving
* any packets proves the loopback path works.
*/
- printf("Loopback: TX=%u, RX=%u\n", nb_tx, total_rx);
+ printf("Loopback: TX=%u, RX=%u\n", nb_tx, count_tagged);
return TEST_SUCCESS;
}
@@ -982,7 +1010,7 @@ test_af_packet_multi_queue(void)
/* Test RX on different queues */
for (q = 0; q < num_queues; q++)
- do_rx_burst(test_port, q, bufs, BURST_SIZE);
+ do_rx_burst(test_port, q, bufs, BURST_SIZE, NULL);
/* Cleanup */
rte_eth_dev_stop(test_port);
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v6 2/4] net/af_packet: filter Rx of outgoing packets in the PMD
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
2026-10-15 11:14 ` [PATCH v6 1/4] test/af_packet: ignore stray packets in loopback test Frank Dressler
@ 2026-10-15 11:14 ` Frank Dressler
2026-10-15 11:14 ` [PATCH v6 3/4] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
` (2 subsequent siblings)
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-15 11:14 UTC (permalink / raw)
To: stephen; +Cc: dev
In contrast to other PMDs, the AF_PACKET PMD does not only return
incoming but also outgoing packets from rte_eth_rx_burst(). This is
fine for applications similar to tcpdump which usually want to see
both.
But this is not always the desired behavior. For example, the
AF_PACKET PMD might be used in a testing environment in which no
regular ethernet PMD is available. In such an environment, the
PMD must behave like a regular one, i.e. not return sent packets
from rte_eth_rx_burst().
For this reason, this change adds the ignore_outgoing=<0|1> devarg.
When set, the PMD ensures that rte_eth_rx_burst() only returns
incoming traffic. The default is 0 so that existing software works
as before.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
app/test/test_pmd_af_packet.c | 275 ++++++++++++++++++++--
doc/guides/nics/af_packet.rst | 2 +
doc/guides/rel_notes/release_26_11.rst | 5 +
drivers/net/af_packet/rte_eth_af_packet.c | 25 +-
4 files changed, 284 insertions(+), 23 deletions(-)
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
index 74eb47f1a2..6410caffcc 100644
--- a/app/test/test_pmd_af_packet.c
+++ b/app/test/test_pmd_af_packet.c
@@ -47,15 +47,27 @@ test_pmd_af_packet(void)
/* Length of Ethernet frames built by the tests */
#define TEST_FRAME_LEN (sizeof(struct rte_ether_hdr) + 46)
-/* Source MAC used to recognise frames sent by this test */
+/* Source MAC address used for frames sent by the TAP device */
static const struct rte_ether_addr DEV_MAC_TAG = {
.addr_bytes = { 'P', 'A', 'C', 'K', 'E', 'T' },
};
+/* Source MAC address used for frames sent by the TAP file descriptor */
+static const struct rte_ether_addr FD_MAC_TAG = {
+ .addr_bytes = { 'T', 'E', 'K', 'C', 'A', 'P' },
+};
+
+/* Count of frames with each tag */
+struct tag_count {
+ unsigned int n_dev_tagged;
+ unsigned int n_fd_tagged;
+};
+
/* Polling and timeout constants */
#define STATS_POLL_INTERVAL_US 100
#define STATS_POLL_TIMEOUT_US 100000 /* 100ms overall timeout */
#define LOOPBACK_TIMEOUT_US 500000 /* 500ms for loopback test */
+#define RX_FLUSH_TIMEOUT_US 100000 /* 100ms to drain queued frames */
/* Multi-threaded test constants */
#define MT_NUM_PACKETS 100
@@ -198,6 +210,28 @@ configure_af_packet_port(uint16_t pid, uint16_t nb_rx_q, uint16_t nb_tx_q)
return 0;
}
+/*
+ * Helper: Fill a minimal Ethernet frame with a broadcast destination and
+ * the given source MAC.
+ * Returns 0 on success, -1 otherwise.
+ */
+static int
+init_frame(void *frame, size_t len, const struct rte_ether_addr *src)
+{
+ struct rte_ether_hdr *eth = frame;
+
+ if (len < TEST_FRAME_LEN)
+ return -1;
+
+ /* Set broadcast destination and source MAC */
+ memset(frame, 0, TEST_FRAME_LEN);
+ memset(ð->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN);
+ rte_ether_addr_copy(src, ð->src_addr);
+ eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+ return 0;
+}
+
/*
* Helper: Allocate and prepare a burst of TX mbufs with minimal Ethernet frames.
* Returns the number of successfully allocated mbufs.
@@ -213,30 +247,25 @@ alloc_tx_mbufs(struct rte_mbuf **bufs, unsigned int count)
return 0;
for (i = 0; i < count; i++) {
- struct rte_ether_hdr *eth_hdr;
+ void *hdr;
- eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(bufs[i],
- TEST_FRAME_LEN);
- if (eth_hdr == NULL) {
+ hdr = rte_pktmbuf_append(bufs[i], TEST_FRAME_LEN);
+ if (hdr == NULL ||
+ init_frame(hdr, TEST_FRAME_LEN, &DEV_MAC_TAG) != 0) {
/* Free all allocated mbufs on failure */
rte_pktmbuf_free_bulk(bufs, count);
return 0;
}
-
- /* Set broadcast destination and DEV_MAC_TAG source MAC */
- memset(ð_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN);
- rte_ether_addr_copy(&DEV_MAC_TAG, ð_hdr->src_addr);
- eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
}
return count;
}
/*
- * Helper: Indicates whether the mbuf source MAC is DEV_MAC_TAG.
+ * Helper: Indicates whether the mbuf source MAC matches tag.
*/
static bool
-is_tagged_mbuf(struct rte_mbuf *buf)
+is_tagged_mbuf(struct rte_mbuf *buf, const struct rte_ether_addr *tag)
{
struct rte_ether_hdr *eth;
@@ -244,7 +273,7 @@ is_tagged_mbuf(struct rte_mbuf *buf)
return false;
eth = rte_pktmbuf_mtod(buf, struct rte_ether_hdr *);
- return rte_is_same_ether_addr(ð->src_addr, &DEV_MAC_TAG);
+ return rte_is_same_ether_addr(ð->src_addr, tag);
}
/*
@@ -267,20 +296,22 @@ do_tx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
/*
* Helper: Receive packets and free received mbufs.
- * If count_tagged is non-NULL, count DEV_MAC_TAG frames in it.
+ * If tag_count is non-NULL, count tags in it.
* Returns the number of packets received.
*/
static uint16_t
do_rx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
- unsigned int count, unsigned int *count_tagged)
+ unsigned int count, struct tag_count *tag_count)
{
uint16_t i, nb_rx;
nb_rx = rte_eth_rx_burst(pid, queue_id, bufs, count);
- if (count_tagged != NULL) {
+ if (tag_count != NULL) {
for (i = 0; i < nb_rx; i++) {
- if (is_tagged_mbuf(bufs[i]))
- (*count_tagged)++;
+ if (is_tagged_mbuf(bufs[i], &DEV_MAC_TAG))
+ tag_count->n_dev_tagged++;
+ else if (is_tagged_mbuf(bufs[i], &FD_MAC_TAG))
+ tag_count->n_fd_tagged++;
}
}
rte_pktmbuf_free_bulk(bufs, nb_rx);
@@ -288,6 +319,46 @@ do_rx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
return nb_rx;
}
+/*
+ * Helper: Discard frames already queued.
+ */
+static void
+flush_rx_port(uint16_t pid, uint16_t nb_queues)
+{
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint64_t flush_deadline;
+ uint16_t q;
+
+ flush_deadline = rte_get_timer_cycles() +
+ (rte_get_timer_hz() * RX_FLUSH_TIMEOUT_US) / US_PER_S;
+
+ for (q = 0; q < nb_queues; q++)
+ while (do_rx_burst(pid, q, bufs, BURST_SIZE, NULL) > 0 &&
+ rte_get_timer_cycles() < flush_deadline)
+ ;
+}
+
+/*
+ * Helper: Write FD_MAC_TAG frames to the TAP fd so they appear as
+ * incoming packets on the netdev.
+ */
+static int
+do_tap_fd_tx_burst(int fd, unsigned int n)
+{
+ unsigned int i;
+ uint8_t frame[TEST_FRAME_LEN];
+
+ if (init_frame(frame, sizeof(frame), &FD_MAC_TAG) != 0)
+ return -1;
+
+ for (i = 0; i < n; i++) {
+ if (write(fd, frame, sizeof(frame)) != (ssize_t)sizeof(frame))
+ return -1;
+ }
+
+ return 0;
+}
+
/*
* Helper: Wait for stats to update by polling at intervals.
* Returns 0 on success (stats updated), -1 on timeout.
@@ -576,7 +647,7 @@ test_af_packet_loopback(void)
struct rte_mbuf *tx_bufs[BURST_SIZE];
struct rte_mbuf *rx_bufs[BURST_SIZE];
uint16_t nb_tx;
- unsigned int count_tagged = 0;
+ struct tag_count tag_count = {0};
uint64_t elapsed = 0;
unsigned int allocated;
char tap_buf[2048];
@@ -620,10 +691,10 @@ test_af_packet_loopback(void)
}
/* Try to receive via af_packet RX */
- do_rx_burst(port_id, 0, rx_bufs, BURST_SIZE, &count_tagged);
+ do_rx_burst(port_id, 0, rx_bufs, BURST_SIZE, &tag_count);
/* Check if we've received enough */
- if (count_tagged >= nb_tx)
+ if (tag_count.n_dev_tagged >= nb_tx)
break;
rte_delay_us_block(STATS_POLL_INTERVAL_US);
@@ -637,7 +708,7 @@ test_af_packet_loopback(void)
* Note: We may not receive all packets due to timing, but receiving
* any packets proves the loopback path works.
*/
- printf("Loopback: TX=%u, RX=%u\n", nb_tx, count_tagged);
+ printf("Loopback: TX=%u, RX=%u\n", nb_tx, tag_count.n_dev_tagged);
return TEST_SUCCESS;
}
@@ -1111,6 +1182,163 @@ test_af_packet_multithread(void)
return TEST_SUCCESS;
}
+/*
+ * Test: Ignore outgoing configuration
+ *
+ * By default an AF_PACKET socket sees frames sent by other sockets on
+ * the same interface. ignore_outgoing filters those out. This test
+ * uses a second vdev to generate outgoing traffic. It also writes to
+ * the TAP fd to generate incoming traffic, so that a dead RX port can
+ * be distinguished from one which filtered the outgoing frames.
+ */
+static int
+test_af_packet_ignore_outgoing(uint16_t nb_queues, bool ignore_outgoing)
+{
+ struct rte_mbuf *bufs[BURST_SIZE];
+ uint16_t tx_port, rx_port, nb_tx, q;
+ struct tag_count tag_count = {0};
+ unsigned int allocated;
+ const unsigned int n_inject = 4;
+ uint64_t elapsed = 0;
+ const char *err = NULL;
+ bool have_rx = false;
+ bool have_tx = false;
+ char args[128];
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ snprintf(args, sizeof(args),
+ "iface=%s,qpairs=%u,ignore_outgoing=%u",
+ TAP_DEV_NAME, nb_queues, ignore_outgoing ? 1 : 0);
+ ret = create_af_packet_port("net_af_packet_ign_rx", args, &rx_port);
+ if (ret != 0) {
+ err = "Failed to create RX af_packet port";
+ goto out;
+ }
+ have_rx = true;
+
+ /* qdisc_bypass=0 so other AF_PACKET sockets see this TX */
+ ret = create_af_packet_port("net_af_packet_ign_tx",
+ "iface=" TAP_DEV_NAME ",qdisc_bypass=0", &tx_port);
+ if (ret != 0) {
+ err = "Failed to create TX af_packet port";
+ goto out;
+ }
+ have_tx = true;
+
+ if (configure_af_packet_port(rx_port, nb_queues, nb_queues) != 0 ||
+ configure_af_packet_port(tx_port, 1, 1) != 0) {
+ err = "Failed to configure ports";
+ goto out;
+ }
+
+ flush_rx_port(rx_port, nb_queues);
+
+ /* send outgoing packets from the TX af_packet port */
+ allocated = alloc_tx_mbufs(bufs, 4);
+ nb_tx = do_tx_burst(tx_port, 0, bufs, allocated);
+ if (allocated == 0 || nb_tx == 0) {
+ err = "TX setup failed";
+ goto out;
+ }
+
+ /* inject incoming packets on the TAP */
+ if (do_tap_fd_tx_burst(tap_fd, n_inject) != 0) {
+ err = "TAP inject failed";
+ goto out;
+ }
+
+ /* wait long enough for frames to arrive */
+ while (elapsed < LOOPBACK_TIMEOUT_US) {
+ for (q = 0; q < nb_queues; q++)
+ do_rx_burst(rx_port, q, bufs, BURST_SIZE, &tag_count);
+ if (!ignore_outgoing &&
+ tag_count.n_dev_tagged >= nb_tx &&
+ tag_count.n_fd_tagged > 0)
+ break;
+ rte_delay_us_block(STATS_POLL_INTERVAL_US);
+ elapsed += STATS_POLL_INTERVAL_US;
+ }
+
+out:
+ if (have_tx) {
+ rte_eth_dev_stop(tx_port);
+ rte_eth_dev_close(tx_port);
+ rte_vdev_uninit("net_af_packet_ign_tx");
+ }
+ if (have_rx) {
+ rte_eth_dev_stop(rx_port);
+ rte_eth_dev_close(rx_port);
+ rte_vdev_uninit("net_af_packet_ign_rx");
+ }
+
+ TEST_ASSERT(err == NULL, "%s", err);
+ TEST_ASSERT(tag_count.n_fd_tagged > 0,
+ "Expected frames written to the TAP fd");
+ if (ignore_outgoing)
+ TEST_ASSERT(tag_count.n_dev_tagged == 0,
+ "Expected no outgoing frames with ignore_outgoing");
+ else
+ TEST_ASSERT(tag_count.n_dev_tagged > 0,
+ "Expected outgoing frames with ignore_outgoing=0");
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Test: invalid ignore_outgoing value
+ */
+static int
+test_af_packet_invalid_ignore_outgoing(void)
+{
+ int ret;
+
+ if (!tap_created) {
+ printf("SKIPPED: TAP interface not available (need root)\n");
+ return TEST_SKIPPED;
+ }
+
+ ret = rte_vdev_init("net_af_packet_ign_bad",
+ "iface=" TAP_DEV_NAME ",ignore_outgoing=2");
+ TEST_ASSERT(ret != 0, "Expected failure with ignore_outgoing=2");
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Test: Ignore outgoing for one queue
+ */
+static int
+test_af_packet_ignore_outgoing_single_queue(void)
+{
+ int ret;
+
+ ret = test_af_packet_ignore_outgoing(1, true);
+ if (ret != TEST_SUCCESS)
+ return ret;
+
+ return test_af_packet_ignore_outgoing(1, false);
+}
+
+/*
+ * Test: Ignore outgoing for multiple queues
+ */
+static int
+test_af_packet_ignore_outgoing_multi_queue(void)
+{
+ int ret;
+
+ ret = test_af_packet_ignore_outgoing(2, true);
+ if (ret != TEST_SUCCESS)
+ return ret;
+
+ return test_af_packet_ignore_outgoing(2, false);
+}
+
static struct unit_test_suite af_packet_test_suite = {
.suite_name = "AF_PACKET PMD Unit Test Suite",
.setup = test_af_packet_setup,
@@ -1136,6 +1364,9 @@ static struct unit_test_suite af_packet_test_suite = {
TEST_CASE(test_af_packet_frame_config),
TEST_CASE(test_af_packet_qdisc_bypass),
TEST_CASE(test_af_packet_multi_queue),
+ TEST_CASE(test_af_packet_invalid_ignore_outgoing),
+ TEST_CASE(test_af_packet_ignore_outgoing_single_queue),
+ TEST_CASE(test_af_packet_ignore_outgoing_multi_queue),
TEST_CASES_END() /**< NULL terminate unit test array */
}
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 1505b98ff7..45adbbf8b2 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -25,6 +25,8 @@ Some of these, in turn, will be used to configure the PACKET_MMAP settings.
disabled by default);
* ``fanout_mode`` - set fanout algorithm.
Possible choices: hash, lb, cpu, rollover, rnd, qm (optional, default hash);
+* ``ignore_outgoing`` - ``rte_eth_rx_burst()`` returns only incoming
+ packets (optional, disabled by default);
* ``blocksz`` - PACKET_MMAP block size (optional, default 4096);
* ``framesz`` - PACKET_MMAP frame size (optional, default 2048B; Note: multiple
of 16B);
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 207df3eca2..4bbb3333ce 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -114,6 +114,11 @@ New Features
Added internal ``flow_graph`` API in ethdev
for PMD drivers to build graph-based flow pattern parsers.
+* **Updated AF_PACKET driver.**
+
+ Added ``ignore_outgoing`` so ``rte_eth_rx_burst()`` returns only incoming
+ packets.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index ef57719803..b744305e00 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -39,6 +39,7 @@
#define ETH_AF_PACKET_FRAMECOUNT_ARG "framecnt"
#define ETH_AF_PACKET_QDISC_BYPASS_ARG "qdisc_bypass"
#define ETH_AF_PACKET_FANOUT_MODE_ARG "fanout_mode"
+#define ETH_AF_PACKET_IGNORE_OUTGOING_ARG "ignore_outgoing"
#define DFLT_FRAME_SIZE (1 << 11)
#define DFLT_FRAME_COUNT (1 << 9)
@@ -59,6 +60,7 @@ struct __rte_cache_aligned pkt_rx_queue {
uint8_t vlan_strip;
uint8_t timestamp_offloading;
uint8_t scatter_enabled;
+ uint8_t ignore_outgoing;
volatile unsigned long rx_pkts;
volatile unsigned long rx_bytes;
@@ -103,6 +105,7 @@ static const char *valid_arguments[] = {
ETH_AF_PACKET_FRAMECOUNT_ARG,
ETH_AF_PACKET_QDISC_BYPASS_ARG,
ETH_AF_PACKET_FANOUT_MODE_ARG,
+ ETH_AF_PACKET_IGNORE_OUTGOING_ARG,
NULL
};
@@ -166,6 +169,7 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
{
unsigned i;
struct tpacket2_hdr *ppd;
+ struct sockaddr_ll *sll;
struct rte_mbuf *mbuf;
uint8_t *pbuf;
struct pkt_rx_queue *pkt_q = queue;
@@ -189,6 +193,14 @@ eth_af_packet_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
if ((ppd->tp_status & TP_STATUS_USER) == 0)
break;
+ /* skip outgoing frames if ignore_outgoing is set */
+ if (pkt_q->ignore_outgoing != 0) {
+ sll = (struct sockaddr_ll *)((uint8_t *)ppd +
+ TPACKET_ALIGN(sizeof(*ppd)));
+ if (sll->sll_pkttype == PACKET_OUTGOING)
+ goto release_frame;
+ }
+
/* allocate the next mbuf */
mbuf = rte_pktmbuf_alloc(pkt_q->mb_pool);
if (unlikely(mbuf == NULL)) {
@@ -877,6 +889,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
unsigned int framecnt,
unsigned int qdisc_bypass,
const char *fanout_mode,
+ unsigned int ignore_outgoing,
struct pmd_internals **internals,
struct rte_eth_dev **eth_dev,
struct rte_kvargs *kvlist)
@@ -1043,6 +1056,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
+ rx_queue->ignore_outgoing = ignore_outgoing != 0;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
@@ -1206,6 +1220,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
unsigned int qpairs = 1;
unsigned int qdisc_bypass = 1;
const char *fanout_mode = NULL;
+ unsigned int ignore_outgoing = 0;
/* do some parameter checking */
if (*sockfd < 0)
@@ -1272,6 +1287,11 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
fanout_mode = pair->value;
continue;
}
+ if (strstr(pair->key, ETH_AF_PACKET_IGNORE_OUTGOING_ARG) != NULL) {
+ if (parse_uint(pair->key, pair->value, &ignore_outgoing, 1) < 0)
+ return -1;
+ continue;
+ }
}
if (framesize > blocksize) {
@@ -1298,12 +1318,14 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, fanout_mode);
else
PMD_LOG(DEBUG, "%s:\tfanout mode %s", name, "default PACKET_FANOUT_HASH");
+ PMD_LOG(DEBUG, "%s:\tignore outgoing %d", name, ignore_outgoing);
if (rte_pmd_init_internals(dev, *sockfd, qpairs,
blocksize, blockcount,
framesize, framecount,
qdisc_bypass,
fanout_mode,
+ ignore_outgoing,
&internals, ð_dev,
kvlist) < 0)
return -1;
@@ -1401,4 +1423,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,
"framesz=<int> "
"framecnt=<int> "
"qdisc_bypass=<0|1> "
- "fanout_mode=<hash|lb|cpu|rollover|rnd|qm>");
+ "fanout_mode=<hash|lb|cpu|rollover|rnd|qm> "
+ "ignore_outgoing=<0|1>");
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v6 3/4] net/af_packet: use kernel to filter single-queue Rx
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
2026-10-15 11:14 ` [PATCH v6 1/4] test/af_packet: ignore stray packets in loopback test Frank Dressler
2026-10-15 11:14 ` [PATCH v6 2/4] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
@ 2026-10-15 11:14 ` Frank Dressler
2026-10-15 11:14 ` [PATCH v6 4/4] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
2026-10-15 19:28 ` [PATCH v6 0/4] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-15 11:14 UTC (permalink / raw)
To: stephen; +Cc: dev
Currently, when the ignore_outgoing devarg is 1, the PMD still
receives outgoing packets from the kernel but skips over them so
that rte_eth_rx_burst() only returns incoming packets.
Since Linux 4.20, the PACKET_IGNORE_OUTGOING socket option
prevents the kernel from delivering outgoing packets to an
AF_PACKET socket.
This change sets this socket option so that the PMD does not need to
filter out outgoing packets anymore. However, this socket option does
not work for packet sockets which are in a fanout group, so it
is only set in the single-queue scenario. When multiple queues
are used, the PMD-side filtering serves as a fallback.
This flag is supported since Linux 4.20, and DPDK requires
at least Linux 5.4, so no feature check has been added.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
doc/guides/nics/af_packet.rst | 3 +++
doc/guides/rel_notes/release_26_11.rst | 3 ++-
drivers/net/af_packet/rte_eth_af_packet.c | 14 +++++++++++++-
3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 45adbbf8b2..6df14c510d 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -43,6 +43,9 @@ Only one ``fanout_mode`` can be chosen.
If left unspecified, the default is to use the ``PACKET_FANOUT_HASH`` behavior
of AF_PACKET for frame reception.
+When ``ignore_outgoing`` is set, ``PACKET_IGNORE_OUTGOING`` is set for
+single-queue ports, which moves the filtering from the PMD to the kernel.
+
Because this implementation is based on PACKET_MMAP, and PACKET_MMAP has its
own pre-requisites, it should be noted that the inner workings of PACKET_MMAP
should be carefully considered before modifying some of these options (namely,
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 4bbb3333ce..e83cb9854f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -117,7 +117,8 @@ New Features
* **Updated AF_PACKET driver.**
Added ``ignore_outgoing`` so ``rte_eth_rx_burst()`` returns only incoming
- packets.
+ packets. The single-queue scenario is accelerated, as the kernel does
+ the filtering.
* **Updated AF_XDP driver.**
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index b744305e00..44b91502fb 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -1038,6 +1038,18 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
#endif
}
+ if (ignore_outgoing != 0 && nb_queues == 1) {
+ rc = setsockopt(qsockfd, SOL_PACKET, PACKET_IGNORE_OUTGOING,
+ &ignore_outgoing, sizeof(ignore_outgoing));
+ if (rc == -1) {
+ PMD_LOG_ERRNO(ERR,
+ "%s: could not set PACKET_IGNORE_OUTGOING "
+ "on AF_PACKET socket for %s",
+ name, pair->value);
+ goto error;
+ }
+ }
+
rc = setsockopt(qsockfd, SOL_PACKET, PACKET_RX_RING, req, sizeof(*req));
if (rc == -1) {
PMD_LOG_ERRNO(ERR,
@@ -1056,7 +1068,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
rx_queue = &((*internals)->rx_queue[q]);
rx_queue->framecount = req->tp_frame_nr;
- rx_queue->ignore_outgoing = ignore_outgoing != 0;
+ rx_queue->ignore_outgoing = ignore_outgoing != 0 && nb_queues > 1;
rx_queue->map = mmap(NULL, 2 * req->tp_block_size * req->tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED,
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v6 4/4] net/af_packet: use kernel to filter multi-queue Rx
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
` (2 preceding siblings ...)
2026-10-15 11:14 ` [PATCH v6 3/4] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
@ 2026-10-15 11:14 ` Frank Dressler
2026-10-15 19:28 ` [PATCH v6 0/4] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
4 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-15 11:14 UTC (permalink / raw)
To: stephen; +Cc: dev
The AF_PACKET PMD currently needs to filter out outgoing packets in
the PMD when the ignore_outgoing devarg is enabled and multiple queues
are used. Kernel-side filtering is only supported for the single-queue
scenario.
Since Linux 6.2, the kernel filters outgoing packets for a fanout
group when PACKET_FANOUT_FLAG_IGNORE_OUTGOING is set.
This change opportunistically sets this flag for all kernel versions
in multi-queue scenarios. Kernels before Linux 6.2 ignore the flag
and have no way to detect support. The PMD filtering is kept as a
fallback for kernels that do not support the flag.
Signed-off-by: Frank Dressler <frank@dressler.pro>
---
doc/guides/nics/af_packet.rst | 3 +++
doc/guides/rel_notes/release_26_11.rst | 3 ++-
drivers/net/af_packet/rte_eth_af_packet.c | 13 +++++++++++++
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/doc/guides/nics/af_packet.rst b/doc/guides/nics/af_packet.rst
index 6df14c510d..702822a428 100644
--- a/doc/guides/nics/af_packet.rst
+++ b/doc/guides/nics/af_packet.rst
@@ -45,6 +45,9 @@ of AF_PACKET for frame reception.
When ``ignore_outgoing`` is set, ``PACKET_IGNORE_OUTGOING`` is set for
single-queue ports, which moves the filtering from the PMD to the kernel.
+For multi-queue ports ``PACKET_FANOUT_FLAG_IGNORE_OUTGOING`` is set to
+achieve the same but this only works from Linux 6.2. For earlier Linux
+versions, the PMD does the filtering for multi-queue ports.
Because this implementation is based on PACKET_MMAP, and PACKET_MMAP has its
own pre-requisites, it should be noted that the inner workings of PACKET_MMAP
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index e83cb9854f..9740e9fdce 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -118,7 +118,8 @@ New Features
Added ``ignore_outgoing`` so ``rte_eth_rx_burst()`` returns only incoming
packets. The single-queue scenario is accelerated, as the kernel does
- the filtering.
+ the filtering. Multi-queue ports are also filtered in the kernel since
+ Linux 6.2, otherwise in the PMD.
* **Updated AF_XDP driver.**
diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 44b91502fb..0d044ab661 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -32,6 +32,11 @@
#include <unistd.h>
#include <poll.h>
+/* Added in Linux 6.2. Older kernels ignore it. */
+#ifndef PACKET_FANOUT_FLAG_IGNORE_OUTGOING
+#define PACKET_FANOUT_FLAG_IGNORE_OUTGOING 0x4000
+#endif
+
#define ETH_AF_PACKET_IFACE_ARG "iface"
#define ETH_AF_PACKET_NUM_Q_ARG "qpairs"
#define ETH_AF_PACKET_BLOCKSIZE_ARG "blocksz"
@@ -995,6 +1000,14 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
goto error;
}
+ /*
+ * Set the flag opportunistically if multiple queues are requested.
+ * It takes effect from Linux 6.2. Older kernels ignore it and the
+ * PMD filters instead.
+ */
+ if (ignore_outgoing != 0 && nb_queues > 1)
+ fanout_arg |= PACKET_FANOUT_FLAG_IGNORE_OUTGOING << 16;
+
for (q = 0; q < nb_queues; q++) {
/* Open an AF_PACKET socket for this queue... */
qsockfd = socket(AF_PACKET, SOCK_RAW, 0);
--
2.56.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst
2026-10-09 20:03 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
@ 2026-10-15 11:55 ` Frank Dressler
0 siblings, 0 replies; 23+ messages in thread
From: Frank Dressler @ 2026-10-15 11:55 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
On 10/9/26 21:03, Stephen Hemminger wrote:
>
> A separate, older bug turned up while checking fanout_add() in the
kernel. get_fanout_mode() always ORs in PACKET_FANOUT_FLAG_ROLLOVER, and
the kernel returns -EINVAL for PACKET_FANOUT_ROLLOVER combined with that
flag. So fanout_mode=rollover with qpairs > 1 always fails setsockopt.
It’s not part of this series, but it deserves its own Fixes patch
>
I agree, this is an issue that needs to be fixed. Another issue with
unconditionally setting PACKET_FANOUT_FLAG_ROLLOVER is that packets
might spill over to another queue and then be received by a worker that
does not track the flow the packet belongs to in case of per-worker flow
tables. So I think that it should be configurable.
A related issue is with the PACKET_FANOUT_FLAG_DEFRAG flag that is also
always set. Setting it can be harmful to IDS/IPS and similar
applications that want to see the IP fragments as the come in to detect
excessive IP fragmentation and overlapping fragments as is used to
bypass detection.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 0/4] net/af_packet: do not return outgoing packets from rte_eth_rx_burst
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
` (3 preceding siblings ...)
2026-10-15 11:14 ` [PATCH v6 4/4] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
@ 2026-10-15 19:28 ` Stephen Hemminger
4 siblings, 0 replies; 23+ messages in thread
From: Stephen Hemminger @ 2026-10-15 19:28 UTC (permalink / raw)
To: Frank Dressler; +Cc: dev
On Thu, 15 Oct 2026 12:14:30 +0100
Frank Dressler <frank@dressler.pro> wrote:
> In contrast to other PMDs, the AF_PACKET PMD does not only return
> incoming but also outgoing packets from rte_eth_rx_burst(). This is
> fine for applications similar to tcpdump which usually want to see
> both. But it is not always the desired behavior. For example, the
> AF_PACKET PMD might be used in a testing environment in which no
> regular ethernet PMD works. In such an environment, the AF_PACKET
> PMD must behave like a regular one, i.e. not return sent packets
> from rte_eth_rx_burst(). One could use the AF_XDP PMD for this but
> it has different dependencies than the AF_PACKET PMD because it
> requires the bpf() syscall, for example, which might not be
> available.
>
> For this reason, this series adds the ignore_outgoing=<0|1> devarg.
> When set, the PMD ensures that rte_eth_rx_burst() only returns
> incoming traffic. The default is 0 so that existing software works
> as before.
>
> The ignore_outgoing test tags the frames it injects so that it can
> distinguish them from stray frames, which would otherwise be counted
> as well and could lead to a wrong result. As the pre-existing
> loopback test could suffer from stray frames as well, the first patch
> changes it to use tagged frames.
>
> The second patch adds filtering of outgoing packets in the PMD
> because that works in all supported kernel versions and for all
> queue counts. The test creates an AF_PACKET vdev and injects frames
> at the fd end of the TAP device and via a second AF_PACKET vdev.
> Frames from the fd end arrive as incoming traffic and frames from
> the second vdev as outgoing traffic, so with ignore_outgoing=1 the
> first vdev should return only the incoming frames. The frames from
> the fd end are required to distinguish a dead device from one which
> dropped the outgoing frames.
>
> The third patch adds PACKET_IGNORE_OUTGOING which moves the
> filtering into the kernel. It is supported since Linux 4.20 but only
> works for single-queue AF_PACKET scenarios. Multi-queue AF_PACKET
> is still filtered in the PMD.
>
> The fourth patch adds PACKET_FANOUT_FLAG_IGNORE_OUTGOING which is
> supported since Linux 6.2 and moves the filtering into the kernel
> for the multi-queue scenarios. The kernel does not expose a direct
> way to check whether it supports PACKET_FANOUT_FLAG_IGNORE_OUTGOING
> but it also does not return an error when the sockopt is set on
> kernels that do not support it. Because of that the sockopt is set
> opportunistically. The PMD check is kept, since older kernels accept
> the flag without effect and support cannot be detected.
>
> Overall we get the following:
>
> | Single Queue | Multi-Queue
> ------------+--------------+-------------
> Kernel 5.4+ | in kernel | in PMD
> Kernel 6.2+ | in kernel | in kernel
>
> I tested this on both, kernel 7.2.8 and kernel 5.4.0 with both single
> and multiple queues. My results, strace'ing testpmd 7.2.8, with the
> variants of ignore_outgoing=0|1 and single/multi-queue:
>
> Kernel 5.4.0:
> qpairs=1,ignore_outgoing=0: -
> qpairs=1,ignore_outgoing=1: setsockopt(14, SOL_PACKET, 0x17 /* PACKET_??? */, [1], 4) = 0
> // 0x17 is PACKET_IGNORE_OUTGOING
>
> qpairs=2,ignore_outgoing=0: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-1878917532], 4) = 0
> // -1878917532 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
> qpairs=2,ignore_outgoing=1: setsockopt(14, SOL_PACKET, PACKET_FANOUT, [-805175699], 4) = 0
> // -805175699 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
>
> Kernel 7.2.8:
> qpairs=1,ignore_outgoing=0: -
> qpairs=1,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_IGNORE_OUTGOING, [1], 4) = 0
>
> qpairs=2,ignore_outgoing=0: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-1878978899], 4) = 0
> // -1878978899 does NOT contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
> qpairs=2,ignore_outgoing=1: setsockopt(13, SOL_PACKET, PACKET_FANOUT, [-805236964], 4) = 0
> // -805236964 does contain PACKET_FANOUT_FLAG_IGNORE_OUTGOING<<16
>
> They show that the setsockopt succeeds even on the old kernel that
> does not support PACKET_FANOUT_FLAG_IGNORE_OUTGOING, so setting it
> opportunistically is fine. They also show that the code only tries to
> set PACKET_FANOUT_FLAG_IGNORE_OUTGOING or PACKET_IGNORE_OUTGOING when
> requested by ignore_outgoing=1. And it shows that
> PACKET_IGNORE_OUTGOING is used for single and
> PACKET_FANOUT_FLAG_IGNORE_OUTGOING for multi-queue.
>
> I have also manually ran testpmd in all modes and both kernels to
> verify that ignore_outgoing works as expected, sending packets and
> checking what testpmd was receiving (``set verbose 1``).
>
>
> v6:
> - add tags to frames to distinguish injected ones from stray ones
> - update the loopback and ignore_outgoing tests to count only tagged
> frames
> - inject frames as incoming to distinguish a dead port from a
> working one in ignore_outgoing=1
> - bound the receive flush so a continuous stream cannot hang the test
> - quote rte_eth_rx_burst() with double backticks in the docs
> - use Linux 6.2 instead of v6.2 throughout the code and the docs
> - add a paragraph to af_packet.rst on single-queue and multi-queue
> filtering
Still see minor issues in AI reviews that need fix before it gets merged.
Subject: Re: [PATCH v6 0/4] net/af_packet: ignore_outgoing devarg
Series applies cleanly to main and builds at every commit with
-Dwerror=true (driver and dpdk-test).
Kernel side checks out: sockaddr_ll is at
TPACKET_ALIGN(sizeof(struct tpacket2_hdr)) as the kernel lays it out,
PACKET_IGNORE_OUTGOING has no effect once a socket joins a fanout
group (the group prot_hook replaces the per-socket one), and pre-6.2
fanout_add() does not validate unknown flag bits, so setting
PACKET_FANOUT_FLAG_IGNORE_OUTGOING unconditionally is safe and the
PMD-side check remains the correct fallback.
Only minor items below.
Patch 2/4 net/af_packet: filter Rx of outgoing packets in the PMD
-----------------------------------------------------------------
Info: ignore_outgoing is unsigned, log it with %u.
- PMD_LOG(DEBUG, "%s:\tignore outgoing %d", name, ignore_outgoing);
+ PMD_LOG(DEBUG, "%s:\tignore outgoing %u", name, ignore_outgoing);
Info: test_af_packet_ignore_outgoing() defines n_inject for the TAP
side but hardcodes 4 for the af_packet side. Use one constant for
both:
- allocated = alloc_tx_mbufs(bufs, 4);
+ allocated = alloc_tx_mbufs(bufs, n_inject);
Patch 3/4 net/af_packet: use kernel to filter single-queue Rx
-------------------------------------------------------------
Info: commit message states the 4.20 availability twice. Drop the
last paragraph and fold "DPDK requires at least Linux 5.4, so no
feature check is needed" into the second one.
Info: release notes should say what the user gets, not how it is
done. Where filtering happens (and the 6.2 dependency added in 4/4)
is already in doc/guides/nics/af_packet.rst. Suggest leaving the
release note entry as added in 2/4:
Added ``ignore_outgoing`` devarg so ``rte_eth_rx_burst()``
returns only incoming packets.
Info: PACKET_QDISC_BYPASS is guarded with #if defined() and 4/4 adds
a fallback define for PACKET_FANOUT_FLAG_IGNORE_OUTGOING, but
PACKET_IGNORE_OUTGOING is used bare. Fine against any 4.20+ uapi
headers; add a matching #ifndef fallback (value 23) only if you want
it consistent with 4/4.
Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-10-15 19:28 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-04 4:22 [PATCH] net/af_packet: add capture direction option Frank Dressler
2026-10-04 16:10 ` Stephen Hemminger
2026-10-05 0:26 ` Frank Dressler
2026-10-05 0:28 ` [PATCH v2] net/af_packet: add option to ignore outgoing packets Frank Dressler
2026-10-05 16:41 ` Stephen Hemminger
2026-10-06 6:42 ` [PATCH v3] " Frank Dressler
2026-10-06 13:41 ` Stephen Hemminger
2026-10-09 15:02 ` [PATCH v4 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:02 ` [PATCH v4 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
2026-10-09 15:02 ` [PATCH v4 2/3] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
2026-10-09 15:02 ` [PATCH v4 3/3] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
2026-10-09 15:10 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Frank Dressler
2026-10-09 15:10 ` [PATCH v5 1/3] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
2026-10-09 15:10 ` [PATCH v5 2/3] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
2026-10-09 15:10 ` [PATCH v5 3/3] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
2026-10-09 20:03 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
2026-10-15 11:55 ` Frank Dressler
2026-10-15 11:14 ` [PATCH v6 0/4] " Frank Dressler
2026-10-15 11:14 ` [PATCH v6 1/4] test/af_packet: ignore stray packets in loopback test Frank Dressler
2026-10-15 11:14 ` [PATCH v6 2/4] net/af_packet: filter Rx of outgoing packets in the PMD Frank Dressler
2026-10-15 11:14 ` [PATCH v6 3/4] net/af_packet: use kernel to filter single-queue Rx Frank Dressler
2026-10-15 11:14 ` [PATCH v6 4/4] net/af_packet: use kernel to filter multi-queue Rx Frank Dressler
2026-10-15 19:28 ` [PATCH v6 0/4] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox