net/sched: Fix ethx:ingress -> ethy:egress -> ethx:ingress mirred loop

When mirred redirects to ingress (from either ingress or egress) the loop
state from sched_mirred_dev array dev is lost because of 1) the packet
deferral into the backlog and 2) the fact the sched_mirred_dev array is
cleared. In such cases, if there was a loop we won't discover it.

Here's a simple test to reproduce:
ip a add dev port0 10.10.10.11/24

tc qdisc add dev port0 clsact
tc filter add dev port0 egress protocol ip \
   prio 10 matchall action mirred ingress redirect dev port1

tc qdisc add dev port1 clsact
tc filter add dev port1 ingress protocol ip \
   prio 10 matchall action mirred egress redirect dev port0

ping -c 1 -W0.01 10.10.10.10

Fixes: fe946a751d ("net/sched: act_mirred: add loop detection")
Tested-by: Victor Nogueira <victor@mojatatu.com>
Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Link: https://patch.msgid.link/20260525122556.973584-6-jhs@mojatatu.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Jamal Hadi Salim 2026-05-25 08:25:52 -04:00 committed by Paolo Abeni
parent 9552b11e3e
commit db875221ab

View File

@ -26,6 +26,10 @@
#include <net/tc_act/tc_mirred.h>
#include <net/tc_wrapper.h>
#define MIRRED_DEFER_LIMIT 3
_Static_assert(MIRRED_DEFER_LIMIT <= 3,
"MIRRED_DEFER_LIMIT exceeds tc_depth bitfield width");
static LIST_HEAD(mirred_list);
static DEFINE_SPINLOCK(mirred_list_lock);
@ -234,12 +238,15 @@ tcf_mirred_forward(bool at_ingress, bool want_ingress, struct sk_buff *skb)
{
int err;
if (!want_ingress)
if (!want_ingress) {
err = tcf_dev_queue_xmit(skb, dev_queue_xmit);
else if (!at_ingress)
err = netif_rx(skb);
else
err = netif_receive_skb(skb);
} else {
skb->tc_depth++;
if (!at_ingress)
err = netif_rx(skb);
else
err = netif_receive_skb(skb);
}
return err;
}
@ -426,6 +433,7 @@ TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
struct netdev_xmit *xmit;
bool m_mac_header_xmit;
struct net_device *dev;
bool want_ingress;
int i, m_eaction;
u32 blockid;
@ -434,7 +442,8 @@ TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
#else
xmit = this_cpu_ptr(&softnet_data.xmit);
#endif
if (unlikely(xmit->sched_mirred_nest >= MIRRED_NEST_LIMIT)) {
if (unlikely(xmit->sched_mirred_nest >= MIRRED_NEST_LIMIT ||
skb->tc_depth >= MIRRED_DEFER_LIMIT)) {
net_warn_ratelimited("Packet exceeded mirred recursion limit on dev %s\n",
netdev_name(skb->dev));
return TC_ACT_SHOT;
@ -453,23 +462,27 @@ TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
tcf_action_inc_overlimit_qstats(&m->common);
return retval;
}
for (i = 0; i < xmit->sched_mirred_nest; i++) {
if (xmit->sched_mirred_dev[i] != dev)
continue;
pr_notice_once("tc mirred: loop on device %s\n",
netdev_name(dev));
tcf_action_inc_overlimit_qstats(&m->common);
return retval;
m_eaction = READ_ONCE(m->tcfm_eaction);
want_ingress = tcf_mirred_act_wants_ingress(m_eaction);
if (!want_ingress) {
for (i = 0; i < xmit->sched_mirred_nest; i++) {
if (xmit->sched_mirred_dev[i] != dev)
continue;
pr_notice_once("tc mirred: loop on device %s\n",
netdev_name(dev));
tcf_action_inc_overlimit_qstats(&m->common);
return retval;
}
xmit->sched_mirred_dev[xmit->sched_mirred_nest++] = dev;
}
xmit->sched_mirred_dev[xmit->sched_mirred_nest++] = dev;
m_mac_header_xmit = READ_ONCE(m->tcfm_mac_header_xmit);
m_eaction = READ_ONCE(m->tcfm_eaction);
retval = tcf_mirred_to_dev(skb, m, dev, m_mac_header_xmit, m_eaction,
retval);
xmit->sched_mirred_nest--;
if (!want_ingress)
xmit->sched_mirred_nest--;
return retval;
}