[RFC net-next v2 1/3] ipv6: ndisc: export the route attach as ndisc_attach_dst()

From: Xiang Mei (Microsoft)

Date: Wed Jul 29 2026 - 19:02:03 EST


ndisc_send_skb() pushes the IPv6 header and transmits in one go, so a
caller that has to build the link-layer header and transmit the skb
itself cannot use it. bonding needs that split to insert VLAN tags
into its NS monitor probes.

Such a caller still has to attach a route before handing the packet to
netfilter: ip6t_mangle_out() calls ip6_route_me_harder() whenever a
rule changes the addresses, mark, hop limit or flow label, and that
dereferences skb_dst(skb) unconditionally. Neither icmp6_dst_alloc()
nor icmpv6_flow_init() is exported, so a module cannot attach one
itself.

Move the route attach into ndisc_attach_dst() and export it. It is a
no-op when the skb already carries a route, so ndisc_send_skb() is
unchanged for existing callers.

Signed-off-by: Xiang Mei (Microsoft) <xmei5@xxxxxxx>
---
v2: v1 fixed the crash, ignoring the data corruption.
v2 tries to fix both.

include/net/ndisc.h | 3 +++
net/ipv6/ndisc.c | 53 ++++++++++++++++++++++++++++++++-------------
2 files changed, 41 insertions(+), 15 deletions(-)

diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 3da1a6f8d3f9..f3e3c3e5c61e 100644
--- a/include/net/ndisc.h
+++ b/include/net/ndisc.h
@@ -411,6 +411,9 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
const struct in6_addr *daddr, const struct in6_addr *saddr,
u64 nonce);

+int ndisc_attach_dst(struct sk_buff *skb, const struct in6_addr *daddr,
+ const struct in6_addr *saddr);
+
void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,
const struct in6_addr *saddr);

diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index fe36b3f51285..91ffbfec9e5d 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -465,13 +465,44 @@ static void ip6_nd_hdr(struct sk_buff *skb,
hdr->daddr = *daddr;
}

+/* Attach a route to a locally generated ndisc message; @skb is not consumed
+ * on failure. Split out of ndisc_send_skb() for callers that build the
+ * link-layer header themselves.
+ */
+int ndisc_attach_dst(struct sk_buff *skb, const struct in6_addr *daddr,
+ const struct in6_addr *saddr)
+{
+ u8 type = icmp6_hdr(skb)->icmp6_type;
+ struct dst_entry *dst;
+ struct flowi6 fl6;
+ struct sock *sk;
+ struct net *net;
+
+ if (skb_dst(skb))
+ return 0;
+
+ rcu_read_lock();
+ net = dev_net_rcu(skb->dev);
+ sk = net->ipv6.ndisc_sk;
+ icmpv6_flow_init(sk, &fl6, type, saddr, daddr, skb->dev->ifindex);
+ dst = icmp6_dst_alloc(skb->dev, &fl6);
+ rcu_read_unlock();
+
+ if (IS_ERR(dst))
+ return PTR_ERR(dst);
+
+ skb_dst_set(skb, dst);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ndisc_attach_dst);
+
void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,
const struct in6_addr *saddr)
{
struct icmp6hdr *icmp6h = icmp6_hdr(skb);
- struct dst_entry *dst = skb_dst(skb);
struct net_device *dev;
struct inet6_dev *idev;
+ struct dst_entry *dst;
struct net *net;
struct sock *sk;
int err;
@@ -479,24 +510,16 @@ void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,

type = icmp6h->icmp6_type;

+ if (ndisc_attach_dst(skb, daddr, saddr)) {
+ kfree_skb(skb);
+ return;
+ }
+
rcu_read_lock();

net = dev_net_rcu(skb->dev);
sk = net->ipv6.ndisc_sk;
- if (!dst) {
- struct flowi6 fl6;
- int oif = skb->dev->ifindex;
-
- icmpv6_flow_init(sk, &fl6, type, saddr, daddr, oif);
- dst = icmp6_dst_alloc(skb->dev, &fl6);
- if (IS_ERR(dst)) {
- rcu_read_unlock();
- kfree_skb(skb);
- return;
- }
-
- skb_dst_set(skb, dst);
- }
+ dst = skb_dst(skb);

icmp6h->icmp6_cksum = csum_ipv6_magic(saddr, daddr, skb->len,
IPPROTO_ICMPV6,
--
2.43.0