[RFC net-next v2 0/3] bonding: fix NS probe transmit over stacked VLANs

From: Xiang Mei (Microsoft)

Date: Wed Jul 29 2026 - 19:01:44 EST


The bonding NS monitor (ns_ip6_target) builds a Neighbour
Solicitation with ndisc_ns_create() and tags it for the slave's VLAN path in
bond_ns_send(). ndisc_ns_create() returns only the ICMPv6 message: the IPv6
and link-layer headers are pushed later, by ndisc_send_skb(). bond_ns_send()
however hands that bare skb to bond_handle_vlan() first, which is too early:

- bond_handle_vlan() inserts each inner tag with mac_len = ETH_HLEN, so it
writes 12 bytes into the ICMPv6 message (inside the target address) when no
Ethernet header is present yet. Any config with two or more stacked VLAN
devices therefore emits a probe no peer can answer.

- Each inner tag also eats VLAN_HLEN of the headroom that was reserved for
the IPv6 header. With LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) = 56
bytes, six stacked VLAN devices leave 36 and the subsequent
ip6_nd_hdr() -> skb_push(sizeof(struct ipv6hdr)) underflows past skb->head:

skbuff: skb_under_panic: text:ffffffff85eef44b len:84 put:40
head:ffff88801400b740 data:ffff88801400b73c
tail:0x50 end:0x180 dev:veth0
kernel BUG at net/core/skbuff.c:214!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
Workqueue: bond0 bond_arp_monitor
RIP: 0010:skb_panic+0x142/0x230
Call Trace:
skb_push (net/core/skbuff.c:224)
ndisc_send_skb (net/ipv6/ndisc.c:454 net/ipv6/ndisc.c:506)
bond_ns_send (drivers/net/bonding/bond_main.c:3255)
bond_ns_send_all (drivers/net/bonding/bond_main.c:3313)
bond_arp_monitor (drivers/net/bonding/bond_main.c:3458)
process_one_work (kernel/workqueue.c:3322)
worker_thread (kernel/workqueue.c:3405)
kthread (kernel/kthread.c:436)
Kernel panic - not syncing: Fatal exception

The minimal fix for just the panic is a skb_cow_head() before ndisc_send_skb();
it stops the crash but leaves the packet malformed for two or more stacked
VLANs, because the tags are still inserted into a header-less skb. This series
instead fixes the root cause: it builds the probe in the same order the rest of
the stack uses, which removes the corruption and the headroom underflow at once.

Approach
========
1/3 Export the ndisc route attach as ndisc_attach_dst(). ndisc_send_skb()
both attaches a route and transmits; a caller that has to build the
link-layer header itself needs only the attach, because
ip6_route_me_harder() in the LOCAL_OUT path dereferences skb_dst()
unconditionally and icmp6_dst_alloc()/icmpv6_flow_init() are not
exported. No behavioural change for existing callers.

2/3 Rework bond_ns_send() to build headers in stack order:
- bond_ns_build_ip6hdr() pushes the IPv6 header and computes the checksum;
- the probe then traverses NF_INET_LOCAL_OUT as a bare L3 packet, so
netfilter sees skb->data at the IPv6 header rather than at a
half-built Ethernet frame;
- only once the hooks accept it does bond_ns_add_llhdr() push the
link-layer header and bond_handle_vlan() add the VLAN tags, after
which the probe is transmitted on the slave being tested.
With the headers pushed first, skb_vlan_push() grows the headroom
itself, so no skb_cow_head() is needed.

3/3 Convert bond_handle_vlan() to skb_vlan_push(), which inserts the tags at
the correct offset once the Ethernet header exists, and share the path
with the ARP probe.

Why RFC
=======
I understand the security issues, but I am not familiar with the related
development concerns. I have tried my best to learn and follow similar
implementations, but still not sure if it's complete/missing any problems.

Testing
=======
Reproduced the skb_under_panic and the malformed probe on a bond over 6 stacked
VLAN devices, and verified correct NS probes (accepted by the peer, correct
checksum and tag stack) with 0, 1, 2, 5 and 6 stacked VLAN devices after the
series.

Xiang Mei (Microsoft) (3):
ipv6: ndisc: export the route attach as ndisc_attach_dst()
bonding: build NS probe headers before inserting VLAN tags
bonding: use skb_vlan_push() for probe tags

drivers/net/bonding/bond_main.c | 144 ++++++++++++++++++++++++++------
include/net/ndisc.h | 3 +
net/ipv6/ndisc.c | 53 ++++++++----
3 files changed, 160 insertions(+), 40 deletions(-)

--
2.43.0