文章总结: 本文系统分析Wireshark中TCP校验和错误的两种常见非故障场景:网卡校验和卸载功能导致抓包时checksum字段未计算而显示错误,以及RFC1624规定的0x0000到0xFFFF转换引发的误报。文章详细说明校验和计算原理、Wireshark验证设置方法,并提供Python脚本用于手动验证,帮助区分真实网络问题与工具特性干扰。 综合评分: 85 文章分类: 安全工具,技术标准,漏洞分析,网络安全,其他
Wireshark TS | TCP Checksum Incorrect 问题
原创
7ACE 7ACE
Echo Reply
2026年5月11日 08:08 江苏
在小说阅读器读本章
去阅读
分享,就是普通人之间最简单有效的团结之一
前言
前段时间在分析一个网络传输异常的案例时,涉及到了 TCP checksum 的相关知识,其中有一个 TCP CHECKSUM INCORRECT 的”问题”,这在抓包分析中实际并不罕见,但很多时候它们并非真正的故障,而是工具、网卡或协议特性共同作用的结果。
本文系统梳理基于 IPv4 TCP checksum 的工作原理,简单分析下导致 Wireshark 提示 checksum 错误的两种“非网络问题”的场景。
一、TCP Checksum 是什么
TCP checksum(校验和)是 TCP 协议中用于检测数据在传输过程中是否发生错误的一种机制。它覆盖了 TCP 头部、TCP 数据负载,以及一个被称为”伪头部”(Pseudo Header)的结构。
1.1 伪头部(Pseudo Header)
伪头部并非真实存在于网络传输的数据包中,而是 TCP 在计算 checksum 时临时构造的辅助数据结构。它包含以下字段:
| 字段 | 长度 | 说明 | | — | — | — | | 源 IP 地址 | 4 字节(IPv4) | 发送方 IP | | 目的 IP 地址 | 4 字节(IPv4) | 接收方 IP | | 保留字段 | 1 字节 | 固定为 0 | | 协议类型 | 1 字节 | TCP = 6 | | TCP 总长度 | 2 字节 | TCP 头部 + 数据负载的长度 |
伪头部的设计目的是让 TCP 校验和能够同时验证 IP 层的关键信息(如源/目的地址),防止因 IP 路由错误导致的数据被投递到错误的目的地。
1.2 Checksum 的计算方式
TCP checksum 的计算采用标准的互联网校验和算法:
- 将伪头部、TCP 头部和 TCP 数据负载按 16 位字分组
- 将所有 16 位字进行 one’s complement 加法求和
- 对求和结果取反,得到 checksum 值
- 将 checksum 填入 TCP 头部的 Checksum 字段(16 位)
关键点:TCP 协议规定校验和字段不允许为 0x0000(该值在 one’s complement 算术中表示 ‘正零’,且与 UDP 中 ‘未计算’ 的语义冲突),因此计算结果若为 0x0000,必须存储为 0xFFFF。这一细节正是后文场景二的核心。
二、Wireshark 中的 TCP Checksum 选项
2.1 默认行为
在 Wireshark 中,TCP checksum 的验证功能默认是关闭的。这意味着在默认抓包视图下,即使数据包的 checksum 不正确,Wireshark 也不会高亮提示。这一设计有其合理性:
- 避免在存在网卡 offload 的环境中产生大量无意义的警告
- 减少分析人员的视觉干扰,聚焦于真正的通信问题
2.2 如何开启 Checksum 验证
如果你需要 Wireshark 对 TCP checksum 进行验证,可以按以下步骤开启:
操作路径:
- 打开 Wireshark,进入菜单栏 Edit → Preferences
- 在左侧导航栏展开 Protocols,找到并选中 TCP
- 在右侧选项列表中,勾选 “Validate the TCP checksum if possible”
- 点击 OK 保存设置
快捷方式:
- 在 Packet List 面板中任意选中一个 TCP 数据包
- 右键展开,选择 Protocol Preferences -> Transmission Control Protocol
- 勾选 “Validate the TCP checksum if possible”
开启后,Wireshark 会对每个 TCP 数据包重新计算 checksum,并与报文中存储的值进行比对。如果不一致,会在 Info 列或 TCP 详情中标记为 [TCP CHECKSUM INCORRECT] 、Checksum: 0xXXXX incorrect、Bad checksum 或类似提示。
三、TCP Checksum 错误的典型场景
当你开启 checksum 验证后,可能会频繁看到 “校验和错误”的提示。但正如前言所说,这些提示很多时候并不代表真正的网络问题。下面分析两种最常见的场景。
场景一:网卡 Checksum Offload 导致的”伪错误”
这是最常见的场景,几乎在所有现代服务器本地抓包时都会遇到。
3.1.1 什么是 Checksum Offload
现代网卡普遍支持 Checksum Offload 功能(包括 TCP checksum offload、UDP checksum offload 等)。该功能允许操作系统在发送数据包时,不计算和填充 TCP checksum 字段,而是将这项工作”卸载”给网卡硬件来完成。
为什么需要 offload?
- 减轻 CPU 负担:checksum 计算涉及遍历整个数据包,对高频网络通信来说是一笔不小的开销
- 提升吞吐量:网卡可以在硬件层面并行处理,效率远高于 CPU 软件计算
- 降低延迟:减少内核协议栈的处理时间
3.1.2 抓包位置与 Checksum 的关系
理解这个问题的关键在于:抓包工具捕获数据包的位置。
-
tcpdump / Wireshark 的捕获点:位于内核协议栈和网卡之间(或更上层)
-
数据包到达捕获点时:TCP checksum 字段可能尚未被正确计算
-
有些系统会填充
0x0000(表示未计算) -
有些系统会填充一个基于伪头部的中间值
-
数据包离开网卡时:网卡硬件才会计算真正的 checksum 并填充
3.1.3 Wireshark 的提示特征
当 Wireshark 遇到这种情况时,通常会给出非常明确的提示,例如填充 0x0000 的:
[TCP CHECKSUM INCORRECT]
Checksum: 0x0000 [incorrect, should be 0xabcd]
[Expert Info (Warning/Checksum): Bad TCP checksum]
或者伪头部的:
Checksum: 0xf9fc [correct] (matches partial checksum, not 0x4eb6, likely caused by "TCP checksum offload")
[Expert Info (Note/Checksum): Partial (pseudo header) checksum (likely caused by "TCP checksum offload")]
[Partial (pseudo header) checksum (likely caused by "TCP checksum offload")]
[Severity level: Note]
[Group: Checksum]
[Calculated Checksum: 0x4eb6]
[Checksum Status: Good]
判断要点:
- 该提示仅出现在本机发送的报文(出方向,Egress)上
- 接收的报文(入方向,Ingress)checksum 正常
- 业务通信完全正常,没有重传或异常
3.1.4 如何确认是 Offload 导致的
方法一:查看系统 Offload 状态(Linux)
ethtool -k <网卡名> | grep checksum
输出示例:
tx-checksum-ip-generic: on
如果 tx-checksum-ip-generic 为 on,说明网卡正在处理 TCP checksum。
方法二:对比两端抓包
- 在发送端抓包:checksum 显示”错误”
- 在接收端抓包(或交换机镜像口抓包):同一个数据包的 checksum 完全正确
这是确认 offload 导致的最有力证据。
方法三:临时关闭 Offload 验证
# Linux 下临时关闭 TCP checksum offload(需要 root)
ethtool -K <网卡名> tx-checksum-ip-generic off
关闭后重新抓包,如果 checksum 提示消失,即可确认。
⚠️ 注意:在生产环境中不建议随意关闭 checksum offload,这会增加 CPU 负载并可能影响网络性能。
场景二:RFC 1624 规定的 0x0000 → 0xFFFF 转换
这是一个非常特殊但完全合规的场景,来源于 RFC 1624《Computation of the Internet Checksum via Incremental Update》。
3.2.1 背景:0x0000 的特殊含义
在 one’s complement 算术中,0x0000 和 0xFFFF 都代表数值零:
0x0000:正零(+0)0xFFFF:负零(-0),在 one’s complement 中所有位为 1
RFC 1624 规定:如果 checksum 计算结果为 0x0000,必须将其转换为 0xFFFF 进行传输。原因是 0x0000 在协议语义中表示”该校验和字段未被计算/未填充”,因此需要一个替代值来表示”计算结果恰好为零”。
3.2.2 网卡的行为
当 TCP 数据包满足以下条件时:
- 数据内容 + 伪头部计算出的 checksum 结果恰好为
0x0000 - 网卡支持 checksum offload
网卡在发送时会按照 RFC 1624 的要求,将 TCP checksum 字段填充为 0xFFFF。
3.2.3 Wireshark 的验证逻辑
Wireshark 在验证 checksum 时,会独立重新计算整个数据包的校验和。如果计算结果是 0x0000,它会预期报文中存储的值也应该是 0x0000。但实际报文中存储的是 0xFFFF(由网卡根据 RFC 1624 转换)。
因此,Wireshark 会报告:
Checksum: 0xffff [should be 0x0000 (see RFC 1624)]
[Calculated Checksum: 0x0000]
[Checksum Status: Bad]
[Expert Info (Warning/Checksum): TCP Checksum 0xffff instead of 0x0000 (see RFC 1624)]
[TCP Checksum 0xffff instead of 0x0000 (see RFC 1624)]
[Severity level: Warning]
[Group: Checksum]
但这里的关键是:0x0000 和 0xFFFF 在 one’s complement 校验和中是等价的。两者都表示”校验通过”。
3.2.4 如何判断属于此场景
- Wireshark 提示 checksum 不正确
- 提示信息中明确显示:存储值为
0xFFFF,期望值为0x0000 - 仅出现在发送方向(出方向)
- 业务通信正常
如果你看到上述特征,可以放心地判定这是 RFC 1624 规定的正常行为,不是网络问题。
四、验证 TCP Checksum 的脚本
为了更方便地验证和排查 checksum 问题,以下提供一个基于 Python 的 TCP checksum 计算脚本。你可以从 Wireshark 中提取原始十六进制数据,使用此脚本独立计算 checksum,并与 Wireshark 的显示进行对比。
4.1 使用方法
- 在 Wireshark 中选中目标数据包
- 右键点击数据包 → Copy → …as a Hex Stream
- 保留并复制 IP 层及之后的数据,将复制的十六进制字符串作为参数传给脚本
4.2 脚本代码
#!/usr/bin/env python3
# tcp_checksum.py - TCP Checksum 验证脚本
import sys
import struct
def parse_hex_stream(hex_str):
"""将十六进制字符串转换为字节数组"""
hex_str = hex_str.replace(' ', '').replace('\n', '').replace(':', '')
return bytes.fromhex(hex_str)
def ones_complement_add(a, b):
""" one's complement 加法 """
result = a + b
if result & 0x10000: # 有进位
result = (result & 0xFFFF) + 1
return result & 0xFFFF
def calculate_checksum(data):
"""计算互联网校验和(RFC 1071)"""
if len(data) % 2 == 1:
data += b'\x00'
total = 0
for i in range(0, len(data), 2):
word = (data[i] << 8) + data[i + 1]
total = ones_complement_add(total, word)
return ~total & 0xFFFF
def extract_ip_pseudo_header(packet):
"""从 IP 数据包中提取伪头部信息"""
# 假设为 IPv4
ip_header_len = (packet[0] & 0x0F) * 4
src_ip = packet[12:16]
dst_ip = packet[16:20]
protocol = packet[9]
# TCP 总长度 = IP 总长度 - IP 头部长度
ip_total_len = struct.unpack('>H', packet[2:4])[0]
tcp_total_len = ip_total_len - ip_header_len
# 构造伪头部
pseudo_header = src_ip + dst_ip + b'\x00' + bytes([protocol]) + struct.pack('>H', tcp_total_len)
return pseudo_header, ip_header_len, tcp_total_len
def analyze_tcp_checksum(hex_stream):
"""分析 TCP checksum"""
packet = parse_hex_stream(hex_stream)
print(f"数据包总长度: {len(packet)} 字节")
print(f"十六进制数据: {packet.hex()}")
print()
# 提取伪头部和 TCP 数据
pseudo_header, ip_header_len, tcp_total_len = extract_ip_pseudo_header(packet)
tcp_data = packet[ip_header_len:ip_header_len + tcp_total_len]
# 提取报文中存储的 checksum(TCP 头部第 16-17 字节)
stored_checksum = (tcp_data[16] << 8) + tcp_data[17]
# 计算 checksum 时,先将 checksum 字段置零
tcp_for_calc = bytearray(tcp_data)
tcp_for_calc[16] = 0
tcp_for_calc[17] = 0
# 计算完整 checksum(伪头部 + TCP 头部 + TCP 数据)
calculated_checksum = calculate_checksum(pseudo_header + bytes(tcp_for_calc))
# 处理 RFC 1624 特殊情况
display_expected = 0xFFFFif calculated_checksum == 0x0000else calculated_checksum
print("=== 分析结果 ===")
print(f"IP 头部长度: {ip_header_len} 字节")
print(f"TCP 总长度: {tcp_total_len} 字节")
print(f"报文中存储的 Checksum: 0x{stored_checksum:04x}")
print(f"重新计算的 Checksum: 0x{calculated_checksum:04x}")
print(f"RFC 1624 调整后期望值: 0x{display_expected:04x}")
print()
if stored_checksum == calculated_checksum:
print("### Checksum 一致,数据包正确")
elif stored_checksum == 0xFFFFand calculated_checksum == 0x0000:
print("### Checksum 符合 RFC 1624 规范(0x0000 → 0xFFFF 转换)")
print(" 这是正常的协议行为,不代表数据包损坏")
elif stored_checksum == 0x0000:
print("### Checksum 为 0x0000,可能未计算(Checksum Offload 导致)")
print(" 建议在对端或交换机镜像口抓包验证")
else:
print("### Checksum 不一致,数据包可能已损坏")
print(f" 差异: 0x{stored_checksum ^ calculated_checksum:04x}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python3 tcp_checksum.py <十六进制数据流>")
print("示例: python3 tcp_checksum.py '4500003c...'")
sys.exit(1)
hex_stream = sys.argv[1]
analyze_tcp_checksum(hex_stream)
4.3 执行示例
python3 tcp_checksum.py "4500003c1c4640004006b1e6c0a80164c0a8010107d00050351d..."
预期输出:
数据包总长度: 60 字节
十六进制数据: 4500003c1c46...
=== 分析结果 ===
IP 头部长度: 20 字节
TCP 总长度: 40 字节
报文中存储的 Checksum: 0x0000
重新计算的 Checksum: 0xabcd
RFC 1624 调整后期望值: 0xabcd
### Checksum 为 0x0000,可能未计算(Checksum Offload 导致)
建议在对端或交换机镜像口抓包验证
五、总结与排查思路
5.1 核心结论
| 场景 | 特征 | 是否真实问题 | | — | — | — | | 网卡 Checksum Offload | 仅出方向报错,接收端正常,业务正常 | ❌ 正常 | | RFC 1624(0x0000→0xFFFF) | 存储值 0xFFFF,期望值 0x0000 | ❌ 正常 | | 真实 Checksum 错误 | 两端抓包均报错,伴随重传/乱序 | ✅ 需要排查 |
5.2 排查 checklist
当你在 Wireshark 中看到 TCP checksum 错误时,建议按以下步骤判断:
- 确认方向:仅发送方向报错?还是接收方向也报错?
- 仅发送方向 → 大概率是 offload
- 接收方向也报错 → 可能是真实问题或 RFC 1624
- 确认业务影响:是否伴随 TCP 重传、乱序、连接中断?
- 无业务异常 → 大概率是误报
- 有业务异常 → 需要深入排查
- 对比抓包:在通信对端或交换机镜像口抓包,对比同一报文的 checksum
- 对端正常 → 确认是发送端 offload 导致
- 对端也报错 → 可能是传输过程中数据被篡改
- 查看具体值:
- 存储值
0x0000→ 未计算(offload) - 存储值
0xFFFF,期望0x0000→ RFC 1624 正常转换 - 其他错误 → 使用脚本验证
- 使用验证脚本:提取原始数据,使用本文提供的脚本独立计算
5.3 何时需要真正关注 Checksum 错误
以下情况下的 checksum 错误需要认真对待:
- 接收方向(Ingress)报文 checksum 错误:说明数据在传输过程中确实发生了损坏
- 伴随 TCP 重传:接收方检测到 checksum 错误后丢弃报文,导致发送方重传
- 高频率出现:偶发的 checksum 错误可能是电磁干扰,高频率出现则可能是硬件故障
- 跨网络后出现:在本地正常,经过特定网络设备后出现 checksum 错误,可能是该设备的问题
TCP checksum 是网络排错中一个看似简单但容易误判的指标。理解其背后的协议规范(RFC 1071、RFC 1624)、网卡 offload 机制以及 Wireshark 的验证逻辑,能够帮助你快速区分”真问题”和”伪告警”,避免在错误的排查方向上浪费时间。
对于 TCP checksum,你是否还遇到过其他有趣的场景?欢迎在评论区讨论交流。
往期推荐
1. Wireshark 提示和技巧 | 捕获点之 TCP 三次握手
2. Wireshark 提示和技巧 | a == ${a} 显示过滤宏
3. Wireshark TS | 当超时或快速重传遇到零窗口
4. Wireshark TS | 防火墙空闲会话超时问题
5. 网络设备 MTU MSS Jumboframe 全解
后台回复「TT」获取 Wireshark 提示和技巧系列 合集
后台回复「TS」获取 Wireshark Troubleshooting系列 合集
如需交流,可后台直接留言,我会在第一时间回复,谢谢!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Echo Reply 7ACE 7ACE《Wireshark TS | TCP Checksum Incorrect 问题》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论