]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net/sched: Fix ethx:ingress -> ethy:egress -> ethx:ingress mirred loop
authorJamal Hadi Salim <jhs@mojatatu.com>
Mon, 25 May 2026 12:25:52 +0000 (08:25 -0400)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 28 May 2026 10:26:36 +0000 (12:26 +0200)
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: fe946a751d9b ("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>
net/sched/act_mirred.c

index 2c5a7a321a94385367a014cc4d9de85acd7af747..dd5e7ea7ef2652c430c223717e93845dd246b204 100644 (file)
 #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;
-       }
 
-       xmit->sched_mirred_dev[xmit->sched_mirred_nest++] = dev;
+       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;
+       }
 
        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;
 }