【问题标题】:Using TunTapDevice in Python with sendp doesn't fire the correct callback在 Python 中使用带有 sendp 的 TunTapDevice 不会触发正确的回调
【发布时间】:2022-01-21 01:58:10
【问题描述】:

我正在使用 python-pytun 创建两个 TUN 接口(tun0 和 tun1)。我的目标是在两个接口上创建数据包接收器,因此当数据包进入其网络时,它们将创建适当的响应。我使用 ICMP 帧作为初始测试。 tun0网络为10.8.0.0/29,tun1网络为10.8.1.0/29。

当我运行代码时,我使用 sendp 将数据包从 tun0 发送到 tun1。奇怪的是,tun0 接收器回调触发,我最终看到了这个输出:

[0] 新数据包:10.8.0.1 -> 10.8.1.2

在程序仍在运行的情况下(因为存在无限循环),我可以使用另一个终端向该接口发送 ping。从那个终端我发出这个命令:

ping -I 10.8.0.1 10.8.1.2

哪个(我认为)生成与 python 代码相同的数据包。无论如何,当我这样做时,我的 python 程序看起来就像它正在触发正确的接收器回调,如下所示:

[1] 新数据包:10.8.0.1 -> 10.8.1.2

有什么想法吗?这是供参考的代码。感谢您的任何意见!

#!/usr/bin/env python3
from pytun import TunTapDevice, IFF_TAP
import os
from scapy.all import *
from threading import Thread
from time import sleep

# Make sure we're running as root, otherwise report an error and exit
if os.geteuid() != 0:
    exit("Please run as root")

class Sniffer(Thread):
    def  __init__(self, interface, receive_callback):
        super().__init__()

        self.interface = interface
        self.receive_callback = receive_callback

    def run(self):
        print("Interface: {}".format(self.interface))
        sniff(iface=self.interface, filter="ip", prn=self.receive_callback)

class InterfaceFactory:
    def __init__(self):
        self.interfaces = []
    
    def create_tun_interface(self, interface, addr, dstaddr, prefixLen, receive_callback=None):
        # Looks like a TAP interface isn't what I want, but I'll leave the code here for now just in case
        #tun = TunTapDevice(name=interface, flags=IFF_TAP)

        # Create the TUN interface with the given name
        tun = TunTapDevice(name=interface)

        # Convert the prefix length to a subnet mask string in the form xxx.xxx.xxx.xxx
        netmask = '.'.join([str((m>>(3-i)*8)&0xff) for i,m in enumerate([-1<<(32-prefixLen)]*4)])

        # Assign network characteristics to the new TUN interface
        tun.addr = addr
        tun.netmask = netmask
        tun.mtu = 1500
        tun.dstaddr = dstaddr

        # I don't think I need to set the MAC address, one seems to be assigned anyway,
        # leaving the code here for debugging purposes.
        #tun.hwaddr = b'\x00\x11\x22\x33\x44\x55'

        # Bring the new TUN interface up
        tun.up()

        # Register the callback function if one was specified
        if receive_callback:
            sniffer = Sniffer(interface, receive_callback)
            sniffer.start()

        # Append the new interface to a member list to keep it within scope,
        # otherwise it would remove the interface from the system
        self.interfaces.append(tun)
    
    def get_tun_interface_handle(self, interface):
        for iface in self.interfaces:
            if iface.name == interface:
                return iface
        return None

interfaceFactory = InterfaceFactory()

def tun0_receive_callback(packet):
    ip_layer = packet.getlayer(IP)
    print("[0] New Packet: {src} -> {dst}".format(src=ip_layer.src, dst=ip_layer.dst))
    #packet.show2()
    #if packet.haslayer(ICMP):

def tun1_receive_callback(packet):
    ip_layer = packet.getlayer(IP)
    print("[1] New Packet: {src} -> {dst}".format(src=ip_layer.src, dst=ip_layer.dst))
    #packet.show2()
    #if packet.haslayer(ICMP):    

interfaceFactory.create_tun_interface("tun0", "10.8.0.1", "10.8.0.0", 29, tun0_receive_callback)
interfaceFactory.create_tun_interface("tun1", "10.8.1.1", "10.8.1.0", 29, tun1_receive_callback)

# Wait a second for the sniffer class to do what it needs to do
# not waiting will prevent the callback from firing
sleep(1)

# Send packet from tun0 to tun1, ICMP type 8 is Echo.  Note: type 0 is Echo Reply
# More info here: https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-types
data = bytes(16)
frame = IP(src='10.8.0.1', dst='10.8.1.2')/ICMP(type=8)/Raw(load=data)
sendp(frame, iface="tun0")

try:
    while True:
        #frame.show2()
        sleep(2)
except KeyboardInterrupt:
    sniffer.join()

