From 71a8e81f68cd1ea15b2791c0f6bc78a371b5abf5 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Mon, 6 Jan 2025 15:07:59 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E5=8E=9F=E7=90=86?= =?UTF-8?q?=E8=A7=A3=E9=87=8A=E5=92=8C=E6=A0=A1=E9=AA=8C=E5=92=8C=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增原理解释文档,详细说明了 ICMP、UDP 和 TCP 报文的构建与发送原理 - 新增校验和计算文档,提供了正确的 ICMP 校验和计算方法 - 这些文档有助于开发者更好地理解网络报文的构建和发送过程 --- main.py | 11 +++++ 原理解释.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ 校验和解释.md | 53 +++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 原理解释.md create mode 100644 校验和解释.md diff --git a/main.py b/main.py index ad66824..5967c8e 100644 --- a/main.py +++ b/main.py @@ -87,9 +87,12 @@ def construct_icmp(): Returns: bytes: 构造的ICMP报文。 """ + # ICMP 报文头部: 类型(8), 代码(0), 校验和(0), 标识符(1), 序列号(1) icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) data = b'Ping' + # 计算校验和 checksum = calculate_checksum(icmp_header + data) + # 填充校验和字段 icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1) return icmp_header + data @@ -148,13 +151,21 @@ def calculate_checksum(data): """ checksum = 0 n = len(data) + + # 按 16 位块划分 for i in range(0, n - 1, 2): chunk = (data[i] << 8) + data[i + 1] checksum += chunk + + # 如果长度为奇数,补零 if n % 2 == 1: checksum += data[-1] << 8 + + # 将 32 位总和折叠为 16 位 checksum = (checksum >> 16) + (checksum & 0xFFFF) checksum += (checksum >> 16) + + # 对总和取反 return ~checksum & 0xFFFF # 发送报文 diff --git a/原理解释.md b/原理解释.md new file mode 100644 index 0000000..1837eb5 --- /dev/null +++ b/原理解释.md @@ -0,0 +1,108 @@ +在这段代码中,网络报文(包)的构建与发送是核心功能之一。包的构建涉及到如何通过原始套接字(Raw Socket)或标准套接字(如 TCP 和 UDP)在网络上传输数据。以下是每种类型的包构建与发送的详细原理: + +### 1. **原始套接字与协议** +- **原始套接字(Raw Socket)** 允许程序直接处理网络层数据包(例如 IP 层),并且能够通过指定协议(如 ICMP)与数据链路层进行交互。 +- **UDP/TCP 套接字** 是应用层与传输层(例如 UDP、TCP)之间的接口,程序通过它们发送数据,并且协议栈自动处理底层的传输细节(例如 IP 包封装)。 + +#### 1.1 **原始套接字(Raw Socket)构建与使用** +对于 `ICMP` 和其他原始协议(如自定义的 IP 包),使用原始套接字 `socket.AF_INET` 和 `socket.SOCK_RAW` 进行数据包的构建和发送。 + +### 2. **ICMP 报文构建与发送** +ICMP(Internet Control Message Protocol)用于网络诊断(如 `ping` 操作)。ICMP 报文通常包含类型、代码和校验和。这里的 ICMP 报文格式是 **Echo 请求**,即 `ping` 请求的报文结构。 + +```python +def construct_icmp(): + icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) # 构造 ICMP 头部 + data = b'Ping' # 载荷部分 + checksum = calculate_checksum(icmp_header + data) # 计算校验和 + icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1) # 使用校验和更新头部 + return icmp_header + data # 返回完整的 ICMP 报文 +``` +- **构造 ICMP 头部**: 使用 `struct.pack` 将 ICMP 头部(类型、代码、校验和等)打包为二进制格式。类型 `8` 代表 Echo 请求(Ping)。 +- **数据部分**: 简单的数据部分为 `b'Ping'`,可以是任何要发送的数据。 +- **校验和**: 校验和是通过对 ICMP 头部和数据部分的所有字节进行计算,确保数据传输的完整性。 + +### 3. **UDP 报文构建与发送** +UDP 是无连接的传输层协议,不会进行可靠性保证,因此它相对简单。每个 UDP 包由 **源端口、目标端口、长度** 和 **校验和** 组成。这里只构造了一个简单的 UDP 头部。 + +```python +def construct_udp(src_port, dest_port): + udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0) + return udp_header +``` +- **构造 UDP 头部**: `src_port` 和 `dest_port` 是源端口和目标端口,`8` 是 UDP 数据报的长度(通常为 8 字节),`0` 是校验和(暂时置为 0,实际传输中会根据数据计算)。 +- **校验和**: 在 UDP 中,校验和是可选的,但通常建议使用。对于 UDP 校验和,若无使用 IPv4,校验和字段通常置为 0;若使用 IPv6,则校验和是必需的。 + +### 4. **TCP 报文构建与发送** +TCP 是面向连接的协议,需要通过三次握手进行建立连接,并且可以确保数据可靠传输。这里构造了一个带有 **SYN** 标志的 TCP 报文。 + +```python +def construct_tcp(src_port, dest_port): + seq = 0 + ack_seq = 0 + offset = 5 + reserved = 0 + flags = 0b000010 # SYN flag + window = socket.htons(5840) + checksum = 0 + urgent_ptr = 0 + + tcp_header = struct.pack('!HHLLBBHHH', + src_port, dest_port, seq, ack_seq, + (offset << 4) + reserved, flags, window, + checksum, urgent_ptr) + return tcp_header +``` +- **源端口和目标端口**: `src_port` 和 `dest_port` 是 TCP 连接的端口。 +- **序列号** (`seq`) 和 **确认号** (`ack_seq`): 对于建立连接的第一次 SYN 报文,序列号和确认号通常为 0。 +- **数据偏移(Offset)**: 通常为 5,表示头部长度(每个 TCP 头部的最小长度为 20 字节)。 +- **标志位(Flags)**: 在此,设置了 SYN 标志(`0b000010`)来表示这是一个连接请求。 +- **窗口大小**: 用于流量控制,表示接收方缓冲区的大小。 +- **校验和和紧急指针**: 校验和和紧急指针在这里暂时为 0,实际上会通过 `calculate_checksum()` 函数进行校验。 + +### 5. **套接字的创建与使用** +套接字是操作系统提供的 API,用于在不同的计算机之间传输数据。代码中根据不同的协议使用不同类型的套接字。 + +#### 5.1 **原始套接字** +```python +sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) +``` +- `AF_INET`:表示 IPv4 地址族(如果是 IPv6 地址族,则为 `AF_INET6`)。 +- `SOCK_RAW`:表示使用原始套接字,允许发送和接收原始 IP 数据包。 +- `IPPROTO_ICMP`:指定使用 ICMP 协议。 + +对于 **UDP** 和 **TCP** 套接字,创建的方法分别是: + +#### 5.2 **UDP 套接字** +```python +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +``` +- `SOCK_DGRAM`:表示数据报套接字,适用于无连接的协议(如 UDP)。 + +#### 5.3 **TCP 套接字** +```python +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +``` +- `SOCK_STREAM`:表示流式套接字,适用于面向连接的协议(如 TCP)。 + +### 6. **报文发送** +报文发送的代码如下: + +```python +sock.sendto(packet, (dest_ip, dest_port)) # 对于 UDP 和 ICMP 使用 sendto +``` +- `sendto()` 用于发送 UDP 和 ICMP 类型的报文,目标地址为 `(dest_ip, dest_port)`。 +- 对于 **TCP**,使用 `sock.connect()` 先建立连接,然后使用 `sock.sendall()` 发送数据: + +```python +sock.connect((dest_ip, dest_port)) +packet = b"Hello, TCP!" # TCP 数据 +sock.sendall(packet) +``` + +### 7. **总结** +- **ICMP**:通过原始套接字构建和发送 ICMP 包,包含头部、数据和校验和。 +- **UDP**:使用 UDP 套接字发送数据,构造简单的 UDP 头部,目标端口和数据发送即可。 +- **TCP**:通过 TCP 套接字建立连接,然后发送数据。TCP 报文构造包括序列号、确认号和标志位等。 + +通过使用不同的协议和套接字类型,我们可以在网络中发送各种类型的数据包。 \ No newline at end of file diff --git a/校验和解释.md b/校验和解释.md new file mode 100644 index 0000000..64c1b4a --- /dev/null +++ b/校验和解释.md @@ -0,0 +1,53 @@ + + +为了正确地计算 ICMP 报文的校验和 (checksum),我们需要遵循以下步骤: + +1. **填充报文头部的校验和字段为 `0`**。 +2. **将报文数据划分为 16 位块并求和**。 +3. **如果报文长度为奇数,在末尾补零**。 +4. **对总和取反,并将其结果存入校验和字段**。 + +以下是修改后的代码,增加了正确的 ICMP 校验和计算: + +```python +# 计算校验和 +def calculate_checksum(data): + checksum = 0 + n = len(data) + + # 按 16 位块划分 + for i in range(0, n - 1, 2): + chunk = (data[i] << 8) + data[i + 1] + checksum += chunk + + # 如果长度为奇数,补零 + if n % 2 == 1: + checksum += data[-1] << 8 + + # 将 32 位总和折叠为 16 位 + checksum = (checksum >> 16) + (checksum & 0xFFFF) + checksum += (checksum >> 16) + + # 对总和取反 + return ~checksum & 0xFFFF + + +# 构造 ICMP 报文 +def construct_icmp(): + # ICMP 报文头部: 类型(8), 代码(0), 校验和(0), 标识符(1), 序列号(1) + icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) + data = b'Ping' + # 计算校验和 + checksum = calculate_checksum(icmp_header + data) + # 填充校验和字段 + icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1) + return icmp_header + data +``` + +### 说明 +- `calculate_checksum` 函数负责按 ICMP 的要求计算校验和。 +- 数据部分(例如 `"Ping"`)被添加到报文头后计算校验和。 +- 构造的 ICMP 报文将包含报文头和数据部分。 + +### 使用方法 +使用这个改进后的 `construct_icmp` 函数可以确保生成的 ICMP 报文具有正确的校验和,从而能够在网络中正常解析和传输。 \ No newline at end of file