Subject: VRF NS for lladdr sent on the wrong interface

From: Stephen Suryaputra <[email-masked]>
Date: Mon, 23 Nov 2020 19:23:45 -0500

Hi,

I'm running into a problem with lladdr pinging all-host mcast all nodes
addr. The ping intially works but after cycling the interface that
receives the ping, the echo request packet causes a neigh solicitation
being sent on a different interface.

To repro, I included the attached namespace scripts. This is the
topology and an output of my test.

# +-------+     +----------+   +-------+
# | h0    |     |    r0    |   |    h1 |
# |    v00+-----+v00    v01+---+v10    |
# |       |     |          |   |       |
# +-------+     +----------+   +-------+

Setup and list of addresses:

$ sudo sh ./setup.sh
$ sudo ip netns exec h0 ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
28: h0_v00@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether b2:72:0a:25:7d:0f brd ff:ff:ff:ff:ff:ff link-netns r0
    inet6 fe80::b072:aff:fe25:7d0f/64 scope link 
       valid_lft forever preferred_lft forever
$ sudo ip netns exec r0 ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: vrf_r0: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP group default qlen 1000
    link/ether 5a:48:08:1e:e6:38 brd ff:ff:ff:ff:ff:ff
    inet 127.0.0.1/8 scope host vrf_r0
       valid_lft forever preferred_lft forever
27: r0_v00@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vrf_r0 state UP group default qlen 1000
    link/ether aa:9d:a5:cf:ab:75 brd ff:ff:ff:ff:ff:ff link-netns h0
    inet6 fe80::a89d:a5ff:fecf:ab75/64 scope link 
       valid_lft forever preferred_lft forever
30: r0_v01@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vrf_r0 state UP group default qlen 1000
    link/ether 52:0f:70:b5:a8:6a brd ff:ff:ff:ff:ff:ff link-netns h1
    inet6 fe80::500f:70ff:feb5:a86a/64 scope link 
       valid_lft forever preferred_lft forever
$ sudo ip netns exec h1 ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
29: h1_v10@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether aa:3c:c1:a0:07:7c brd ff:ff:ff:ff:ff:ff link-netns r0
    inet6 fe80::a83c:c1ff:fea0:77c/64 scope link 
       valid_lft forever preferred_lft forever

Initially ping is replied by r0_v00:

$ sudo ip netns exec h0 ping -c 1 ff02::1%h0_v00
PING ff02::1%h0_v00(ff02::1%h0_v00) 56 data bytes
64 bytes from fe80::a89d:a5ff:fecf:ab75%h0_v00: icmp_seq=1 ttl=64 time=4.44 ms

--- ff02::1%h0_v00 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 4.443/4.443/4.443/0.000 ms

Cycle r0_v00. Ping isn't replied and the tcpdump shows that the NS for h0_v00 lladdr
is sent over r0_v01:

$ sudo ip netns exec r0 ip link set r0_v00 down
$ sudo ip netns exec r0 ip link set r0_v00 up
$ sudo ip netns exec r0 ip addr show dev r0_v00
27: r0_v00@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vrf_r0 state UP group default qlen 1000
    link/ether aa:9d:a5:cf:ab:75 brd ff:ff:ff:ff:ff:ff link-netns h0
    inet6 fe80::a89d:a5ff:fecf:ab75/64 scope link 
       valid_lft forever preferred_lft forever

$ sudo ip netns exec h0 ping -c 1 ff02::1%h0_v00
PING ff02::1%h0_v00(ff02::1%h0_v00) 56 data bytes

--- ff02::1%h0_v00 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

$ sudo ip netns exec h1 tcpdump -i h1_v10
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on h1_v10, link-type EN10MB (Ethernet), capture size 262144 bytes
^C12:36:12.210524 IP6 fe80::a83c:c1ff:fea0:77c > ip6-allrouters: ICMP6, router solicitation, length 16
12:36:34.456650 IP6 fe80::500f:70ff:feb5:a86a > ff02::1:ff25:7d0f: ICMP6, neighbor solicitation, who has fe80::b072:aff:fe25:7d0f, length 32
12:36:35.474519 IP6 fe80::500f:70ff:feb5:a86a > ff02::1:ff25:7d0f: ICMP6, neighbor solicitation, who has fe80::b072:aff:fe25:7d0f, length 32
12:36:36.498455 IP6 fe80::500f:70ff:feb5:a86a > ff02::1:ff25:7d0f: ICMP6, neighbor solicitation, who has fe80::b072:aff:fe25:7d0f, length 32

4 packets captured
4 packets received by filter
0 packets dropped by kernel

I'm thinking that the following patch is needed:

diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index f2793ffde191..30f4e867fe5b 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -1315,11 +1315,14 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
 	int orig_iif = skb->skb_iif;
 	bool need_strict = rt6_need_strict(&ipv6_hdr(skb)->daddr);
 	bool is_ndisc = ipv6_ndisc_frame(skb);
+	bool is_ll_src;
 
 	/* loopback, multicast & non-ND link-local traffic; do not push through
 	 * packet taps again. Reset pkt_type for upper layers to process skb
 	 */
-	if (skb->pkt_type == PACKET_LOOPBACK || (need_strict && !is_ndisc)) {
+	is_ll_src = ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL;
+	if (skb->pkt_type == PACKET_LOOPBACK ||
+	    (need_strict && !is_ndisc && !is_ll_src)) {
 		skb->dev = vrf_dev;
 		skb->skb_iif = vrf_dev->ifindex;
 		IP6CB(skb)->flags |= IP6SKB_L3SLAVE;

But wanting to probe first to see if I could have missed something. Or
is there a better patch. I would be happy to follow up with a formal patch.

Thank you,

Stephen.