-优化了代码解释的结构和内容,使其更加清晰和详细 - 增加了对 DNS 查询报文构造的解释 -改进了对 IP 头部和 TCP 报文的解释 - 调整了部分术语和描述,使其更符合技术文档的标准
197 lines
8.5 KiB
Markdown
197 lines
8.5 KiB
Markdown
这段代码实现了一个基本的网络通信工具,允许用户通过图形用户界面(GUI)构造并发送不同类型的网络报文(如ICMP、UDP、TCP、DNS、IP)。让我们逐部分深入分析代码的实现原理和工作方式。
|
||
|
||
### 1. **导入模块**
|
||
```python
|
||
import tkinter as tk
|
||
from tkinter import ttk, messagebox
|
||
import socket
|
||
import struct
|
||
import time
|
||
import uuid
|
||
import re
|
||
import random
|
||
```
|
||
- **tkinter**: 用于构建GUI,提供创建窗口、标签、按钮等界面元素的功能。
|
||
- **socket**: 用于低层次的网络操作,如创建套接字、发送和接收数据等。
|
||
- **struct**: 用于处理二进制数据的打包和解包,例如网络数据报文的处理。
|
||
- **time**: 用于获取时间戳、计算延迟等。
|
||
- **uuid**: 用于获取唯一的标识符,特别是获取本机的MAC地址。
|
||
- **re**: 用于正则表达式,主要用来验证输入的IP地址或域名的格式。
|
||
- **random**: 用于生成随机数,这里主要用在构造DNS查询报文时随机生成事务ID。
|
||
|
||
### 2. **获取本机信息**
|
||
```python
|
||
def get_local_info():
|
||
hostname = socket.gethostname()
|
||
local_ips = socket.gethostbyname_ex(hostname)[-1]
|
||
mac = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
|
||
return hostname, mac, local_ips
|
||
```
|
||
- **`socket.gethostname()`**: 获取当前计算机的主机名。
|
||
- **`socket.gethostbyname_ex(hostname)[-1]`**: 根据主机名获取本机所有的IP地址(IPv4),返回的列表中的最后一个元素包含了所有本机的IP。
|
||
- **`uuid.getnode()`**: 获取计算机的硬件地址(MAC地址)。返回的是一个64位的数字,我们通过格式化成16进制,并使用正则表达式将其格式化为常见的MAC地址格式(如:`00:1A:2B:3C:4D:5E`)。
|
||
|
||
### 3. **验证IP地址或域名**
|
||
```python
|
||
def validate_ip_or_domain(value):
|
||
ip_pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$')
|
||
domain_pattern = re.compile(r'^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$')
|
||
return ip_pattern.match(value) or domain_pattern.match(value)
|
||
```
|
||
- 该函数通过正则表达式检查用户输入的值是否符合IP地址或域名的格式。
|
||
- **IP地址格式**: `\d{1,3}(\.\d{1,3}){3}` 匹配四个以点分隔的1到3位数字。
|
||
- **域名格式**: `([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}` 匹配由字母、数字或中划线组成的域名部分和一个或多个子域名部分。
|
||
|
||
### 4. **验证端口号**
|
||
```python
|
||
def validate_port(port):
|
||
return port.isdigit() and 1 <= int(port) <= 65535
|
||
```
|
||
- 该函数检查端口号是否在1到65535的范围内,确保用户输入的是有效的端口号。
|
||
|
||
### 5. **构造IP头部**
|
||
```python
|
||
def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
|
||
version = 4
|
||
ihl = 5
|
||
tos = 0
|
||
total_length = 20 + payload_length # 20字节的IP头部 + 数据部分长度
|
||
identification = 54321
|
||
flags_offset = 0
|
||
ttl = 64
|
||
header_checksum = 0
|
||
|
||
src_ip_bytes = socket.inet_aton(src_ip) # 转换IP地址为二进制格式
|
||
dest_ip_bytes = socket.inet_aton(dest_ip) # 转换目标IP为二进制格式
|
||
ip_header = struct.pack('!BBHHHBBH4s4s',
|
||
(version << 4) + ihl, tos, total_length,
|
||
identification, flags_offset, ttl, protocol,
|
||
header_checksum, src_ip_bytes, dest_ip_bytes)
|
||
|
||
return ip_header
|
||
```
|
||
- 该函数用于构造IP头部,按网络字节序(大端字节序)格式将各种字段打包。
|
||
- **`struct.pack`**: 用于将数据按指定格式打包成二进制数据。格式`'!BBHHHBBH4s4s'`表示:
|
||
- `!`:大端字节序。
|
||
- `B`:一个字节(8位),表示一个无符号整数。
|
||
- `H`:两个字节(16位),表示一个无符号整数。
|
||
- `4s`:4个字节的字符串(即4个字符的IP地址)。
|
||
- **字段说明**:
|
||
- **版本(version)**: 使用IPv4协议,版本号为4。
|
||
- **头部长度(ihl)**: 5表示头部长度为5个32位字(即20字节)。
|
||
- **总长度(total_length)**: 包括IP头部和数据部分的总长度。
|
||
- **TTL(ttl)**: 存活时间(Time To Live),默认为64。
|
||
- **协议(protocol)**: 用于指示数据部分使用的协议类型,如TCP、UDP、ICMP等。
|
||
|
||
### 6. **构造ICMP报文**
|
||
```python
|
||
def construct_icmp():
|
||
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
|
||
```
|
||
- **ICMP报文**用于网络诊断(例如Ping)。
|
||
- **`struct.pack('!BBHHH', 8, 0, 0, 1, 1)`**构造一个ICMP头部。`8`表示回显请求类型,`0`表示无错误,`checksum`和`seq`等字段在后续计算和填充。
|
||
- **`calculate_checksum`**: 计算ICMP报文的校验和,确保数据在传输过程中没有被篡改。
|
||
- **数据部分**: 这里简单地将“Ping”作为数据发送,实际应用中可以是更复杂的内容。
|
||
|
||
### 7. **构造UDP报文**
|
||
```python
|
||
def construct_udp(src_port, dest_port):
|
||
udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
|
||
return udp_header
|
||
```
|
||
- 构造UDP头部,包含源端口、目标端口、长度和校验和。这里暂时将校验和置为0。
|
||
|
||
### 8. **构造TCP报文(SYN)**
|
||
```python
|
||
def construct_tcp(src_port, dest_port):
|
||
seq = 0
|
||
ack_seq = 0
|
||
offset = 5
|
||
reserved = 0
|
||
flags = 0b000010 # SYN标志位为1,表示建立连接
|
||
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
|
||
```
|
||
- **SYN报文**用于TCP的三次握手,建立连接时发送。
|
||
- **`flags = 0b000010`**表示SYN标志为1。
|
||
- **`window`**: 接收窗口大小。
|
||
- **`seq`**和**`ack_seq`**: TCP的序列号和确认号,初始化为0。
|
||
|
||
### 9. **计算校验和**
|
||
```python
|
||
def calculate_checksum(data):
|
||
checksum = 0
|
||
n = len(data)
|
||
|
||
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
|
||
|
||
checksum = (checksum >> 16) + (checksum & 0xFFFF)
|
||
checksum += (checksum >> 16)
|
||
|
||
return ~checksum & 0xFFFF
|
||
```
|
||
- 该函数计算数据的校验和,主要用于检测数据在传输过程中是否发生了错误。
|
||
- 将数据拆分为16位块并求和。
|
||
- 如果数据长度为奇数,则补零。
|
||
- 最后对结果取反,并返回16位校验和。
|
||
|
||
### 10. **构造DNS查询报文**
|
||
```python
|
||
def build_dns_query(domain):
|
||
|
||
|
||
# 随机生成事务ID
|
||
txid = random.randint(0, 65535)
|
||
flags = 0x0100 # 查询标志
|
||
qdcount = 1 # 查询数量
|
||
ancount = 0 # 回答数量
|
||
nscount = 0 # 权威服务器数量
|
||
arcount = 0 # 附加记录数量
|
||
|
||
query = struct.pack('>HHHHHH', txid, flags, qdcount, ancount, nscount, arcount)
|
||
query += encode_domain(domain)
|
||
query += struct.pack('>HH', 1, 1) # 类型为A记录(IPv4地址)
|
||
|
||
return query
|
||
```
|
||
- **`build_dns_query`**构造DNS查询报文,查询给定域名的A记录(即返回IPv4地址)。
|
||
- **`encode_domain(domain)`**: 将域名按照DNS协议格式编码,域名分段,每段前面加上该段的长度。
|
||
- 事务ID(txid)随机生成,查询的标志位设置为标准查询。
|
||
|
||
### 11. **发送报文**
|
||
```python
|
||
def send_packet():
|
||
src_ip = ip_combobox.get()
|
||
dest_host = dest_entry.get()
|
||
dest_port = port_entry.get()
|
||
packet_type = var.get()
|
||
```
|
||
- 该函数会根据用户在GUI中输入的源IP地址、目标地址、目标端口和报文类型(ICMP、UDP、TCP、DNS、IP)来发送报文。具体的报文构造和发送过程,会根据不同类型的报文选择相应的构造函数。
|
||
|
||
### 12. **GUI界面设计与事件循环**
|
||
```python
|
||
window = tk.Tk()
|
||
window.title("网络通信软件")
|
||
```
|
||
- 使用**tkinter**创建GUI窗口,设置窗口标题和布局。
|
||
- 通过**`Label`**、**`Entry`**、**`Button`**等控件让用户输入本机IP、目标地址、端口和报文类型。
|
||
- 通过**`Button`**控件触发发送报文的功能,调用 `send_packet()`。
|
||
|
||
**总结:**
|
||
这段代码的核心功能是通过GUI界面实现用户自定义网络报文的构造、发送和反馈。主要操作包括获取本机信息、验证输入数据、根据不同报文类型(如ICMP、TCP、UDP、DNS)构造报文,最后通过`socket`库将其发送到指定目标。 |