Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No route found for IPv6 destination ff02::1:ff00:1 (no default route?) when running IPv6-only #4304

Open
oskar456 opened this issue Feb 28, 2024 · 5 comments
Milestone

Comments

@oskar456
Copy link

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.

@guedou
Copy link
Member

guedou commented Mar 16, 2024

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)

@oskar456
Copy link
Author

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

@guedou
Copy link
Member

guedou commented Apr 21, 2024

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?

@oskar456
Copy link
Author

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.

@oskar456
Copy link
Author

oskar456 commented May 7, 2024

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?

oskar456 added a commit to oskar456/scapy that referenced this issue May 7, 2024
Instead of considering the full IPv4 routing table, we only look for
IPv4 default route and IPv6 routes shorter or equal /8 (for instance
`2000::/3`). Since the `conf.route6` is not ready yet when we first run,
we reload `conf.ifaces` after loading `route6.py`.

This fixes secdev#4304

Signed-off-by: Ondřej Caletka <ondrej@caletka.cz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants