Repository: secdev/scapy

Issue:
Wrong source MAC address in IPv6 NS packet
### Brief description

Wrong source MAC is used when creating an ICMP6, neighbor solicitation packet.

### Scapy version

2.5.0

### Python version

3.10.6

### Operating system

Centos 7

### Additional environment information

```
[centos@vm1-1 ~]$ ip a
...
2: ens11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 54:52:00:00:01:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.101/24 brd 192.168.100.255 scope global ens11
       valid_lft forever preferred_lft forever
    inet6 2002:c0a8:6400::65/128 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::5652:ff:fe00:101/64 scope link 
       valid_lft forever preferred_lft forever
3: ens12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 54:52:00:01:01:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.150.101/24 brd 192.168.150.255 scope global ens12
       valid_lft forever preferred_lft forever
    inet6 2002:c0a8:9600::65/128 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::5652:ff:fe01:101/64 scope link 
       valid_lft forever preferred_lft forever
4: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:c0:97:01 brd ff:ff:ff:ff:ff:ff
    inet 172.16.111.240/24 brd 172.16.111.255 scope global dynamic ens3
       valid_lft 35809sec preferred_lft 35809sec
    inet6 fe80::5054:ff:fec0:9701/64 scope link 
       valid_lft forever preferred_lft forever
```

```
[centos@vm1-1 ~]$ ip -6 route
2002:c0a8:6400::65 dev ens11 proto kernel metric 256 pref medium
2002:c0a8:6400::/64 dev ens11 proto kernel metric 256 pref medium
2002:c0a8:9600::65 dev ens12 proto kernel metric 256 pref medium
2002:c0a8:9600::/64 dev ens12 proto kernel metric 256 pref medium
fe80::/64 dev ens3 proto kernel metric 256 pref medium
fe80::/64 dev ens11 proto kernel metric 256 pref medium
fe80::/64 dev ens12 proto kernel metric 256 pref medium
default via fe80::5e00:100 dev ens11 proto ra metric 1024 expires 8990sec hoplimit 64 pref medium
default via fe80::5e00:100 dev ens12 proto ra metric 1024 expires 8990sec hoplimit 64 pref medium
```

### How to reproduce

```
from scapy.all import *

pkt = IPv6(version=6, src='2002:c0a8:6400::65', dst='2002:c0a8:6400::68')/UDP(sport=6000, dport=5000)/Raw()
send(pkt)
```




### Actual result

The ICMP6 pkt is sent on IF ens11 with MAC of ens3 interface 52:54:00:c0:97:01:
```
[centos@vm1-1 ~]$ tcpdump -i ens11 -ennxx
09:03:39.738775 52:54:00:c0:97:01 > 33:33:ff:00:00:68, ethertype IPv6 (0x86dd), length 86: 2002:c0a8:6400::65 > ff02::1:ff00:68: ICMP6, neighbor solicitation, who has 2002:c0a8:6400::68, length 32
	0x0000:  3333 ff00 0068 5254 00c0 9701 86dd 6000
	0x0010:  0000 0020 3aff 2002 c0a8 6400 0000 0000
	0x0020:  0000 0000 0065 ff02 0000 0000 0000 0000
	0x0030:  0001 ff00 0068 8700 99c0 0000 0000 2002
	0x0040:  c0a8 6400 0000 0000 0000 0000 0068 0101
	0x0050:  5452 0000 0101
```

### Expected result

Source MAC should be MAC of ens11 interface.

Source MAC when sending the packet is obtained by i2h() in SourceMACField, which tries to resolve route for dst addr in the packet created by neighsol(addr, src, iface, timeout=1, chainCC=0):
```
(Pdb) !p.show()
###[ Ethernet ]### 
  dst       = 33:33:ff:00:00:68
  src       = 52:54:00:c0:97:01
  type      = IPv6
###[ IPv6 ]### 
     version   = 6
     tc        = 0
     fl        = 0
     plen      = None
     nh        = ICMPv6
     hlim      = 255
     src       = 2002:c0a8:6400::65 [6to4 GW: 192.168.100.0]
     dst       = ff02::1:ff00:68
###[ ICMPv6 Neighbor Discovery - Neighbor Solicitation ]### 
        type      = Neighbor Solicitation
        code      = 0
        cksum     = None
        res       = 0
        tgt       = 2002:c0a8:6400::68 [6to4 GW: 192.168.100.0]
###[ ICMPv6 Neighbor Discovery Option - Source Link-Layer Address ]### 
           type      = 1
           len       = 1
           lladdr    = 54:52:00:00:01:01


p.route()
('ens3', 'fe80::5054:ff:fec0:9701', '::'):
```
Using the Multicast address to query the routing table will not provide ens11, i.e. the interface attached to the LAN where the NS packet should be sent.
 
