]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: ipv6: Add ip6_mr_output()
authorPetr Machata <petrm@nvidia.com>
Mon, 16 Jun 2025 22:44:18 +0000 (00:44 +0200)
committerJakub Kicinski <kuba@kernel.org>
Wed, 18 Jun 2025 01:18:46 +0000 (18:18 -0700)
Multicast routing is today handled in the input path. Locally generated MC
packets don't hit the IPMR code today. Thus if a VXLAN remote address is
multicast, the driver needs to set an OIF during route lookup. Thus MC
routing configuration needs to be kept in sync with the VXLAN FDB and MDB.
Ideally, the VXLAN packets would be routed by the MC routing code instead.

To that end, this patch adds support to route locally generated multicast
packets. The newly-added routines do largely what ip6_mr_input() and
ip6_mr_forward() do: make an MR cache lookup to find where to send the
packets, and use ip6_output() to send each of them. When no cache entry is
found, the packet is punted to the daemon for resolution.

Similarly to the IPv4 case in a previous patch, the new logic is contingent
on a newly-added IP6CB flag being set.

Signed-off-by: Petr Machata <petrm@nvidia.com>
Link: https://patch.msgid.link/3bcc034a3ab4d3c291072fff38f78d7fbbeef4e6.1750113335.git.petrm@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
include/linux/ipv6.h
include/linux/mroute6.h
net/ipv6/ip6mr.c
net/ipv6/route.c

index 5aeeed22f35bfc8f7d2b927f539c37d765f7cc4d..db0eb0d86b641cafa7d8f87060e900f379c422d4 100644 (file)
@@ -156,6 +156,7 @@ struct inet6_skb_parm {
 #define IP6SKB_SEG6          256
 #define IP6SKB_FAKEJUMBO      512
 #define IP6SKB_MULTIPATH      1024
+#define IP6SKB_MCROUTE        2048
 };
 
 #if defined(CONFIG_NET_L3_MASTER_DEV)
index 63ef5191cc57908ca6b0da692c9e812875715000..fddafdc168f7334cfde612c4f6d545243987d4c6 100644 (file)
@@ -31,6 +31,7 @@ extern int ip6_mroute_getsockopt(struct sock *, int, sockptr_t, sockptr_t);
 extern int ip6_mr_input(struct sk_buff *skb);
 extern int ip6mr_compat_ioctl(struct sock *sk, unsigned int cmd, void __user *arg);
 extern int ip6_mr_init(void);
+extern int ip6_mr_output(struct net *net, struct sock *sk, struct sk_buff *skb);
 extern void ip6_mr_cleanup(void);
 int ip6mr_ioctl(struct sock *sk, int cmd, void *arg);
 #else
@@ -58,6 +59,12 @@ static inline int ip6_mr_init(void)
        return 0;
 }
 
+static inline int
+ip6_mr_output(struct net *net, struct sock *sk, struct sk_buff *skb)
+{
+       return ip6_output(net, sk, skb);
+}
+
 static inline void ip6_mr_cleanup(void)
 {
        return;
index bd964564160d08724f8a4b669d83737cf92ee556..a35f4f1c658960e4b087848461f3ea7af653d070 100644 (file)
@@ -2119,6 +2119,19 @@ out_free:
        kfree_skb(skb);
 }
 
+static void ip6mr_output2(struct net *net, struct mr_table *mrt,
+                         struct sk_buff *skb, int vifi)
+{
+       if (ip6mr_prepare_xmit(net, mrt, skb, vifi))
+               goto out_free;
+
+       ip6_output(net, NULL, skb);
+       return;
+
+out_free:
+       kfree_skb(skb);
+}
+
 /* Called with rcu_read_lock() */
 static int ip6mr_find_vif(struct mr_table *mrt, struct net_device *dev)
 {
@@ -2231,6 +2244,56 @@ dont_forward:
        kfree_skb(skb);
 }
 
