基于字节数组计算TCP校验和遇错求助(附C#代码)
TCP校验和计算错误排查与修正建议
核心错误点排查
1. 伪头部长度字段问题
TCP伪头部的长度字段是TCP段总长度(TCP头+负载字节数),需按网络字节序(大端)存储,且为16位值。常见错误:
- 误将IPv4总长度当作该值,或计算时遗漏TCP头长度(需从TCP头的Data Offset字段获取,单位为4字节)
- 长度值未转换为大端字节序就加入伪头部
2. 一的补码计算逻辑错误
TCP校验和标准计算流程:
- 拼接伪头部、校验和字段设为0的TCP头、负载为连续字节数组
- 按16位为一组累加,若总字节数为奇数,末尾补0字节
- 将累加产生的进位(超过16位的部分)加到低16位上
- 对最终16位值取反,得到的一的补码即为校验和
常见错误:
- 累加时未处理跨16位的进位,直接截断低16位
- 取反操作未考虑C#中int的符号位,需先转成ushort再取反
修正后的C#代码示例
假设输入为IPv4头字节数组ipHeader、TCP头字节数组tcpHeader、负载字节数组payload,以下是正确计算逻辑:
public ushort CalculateTcpChecksum(byte[] ipHeader, byte[] tcpHeader, byte[] payload) { // 构造伪头部 List<byte> pseudoHeader = new List<byte>(); // 源IP(IPv4头第12-15字节) pseudoHeader.AddRange(ipHeader.Skip(12).Take(4)); // 目的IP(IPv4头第16-19字节) pseudoHeader.AddRange(ipHeader.Skip(16).Take(4)); // 保留字段(固定为0) pseudoHeader.Add(0); // 协议号(TCP为6) pseudoHeader.Add(6); // TCP总长度(TCP头+负载,转网络字节序) ushort tcpTotalLength = (ushort)(tcpHeader.Length + payload.Length); pseudoHeader.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)tcpTotalLength))); // 拼接校验计算用的完整数据 List<byte> checksumData = new List<byte>(pseudoHeader); // 复制TCP头并将校验和字段(第16-17字节)清零 byte[] tcpHeaderZeroed = (byte[])tcpHeader.Clone(); tcpHeaderZeroed[16] = 0; tcpHeaderZeroed[17] = 0; checksumData.AddRange(tcpHeaderZeroed); checksumData.AddRange(payload); // 按16位累加并处理进位 uint sum = 0; int i = 0; while (i < checksumData.Count) { // 按网络字节序解析16位值 ushort value = (ushort)((checksumData[i] << 8) | checksumData[i + 1]); sum += value; // 处理进位:将高16位叠加到低16位 if ((sum & 0xFFFF0000) != 0) { sum = (sum & 0xFFFF) + (sum >> 16); } i += 2; } // 处理奇数长度的末尾字节 if (checksumData.Count % 2 != 0) { sum += checksumData[checksumData.Count - 1]; if ((sum & 0xFFFF0000) != 0) { sum = (sum & 0xFFFF) + (sum >> 16); } } // 取反得到最终校验和(转ushort避免符号位干扰) return (ushort)~sum; }
关键验证点
- 伪头部TCP总长度:比如TCP头20字节+负载100字节,总长度120,转大端后应为
0x0078 - 累加字节序:网络字节序16位值高字节在前,需将第一个字节左移8位加第二个字节
- 进位处理:若累加后sum为
0x12345,需转换为0x2345 + 0x1 = 0x2346 - 取反操作:必须针对16位无符号数,避免C# int类型符号位的影响
内容的提问来源于stack exchange,提问作者UnSure
相关产品推荐
相关产品推荐

