* [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; 7+ 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] 7+ 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; 7+ 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] 7+ 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; 7+ 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] 7+ 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
2026-10-06 6:42 ` [PATCH v3] " Frank Dressler
1 sibling, 2 replies; 7+ 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] 7+ 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
1 sibling, 0 replies; 7+ 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] 7+ 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
1 sibling, 1 reply; 7+ 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] 7+ 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; 7+ 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] 7+ messages in thread
end of thread, other threads:[~2026-10-06 13:41 UTC | newest]
Thread overview: 7+ 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox