1

Problem: ip rule built to route L4 traffic out a specific interface are not respected when packets are generated with different source address.

Overview I want to generate packets with a different source address than the host's address's. To accomplish this I am using python's package Scapy. Note: my goal is to send to send DNS traffic, however I was not able to find a simple solution that let me spoof the source address in DNS requests, so I am just generating a UDP packet with src and dst address at port 53, believe this still works as I am only testing L3 and L4, not the actual DNS protocol at the moment. Here is my script

#!/usr/bin/python3

# The following is designed to generate a packet with a different source address

import sys 
from scapy.all import *

def main():
    S = "10.0.26.122" # spoofed source IP address
    D = "10.0.26.123" # destination IP address
    SP = 53 # source port
    DP = 53 # destination port
    payload = "This is a fake message" # packet payload

    spoofed_packet = IP(src=S, dst=D) / UDP(sport=53, dport=53) / payload
    send(spoofed_packet)

#Entry point
main()

Before running the script, here is what my route table looks like:

# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.104.8.1      0.0.0.0         UG    101    0        0 ens192
10.0.21.0       0.0.0.0         255.255.255.0   U     104    0        0 ens256
10.0.26.0       0.0.0.0         255.255.255.0   U     0      0        0 ens224
10.0.27.0       0.0.0.0         255.255.255.0   U     102    0        0 ens193
10.0.28.0       10.0.29.1       255.255.255.0   UG    100    0        0 ens161
10.0.29.0       0.0.0.0         255.255.255.0   U     100    0        0 ens161
10.104.8.0      0.0.0.0         255.255.255.0   U     101    0        0 ens192
10.212.134.0    10.104.8.1      255.255.255.0   UG    101    0        0 ens192
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

Here are the ip interfaces

# ip -br a
lo               UNKNOWN        127.0.0.1/8
ens161           UP             10.0.29.122/24
ens192           UP             10.104.8.122/24
ens193           UP             10.0.27.122/24
ens224           UP             10.0.26.122/24
ens256           UP             10.0.21.122/24
virbr0           DOWN           192.168.122.1/24
virbr0-nic       DOWN
ip_vti0@NONE     DOWN

When I run the script with ./packet-gen.py "10.0.26.122" "10.0.26.123" it works. This is because I have not yet built my ip rule / separate routing table. I perform a tcpdump at the host (10.0.26.122) and on the far end host (10.0.26.123), and I see the UDP packet being sent. I also tested with dig www.google.com @10.0.26.123 and see an actual DNS request being performed and get a response.

Now the problem. I want to remove the route entry in the main table, then only route based on the port number. To do this I run the following to first remove the route entry to 10.0.26.0/24.

# ip route del 10.0.26.0/24 dev ens224
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.104.8.1      0.0.0.0         UG    101    0        0 ens192
10.0.21.0       0.0.0.0         255.255.255.0   U     104    0        0 ens256
10.0.27.0       0.0.0.0         255.255.255.0   U     102    0        0 ens193
10.0.28.0       10.0.29.1       255.255.255.0   UG    100    0        0 ens161
10.0.29.0       0.0.0.0         255.255.255.0   U     100    0        0 ens161
10.104.8.0      0.0.0.0         255.255.255.0   U     101    0        0 ens192
10.212.134.0    10.104.8.1      255.255.255.0   UG    101    0        0 ens192
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

The entry is removed. If I run my script again it does not work. The dig request also fails. This is expected as there is no L3 route in the main kernel routing table.

To route on L4 I first created a new ip route table to send all traffic via ens224:

# ip route add table 53 0.0.0.0/0 dev ens224

Then I create an ip rule to capture any traffic using port 53, and send out my custom table 53.

# ip rule add ipproto udp dport 53 lookup 53

I also created a special sysctl rule for rp_filter too loosen strict reverse path forwarding rules

# sysctl -w "net.ipv4.conf.ens224.rp_filter=2"

To check my work I see the following:

# ip route list table 53
default dev ens224 scope link
# ip rule list
0:      from all lookup local
32765:  from all ipproto udp dport 53 lookup 53
32766:  from all lookup main
32767:  from all lookup default
# ip route get 10.0.26.123 ipproto udp dport 53
10.0.26.123 dev ens224 table 53 src 10.0.26.122 uid 0
    cache
# ip route get 10.0.26.123
10.0.26.123 via 10.104.8.1 dev ens192 src 10.104.8.122 uid 0
    cache

The last command shows that by default if communication is not dns, use the default route.

To test this I first try to ping 10.0.26.123. It fails which is expected. Now I try to perform a dig request dig www.google.com @10.0.26.123, and it works. The dig request hits the ip rule before going to the main table and is routed appropriately. I see the traffic reach the service with tcpdump (10.0.26.123), and coming from my host (10.0.26.122).

Now I try running my scapy script again, and nothing. Even with the same source address as the host, nothing in tcpdump on my host or the server. I tried changing the source address, no change, nothing. If I add back the main L3 route for 10.0.26.0/24 in the main table, the scapy script works again. What am I missing here? Why wont my generate traffic respect the ip rule sets I created?

1
  • I have no definitive answer, but strace dig google.com gives me something strange : all sockets opened are closed immediately afterward with no use ! You should compare dig and your program behaviour with such a tool. Oct 28, 2021 at 18:50

1 Answer 1

0

Scapy has does not use the OS routing mechanism but rather uses its own routing table (conf.route object), synchronized from the OS routing table when Scapy starts (or whenever conf.route.resync() method is called).

Scapy's routing mechanism does not support source routing, which explains the behavior you describe.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .