* Re: [PATCH dpdk] net/tap: add software MAC address filtering
2026-03-19 22:10 [PATCH dpdk] net/tap: add software MAC address filtering Robin Jarry
@ 2026-03-19 22:43 ` Stephen Hemminger
2026-03-20 8:26 ` Robin Jarry
2026-03-20 10:10 ` Morten Brørup
` (4 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Stephen Hemminger @ 2026-03-19 22:43 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev
On Thu, 19 Mar 2026 23:10:35 +0100
Robin Jarry <rjarry@redhat.com> wrote:
> Linux TAP devices deliver all packets to userspace regardless of the
> PROMISC/ALLMULTI flags on the interface. Add an opt-in "macfilter"
> devarg that, when enabled, drops received packets whose destination MAC
> does not match any configured unicast or multicast address.
>
> When macfilter is active the receive path checks the destination MAC
> against the device's unicast address table (managed by the ethdev
> layer), the multicast address list (stored by the driver since the
> ethdev layer does not keep a copy), and accepts broadcast
> unconditionally. Promiscuous and all-multicast modes bypass the
> respective checks.
>
> To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
> allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
> pointing into dev_private, and advertise the new limit in dev_infos_get.
>
> Dropped packets are reported via per-queue xstats
> (rx_q<N>_mac_filter_drops).
>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
> drivers/net/tap/rte_eth_tap.c | 178 ++++++++++++++++++++++++++++++----
> drivers/net/tap/rte_eth_tap.h | 7 ++
> 2 files changed, 167 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
> index 13e0a23c34a1..f4ea3bc5d160 100644
> --- a/drivers/net/tap/rte_eth_tap.c
> +++ b/drivers/net/tap/rte_eth_tap.c
> @@ -53,11 +53,14 @@
> #define ETH_TAP_MAC_ARG "mac"
> #define ETH_TAP_MAC_FIXED "fixed"
> #define ETH_TAP_PERSIST_ARG "persist"
> +#define ETH_TAP_MAC_FILTER_ARG "macfilter"
>
> #define ETH_TAP_USR_MAC_FMT "xx:xx:xx:xx:xx:xx"
> #define ETH_TAP_CMP_MAC_FMT "0123456789ABCDEFabcdef"
> #define ETH_TAP_MAC_ARG_FMT ETH_TAP_MAC_FIXED "|" ETH_TAP_USR_MAC_FMT
>
> +#define TAP_MAX_MAC_ADDRS 16
> +
> #define TAP_GSO_MBUFS_PER_CORE 128
> #define TAP_GSO_MBUF_SEG_SIZE 128
> #define TAP_GSO_MBUF_CACHE_SIZE 4
> @@ -110,6 +113,7 @@ static const char *valid_arguments[] = {
> ETH_TAP_REMOTE_ARG,
> ETH_TAP_MAC_ARG,
> ETH_TAP_PERSIST_ARG,
> + ETH_TAP_MAC_FILTER_ARG,
> NULL
> };
>
> @@ -437,6 +441,45 @@ tap_rxq_pool_free(struct rte_mbuf *pool)
> rte_pktmbuf_free(pool);
> }
>
> +static inline bool
> +tap_mac_filter_match(struct rx_queue *rxq, struct rte_mbuf *mbuf)
> +{
> + struct pmd_internals *pmd = rxq->pmd;
> + struct rte_eth_dev_data *data;
> + struct rte_ether_addr *dst;
> + uint32_t i;
> +
> + if (!pmd->macfilter)
> + return true;
> +
> + data = pmd->dev->data;
> + if (data->promiscuous)
> + return true;
> +
> + dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
> +
> + if (rte_is_broadcast_ether_addr(dst))
> + return true;
> +
> + if (rte_is_multicast_ether_addr(dst)) {
> + if (data->all_multicast)
> + return true;
> + for (i = 0; i < pmd->nb_mc_addrs; i++) {
> + if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
> + return true;
> + }
> + return false;
> + }
> +
> + for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
> + if (rte_is_zero_ether_addr(&data->mac_addrs[i]))
> + continue;
> + if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
> + return true;
> + }
> + return false;
> +}
> +
> /* Callback to handle the rx burst of packets to the correct interface and
> * file descriptor(s) in a multi-queue setup.
> */
> @@ -515,6 +558,13 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
> data_off = 0;
> }
> seg->next = NULL;
> +
> + if (!tap_mac_filter_match(rxq, mbuf)) {
> + rxq->stats.mac_drops++;
> + rte_pktmbuf_free(mbuf);
> + continue;
> + }
> +
> mbuf->packet_type = rte_net_get_ptype(mbuf, NULL,
> RTE_PTYPE_ALL_MASK);
> if (rxq->rxmode->offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM)
> @@ -933,7 +983,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
> struct pmd_internals *internals = dev->data->dev_private;
>
> dev_info->if_index = internals->if_index;
> - dev_info->max_mac_addrs = 1;
> + dev_info->max_mac_addrs = TAP_MAX_MAC_ADDRS;
> dev_info->max_rx_pktlen = RTE_ETHER_MAX_JUMBO_FRAME_LEN;
> dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
> dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
> @@ -1025,6 +1075,43 @@ tap_stats_reset(struct rte_eth_dev *dev)
> return 0;
> }
>
> +static int
> +tap_xstats_get_names(struct rte_eth_dev *dev,
> + struct rte_eth_xstat_name *names,
> + unsigned int limit __rte_unused)
> +{
> + unsigned int i;
> +
> + if (names == NULL)
> + return dev->data->nb_rx_queues;
> +
> + for (i = 0; i < dev->data->nb_rx_queues; i++)
> + snprintf(names[i].name, sizeof(names[i].name),
> + "rx_q%u_mac_filter_drops", i);
> +
> + return dev->data->nb_rx_queues;
> +}
> +
> +static int
> +tap_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *xstats,
> + unsigned int n)
> +{
> + struct rte_eth_dev_data *data = dev->data;
> + struct rx_queue *rxq;
> + unsigned int i;
> +
> + if (n < dev->data->nb_rx_queues)
> + return dev->data->nb_rx_queues;
> +
> + for (i = 0; i < dev->data->nb_rx_queues; i++) {
> + rxq = data->rx_queues[i];
> + xstats[i].id = i;
> + xstats[i].value = rxq->stats.mac_drops;
> + }
> +
> + return dev->data->nb_rx_queues;
> +}
> +
> static void
> tap_queue_close(struct pmd_process_private *process_private, uint16_t qid)
> {
> @@ -1089,14 +1176,15 @@ tap_dev_close(struct rte_eth_dev *dev)
> rte_mempool_free(internals->gso_ctx_mp);
> internals->gso_ctx_mp = NULL;
>
> + rte_free(internals->mc_addrs);
> + internals->mc_addrs = NULL;
> + internals->nb_mc_addrs = 0;
> +
> if (internals->ka_fd != -1) {
> close(internals->ka_fd);
> internals->ka_fd = -1;
> }
>
> - /* mac_addrs must not be freed alone because part of dev_private */
> - dev->data->mac_addrs = NULL;
> -
> internals = dev->data->dev_private;
> TAP_LOG(DEBUG, "Closing %s Ethernet device on numa %u",
> tuntap_types[internals->type], rte_socket_id());
> @@ -1574,6 +1662,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
> }
> tmp = &rxq->pool;
>
> + rxq->pmd = internals;
> rxq->mp = mp;
> rxq->trigger_seen = 1; /* force initial burst */
> rxq->in_port = dev->data->port_id;
> @@ -1692,17 +1781,50 @@ tap_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
> }
>
> static int
> -tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
> - struct rte_ether_addr *mc_addr_set __rte_unused,
> - uint32_t nb_mc_addr __rte_unused)
> +tap_set_mc_addr_list(struct rte_eth_dev *dev,
> + struct rte_ether_addr *mc_addr_set,
> + uint32_t nb_mc_addr)
> {
> - /*
> - * Nothing to do actually: the tap has no filtering whatsoever, every
> - * packet is received.
> - */
> + struct pmd_internals *pmd = dev->data->dev_private;
> +
> + if (nb_mc_addr == 0) {
> + rte_free(pmd->mc_addrs);
> + pmd->mc_addrs = NULL;
> + pmd->nb_mc_addrs = 0;
> + return 0;
> + }
> +
> + pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
> + nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
> + if (pmd->mc_addrs == NULL) {
> + pmd->nb_mc_addrs = 0;
> + return -ENOMEM;
> + }
> +
> + memcpy(pmd->mc_addrs, mc_addr_set,
> + nb_mc_addr * sizeof(*pmd->mc_addrs));
> + pmd->nb_mc_addrs = nb_mc_addr;
> +
> return 0;
> }
>
> +static int
> +tap_mac_addr_add(struct rte_eth_dev *dev __rte_unused,
> + struct rte_ether_addr *mac_addr __rte_unused,
> + uint32_t index __rte_unused,
> + uint32_t vmdq __rte_unused)
> +{
> + /* ethdev layer already stores the address in mac_addrs[] */
> + return 0;
> +}
> +
> +static void
> +tap_mac_addr_remove(struct rte_eth_dev *dev __rte_unused,
> + uint32_t index __rte_unused)
> +{
> + /* ethdev layer already zeroes the slot in mac_addrs[] */
> +}
> +
> static void tap_dev_intr_handler(void *cb_arg);
> static int tap_lsc_intr_handle_set(struct rte_eth_dev *dev, int set);
>
> @@ -2038,10 +2160,15 @@ static const struct eth_dev_ops ops = {
> .allmulticast_enable = tap_allmulti_enable,
> .allmulticast_disable = tap_allmulti_disable,
> .mac_addr_set = tap_mac_set,
> + .mac_addr_add = tap_mac_addr_add,
> + .mac_addr_remove = tap_mac_addr_remove,
> .mtu_set = tap_mtu_set,
> .set_mc_addr_list = tap_set_mc_addr_list,
> .stats_get = tap_stats_get,
> .stats_reset = tap_stats_reset,
> + .xstats_get = tap_xstats_get,
> + .xstats_get_names = tap_xstats_get_names,
> + .xstats_reset = tap_stats_reset,
> .dev_supported_ptypes_get = tap_dev_supported_ptypes_get,
> .rss_hash_update = tap_rss_hash_update,
> #ifdef HAVE_TCA_FLOWER
> @@ -2052,7 +2179,7 @@ static const struct eth_dev_ops ops = {
> static int
> eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
> const char *remote_iface, struct rte_ether_addr *mac_addr,
> - enum rte_tuntap_type type, int persist)
> + enum rte_tuntap_type type, int persist, int macfilter)
> {
> int numa_node = rte_socket_id();
> struct rte_eth_dev *dev;
> @@ -2102,7 +2229,14 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
> data->numa_node = numa_node;
>
> data->dev_link = pmd_link;
> - data->mac_addrs = &pmd->eth_addr;
> + data->mac_addrs = rte_zmalloc_socket(rte_vdev_device_name(vdev),
> + TAP_MAX_MAC_ADDRS *
> + sizeof(struct rte_ether_addr),
> + 0, numa_node);
> + if (data->mac_addrs == NULL) {
> + TAP_LOG(ERR, "Failed to allocate mac_addrs");
> + goto error_exit;
> + }
> /* Set the number of RX and TX queues */
> data->nb_rx_queues = 0;
> data->nb_tx_queues = 0;
> @@ -2120,6 +2254,8 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
> process_private->fds[i] = -1;
>
>
> + pmd->macfilter = macfilter;
> +
> if (pmd->type == ETH_TUNTAP_TYPE_TAP) {
> if (rte_is_zero_ether_addr(mac_addr))
> rte_eth_random_addr((uint8_t *)&pmd->eth_addr);
> @@ -2227,6 +2363,9 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
> }
> #endif
>
> + /* Copy final MAC to slot 0 (remote path may have overwritten it) */
> + data->mac_addrs[0] = pmd->eth_addr;
> +
> rte_eth_dev_probing_finish(dev);
> return 0;
>
> @@ -2246,8 +2385,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
> free(dev->process_private);
>
> error_exit_nodev_release:
> - /* mac_addrs must not be freed alone because part of dev_private */
> - dev->data->mac_addrs = NULL;
> rte_eth_dev_release_port(dev);
>
> error_exit_nodev:
> @@ -2405,7 +2542,7 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
> TAP_LOG(DEBUG, "Initializing pmd_tun for %s", name);
>
> ret = eth_dev_tap_create(dev, tun_name, remote_iface, 0,
> - ETH_TUNTAP_TYPE_TUN, 0);
> + ETH_TUNTAP_TYPE_TUN, 0, 0);
>
> leave:
> if (ret == -1) {
> @@ -2529,6 +2666,7 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
> struct rte_eth_dev *eth_dev;
> int tap_devices_count_increased = 0;
> int persist = 0;
> + int macfilter = 0;
>
> name = rte_vdev_device_name(dev);
> params = rte_vdev_device_args(dev);
> @@ -2617,6 +2755,9 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
>
> if (rte_kvargs_count(kvlist, ETH_TAP_PERSIST_ARG) == 1)
> persist = 1;
> +
> + if (rte_kvargs_count(kvlist, ETH_TAP_MAC_FILTER_ARG) == 1)
> + macfilter = 1;
> }
> }
>
> @@ -2634,7 +2775,7 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
> tap_devices_count++;
> tap_devices_count_increased = 1;
> ret = eth_dev_tap_create(dev, tap_name, remote_iface, &user_mac,
> - ETH_TUNTAP_TYPE_TAP, persist);
> + ETH_TUNTAP_TYPE_TAP, persist, macfilter);
>
> leave:
> if (ret == -1) {
> @@ -2687,5 +2828,6 @@ RTE_PMD_REGISTER_PARAM_STRING(net_tun,
> RTE_PMD_REGISTER_PARAM_STRING(net_tap,
> ETH_TAP_IFACE_ARG "=<string> "
> ETH_TAP_MAC_ARG "=" ETH_TAP_MAC_ARG_FMT " "
> - ETH_TAP_REMOTE_ARG "=<string>");
> + ETH_TAP_REMOTE_ARG "=<string> "
> + ETH_TAP_MAC_FILTER_ARG "=<int>");
> RTE_LOG_REGISTER_DEFAULT(tap_logtype, NOTICE);
> diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
> index b44eaf9a1bdb..dd65b33bf351 100644
> --- a/drivers/net/tap/rte_eth_tap.h
> +++ b/drivers/net/tap/rte_eth_tap.h
> @@ -38,9 +38,13 @@ struct queue_stats {
> uint64_t packets;
> uint64_t bytes;
> uint64_t errors;
> + uint64_t mac_drops; /* Packets dropped by MAC filter */
> };
>
> +struct pmd_internals;
> +
> struct rx_queue {
> + struct pmd_internals *pmd; /* back-pointer to driver state */
> struct rte_mempool *mp; /* Mempool for RX packets */
> uint32_t trigger_seen; /* Last seen Rx trigger value */
> uint16_t in_port; /* Port ID */
> @@ -69,7 +73,10 @@ struct pmd_internals {
> char name[IFNAMSIZ]; /* Internal Tap device name */
> int type; /* Type field - TUN|TAP */
> int persist; /* 1 if keep link up, else 0 */
> + int macfilter; /* SW MAC filtering enabled */
> struct rte_ether_addr eth_addr; /* Mac address of the device port */
> + struct rte_ether_addr *mc_addrs; /* multicast address list */
> + uint32_t nb_mc_addrs; /* multicast address count */
> unsigned int remote_initial_flags;/* Remote netdevice flags on init */
> int remote_if_index; /* remote netdevice IF_INDEX */
> int if_index; /* IF_INDEX for the port */
TAP should just always do it. TAP should behave like a real NIC.
Flags should be uint8_t of bool not int.
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH dpdk] net/tap: add software MAC address filtering
2026-03-19 22:43 ` Stephen Hemminger
@ 2026-03-20 8:26 ` Robin Jarry
0 siblings, 0 replies; 15+ messages in thread
From: Robin Jarry @ 2026-03-20 8:26 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
Stephen Hemminger, Mar 19, 2026 at 23:43:
> TAP should just always do it. TAP should behave like a real NIC.
I would also prefer if it was the default behavior but after discussing
it with David, we thought it may cause issues to users who assume the
driver will receive all frames, regardless of promisc/allmulti.
I will happily submit a v2 without the opt-in flag. Users can always
enable promisc if they want to revert to the previous behavior.
^ permalink raw reply [flat|nested] 15+ messages in thread
* RE: [PATCH dpdk] net/tap: add software MAC address filtering
2026-03-19 22:10 [PATCH dpdk] net/tap: add software MAC address filtering Robin Jarry
2026-03-19 22:43 ` Stephen Hemminger
@ 2026-03-20 10:10 ` Morten Brørup
2026-03-20 16:45 ` [PATCH dpdk v2] " Robin Jarry
` (3 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Morten Brørup @ 2026-03-20 10:10 UTC (permalink / raw)
To: Robin Jarry, dev, Stephen Hemminger
> +static inline bool
> +tap_mac_filter_match(struct rx_queue *rxq, struct rte_mbuf *mbuf)
> +{
> + struct pmd_internals *pmd = rxq->pmd;
> + struct rte_eth_dev_data *data;
> + struct rte_ether_addr *dst;
> + uint32_t i;
> +
> + if (!pmd->macfilter)
> + return true;
> +
> + data = pmd->dev->data;
> + if (data->promiscuous)
> + return true;
> +
> + dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
> +
> + if (rte_is_broadcast_ether_addr(dst))
> + return true;
> +
> + if (rte_is_multicast_ether_addr(dst)) {
> + if (data->all_multicast)
> + return true;
> + for (i = 0; i < pmd->nb_mc_addrs; i++) {
> + if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
> + return true;
> + }
> + return false;
> + }
> +
> + for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
> + if (rte_is_zero_ether_addr(&data->mac_addrs[i]))
> + continue;
> + if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
> + return true;
> + }
> + return false;
> +}
Performance optimization; the vast majority of packets are unicast:
if (likely(rte_is_unicast_ether_addr(dst)) {
if (unlikely(rte_is_zero_ether_addr(dst)))
return false; /* Invalid destination address in packet */
/* Note: Compiler loop unrolls. */
for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
return true;
}
}
if (data->all_multicast)
return true;
for (i = 0; i < pmd->nb_mc_addrs; i++) {
if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
return true;
}
if (rte_is_broadcast_ether_addr(dst))
return true;
return false;
Also, all the variables and function parameters could be "const"; although I don't know if it makes any practical difference.
> static int
> -tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
> - struct rte_ether_addr *mc_addr_set __rte_unused,
> - uint32_t nb_mc_addr __rte_unused)
> +tap_set_mc_addr_list(struct rte_eth_dev *dev,
> + struct rte_ether_addr *mc_addr_set,
> + uint32_t nb_mc_addr)
> {
> - /*
> - * Nothing to do actually: the tap has no filtering whatsoever,
> every
> - * packet is received.
> - */
> + struct pmd_internals *pmd = dev->data->dev_private;
> +
> + if (nb_mc_addr == 0) {
> + rte_free(pmd->mc_addrs);
> + pmd->mc_addrs = NULL;
> + pmd->nb_mc_addrs = 0;
> + return 0;
> + }
> +
> + pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
> + nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
> + if (pmd->mc_addrs == NULL) {
> + pmd->nb_mc_addrs = 0;
> + return -ENOMEM;
> + }
> +
> + memcpy(pmd->mc_addrs, mc_addr_set,
> + nb_mc_addr * sizeof(*pmd->mc_addrs));
> + pmd->nb_mc_addrs = nb_mc_addr;
> +
> return 0;
> }
This is not thread safe.
If it needs to be thread safe, you must ensure that tap_mac_filter_match() - running on another lcore - is done using the mc_addrs array before you free it here.
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH dpdk v2] net/tap: add software MAC address filtering
2026-03-19 22:10 [PATCH dpdk] net/tap: add software MAC address filtering Robin Jarry
2026-03-19 22:43 ` Stephen Hemminger
2026-03-20 10:10 ` Morten Brørup
@ 2026-03-20 16:45 ` Robin Jarry
2026-03-21 2:58 ` Stephen Hemminger
2026-03-21 14:48 ` [PATCH dpdk v3] " Robin Jarry
` (2 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Robin Jarry @ 2026-03-20 16:45 UTC (permalink / raw)
To: dev, Stephen Hemminger
Linux TAP devices deliver all packets to userspace regardless of the
PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
disabled, drop received packets whose destination MAC does not match
any configured unicast or multicast address.
The receive path checks the destination MAC against the device's
unicast address table (managed by the ethdev layer), the multicast
address list (stored by the driver since the ethdev layer does not keep
a copy), and accepts broadcast unconditionally. Promiscuous and
all-multicast modes bypass the respective checks.
To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
pointing into dev_private, and advertise the new limit in dev_infos_get.
Dropped packets are reported via per-queue xstats
(rx_q<N>_mac_filter_drops).
Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
Notes:
v2:
* removed opt-in `macfilter=1` flag
* reorganized filter_match function to check unicast addresses first
drivers/net/tap/rte_eth_tap.c | 159 ++++++++++++++++++++++++++++++----
drivers/net/tap/rte_eth_tap.h | 6 ++
2 files changed, 150 insertions(+), 15 deletions(-)
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 13e0a23c34a1..d6c35ce5e788 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -58,6 +58,8 @@
#define ETH_TAP_CMP_MAC_FMT "0123456789ABCDEFabcdef"
#define ETH_TAP_MAC_ARG_FMT ETH_TAP_MAC_FIXED "|" ETH_TAP_USR_MAC_FMT
+#define TAP_MAX_MAC_ADDRS 16
+
#define TAP_GSO_MBUFS_PER_CORE 128
#define TAP_GSO_MBUF_SEG_SIZE 128
#define TAP_GSO_MBUF_CACHE_SIZE 4
@@ -437,6 +439,42 @@ tap_rxq_pool_free(struct rte_mbuf *pool)
rte_pktmbuf_free(pool);
}
+static inline bool
+tap_mac_filter_match(struct rx_queue *rxq, struct rte_mbuf *mbuf)
+{
+ struct pmd_internals *pmd = rxq->pmd;
+ struct rte_eth_dev_data *data;
+ struct rte_ether_addr *dst;
+ uint32_t i;
+
+ if (pmd->type != ETH_TUNTAP_TYPE_TAP)
+ return true;
+
+ data = pmd->dev->data;
+ if (data->promiscuous)
+ return true;
+
+ dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
+
+ if (likely(rte_is_unicast_ether_addr(dst))) {
+ for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
+ if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
+ return true;
+ }
+ return false;
+ }
+
+ if (data->all_multicast)
+ return true;
+
+ for (i = 0; i < pmd->nb_mc_addrs; i++) {
+ if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
+ return true;
+ }
+
+ return rte_is_broadcast_ether_addr(dst);
+}
+
/* Callback to handle the rx burst of packets to the correct interface and
* file descriptor(s) in a multi-queue setup.
*/
@@ -515,6 +553,13 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
data_off = 0;
}
seg->next = NULL;
+
+ if (!tap_mac_filter_match(rxq, mbuf)) {
+ rxq->stats.mac_drops++;
+ rte_pktmbuf_free(mbuf);
+ continue;
+ }
+
mbuf->packet_type = rte_net_get_ptype(mbuf, NULL,
RTE_PTYPE_ALL_MASK);
if (rxq->rxmode->offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM)
@@ -933,7 +978,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
struct pmd_internals *internals = dev->data->dev_private;
dev_info->if_index = internals->if_index;
- dev_info->max_mac_addrs = 1;
+ dev_info->max_mac_addrs = TAP_MAX_MAC_ADDRS;
dev_info->max_rx_pktlen = RTE_ETHER_MAX_JUMBO_FRAME_LEN;
dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
@@ -1025,6 +1070,43 @@ tap_stats_reset(struct rte_eth_dev *dev)
return 0;
}
+static int
+tap_xstats_get_names(struct rte_eth_dev *dev,
+ struct rte_eth_xstat_name *names,
+ unsigned int limit __rte_unused)
+{
+ unsigned int i;
+
+ if (names == NULL)
+ return dev->data->nb_rx_queues;
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++)
+ snprintf(names[i].name, sizeof(names[i].name),
+ "rx_q%u_mac_filter_drops", i);
+
+ return dev->data->nb_rx_queues;
+}
+
+static int
+tap_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *xstats,
+ unsigned int n)
+{
+ struct rte_eth_dev_data *data = dev->data;
+ struct rx_queue *rxq;
+ unsigned int i;
+
+ if (n < dev->data->nb_rx_queues)
+ return dev->data->nb_rx_queues;
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++) {
+ rxq = data->rx_queues[i];
+ xstats[i].id = i;
+ xstats[i].value = rxq->stats.mac_drops;
+ }
+
+ return dev->data->nb_rx_queues;
+}
+
static void
tap_queue_close(struct pmd_process_private *process_private, uint16_t qid)
{
@@ -1089,14 +1171,15 @@ tap_dev_close(struct rte_eth_dev *dev)
rte_mempool_free(internals->gso_ctx_mp);
internals->gso_ctx_mp = NULL;
+ rte_free(internals->mc_addrs);
+ internals->mc_addrs = NULL;
+ internals->nb_mc_addrs = 0;
+
if (internals->ka_fd != -1) {
close(internals->ka_fd);
internals->ka_fd = -1;
}
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
-
internals = dev->data->dev_private;
TAP_LOG(DEBUG, "Closing %s Ethernet device on numa %u",
tuntap_types[internals->type], rte_socket_id());
@@ -1574,6 +1657,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
}
tmp = &rxq->pool;
+ rxq->pmd = internals;
rxq->mp = mp;
rxq->trigger_seen = 1; /* force initial burst */
rxq->in_port = dev->data->port_id;
@@ -1692,17 +1776,50 @@ tap_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
}
static int
-tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
- struct rte_ether_addr *mc_addr_set __rte_unused,
- uint32_t nb_mc_addr __rte_unused)
+tap_set_mc_addr_list(struct rte_eth_dev *dev,
+ struct rte_ether_addr *mc_addr_set,
+ uint32_t nb_mc_addr)
{
- /*
- * Nothing to do actually: the tap has no filtering whatsoever, every
- * packet is received.
- */
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (nb_mc_addr == 0) {
+ rte_free(pmd->mc_addrs);
+ pmd->mc_addrs = NULL;
+ pmd->nb_mc_addrs = 0;
+ return 0;
+ }
+
+ pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
+ nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
+ if (pmd->mc_addrs == NULL) {
+ pmd->nb_mc_addrs = 0;
+ return -ENOMEM;
+ }
+
+ memcpy(pmd->mc_addrs, mc_addr_set,
+ nb_mc_addr * sizeof(*pmd->mc_addrs));
+ pmd->nb_mc_addrs = nb_mc_addr;
+
return 0;
}
+static int
+tap_mac_addr_add(struct rte_eth_dev *dev __rte_unused,
+ struct rte_ether_addr *mac_addr __rte_unused,
+ uint32_t index __rte_unused,
+ uint32_t vmdq __rte_unused)
+{
+ /* ethdev layer already stores the address in mac_addrs[] */
+ return 0;
+}
+
+static void
+tap_mac_addr_remove(struct rte_eth_dev *dev __rte_unused,
+ uint32_t index __rte_unused)
+{
+ /* ethdev layer already zeroes the slot in mac_addrs[] */
+}
+
static void tap_dev_intr_handler(void *cb_arg);
static int tap_lsc_intr_handle_set(struct rte_eth_dev *dev, int set);
@@ -2038,10 +2155,15 @@ static const struct eth_dev_ops ops = {
.allmulticast_enable = tap_allmulti_enable,
.allmulticast_disable = tap_allmulti_disable,
.mac_addr_set = tap_mac_set,
+ .mac_addr_add = tap_mac_addr_add,
+ .mac_addr_remove = tap_mac_addr_remove,
.mtu_set = tap_mtu_set,
.set_mc_addr_list = tap_set_mc_addr_list,
.stats_get = tap_stats_get,
.stats_reset = tap_stats_reset,
+ .xstats_get = tap_xstats_get,
+ .xstats_get_names = tap_xstats_get_names,
+ .xstats_reset = tap_stats_reset,
.dev_supported_ptypes_get = tap_dev_supported_ptypes_get,
.rss_hash_update = tap_rss_hash_update,
#ifdef HAVE_TCA_FLOWER
@@ -2102,7 +2224,14 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
data->numa_node = numa_node;
data->dev_link = pmd_link;
- data->mac_addrs = &pmd->eth_addr;
+ data->mac_addrs = rte_zmalloc_socket(rte_vdev_device_name(vdev),
+ TAP_MAX_MAC_ADDRS *
+ sizeof(struct rte_ether_addr),
+ 0, numa_node);
+ if (data->mac_addrs == NULL) {
+ TAP_LOG(ERR, "Failed to allocate mac_addrs");
+ goto error_exit;
+ }
/* Set the number of RX and TX queues */
data->nb_rx_queues = 0;
data->nb_tx_queues = 0;
@@ -2119,7 +2248,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++)
process_private->fds[i] = -1;
-
if (pmd->type == ETH_TUNTAP_TYPE_TAP) {
if (rte_is_zero_ether_addr(mac_addr))
rte_eth_random_addr((uint8_t *)&pmd->eth_addr);
@@ -2227,6 +2355,9 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
}
#endif
+ /* Copy final MAC to slot 0 (remote path may have overwritten it) */
+ data->mac_addrs[0] = pmd->eth_addr;
+
rte_eth_dev_probing_finish(dev);
return 0;
@@ -2246,8 +2377,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
free(dev->process_private);
error_exit_nodev_release:
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
rte_eth_dev_release_port(dev);
error_exit_nodev:
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index b44eaf9a1bdb..8aa292bbd013 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -38,9 +38,13 @@ struct queue_stats {
uint64_t packets;
uint64_t bytes;
uint64_t errors;
+ uint64_t mac_drops; /* Packets dropped by MAC filter */
};
+struct pmd_internals;
+
struct rx_queue {
+ struct pmd_internals *pmd; /* back-pointer to driver state */
struct rte_mempool *mp; /* Mempool for RX packets */
uint32_t trigger_seen; /* Last seen Rx trigger value */
uint16_t in_port; /* Port ID */
@@ -70,6 +74,8 @@ struct pmd_internals {
int type; /* Type field - TUN|TAP */
int persist; /* 1 if keep link up, else 0 */
struct rte_ether_addr eth_addr; /* Mac address of the device port */
+ struct rte_ether_addr *mc_addrs; /* multicast address list */
+ uint32_t nb_mc_addrs; /* multicast address count */
unsigned int remote_initial_flags;/* Remote netdevice flags on init */
int remote_if_index; /* remote netdevice IF_INDEX */
int if_index; /* IF_INDEX for the port */
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH dpdk v2] net/tap: add software MAC address filtering
2026-03-20 16:45 ` [PATCH dpdk v2] " Robin Jarry
@ 2026-03-21 2:58 ` Stephen Hemminger
2026-03-21 5:48 ` Morten Brørup
0 siblings, 1 reply; 15+ messages in thread
From: Stephen Hemminger @ 2026-03-21 2:58 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev
On Fri, 20 Mar 2026 17:45:24 +0100
Robin Jarry <rjarry@redhat.com> wrote:
> + dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
> +
> + if (likely(rte_is_unicast_ether_addr(dst))) {
> + for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
> + if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
> + return true;
> + }
> + return false;
> + }
Since unused slots are zero, probably need to drop packets with all zero dest.
^ permalink raw reply [flat|nested] 15+ messages in thread* RE: [PATCH dpdk v2] net/tap: add software MAC address filtering
2026-03-21 2:58 ` Stephen Hemminger
@ 2026-03-21 5:48 ` Morten Brørup
0 siblings, 0 replies; 15+ messages in thread
From: Morten Brørup @ 2026-03-21 5:48 UTC (permalink / raw)
To: Stephen Hemminger, Robin Jarry; +Cc: dev
> On Fri, 20 Mar 2026 17:45:24 +0100
> Robin Jarry <rjarry@redhat.com> wrote:
>
> > + dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
> > +
> > + if (likely(rte_is_unicast_ether_addr(dst))) {
The NULL addr check should go here:
if (unlikely(rte_is_zero_ether_addr(dst)))
return false; /* Invalid destination address in packet */
> > + for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
> > + if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
> > + return true;
> > + }
> > + return false;
> > + }
>
> Since unused slots are zero, probably need to drop packets with all
> zero dest.
You should drop packets with NULL dst addr; it is not allowed on the wire.
Alternatively, you can put the broadcast address in the unused entries in the mac_addrs table; it will not match any unicast ether addr.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH dpdk v3] net/tap: add software MAC address filtering
2026-03-19 22:10 [PATCH dpdk] net/tap: add software MAC address filtering Robin Jarry
` (2 preceding siblings ...)
2026-03-20 16:45 ` [PATCH dpdk v2] " Robin Jarry
@ 2026-03-21 14:48 ` Robin Jarry
2026-03-21 17:06 ` Stephen Hemminger
2026-03-21 23:20 ` Stephen Hemminger
2026-03-23 8:42 ` [PATCH dpdk v4] " Robin Jarry
2026-03-24 19:09 ` [PATCH dpdk v5] " Robin Jarry
5 siblings, 2 replies; 15+ messages in thread
From: Robin Jarry @ 2026-03-21 14:48 UTC (permalink / raw)
To: dev, Stephen Hemminger
Linux TAP devices deliver all packets to userspace regardless of the
PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
disabled, drop received packets whose destination MAC does not match
any configured unicast or multicast address.
The receive path checks the destination MAC against the device's
unicast address table (managed by the ethdev layer), the multicast
address list (stored by the driver since the ethdev layer does not keep
a copy), and accepts broadcast unconditionally. Promiscuous and
all-multicast modes bypass the respective checks.
To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
pointing into dev_private, and advertise the new limit in dev_infos_get.
Dropped packets are reported via per-queue xstats
(rx_q<N>_mac_filter_drops).
Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
Notes:
v3:
* always drop zero destination mac addresses unless promisc is enabled
v2:
* removed opt-in `macfilter=1` flag
* reorganized filter_match function to check unicast addresses first
drivers/net/tap/rte_eth_tap.c | 162 ++++++++++++++++++++++++++++++----
drivers/net/tap/rte_eth_tap.h | 6 ++
2 files changed, 153 insertions(+), 15 deletions(-)
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 13e0a23c34a1..4b5e06ed759f 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -58,6 +58,8 @@
#define ETH_TAP_CMP_MAC_FMT "0123456789ABCDEFabcdef"
#define ETH_TAP_MAC_ARG_FMT ETH_TAP_MAC_FIXED "|" ETH_TAP_USR_MAC_FMT
+#define TAP_MAX_MAC_ADDRS 16
+
#define TAP_GSO_MBUFS_PER_CORE 128
#define TAP_GSO_MBUF_SEG_SIZE 128
#define TAP_GSO_MBUF_CACHE_SIZE 4
@@ -437,6 +439,45 @@ tap_rxq_pool_free(struct rte_mbuf *pool)
rte_pktmbuf_free(pool);
}
+static inline bool
+tap_mac_filter_match(struct rx_queue *rxq, struct rte_mbuf *mbuf)
+{
+ struct pmd_internals *pmd = rxq->pmd;
+ struct rte_eth_dev_data *data;
+ struct rte_ether_addr *dst;
+ uint32_t i;
+
+ if (pmd->type != ETH_TUNTAP_TYPE_TAP)
+ return true;
+
+ data = pmd->dev->data;
+ if (data->promiscuous)
+ return true;
+
+ dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
+
+ if (unlikely(rte_is_zero_ether_addr(dst)))
+ return false;
+
+ if (likely(rte_is_unicast_ether_addr(dst))) {
+ for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
+ if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
+ return true;
+ }
+ return false;
+ }
+
+ if (data->all_multicast)
+ return true;
+
+ for (i = 0; i < pmd->nb_mc_addrs; i++) {
+ if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
+ return true;
+ }
+
+ return rte_is_broadcast_ether_addr(dst);
+}
+
/* Callback to handle the rx burst of packets to the correct interface and
* file descriptor(s) in a multi-queue setup.
*/
@@ -515,6 +556,13 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
data_off = 0;
}
seg->next = NULL;
+
+ if (!tap_mac_filter_match(rxq, mbuf)) {
+ rxq->stats.mac_drops++;
+ rte_pktmbuf_free(mbuf);
+ continue;
+ }
+
mbuf->packet_type = rte_net_get_ptype(mbuf, NULL,
RTE_PTYPE_ALL_MASK);
if (rxq->rxmode->offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM)
@@ -933,7 +981,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
struct pmd_internals *internals = dev->data->dev_private;
dev_info->if_index = internals->if_index;
- dev_info->max_mac_addrs = 1;
+ dev_info->max_mac_addrs = TAP_MAX_MAC_ADDRS;
dev_info->max_rx_pktlen = RTE_ETHER_MAX_JUMBO_FRAME_LEN;
dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
@@ -1025,6 +1073,43 @@ tap_stats_reset(struct rte_eth_dev *dev)
return 0;
}
+static int
+tap_xstats_get_names(struct rte_eth_dev *dev,
+ struct rte_eth_xstat_name *names,
+ unsigned int limit __rte_unused)
+{
+ unsigned int i;
+
+ if (names == NULL)
+ return dev->data->nb_rx_queues;
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++)
+ snprintf(names[i].name, sizeof(names[i].name),
+ "rx_q%u_mac_filter_drops", i);
+
+ return dev->data->nb_rx_queues;
+}
+
+static int
+tap_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *xstats,
+ unsigned int n)
+{
+ struct rte_eth_dev_data *data = dev->data;
+ struct rx_queue *rxq;
+ unsigned int i;
+
+ if (n < dev->data->nb_rx_queues)
+ return dev->data->nb_rx_queues;
+
+ for (i = 0; i < dev->data->nb_rx_queues; i++) {
+ rxq = data->rx_queues[i];
+ xstats[i].id = i;
+ xstats[i].value = rxq->stats.mac_drops;
+ }
+
+ return dev->data->nb_rx_queues;
+}
+
static void
tap_queue_close(struct pmd_process_private *process_private, uint16_t qid)
{
@@ -1089,14 +1174,15 @@ tap_dev_close(struct rte_eth_dev *dev)
rte_mempool_free(internals->gso_ctx_mp);
internals->gso_ctx_mp = NULL;
+ rte_free(internals->mc_addrs);
+ internals->mc_addrs = NULL;
+ internals->nb_mc_addrs = 0;
+
if (internals->ka_fd != -1) {
close(internals->ka_fd);
internals->ka_fd = -1;
}
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
-
internals = dev->data->dev_private;
TAP_LOG(DEBUG, "Closing %s Ethernet device on numa %u",
tuntap_types[internals->type], rte_socket_id());
@@ -1574,6 +1660,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
}
tmp = &rxq->pool;
+ rxq->pmd = internals;
rxq->mp = mp;
rxq->trigger_seen = 1; /* force initial burst */
rxq->in_port = dev->data->port_id;
@@ -1692,17 +1779,50 @@ tap_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
}
static int
-tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
- struct rte_ether_addr *mc_addr_set __rte_unused,
- uint32_t nb_mc_addr __rte_unused)
+tap_set_mc_addr_list(struct rte_eth_dev *dev,
+ struct rte_ether_addr *mc_addr_set,
+ uint32_t nb_mc_addr)
{
- /*
- * Nothing to do actually: the tap has no filtering whatsoever, every
- * packet is received.
- */
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (nb_mc_addr == 0) {
+ rte_free(pmd->mc_addrs);
+ pmd->mc_addrs = NULL;
+ pmd->nb_mc_addrs = 0;
+ return 0;
+ }
+
+ pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
+ nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
+ if (pmd->mc_addrs == NULL) {
+ pmd->nb_mc_addrs = 0;
+ return -ENOMEM;
+ }
+
+ memcpy(pmd->mc_addrs, mc_addr_set,
+ nb_mc_addr * sizeof(*pmd->mc_addrs));
+ pmd->nb_mc_addrs = nb_mc_addr;
+
return 0;
}
+static int
+tap_mac_addr_add(struct rte_eth_dev *dev __rte_unused,
+ struct rte_ether_addr *mac_addr __rte_unused,
+ uint32_t index __rte_unused,
+ uint32_t vmdq __rte_unused)
+{
+ /* ethdev layer already stores the address in mac_addrs[] */
+ return 0;
+}
+
+static void
+tap_mac_addr_remove(struct rte_eth_dev *dev __rte_unused,
+ uint32_t index __rte_unused)
+{
+ /* ethdev layer already zeroes the slot in mac_addrs[] */
+}
+
static void tap_dev_intr_handler(void *cb_arg);
static int tap_lsc_intr_handle_set(struct rte_eth_dev *dev, int set);
@@ -2038,10 +2158,15 @@ static const struct eth_dev_ops ops = {
.allmulticast_enable = tap_allmulti_enable,
.allmulticast_disable = tap_allmulti_disable,
.mac_addr_set = tap_mac_set,
+ .mac_addr_add = tap_mac_addr_add,
+ .mac_addr_remove = tap_mac_addr_remove,
.mtu_set = tap_mtu_set,
.set_mc_addr_list = tap_set_mc_addr_list,
.stats_get = tap_stats_get,
.stats_reset = tap_stats_reset,
+ .xstats_get = tap_xstats_get,
+ .xstats_get_names = tap_xstats_get_names,
+ .xstats_reset = tap_stats_reset,
.dev_supported_ptypes_get = tap_dev_supported_ptypes_get,
.rss_hash_update = tap_rss_hash_update,
#ifdef HAVE_TCA_FLOWER
@@ -2102,7 +2227,14 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
data->numa_node = numa_node;
data->dev_link = pmd_link;
- data->mac_addrs = &pmd->eth_addr;
+ data->mac_addrs = rte_zmalloc_socket(rte_vdev_device_name(vdev),
+ TAP_MAX_MAC_ADDRS *
+ sizeof(struct rte_ether_addr),
+ 0, numa_node);
+ if (data->mac_addrs == NULL) {
+ TAP_LOG(ERR, "Failed to allocate mac_addrs");
+ goto error_exit;
+ }
/* Set the number of RX and TX queues */
data->nb_rx_queues = 0;
data->nb_tx_queues = 0;
@@ -2119,7 +2251,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++)
process_private->fds[i] = -1;
-
if (pmd->type == ETH_TUNTAP_TYPE_TAP) {
if (rte_is_zero_ether_addr(mac_addr))
rte_eth_random_addr((uint8_t *)&pmd->eth_addr);
@@ -2227,6 +2358,9 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
}
#endif
+ /* Copy final MAC to slot 0 (remote path may have overwritten it) */
+ data->mac_addrs[0] = pmd->eth_addr;
+
rte_eth_dev_probing_finish(dev);
return 0;
@@ -2246,8 +2380,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
free(dev->process_private);
error_exit_nodev_release:
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
rte_eth_dev_release_port(dev);
error_exit_nodev:
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index b44eaf9a1bdb..8aa292bbd013 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -38,9 +38,13 @@ struct queue_stats {
uint64_t packets;
uint64_t bytes;
uint64_t errors;
+ uint64_t mac_drops; /* Packets dropped by MAC filter */
};
+struct pmd_internals;
+
struct rx_queue {
+ struct pmd_internals *pmd; /* back-pointer to driver state */
struct rte_mempool *mp; /* Mempool for RX packets */
uint32_t trigger_seen; /* Last seen Rx trigger value */
uint16_t in_port; /* Port ID */
@@ -70,6 +74,8 @@ struct pmd_internals {
int type; /* Type field - TUN|TAP */
int persist; /* 1 if keep link up, else 0 */
struct rte_ether_addr eth_addr; /* Mac address of the device port */
+ struct rte_ether_addr *mc_addrs; /* multicast address list */
+ uint32_t nb_mc_addrs; /* multicast address count */
unsigned int remote_initial_flags;/* Remote netdevice flags on init */
int remote_if_index; /* remote netdevice IF_INDEX */
int if_index; /* IF_INDEX for the port */
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH dpdk v3] net/tap: add software MAC address filtering
2026-03-21 14:48 ` [PATCH dpdk v3] " Robin Jarry
@ 2026-03-21 17:06 ` Stephen Hemminger
2026-03-21 23:20 ` Stephen Hemminger
1 sibling, 0 replies; 15+ messages in thread
From: Stephen Hemminger @ 2026-03-21 17:06 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev
On Sat, 21 Mar 2026 15:48:40 +0100
Robin Jarry <rjarry@redhat.com> wrote:
> Linux TAP devices deliver all packets to userspace regardless of the
> PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
> disabled, drop received packets whose destination MAC does not match
> any configured unicast or multicast address.
>
> The receive path checks the destination MAC against the device's
> unicast address table (managed by the ethdev layer), the multicast
> address list (stored by the driver since the ethdev layer does not keep
> a copy), and accepts broadcast unconditionally. Promiscuous and
> all-multicast modes bypass the respective checks.
>
> To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
> allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
> pointing into dev_private, and advertise the new limit in dev_infos_get.
>
> Dropped packets are reported via per-queue xstats
> (rx_q<N>_mac_filter_drops).
>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
Not sure the xstat for mac_filter_drops is needed, since it is what
other devices do as normal operation.
AI review found:
Two issues worth addressing in this patch.
The rte_realloc call in tap_set_mc_addr_list assigns directly back to
pmd->mc_addrs. When realloc fails it returns NULL but leaves the
original allocation intact, so the old pointer is lost and the memory
leaks. Use a temporary variable for the realloc result.
The ops table sets .xstats_reset = tap_stats_reset, but that function
only zeroes the base counters. The new mac_drops xstat is never reset.
Either extend the existing reset or add a dedicated xstats reset
function.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH dpdk v3] net/tap: add software MAC address filtering
2026-03-21 14:48 ` [PATCH dpdk v3] " Robin Jarry
2026-03-21 17:06 ` Stephen Hemminger
@ 2026-03-21 23:20 ` Stephen Hemminger
1 sibling, 0 replies; 15+ messages in thread
From: Stephen Hemminger @ 2026-03-21 23:20 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev
On Sat, 21 Mar 2026 15:48:40 +0100
Robin Jarry <rjarry@redhat.com> wrote:
> Linux TAP devices deliver all packets to userspace regardless of the
> PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
> disabled, drop received packets whose destination MAC does not match
> any configured unicast or multicast address.
>
> The receive path checks the destination MAC against the device's
> unicast address table (managed by the ethdev layer), the multicast
> address list (stored by the driver since the ethdev layer does not keep
> a copy), and accepts broadcast unconditionally. Promiscuous and
> all-multicast modes bypass the respective checks.
>
> To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
> allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
> pointing into dev_private, and advertise the new limit in dev_infos_get.
>
> Dropped packets are reported via per-queue xstats
> (rx_q<N>_mac_filter_drops).
>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
Here is a test for this.
From f1f093323a713abdfeb36a486a16b4b816095953 Mon Sep 17 00:00:00 2001
From: Stephen Hemminger <stephen@networkplumber.org>
Date: Sat, 21 Mar 2026 16:14:00 -0700
Subject: [PATCH 2/2] net/tap: add tests for software MAC address filtering
Extend the TAP PMD test suite to cover the new MAC filtering API.
Verify that dev_info advertises multiple unicast addresses,
mac_addr_add/remove and set_mc_addr_list work correctly,
and mac_filter_drops xstats are present and reset properly.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/test_pmd_tap.c | 133 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 133 insertions(+)
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
index dabd7d3506..bd3d4a64cc 100644
--- a/app/test/test_pmd_tap.c
+++ b/app/test/test_pmd_tap.c
@@ -757,6 +757,135 @@ test_tap_setup(void)
return 0;
}
+/*
+ * MAC address filtering tests.
+ *
+ * These exercise the new software MAC filtering paths added to TAP:
+ * mac_addr_add/remove, set_mc_addr_list, xstats for mac_filter_drops,
+ * and the increased max_mac_addrs reported in dev_info.
+ */
+
+static int
+test_tap_mac_filter_info(void)
+{
+ struct rte_eth_dev_info dev_info;
+
+ TEST_ASSERT_SUCCESS(rte_eth_dev_info_get(tap_port0, &dev_info),
+ "failed to get dev info");
+ TEST_ASSERT(dev_info.max_mac_addrs >= 16,
+ "max_mac_addrs=%u, expected >= 16",
+ dev_info.max_mac_addrs);
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_tap_mac_addr_ops(void)
+{
+ struct rte_ether_addr addr, addr2;
+
+ rte_eth_random_addr(addr.addr_bytes);
+ rte_eth_random_addr(addr2.addr_bytes);
+
+ TEST_ASSERT_SUCCESS(rte_eth_dev_mac_addr_add(tap_port0, &addr, 0),
+ "mac_addr_add first address failed");
+ TEST_ASSERT_SUCCESS(rte_eth_dev_mac_addr_add(tap_port0, &addr2, 0),
+ "mac_addr_add second address failed");
+
+ rte_eth_dev_mac_addr_remove(tap_port0, &addr2);
+ rte_eth_dev_mac_addr_remove(tap_port0, &addr);
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_tap_mc_addr_list(void)
+{
+ struct rte_ether_addr mc_addrs[3];
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ rte_eth_random_addr(mc_addrs[i].addr_bytes);
+ mc_addrs[i].addr_bytes[0] |= RTE_ETHER_GROUP_ADDR;
+ }
+
+ TEST_ASSERT_SUCCESS(rte_eth_dev_set_mc_addr_list(tap_port0, mc_addrs, 3),
+ "set_mc_addr_list(3) failed");
+ TEST_ASSERT_SUCCESS(rte_eth_dev_set_mc_addr_list(tap_port0, mc_addrs, 1),
+ "set_mc_addr_list(1) failed");
+ TEST_ASSERT_SUCCESS(rte_eth_dev_set_mc_addr_list(tap_port0, NULL, 0),
+ "set_mc_addr_list(0) failed");
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_tap_xstats_mac_filter(void)
+{
+ struct rte_eth_xstat_name *names = NULL;
+ struct rte_eth_xstat *xstats = NULL;
+ int nb_xstats, ret, i;
+ int found = 0;
+ int result = TEST_FAILED;
+
+ nb_xstats = rte_eth_xstats_get_names(tap_port0, NULL, 0);
+ TEST_ASSERT(nb_xstats > 0,
+ "expected at least 1 xstat, got %d", nb_xstats);
+
+ names = calloc(nb_xstats, sizeof(*names));
+ xstats = calloc(nb_xstats, sizeof(*xstats));
+ if (names == NULL || xstats == NULL) {
+ printf("Error: failed to allocate xstats arrays\n");
+ goto out;
+ }
+
+ ret = rte_eth_xstats_get_names(tap_port0, names, nb_xstats);
+ if (ret != nb_xstats) {
+ printf("Error: xstats_get_names returned %d, expected %d\n",
+ ret, nb_xstats);
+ goto out;
+ }
+
+ /* Verify at least one mac_filter_drops counter exists */
+ for (i = 0; i < nb_xstats; i++) {
+ if (strstr(names[i].name, "mac_filter_drops") != NULL)
+ found++;
+ }
+ if (found == 0) {
+ printf("Error: no mac_filter_drops xstat found\n");
+ goto out;
+ }
+
+ /* Reset and verify the mac_filter_drops counters are zero */
+ if (rte_eth_xstats_reset(tap_port0) != 0) {
+ printf("Error: xstats_reset failed\n");
+ goto out;
+ }
+
+ ret = rte_eth_xstats_get(tap_port0, xstats, nb_xstats);
+ if (ret != nb_xstats) {
+ printf("Error: xstats_get after reset returned %d\n", ret);
+ goto out;
+ }
+
+ for (i = 0; i < nb_xstats; i++) {
+ if (strstr(names[i].name, "mac_filter_drops") == NULL)
+ continue;
+ if (xstats[i].value != 0) {
+ printf("Error: %s not zero after reset\n",
+ names[i].name);
+ goto out;
+ }
+ }
+
+ result = TEST_SUCCESS;
+
+out:
+ free(names);
+ free(xstats);
+ return result;
+}
+
/* Individual test case wrappers */
static int
@@ -1128,6 +1257,10 @@ static struct unit_test_suite test_pmd_tap_suite = {
TEST_CASE(test_tap_multiqueue),
TEST_CASE(test_tap_rx_queue_setup),
TEST_CASE(test_tap_tx_burst),
+ TEST_CASE(test_tap_mac_filter_info),
+ TEST_CASE(test_tap_mac_addr_ops),
+ TEST_CASE(test_tap_mc_addr_list),
+ TEST_CASE(test_tap_xstats_mac_filter),
TEST_CASES_END()
}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH dpdk v4] net/tap: add software MAC address filtering
2026-03-19 22:10 [PATCH dpdk] net/tap: add software MAC address filtering Robin Jarry
` (3 preceding siblings ...)
2026-03-21 14:48 ` [PATCH dpdk v3] " Robin Jarry
@ 2026-03-23 8:42 ` Robin Jarry
2026-03-23 18:52 ` Stephen Hemminger
2026-03-24 19:09 ` [PATCH dpdk v5] " Robin Jarry
5 siblings, 1 reply; 15+ messages in thread
From: Robin Jarry @ 2026-03-23 8:42 UTC (permalink / raw)
To: dev, Stephen Hemminger
Linux TAP devices deliver all packets to userspace regardless of the
PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
disabled, drop received packets whose destination MAC does not match
any configured unicast or multicast address.
The receive path checks the destination MAC against the device's
unicast address table (managed by the ethdev layer), the multicast
address list (stored by the driver since the ethdev layer does not keep
a copy), and accepts broadcast unconditionally. Promiscuous and
all-multicast modes bypass the respective checks.
To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
pointing into dev_private, and advertise the new limit in dev_infos_get.
Add a test to ensure it works as expected.
Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
Notes:
v4:
* removed xstats
* added test
v3:
* always drop zero destination mac addresses unless promisc is enabled
v2:
* removed opt-in `macfilter=1` flag
* reorganized filter_match function to check unicast addresses first
app/test/test_pmd_tap.c | 238 ++++++++++++++++++++++++++++++++++
drivers/net/tap/rte_eth_tap.c | 120 ++++++++++++++---
drivers/net/tap/rte_eth_tap.h | 5 +
3 files changed, 348 insertions(+), 15 deletions(-)
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
index dabd7d350672..9a5df4a92a99 100644
--- a/app/test/test_pmd_tap.c
+++ b/app/test/test_pmd_tap.c
@@ -26,6 +26,9 @@ test_pmd_tap(void)
#include <string.h>
#include <unistd.h>
#include <limits.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <linux/if_packet.h>
#include <rte_ethdev.h>
#include <rte_bus_vdev.h>
@@ -1106,6 +1109,240 @@ test_tap_tx_burst(void)
return TEST_SUCCESS;
}
+/*
+ * Inject a raw Ethernet frame via an AF_PACKET socket bound to the TAP's
+ * Linux interface and attempt to receive it with rte_eth_rx_burst.
+ */
+static int
+tap_inject_packet(const char *ifname, const struct rte_ether_addr *dst)
+{
+ uint8_t frame[RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN];
+ union {
+ struct sockaddr_ll sll;
+ struct sockaddr sock;
+ } addr;
+ struct rte_ether_hdr *eth;
+ unsigned int ifindex;
+ int fd;
+
+ ifindex = if_nametoindex(ifname);
+ if (ifindex == 0) {
+ printf("Error: if_nametoindex(%s) failed\n", ifname);
+ return -1;
+ }
+
+ fd = socket(AF_PACKET, SOCK_RAW, RTE_BE16(RTE_ETHER_TYPE_IPV4));
+ if (fd < 0) {
+ printf("Error: AF_PACKET socket failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sll.sll_family = AF_PACKET;
+ addr.sll.sll_ifindex = ifindex;
+ addr.sll.sll_protocol = RTE_BE16(RTE_ETHER_TYPE_IPV4);
+
+ if (bind(fd, &addr.sock, sizeof(addr.sll)) < 0) {
+ printf("Error: bind to %s failed: %s\n", ifname, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ memset(frame, 0, sizeof(frame));
+ eth = (struct rte_ether_hdr *)frame;
+ eth->dst_addr = *dst;
+ eth->src_addr = (struct rte_ether_addr){{0x02, 0, 0, 0xbb, 0, 0xaa}};
+ eth->ether_type = RTE_BE16(RTE_ETHER_TYPE_IPV4);
+
+ if (send(fd, frame, sizeof(frame), 0) < 0) {
+ printf("Error: send failed: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return 0;
+}
+
+static uint16_t
+tap_drain_rx(int port, const struct rte_ether_addr *dst)
+{
+ struct rte_mbuf *mbufs[MAX_PKT_BURST];
+ struct rte_ether_hdr *eth;
+ uint16_t total = 0;
+
+ /* drain the rxq 10 times to ensure the kernel has sent the packet */
+ for (uint16_t i = 0; i < 10; i++) {
+ uint16_t nb_rx = rte_eth_rx_burst(port, 0, mbufs, MAX_PKT_BURST);
+ for (uint16_t j = 0; j < nb_rx; j++) {
+ eth = rte_pktmbuf_mtod(mbufs[j], struct rte_ether_hdr *);
+ if (dst != NULL && rte_is_same_ether_addr(ð->dst_addr, dst))
+ total += 1;
+ rte_pktmbuf_free(mbufs[j]);
+ }
+ usleep(1000);
+ }
+
+ return total;
+}
+
+static int
+test_tap_mac_filter(void)
+{
+ struct rte_ether_addr bcast_mac = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+ struct rte_ether_addr mcast_mac = {{0x01, 0, 0x5e, 0, 0, 0x01}};
+ struct rte_ether_addr foreign_mac = {{0x02, 0, 0, 0, 0, 0xaa}};
+ static const char *ifname = "dtap_test0";
+ struct rte_ether_addr port_mac;
+ uint16_t nb_rx;
+ int ret;
+
+ printf("Testing TAP MAC address filtering\n");
+
+ ret = rte_eth_macaddr_get(tap_port0, &port_mac);
+ if (ret != 0) {
+ printf("Error: failed to get MAC for port %d\n", tap_port0);
+ return TEST_FAILED;
+ }
+
+ /* Disable promisc so MAC filter is active */
+ rte_eth_promiscuous_disable(tap_port0);
+ rte_eth_allmulticast_disable(tap_port0);
+
+ /* Drain any stale packets */
+ tap_drain_rx(tap_port0, NULL);
+
+ /* Test 1: packet to port's own MAC should be received */
+ printf(" Test: unicast to own MAC\n");
+ ret = tap_inject_packet(ifname, &port_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &port_mac);
+ if (nb_rx == 0) {
+ printf("Error: packet to own MAC was not received\n");
+ return TEST_FAILED;
+ }
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+
+ /* Test 2: packet to foreign unicast MAC should be dropped */
+ printf(" Test: unicast to foreign MAC\n");
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ if (nb_rx != 0) {
+ printf("Error: packet to foreign MAC was not dropped (%u received)\n", nb_rx);
+ return TEST_FAILED;
+ }
+ printf(" Dropped - OK\n");
+
+ /* Test 3: broadcast should always be received */
+ printf(" Test: broadcast\n");
+ ret = tap_inject_packet(ifname, &bcast_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &bcast_mac);
+ if (nb_rx == 0) {
+ printf("Error: broadcast packet was not received\n");
+ return TEST_FAILED;
+ }
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+
+ /* Test 4: promisc mode should bypass the filter */
+ printf(" Test: promisc receives foreign MAC\n");
+ rte_eth_promiscuous_enable(tap_port0);
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ if (nb_rx == 0) {
+ printf("Error: promisc mode did not receive foreign MAC\n");
+ rte_eth_promiscuous_disable(tap_port0);
+ return TEST_FAILED;
+ }
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+ rte_eth_promiscuous_disable(tap_port0);
+
+ /* Test 5: multicast without allmulti and without mc list should drop */
+ printf(" Test: multicast dropped without mc list\n");
+ ret = tap_inject_packet(ifname, &mcast_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &mcast_mac);
+ if (nb_rx != 0) {
+ printf("Error: multicast was not dropped (%u received)\n", nb_rx);
+ return TEST_FAILED;
+ }
+ printf(" Dropped - OK\n");
+
+ /* Test 6: multicast with matching mc list should be received */
+ printf(" Test: multicast received with mc list\n");
+ ret = rte_eth_dev_set_mc_addr_list(tap_port0, &mcast_mac, 1);
+ if (ret != 0) {
+ printf("Error: set_mc_addr_list failed: %s\n", rte_strerror(-ret));
+ return TEST_FAILED;
+ }
+ ret = tap_inject_packet(ifname, &mcast_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &mcast_mac);
+ if (nb_rx == 0) {
+ printf("Error: multicast with matching mc list was not received\n");
+ return TEST_FAILED;
+ }
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+ rte_eth_dev_set_mc_addr_list(tap_port0, NULL, 0);
+
+ /* Test 7: allmulti should receive any multicast */
+ printf(" Test: allmulti receives multicast\n");
+ rte_eth_allmulticast_enable(tap_port0);
+ ret = tap_inject_packet(ifname, &mcast_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &mcast_mac);
+ if (nb_rx == 0) {
+ printf("Error: allmulti did not receive multicast\n");
+ rte_eth_allmulticast_disable(tap_port0);
+ return TEST_FAILED;
+ }
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+ rte_eth_allmulticast_disable(tap_port0);
+
+ /* Test 8: secondary unicast MAC via mac_addr_add */
+ printf(" Test: secondary unicast MAC\n");
+ ret = rte_eth_dev_mac_addr_add(tap_port0, &foreign_mac, 0);
+ if (ret != 0) {
+ printf("Error: mac_addr_add failed: %s\n", rte_strerror(-ret));
+ return TEST_FAILED;
+ }
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ if (nb_rx == 0) {
+ printf("Error: packet to added MAC was not received\n");
+ return TEST_FAILED;
+ }
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+
+ /* Remove and verify it's dropped again */
+ rte_eth_dev_mac_addr_remove(tap_port0, &foreign_mac);
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ if (nb_rx != 0) {
+ printf("Error: packet to removed MAC was not dropped (%u received)\n", nb_rx);
+ return TEST_FAILED;
+ }
+ printf(" Dropped after remove - OK\n");
+
+ /* Restore promisc (default state) */
+ rte_eth_promiscuous_enable(tap_port0);
+
+ return TEST_SUCCESS;
+}
+
static struct unit_test_suite test_pmd_tap_suite = {
.setup = test_tap_setup,
.teardown = test_tap_cleanup,
@@ -1128,6 +1365,7 @@ static struct unit_test_suite test_pmd_tap_suite = {
TEST_CASE(test_tap_multiqueue),
TEST_CASE(test_tap_rx_queue_setup),
TEST_CASE(test_tap_tx_burst),
+ TEST_CASE(test_tap_mac_filter),
TEST_CASES_END()
}
};
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 13e0a23c34a1..64b359914bac 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -58,6 +58,7 @@
#define ETH_TAP_CMP_MAC_FMT "0123456789ABCDEFabcdef"
#define ETH_TAP_MAC_ARG_FMT ETH_TAP_MAC_FIXED "|" ETH_TAP_USR_MAC_FMT
+#define TAP_MAX_MAC_ADDRS 16
#define TAP_GSO_MBUFS_PER_CORE 128
#define TAP_GSO_MBUF_SEG_SIZE 128
#define TAP_GSO_MBUF_CACHE_SIZE 4
@@ -437,6 +438,45 @@ tap_rxq_pool_free(struct rte_mbuf *pool)
rte_pktmbuf_free(pool);
}
+static inline bool
+tap_mac_filter_match(struct rx_queue *rxq, struct rte_mbuf *mbuf)
+{
+ struct pmd_internals *pmd = rxq->pmd;
+ struct rte_eth_dev_data *data;
+ struct rte_ether_addr *dst;
+ uint32_t i;
+
+ if (pmd->type != ETH_TUNTAP_TYPE_TAP)
+ return true;
+
+ data = pmd->dev->data;
+ if (data->promiscuous)
+ return true;
+
+ dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
+
+ if (unlikely(rte_is_zero_ether_addr(dst)))
+ return false;
+
+ if (likely(rte_is_unicast_ether_addr(dst))) {
+ for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
+ if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
+ return true;
+ }
+ return false;
+ }
+
+ if (data->all_multicast)
+ return true;
+
+ for (i = 0; i < pmd->nb_mc_addrs; i++) {
+ if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
+ return true;
+ }
+
+ return rte_is_broadcast_ether_addr(dst);
+}
+
/* Callback to handle the rx burst of packets to the correct interface and
* file descriptor(s) in a multi-queue setup.
*/
@@ -515,6 +555,12 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
data_off = 0;
}
seg->next = NULL;
+
+ if (!tap_mac_filter_match(rxq, mbuf)) {
+ rte_pktmbuf_free(mbuf);
+ continue;
+ }
+
mbuf->packet_type = rte_net_get_ptype(mbuf, NULL,
RTE_PTYPE_ALL_MASK);
if (rxq->rxmode->offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM)
@@ -933,7 +979,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
struct pmd_internals *internals = dev->data->dev_private;
dev_info->if_index = internals->if_index;
- dev_info->max_mac_addrs = 1;
+ dev_info->max_mac_addrs = TAP_MAX_MAC_ADDRS;
dev_info->max_rx_pktlen = RTE_ETHER_MAX_JUMBO_FRAME_LEN;
dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
@@ -1089,14 +1135,15 @@ tap_dev_close(struct rte_eth_dev *dev)
rte_mempool_free(internals->gso_ctx_mp);
internals->gso_ctx_mp = NULL;
+ rte_free(internals->mc_addrs);
+ internals->mc_addrs = NULL;
+ internals->nb_mc_addrs = 0;
+
if (internals->ka_fd != -1) {
close(internals->ka_fd);
internals->ka_fd = -1;
}
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
-
internals = dev->data->dev_private;
TAP_LOG(DEBUG, "Closing %s Ethernet device on numa %u",
tuntap_types[internals->type], rte_socket_id());
@@ -1574,6 +1621,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
}
tmp = &rxq->pool;
+ rxq->pmd = internals;
rxq->mp = mp;
rxq->trigger_seen = 1; /* force initial burst */
rxq->in_port = dev->data->port_id;
@@ -1692,17 +1740,50 @@ tap_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
}
static int
-tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
- struct rte_ether_addr *mc_addr_set __rte_unused,
- uint32_t nb_mc_addr __rte_unused)
+tap_set_mc_addr_list(struct rte_eth_dev *dev,
+ struct rte_ether_addr *mc_addr_set,
+ uint32_t nb_mc_addr)
{
- /*
- * Nothing to do actually: the tap has no filtering whatsoever, every
- * packet is received.
- */
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (nb_mc_addr == 0) {
+ rte_free(pmd->mc_addrs);
+ pmd->mc_addrs = NULL;
+ pmd->nb_mc_addrs = 0;
+ return 0;
+ }
+
+ pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
+ nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
+ if (pmd->mc_addrs == NULL) {
+ pmd->nb_mc_addrs = 0;
+ return -ENOMEM;
+ }
+
+ memcpy(pmd->mc_addrs, mc_addr_set,
+ nb_mc_addr * sizeof(*pmd->mc_addrs));
+ pmd->nb_mc_addrs = nb_mc_addr;
+
return 0;
}
+static int
+tap_mac_addr_add(struct rte_eth_dev *dev __rte_unused,
+ struct rte_ether_addr *mac_addr __rte_unused,
+ uint32_t index __rte_unused,
+ uint32_t vmdq __rte_unused)
+{
+ /* ethdev layer already stores the address in mac_addrs[] */
+ return 0;
+}
+
+static void
+tap_mac_addr_remove(struct rte_eth_dev *dev __rte_unused,
+ uint32_t index __rte_unused)
+{
+ /* ethdev layer already zeroes the slot in mac_addrs[] */
+}
+
static void tap_dev_intr_handler(void *cb_arg);
static int tap_lsc_intr_handle_set(struct rte_eth_dev *dev, int set);
@@ -2038,6 +2119,8 @@ static const struct eth_dev_ops ops = {
.allmulticast_enable = tap_allmulti_enable,
.allmulticast_disable = tap_allmulti_disable,
.mac_addr_set = tap_mac_set,
+ .mac_addr_add = tap_mac_addr_add,
+ .mac_addr_remove = tap_mac_addr_remove,
.mtu_set = tap_mtu_set,
.set_mc_addr_list = tap_set_mc_addr_list,
.stats_get = tap_stats_get,
@@ -2102,7 +2185,14 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
data->numa_node = numa_node;
data->dev_link = pmd_link;
- data->mac_addrs = &pmd->eth_addr;
+ data->mac_addrs = rte_calloc_socket(rte_vdev_device_name(vdev),
+ TAP_MAX_MAC_ADDRS,
+ sizeof(*data->mac_addrs),
+ 0, numa_node);
+ if (data->mac_addrs == NULL) {
+ TAP_LOG(ERR, "Failed to allocate mac_addrs");
+ goto error_exit;
+ }
/* Set the number of RX and TX queues */
data->nb_rx_queues = 0;
data->nb_tx_queues = 0;
@@ -2119,7 +2209,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++)
process_private->fds[i] = -1;
-
if (pmd->type == ETH_TUNTAP_TYPE_TAP) {
if (rte_is_zero_ether_addr(mac_addr))
rte_eth_random_addr((uint8_t *)&pmd->eth_addr);
@@ -2227,6 +2316,9 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
}
#endif
+ /* Copy final MAC to slot 0 (remote path may have overwritten it) */
+ data->mac_addrs[0] = pmd->eth_addr;
+
rte_eth_dev_probing_finish(dev);
return 0;
@@ -2246,8 +2338,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
free(dev->process_private);
error_exit_nodev_release:
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
rte_eth_dev_release_port(dev);
error_exit_nodev:
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index b44eaf9a1bdb..f53a5ad077ba 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -40,7 +40,10 @@ struct queue_stats {
uint64_t errors;
};
+struct pmd_internals;
+
struct rx_queue {
+ struct pmd_internals *pmd; /* back-pointer to driver state */
struct rte_mempool *mp; /* Mempool for RX packets */
uint32_t trigger_seen; /* Last seen Rx trigger value */
uint16_t in_port; /* Port ID */
@@ -70,6 +73,8 @@ struct pmd_internals {
int type; /* Type field - TUN|TAP */
int persist; /* 1 if keep link up, else 0 */
struct rte_ether_addr eth_addr; /* Mac address of the device port */
+ struct rte_ether_addr *mc_addrs; /* multicast address list */
+ uint32_t nb_mc_addrs; /* multicast address count */
unsigned int remote_initial_flags;/* Remote netdevice flags on init */
int remote_if_index; /* remote netdevice IF_INDEX */
int if_index; /* IF_INDEX for the port */
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH dpdk v4] net/tap: add software MAC address filtering
2026-03-23 8:42 ` [PATCH dpdk v4] " Robin Jarry
@ 2026-03-23 18:52 ` Stephen Hemminger
0 siblings, 0 replies; 15+ messages in thread
From: Stephen Hemminger @ 2026-03-23 18:52 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev
On Mon, 23 Mar 2026 09:42:42 +0100
Robin Jarry <rjarry@redhat.com> wrote:
> Linux TAP devices deliver all packets to userspace regardless of the
> PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
> disabled, drop received packets whose destination MAC does not match
> any configured unicast or multicast address.
>
> The receive path checks the destination MAC against the device's
> unicast address table (managed by the ethdev layer), the multicast
> address list (stored by the driver since the ethdev layer does not keep
> a copy), and accepts broadcast unconditionally. Promiscuous and
> all-multicast modes bypass the respective checks.
>
> To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
> allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
> pointing into dev_private, and advertise the new limit in dev_infos_get.
>
> Add a test to ensure it works as expected.
>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
Good to see test but:
- test should the TEST_ASSERT macros to keep things smaller.
- tests must test return value of functions during setup and normal teardown.
(i.e promiscious_disable, all mullticast_disable)
- there are helpers to inject packet already in the tap test.
would that work?
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH dpdk v5] net/tap: add software MAC address filtering
2026-03-19 22:10 [PATCH dpdk] net/tap: add software MAC address filtering Robin Jarry
` (4 preceding siblings ...)
2026-03-23 8:42 ` [PATCH dpdk v4] " Robin Jarry
@ 2026-03-24 19:09 ` Robin Jarry
2026-03-25 16:40 ` Stephen Hemminger
5 siblings, 1 reply; 15+ messages in thread
From: Robin Jarry @ 2026-03-24 19:09 UTC (permalink / raw)
To: dev, Stephen Hemminger
Linux TAP devices deliver all packets to userspace regardless of the
PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
disabled, drop received packets whose destination MAC does not match
any configured unicast or multicast address.
The receive path checks the destination MAC against the device's
unicast address table (managed by the ethdev layer), the multicast
address list (stored by the driver since the ethdev layer does not keep
a copy), and accepts broadcast unconditionally. Promiscuous and
all-multicast modes bypass the respective checks.
To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
pointing into dev_private, and advertise the new limit in dev_infos_get.
Add a test to ensure it works as expected.
Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
Notes:
v5:
* Use TEST_ASSERT() instead of manual boilerplate.
* Keep specific test functions to inject and receive packets. The existing ones
cannot be reused.
v4:
* removed xstats
* added test
v3:
* always drop zero destination mac addresses unless promisc is enabled
v2:
* removed opt-in `macfilter=1` flag
* reorganized filter_match function to check unicast addresses first
app/test/test_pmd_tap.c | 180 ++++++++++++++++++++++++++++++++++
drivers/net/tap/rte_eth_tap.c | 120 ++++++++++++++++++++---
drivers/net/tap/rte_eth_tap.h | 5 +
3 files changed, 290 insertions(+), 15 deletions(-)
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
index dabd7d350672..6d0e8b4f5450 100644
--- a/app/test/test_pmd_tap.c
+++ b/app/test/test_pmd_tap.c
@@ -26,6 +26,9 @@ test_pmd_tap(void)
#include <string.h>
#include <unistd.h>
#include <limits.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <linux/if_packet.h>
#include <rte_ethdev.h>
#include <rte_bus_vdev.h>
@@ -1106,6 +1109,182 @@ test_tap_tx_burst(void)
return TEST_SUCCESS;
}
+/*
+ * Inject a raw Ethernet frame via an AF_PACKET socket bound to the TAP's
+ * Linux interface and attempt to receive it with rte_eth_rx_burst.
+ */
+static int
+tap_inject_packet(const char *ifname, const struct rte_ether_addr *dst)
+{
+ uint8_t frame[RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN];
+ union {
+ struct sockaddr_ll sll;
+ struct sockaddr sock;
+ } addr;
+ struct rte_ether_hdr *eth;
+ unsigned int ifindex;
+ int fd, ret;
+
+ ifindex = if_nametoindex(ifname);
+ TEST_ASSERT(ifindex != 0, "if_nametoindex(%s) failed", ifname);
+
+ fd = socket(AF_PACKET, SOCK_RAW, RTE_BE16(RTE_ETHER_TYPE_IPV4));
+ TEST_ASSERT(fd >= 0, "AF_PACKET socket failed: %s", strerror(errno));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sll.sll_family = AF_PACKET;
+ addr.sll.sll_ifindex = ifindex;
+ addr.sll.sll_protocol = RTE_BE16(RTE_ETHER_TYPE_IPV4);
+
+ ret = bind(fd, &addr.sock, sizeof(addr.sll));
+ TEST_ASSERT(ret == 0, "bind to %s failed: %s", ifname, strerror(errno));
+
+ memset(frame, 0, sizeof(frame));
+ eth = (struct rte_ether_hdr *)frame;
+ eth->dst_addr = *dst;
+ eth->src_addr = (struct rte_ether_addr){{0x02, 0, 0, 0xbb, 0, 0xaa}};
+ eth->ether_type = RTE_BE16(RTE_ETHER_TYPE_IPV4);
+
+ ret = send(fd, frame, sizeof(frame), 0);
+ TEST_ASSERT(ret == sizeof(frame), "send failed: %s", strerror(errno));
+
+ close(fd);
+ return 0;
+}
+
+static uint16_t
+tap_drain_rx(int port, const struct rte_ether_addr *dst)
+{
+ struct rte_mbuf *mbufs[MAX_PKT_BURST];
+ struct rte_ether_hdr *eth;
+ uint16_t total = 0;
+
+ /* drain the rxq 10 times to ensure the kernel has sent the packet */
+ for (uint16_t i = 0; i < 10; i++) {
+ uint16_t nb_rx = rte_eth_rx_burst(port, 0, mbufs, MAX_PKT_BURST);
+ for (uint16_t j = 0; j < nb_rx; j++) {
+ eth = rte_pktmbuf_mtod(mbufs[j], struct rte_ether_hdr *);
+ if (dst != NULL && rte_is_same_ether_addr(ð->dst_addr, dst))
+ total += 1;
+ rte_pktmbuf_free(mbufs[j]);
+ }
+ usleep(1000);
+ }
+
+ return total;
+}
+
+static int
+test_tap_mac_filter(void)
+{
+ struct rte_ether_addr bcast_mac = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+ struct rte_ether_addr mcast_mac = {{0x01, 0, 0x5e, 0, 0, 0x01}};
+ struct rte_ether_addr foreign_mac = {{0x02, 0, 0, 0, 0, 0xaa}};
+ static const char *ifname = "dtap_test0";
+ struct rte_ether_addr port_mac;
+ uint16_t nb_rx;
+ int ret;
+
+ printf("Testing TAP MAC address filtering\n");
+
+ ret = rte_eth_macaddr_get(tap_port0, &port_mac);
+ TEST_ASSERT(ret == 0, "failed to get MAC for port %d", tap_port0);
+
+ /* Disable promisc so MAC filter is active */
+ rte_eth_promiscuous_disable(tap_port0);
+ rte_eth_allmulticast_disable(tap_port0);
+
+ /* Drain any stale packets */
+ tap_drain_rx(tap_port0, NULL);
+
+ /* Test 1: packet to port's own MAC should be received */
+ printf(" Test: unicast to own MAC\n");
+ ret = tap_inject_packet(ifname, &port_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &port_mac);
+ TEST_ASSERT(nb_rx > 0, "packet to own MAC was not received");
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+
+ /* Test 2: packet to foreign unicast MAC should be dropped */
+ printf(" Test: unicast to foreign MAC\n");
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ TEST_ASSERT(nb_rx == 0, "packet to foreign MAC was not dropped");
+ printf(" Dropped - OK\n");
+
+ /* Test 3: broadcast should always be received */
+ printf(" Test: broadcast\n");
+ ret = tap_inject_packet(ifname, &bcast_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &bcast_mac);
+ TEST_ASSERT(nb_rx > 0, "broadcast packet was not received");
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+
+ /* Test 4: promisc mode should bypass the filter */
+ printf(" Test: promisc receives foreign MAC\n");
+ rte_eth_promiscuous_enable(tap_port0);
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ TEST_ASSERT(nb_rx > 0, "promisc mode did not receive foreign MAC");
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+ rte_eth_promiscuous_disable(tap_port0);
+
+ /* Test 5: multicast without allmulti and without mc list should drop */
+ printf(" Test: multicast dropped without mc list\n");
+ ret = tap_inject_packet(ifname, &mcast_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &mcast_mac);
+ TEST_ASSERT(nb_rx == 0, "multicast was not dropped");
+ printf(" Dropped - OK\n");
+
+ /* Test 6: multicast with matching mc list should be received */
+ printf(" Test: multicast received with mc list\n");
+ ret = rte_eth_dev_set_mc_addr_list(tap_port0, &mcast_mac, 1);
+ TEST_ASSERT(ret == 0, "set_mc_addr_list failed: %s", rte_strerror(-ret));
+ ret = tap_inject_packet(ifname, &mcast_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &mcast_mac);
+ TEST_ASSERT(nb_rx > 0, "multicast with matching mc list was not received");
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+ rte_eth_dev_set_mc_addr_list(tap_port0, NULL, 0);
+
+ /* Test 7: allmulti should receive any multicast */
+ printf(" Test: allmulti receives multicast\n");
+ rte_eth_allmulticast_enable(tap_port0);
+ ret = tap_inject_packet(ifname, &mcast_mac);
+ if (ret < 0)
+ return TEST_FAILED;
+ nb_rx = tap_drain_rx(tap_port0, &mcast_mac);
+ TEST_ASSERT(nb_rx > 0, "allmulti did not receive multicast");
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+ rte_eth_allmulticast_disable(tap_port0);
+
+ /* Test 8: secondary unicast MAC via mac_addr_add */
+ printf(" Test: secondary unicast MAC\n");
+ ret = rte_eth_dev_mac_addr_add(tap_port0, &foreign_mac, 0);
+ TEST_ASSERT(ret == 0, "mac_addr_add failed: %s", rte_strerror(-ret));
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ TEST_ASSERT(nb_rx > 0, "packet to added MAC was not received");
+ printf(" Received %u packet(s) - OK\n", nb_rx);
+
+ /* Remove and verify it's dropped again */
+ rte_eth_dev_mac_addr_remove(tap_port0, &foreign_mac);
+ ret = tap_inject_packet(ifname, &foreign_mac);
+ TEST_ASSERT(ret == 0, "packet inject failed");
+ nb_rx = tap_drain_rx(tap_port0, &foreign_mac);
+ TEST_ASSERT(nb_rx == 0, "packet to removed MAC was not dropped");
+ printf(" Dropped after remove - OK\n");
+
+ /* Restore promisc (default state) */
+ rte_eth_promiscuous_enable(tap_port0);
+
+ return TEST_SUCCESS;
+}
+
static struct unit_test_suite test_pmd_tap_suite = {
.setup = test_tap_setup,
.teardown = test_tap_cleanup,
@@ -1128,6 +1307,7 @@ static struct unit_test_suite test_pmd_tap_suite = {
TEST_CASE(test_tap_multiqueue),
TEST_CASE(test_tap_rx_queue_setup),
TEST_CASE(test_tap_tx_burst),
+ TEST_CASE(test_tap_mac_filter),
TEST_CASES_END()
}
};
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 13e0a23c34a1..64b359914bac 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -58,6 +58,7 @@
#define ETH_TAP_CMP_MAC_FMT "0123456789ABCDEFabcdef"
#define ETH_TAP_MAC_ARG_FMT ETH_TAP_MAC_FIXED "|" ETH_TAP_USR_MAC_FMT
+#define TAP_MAX_MAC_ADDRS 16
#define TAP_GSO_MBUFS_PER_CORE 128
#define TAP_GSO_MBUF_SEG_SIZE 128
#define TAP_GSO_MBUF_CACHE_SIZE 4
@@ -437,6 +438,45 @@ tap_rxq_pool_free(struct rte_mbuf *pool)
rte_pktmbuf_free(pool);
}
+static inline bool
+tap_mac_filter_match(struct rx_queue *rxq, struct rte_mbuf *mbuf)
+{
+ struct pmd_internals *pmd = rxq->pmd;
+ struct rte_eth_dev_data *data;
+ struct rte_ether_addr *dst;
+ uint32_t i;
+
+ if (pmd->type != ETH_TUNTAP_TYPE_TAP)
+ return true;
+
+ data = pmd->dev->data;
+ if (data->promiscuous)
+ return true;
+
+ dst = rte_pktmbuf_mtod(mbuf, struct rte_ether_addr *);
+
+ if (unlikely(rte_is_zero_ether_addr(dst)))
+ return false;
+
+ if (likely(rte_is_unicast_ether_addr(dst))) {
+ for (i = 0; i < TAP_MAX_MAC_ADDRS; i++) {
+ if (rte_is_same_ether_addr(dst, &data->mac_addrs[i]))
+ return true;
+ }
+ return false;
+ }
+
+ if (data->all_multicast)
+ return true;
+
+ for (i = 0; i < pmd->nb_mc_addrs; i++) {
+ if (rte_is_same_ether_addr(dst, &pmd->mc_addrs[i]))
+ return true;
+ }
+
+ return rte_is_broadcast_ether_addr(dst);
+}
+
/* Callback to handle the rx burst of packets to the correct interface and
* file descriptor(s) in a multi-queue setup.
*/
@@ -515,6 +555,12 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
data_off = 0;
}
seg->next = NULL;
+
+ if (!tap_mac_filter_match(rxq, mbuf)) {
+ rte_pktmbuf_free(mbuf);
+ continue;
+ }
+
mbuf->packet_type = rte_net_get_ptype(mbuf, NULL,
RTE_PTYPE_ALL_MASK);
if (rxq->rxmode->offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM)
@@ -933,7 +979,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
struct pmd_internals *internals = dev->data->dev_private;
dev_info->if_index = internals->if_index;
- dev_info->max_mac_addrs = 1;
+ dev_info->max_mac_addrs = TAP_MAX_MAC_ADDRS;
dev_info->max_rx_pktlen = RTE_ETHER_MAX_JUMBO_FRAME_LEN;
dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
@@ -1089,14 +1135,15 @@ tap_dev_close(struct rte_eth_dev *dev)
rte_mempool_free(internals->gso_ctx_mp);
internals->gso_ctx_mp = NULL;
+ rte_free(internals->mc_addrs);
+ internals->mc_addrs = NULL;
+ internals->nb_mc_addrs = 0;
+
if (internals->ka_fd != -1) {
close(internals->ka_fd);
internals->ka_fd = -1;
}
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
-
internals = dev->data->dev_private;
TAP_LOG(DEBUG, "Closing %s Ethernet device on numa %u",
tuntap_types[internals->type], rte_socket_id());
@@ -1574,6 +1621,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
}
tmp = &rxq->pool;
+ rxq->pmd = internals;
rxq->mp = mp;
rxq->trigger_seen = 1; /* force initial burst */
rxq->in_port = dev->data->port_id;
@@ -1692,17 +1740,50 @@ tap_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
}
static int
-tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
- struct rte_ether_addr *mc_addr_set __rte_unused,
- uint32_t nb_mc_addr __rte_unused)
+tap_set_mc_addr_list(struct rte_eth_dev *dev,
+ struct rte_ether_addr *mc_addr_set,
+ uint32_t nb_mc_addr)
{
- /*
- * Nothing to do actually: the tap has no filtering whatsoever, every
- * packet is received.
- */
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (nb_mc_addr == 0) {
+ rte_free(pmd->mc_addrs);
+ pmd->mc_addrs = NULL;
+ pmd->nb_mc_addrs = 0;
+ return 0;
+ }
+
+ pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
+ nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
+ if (pmd->mc_addrs == NULL) {
+ pmd->nb_mc_addrs = 0;
+ return -ENOMEM;
+ }
+
+ memcpy(pmd->mc_addrs, mc_addr_set,
+ nb_mc_addr * sizeof(*pmd->mc_addrs));
+ pmd->nb_mc_addrs = nb_mc_addr;
+
return 0;
}
+static int
+tap_mac_addr_add(struct rte_eth_dev *dev __rte_unused,
+ struct rte_ether_addr *mac_addr __rte_unused,
+ uint32_t index __rte_unused,
+ uint32_t vmdq __rte_unused)
+{
+ /* ethdev layer already stores the address in mac_addrs[] */
+ return 0;
+}
+
+static void
+tap_mac_addr_remove(struct rte_eth_dev *dev __rte_unused,
+ uint32_t index __rte_unused)
+{
+ /* ethdev layer already zeroes the slot in mac_addrs[] */
+}
+
static void tap_dev_intr_handler(void *cb_arg);
static int tap_lsc_intr_handle_set(struct rte_eth_dev *dev, int set);
@@ -2038,6 +2119,8 @@ static const struct eth_dev_ops ops = {
.allmulticast_enable = tap_allmulti_enable,
.allmulticast_disable = tap_allmulti_disable,
.mac_addr_set = tap_mac_set,
+ .mac_addr_add = tap_mac_addr_add,
+ .mac_addr_remove = tap_mac_addr_remove,
.mtu_set = tap_mtu_set,
.set_mc_addr_list = tap_set_mc_addr_list,
.stats_get = tap_stats_get,
@@ -2102,7 +2185,14 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
data->numa_node = numa_node;
data->dev_link = pmd_link;
- data->mac_addrs = &pmd->eth_addr;
+ data->mac_addrs = rte_calloc_socket(rte_vdev_device_name(vdev),
+ TAP_MAX_MAC_ADDRS,
+ sizeof(*data->mac_addrs),
+ 0, numa_node);
+ if (data->mac_addrs == NULL) {
+ TAP_LOG(ERR, "Failed to allocate mac_addrs");
+ goto error_exit;
+ }
/* Set the number of RX and TX queues */
data->nb_rx_queues = 0;
data->nb_tx_queues = 0;
@@ -2119,7 +2209,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++)
process_private->fds[i] = -1;
-
if (pmd->type == ETH_TUNTAP_TYPE_TAP) {
if (rte_is_zero_ether_addr(mac_addr))
rte_eth_random_addr((uint8_t *)&pmd->eth_addr);
@@ -2227,6 +2316,9 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
}
#endif
+ /* Copy final MAC to slot 0 (remote path may have overwritten it) */
+ data->mac_addrs[0] = pmd->eth_addr;
+
rte_eth_dev_probing_finish(dev);
return 0;
@@ -2246,8 +2338,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
free(dev->process_private);
error_exit_nodev_release:
- /* mac_addrs must not be freed alone because part of dev_private */
- dev->data->mac_addrs = NULL;
rte_eth_dev_release_port(dev);
error_exit_nodev:
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index b44eaf9a1bdb..f53a5ad077ba 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -40,7 +40,10 @@ struct queue_stats {
uint64_t errors;
};
+struct pmd_internals;
+
struct rx_queue {
+ struct pmd_internals *pmd; /* back-pointer to driver state */
struct rte_mempool *mp; /* Mempool for RX packets */
uint32_t trigger_seen; /* Last seen Rx trigger value */
uint16_t in_port; /* Port ID */
@@ -70,6 +73,8 @@ struct pmd_internals {
int type; /* Type field - TUN|TAP */
int persist; /* 1 if keep link up, else 0 */
struct rte_ether_addr eth_addr; /* Mac address of the device port */
+ struct rte_ether_addr *mc_addrs; /* multicast address list */
+ uint32_t nb_mc_addrs; /* multicast address count */
unsigned int remote_initial_flags;/* Remote netdevice flags on init */
int remote_if_index; /* remote netdevice IF_INDEX */
int if_index; /* IF_INDEX for the port */
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH dpdk v5] net/tap: add software MAC address filtering
2026-03-24 19:09 ` [PATCH dpdk v5] " Robin Jarry
@ 2026-03-25 16:40 ` Stephen Hemminger
2026-03-26 9:15 ` Robin Jarry
0 siblings, 1 reply; 15+ messages in thread
From: Stephen Hemminger @ 2026-03-25 16:40 UTC (permalink / raw)
To: Robin Jarry; +Cc: dev
On Tue, 24 Mar 2026 20:09:16 +0100
Robin Jarry <rjarry@redhat.com> wrote:
> Linux TAP devices deliver all packets to userspace regardless of the
> PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
> disabled, drop received packets whose destination MAC does not match
> any configured unicast or multicast address.
>
> The receive path checks the destination MAC against the device's
> unicast address table (managed by the ethdev layer), the multicast
> address list (stored by the driver since the ethdev layer does not keep
> a copy), and accepts broadcast unconditionally. Promiscuous and
> all-multicast modes bypass the respective checks.
>
> To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
> allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
> pointing into dev_private, and advertise the new limit in dev_infos_get.
>
> Add a test to ensure it works as expected.
>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
Looks good, willing to merge this version.
AI review found some other small things; but these could be addressed later.
Warnings 2 and 3 look like just nuisance stuff.
---
**net/tap: add software MAC address filtering**
**Errors:**
1. **Resource leak in `tap_set_mc_addr_list()` on `rte_realloc()` failure.**
`rte_realloc()` returns NULL on failure but does NOT free the original allocation. The code assigns the result directly back to `pmd->mc_addrs`, so when it returns NULL the pointer to the old allocation is lost and leaked.
```c
/* Current - leaks old mc_addrs on failure */
pmd->mc_addrs = rte_realloc(pmd->mc_addrs,
nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
if (pmd->mc_addrs == NULL) {
pmd->nb_mc_addrs = 0;
return -ENOMEM;
}
/* Fix - save old pointer */
struct rte_ether_addr *new_addrs;
new_addrs = rte_realloc(pmd->mc_addrs,
nb_mc_addr * sizeof(*pmd->mc_addrs), 0);
if (new_addrs == NULL)
return -ENOMEM;
pmd->mc_addrs = new_addrs;
```
Note the fix also avoids zeroing `nb_mc_addrs` on failure, which preserves the old list as a reasonable fallback.
2. **fd leak in `tap_inject_packet()` on error after `socket()`.**
If `bind()` or `send()` fails, `TEST_ASSERT` causes an immediate return without closing `fd`. This is test code so the impact is minor, but the fd leaks on every failed assertion after the socket is opened.
Suggest using a local `goto cleanup` pattern or closing `fd` before each `TEST_ASSERT`.
**Warnings:**
1. **Inconsistent error handling in test 7.** All other tests in `test_tap_mac_filter` use `TEST_ASSERT(ret == 0, ...)` for the inject call, but test 7 uses a manual `if (ret < 0) return TEST_FAILED;` check. This also skips `rte_eth_allmulticast_disable()` cleanup on failure. Should use `TEST_ASSERT` for consistency.
2. **`rte_malloc` used for `mc_addrs` in `tap_set_mc_addr_list()`.** The multicast address list is a control-path data structure not accessed by DMA and not shared between processes (it's in `pmd_internals`, which is `dev_private`). Standard `malloc`/`realloc`/`free` would be more appropriate per DPDK guidelines, and would avoid consuming hugepage memory. If `rte_malloc` is kept, the leak fix above needs to use `rte_free` for cleanup, which it already would since the old pointer came from `rte_realloc`.
3. **Broadcast check could be moved before the `mc_addrs` loop.** In `tap_mac_filter_match()`, broadcast frames (which are a subset of multicast) traverse the entire `mc_addrs` loop before hitting the `rte_is_broadcast_ether_addr()` check at the end. Moving the broadcast check before the loop would avoid unnecessary iterations for a common packet type:
```c
if (rte_is_broadcast_ether_addr(dst))
return true;
if (data->all_multicast)
return true;
for (i = 0; i < pmd->nb_mc_addrs; i++) { ... }
```
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH dpdk v5] net/tap: add software MAC address filtering
2026-03-25 16:40 ` Stephen Hemminger
@ 2026-03-26 9:15 ` Robin Jarry
0 siblings, 0 replies; 15+ messages in thread
From: Robin Jarry @ 2026-03-26 9:15 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
Stephen Hemminger, Mar 25, 2026 at 17:40:
> On Tue, 24 Mar 2026 20:09:16 +0100
> Robin Jarry <rjarry@redhat.com> wrote:
>
>> Linux TAP devices deliver all packets to userspace regardless of the
>> PROMISC/ALLMULTI flags on the interface. When promiscuous mode is
>> disabled, drop received packets whose destination MAC does not match
>> any configured unicast or multicast address.
>>
>> The receive path checks the destination MAC against the device's
>> unicast address table (managed by the ethdev layer), the multicast
>> address list (stored by the driver since the ethdev layer does not keep
>> a copy), and accepts broadcast unconditionally. Promiscuous and
>> all-multicast modes bypass the respective checks.
>>
>> To support multiple unicast addresses via rte_eth_dev_mac_addr_add(),
>> allocate mac_addrs with rte_zmalloc (TAP_MAX_MAC_ADDRS=16) instead of
>> pointing into dev_private, and advertise the new limit in dev_infos_get.
>>
>> Add a test to ensure it works as expected.
>>
>> Signed-off-by: Robin Jarry <rjarry@redhat.com>
>> ---
>
> Looks good, willing to merge this version.
>
> AI review found some other small things; but these could be addressed later.
> Warnings 2 and 3 look like just nuisance stuff.
Hey Stephen, thanks for reviewing.
> 1. **Resource leak in `tap_set_mc_addr_list()` on `rte_realloc()` failure.**
> `rte_realloc()` returns NULL on failure but does NOT free the original allocation. The code assigns the result directly back to `pmd->mc_addrs`, so when it returns NULL the pointer to the old allocation is lost and leaked.
I can send a v6 with this fix.
> 2. **fd leak in `tap_inject_packet()` on error after `socket()`.**
> If `bind()` or `send()` fails, `TEST_ASSERT` causes an immediate return without closing `fd`. This is test code so the impact is minor, but the fd leaks on every failed assertion after the socket is opened.
>
> Suggest using a local `goto cleanup` pattern or closing `fd` before each `TEST_ASSERT`.
This seems not important for test code.
> 2. **`rte_malloc` used for `mc_addrs` in `tap_set_mc_addr_list()`.** The multicast address list is a control-path data structure not accessed by DMA and not shared between processes (it's in `pmd_internals`, which is `dev_private`). Standard `malloc`/`realloc`/`free` would be more appropriate per DPDK guidelines, and would avoid consuming hugepage memory. If `rte_malloc` is kept, the leak fix above needs to use `rte_free` for cleanup, which it already would since the old pointer came from `rte_realloc`.
I don't mind, but it seems pedantic.
> 3. **Broadcast check could be moved before the `mc_addrs` loop.** In `tap_mac_filter_match()`, broadcast frames (which are a subset of multicast) traverse the entire `mc_addrs` loop before hitting the `rte_is_broadcast_ether_addr()` check at the end. Moving the broadcast check before the loop would avoid unnecessary iterations for a common packet type:
I can change this.
Let me know if you want a respin.
^ permalink raw reply [flat|nested] 15+ messages in thread