课程
《计算机网络》课后探研题书面报告
《计算机网络》课后探研题书面报告
课后探研题 3: 网际校验和算法
1 实验目的
- 理解网际校验和算法(Internet CheckSum)的工作原理;
- 培养协议分析、设计和实现的能力。
2 实验内容
自主学习网际校验和算法相关知识,编程实现网际校验和算法。从捕获的报文文件中读取数据,调用算法进行差错检验,并输出结果。
2.1 步骤
- 收集网际校验和算法的相关资料;
- 阅读 RFC1071 文档,理解网际校验和算法的原理;
- 编程实现网际校验和算法;
- 完成书面报告。
3 实验步骤及编程实现
3.1 编程环境
- 操作系统: Windows
- 编程语言: Python 3.x
- 开发工具: Visual Studio Code / Python IDLE
3.2 报文数据类型定义
在 Python 中,我们使用 bytes 类型来存储报文数据。为了便于处理,我们将十六进制字符串转换为字节流。
- 以太网头: 14 字节(忽略)
- IP 头: 通常 20 字节(包含 Header Checksum)
- TCP/UDP/ICMP 报文: 包含在 IP 数据负载中
相关常数定义:
- 协议号:ICMP (1), TCP (6), UDP (17)
3.3 算法说明及流程图
3.3.1 算法原理
网际校验和算法(Internet Checksum Algorithm)是 TCP/IP 协议族中用于差错检测的一种重要机制,其标准定义在 RFC 1071 中。该算法采用的是 16 位反码求和(One’s Complement Sum)。与其它的校验算法(如 CRC 循环冗余校验)相比,反码求和算法的检错能力虽然稍弱,但其最大的优点是软件实现简单、计算速度快,且具有端序独立性(Endian-independence)。
算法详细步骤:
-
数据对齐: 将待校验的数据流看作是一个由 16 位整数组成的序列。如果数据字节数为奇数,则在末尾填充一个字节的 0(Padding),使其总长度为偶数。
-
反码求和: 将所有 16 位整数进行二进制反码算术相加。
- 在二进制反码算术中,最高位产生的进位(Carry)需要加回到最低位(End-around Carry)。
- 例如,两个 16 位数相加,如果结果超过
0xFFFF(即 65535),则将溢出的部分加回到低位。 sum = sum + wordif (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16)
-
取反: 将最终的求和结果进行按位取反(One’s Complement),即 0 变 1,1 变 0。得到的结果即为校验和。
在此算法中,如果数据传输正确,接收方计算校验和的结果应为 0(或者 0xFFFF,取决于具体实现中是否包含原来的校验和字段)。
对于不同协议的特殊处理:
-
IP 首部校验: 仅对 IP 首部进行计算。在计算前,发送方必须将 IP 首部中的
Header Checksum字段置为 0。 -
TCP/UDP 校验: 校验范围覆盖伪首部、TCP/UDP 首部以及数据部分。
- 伪首部 (Pseudo Header): 这是一个虚拟的数据结构,目的是为了检验数据是否到达了正确的目的地。它包含源 IP 地址(4 字节)、目的 IP 地址(4 字节)、保留字节(1 字节,置 0)、协议号(1 字节)以及 TCP/UDP 长度(2 字节)。
- 引入伪首部的目的是让 TCP/UDP 层能够验证 IP 层提供的地址信息是否正确,防止 IP 分组投递错误。
3.3.2 核心代码说明
代码中 calculate_checksum 函数是整个校验逻辑的核心。
def calculate_checksum(data):
"""
计算 16 位反码求和校验和
参数 data: 输入的字节流 (bytes)
返回: 计算出的 16 位校验和 (int)
"""
# 1. 处理奇数长度数据
if len(data) % 2 == 1:
data += b'\x00' # 如果数据长度为奇数,在末尾填充一个 0 字节
s = 0
# 2. 逐个 16 位字累加
for i in range(0, len(data), 2):
# 将两个字节组合成一个 16 位整数
# data[i] 为高 8 位,data[i+1] 为低 8 位
w = (data[i] << 8) + (data[i+1])
s += w
# 3. 处理溢出进位 (End-around carry)
# s >> 16 获取溢出的高位
# s & 0xffff 获取低 16 位
# 将溢出位加回到低位,直到没有溢出为止
s = (s >> 16) + (s & 0xffff)
# 再次处理可能产生的进位 (例如 0xFFFF + 1 = 0x10000 -> 1 + 0 = 1)
s += (s >> 16)
# 4. 按位取反并截断为 16 位
return ~s & 0xffff
该函数利用 Python 的大整数特性,先将所有数相加,最后再处理进位,简化了逻辑。~s & 0xffff 确保最终结果仅保留 16 位。
3.4 测试用报文文件格式说明
测试文件采用 Wireshark 导出的文本格式,包含十六进制数据流。本实验主要处理以 |0 开头的行,该行包含了完整的以太网帧数据。
解析逻辑如下:
- 读取文件行。
- 找到以
|0开头的行(例如|0 |10|e7|...)。 - 提取
|之间的十六进制数值并转换为字节。 - 跳过前 14 字节(以太网首部),从第 15 字节开始解析 IP 报文。
4 实验结果
4.1 程序运行结果
运行程序对抓包的文件进行校验,结果如下:
| 文件名 | 协议 | 类型 | 结果 | 计算值 | 原始值 | 说明 |
|---|---|---|---|---|---|---|
| icmp_reply.txt | IP | Header | PASS | 0x2d95 | 0x2d95 | 校验通过 |
| icmp_reply.txt | ICMP | Payload | PASS | 0x555a | 0x555a | 校验通过 |
| icmp_request.txt | IP | Header | FAIL | 0xe294 | 0x0 | 原始值为 0 |
| icmp_request.txt | ICMP | Payload | PASS | 0x4d5a | 0x4d5a | 校验通过 |
| tcp.payload != 0.txt | IP | Header | PASS | 0x4031 | 0x4031 | 校验通过 |
| tcp.payload != 0.txt | TCP | Payload | PASS | 0x7a19 | 0x7a19 | 校验通过 |
| udp.txt | IP | Header | PASS | 0x4b4c | 0x4b4c | 校验通过 |
| udp.txt | UDP | Payload | PASS | 0xb375 | 0xb375 | 校验通过 |
4.2 结果分析与讨论
4.2.1 校验通过情况分析
从实验结果可以看到,对于 icmp_reply.txt、tcp.payload != 0.txt、udp.txt 等文件,程序计算得到的校验和与报文中原始的校验和完全一致(PASS)。这验证了:
- 算法实现的正确性: 反码求和算法逻辑无误。
- 协议解析的正确性: IP 首部解析、TCP/UDP 伪首部构造均正确。
- 对于 TCP/UDP,构造了包含源 IP、目的 IP、协议号及长度的伪首部,并正确参与了校验和计算。
- 对于 ICMP,仅计算了 ICMP 报文本身的校验和,符合 RFC 792 标准。
4.2.2 校验失败情况分析
在实验中,icmp_request.txt、tcp.flags.fin ==1.txt 等文件的 IP 或 TCP 校验和验证失败。具体表现为原始校验和为 0x0000 或与计算值不符。经分析,这并非程序错误,而是由于现代网络适配器(网卡)广泛支持 Checksum Offloading(校验和卸载) 技术。
现象解释:
- Wireshark 抓包通常发生在操作系统的协议栈和网卡驱动之间。
- 当发送报文时,操作系统为了节省 CPU 资源,将计算校验和的工作”卸载”给网卡硬件。
- 因此,当 Wireshark 捕获到这些 出站(Outbound) 报文时,校验和字段尚未被计算,仍然保持为初始状态(
0x0000)或无效值。 - 网卡在真正发送信号到物理介质前,会计算并填充正确的校验和。
- 这也是为什么接收到的报文(如
icmp_reply.txt,通常是入站报文)校验和都是正确的,因为它们已经由发送端的网卡计算好,并经过了网络的传输。
结论:
程序显示的 FAIL 实际上反映了抓包文件中的真实状态(未计算校验和),进一步验证了我们对网络硬件工作机制的理解。本实验中,对于具备完整校验和的入站报文,程序均给出了 PASS 的结果,证明了校验工具的可靠性。
参考文献
[1] RFC 1071 - Computing the Internet Checksum.
[2] RFC 791 - Internet Protocol.
[3] RFC 792 - Internet Control Message Protocol.
[4] 谢希仁. 计算机网络(第 7 版). 电子工业出版社.













