python 中的 ICMP 数据包是如何构造的

How is an ICMP packet constructed in python

为了学习,我目前正在尝试创建一个简单的 python 程序来向某些设备发送 ICMP ping 数据包。为了开始,我查看了 python 模块 Pyping 的源代码:https://github.com/Akhavi/pyping/blob/master/pyping/core.py

我试图了解发送和构建数据包时发生的所有事情,但是我设法卡在了代码的一部分,似乎无法弄清楚它的功能和用途。我一直在研究 ICMP 数据包,我知道它们包含 类型代码校验和和数据 现在让我困惑的代码是:

    self.own_id = os.getpid() & 0xFFFF

    header = struct.pack(
        "!BBHHH", ICMP_ECHO, 0, checksum, self.own_id, self.seq_number
    )

    padBytes = []
    startVal = 0x42
    for i in range(startVal, startVal + (self.packet_size)):
        padBytes += [(i & 0xff)]  # Keep chars in the 0-255 range
    data = bytes(padBytes)

我的问题是:

  1. header加上self.own_id和self.seq_number有什么用?
  2. for-loop 中正在计算什么,为什么它的起始值为 0x42?

我是网络新手,非常感谢任何帮助。

使用 scapy http://www.secdev.org/projects/scapy/ !

Scapy 是用 python 编写的数据包操作框架。 您可以伪造很多类型的数据包(http、tcp、ip、udp、icmp 等...)

ICMP 回显请求和回显回复通常称为 ping。

ID 和序列号允许将回复与请求相匹配。它们是必需的,因为 ICMP 协议没有像 TCP 和 UDP 那样的源和目标端口,只有源和目标 IP 地址。多个进程可以 ping 同一台主机,并且必须将答复传递给正确的进程。因此,每个回复都包含从相应请求中逐字复制的数据。 ID 标识发送请求的进程。序列号有助于将回复与流程中的请求相匹配。这是计算 RTT(round-trip 时间)和检测未应答的 ping 所必需的。


在循环中计算的数据是有效负载,它也是从请求到回复的字面上复制的。有效负载是可选的,ping 实现可以根据需要使用它。


为什么是 0x42?我猜作者可能是道格拉斯·亚当斯的粉丝。

ICMP Echo Request 数据包描述

ICMP Echo Request PDU 看起来像这样:

    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
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Type(8)   |     Code(0)   |          Checksum             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Identifier          |        Sequence Number        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             Payload                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

下面是来自上面维基 link 的各个字段的描述:

The Identifier and Sequence Number can be used by the client to match the reply with the request that caused the reply.

In practice, most Linux systems use a unique identifier for every ping process, and sequence number is an increasing number within that process. Windows uses a fixed identifier, which varies between Windows versions, and a sequence number that is only reset at boot time.

pyping 代码的描述

Header代

查看 send_one_ping 的完整函数 body,这是您的代码来源。我将用一些信息对其进行注释:

def send_one_ping(self, current_socket):
    """
    Send one ICMP ECHO_REQUEST
    """
    # Header is type (8), code (8), checksum (16), id (16), sequence (16)

    # Annotation: the Type is 8 bits, the code is 8 bits, the
    # header checksum is 16 bits

    # Additional Header Information is 32-bits (identifier and sequence number)

    # After that is Payload, which is of arbitrary length.

所以这一行

    header = struct.pack(
        "!BBHHH", ICMP_ECHO, 0, checksum, self.own_id, self.seq_number
    )

此行使用 struct 和布局 !BBHHH 创建数据包 header,这意味着:

  • B - 无符号字符(8 位)
  • B - 无符号字符(8 位)
  • H - 无符号短整型(16 位)
  • H - 无符号短整型(16 位)
  • H - 无符号短整型(16 位)

因此 header 将如下所示:

    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
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     ICMP_ECHO  |     0        |          checksum             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           self.own_id         |        self.seq_number        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

请注意:

  • self.own_id 设置发送此数据的应用程序的标识符。对于此代码,它仅使用程序的程序标识符编号。
  • self.seq_number 设置序号。如果您要连续发送多个,这有助于您识别这是哪个 ICMP 请求数据包。它会帮助你做一些事情,比如计算 ICMP 数据包丢失。

客户端可以使用标识符和序列号字段组合来将回显回复与回显请求相匹配。

有效载荷生成

现在让我们继续讨论有效负载部分。有效载荷是任意长度的,但是 Ping class 此代码是从默认值到 55 bytes.

的总数据包有效载荷大小

所以下面的部分只是创建了一堆任意字节来填充到有效负载部分。

padBytes = []
startVal = 0x42

# Annotation: 0x42 = 66 decimal
# This loop would go from [66, 66 + packet_size],
# which in default pyping means [66, 121)
for i in range(startVal, startVal + (self.packet_size)):
    padBytes += [(i & 0xff)]  # Keep chars in the 0-255 range
data = bytes(padBytes)

最后,byte(padBytes)实际上是这样的:

>> bytes(padBytes)
b'BCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwx'

为什么选择 0x42

据我所知,0x42 作为有效载荷标识符没有实际意义,因此这似乎相当武断。这里的有效载荷实际上是毫无意义的。从 Payload Generation 部分可以看出,它只是生成一个连续的序列,实际上没有任何意义。如果他们愿意,他们本可以决定用 0x42 字节填充整个数据包有效负载。