Repository: secdev/scapy

Issue:
[IPv6] Wrong link-local address selection with multiple interfaces
It seems that Scapy does not correctly handle multiple interfaces with IPv6. The following script must not fail:
```python
import os
import time

os.system("ip link add name scapy0 type dummy")
os.system("ip link set scapy0 up")

from scapy.all import *

conf.iface = "scapy0"

p = Ether()/IPv6(dst="ff02::1")/ICMPv6NIQueryName(data="ff02::1")
print p.sprintf("%Ether.src% > %Ether.dst%\n%IPv6.src% > %IPv6.dst%")

ip6_ll_address = [a[0] for a in in6_getifaddr() if a[2] == conf.iface and
                                                   a[0].startswith("fe80")][0]
print p[IPv6].src, ip6_ll_address
assert p[IPv6].src == ip6_ll_address

mac_address =  get_if_hwaddr(conf.iface)
print p[Ether].src, mac_address
assert p[Ether].src == get_if_hwaddr(conf.iface)

p = Ether(raw(p))
print p.sprintf("%Ether.src% > %Ether.dst%\n%IPv6.src% > %IPv6.dst%")

ip6_ll_address = [a[0] for a in in6_getifaddr() if a[2] == conf.iface and
                                                   a[0].startswith("fe80")][0]
print p[IPv6].src, ip6_ll_address
assert p[IPv6].src == ip6_ll_address

mac_address =  get_if_hwaddr(conf.iface)
print p[Ether].src, mac_address
assert p[Ether].src == get_if_hwaddr(conf.iface)

os.system("ip link del name dev scapy0")
```

Filtering unneeded routes works as a temporary workaround:
```python
conf.route6.routes = [r6 for r6 in conf.route6.routes if r6[3] == conf.iface
```



Discussion:
I am working on a fix.

Resolving patch:
diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
--- a/scapy/layers/inet6.py
+++ b/scapy/layers/inet6.py
@@ -414,6 +414,7 @@ class IPv6(_IPv6GuessPayload, Packet, IPTools):
                     DestIP6Field("dst", "::1") ]
 
     def route(self):
+        """Used to select the L2 address"""
         dst = self.dst
         if isinstance(dst,Gen):
             dst = next(iter(dst))
diff --git a/scapy/route6.py b/scapy/route6.py
--- a/scapy/route6.py
+++ b/scapy/route6.py
@@ -197,6 +197,10 @@ def route(self, dst, dev=None):
             dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0]
             # TODO : Check if name resolution went well
 
+        # Use the default interface while dealing with link-local addresses
+        if dev is None and (in6_islladdr(dst) or in6_ismlladdr(dst)):
+            dev = conf.iface
+
         # Deal with dev-specific request for cache search
         k = dst
         if dev is not None:


Test patch:
diff --git a/test/linux.uts b/test/linux.uts
--- a/test/linux.uts
+++ b/test/linux.uts
@@ -172,3 +172,24 @@ assert(got_lo_device)
 
 exit_status = os.system("ip link del dev scapy_lo")
 assert(exit_status == 0)
+
+= IPv6 link-local address selection
+
+from mock import patch
+bck_conf_route6_routes = conf.route6.routes
+conf.route6.routes =  [('fe80::', 64, '::', 'scapy0', ['fe80::e039:91ff:fe79:1910'], 256)]
+bck_conf_iface = conf.iface
+conf.iface = "scapy0"
+with patch("scapy.layers.l2.get_if_hwaddr") as mgih:
+    mgih.return_value = 'e2:39:91:79:19:10'
+    p = Ether()/IPv6(dst="ff02::1")/ICMPv6NIQueryName(data="ff02::1")
+    print(p.sprintf("%Ether.src% > %Ether.dst%\n%IPv6.src% > %IPv6.dst%"))
+    ip6_ll_address = 'fe80::e039:91ff:fe79:1910'
+    print(p[IPv6].src, ip6_ll_address)
+    assert p[IPv6].src == ip6_ll_address
+    mac_address = 'e2:39:91:79:19:10'
+    print(p[Ether].src, mac_address)
+    assert p[Ether].src == mac_address
+
+conf.route6.routes = bck_conf_route6_routes
+conf.iface = bck_conf_iface
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -3435,6 +3435,8 @@ raw(defragment6(l)) == b'`\x00\x00\x00\x0f\xb4\x06@\x00\x00\x00\x00\x00\x00\x00\
 + Test Route6 class
 
 = Route6 - Route6 flushing
+conf_iface = conf.iface
+conf.iface = "eth0"
 conf.route6.routes=[
 (                               '::1', 128,                       '::',   'lo', ['::1'], 1), 
 (          'fe80::20f:1fff:feca:4650', 128,                       '::',   'lo', ['::1'], 1)]
@@ -3452,6 +3454,7 @@ conf.route6.routes=[
 (                                '::',   0, 'fe80::20f:34ff:fe8a:8aa1', 'eth0', ['2001:db8:0:4444:20f:1fff:feca:4650', '2002:db8:0:4444:20f:1fff:feca:4650'], 1)
 ]
 conf.route6.route("2002::1") == ('eth0', '2002:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') and conf.route6.route("2001::1") == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') and conf.route6.route("fe80::20f:1fff:feab:4870") == ('eth0', 'fe80::20f:1fff:feca:4650', '::') and conf.route6.route("::1") == ('lo', '::1', '::') and conf.route6.route("::") == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1') and conf.route6.route('ff00::') == ('eth0', '2001:db8:0:4444:20f:1fff:feca:4650', 'fe80::20f:34ff:fe8a:8aa1')
+conf.iface = conf_iface
 conf.route6.resync()
 if not len(conf.route6.routes):
     # IPv6 seems disabled. Force a route to ::1