Skip to content

Commit

Permalink
Use IPv6 default route when no IPv4 is available
Browse files Browse the repository at this point in the history
  • Loading branch information
guedou committed Jul 12, 2024
1 parent ac3d5bb commit 70c08cc
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 2 deletions.
3 changes: 3 additions & 0 deletions doc/scapy/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ make\_table() displays a table according to a lambda function
Sending packets
---------------

.. note::
Scapy automatically detects the network interface to be used by default, and stores this result in ``conf.iface``. Packets built by Scapy uses this variable to set relevant fields such as Ethernet source addresses. When sending packets, with functions such as ``send()``, Scapy will use the network interface stored in ``conf.iface``. This behavior can be changed using the ``iface=`` argument. With IPv6 and link-local addresses, it is mandatory to setup both ``conf.iface`` and ``iface=`` the same value to get the desired result, as Scapy cannot find which interface to use for link-local communications.

.. index::
single: Sending packets, send

Expand Down
26 changes: 24 additions & 2 deletions scapy/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,11 @@ 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)
ipv4_interface = resolve_iface(conf.loopback_name)
routes = conf.route.routes[:]
routes.sort(key=lambda x: x[1])
ifaces = (x[3] for x in routes)
Expand All @@ -382,9 +385,28 @@ def get_working_if():
for ifname in itertools.chain(ifaces, conf.ifaces.values()):
iface = resolve_iface(ifname) # type: ignore
if iface.is_valid():
return iface
ipv4_interface = iface
break

if ipv4_interface.name != conf.loopback_name:
return ipv4_interface

# IPv6
if conf.route6:
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 3) of the highest priority default
ifname = tmp_routes[-1][3]
iface = resolve_iface(ifname)
if iface.is_valid():
return iface

# There is no hope left
return resolve_iface(conf.loopback_name)
return ipv4_interface


def get_working_ifaces():
Expand Down
3 changes: 3 additions & 0 deletions scapy/route6.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,6 @@ def route(self, dst="", dev=None, verbose=conf.verb):


conf.route6 = Route6()

# Update conf.iface
conf.ifaces.load_confiface()
14 changes: 14 additions & 0 deletions test/regression.uts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,20 @@ assert "cuz you know the way to go" + conf.iface # right +

_test_get_working_if()

# Test IPv6 default route interface selection
ni4 = NetworkInterface(InterfaceProvider(), {"name": conf.loopback_name, "ips": ["127.0.0.1"], "mac": "aa:aa:aa:aa:aa:aa"})
ni6 = NetworkInterface(InterfaceProvider(), {"name": "scapy0", "ips": ["::1"], "mac": "aa:aa:aa:aa:aa:aa"})

import mock
@mock.patch("scapy.interfaces.conf.ifaces.values")
@mock.patch("scapy.interfaces.conf.route.routes", [(0, 0, '0.0.0.0', 'lo0', '127.0.0.1', 1)])
@mock.patch("scapy.interfaces.conf.route6.routes", [("::", 0, "", ni6, [""], 0)])
def _test_get_working_if_v6(ifaces_values):
ifaces_values.side_effect = lambda: []
assert get_working_if() == ni6

_test_get_working_if_v6()

= Test conf.ifaces

conf.iface
Expand Down

0 comments on commit 70c08cc

Please sign in to comment.