DEV Community

Melissa
Melissa

Posted on

Why Scapy Instead of Ping? - Layer Locking

Reason for layer locking: More accurate result.
Operating systems are generic, Scapy frames are specific, so Scapy frames give troubleshooting accuracy.

Environment: VyOS router in Oracle VM VirtualBox.

LAYER 2 - Data Link Test

Steps:

  1. Put listener on eth0 on VyOS
  2. Run Scapy frame

Scapy frame to send (L2 broadcast - locks to L2):

Layer 2 Lock Test - Pure L2 - VyOS eth0

from scapy.all import *
p = Ether(dst="ff:ff:ff:ff:ff:ff")
hexdump(p)

0000 FF FF FF FF FF FF 00 00 00 00 00 00 90 00

Breakdown:
FF FF FF FF FF FF = dst - broadcast all MACs
00 00 00 00 00 00 = src - zero - Scapy default when no MAC bound
90 00 = type - Loopback - blank L2 frame

srp(p, iface="eth0", timeout=3)

Begin emission:
Finished sending 1 packets.
Received 0 packets, got 0 answers, remaining 1 packets

Result: L2 TX works - frame was put on wire, layer-locked, no IP.

LAYER 3 - Network Layer Test

L3 Test - Hardcoded MAC + Static IP - VyOS eth0

Goal: Exclude DHCP and ARP to prove L3 TX.

STEP 1 - Find MAC (Linux command)
$ cat /sys/class/net/eth0/address
08:00:27:bf:9a:64

STEP 2 - Put static IP (VyOS config)
configure
set interfaces ethernet eth0 address 192.168.56.10/24
commit
save
exit

STEP 3 - Put listener in background (same terminal)
$ sudo tcpdump -i eth0 -e -n icmp &
[1] 3047
tcpdump: listening on eth0, link-type EN10MB

STEP 4 - Scapy - hardcode MAC and IP to kill ARP
$ sudo python3

from scapy.all import *
my_mac = "08:00:27:bf:9a:64"
my_ip = "192.168.56.10"
p = Ether(dst=my_mac, src=my_mac) / IP(dst=my_ip, src=my_ip) / ICMP()
hexdump(p)

0000 08 00 27 BF 9A 64 08 00 27 BF 9A 64 08 00 45 00..'.d..'.d..E.
0010 00 1C 00 01 00 00 40 01 89 7B C0 A8 38 0A C0 A8......@..{..8...
0020 38 0A 08 00 F7 FF 00 00 00 00 8.........

Breakdown:
08 00 27 BF 9A 64 = dst MAC = your eth0 (hardcoded)
08 00 27 BF 9A 64 = src MAC = your eth0 (hardcoded)
08 00 = Type = IPv4
45 00 = IP version / header
C0 A8 38 0A = hex for 192.168.56.10 src
C0 A8 38 0A = hex for 192.168.56.10 dst
08 00 = ICMP Echo Request

srp(p, iface="eth0", timeout=3)

Begin emission:
Finished sending 1 packets.
13:38:43.976853 08:00:27:bf:9a:64 > 08:00:27:bf:9a:64, ethertype IPv4 (0x0800), length 42: 192.168.56.10 > 192.168.56.10: ICMP echo request
.
Received 0 packets, got 0 answers, remaining 1 packets

RESULT:
L3 TX UP - Confirmed.
tcpdump saw the frame on eth0 (line above), proving Layer 2 and Layer 3 headers were built correctly and transmitted with no DHCP/ARP.

Received 0 is EXPECTED here because we pinged ourselves (src=dst). Linux routes self-pings via lo, not eth0, so no Echo Reply is generated on eth0. This is not L3 down.

For full L3 up with reply, ping gateway 192.168.56.1 with its MAC, and srp will show "got 1 answers".

LAYER 4 - Transport Layer Test

L4 Test - UDP Port 50001

Why put a listener on port 50001?

We use 50001 because it is in the clean dynamic range 49152-65535. Nothing else on VyOS uses it, so there is no L5-L7 app interfering. If something arrives there, we know it is pure L4 Transport being tested. We also need a listener (nc -l -u -p 50001) otherwise the OS would drop the UDP and we would see nothing.

The listener was run in one terminal as:
sudo nc -l -u -p 50001 > /tmp/udp.txt &
The frame we sent:

L4 must be carried inside L3, so we still need an IP. We used loopback because we are sending from VyOS to itself:
from scapy.all import *
udp_clean = IP(dst="127.0.0.1")/UDP(dport=50001)/Raw(load="L4-TEST")
send(udp_clean)
Breakdown:

  • IP(dst="127.0.0.1") = L3 part - VyOS lo loopback
  • /UDP(dport=50001) = L4 part - clean port
  • /Raw(load="L4-TEST") = payload to prove delivery

Sent with:
sudo python3 -c "from scapy.all import *; send(IP(dst='127.0.0.1')/UDP(dport=50001)/Raw(load='L4-TEST'), verbose=1)"
The result:
cat /tmp/udp.txt
L4-TEST
And tcpdump -i lo -n port 50001 showed the packet on lo.

Conclusion: Payload L4-TEST was delivered to clean port 50001. No other service uses this port. This proves L4 UDP is UP.

Top comments (0)