3. Specification (规范)
3.1. Internet Header Format (互联网头部格式)
以下是互联网头部内容的摘要:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
(互联网数据报头部示例)
注意: 每个刻度标记代表一个比特位置。
Version (版本): 4 bits
版本字段指示互联网头部的格式。本文档描述版本4。
取值: 4 (二进制: 0100)
# 示例
version = 4 # IPv4
IHL (Internet Header Length, 互联网头部长度): 4 bits
互联网头部长度是以32比特字 (32-bit words) 为单位的互联网头部长度,因此指向数据的开始位置。请注意,正确头部的最小值为5。
取值范围: 5-15 (表示20-60字节)
计算:
头部长度 (字节) = IHL × 4
IHL=5 → 20字节 (最小,无选项)
IHL=6 → 24字节 (4字节选项)
IHL=15 → 60字节 (最大,40字节选项)
示例:
# 典型的IP头部 (无选项)
IHL = 5
header_length_bytes = IHL * 4 # 20字节
# 带选项的IP头部
IHL = 7
header_length_bytes = IHL * 4 # 28字节
Type of Service (服务类型): 8 bits
服务类型提供了所需服务质量抽象参数的指示。这些参数用于在通过特定网络传输数据报时指导实际服务参数的选择。
字段结构:
0 1 2 3 4 5 6 7
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | | | | |
| PRECEDENCE | D | T | R | 0 | 0 |
| | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
Bits 0-2: Precedence (优先级)
Bit 3: D = Delay (0=Normal, 1=Low)
Bit 4: T = Throughput (0=Normal, 1=High)
Bit 5: R = Reliability (0=Normal, 1=High)
Bits 6-7: Reserved (保留,必须为0)
Precedence (优先级)
| 值 | 含义 | 用途 |
|---|---|---|
| 111 | Network Control | 网络控制 |
| 110 | Internetwork Control | 互联网控制 |
| 101 | CRITIC/ECP | 关键/紧急 |
| 100 | Flash Override | 闪电覆盖 |
| 011 | Flash | 闪电 |
| 010 | Immediate | 立即 |
| 001 | Priority | 优先 |
| 000 | Routine | 常规 |
服务指示
- D (Delay): 低延迟 vs 正常延迟
- T (Throughput): 高吞吐量 vs 正常吞吐量
- R (Reliability): 高可靠性 vs 正常可靠性
使用建议:
❌ 不建议: 同时设置D、T、R全部为1
✅ 建议: 最多设置两个指示
常见组合:
- 交互式流量 (Telnet): 低延迟 (D=1)
- 文件传输 (FTP): 高吞吐量 (T=1)
- 关键数据: 高可靠性 (R=1)
- 批量数据: 正常 (D=T=R=0)
示例:
# ToS字段示例
TOS_ROUTINE = 0x00 # 000 00000
TOS_LOW_DELAY = 0x10 # 000 10000 (Telnet)
TOS_HIGH_THROUGHPUT = 0x08 # 000 01000 (FTP)
TOS_HIGH_RELIABILITY = 0x04 # 000 00100 (SNMP)
Total Length (总长度): 16 bits
总长度是数据报的长度,以八位字节 (octets) 为单位,包括互联网头部和数据。此字段允许数据报的长度最多为65,535个八位字节。
限制和建议:
- 理论最大值: 65,535字节
- 实际最小值: 576字节 (所有主机必须准备接受)
- 推荐: 仅在确保目的地可以接受时才发送大于576字节的数据报
576字节的原因:
576字节 = 20字节IP头部 + 8字节传输层头部 + 512字节数据
(或 60字节最大IP头部 + 4字节TCP头部 + 512字节数据)
这允许合理大小的数据块传输,同时确保广泛兼容性
计算示例:
# 示例1: 最小IP数据报
ip_header = 20 # 字节
data = 0 # 字节
total_length = ip_header + data # 20字节
# 示例2: 典型数据报
ip_header = 20
tcp_header = 20
data = 1460
total_length = ip_header + tcp_header + data # 1500字节 (以太网MTU)
# 示例3: 分段数据报
ip_header = 20
data = 556
total_length = ip_header + data # 576字节
Identification (标识): 16 bits
由发送方分配的标识值,用于帮助组装数据报的分段。
用途:
- 标识属于同一原始数据报的所有分段
- 必须在源-目的地址对和协议的组合中唯一
示例:
import random
# 生成唯一标识
identification = random.randint(0, 65535)
# 所有分段使用相同的标识
fragment1.identification = identification
fragment2.identification = identification
fragment3.identification = identification
Flags (标志): 3 bits
各种控制标志。
0 1 2
+---+---+---+
| | D | M |
| 0 | F | F |
+---+---+---+
Bit 0: Reserved (保留,必须为0)
Bit 1: DF (Don't Fragment) - 不分段
0 = May Fragment (可以分段)
1 = Don't Fragment (不要分段)
Bit 2: MF (More Fragments) - 更多分段
0 = Last Fragment (最后一个分段)
1 = More Fragments (还有更多分段)
DF (Don't Fragment) 标志
DF=1 时:
- 数据报不得被分段
- 如果需要分段才能传递,则丢弃数据报
- 发送ICMP "Fragmentation Needed" 错误
用途:
# Path MTU Discovery (路径MTU发现)
# 发送带DF标志的数据报,逐渐增大大小
# 当收到ICMP错误时,确定路径MTU
def discover_path_mtu(dest_ip):
mtu = 1500
while mtu > 68:
send_packet(dest_ip, size=mtu, DF=1)
if receives_icmp_frag_needed():
mtu = get_mtu_from_icmp()
else:
return mtu
return 68 # 最小MTU
MF (More Fragments) 标志
MF=0: 这是最后一个分段 (或未分段的数据报) MF=1: 后面还有更多分段
示例:
原始数据报:
Flags: [0|0|0], Offset=0
分段后:
分段1: Flags: [0|0|1], Offset=0 (还有更多)
分段2: Flags: [0|0|1], Offset=185 (还有更多)
分段3: Flags: [0|0|0], Offset=370 (最后一个)
Fragment Offset (分段偏移): 13 bits
此字段指示此分段在数据报中的位置。偏移量以8个八位字节 (64比特) 块为单位。
计算:
实际偏移 (字节) = Fragment Offset × 8
Offset=0 → 从字节0开始
Offset=1 → 从字节8开始
Offset=10 → 从字节80开始
Offset=185 → 从字节1480开始
最大范围:
13 bits → 最大值 8191
8191 × 8 = 65,528字节
这与Total Length的最大值65,535字节相符
分段示例:
# 原始数据报: 1500字节 (20头部 + 1480数据)
# 分成3个分段,每个最多576字节
# 分段1: 576字节 (20头部 + 556数据)
fragment1 = {
'total_length': 576,
'identification': 12345,
'flags_MF': 1,
'fragment_offset': 0, # 0 × 8 = 字节0
'data': original_data[0:556]
}
# 分段2: 576字节 (20头部 + 556数据)
fragment2 = {
'total_length': 576,
'identification': 12345,
'flags_MF': 1,
'fragment_offset': 69, # 69 × 8 = 字节552
'data': original_data[556:1112]
}
# 分段3: 408字节 (20头部 + 388数据)
fragment3 = {
'total_length': 408,
'identification': 12345,
'flags_MF': 0, # 最后一个分段
'fragment_offset': 139, # 139 × 8 = 字节1112
'data': original_data[1112:1500]
}
Time to Live (生存时间, TTL): 8 bits
此字段指示数据报允许在互联网系统中保持活动的最长时间。如果此字段包含值零,则数据报必须被销毁。
单位: 秒(理论上)或跳数(实际上)
范围: 0-255
操作:
- 发送方设置初始TTL值
- 每个路由器在转发前减少TTL
- 如果TTL达到0,丢弃数据报并发送ICMP Time Exceeded
常见初始值:
Linux: 64
Windows: 128
Cisco: 255
示例:
def forward_packet(packet):
packet.ttl -= 1
if packet.ttl == 0:
send_icmp_time_exceeded(packet.source)
drop_packet(packet)
else:
route_packet(packet)
应用:
# Traceroute利用TTL
# 发送TTL=1, 2, 3...的数据报
# 每个路由器返回ICMP Time Exceeded
# 从而映射出到目的地的路径
$ traceroute google.com
1 192.168.1.1 1 ms
2 10.0.0.1 5 ms
3 172.16.1.1 10 ms
...
Protocol (协议): 8 bits
此字段指示下一级协议,用于互联网数据报的数据部分。
常见值:
| 值 | 协议 | 说明 |
|---|---|---|
| 1 | ICMP | 互联网控制消息协议 |
| 2 | IGMP | 互联网组管理协议 |
| 6 | TCP | 传输控制协议 |
| 17 | UDP | 用户数据报协议 |
| 41 | IPv6 | IPv6封装 |
| 47 | GRE | 通用路由封装 |
| 50 | ESP | 封装安全载荷 |
| 51 | AH | 认证头部 |
| 89 | OSPF | 开放最短路径优先 |
示例:
PROTOCOL_ICMP = 1
PROTOCOL_TCP = 6
PROTOCOL_UDP = 17
def process_ip_packet(packet):
if packet.protocol == PROTOCOL_TCP:
handle_tcp(packet.data)
elif packet.protocol == PROTOCOL_UDP:
handle_udp(packet.data)
elif packet.protocol == PROTOCOL_ICMP:
handle_icmp(packet.data)
Header Checksum (头部校验和): 16 bits
头部校验和字段是互联网头部的16位反码和的反码。
重要:
- 仅检查头部,不检查数据
- 每经过一个路由器都必须重新计算(因为TTL等字段会变化)
计算方法:
def calculate_checksum(header):
"""
计算IP头部校验和
"""
# 1. 将校验和字段设为0
header[10:12] = b'\x00\x00'
# 2. 将头部视为16位字的序列并求和
sum = 0
for i in range(0, len(header), 2):
word = (header[i] << 8) + header[i+1]
sum += word
# 3. 将进位加回
while sum >> 16:
sum = (sum & 0xFFFF) + (sum >> 16)
# 4. 取反码
checksum = ~sum & 0xFFFF
return checksum
def verify_checksum(header):
"""
验证IP头部校验和
"""
sum = 0
for i in range(0, len(header), 2):
word = (header[i] << 8) + header[i+1]
sum += word
while sum >> 16:
sum = (sum & 0xFFFF) + (sum >> 16)
# 如果校验和正确,结果应该是0xFFFF
return (sum & 0xFFFF) == 0xFFFF
示例:
IP头部 (十六进制):
45 00 00 3c 1c 46 40 00 40 06 00 00 ac 10 0a 63 ac 10 0a 0c
计算步骤:
1. 求和: 0x4500 + 0x003c + 0x1c46 + 0x4000 + 0x4006 + 0x0000
+ 0xac10 + 0x0a63 + 0xac10 + 0x0a0c
= 0x2BBCF
2. 处理进位: 0xBBCF + 0x0002 = 0xBBD1
3. 取反码: ~0xBBD1 = 0x442E
校验和 = 0x442E
Source Address (源地址): 32 bits
源地址是32位的发送方IP地址。
格式: 点分十进制
192.168.1.100 = 11000000.10101000.00000001.01100100
Destination Address (目的地址): 32 bits
目的地址是32位的接收方IP地址。
Options (选项): 可变长度
选项可能出现也可能不出现在数据报中。它们在所有IP实现中必须 (must) 被实现。
常见选项:
1. Security (安全)
用于指定安全级别
2. Loose Source Routing (松散源路由)
指定数据报必须经过的路由器列表
3. Strict Source Routing (严格源路由)
指定数据报必须严格按照的路径
4. Record Route (记录路由)
记录数据报经过的路由器
5. Timestamp (时间戳)
记录数据报经过路由器的时间
Padding (填充): 可变长度
互联网头部填充用于确保互联网头部以32位字边界结束。填充为零。
字段总结表
| 字段 | 大小 | 范围/值 | 说明 |
|---|---|---|---|
| Version | 4 bits | 4 | IP版本 |
| IHL | 4 bits | 5-15 | 头部长度 (×4字节) |
| ToS | 8 bits | 0-255 | 服务类型 |
| Total Length | 16 bits | 20-65535 | 总长度 (字节) |
| Identification | 16 bits | 0-65535 | 分段标识 |
| Flags | 3 bits | DF, MF | 控制标志 |
| Fragment Offset | 13 bits | 0-8191 | 偏移量 (×8字节) |
| TTL | 8 bits | 0-255 | 生存时间 |
| Protocol | 8 bits | 1,6,17... | 上层协议 |
| Header Checksum | 16 bits | - | 头部校验和 |
| Source Address | 32 bits | - | 源IP地址 |
| Dest Address | 32 bits | - | 目的IP地址 |
| Options | 变长 | - | 可选字段 |
| Padding | 变长 | - | 填充到32位边界 |
关键要点: IP头部格式是互联网协议的核心,每个字段都有其特定用途。理解这些字段对于网络编程、故障排除和安全分析至关重要。