使用Python TAP设备

时间:2017-06-04 22:16:51

标签: 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)甚至没有放入数据包&# 39;在线上'或(2)不将ICMP数据包传递给用户空间。

(失败)解决方案尝试:我在TAP设备上尝试了几件事来获取ICMP数据包,但都没有导致TAP设备收到ICMP回应请求:

  1. 忽略ICMP回显处理:

    echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all

  2. 添加iptables规则以删除ICMP回应请求:

    sudo iptables -I INPUT -p icmp --icmp-type echo-request -j DROP

  3. 添加一个跳过'跳过的iptables规则。到QUEUE目标(想法是将ICMP数据包传递给用户空间):

    sudo iptables -I INPUT -p icmp --icmp-type echo-request -j QUEUE

  4. 使用原始套接字作为特殊情况来处理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)

  5. 您能否指出让Python TAP设备接收ICMP回应请求的正确方法?

1 个答案:

答案 0 :(得分:0)

我查看了解决方案尝试4,并在创建原始套接字并将套接字绑定到地址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设备正常运行:

  1. 带上TAP设备。例如。在Linux中,可以使用ip命令完成此操作,如下所示(假设TAP设备名称为tap0):
  2. $ ip link set dev tap0 up

    1. 您可能希望将IP地址与您的TAP设备相关联。您应该添加路由表条目,以便通过tap0接口转发定向到该地址的数据包(假设要关联的IP地址是10.0.0.4,以及255.255.255.0网络掩码:
    2. $ ip route add dev tap0 10.0.0.4/24

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