Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPv6 source address selection regression #4312

Open
gromit1811 opened this issue Mar 8, 2024 · 4 comments · May be fixed by #4321
Open

IPv6 source address selection regression #4312

gromit1811 opened this issue Mar 8, 2024 · 4 comments · May be fixed by #4321

Comments

@gromit1811
Copy link

Brief description

I'm trying to generate an IPv6 MLD query using scapy. With scapy 2.4.4 (in Debian 11) it works as expected. With 2.5.0 (Debian 12) and current git a05013c source address selection is weird. If I'm not specifying IPv6 and MAC source addresses explicitly, they're both all 0s. With scapy 2.4.4, I got the IPv6 link-local address of the sending interface (and the associated MAC address) as expected.

In addition, I'm getting 3 of these warnings:

WARNING: No route found for IPv6 destination ff02::1 (no default route?)

True, there is no default route (and also no global IPv6 address), but I don't see why I'd need one - MLD query is a link-local multicast packet, so the source address should be link-local as well.

BTW, this might be related to #4304 (I'm seeing the same warning messages), but in this case here, functionality is impacted, so it's not just a warning that can be safely ignored.

Scapy version

2.5.0.dev300

Python version

3.11.2

Operating system

Debian bookworm 12, kernel 6.6.13-1~bpo12+1

Additional environment information

Tested in a Linux network namespace with only a veth pair, completely isolated from the rest of the network stack. The environment I noticed this originally was also in a netns, but with a few more interfaces and a bridge.

How to reproduce

Use this script to create a "testns" namespace, set up a veth pair, run scapy and monitor packets with tcpdump:

#!/bin/bash

set -e

# Run inside netns so that it's properly isolated
if ! [ "$IN_NETNS" ]; then
  ip netns del testns 2> /dev/null || true
  ip netns add testns
  ip netns exec testns ip link set lo up
  IN_NETNS=1 exec ip netns exec testns "$0"
fi

ip link add veth0a type veth peer name veth0b
ip link set veth0a up
ip link set veth0b up

ip -6 addr
ip -6 route

# Wait some time until ND/RD after link up have been sent to avoid
# irrelevant packets in tcpdump output
sleep 20

tcpdump -nel -i veth0a &
sleep 1

scapy <<"EOF"
frame = Ether() / IPv6(dst="ff02::1", hlim=1) / IPv6ExtHdrHopByHop(options=RouterAlert()) / ICMPv6MLQuery2()
sendp(frame, iface="veth0a")
EOF

sleep 1
kill %%

Actual result

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth0b@veth0a: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::98d4:a0ff:fe6d:83ee/64 scope link tentative 
       valid_lft forever preferred_lft forever
3: veth0a@veth0b: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::dc66:dff:fe95:9e74/64 scope link tentative 
       valid_lft forever preferred_lft forever
fe80::/64 dev veth0b proto kernel metric 256 pref medium
fe80::/64 dev veth0a proto kernel metric 256 pref medium
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth0a, link-type EN10MB (Ethernet), snapshot length 262144 bytes
                                      
                     aSPY//YASa       
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    | Version 2.5.0
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   | https://github.com/secdev/scapy
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | Craft packets like it is your last
       scccccp///pSP///p          p//Y   | day on earth.
      sY/////////y  caa           S//P   |                      -- Lao-Tze
       cayCyayP//Ya              pY/Ya   |
        sY/PsY////YCc          aC//Yp 
         sc  sccaCY//PCypaapyCP//YSs  
                  spCPY//////YPSps    
                       ccaacs         
                                       using IPython 8.5.0
>>> >>> WARNING: No route found for IPv6 destination ff02::1 (no default route?)
WARNING: No route found for IPv6 destination ff02::1 (no default route?)
WARNING: more No route found for IPv6 destination ff02::1 (no default route?)
.
Sent 1 packets.
>>> 10:00:51.203947 00:00:00:00:00:00 > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 90: :: > ff02::1: HBH ICMP6, multicast listener query v2 [gaddr ::], length 28

1 packet captured
1 packet received by filter
0 packets dropped by kernel

Note the bogus source addresses in the tcpdump output.

Expected result

This is with the same script and scapy 2.4.4:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth0b@veth0a: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::e4f2:75ff:fe98:da6c/64 scope link tentative 
       valid_lft forever preferred_lft forever
3: veth0a@veth0b: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::4c5c:b6ff:fea9:c272/64 scope link tentative 
       valid_lft forever preferred_lft forever
