WiresharkTS|TCPChecksumIncorrect问题

admin 2026-05-12 05:55:05 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统分析Wireshark中TCP校验和错误的两种常见非故障场景:网卡校验和卸载功能导致抓包时checksum字段未计算而显示错误,以及RFC1624规定的0x0000到0xFFFF转换引发的误报。文章详细说明校验和计算原理、Wireshark验证设置方法,并提供Python脚本用于手动验证,帮助区分真实网络问题与工具特性干扰。 综合评分: 85 文章分类: 安全工具,技术标准,漏洞分析,网络安全,其他


cover_image

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 的计算采用标准的互联网校验和算法:

  1. 将伪头部、TCP 头部和 TCP 数据负载按 16 位字分组
  2. 将所有 16 位字进行 one’s complement 加法求和
  3. 对求和结果取反,得到 checksum 值
  4. 将 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 进行验证,可以按以下步骤开启:

操作路径

  1. 打开 Wireshark,进入菜单栏 Edit → Preferences
  2. 在左侧导航栏展开 Protocols,找到并选中 TCP
  3. 在右侧选项列表中,勾选 “Validate the TCP checksum if possible”
  4. 点击 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 incorrectBad 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 数据包满足以下条件时:

  1. 数据内容 + 伪头部计算出的 checksum 结果恰好为 0x0000
  2. 网卡支持 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)]
&nbsp; &nbsp; [Calculated Checksum: 0x0000]
&nbsp; &nbsp; [Checksum Status: Bad]
&nbsp; &nbsp; &nbsp; &nbsp; [Expert Info (Warning/Checksum): TCP Checksum 0xffff instead of 0x0000 (see RFC 1624)]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [TCP Checksum 0xffff instead of 0x0000 (see RFC 1624)]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [Severity level: Warning]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [Group: Checksum]

但这里的关键是:0x0000 和 0xFFFF 在 one’s complement 校验和中是等价的。两者都表示”校验通过”。

3.2.4 如何判断属于此场景

  1. Wireshark 提示 checksum 不正确
  2. 提示信息中明确显示:存储值为0xFFFF期望值为0x0000
  3. 仅出现在发送方向(出方向)
  4. 业务通信正常

如果你看到上述特征,可以放心地判定这是 RFC 1624 规定的正常行为,不是网络问题


四、验证 TCP Checksum 的脚本

为了更方便地验证和排查 checksum 问题,以下提供一个基于 Python 的 TCP checksum 计算脚本。你可以从 Wireshark 中提取原始十六进制数据,使用此脚本独立计算 checksum,并与 Wireshark 的显示进行对比。

4.1 使用方法

  1. 在 Wireshark 中选中目标数据包
  2. 右键点击数据包 → Copy → …as a Hex Stream
  3. 保留并复制 IP 层及之后的数据,将复制的十六进制字符串作为参数传给脚本

4.2 脚本代码

#!/usr/bin/env python3
# tcp_checksum.py - TCP Checksum 验证脚本

import&nbsp;sys
import&nbsp;struct

def&nbsp;parse_hex_stream(hex_str):
&nbsp; &nbsp;&nbsp;"""将十六进制字符串转换为字节数组"""
&nbsp; &nbsp; hex_str = hex_str.replace(' ',&nbsp;'').replace('\n',&nbsp;'').replace(':',&nbsp;'')
&nbsp; &nbsp;&nbsp;return&nbsp;bytes.fromhex(hex_str)

def&nbsp;ones_complement_add(a, b):
&nbsp; &nbsp;&nbsp;""" one's complement 加法 """
&nbsp; &nbsp; result = a + b
&nbsp; &nbsp;&nbsp;if&nbsp;result &&nbsp;0x10000: &nbsp;# 有进位
&nbsp; &nbsp; &nbsp; &nbsp; result = (result &&nbsp;0xFFFF) +&nbsp;1
&nbsp; &nbsp;&nbsp;return&nbsp;result &&nbsp;0xFFFF

