Repository: secdev/scapy

Issue:
Scapy may use unexpected sender MAC address for ARP request payload.
#### Brief description

On a host with more than one interface, Scapy may use unexpected sender MAC address in ARP request payload.

ARP protocol could be not working as expected.

#### Environment

- Scapy version: 2.4.0.dev310
- Python version: 2.7.15rc1
- Operating System: Ubuntu 18.04 bionic amd64

![test-net-arp-issue](https://user-images.githubusercontent.com/18270022/42639388-2c4cb094-85f0-11e8-92a8-79642f057203.png)


#### How to reproduce

Hi, I went into this issue while sending packets from one host to another on a secondary interface (eth1, see attached picture of the setup).
Interface eth0 is used to connect to hosts, and each eth0 belongs to two different, unconnected networks (i.e. A and B have no knowledge about each other's eth0).

I wanted to send a packet from host A to host B using eth1, but as my script can run on several different network architectures (not only on a flat network for instance) I thought that I could let Scapy find the ethernet destination of my packet for me (i.e. let Scapy find the next hop MAC address, in our case the MAC address of the eth1 interface of host B).

So here is what I'm doing:
```
from scapy.all import *
interface = 'eth1'
# Packet 'p' below is incomplete, but it doesn't matter
p = Ether(src=get_if_hwaddr(interface)) / IP(dst='192.168.1.1', src='192.168.1.3') / UDP(dport=53, chksum=0)
sendp(p, iface=interface, verbose=False)
```

#### Actual result

When Scapy sends packet p with sendp, it doesn't know how to reach 192.168.1.1 (ARP table has no entry for 192.168.1.1), so Scapy works to build an ARP request and sends it.
The issue is that on my network setup Scapy fills the 'sender MAC address' in the ARP request with the MAC address of **eth0** interface instead of eth1 (as specified in sendp function call), and also not taking into account that subnet 192.168.1.0/24 is configured on eth1.

This results in host A not taking into account the ARP reply sent by host B, because host B sends its reply to the MAC address he got from the 'sender MAC address' of the ARP request, which is indeed not the one of eth1 on host A.

This is observed with Scapy 2.4.0.dev310.

#### Expected result

Running some more tests I got different results using Scapy 2.4.0: with this version, the "sender MAC address" field in ARP request payload receives the expected MAC address (eth1 MAC address).

#### Related resources

I worked with @guedou on this issue and he eventually went through a git bisect, which gave the following result:

```
# git bisect good
435918e3d8e95b7374fadd8c6d962ee9b5032fcb is the first bad commit
commit 435918e3d8e95b7374fadd8c6d962ee9b5032fcb
Author: Pierre LALET <pierre.lalet@cea.fr>
Date:   Wed May 16 17:35:28 2018 +0200

    Make ARP() great again
    
    This uses MultipleTypeField() for {hw,p}{dst,src} fields, to achieve
    two goals:
    
      - Make ARP() usable with other {hw,p}types than Ether and IP.
    
      - Keep ARP() as simple as possible.
    
    Notes:
    
      - raw(ARP()) returns the same result as before.
    
      - ARP(pytpe="IPv4") works and raw(ARP(pytpe="IPv4")) == raw(ARP())
    
      - ARP(ptype="IPv6") now works, plen is then set to 16 and psrc &
        pdst fields are IPv6 addresses:
    
          ARP(raw(ARP(ptype="IPv6"))).pdst == "::"
```


@guedou worked on a potential fix, which follows:
```
diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py
index dd0352ff..da98474b 100644
--- a/scapy/layers/l2.py
+++ b/scapy/layers/l2.py
@@ -30,7 +30,7 @@ from scapy.fields import BCDFloatField, BitField, ByteField, \
     SourceIP6Field, SourceIPField, StrFixedLenField, StrLenField, \
     X3BytesField, XByteField, XIntField, XShortEnumField, XShortField
 from scapy.modules.six import viewitems
-from scapy.packet import bind_layers, Packet
+from scapy.packet import bind_layers, Packet, NoPayload
 from scapy.plist import PacketList, SndRcvList
 from scapy.sendrecv import sendp, srp, srp1
 from scapy.utils import checksum, hexdump, hexstr, inet_ntoa, inet_aton, \
@@ -125,8 +125,15 @@ class SourceMACField(MACField):
 
     def __init__(self, name, getif=None):
         MACField.__init__(self, name, None)
-        self.getif = ((lambda pkt: pkt.payload.route()[0])
-                      if getif is None else getif)
+        def _route(pkt):
+            if getif is None:
+               if type(pkt.payload) is NoPayload:
+                   return pkt.route()[0]
+               else:
+                   return pkt.payload.route()[0]
+            else:
+               return getif
+        self.getif = _route
 
     def i2h(self, pkt, x):
         if x is None:
```


The issue can easily be tested using this code (on host A for instance):
```
from scapy.all import *
print getmacbyip("192.168.1.1")
```
The output should be the MAC address of the expected interface.


Hope this can help fix the current code.
Let me know if you need more information.



Discussion:
Hi,

Sorry I totally missed this issue, even with the assignment :-/

Your patch looks good to me, but I think it could be fixed differently (and that fix could address similar bugs). What would you think of this:

```patch
--- a/scapy/packet.py
+++ b/scapy/packet.py
@@ -1094,7 +1094,7 @@ values.
         return self.haslayer(cls)
 
     def route(self):
-        return (None, None, None)
+        return self.payload.route()
 
     def fragment(self, *args, **kargs):
         return self.payload.fragment(*args, **kargs)
@@ -1485,6 +1485,10 @@ class NoPayload(Packet):
     def command(self):
         return ""
 
+    def route(self):
+        return (None, None, None)
+
+
 ####################
 #  packet classes  #
 ####################
```

Resolving patch:
diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py
--- a/scapy/layers/l2.py
+++ b/scapy/layers/l2.py
@@ -128,8 +128,7 @@ class SourceMACField(MACField):
 
     def __init__(self, name, getif=None):
         MACField.__init__(self, name, None)
-        self.getif = ((lambda pkt: pkt.payload.route()[0])
-                      if getif is None else getif)
+        self.getif = (lambda pkt: pkt.route()[0]) if getif is None else getif
 
     def i2h(self, pkt, x):
         if x is None:
diff --git a/scapy/packet.py b/scapy/packet.py
--- a/scapy/packet.py
+++ b/scapy/packet.py
@@ -1094,7 +1094,7 @@ def __contains__(self, cls):
         return self.haslayer(cls)
 
     def route(self):
-        return (None, None, None)
+        return self.payload.route()
 
     def fragment(self, *args, **kargs):
         return self.payload.fragment(*args, **kargs)
@@ -1485,6 +1485,10 @@ def lastlayer(self, layer):
     def command(self):
         return ""
 
+    def route(self):
+        return (None, None, None)
+
+
 ####################
 #  packet classes  #
 ####################


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -219,6 +219,10 @@ if WINDOWS:
 
 True
 
+= Packet.route()
+assert (Ether() / ARP()).route()[0] is not None
+assert (Ether() / ARP()).payload.route()[0] is not None
+
 
 ############
 ############