使用Python的原始套接字和sendto函数

13
我正在尝试将scapy与twisted集成,但在OSX上遇到了一个非常奇怪的bug,我似乎无法解决它。
基本上,我无法通过原始套接字发送有效的TCP数据包(包括IP头)。这是我的操作:
import socket
from scapy.all import IP, TCP
pkt = IP(src='0.0.0.0', dst='127.0.0.1')/TCP()
spkt1 = str(pkt)
outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1)
outs.sendto(spkt1, ('127.0.0.1', 0))

当我运行这个程序时,出现了以下错误:

outs.sendto(spkt1, ('127.0.0.1', 0)) socket.error: [Errno 22] Invalid argument

如果您没有安装Scapy或不想使用它,这是该数据包的Base64编码:

import base64
spkt1 = base64.b64decode("RQAAKAABAABABvvOAAAAAH8AAAEAFABQAAAAAAAAAABQAiAAEH4AAA==")

非常奇怪的是,一个几乎一模一样的数据包似乎发送成功了:

spkt2 = base64.b64decode("RQBAAAWwAAACBgAAAAAAAH8AAAEAyAOEAAAAAAAAAACwAgDIAHsAAAIEBbQBAwMBAQEICk3PUjMAAAAABAIAAA==")
这是这两个数据包的外观:
SPKT1
0000   45 00 00 28 00 01 00 00  40 06 FB CE 00 00 00 00   E..(....@.......
0010   7F 00 00 01 00 14 00 50  00 00 00 00 00 00 00 00   .......P........
0020   50 02 20 00 10 7E 00 00                            P. ..~..
SPKT2
0000   45 00 40 00 05 B0 00 00  02 06 00 00 00 00 00 00   E.@.............
0010   7F 00 00 01 00 C8 03 84  00 00 00 00 00 00 00 00   ................
0020   B0 02 00 C8 00 7B 00 00  02 04 05 B4 01 03 03 01   .....{..........
0030   01 01 08 0A 4D CF 52 33  00 00 00 00 04 02 00 00   ....M.R3........

通过在Wireshark中检查它们,它们只在TCP部分上不同。

我进行了许多不同的实验,最终通过设置特定的TCP选项来发送数据包,但是这样的数据包不起作用是没有道理的。

有人知道为什么会发生这种情况吗?

编辑:

这个数据包似乎有效:

pkt = IP(len=16384, src='0.0.0.0', dst='127.0.0.1',
     id=RandShort(), ttl=2)/TCP(sport=255,
      dport=900, flags="S", window=200,
      options=[('MSS', 1460), ('WScale', 2)])
spkt = bytes(pkt)
spkt += '\x00'*20
如果你不添加零,它就无法工作。

你能修复第一个代码片段中的 import 吗?(另外,有趣的事实是,在阅读您问题的请求时,您可以使用 "…".decode("base64")"…".encode("base64") 而不是 import base64)。好的,抱歉,我无法帮助您解决这个问题。但是您已经得到了我的赞同。 - David Wolever
就我所知,我在你的代码上也遇到了同样的错误。 - Yuval Adam
6个回答

3
我最终决定放弃使用原始套接字,因为它们存在太多错误而无法使用。特别是由于这个软件需要跨平台,针对OSX的问题可能不适用于其他操作系统。目前,我只是简单地包装了scapy提供的“套接字”。未来,我将编写仅依赖于libdnet的代码(因为scapy使用它来编写原始帧)。您可以在此处找到该实现:https://github.com/hellais/txscapy

1

IP头必须是32位的倍数才能有效。并且最后还有一个填充区域。

因此,根据头部设置的IP选项 - 占用可变数量的位数 - 需要计算位数和填充。

看起来不同的操作系统处理方式不同。聪明的操作系统可能会为您完成这个填充。


0

虽然我没有确凿的证据,但我认为这可能与以太网最小有效载荷大小有关。

来自 wikipedia

当存在802.1Q标记时,最小有效载荷为42个八位字节;当不存在时,最小有效载荷为46个八位字节。

你第一个示例数据包只有40个字节,因此无论哪种情况都低于限制。你可以尝试将填充从20个字节更改为这些值,以验证其是否在某个限制处停止工作。

如果是这样,这个行为就很合理了;操作系统拒绝该数据包是因为你没有提供足够的数据来构建有效的数据包。


0

0.0.0.0在我的看法中不是一个有效的IP源地址。将其更改为其他任何值是否有任何区别?


看起来好像没有任何区别。 - Yuval Adam
不,这没有任何区别。有趣的是,例如这个数据包似乎可以工作: pkt = IP(len=16384, src='0.0.0.0', dst='127.0.0.1', id=RandShort(), ttl=2)/TCP(sport=255, dport=900, flags="S", window=200, options=[('MSS', 1460), ('WScale', 2)]) spkt = bytes(pkt) spkt += '\x00'*20如果您不添加20个零,则会像其他数据包一样失败。 (将数据包适当格式化后添加到帖子中) - Arturo Filastò - hellais
奇怪。不知道。我的下一个想法是OS X内核充满了像这样的模糊bug。;) - Jean-Paul Calderone
是的,这可能是OSX的一个bug。我打算放弃使用原始套接字,因为它们对跨平台支持非常糟糕,而改用libdnet。 - Arturo Filastò - hellais

0
另一个相关问题。Python的impacket模块有一个ping.py脚本来ping主机。在Mac OS X Lion上使用此脚本时,我遇到了一个错误:
Traceback (most recent call last):
  File "/private/var/www/env/bin/ping.py", line 73, in <module>
    s.sendto(ip.get_packet(), (dst, 0))
socket.error: [Errno 22] Invalid argument

但在Ubuntu上一切正常,我可以从主机那里得到回复。


0
Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> from scapy.all import IP, TCP
WARNING: No route found for IPv6 destination :: (no default route?)
>>> pkt = IP(src='0.0.0.0', dst='127.0.0.1')/TCP()
>>> spkt1 = str(pkt)
>>> 
>>> outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
>>> outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1)
>>> outs.sendto(spkt1, ('127.0.0.1', 0))
40

我似乎在编写该特定数据包集时没有遇到任何错误 - 我正在使用带有2.6.38- * Gnu / Linux内核的x86_64。

也许你的问题与Mac OS X原始套接字的一些脑损伤有关?


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