【问题标题】:Creating an ICMP traceroute in Python在 Python 中创建 ICMP 跟踪路由
【发布时间】:2012-04-04 09:10:51
【问题描述】:

我正在尝试在 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')

【问题讨论】:

  • 它是 receiving 套接字,如果你不以 root 身份运行它会导致 operation not permitted
  • 你在哪个平台上运行这个?实际上你提到的文章提到了Because raw sockets require root privileges, traceroute is typically setuid. For our purposes, we can just run the script as root:。但是,它看起来像例如在 RHEL5 上,traceroute 不是 setuid root(另请参阅 traceroute.sourceforge.net
  • 是的,在 Ubuntu 上运行并以 root 身份运行,运行它没有问题,问题在于运行时的语法
  • 您可以发布您收到的错误消息吗?当我之前尝试过该脚本时,它对我来说完美无缺(在 Ubuntu 上以 root 身份)。 -- 我刚刚注意到您在#! 之前有一个空格,您运行的脚本中是否也有这个空格(它应该在那里)?
  • 是的,这个脚本工作正常,但是它在发送套接字上发送一个 UDP 数据包,而不是一个 ICMP 数据包。我需要将发送套接字更改为 ICMP。我在下面做了一些修改,但是 socket.setsockopts() 给出了错误 send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)。

标签: python sockets udp icmp traceroute


【解决方案1】:

不知道你为什么选择 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 套接字发送到远程系统,使用(希望)未打开的端口作为目标端口。这会引发来自远程系统的 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 套接字处理程序。

第三,如果您想知道为什么我最初创建带有零校验和的标头,这是因为内核在创建实际校验和并将其添加回标头之前基于该字段为零的校验和结果。

由于 struct 模块处理所谓的“打包”二进制数据,我建议您熟悉位移位和位运算符。当您进一步进入原始套接字时,您将需要这个,特别是当您需要从可能重叠或可能不重叠的位字段中提取某些位时。例如下面是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,也就是说,它应该是一个 16 位的值。所以一般不要这样做,即使用协议字段期望的确切位。)

作为学习位移和操作的另一个例子,以IP头的VER字段为例。这是一个半字节大小的字段,即 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

希望这会为您指明正确的方向。

【讨论】:

    【解决方案2】:

    老问题,但为了清楚起见,我最近不得不这样做。

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

    1. 使用正确的校验和创建 ICMP 标头(如 Eugene 上面所述)
    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 的整数值

    然后只需在增加 TTL 的控制结构中执行发送/接收并观察返回数据包中的类型/代码。

    我根据 pyping 模块中已经编写的内容建模了我的头文件包/解包;只需查找标头类型 = 11,中间跃点代码为 0,标头类型 = 0 表示您到达目的地时。

    Scapy 工作正常,但速度较慢,并且是一个额外的外部依赖项。我能够在一个下午使用 AS-lookup(通过 RADb)和地理定位编写一个强大的跟踪路由,只使用套接字。

    【讨论】:

      【解决方案3】:

      最终使用 scapy 编写自己的代码,因为这是不可能的。

      【讨论】:

      • 请通过展示你最终如何解决它来帮助其他人,即使你使用了另一个库(scapy)。
      猜你喜欢
      • 2021-03-05
      • 1970-01-01
      • 2018-01-21
      • 2020-07-24
      • 1970-01-01
      • 1970-01-01
      • 2018-08-30
      • 1970-01-01
      • 2011-03-29
      相关资源
      最近更新 更多