fe80::/64 dev veth0b proto kernel metric 256 pref medium
fe80::/64 dev veth0a proto kernel metric 256 pref medium
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth0a, link-type EN10MB (Ethernet), snapshot length 262144 bytes
                                      
                     aSPY//YASa       
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.4
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   | https://github.com/secdev/scapy
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | We are in France, we say Skappee.
       scccccp///pSP///p          p//Y   | OK? Merci.
      sY/////////y  caa           S//P   |             -- Sebastien Chabal
       cayCyayP//Ya              pY/Ya   |
        sY/PsY////YCc          aC//Yp 
         sc  sccaCY//PCypaapyCP//YSs  
                  spCPY//////YPSps    
                       ccaacs         
                                       using IPython 7.20.0
>>> >>> .
Sent 1 packets.
>>> 11:08:55.127271 e6:f2:75:98:da:6c > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 90: fe80::e4f2:75ff:fe98:da6c > ff02::1: HBH ICMP6, multicast listener query v2 [gaddr ::], length 28

1 packet captured
1 packet received by filter
0 packets dropped by kernel

I can get a result like this also with scapy 2.5.0 if I explicitly specify source addresses in Ether() and IPv6(), but I don't think that should be necessary.

Related resources

Note that this is from a different run (scapy git a05013c again), so addresses are different:

(venv) root@debkvm:~# ip netns exec testns scapy
[...]
>>> conf.auto_crop_tables=False
>>> conf.ifaces
Source  Index  Name    MAC                IPv4       IPv6
sys     1      lo      00:00:00:00:00:00  127.0.0.1  ::1
sys     2      veth0b  5a:84:c1:3a:c7:ce
sys     3      veth0a  36:39:b4:53:45:2d
>>> conf.route6 
Destination                    Next Hop  Iface   Src candidates             Metr
ic
fe80::/64                      ::        veth0a  fe80::3439:b4ff:fe53:452d  256

::1/128                        ::        lo      ::1                        0

fe80::3439:b4ff:fe53:452d/128  ::        veth0a  fe80::3439:b4ff:fe53:452d  0

fe80::5884:c1ff:fe3a:c7ce/128  ::        veth0b  fe80::5884:c1ff:fe3a:c7ce  0
@guedou
Copy link
Member

guedou commented Mar 9, 2024

Thanks for your detailed message. Can your share the content to conf.iface ? Can you try to set it to veth0a if not alteady set to this interface?

@gromit1811
Copy link
Author

It's "lo" by default:

>>> conf.iface
<NetworkInterface lo [UP+LOOPBACK+RUNNING]>
>>>

If I set it to "veth0a", the warnings disappear and I get proper source addresses for the MLD packet.

@guedou
Copy link
Member

guedou commented Mar 11, 2024

Thanks for your answer! I was expecting this result.

Scapy uses conf.iface to build Packets, while the send() function iface argument is used to specify the output interface. With IPv6 and link-local addresses, it is mandatory to setup both conf.iface and iface to the same value to get the desired result, as Scapy cannot find which interface to use for link-local communications. Previous defaults were not satisfactory and we change the previous default behavior.

Currently, get_working_if() is the culprit and returns the loopback interface when no default IPv4 route is found.

We could add some IPv6 logic but it will be tricky for link-local address and only work fine with a single network interface when no IPv4 route exists:

  1. get the IPv6 interface with a default route, and return it
  2. get the first interface (excluding the loopback) with a fe80::/64 prefix and return it

@gromit1811
Copy link
Author

Thanks for your explanation! And thanks a lot for such a useful tool, BTW!

I wasn't aware of the different purposes of conf.iface and sendp(iface=XXX). It's clear that the sendp() argument is used for the actual transmission, but I assumed that this would also be used to build the packet, because with IPv6 link-local traffic, there's no way you can build a correct packet without taking the actual outgoing interface into account. I guess if I would have had a default route on an interface other than veth0a, scapy wouldn't have complained but would have silently built a packet with the wrong source address, right?

May I suggest to mention this important difference in the docs (in Usage/Sending Packets and possibly also in the API reference)? And if I understand correctly, send()is affected as well. Just gave it a try and there I even don't see a packet being transmitted without setting conf.iface properly. So I guess conf.iface should be much more prominent in the docs. 😉

BTW, I also gave scapy 2.4.4 another try: There it picked veth0b as its default interface, so it used the wrong source for sending on veth0a as well. But it didn't look as wrong an "all 0s" address, so the IP stack accepted it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants