docs: 添加原理解释和校验和计算文档

- 新增原理解释文档,详细说明了 ICMP、UDP 和 TCP 报文的构建与发送原理
- 新增校验和计算文档,提供了正确的 ICMP 校验和计算方法
- 这些文档有助于开发者更好地理解网络报文的构建和发送过程
This commit is contained in:
fly6516 2025-01-06 15:07:59 +08:00
parent 0a2ace4916
commit 71a8e81f68
3 changed files with 172 additions and 0 deletions

11
main.py
View File

@ -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
# 发送报文

108
原理解释.md Normal file
View File

@ -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 报文构建与发送**
ICMPInternet 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 报文构造包括序列号、确认号和标志位等。
通过使用不同的协议和套接字类型,我们可以在网络中发送各种类型的数据包。

53
校验和解释.md Normal file
View File

@ -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 报文具有正确的校验和,从而能够在网络中正常解析和传输。