I'm implementing a simple userspace networking stack for self-learning purposes. I'm writing it in Python, running it in Linux (Ubuntu 16.04.2 LTS). I'm using a Python TAP device to receive Layer 2 frames (e.g. Ethernet). From there, I extract the headers and process frames according to header fields.
Problem: The TAP device receives several types of frames, however not ICMP packets (e.g. ICMP echo requests). I would like it to receive ICMP echo requests too.
Details: To test the behavior of the stack I'm running ping 10.0.0.4
on the same machine. My Ubuntu environment is running on a VM, and so I've also tried running ping 10.0.0.4
from the host machine (after adding the appropriate entry to the routing table). I always get ICMP echo replies, even though the TAP device sees none of the echo requests:
PING 10.0.0.4 (10.0.0.4): 56 data bytes
64 bytes from 10.0.0.4: icmp_seq=0 ttl=64 time=0.451 ms
64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=0.530 ms
Here's the packet handling code (simplified for the purposes of this question):
from pytun import TunTapDevice, IFF_TAP, IFF_NO_PI
tap_dev = TunTapDevice(flags = (IFF_TAP | IFF_NO_PI))
tap_dev.persist(True)
tap_dev.addr = '10.0.0.4'
tap_dev.netmask = '255.255.255.0'
tap_dev.up()
while (1):
frame = tap_dev.read(1500)
# extract the Ethernet header from the raw frame
# (assume this is working correctly)
eth_frame_hdr = unpack_eth_hdr(frame)
# check if it is an IPv4 packet
if eth_frame_hdr.type == 0x0800:
ipv4_hdr = unpack_ipv4_hdr(frame)
# check if an icmp packet
if ipv4_hdr.proto == 0x01:
process_icmp(frame)
My diagnosis: I think what's happening is that the Linux kernel is handling the ICMP echo requests directly, and either (1) doesn't even put a packet 'on the wire' or (2) doesn't pass the ICMP packets to userspace.
(Failed) resolution attempts: I've tried several things to get ICMP packets on the TAP device, none of them resulted in the TAP device receiving the ICMP echo requests:
Ignoring ICMP echo handling:
echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all
Add an iptables rule to drop ICMP echo requests:
sudo iptables -I INPUT -p icmp --icmp-type echo-request -j DROP
Add an iptables rule which 'jumps' to the
QUEUE
target (idea was to pass ICMP packets to userspace):sudo iptables -I INPUT -p icmp --icmp-type echo-request -j QUEUE
Use a raw socket as a special case to handle ICMP packets:
from socket import * icmp_listener_sock = socket(AF_PACKET, SOCK_RAW, IPPROTO_ICMP) icmp_listener_sock.bind((tap_dev.name, IPPROTO_ICMP)) (icmp_ipv4_dgram, snd_addr) = icmp_listener_sock.recvfrom(2048) process_icmp(icmp_ipv4_dgram)
Can you point me to the right way to have the Python TAP device receive the ICMP echo requests?