def&nbsp;calculate_checksum(data):
&nbsp; &nbsp;&nbsp;"""计算互联网校验和(RFC 1071)"""
&nbsp; &nbsp;&nbsp;if&nbsp;len(data) %&nbsp;2&nbsp;==&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp; data +=&nbsp;b'\x00'

&nbsp; &nbsp; total =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(data),&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp; word = (data[i] <<&nbsp;8) + data[i +&nbsp;1]
&nbsp; &nbsp; &nbsp; &nbsp; total = ones_complement_add(total, word)

&nbsp; &nbsp;&nbsp;return&nbsp;~total &&nbsp;0xFFFF

def&nbsp;extract_ip_pseudo_header(packet):
&nbsp; &nbsp;&nbsp;"""从 IP 数据包中提取伪头部信息"""
&nbsp; &nbsp;&nbsp;# 假设为 IPv4
&nbsp; &nbsp; ip_header_len = (packet[0] &&nbsp;0x0F) *&nbsp;4

&nbsp; &nbsp; src_ip = packet[12:16]
&nbsp; &nbsp; dst_ip = packet[16:20]
&nbsp; &nbsp; protocol = packet[9]

&nbsp; &nbsp;&nbsp;# TCP 总长度 = IP 总长度 - IP 头部长度
&nbsp; &nbsp; ip_total_len = struct.unpack('>H', packet[2:4])[0]
&nbsp; &nbsp; tcp_total_len = ip_total_len - ip_header_len

&nbsp; &nbsp;&nbsp;# 构造伪头部
&nbsp; &nbsp; pseudo_header = src_ip + dst_ip +&nbsp;b'\x00'&nbsp;+ bytes([protocol]) + struct.pack('>H', tcp_total_len)

&nbsp; &nbsp;&nbsp;return&nbsp;pseudo_header, ip_header_len, tcp_total_len

def&nbsp;analyze_tcp_checksum(hex_stream):
&nbsp; &nbsp;&nbsp;"""分析 TCP checksum"""
&nbsp; &nbsp; packet = parse_hex_stream(hex_stream)

&nbsp; &nbsp; print(f"数据包总长度:&nbsp;{len(packet)}&nbsp;字节")
&nbsp; &nbsp; print(f"十六进制数据:&nbsp;{packet.hex()}")
&nbsp; &nbsp; print()

&nbsp; &nbsp;&nbsp;# 提取伪头部和 TCP 数据
&nbsp; &nbsp; pseudo_header, ip_header_len, tcp_total_len = extract_ip_pseudo_header(packet)
&nbsp; &nbsp; tcp_data = packet[ip_header_len:ip_header_len + tcp_total_len]

&nbsp; &nbsp;&nbsp;# 提取报文中存储的 checksum(TCP 头部第 16-17 字节)
&nbsp; &nbsp; stored_checksum = (tcp_data[16] <<&nbsp;8) + tcp_data[17]

&nbsp; &nbsp;&nbsp;# 计算 checksum 时,先将 checksum 字段置零
&nbsp; &nbsp; tcp_for_calc = bytearray(tcp_data)
&nbsp; &nbsp; tcp_for_calc[16] =&nbsp;0
&nbsp; &nbsp; tcp_for_calc[17] =&nbsp;0

&nbsp; &nbsp;&nbsp;# 计算完整 checksum(伪头部 + TCP 头部 + TCP 数据)
&nbsp; &nbsp; calculated_checksum = calculate_checksum(pseudo_header + bytes(tcp_for_calc))

&nbsp; &nbsp;&nbsp;# 处理 RFC 1624 特殊情况
&nbsp; &nbsp; display_expected =&nbsp;0xFFFFif&nbsp;calculated_checksum ==&nbsp;0x0000else&nbsp;calculated_checksum

&nbsp; &nbsp; print("=== 分析结果 ===")
&nbsp; &nbsp; print(f"IP 头部长度:&nbsp;{ip_header_len}&nbsp;字节")
&nbsp; &nbsp; print(f"TCP 总长度:&nbsp;{tcp_total_len}&nbsp;字节")
&nbsp; &nbsp; print(f"报文中存储的 Checksum: 0x{stored_checksum:04x}")
&nbsp; &nbsp; print(f"重新计算的 Checksum: 0x{calculated_checksum:04x}")
&nbsp; &nbsp; print(f"RFC 1624 调整后期望值: 0x{display_expected:04x}")
&nbsp; &nbsp; print()

