python - 使用 Python TAP 设备嗅探 ICMP 数据包(例如 ping echo 请求)

标签 python linux sockets network-programming iptables

我正在实现一个简单的用户空间网络栈,用于自学目的。我用 Python 编写它,在 Linux (Ubuntu 16.04.2 LTS) 上运行它。我正在使用 a Python TAP device接收第 2 层帧(例如以太网)。从那里,我提取标题并根据标题字段处理帧。

问题:TAP 设备接收到多种类型的帧,但不是 ICMP 数据包(例如 ICMP 回显请求)。我希望它也能接收 ICMP 回显请求。

详细信息:为了测试堆栈的行为,我在同一台机器上 运行ping 10.0.0.4。我的 Ubuntu 环境在虚拟机上运行,​​所以我尝试运行 ping 10.0.0.4 从主机(添加路由表的适当条目)。我总是收到 ICMP 回显回复,即使 TAP 设备看不到任何回显请求:

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) 甚至没有将数据包“在线”或 (2 ) 不会将 ICMP 数据包传递给用户空间。

(失败)解析尝试:我已经尝试了几种方法来在 TAP 设备上获取 ICMP 数据包,但没有一种方法导致 TAP 设备接收到 ICMP 回显请求:

  1. 忽略 ICMP 回显处理:

    回显 1 | sudo tee/proc/sys/net/ipv4/icmp_echo_ignore_all

  2. 添加 iptables 规则以丢弃 ICMP 回显请求:

    sudo iptables -I INPUT -p icmp --icmp-type echo-r​​equest -j DROP

  3. 添加一个 iptables 规则,它“跳转”到 QUEUE 目标(想法是将 ICMP 数据包传递到用户空间):

    sudo iptables -I INPUT -p icmp --icmp-type echo-r​​equest -j QUEUE

  4. 使用原始套接字作为处理 ICMP 数据包的特例:

    从套接字导入 * 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,并通过更改 AF_PACKET 使其工作至 AF_INET创建原始套接字并将套接字绑定(bind)到地址时 (<ip-address>, 0) .

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 数据包的问题。

编辑(和明确的答案):

我尝试了一种不同的方法,原始帖子中没有列出。而不是使用 python-pytun包,我直接打开了 Linux 的 TUN/TAP 设备,使用类似 Python 的 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 设备运行:

  1. 启动 TAP 设备。例如。在 Linux 中,这可以通过 ip 来完成命令,如下图(假设TAP设备名称为tap0):

$ ip link set dev tap0 up

  1. 您可能希望将一个 IP 地址与您的 TAP 设备相关联。您应该添加一个路由表条目,以便定向到该地址的数据包通过 tap0 转发接口(interface)(假设要关联的 IP 地址为 10.0.0.4,网络掩码为 255.255.255.0:

$ ip route add dev tap0 10.0.0.4/24

您也可以使用 Python 的 subprocess 在 Python 中执行上述操作包(参见示例 in my Github )。

关于python - 使用 Python TAP 设备嗅探 ICMP 数据包(例如 ping echo 请求),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44359329/

相关文章:

python - Flask/Python 上下文调试

Ruby 将字节写入套接字

c++ - 使用Boost Asio时获取 “resolve: Host not found (authoritative)”异常

linux - 使用 Awk 将服务器名称从列表传递到 nslookup

c - 虽然没有收到信号?

c++ - 从浏览器套接字接收函数,但不在缓冲区中存储任何内容

python - 如何从节点 id(OSMnx) 获取经纬度坐标?

python - 在 Spyder 的变量资源管理器中查看变量

python - 在 Flask 中设置静态文件夹路径

Linux程序——反向高亮文本