当前位置:首页>python>用Python来了解网络连接的底层原理(二)

用Python来了解网络连接的底层原理(二)

  • 2026-09-08 07:18:16
用Python来了解网络连接的底层原理(二)

在前一篇中我们聊了基础的网络连接,但实际网络通信中,除了要传输的数据本身,还会附带大量元数据—— 路由器靠这些信息决定数据包的走向,甚至判断是否要拦截它。今天我们就用 Python 来看看 IP 头,搞懂这些元数据。

IP 头里很多字段小于 1 字节(比如 4 位的版本号),必须用位运算才能正确解析。要解析 IP 头部,首先得拿到原始数据包,用 Python 的 socket 可以实现简单的嗅探器:

import socket# 创建原始套接字(需要管理员权限!)s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)# 绑定到本机网卡 IP(替换成你的 IP)s.bind(("192.168.1.81", 0))# 开启混杂模式,接收所有数据包s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)# 接收数据包(IP 包最大 65535 字节)packet = s.recvfrom(65535)packet = packet[0]  # 只取数据部分,忽略地址print(packet)  # 输出原始字节流(内容较长,会截断)

需要说明一下,这段代码需要使用管理员权限运行,最终输出内容是字节流

IP 头部是数据包开头的一串字节,包含源 IP、目的 IP、TTL 等核心信息。基本规格

  • 最小 20 字节(5 个 32 位字段)
  • 最大 60 字节(可加 10 个可选字段)
  • 很多字段小于 1 字节(比如 4 位版本号、3 位标志位)
  • IPV4头部结构(RFC 791 标准)
  • 32 位字段
    子字段
    长度
    说明
    第 1 个
    Version
    4 位
    IP 版本(IPv4=4)
    IHL
    4 位
    头部长度(单位:32 位)
    Type of Service
    8 位
    服务类型(QoS)
    Total Length
    16 位
    整个数据包长度
    第 2 个
    Identification
    16 位
    数据包标识(分片重组用)
    Flags
    3 位
    分片标志
    Fragment Offset
    13 位
    分片偏移
    第 3 个
    Time to Live (TTL)
    8 位
    生存时间(跳数)
    Protocol
    8 位
    上层协议(TCP=6,UDP=17)
    Header Checksum
    16 位
    头部校验和
    第 4 个
    Source Address
    32 位
    源 IP 地址
    第 5 个
    Destination Address
    32 位
    目的 IP 地址

原始字节流很难直接解析,Python 的 struct.unpack 可以按指定格式把二进制数据转成可读的元组。

字节序(大小端)

  • 主机字节序:多数 PC 是小端(sys.byteorder 查看)
  • 网络字节序:标准是大端(用 ! 标识)
格式符
对应类型
字节数
说明
B
unsigned char
1
8 位无符号整数
H
unsigned short
2
16 位无符号整数
L
unsigned long
4
32 位无符号整数
4s
bytes
4
保留原始字节

先截取数据包前 20 字节(最小 IP 头部),再按格式解包:

import structimport socket# 截取前 20 字节 IP 头部ip_header_bytes = packet[0:20]# 解包格式:!BBHHHBBH4s4s# !: 网络字节序(大端)# B: Version+IHL (8位)# B: Type of Service (8位)# H: Total Length (16位)# H: Identification (16位)# H: Flags+Fragment Offset (16位)# B: TTL (8位)# B: Protocol (8位)# H: Header Checksum (16位)# 4s: 源 IP (32位)# 4s: 目的 IP (32位)ip_header = struct.unpack('!BBHHHBBH4s4s', ip_header_bytes)print(ip_header)# 示例输出:(69, 0, 1309, 30503, 16384, 128, 6, 0, b'\xc0\xa8\x01Q', b'h\xf4*D')

解包后得到的是元组,我们需要逐个解析字段含义:

1. 版本号(Version)+ 头部长度(IHL)

# 第一个元素是 Version+IHL(8位)version_ihl = ip_header[0]# 版本号:前 4 位(右移 4 位)ip_version = version_ihl >> 4print(f"IP 版本:{ip_version}")  # 4(IPv4)# 头部长度:后 4 位(与 0xF 按位与)ihl = version_ihl & 0xFprint(f"IP 头部长度:{ihl * 4} 字节")  # IHL 单位是 32位,所以 ×4

2. 服务类型(Type of Service)

tos = ip_header[1]print(f"服务类型:{format(tos, '08b')}")  # 00000000(默认优先级)

3. 总长度(Total Length)

total_length = ip_header[2]print(f"数据包总长度:{total_length} 字节")  # 1309print(f"实际长度验证:{len(packet)} 字节")  # 1309(匹配)

4. 标识 + 标志 + 分片偏移

# 标识(分片重组用)identification = ip_header[3]print(f"数据包标识:{identification}")  # 30503# 标志 + 分片偏移(16位)flags_frag = ip_header[4]# 标志:前 3 位(右移 13 位)flags = flags_frag >> 13print(f"分片标志:{format(flags, '03b')}")  # 010(禁止分片)# 分片偏移:后 13 位frag_offset = flags_frag & 0b0001111111111111print(f"分片偏移:{frag_offset}")  # 0(未分片)

5. TTL + 协议 + 校验和

# 生存时间(跳数)ttl = ip_header[5]print(f"TTL:{ttl}")  # 128# 上层协议(6=TCP,17=UDP)protocol = ip_header[6]print(f"上层协议:{protocol}")  # 6(TCP)# 头部校验和checksum = ip_header[7]print(f"头部校验和:{format(checksum, '016b')}")  # 0000000000000000

6. 源 IP + 目的 IP

# 二进制转点分十进制src_ip = socket.inet_ntoa(ip_header[8])dst_ip = socket.inet_ntoa(ip_header[9])print(f"源 IP:{src_ip}")  # 192.168.1.81print(f"目的 IP:{dst_ip}")  # 104.244.42.68(Twitter 服务器)

结合上面的内容,我们就可以对IP头进行解析分析,当然原始套接字(SOCK_RAW)要获取未解析的数据包,需要管理员权限,且开启混杂模式后会接收所有经过网卡的数据包。

今天就到这里,下次我们再看看TCP、UDP协议

最新文章

随机文章