* [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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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
` (3 more replies)
3 siblings, 4 replies; 16+ 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] 16+ 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
` (2 subsequent siblings)
3 siblings, 0 replies; 16+ 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] 16+ 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
2026-10-09 20:03 ` [PATCH v5 0/3] net/af_packet: do not return outgoing packets from rte_eth_rx_burst Stephen Hemminger
3 siblings, 0 replies; 16+ 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] 16+ 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
3 siblings, 0 replies; 16+ 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] 16+ 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
3 siblings, 0 replies; 16+ 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] 16+ messages in thread
end of thread, other threads:[~2026-10-09 20:04 UTC | newest]
Thread overview: 16+ 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
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.