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

Fallback to IPv6 default routes for network interface detection #4321

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Comment on lines +229 to +231
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I disagree with that now that we have Scoped IPs.
The PR is still useful though, I think having a better default conf.iface is good, especially for L2 functions. (e.g. sniff)

.. 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()

# Reload interfaces
conf.ifaces.reload()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bit crappy. We only need to reload the default interface, not the entire interface list

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
Loading