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

Posted

技术标签:

【中文标题】如何在 Python 中使用原始套接字?【英文标题】:How Do I Use Raw Socket in Python? 【发布时间】:2010-11-10 05:13:14 【问题描述】:

我正在编写一个应用程序来测试处理损坏数据的网络驱动程序。而且我想到了使用原始套接字发送这些数据,因此发送机器的 TCP-IP 堆栈不会更正它。

我仅在 Linux 上编写此应用程序。我有在系统调用中使用原始套接字的代码示例,但我真的很想保持我的测试尽可能动态,并用 Python 编写大部分(如果不是全部)。

我在网上搜索了一些关于在 python 中使用原始套接字的解释和示例,但没有发现任何真正有启发性的东西。只是一个非常古老的代码示例来演示这个想法,但绝不可行。

根据我的收集,Python 中的原始套接字使用在语义上与 UNIX 的原始套接字几乎相同,但没有定义数据包结构的 structs。

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

【问题讨论】:

【参考方案1】:

你这样做:

首先禁用网卡的自动校验和:

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)

完成。

【讨论】:

我怀疑这是错误的; AF_PACKET 接口不会将校验和部分暴露给用户空间,因此它是由驱动程序/硬件自动生成和检查的,我们对此无能为力。 Wireshark 说我发送的“校验和”是“数据”的一部分。 这取决于您的硬件/驱动程序。有些驱动程序会忽略已经存在的校验和,而有些驱动程序会将其作为数据的一部分。在不同的驱动程序中可靠地执行实际上是一件令人头疼的事情。这个问题在 C 实现中也会存在。不放置校验和似乎总是会导致正确的行为(即:数据包校验和由驱动程序/硬件计算)。 这没有多大意义。在可以处理显式校验和的驱动程序中,它应该如何知道最后的 4 个空字节(或任何 4 个字节,就此而言)将用作校验和?即使字节匹配正确的校验和,这也意味着驱动程序必须计算它,但只有在最后 4 个字节还不是它计算的值时才将其附加到帧:这是极不可能且容易出错的行为。 您可以自己配置行为,具体取决于卡/驱动程序。看看ethtool。这将类似于ethtool -K tx off(无论如何在Linux上......) 哦,所以如果你没有在接口上明确禁用自动校验和生成,你一定不能通过 AF_PACKET 发送它。我认为这个信息应该在答案中注明……或者不,因为它专门讨论了 Python。【参考方案2】:

Sockets 系统调用(或 Winsocks,在 Windows 上),已经封装在标准模块 socket:intro、reference。

我从未使用过原始套接字,但看起来它们可以与此模块一起使用:

最后一个例子展示了如何写一个 非常简单的原始网络嗅探器 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)

【讨论】:

这正是他回答中展开链接的代码示例。这是一个很好的,但我需要知道我如何发送原始数据。在我基于这些示例的所有尝试中,我构建的原始数据根本没有被发送。即使我尝试发送原始采样数据包,这确实有效,而不是自己构建。 使用原始套接字,您还必须自己构建 IP 和 TCP 或 UDP 标头。也许您只是想发送您的应用程序数据,并希望操作系统会为您完成其余的工作。一旦你使用原始套接字,所有的赌注都没有了。一切都必须由您完成。 当我说 TCP 或 UDP 时,我实际上是指您想要自己构建的任何基于 IP 的协议。 @Amey:你应该在一个新问题中写这个。就个人而言,我帮不了你。【参考方案3】:

this 你提到的旧代码找到了吗?这对我来说看起来很明智,但我自己没有测试过(或使用过原始套接字)。文档中的This example 展示了如何使用原始套接字来嗅探数据包,并且看起来非常相似。

【讨论】:

这正是我所说的代码示例!。问题在于它使用了套接字的初始化,并且完全不推荐使用协议。此外,我认为这是一种将服务器端和客户端混合到一个概念应用程序中的方式。 对,AF_PACKET 地址族似乎不再存在。第二个例子(在 Bastien 的回答中转载)使用 AF_INET,我想这可能是有道理的。如果您想要测试的只是发送数据(检查例如基于模式的损坏或其他),那么寻址应该无关紧要。我认为第一个示例通过执行完全同步的发送和接收调用来“混合概念”。不是很常见或很好,但应该是合法的。【参考方案4】:

最终,这种情况的最佳解决方案是用 C 编写整个东西,因为它不是一个大应用程序,所以用一种以上的语言编写这么小的东西会招致更大的惩罚。

在玩弄了 C 和 python RAW 套接字之后,我最终更喜欢 C RAW 套接字。 RAW 套接字需要少于 8 位组的位级修改来写入数据包标头。有时只写 4 位或更少。 python 对此没有任何帮助,而 Linux C 对此有完整的 API。

但我绝对相信,如果只是在 python 中方便地处理这一点头初始化,我就不会在这里使用 C。

【讨论】:

这没有回答问题。 我同意,作为 Python RAW 套接字是否优于 C RAW 套接字的总体观点,这并不能回答问题。但最终这与我当时正在处理的问题有关,这解决了问题,因此就我而言是正确的答案。 不,看看this question【参考方案5】:
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                        ]]]....

【讨论】:

【参考方案6】:

socket 类应该会有所帮助。如果不是,您需要用 C 编写 Python 模块或只使用 C。请参阅 http://mail.python.org/pipermail/python-list/2001-April/077454.html

基本谷歌搜索发现。

我实际上尝试了“unwind”指出的代码示例。 AF_PACKET 在 python 2.7.4 中对我有用

【讨论】:

我在第一次谷歌搜索时查看了这篇文章,并没有发现这很有帮助。大多数讨论都是非常过时的。 AF_PACKET 不再出现在 Python 中。 注意AF_PACKET可能没有为python 2.7定义,但在python 3中定义。【参考方案7】:

您可以使用这个 Python 库:rawsocketpy 它允许在第 2 层使用原始套接字 => 没有 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()

【讨论】:

【参考方案8】:

FTR,如果您想要 2 级访问(以太网、RadioTap...),这在 Windows 上是不可能的(截至今天)。

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

您有各种可用于 libpcap 的 Python 绑定,处于各种级别(非常高或非常低)。

我的建议是使用scapy 的套接字(即使你不使用它来剖析),它实现了 Native 和 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

【讨论】:

以上是关于如何在 Python 中使用原始套接字?的主要内容,如果未能解决你的问题,请参考以下文章

python使用原始套接字 解析原始ip头数据

python使用原始套接字 解析原始ip头数据

python 使用套接字创建原始http请求

Python 原始套接字 (Windows):嗅探以太网帧

如何在内核模式驱动程序中使用 Winsock Kernel (WSK) 发送原始套接字数据包?

python中的原始套接字和sendto