Python-web-communicate/原理解释.md
fly6516 71a8e81f68 docs: 添加原理解释和校验和计算文档
- 新增原理解释文档,详细说明了 ICMP、UDP 和 TCP 报文的构建与发送原理
- 新增校验和计算文档,提供了正确的 ICMP 校验和计算方法
- 这些文档有助于开发者更好地理解网络报文的构建和发送过程
2025-01-06 15:07:59 +08:00

5.9 KiB
Raw Permalink Blame History

在这段代码中网络报文的构建与发送是核心功能之一。包的构建涉及到如何通过原始套接字Raw Socket或标准套接字如 TCP 和 UDP在网络上传输数据。以下是每种类型的包构建与发送的详细原理

1. 原始套接字与协议

  • 原始套接字Raw Socket 允许程序直接处理网络层数据包(例如 IP 层),并且能够通过指定协议(如 ICMP与数据链路层进行交互。
  • UDP/TCP 套接字 是应用层与传输层(例如 UDP、TCP之间的接口程序通过它们发送数据并且协议栈自动处理底层的传输细节例如 IP 包封装)。

1.1 原始套接字Raw Socket构建与使用

对于 ICMP 和其他原始协议(如自定义的 IP 包),使用原始套接字 socket.AF_INETsocket.SOCK_RAW 进行数据包的构建和发送。

2. ICMP 报文构建与发送

ICMPInternet 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_portdest_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_portdest_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 协议。

对于 UDPTCP 套接字,创建的方法分别是:

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 报文构造包括序列号、确认号和标志位等。

通过使用不同的协议和套接字类型,我们可以在网络中发送各种类型的数据包。