我正在尝试在Python中实现基于ICMP的Traceroute。我发现了一个非常有用的指南(https://blogs.oracle.com/ksplice/entry/learning_by_doing_writing_your),它允许我创建一个基于UDP的Traceroute,所以只需要修改。但是我环顾四周,无法更改发送套接字并使其正常工作。有人能帮助我吗?
#!/usr/bin/python
import socket
def main(dest_name):
dest_addr = socket.gethostbyname(dest_name)
port = 33434
max_hops = 30
icmp = socket.getprotobyname('icmp')
udp = socket.getprotobyname('udp')
ttl = 1
while True:
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
recv_socket.bind(("", port))
send_socket.sendto("", (dest_name, port))
curr_addr = None
curr_name = None
try:
_, curr_addr = recv_socket.recvfrom(512)
curr_addr = curr_addr[0]
try:
curr_name = socket.gethostbyaddr(curr_addr)[0]
except socket.error:
curr_name = curr_addr
except socket.error:
pass
finally:
send_socket.close()
recv_socket.close()
if curr_addr is not None:
curr_host = "%s (%s)" % (curr_name, curr_addr)
else:
curr_host = "*"
print "%d\t%s" % (ttl, curr_host)
ttl += 1
if curr_addr == dest_addr or ttl > max_hops:
break
if __name__ == "__main__":
main('google.com')
答案 0 :(得分:3)
不确定为什么你选择scapy(虽然它是好的模块),因为这当然只能使用python。要发送ICMP数据包,只需发送你的recv_socket即可。要发送此套接字,您需要先创建一个ICMP数据包。
但是,您似乎想要通过ICMP套接字发送UDP数据包。这不会像你想象的那样奏效。
首先,让我说明显存在Linux内核的补丁,它允许SOCK_DGRAM使用IPPROTO_ICMP:ICMP sockets (linux)。我没试过这个。
但是,通常情况下,套接字标志的这种组合不起作用。这是因为ICMP套接字需要ICMP头。如果你在recv_socket上发送一个空字符串,与send_socket类似,内核将丢弃该数据包。此外,如果要在ICMP标头上层叠UDP标头,接收系统将仅对收到的ICMP标头作出反应,并将UDP标头视为仅附加到ICMP的数据。事实上,在对您的ICMP回复中,远程系统将您的UDP标头简单地包含在您首先发送给它的数据中。
您能够通过send_socket发送空字符串的原因是因为内核已为您创建了UDP标头,并且您通过该UDP套接字发送的内容只是作为数据附加到UDP标头。它不像ICMP套接字那样。在我写的时候,你需要创建icmp标头来发送这个套接字。
UDP" ping"中发生的事件的现状是这样的:UDP数据包通过UDP套接字发送到远程系统,使用(希望)未打开的端口作为目标端口。这会从远程系统(类型3,代码3)中引发ICMP响应。此时,您将需要一个ICMP注册的套接字来处理ICMP回复,这需要root(或Windows上的管理员)权限。
创建icmp echo标头非常简单:
import struct
icmp = struct.pack(">BBHHH", 8, 0, 0, 0, 0)
icmp = struct.pack(">BBHHH", 8, 0, checksum(icmp), 0, 0)
此标题需要注意几点。首先,参数4和5是"标识符"和"序列号"领域。通常,标识符设置为进程ID,而序列号从1开始,因此您可以跟踪发送到回复的顺序。在这个例子中,我将数字设置为零仅仅是为了说明(尽管如果你不关心它们,保持这种方式是完全正常的。)
其次,请参阅我提到的校验和(icmp)函数?我假设您可以访问执行此操作的代码。它被称为1的补码校验和,并在RFC 1071中得到批准。校验和是至关重要的,因为如果没有正确计算,接收系统可能不会将数据包转发到任何ICMP套接字处理程序。
第三,如果您想知道为什么我最初创建了具有零校验和的标头,那么因为内核基于该字段的校验和结果为零,所以在创建实际校验和并添加回来之前到标题。
因为struct模块处理的是#34; packed"二进制数据,我建议您熟悉位移和位运算符。当你进一步进入原始套接字时,你需要这个,特别是当你需要从可能重叠或不重叠的位字段中提取某些位时。例如,以下是IP标头:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 |Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 | Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 | Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 | Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20 | Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
假设您要在源字段中插入IP地址192.168.1.10,首先必须记下其长度,4个字节。要正确地将其插入到struct(只是这个字段,而不是标题的其余部分,作为示例),您必须:
struct.pack(">I", 192 << 24| 168 << 16| 1 << 8| 10)
这样做会为此字段添加正确的整数,3232235786L。
(有些读者可能会指出struct.pack可以得到相同的结果(&#34;&gt; BBBB&#34;,192,168,1,10)。虽然这适用于这个用例,它通常是不正确的。在这种情况下,它的工作原理是因为IP地址是面向字节的;但是,非字节的字段(例如校验和字段)将失败,因为校验和的结果值大于255也就是说,它应该是一个16位的值。所以不要这样做,即使用协议字段预期的确切位。)
作为学习位移和操作的另一个例子,取IP头的VER字段。这是一个半字节大小的字段,即4比特。为了提取这个,你可以做以下的例子(假设IHL为零):
# Assume ip[] already has a full IP header.
ver = struct.unpack("!B", ip[0])[0]
# This produces the integer 4, which is required for sending IPV4
ver >> 4
简而言之,我已将一个字节的值右移4位。在这个降档中,与旧值相比的新值现在看起来像这样:
# OLD VALUE IN BINARY; value is 64
# 01000000
# NEW VALUE IN BINARY; value is 4
# 00000100
重要的是要注意,如果没有这种转变,检查原始二进制值将导致64,这不是预期的。反之,为了#34;打包&#34;值4进入一个字节的高4位,执行以下操作:
# New value will be 64, or binary 01000000
x = 4 << 4
希望这能指出你正确的方向。
答案 1 :(得分:0)
老问题,但是为了清晰起见又增加了另一点,因为我最近不得不这样做。
您可以使用套接字编写ICMP traceroute的本机Python版本。要解决的两个重要问题是:
对于第一个问题,在pyping模块中有一个很好的例子,它可以很好地工作;它是我在编写traceroute实用程序时使用的。
对于第二个,它很简单:
current_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW,
socket.getprotobyname("icmp"))
current_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)
其中&#39; ttl&#39;是您正在设置的TTL的整数值
然后,只需要执行发送/接收并观察返回数据包中的类型/代码,在控制结构中增加TTL。
我在已经在pyping模块中编写的内容之后对我的头文件包/解压缩建模;只需查找标题类型= 11,代码0表示中间跃点,标题类型= 0表示当您到达目的地时。
Scapy工作正常,但速度较慢,是一种额外的外部依赖。我能够在一个下午用AS查找(通过RADb)和地理定位编写一个强大的traceroute,只使用套接字。
答案 2 :(得分:-2)
结束了使用scapy编写自己的内容,因为这是不可能的。