我该如何在Python中使用原始套接字?

48

我正在编写一款测试网络驱动程序处理损坏数据的应用程序。我考虑使用原始套接字来发送这些数据,以便发送机的TCP-IP堆栈不会对其进行纠正。

我完全在Linux上编写此应用程序。我有使用系统调用中的原始套接字的代码示例,但我真的很想让我的测试尽可能动态,并且大部分甚至全部都用Python编写。

我已经在网上搜索了一些关于Python中使用原始套接字的解释和示例,但没有找到任何真正有启发性的东西。只是一个非常古老的代码示例,展示了这个想法,但却没有任何作用。

从我所了解的情况来看,在Python中使用原始套接字几乎与UNIX的原始套接字语义相同,但没有定义包结构的struct。

我在想,是否最好不要使用Python编写测试的原始套接字部分,而是使用C编写系统调用,并从主Python代码中调用?

8个回答

62

你可以这样做:

首先禁用网络适配器的自动校验和功能:

sudo ethtool -K eth1 tx off

然后从 Python 2 发送您的错误帧(您必须自己转换为 Python 3):

#!/usr/bin/env python
from socket import socket, AF_PACKET, SOCK_RAW
s = socket(AF_PACKET, SOCK_RAW)
s.bind(("eth1", 0))

# We're putting together an ethernet frame here, 
# but you could have anything you want instead
# Have a look at the 'struct' module for more 
# flexible packing/unpacking of binary data
# and 'binascii' for 32 bit CRC
src_addr = "\x01\x02\x03\x04\x05\x06"
dst_addr = "\x01\x02\x03\x04\x05\x06"
payload = ("["*30)+"PAYLOAD"+("]"*30)
checksum = "\x1a\x2b\x3c\x4d"
ethertype = "\x08\x01"

s.send(dst_addr+src_addr+ethertype+payload+checksum)

完成。


2
我怀疑这是错误的; AF_PACKET接口不向用户空间公开校验和部分,因此它是由驱动程序/硬件自动生成和检查的,我们对此无能为力。Wireshark说我发送的“校验和”是“数据”的一部分。 - lvella
1
这取决于您的硬件/驱动程序。一些驱动程序会忽略已存在的校验和,而另一些则会将其视为数据的一部分。在不同的驱动程序中可靠地执行此操作实际上非常麻烦。在C语言实现中也会存在此问题。不放置校验和似乎总是导致正确的行为(即:数据包校验和由驱动程序/硬件计算)。 - brice
2
那并没有什么意义。对于可以处理明确校验和的驱动程序来说,它怎么知道末尾的4个空字节(或任何其他4个字节)应该用作校验和?即使这些字节匹配了正确的校验和,这也意味着驱动程序必须计算它,但仅当最后4个字节不是已经计算出来的校验和时才将其附加到帧中:这是一种高度不可能且容易出错的行为。 - lvella
1
你可以根据网卡/驱动程序自行配置行为。查看ethtool。它的用法类似于ethtool -K tx off(无论在哪个Linux版本中...)。 - brice
1
AF_PACKET是正确的,也是唯一真正有意义的。根据man页面Packet sockets用于在设备驱动程序(OSI层2)级别接收或发送原始数据包。 - brice
显示剩余5条评论

10
套接字系统调用(或在Windows上的Winsocks)已经封装在标准模块“socket”中:介绍参考文献
我从未使用过原始套接字,但看起来可以与此模块一起使用:

最后一个示例展示了如何使用Windows上的原始套接字编写非常简单的网络嗅探器。该示例需要管理员特权来修改接口:

import socket

# the public network interface
HOST = socket.gethostbyname(socket.gethostname())

# create a raw socket and bind it to the public interface
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind((HOST, 0))

# Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# receive all packages
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

# receive a package
print s.recvfrom(65565)

# disabled promiscuous mode
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

这正是代码示例,unwind在他的回答中提供了链接。这是一个很好的示例,但我需要知道如何发送原始数据。根据这些示例,我尝试过的所有方法都无法成功发送我构建的原始数据。即使我尝试发送原始采样数据包,也不能代替自己构建的数据。 - Avihu Turzion
使用原始套接字,您还必须自己构建IP和TCP或UDP头。也许您只是想发送应用程序数据,并希望操作系统为您完成其余工作。一旦使用原始套接字,所有的赌注都取消了。一切都必须由您自己完成。 - Aditya Sehgal
当我说TCP或UDP时,我真正意思是任何你想要自己构建的基于IP的协议。 - Aditya Sehgal
1
@Amey:你应该用一个新的问题来描述你的问题。就我个人而言,我无法帮助你。 - Bastien Léonard

2
这段话的意思是:“this 是你提到过的旧代码吗?看起来对我来说很合理,但我自己还没有测试过它(或者使用原始套接字)。文档中的这个示例 展示了如何使用原始套接字来嗅探数据包,并且它看起来相似。”

