diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index 67b3f02a05c..cabd87a5e29 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -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 diff --git a/scapy/interfaces.py b/scapy/interfaces.py index f40f529ea8b..c7c25992d0c 100644 --- a/scapy/interfaces.py +++ b/scapy/interfaces.py @@ -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) @@ -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(): diff --git a/scapy/route6.py b/scapy/route6.py index dd86b26ca6b..745d2d94e3a 100644 --- a/scapy/route6.py +++ b/scapy/route6.py @@ -360,3 +360,6 @@ def route(self, dst="", dev=None, verbose=conf.verb): conf.route6 = Route6() + +# Reload interfaces +conf.ifaces.reload() diff --git a/test/regression.uts b/test/regression.uts index 189f3f60de8..9b95f53e571 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -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