2012-04-04 63 views
0

我想在Python中实现基于ICMP的Traceroute。我发现了一个非常有用的指南(https://blogs.oracle.com/ksplice/entry/learning_by_doing_writing_your),它允许我创建基于UDP的Traceroute,因此只需要修改即可。不过,我环顾四周,无法更改发送套接字并使其工作。有人能帮助我吗?在Python中创建ICMP跟踪路由

#!/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

它是**接收**套接字,如果您不以root身份运行,会导致“操作不允许”。 – 2012-04-04 09:18:18

+0

哪个平台在运行?其实你提到的文章提到'因为原始套接字需要root权限,所以traceroute通常是setuid。对于我们的目的,我们可以以root身份运行脚本:'。但是,它看起来像在RHEL5上,'traceroute'不是setuid root(参见http://traceroute.sourceforge.net/) – 2012-04-04 09:29:33

+0

是在Ubuntu上运行并以root身份运行,没有运行问题的问题是在运行时使用语法 – Jamesla 2012-04-04 11:31:53

回答

-2

结束了写我自己使用scapy,因为这是不可能的。

+0

请帮助别人,即使你使用了另一个库(scapy)。 – KillianDS 2012-11-09 12:25:12

2

不知道你为什么选择scapy(虽然它是很好的模块),因为这当然可以使用python。要发送一个ICMP数据包,你只需发送你的recv_socket。为了发出这个套接字,你需要先创建一个ICMP包。

但是,你似乎想要通过ICMP套接字发送UDP数据包。这不会像你想象的那样工作。

首先,让我明白地说,Linux内核中存在一个补丁,它将允许SOCK_DGRAM和IPPROTO_ICMP:ICMP sockets (linux)。我没有测试过这个。

虽然,这种套接字标志的组合不起作用。这是因为ICMP套接字需要ICMP头。如果要发送类似于send_socket的空字符串,则内核将丢弃该数据包。此外,如果要在UDP头部上覆盖一个ICMP头部,接收系统只会对收到的ICMP头部做出反应,并将UDP头部视为仅附加到ICMP的数据。事实上,在它的ICMP回复中,远程系统将首先将你的UDP头部包含在你发送给它的数据中。

你能在send_socket发送一个空字符串的原因是因为内核创造了UDP报头的你,和你发送了该UDP套接字被简单地追加为数据的UDP报头。这不像ICMP套接字那样。正如我写的,你需要创建icmp头在这个套接字上发送。

的一个UDP的“ping”会发生什么的现状是这样的:一个UDP分组是通过UDP套接字到远程系统发送的,使用(希望)未开封口作为目的端口。这会从远程系统中获取ICMP响应(类型3,代码3)。此时,您将需要一个ICMP注册的套接字来处理ICMP回复,这需要root(或Windows上的管理员)权限。

要创建一个ICMP报头是很容易的:

import struct 
icmp = struct.pack(">BBHHH", 8, 0, 0, 0, 0) 
icmp = struct.pack(">BBHHH", 8, 0, checksum(icmp), 0, 0) 

几件事情要与这个头注意。首先,参数四和五是“标识符”和“序列号”字段。通常,标识符被设置为进程ID,而序列号从1开始,因此您可以跟踪发送到回复的序列。在这个例子中,我将数字设置为零仅仅是为了说明(尽管如果不关心它们,以这种方式保持它是完全正常的)。

其次,请参阅我提到的校验和(icmp)函数?我假设你有权访问这样做的代码。这是被称为1的补码校验,并根据RFC 1071校验和是至关重要的,因为批准,而不会被计算正确,接收系统可能无法将数据包转发到任何ICMP套接字句柄。第三,如果您想知道为什么我最初创建的校验和为零,那是因为内核在校验和结果为零的情况下,在实际校验和被创建并添加回头部之前。

由于结构模块处理所谓的“打包”二进制数据,我建议您熟悉位移和位运算符。当你进一步进入原始套接字时,你将需要这些,特别是当你需要从可能或不可能重叠的位字段中提取某些位时。例如,下面是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.pack(">I", 192 << 24| 168 << 16| 1 << 8| 10) 

这样做增加了对这一领域,3232235786L适当的整数。 (有些读者可能会指出,struct.pack(“> BBBB”,192,168,1,10)可能会有相同的结果。虽然这可以用于这个用例,但它在一般情况下是不正确的在这种情况下,它的工作原理是因为IP地址是面向字节的;但是,不是面向字节的字段(如校验和字段)将失败,因为校验和的结果值大于255,也就是说,所以一般不要这样做,即使用协议字段期望的确切位)。

作为学习位移和操作的另一个例子,取VER字段的IP头。这是一个半字节大小的字段,即4位。为了提取这一点,你会做以下为例(假设国际人道法是零):

# 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,这不是什么很重要预期。对于逆,以“包”的值4到一个字节中的高4位,这样做:

# New value will be 64, or binary 01000000 
x = 4 << 4 

希望这点你在正确的方向。

0

老问题,但增加了另一点清晰,因为我最近不得不这样做。

您可以使用套接字编写本地Python版本的ICMP跟踪路由。要解决的两个重要问题是:

  1. 创建ICMP报头与正确的校验(如尤金上述规定)
  2. 设置插座的使用sockopts

对于第一个问题的TTL,在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) 

其中“TTL”是你设置

的TTL的整数值,然后,它只是一个做的SEND/RECV的事并在一个增加TTL的控制结构中观察返回数据包中的类型/代码。

我在pyping模块中已经写入的东西之后模拟了我的头文件包/解包;只需查找标题类型= 11,中间跳转代码为0,到达目的地时标题类型= 0。

Scapy工作正常,但速度较慢,并且是一个额外的外部依赖项。我能够在一个下午用AS查找(通过RADb)和地理位置编写健壮的traceroute,只使用套接字。