Skip to content

Commit 4447e20

Browse files
committed
Use IPv6 default route when no IPv4 is available
1 parent ac3d5bb commit 4447e20

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

doc/scapy/usage.rst

+3
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ make\_table() displays a table according to a lambda function
226226
Sending packets
227227
---------------
228228

229+
.. note::
230+
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.
231+
229232
.. index::
230233
single: Sending packets, send
231234

scapy/interfaces.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -372,19 +372,39 @@ def get_if_list():
372372
def get_working_if():
373373
# type: () -> NetworkInterface
374374
"""Return an interface that works"""
375+
376+
# IPv4
375377
# return the interface associated with the route with smallest
376378
# mask (route by default if it exists)
379+
ipv4_interface = resolve_iface(conf.loopback_name)
377380
routes = conf.route.routes[:]
378381
routes.sort(key=lambda x: x[1])
379382
ifaces = (x[3] for x in routes)
380383
# First check the routing ifaces from best to worse,
381384
# then check all the available ifaces as backup.
382385
for ifname in itertools.chain(ifaces, conf.ifaces.values()):
383386
iface = resolve_iface(ifname) # type: ignore
387+
if iface.is_valid():
388+
ipv4_interface = iface
389+
390+
if ipv4_interface.name != conf.loopback_name:
391+
return ipv4_interface
392+
393+
# IPv6
394+
routes_ipv6 = conf.route6.routes
395+
default_routes_ipv6 = [r for r in routes_ipv6 if r[0] == "::"]
396+
if default_routes_ipv6:
397+
# Sort the default routes using the priority (at index -1)
398+
tmp_routes = sorted(default_routes_ipv6, key=lambda r: r[-1])
399+
400+
# Return the interface (at index 3) of the highest priority default
401+
ifname = tmp_routes[-1][3]
402+
iface = resolve_iface(ifname)
384403
if iface.is_valid():
385404
return iface
405+
386406
# There is no hope left
387-
return resolve_iface(conf.loopback_name)
407+
return ipv4_interface
388408

389409

390410
def get_working_ifaces():

test/regression.uts

+15
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,21 @@ assert "cuz you know the way to go" + conf.iface # right +
334334

335335
_test_get_working_if()
336336

337+
# Test IPv6 default route interface selection
338+
ni4 = NetworkInterface(InterfaceProvider(), {"name": conf.loopback_name, "ips": ["127.0.0.1"], "mac": "aa:aa:aa:aa:aa:aa"})
339+
ni6 = NetworkInterface(InterfaceProvider(), {"name": "scapy0", "ips": ["::1"], "mac": "aa:aa:aa:aa:aa:aa"})
340+
341+
import mock
342+
@mock.patch("scapy.interfaces.conf.ifaces.values")
343+
@mock.patch("scapy.interfaces.conf.route.routes", [(0, 0, '0.0.0.0', 'lo0', '127.0.0.1', 1)])
344+
@mock.patch("scapy.interfaces.conf.route6.routes", [("::", 0, "", ni6, [""], 0)])
345+
def _test_get_working_if_v6(ifaces_values):
346+
ifaces_values.side_effect = lambda: []
347+
348+
assert get_working_if() == ni6
349+
350+
_test_get_working_if_v6()
351+
337352
= Test conf.ifaces
338353

339354
conf.iface

0 commit comments

Comments
 (0)