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