如何在ICMP数据字段中发送图像
我正在使用scapy这个工具通过ICMP发送数据。我想通过ICMP发送图片和其他文件,目前我只能发送简单的字符串。请问我该如何发送图片和其他文件呢?
2 个回答
我想你发现了为什么互联网的发明者开发了TCP协议。ICMP数据包的有效载荷大小是有限制的。大多数网络对以太网数据包的总大小有1500字节的限制,所以加上20字节的IP头和8字节的ICMP头,你在一个数据包中最大能放1472个字节。这对于很多图片文件来说是不够的。
你需要一种方法,把你的图片数据流分成差不多这个大小的小块,分别发送多个ICMP数据包,然后在接收端把它们重新组合成一个完整的数据流。
考虑到ICMP数据包的接收顺序没有保证,甚至可能有些数据包根本就收不到,你需要在每个数据包里加上某种序列号,这样才能按顺序重新组合它们。你可能还需要一些超时逻辑,这样接收端就能判断某个数据包丢失了,从而知道图片可能无法完整显示。
RTP和TCP是两个建立在IP之上的协议,可以实现这些功能。它们的文档非常详细,你可以从中学习如何实现你想做的事情,以及一些可能遇到的陷阱和性能问题。
我对scapy不太了解,所以我会用简单的算法和纯Python来解释。
要发送图片文件,你需要把图片分成几个小块,同时要有办法记录每一块的发送顺序。
首先,把图片文件当作二进制文件来读取。然后,决定把二进制数据分成多大。假设最大传输单元(MTU)是1500字节,我建议你用1500减去IP头和ICMP头的大小,通常可以用1472字节,这样可以避免IP分片。此外,我建议使用16位或32位的数字来记录发送块的顺序,并从1500中减去这个数字。举个例子:1500 - IP - ICMP - 4 = 1468。
根据上面的例子,我会这样分割图片:
image = file("/path/to/file","rb").read()
chunk = []
interval = 1500 - 20 - 8 - 4
for n in range(0, len(image), interval):
chunk.append(image[n:n + interval])
现在图片已经分成小块,我会在每个小块上添加追踪字节。你可以选择把它放在前面或后面,我选择放在前面:
for n in range(len(chunk)):
chunk[n] = struct.pack(">I", n) + chunk[n]
现在我们已经给每个小块标记了追踪信息,接下来需要构建ICMP头,并把每个小块附加上去。但是我们该用什么类型/代码呢?在这个情况下,我们会构建一个ECHO REQUEST包(其他类型比如0或3是无请求的,可能会导致接收的内核不把包转发给你的ICMP套接字处理程序)。还有一点很重要:你需要计算ICMP校验和的代码:
icmp_chunks = []
for img_piece in chunk:
icmp = struct.pack(">BBHHH%ds" % len(img_piece), 8, 0, 0, 0, 0, img_piece)
icmp = struct.pack(">BBHHH%ds" % len(img_piece), 8, 0, cksum(icmp), 0, 0, img_piece)
icmp_chunks.append(icmp)
再强调一下,注意第二个icmp头包中的cksum(icmp)参数。这个校验和是必须的,否则接收的内核可能不会把包转发给任何ICMP套接字处理程序,如果校验和不正确。另外,注意第4和第5个参数是零:这些是“标识符”和“序列号”字段。通常这些用来存储进程ID和递增的数字(分别)。套接字对象用这种方式来跟踪哪个套接字发送了什么和发送了多少。在这个情况下,由于我们发送的是无请求的ECHO REPLY,你可以在接收端编写代码来查找类型为0的包,并进一步查找发送块中的追踪字节。因此,我暂时把标识符和序列字段设为零。如果你在想我为什么构造了两个icmp头,那是因为内核在计算校验和时需要初始的校验和字段为零。
到现在为止,我们已经有了icmp头,并准备发送:
for i in icmp_chunks:
[socket].sendto(i, ("hostname-or-ip", 0))
我假设[套接字]对象是scapy或你自己的套接字对象。
在接收端,你需要读取数据并查找你的图片负载,把每个负载重新组合回原来的顺序。不过,你仍然需要某种方法来确认所有接收到的小块确实都是完整的。可以考虑在发送图片块之前,先构建一个初始字节序列来指示总的预期大小。
希望这对你有帮助。
更新:我测试了向Ubuntu Linux发送无请求的类型0包,发现接收的内核没有把包转发给我的处理程序。所以我更新了我的解释以考虑到这一点。