No route found for IPv6 destination ff02::1:ff00:1 (no default route?) when running IPv6-only
Brief description
When running Scapy on IPv6-only host, it produces warnings every time it sends any data despite working normally.
Scapy version
2.5.0
Python version
3.10.12
Operating system
3.10.0-1160.102.1.el7.x86_64
Additional environment information
Running in an IPv6-only docker container based on Ubuntu
How to reproduce
>>> a, u = sr(IPv6(dst="2001:db8:f:1::1")/ICMPv6EchoRequest())
Actual result
Begin emission:
WARNING: No route found for IPv6 destination ff02::1:ff00:1 (no default route?)
WARNING: Mac address to reach destination not found. Using broadcast.
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
Expected result
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
Related resources
The routing table has an IPv6 default route. The issue lies in the fact the loopback gets selected as the default interface (conf.iface) because the selection algorithm of default interface is IPv4-only. Also even though eth0 has some IPv6 addresses, they are not shown when there is no IPv4 address configured.
>>> conf.route6
INFO: Table cropped to fit the terminal (conf.auto_crop_tables==True)
Destination Next Hop Iface Src candidates Metric
2001:db8:f:1::/64 :: eth0 2001:db8:f:1:a8c1:abff:fe7e:4cd5 256
fe80::/64 :: eth0 fe80::a8c1:abff:fe7e:4cd5 256
::1/128 :: lo ::1 0
2001:db8:f:1:a8c1:abff:fe7e:4cd_ :: lo ::1 0
fe80::a8c1:abff:fe7e:4cd5/128 :: lo ::1 0
::/0 fe80::a8c1:abff:fe2c:3b40 eth0 2001:db8:f:1:a8c1:abff:fe7e:4cd5 1024
>>> conf.ifaces
Source Index Name MAC IPv4 IPv6
sys 1 lo 00:00:00:00:00:00 127.0.0.1 ::1
sys 1620 eth0 aa:c1:ab:7e:4c:d5
>>> conf.iface
<NetworkInterface lo [UP+LOOPBACK+RUNNING]>
Changing conf.iface manually to eth0 works around the issue. I failed to find a way how to do this automatically though, as it is apparently not possible to adjust this using prestart.py.
Thanks for you report. Does this patch fixes your issue:
$ git diff scapy/
diff --git a/scapy/interfaces.py b/scapy/interfaces.py
index f40f529e..30056b60 100644
--- a/scapy/interfaces.py
+++ b/scapy/interfaces.py
@@ -372,6 +372,8 @@ def get_if_list():
def get_working_if():
# type: () -> NetworkInterface
"""Return an interface that works"""
+
+ # IPv4
# return the interface associated with the route with smallest
# mask (route by default if it exists)
routes = conf.route.routes[:]
@@ -383,6 +385,17 @@ def get_working_if():
iface = resolve_iface(ifname) # type: ignore
if iface.is_valid():
return iface
+
+ # IPv6
+ routes_ipv6 = conf.route6.routes
+ default_routes_ipv6 = [r for r in routes_ipv6 if r[0] == "::"]
+ if default_routes_ipv6:
+ # Sort the default routes using the priority (at index -1)
+ tmp_routes = sorted(default_routes_ipv6, key=lambda r: r[-1])
+
+ # Return the interface (at index 2), of the highes priority default
+ return tmp_routes[-1][2]
+
# There is no hope left
return resolve_iface(conf.loopback_name)
Thanks for you report. Does this patch fixes your issue:
Hey, sorry for delayed answer. No, this patch does not help as the IPv6 section is never reached, if there is at least one route in the IPv4 routing table. Which is almost always the case because of the loopback interface:
>>> conf.route
Network Netmask Gateway Iface Output IP Metric
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 1
That's a good point. We could check if the only IPv4 interface is the loopback, and move on to IPv6 if it is true. What do you think?
That would work. Or we can just look for IPv4 default route, if it is not there look for IPv6 default route and if that is not there either, fall back to loopback.
So I have created the proposed solution:
diff --git a/scapy/interfaces.py b/scapy/interfaces.py
index f40f529e..73403098 100644
--- a/scapy/interfaces.py
+++ b/scapy/interfaces.py
@@ -372,11 +372,17 @@ def get_if_list():
def get_working_if():
# type: () -> NetworkInterface
"""Return an interface that works"""
- # return the interface associated with the route with smallest
- # mask (route by default if it exists)
- routes = conf.route.routes[:]
- routes.sort(key=lambda x: x[1])
- ifaces = (x[3] for x in routes)
+ # return the interface associated with the default route
+ # IPv4 default route is preferred, then IPv6 route shorter or equal than /8 or
+ # loopback as a fallback
+ default_routes_v4 = (x for x in conf.route.routes if x[1] == 0)
+ if conf.route6:
+ default_routes_v6 = (x for x in conf.route6.routes if x[1] <= 8)
+ default_routes_v6 = sorted(default_routes_v6, key=lambda x: x[1])
+ else:
+ default_routes_v6 = list()
+
+ ifaces = (x[3] for x in itertools.chain(default_routes_v4, default_routes_v6))
# First check the routing ifaces from best to worse,
# then check all the available ifaces as backup.
for ifname in itertools.chain(ifaces, conf.ifaces.values()):
Unfortunately it does not work because conf.route6 is populated later than get_working_if() is called. Trying to import route6 from interfaces.py leads to circular import. Any idea how to resolve this?