1
这正是我所说的代码示例!它的问题在于它对套接字和协议的初始化完全过时。此外,我认为它将服务器和客户端混合到一个概念应用程序中。 - Avihu Turzion
好的,看起来AF_PACKET地址族已经不存在了。第二个例子(在Bastien的答案中重现)使用了AF_INET,我想这可能是有道理的。如果你只想测试发送数据(检查例如基于模式的损坏或其他问题),那么寻址不应该成为问题。我认为第一个例子通过进行完全同步的发送后跟接收调用“混合概念”。这并不常见或美观,但应该是合法的。 - unwind

2

最终对于这个问题的最佳解决方案是使用C语言编写整个程序,因为它不是一个很大的应用程序,所以使用多于1种语言编写这么小的东西会带来更大的开销。

在尝试了C和Python原始套接字之后,我最终偏爱C的原始套接字。原始套接字需要对少于8位组的位级修改来编写数据包头。有时只需编写4位或更少的位。Python没有定义任何帮助内容,而Linux C具有完整的API。

但我确信,如果Python可以方便地处理这个小小的标题初始化,我就不会在这里使用C了。


10
这并不回答问题。 - Humphrey Bogart
我同意从总体角度来看,Python原始套接字是否优于C原始套接字并不能回答这个问题。但最终这与我当时处理的问题有关,而且这解决了问题,因此在我看来是正确的答案。 - Avihu Turzion
不,看一下这个问题:https://dev59.com/wWw15IYBdhLWcg3w4_7k - brice

1
s = socket(AF_PACKET, SOCK_RAW)
s = socket(PF_PACKET, SOCK_RAW)

结果:

[root@localhost python]# tcpdump -i eth0

capture size 96 bytes
11:01:46.850438 

01:02:03:04:05:06 (oui Unknown) > 01:02:03:04:05:06 (oui Unknown), ethertype Unknown (0x0801), length 85:

        0x0000:  5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5b5b  [[[[[[[[[[[[[[[[
        0x0010:  5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5041  [[[[[[[[[[[[[[PA
        0x0020:  594c 4f41 445d 5d5d 5d5d 5d5d 5d5d 5d5d  YLOAD]]]]]]]]]]]
        0x0030:  5d5d 5d5d 5d5d 5d5d 5d5d 5d5d 5d5d 5d5d  ]]]]]]]]]]]]]]]]
        0x0040:  5d5d 5d00 0000 00                        ]]]....

0
你可以使用这个Python库: rawsocketpy 它允许在第二层使用原始套接字=>没有IP/TCP/UDP头部。
#!/usr/bin/env python
from rawsocketpy import RawSocket

sock = RawSocket("wlp2s0", 0xEEFA)
sock.send("some data")
sock.send("personal data", dest="\xAA\xBB\xCC\xDD\xEE\xFF")

或者服务器表单:

#!/usr/bin/env python
from rawsocketpy import RawRequestHandler, RawAsyncServerCallback
import time

def callback(handler, server):
    print("Testing")
    handler.setup()
    handler.handle()
    handler.finish()

class LongTaskTest(RawRequestHandler):
    def handle(self):
        time.sleep(1)
        print(self.packet)

    def finish(self):
        print("End")

    def setup(self):
        print("Begin") 

def main():
    rs = RawAsyncServerCallback("wlp2s0", 0xEEFA, LongTaskTest, callback)
    rs.spin()

if __name__ == '__main__':
    main()

0

顺便说一句,如果你想要二级访问权限(以太网、RadioTap...),那么在Windows上原生实现是不可能的(截至今天)。

如果你想要通过跨平台方法来访问这些内容,首选是使用 libpcap 及其 Python 绑定(因为它将使用 Npcap/WinPcap 在 Windows 上工作)。

你可以使用各种级别的 Python 绑定来访问 libpcap。

我的建议是使用 scapy 的套接字(即使你不使用它进行解析),它实现了本地和 Libpcap 调用,并且可以使用 conf.use_pcap = True 来选择它们。

from scapy.all import conf
# conf.use_pcap = True (will be automatic if required)
socket = conf.L2socket(iface="eth0")
# On any platforms, you have `get_if_list()` in `scapy.all` available, to see the ifaces available. You could also ignore it to use the default one

0
socket类应该会有所帮助。如果不行,您需要编写一个C语言的Python模块或直接使用C语言。请参见http://mail.python.org/pipermail/python-list/2001-April/077454.html
基本的谷歌搜索就能找到这些信息。
我实际上已经尝试了"unwind"指出的代码示例。在python 2.7.4中AF_PACKET对我很有效。

4
我已经浏览了这篇文章,在我最初的谷歌搜索中,并没有发现很有帮助的内容。大部分讨论已经过时了。AF_PACKET在Python中已不再出现。 - Avihu Turzion
请注意,AF_PACKET在Python 2.7中可能未定义,但在Python 3中已定义。 - Generic Ratzlaugh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接