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

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

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

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回显请求:
  • 忽略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规则,该规则“跳转”到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,并通过在创建原始套接字并将套接字绑定(bind)到地址AF_PACKET时将AF_INET更改为(<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包,而是使用类似Python的open()ioctl()系统调用直接打开了Linux的TUN/TAP设备。

    它工作得很好,不需要原始套接字解决方法来处理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设备关联。您应该添加一个路由表条目,以便定向到该地址的数据包通过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回显请求),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44359329/

    10-14 06:13