&nbsp; &nbsp;&nbsp;if&nbsp;stored_checksum == calculated_checksum:
&nbsp; &nbsp; &nbsp; &nbsp; print("### Checksum 一致,数据包正确")
&nbsp; &nbsp;&nbsp;elif&nbsp;stored_checksum ==&nbsp;0xFFFFand&nbsp;calculated_checksum ==&nbsp;0x0000:
&nbsp; &nbsp; &nbsp; &nbsp; print("### Checksum 符合 RFC 1624 规范(0x0000 → 0xFFFF 转换)")
&nbsp; &nbsp; &nbsp; &nbsp; print(" &nbsp; &nbsp;这是正常的协议行为,不代表数据包损坏")
&nbsp; &nbsp;&nbsp;elif&nbsp;stored_checksum ==&nbsp;0x0000:
&nbsp; &nbsp; &nbsp; &nbsp; print("### Checksum 为 0x0000,可能未计算(Checksum Offload 导致)")
&nbsp; &nbsp; &nbsp; &nbsp; print(" &nbsp; &nbsp;建议在对端或交换机镜像口抓包验证")
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; print("### Checksum 不一致,数据包可能已损坏")
&nbsp; &nbsp; &nbsp; &nbsp; print(f" &nbsp; 差异: 0x{stored_checksum ^ calculated_checksum:04x}")

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp;&nbsp;if&nbsp;len(sys.argv) <&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; print("用法: python3 tcp_checksum.py <十六进制数据流>")
&nbsp; &nbsp; &nbsp; &nbsp; print("示例: python3 tcp_checksum.py '4500003c...'")
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp; hex_stream = sys.argv[1]
&nbsp; &nbsp; analyze_tcp_checksum(hex_stream)

4.3 执行示例

python3 tcp_checksum.py&nbsp;"4500003c1c4640004006b1e6c0a80164c0a8010107d00050351d..."

预期输出

数据包总长度: 60 字节
十六进制数据: 4500003c1c46...

=== 分析结果 ===
IP 头部长度: 20 字节
TCP 总长度: 40 字节
报文中存储的 Checksum: 0x0000
重新计算的 Checksum: 0xabcd
RFC 1624 调整后期望值: 0xabcd

### Checksum 为 0x0000,可能未计算(Checksum Offload 导致)
&nbsp; &nbsp; 建议在对端或交换机镜像口抓包验证

五、总结与排查思路

5.1 核心结论

| 场景 | 特征 | 是否真实问题 | | — | — | — | | 网卡 Checksum Offload | 仅出方向报错,接收端正常,业务正常 | ❌ 正常 | | RFC 1624(0x0000→0xFFFF) | 存储值 0xFFFF,期望值 0x0000 | ❌ 正常 | | 真实 Checksum 错误 | 两端抓包均报错,伴随重传/乱序 | ✅ 需要排查 |

5.2 排查 checklist

当你在 Wireshark 中看到 TCP checksum 错误时,建议按以下步骤判断:

  1. 确认方向:仅发送方向报错?还是接收方向也报错?
  • 仅发送方向 → 大概率是 offload
  • 接收方向也报错 → 可能是真实问题或 RFC 1624
  1. 确认业务影响:是否伴随 TCP 重传、乱序、连接中断?
  • 无业务异常 → 大概率是误报
  • 有业务异常 → 需要深入排查
  1. 对比抓包:在通信对端或交换机镜像口抓包,对比同一报文的 checksum
  • 对端正常 → 确认是发送端 offload 导致
  • 对端也报错 → 可能是传输过程中数据被篡改
  1. 查看具体值
  • 存储值 0x0000 → 未计算(offload)
  • 存储值 0xFFFF,期望 0x0000 → RFC 1624 正常转换
  • 其他错误 → 使用脚本验证
  1. 使用验证脚本:提取原始数据,使用本文提供的脚本独立计算

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 问题》

评论:0   参与:  0