[SRU bionic PATCH 2/2] ipv6: fix neighbour resolution with raw socket

Nicolas Dichtel nicolas.dichtel at 6wind.com
Fri Sep 6 09:54:44 UTC 2019

BugLink: https://bugs.launchpad.net/bugs/1834465

The scenario is the following: the user uses a raw socket to send an ipv6
packet, destinated to a not-connected network, and specify a connected nh.
Here is the corresponding python script to reproduce this scenario:

 import socket
 send_s = socket.socket(socket.AF_INET6, socket.SOCK_RAW, IPPROTO_RAW)
 # scapy
 # p = IPv6(src='fd00:100::1', dst='fd00:200::fa')/ICMPv6EchoRequest()
 # str(p)
 req = b'`\x00\x00\x00\x00\x08:@\xfd\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfd\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfa\x80\x00\x81\xc0\x00\x00\x00\x00'
 send_s.sendto(req, ('fd00:175::2', 0, 0, 0))

fd00:175::/64 is a connected route and fd00:200::fa is not a connected

With this scenario, the kernel starts by sending a NS to resolve
fd00:175::2. When it receives the NA, it flushes its queue and try to send
the initial packet. But instead of sending it, it sends another NS to
resolve fd00:200::fa, which obvioulsy fails, thus the packet is dropped. If
the user sends again the packet, it now uses the right nh (fd00:175::2).

The problem is that ip6_dst_lookup_neigh() uses the rt6i_gateway, which is
:: because the associated route is a connected route, thus it uses the dst
addr of the packet. Let's use rt6_nexthop() to choose the right nh.

Signed-off-by: Nicolas Dichtel <nicolas.dichtel at 6wind.com>
Signed-off-by: David S. Miller <davem at davemloft.net>
(cherry picked from commit 2c6b55f45d53420d8310d41310e0e2cd41fe073f)

The upstream patch was slightly modified because ip6_dst_lookup_neigh()
does not exist in v4.15. It has been introduced in v4.18 by the upstream
commit f8a1b43b709d ("net/ipv6: Create a neigh_lookup for FIB entries"). In
fact, ip6_dst_lookup_neigh() just calls ip6_neigh_lookup().

Signed-off-by: Nicolas Dichtel <nicolas.dichtel at 6wind.com>
 net/ipv6/route.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 28d39567c26a..df5cadc6423d 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -201,12 +201,10 @@ static u32 *ipv6_cow_metrics(struct dst_entry *dst, unsigned long old)
 		return dst_cow_metrics_generic(dst, old);
-static inline const void *choose_neigh_daddr(struct rt6_info *rt,
+static inline const void *choose_neigh_daddr(const struct in6_addr *p,
 					     struct sk_buff *skb,
 					     const void *daddr)
-	struct in6_addr *p = &rt->rt6i_gateway;
 	if (!ipv6_addr_any(p))
 		return (const void *) p;
 	else if (skb)
@@ -221,7 +219,7 @@ static struct neighbour *ip6_neigh_lookup(const struct dst_entry *dst,
 	struct rt6_info *rt = (struct rt6_info *) dst;
 	struct neighbour *n;
-	daddr = choose_neigh_daddr(rt, skb, daddr);
+	daddr = choose_neigh_daddr(rt6_nexthop(rt, &in6addr_any), skb, daddr);
 	n = __ipv6_neigh_lookup(dst->dev, daddr);
 	if (n)
 		return n;
@@ -233,7 +231,7 @@ static void ip6_confirm_neigh(const struct dst_entry *dst, const void *daddr)
 	struct net_device *dev = dst->dev;
 	struct rt6_info *rt = (struct rt6_info *)dst;
-	daddr = choose_neigh_daddr(rt, NULL, daddr);
+	daddr = choose_neigh_daddr(&rt->rt6i_gateway, NULL, daddr);
 	if (!daddr)
 	if (dev->flags & (IFF_NOARP | IFF_LOOPBACK))

More information about the kernel-team mailing list