+/* Called under rcu_read_lock() */
+static void ip6_mr_output_finish(struct net *net, struct mr_table *mrt,
+                                struct net_device *dev, struct sk_buff *skb,
+                                struct mfc6_cache *c)
+{
+       int psend = -1;
+       int ct;
+
+       WARN_ON_ONCE(!rcu_read_lock_held());
+
+       atomic_long_inc(&c->_c.mfc_un.res.pkt);
+       atomic_long_add(skb->len, &c->_c.mfc_un.res.bytes);
+       WRITE_ONCE(c->_c.mfc_un.res.lastuse, jiffies);
+
+       /* Forward the frame */
+       if (ipv6_addr_any(&c->mf6c_origin) &&
+           ipv6_addr_any(&c->mf6c_mcastgrp)) {
+               if (ipv6_hdr(skb)->hop_limit >
+                   c->_c.mfc_un.res.ttls[c->_c.mfc_parent]) {
+                       /* It's an (*,*) entry and the packet is not coming from
+                        * the upstream: forward the packet to the upstream
+                        * only.
+                        */
+                       psend = c->_c.mfc_parent;
+                       goto last_forward;
+               }
+               goto dont_forward;
+       }
+       for (ct = c->_c.mfc_un.res.maxvif - 1;
+            ct >= c->_c.mfc_un.res.minvif; ct--) {
+               if (ipv6_hdr(skb)->hop_limit > c->_c.mfc_un.res.ttls[ct]) {
+                       if (psend != -1) {
+                               struct sk_buff *skb2;
+
+                               skb2 = skb_clone(skb, GFP_ATOMIC);
+                               if (skb2)
+                                       ip6mr_output2(net, mrt, skb2, psend);
+                       }
+                       psend = ct;
+               }
+       }
+last_forward:
+       if (psend != -1) {
+               ip6mr_output2(net, mrt, skb, psend);
+               return;
+       }
+
+dont_forward:
+       kfree_skb(skb);
+}
 
 /*
  *     Multicast packets for forwarding arrive here
@@ -2298,6 +2361,61 @@ int ip6_mr_input(struct sk_buff *skb)
        return 0;
 }
 
+int ip6_mr_output(struct net *net, struct sock *sk, struct sk_buff *skb)
+{
+       struct net_device *dev = skb_dst(skb)->dev;
+       struct flowi6 fl6 = (struct flowi6) {
+               .flowi6_iif = LOOPBACK_IFINDEX,
+               .flowi6_mark = skb->mark,
+       };
+       struct mfc6_cache *cache;
+       struct mr_table *mrt;
+       int err;
+       int vif;
+
+       WARN_ON_ONCE(!rcu_read_lock_held());
+
+       if (IP6CB(skb)->flags & IP6SKB_FORWARDED)
+               goto ip6_output;
+       if (!(IP6CB(skb)->flags & IP6SKB_MCROUTE))
+               goto ip6_output;
+
+       err = ip6mr_fib_lookup(net, &fl6, &mrt);
+       if (err < 0) {
+               kfree_skb(skb);
+               return err;
+       }
+
+       cache = ip6mr_cache_find(mrt,
+                                &ipv6_hdr(skb)->saddr, &ipv6_hdr(skb)->daddr);
+       if (!cache) {
+               vif = ip6mr_find_vif(mrt, dev);
+               if (vif >= 0)
+                       cache = ip6mr_cache_find_any(mrt,
+                                                    &ipv6_hdr(skb)->daddr,
+                                                    vif);
+       }
+
+       /* No usable cache entry */
+       if (!cache) {
+               vif = ip6mr_find_vif(mrt, dev);
+               if (vif >= 0)
+                       return ip6mr_cache_unresolved(mrt, vif, skb, dev);
+               goto ip6_output;
+       }
+
+       /* Wrong interface */
+       vif = cache->_c.mfc_parent;
+       if (rcu_access_pointer(mrt->vif_table[vif].dev) != dev)
+               goto ip6_output;
+
+       ip6_mr_output_finish(net, mrt, dev, skb, cache);
+       return 0;
+
+ip6_output:
+       return ip6_output(net, sk, skb);
+}
+
 int ip6mr_get_route(struct net *net, struct sk_buff *skb, struct rtmsg *rtm,
                    u32 portid)
 {
index 79c8f1acf8a35e465ab1a21aca50a554ca6f513b..df0caffefb3824f5d962ff62f9ee96005ed9c718 100644 (file)
@@ -1145,6 +1145,7 @@ static void ip6_rt_init_dst(struct rt6_info *rt, const struct fib6_result *res)
                rt->dst.input = ip6_input;
        } else if (ipv6_addr_type(&f6i->fib6_dst.addr) & IPV6_ADDR_MULTICAST) {
                rt->dst.input = ip6_mc_input;
+               rt->dst.output = ip6_mr_output;
        } else {
                rt->dst.input = ip6_forward;
        }