【问题讨论】:

    标签: python python-3.x networking ethernet icmp


    【解决方案1】:

    我找到了解决方案。当软件运行时,我可以在 Linux 中发出路由命令来查看路由,并且 tun0 和 tun1 都存在。我也可以发出类似的问题:

    ip路由得到10.8.1.2

    它会报告 tun1 是目标接口。 我确实发现 Python(我认为特别是 scapy)需要同步并重新加载接口列表和路由表。否则将导致软件不知道 tun0 和 tun1 是可用的接口或可用的路由。

    为此,我执行了以下操作:

    conf.ifaces.reload()
    conf.route.resync()
    

    我可以通过查看接口和路由来确认:

    print(conf.ifaces)
    print(conf.route)
    

    这也允许我用 send 替换 sendp,因为我只在第 3 层工作。

    我认为我遇到的唯一新问题是,当我现在运行软件时,我让它从 10.8.0.2 向 10.8.1.2 发送 ICMP 请求(因此从 tun0 发送到 tun1)。该请求每 2 秒发送一次。在运行时,我使用以下命令在 tun0 上启动 tcpdump:

    tcpdump -i tun0 icmp

    而且我没有看到数据包发出。我只看到数据包回来了。我不明白为什么会这样。这是发布的更新代码,以防有人想查看。注意:它确实包含许多未使用的代码和参数,因为我一直在使用它来寻找答案。

    #!/usr/bin/env python3
    from pytun import TunTapDevice, IFF_TAP
    import os
    from scapy.all import *
    from threading import Thread
    from time import sleep
    
    # Make sure we're running as root, otherwise report an error and exit
    if os.geteuid() != 0:
        exit("Please run as root")
    
    class Sniffer(Thread):
        def  __init__(self, interface, receive_callback):
            super().__init__()
    
            self.interface = interface
            self.receive_callback = receive_callback
    
        def run(self):
            print("Interface: {}".format(self.interface))
            sniff(iface=self.interface, filter="icmp", prn=self.receive_callback)
    
    class InterfaceFactory:
        def __init__(self):
            self.interfaces = []
        
        def create_tun_interface(self, interface, addr, dstaddr, macAddr, prefixLen, receive_callback=None):
            # Create the TUN interface with the given name
            #tun = TunTapDevice(name=interface, flags=IFF_TAP)
            tun = TunTapDevice(name=interface)
    
            # Convert the prefix length to a subnet mask string in the form xxx.xxx.xxx.xxx
            netmask = '.'.join([str((m>>(3-i)*8)&0xff) for i,m in enumerate([-1<<(32-prefixLen)]*4)])
    
            # Assign network characteristics to the new TUN interface
            tun.addr = addr
            tun.netmask = netmask
            tun.mtu = 1500
            tun.dstaddr = dstaddr
    
            # I don't think I need to set the MAC address, one seems to be assigned anyway,
            # leaving the code here for debugging purposes.
            tun.persist(False)
    
            #tun.hwaddr = macAddr
    
            # Bring the new TUN interface up
            tun.up()
    
            # Register the callback function if one was specified
            if receive_callback:
                sniffer = Sniffer(interface, receive_callback)
                sniffer.start()
    
            # Append the new interface to a member list to keep it within scope,
            # otherwise it would remove the interface from the system
            self.interfaces.append(tun)
        
        def get_tun_interface_handle(self, interface):
            for iface in self.interfaces:
                if iface.name == interface:
                    return iface
            return None
    
    interfaceFactory = InterfaceFactory()
    
    def tun0_receive_callback(packet):
        ip_layer = packet.getlayer(IP)
        print("[0] New Packet: {src} -> {dst}".format(src=ip_layer.src, dst=ip_layer.dst))
        #packet.show2()
        #if packet.haslayer(ICMP):
    
    def tun1_receive_callback(packet):
        ip_layer = packet.getlayer(IP)
        print("[1] New Packet: {src} -> {dst}".format(src=ip_layer.src, dst=ip_layer.dst))
    
        frame = IP(src=ip_layer.dst, dst=ip_layer.src)/ICMP(type=0)
        send(frame)
        #packet.show2()
        #if packet.haslayer(ICMP):    
    
    interfaceFactory.create_tun_interface("tun0", "10.8.0.1", "10.8.0.0", b'\x00\x11\x22\x33\x44\x55', 28, tun0_receive_callback)
    interfaceFactory.create_tun_interface("tun1", "10.8.1.1", "10.8.1.0", b'\x00\x11\x22\x33\x44\x56', 28, tun1_receive_callback)
    
    # Wait a second for the sniffer class to do what it needs to do
    # not waiting will prevent the callback from firing
    #sleep(1)
    
    # Send packet from tun0 to tun1, ICMP type 8 is Echo.  Note: type 0 is Echo Reply
    # More info here: https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-types
    #data = bytes(16)
    #frame = Ether(src='00:11:22:33:44:55', dst='00:11:22:33:44:56')/IP(src='10.8.0.2', dst='10.8.1.2')/ICMP(type=8)
    frame = IP(src='10.8.0.2', dst='10.8.1.2')/ICMP(type=8)
    
    conf.ifaces.reload()
    conf.route.resync()
    print(conf.route.route("10.8.0.2"))
    print(conf.route.route("10.8.1.2"))
    
    
    try:
        while True:
            #sendp(frame, iface="tun0")
            send(frame)
            #frame.show2()
            sleep(2)
    except KeyboardInterrupt:
        sniffer.join()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-21
      • 2014-01-26
      • 2010-10-09
      • 1970-01-01
      • 2015-10-27
      相关资源
      最近更新 更多