Is there any reason why src mac is not added directly in neighsol()?
Src MAC is known and added to the ICMP6 payload with `ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr(iface))`

```
 79  	@conf.commands.register
 80  	def neighsol(addr, src, iface, timeout=1, chainCC=0):
 81  	    """Sends and receive an ICMPv6 Neighbor Solicitation message
...
 91  	    """
 92  	
 93  	    nsma = in6_getnsma(inet_pton(socket.AF_INET6, addr))
 94  	    d = inet_ntop(socket.AF_INET6, nsma)
 95  	    dm = in6_getnsmac(nsma)
 96  	    p = Ether(dst=dm) / IPv6(dst=d, src=src, hlim=255)
 97  	    p /= ICMPv6ND_NS(tgt=addr)
 98  	    p /= ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr(iface))
 99       res = srp1(p, type=ETH_P_IPV6, iface=iface, timeout=1, verbose=0,
100  	               chainCC=chainCC)
101  	
102      return res
```


### Related resources

_No response_


Discussion:
What is the content of `conf.iface`? It is used to build packets.
```
>>> conf.iface
<NetworkInterface ens3 [UP+BROADCAST+RUNNING+SLAVE]>
```
Rather than during packet build, conf.iface is used here:
https://github.com/secdev/scapy/blob/9473f77d8b548c8e478e52838bdd4c12f5d4f4ff/scapy/route6.py#L255
My guess is that `en3` is the associated with the default IPv4 gateway. With your setup, you need to help Scapy and set `conf.iface` to ens11.
What I do not understand is why NS needs to rely on default interface in:
https://github.com/secdev/scapy/blob/7f89ce51c09417c2aa9d78d3e5c75091cacd4bff/scapy/layers/inet6.py#L76

neighsol() knows the outgoing iface and it even knows its MAC (which, if I am not mistaken, is mandatory in multicast solicitations).

Is there any reason why the packet in neighsol() cannot be constructed directly with src MAC inside?
I might be missing some use case for this function.

Something like:
```
    ...
    dm = in6_getnsmac(nsma)
    sm = get_if_hwaddr(iface)
    p = Ether(dst=dm, src=sm) / ...

```



Resolving patch:
diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
--- a/scapy/layers/inet6.py
+++ b/scapy/layers/inet6.py
@@ -79,7 +79,9 @@ def neighsol(addr, src, iface, timeout=1, chainCC=0):
     This function sends an ICMPv6 Neighbor Solicitation message
     to get the MAC address of the neighbor with specified IPv6 address address.
 
-    'src' address is used as source of the message. Message is sent on iface.
+    'src' address is used as the source IPv6 address of the message. Message
+    is sent on 'iface'. The source MAC address is retrieved accordingly.
+
     By default, timeout waiting for an answer is 1 second.
 
     If no answer is gathered, None is returned. Else, the answer is
@@ -89,9 +91,10 @@ def neighsol(addr, src, iface, timeout=1, chainCC=0):
     nsma = in6_getnsma(inet_pton(socket.AF_INET6, addr))
     d = inet_ntop(socket.AF_INET6, nsma)
     dm = in6_getnsmac(nsma)
-    p = Ether(dst=dm) / IPv6(dst=d, src=src, hlim=255)
+    sm = get_if_hwaddr(iface)
+    p = Ether(dst=dm, src=sm) / IPv6(dst=d, src=src, hlim=255)
     p /= ICMPv6ND_NS(tgt=addr)
-    p /= ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr(iface))
+    p /= ICMPv6NDOptSrcLLAddr(lladdr=sm)
     res = srp1(p, type=ETH_P_IPV6, iface=iface, timeout=1, verbose=0,
                chainCC=chainCC)
 


Test patch: