Skip to content

Commit 70c08cc

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

File tree

4 files changed

+44
-2
lines changed

4 files changed

+44
-2
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

+24-2
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,11 @@ 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)
@@ -382,9 +385,28 @@ def get_working_if():
382385
for ifname in itertools.chain(ifaces, conf.ifaces.values()):
383386
iface = resolve_iface(ifname) # type: ignore
384387
if iface.is_valid():
385-
return iface
388+
ipv4_interface = iface
389+
break
390+
391+
if ipv4_interface.name != conf.loopback_name:
392+
return ipv4_interface
393+
394+
# IPv6
395+
if conf.route6:
396+
routes_ipv6 = conf.route6.routes
397+
default_routes_ipv6 = [r for r in routes_ipv6 if r[0] == "::"]
398+
if default_routes_ipv6:
399+
# Sort the default routes using the priority (at index -1)
400+
tmp_routes = sorted(default_routes_ipv6, key=lambda r: r[-1])
401+
402+
# Return the interface (at index 3) of the highest priority default
403+
ifname = tmp_routes[-1][3]
404+
iface = resolve_iface(ifname)
405+
if iface.is_valid():
406+
return iface
407+
386408
# There is no hope left
387-
return resolve_iface(conf.loopback_name)
409+
return ipv4_interface
388410

389411

390412
def get_working_ifaces():

scapy/route6.py

+3
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,6 @@ def route(self, dst="", dev=None, verbose=conf.verb):
360360

361361

362362
conf.route6 = Route6()
363+
364+
# Update conf.iface
365+
conf.ifaces.load_confiface()

test/regression.uts

+14
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,20 @@ 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+
assert get_working_if() == ni6
348+
349+
_test_get_working_if_v6()
350+
337351
= Test conf.ifaces
338352

339353
conf.iface

0 commit comments

Comments
 (0)