- 新增原理解释文档,详细说明了 ICMP、UDP 和 TCP 报文的构建与发送原理 - 新增校验和计算文档,提供了正确的 ICMP 校验和计算方法 - 这些文档有助于开发者更好地理解网络报文的构建和发送过程
5.9 KiB
5.9 KiB
在这段代码中,网络报文(包)的构建与发送是核心功能之一。包的构建涉及到如何通过原始套接字(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
请求的报文结构。
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 头部。
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 报文。
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 原始套接字
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 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
SOCK_DGRAM
:表示数据报套接字,适用于无连接的协议(如 UDP)。
5.3 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SOCK_STREAM
:表示流式套接字,适用于面向连接的协议(如 TCP)。
6. 报文发送
报文发送的代码如下:
sock.sendto(packet, (dest_ip, dest_port)) # 对于 UDP 和 ICMP 使用 sendto
sendto()
用于发送 UDP 和 ICMP 类型的报文,目标地址为(dest_ip, dest_port)
。- 对于 TCP,使用
sock.connect()
先建立连接,然后使用sock.sendall()
发送数据:
sock.connect((dest_ip, dest_port))
packet = b"Hello, TCP!" # TCP 数据
sock.sendall(packet)
7. 总结
- ICMP:通过原始套接字构建和发送 ICMP 包,包含头部、数据和校验和。
- UDP:使用 UDP 套接字发送数据,构造简单的 UDP 头部,目标端口和数据发送即可。
- TCP:通过 TCP 套接字建立连接,然后发送数据。TCP 报文构造包括序列号、确认号和标志位等。
通过使用不同的协议和套接字类型,我们可以在网络中发送各种类型的数据包。