使用 Python TAP 设备嗅探 ICMP 数据包(例如 ping 回显请求)
Sniff ICMP packets (e.g. ping echo requests) using Python TAP device
我正在为 self-learning 目的实现一个简单的用户空间网络堆栈。我在 Python 中写它,运行 在 Linux 中写它(Ubuntu 16.04.2 LTS)。我正在使用 a Python TAP device 接收第 2 层帧(例如以太网)。从那里,我提取 headers 并根据 header 字段处理帧。
问题: TAP 设备接收多种类型的帧,但不接收 ICMP 数据包(例如 ICMP 回显请求)。我希望它也能接收 ICMP 回显请求。
详情:为了测试堆栈的行为,我运行ping 10.0.0.4
在同一台机器上.我的 Ubuntu 环境是 运行 在虚拟机上,所以我 也 尝试了 运行 ping 10.0.0.4
主机(在将适当的条目添加到路由table之后)。我总是收到 ICMP 回显回复,即使 TAP 设备看到 none 个回显请求:
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
这是数据包处理代码(针对这个问题进行了简化):
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)
我的诊断: 我认为正在发生的事情是 Linux 内核直接处理 ICMP 回显请求,并且 (1) 甚至没有放置数据包 'on the wire' 或 (2) 不将 ICMP 数据包传递到用户空间。
(失败)解析尝试: 我已经尝试了几种方法在 TAP 设备上获取 ICMP 数据包,其中 none 导致 TAP 设备接收到ICMP 回应请求:
忽略 ICMP 回显处理:
echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all
添加 iptables 规则以丢弃 ICMP 回显请求:
sudo iptables -I INPUT -p icmp --icmp-type echo-request -j DROP
添加一个 iptables 规则 'jumps' 到 QUEUE
目标(想法是将 ICMP 数据包传递到用户空间):
sudo iptables -I INPUT -p icmp --icmp-type echo-request -j QUEUE
使用原始套接字作为处理 ICMP 数据包的特例:
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)
你能告诉我让 Python TAP 设备接收 ICMP 回显请求的正确方法吗?
我查看了解决方案尝试 4,并通过在创建原始套接字并将套接字绑定到地址 (<ip-address>, 0)
.[=25= 时将 AF_PACKET
更改为 AF_INET
使其工作]
from socket import *
icmp_listener_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
icmp_listener_sock.bind((tap_dev.ip_addr, 0))
(icmp_ipv4_dgram, snd_addr) = icmp_listener_sock.recvfrom(2048)
process_icmp(icmp_ipv4_dgram)
请注意,这是一种解决方法,它没有回答有关如何使用 Python TAP 设备获取 ICMP 数据包的问题。
编辑(和明确的答案):
我尝试了一种不同的方法,原始 post 中没有列出这种方法。我没有使用 python-pytun
包,而是直接打开了 Linux 的 TUN/TAP 设备,使用 Python-like open()
和 ioctl()
系统电话。
它工作得很好,不需要原始套接字解决方法来处理 ICMP 数据包。
事后看来,这是我应该遵循的方法...
这是一个简单的操作示例:
import os
import struct
from fcntl import ioctl
# ioctl constants
TUNSETIFF = 0x400454ca
TUNSETPERSIST = 0x400454cb
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
SIOCGIFHWADDR = 0x00008927
try:
# tap device name
tap_devname = 'tap0'
# open tap device
tap_fd = os.open('/dev/net/tun', os.O_RDWR)
# set tap device flags via ioctl():
#
# IFF_TUN : tun device (no Ethernet headers)
# IFF_TAP : tap device
# IFF_NO_PI : do not provide packet information, otherwise we end
# up with unnecessary packet information prepended to
# the Ethernet frame
ifr = struct.pack("16sH", ("%s" % (tap_devname)), IFF_TAP | IFF_NO_PI)
ioctl(tap_fd, TUNSETIFF, ifr)
# set device to persistent (if needed be, if not, comment the next line)
ioctl(tap_fd, TUNSETPERSIST, 1)
print("[INFO] tap device w/ name %s allocated" % (ifr[:16].strip("\x00")))
except Exception as e:
print("[ERROR] cannot setup tap device (%s)" % (e.message))
注意:完成上述操作后,您应该做两件事来使 TAP 设备运行:
- 启动 TAP 设备。例如。在 Linux 中,这可以通过
ip
命令完成,如下所示(假设 TAP 设备名称为 tap0
):
$ ip link set dev tap0 up
- 您可能希望将一个 IP 地址与您的 TAP 设备相关联。您应该添加一个路由 table 条目,以便通过
tap0
接口转发指向该地址的数据包(假设要关联的 IP 地址为 10.0.0.4,网络掩码为 255.255.255.0:
$ ip route add dev tap0 10.0.0.4/24
您也可以在 Python 中执行上述操作,使用 Python 的 subprocess
包(参见示例 in my Github)。
我正在为 self-learning 目的实现一个简单的用户空间网络堆栈。我在 Python 中写它,运行 在 Linux 中写它(Ubuntu 16.04.2 LTS)。我正在使用 a Python TAP device 接收第 2 层帧(例如以太网)。从那里,我提取 headers 并根据 header 字段处理帧。
问题: TAP 设备接收多种类型的帧,但不接收 ICMP 数据包(例如 ICMP 回显请求)。我希望它也能接收 ICMP 回显请求。
详情:为了测试堆栈的行为,我运行ping 10.0.0.4
在同一台机器上.我的 Ubuntu 环境是 运行 在虚拟机上,所以我 也 尝试了 运行 ping 10.0.0.4
主机(在将适当的条目添加到路由table之后)。我总是收到 ICMP 回显回复,即使 TAP 设备看到 none 个回显请求:
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
这是数据包处理代码(针对这个问题进行了简化):
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)
我的诊断: 我认为正在发生的事情是 Linux 内核直接处理 ICMP 回显请求,并且 (1) 甚至没有放置数据包 'on the wire' 或 (2) 不将 ICMP 数据包传递到用户空间。
(失败)解析尝试: 我已经尝试了几种方法在 TAP 设备上获取 ICMP 数据包,其中 none 导致 TAP 设备接收到ICMP 回应请求:
忽略 ICMP 回显处理:
echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all
添加 iptables 规则以丢弃 ICMP 回显请求:
sudo iptables -I INPUT -p icmp --icmp-type echo-request -j DROP
添加一个 iptables 规则 'jumps' 到
QUEUE
目标(想法是将 ICMP 数据包传递到用户空间):sudo iptables -I INPUT -p icmp --icmp-type echo-request -j QUEUE
使用原始套接字作为处理 ICMP 数据包的特例:
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)
你能告诉我让 Python TAP 设备接收 ICMP 回显请求的正确方法吗?
我查看了解决方案尝试 4,并通过在创建原始套接字并将套接字绑定到地址 (<ip-address>, 0)
.[=25= 时将 AF_PACKET
更改为 AF_INET
使其工作]
from socket import *
icmp_listener_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
icmp_listener_sock.bind((tap_dev.ip_addr, 0))
(icmp_ipv4_dgram, snd_addr) = icmp_listener_sock.recvfrom(2048)
process_icmp(icmp_ipv4_dgram)
请注意,这是一种解决方法,它没有回答有关如何使用 Python TAP 设备获取 ICMP 数据包的问题。
编辑(和明确的答案):
我尝试了一种不同的方法,原始 post 中没有列出这种方法。我没有使用 python-pytun
包,而是直接打开了 Linux 的 TUN/TAP 设备,使用 Python-like open()
和 ioctl()
系统电话。
它工作得很好,不需要原始套接字解决方法来处理 ICMP 数据包。
事后看来,这是我应该遵循的方法...
这是一个简单的操作示例:
import os
import struct
from fcntl import ioctl
# ioctl constants
TUNSETIFF = 0x400454ca
TUNSETPERSIST = 0x400454cb
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
SIOCGIFHWADDR = 0x00008927
try:
# tap device name
tap_devname = 'tap0'
# open tap device
tap_fd = os.open('/dev/net/tun', os.O_RDWR)
# set tap device flags via ioctl():
#
# IFF_TUN : tun device (no Ethernet headers)
# IFF_TAP : tap device
# IFF_NO_PI : do not provide packet information, otherwise we end
# up with unnecessary packet information prepended to
# the Ethernet frame
ifr = struct.pack("16sH", ("%s" % (tap_devname)), IFF_TAP | IFF_NO_PI)
ioctl(tap_fd, TUNSETIFF, ifr)
# set device to persistent (if needed be, if not, comment the next line)
ioctl(tap_fd, TUNSETPERSIST, 1)
print("[INFO] tap device w/ name %s allocated" % (ifr[:16].strip("\x00")))
except Exception as e:
print("[ERROR] cannot setup tap device (%s)" % (e.message))
注意:完成上述操作后,您应该做两件事来使 TAP 设备运行:
- 启动 TAP 设备。例如。在 Linux 中,这可以通过
ip
命令完成,如下所示(假设 TAP 设备名称为tap0
):
$ ip link set dev tap0 up
- 您可能希望将一个 IP 地址与您的 TAP 设备相关联。您应该添加一个路由 table 条目,以便通过
tap0
接口转发指向该地址的数据包(假设要关联的 IP 地址为 10.0.0.4,网络掩码为 255.255.255.0:
$ ip route add dev tap0 10.0.0.4/24
您也可以在 Python 中执行上述操作,使用 Python 的 subprocess
包(参见示例 in my Github)。