docs: 添加原理解释和校验和计算文档
- 新增原理解释文档,详细说明了 ICMP、UDP 和 TCP 报文的构建与发送原理 - 新增校验和计算文档,提供了正确的 ICMP 校验和计算方法 - 这些文档有助于开发者更好地理解网络报文的构建和发送过程
This commit is contained in:
parent
0a2ace4916
commit
71a8e81f68
11
main.py
11
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
|
||||
|
||||
# 发送报文
|
||||
|
108
原理解释.md
Normal file
108
原理解释.md
Normal 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 报文构建与发送**
|
||||
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 报文构造包括序列号、确认号和标志位等。
|
||||
|
||||
通过使用不同的协议和套接字类型,我们可以在网络中发送各种类型的数据包。
|
53
校验和解释.md
Normal file
53
校验和解释.md
Normal 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 报文具有正确的校验和,从而能够在网络中正常解析和传输。
|
Loading…
Reference in New Issue
Block a user