Raw Socket手动构造TCP数据包收发内容不一致问题排查咨询
手动构造Raw Socket TCP数据包收发不匹配问题排查
问题背景
通过Raw Socket手动构造包含IP头、TCP头及Payload的TCP数据包,发送后接收端收到的数据包与发送包完全不匹配,具体差异包括:
- IP头部长度字段不一致
- TCP头部长度及标志位不匹配
- Payload缺失(预期为
Hello, World!)
发送数据包(十六进制)
45 00 00 35 00 00 00 00 FF 06 3C C2 00 00 00 00 7F 00 00 01 31 D4 39 30 29 00 00 00 00 00 00 00 50 18 FF FF A5 71 00 00 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21
接收数据包(十六进制)
45 00 00 3C F8 A9 00 00 80 FF 00 00 7F 00 00 01 7F 00 00 01 45 00 00 28 00 00 00 00 FF 06 BD CD 7F 00 00 01 7F 00 00 01 31 D4 39 30 29 00 00 00 00 00 00 2A 50 12 FF FF 1B BF 00 00
已执行操作
- 手动构造IP、TCP头部,设置源/目的IP、端口、序列号等字段
- 计算并设置IP和TCP头部的校验和
- 使用
recv函数接收数据包
问题解答
1. 收发数据包不一致的可能原因
- 系统自动修改/响应数据包:接收包为嵌套IP结构,大概率是系统返回的ICMP错误封装包,说明发送的数据包不符合TCP/IP规范,被网络栈拦截并返回错误
- Socket选项未正确配置:未开启
IP_HDRINCL选项时,系统会自动添加IP头部,导致实际发送的数据包与构造的包重复头部 - 数据包构造错误:IP/TCP头部字段(如总长度、头部长度、校验和)计算或填充错误,导致网络栈无法正确解析
- 接收端过滤失效:Raw Socket默认接收所有匹配的数据包,误收到了系统自动生成的响应包或其他进程的数据包
2. 数据包构造/接收流程的常见问题
构造阶段
- IP头部字段错误:
- 版本/头部长度字段(首字节
45):4表示IPv4,5代表头部长度为20字节,若实际头部长度与该值不符,会导致解析错位 - IP总长度字段:发送包中为
00 35(53字节),若实际总长度(IP头+TCP头+Payload)与该值不匹配,网络栈会丢弃或修改数据包 - 校验和计算错误:IP校验和仅覆盖IP头部,TCP校验和需包含伪头部(IP源/目的地址、协议号、TCP长度),伪头部构造错误会直接导致校验和无效
- 版本/头部长度字段(首字节
- TCP头部字段错误:
- TCP头部长度字段(TCP头第13字节高4位):设置错误会导致Payload偏移计算错误,无法正确读取内容
- 标志位与状态机不匹配:发送包标志位为
18(PSH+ACK),若未建立TCP连接就发送该标志位的包,系统会返回错误响应
- 字节序错误:IP/TCP头部的多字节字段(端口、序列号、长度、校验和)必须使用网络字节序(大端序),误用主机字节序会导致字段值完全错误
接收阶段
- 未设置过滤规则:Raw Socket未绑定源/目的IP、端口的过滤条件,导致收到无关数据包
- 接收缓冲区不足:
recv函数的缓冲区大小小于数据包长度,导致数据被截断 - 回环测试特殊处理:本地回环场景下,系统网络栈会对Raw Socket数据包进行特殊封装处理,返回错误包而非原始发送包
3. 有效调试方法
发送端验证
- 抓包对比实际发送包:用Wireshark/tcpdump在发送端抓包,对比构造的包与实际发出的包是否一致,确认是构造错误还是系统修改问题
- 强制自定义IP头部:开启
IP_HDRINCL选项(Windows/Linux均支持),禁止系统自动添加或修改IP头部 - 重新校验字段值:
- IP校验和:将IP头部校验和字段设为0,对头部按16位累加后取反得到正确值
- TCP校验和:构造伪头部(源IP、目的IP、0、协议号6、TCP总长度),拼接TCP头部与Payload,将校验和字段设为0后按16位累加取反
- 所有多字节字段用
htons()/htonl()转换为网络字节序
- 逐字段验证构造结果:打印
create_data_packet函数生成的数据包十六进制,与预期发送包逐字节对比
接收端验证
- 添加数据包过滤规则:通过
setsockopt设置BPF过滤规则,仅接收源/目的IP、端口匹配的数据包 - 解析接收包结构:手动解析接收包的IP头、协议字段,确认是否为目标包或系统错误响应包
- 扩大接收缓冲区:将
recv的缓冲区大小设置为至少1500字节(MTU最大值),避免数据截断 - 抓包验证传输链路:在接收端抓包,确认网络中传输的数据包是否正确,再对比接收代码的输出,排查是否为接收逻辑问题
附:相关代码片段
接收端代码
recv_len = receive_packet(sock, packet); if (recv_len < 0) { std::cerr << "Failed to receive packet: " << WSAGetLastError() << std::endl; return false; } // Output for debugging std::cout << "Raw packet data (hex): "; for (int i = 0; i < recv_len; ++i) { printf("%02X ", static_cast<unsigned char>(packet[i])); }
发送端代码
// Create the data packet create_data_packet(saddr, daddr, seq, ack, data, data_len, packet, packet_len); // Send the data packet to the destination address if (sendto(sock, packet.get(), packet_len, 0, reinterpret_cast<sockaddr*>(&daddr), sizeof(daddr)) == SOCKET_ERROR) { std::cerr << "Failed to send data packet: " << WSAGetLastError() << std::endl; closesocket(sock); // Close the socket before exiting WSACleanup(); // Clean up Winsock before exiting return 1; // Exit with an error code if sending the packet fails } std::cout << "Data packet sent successfully." << std::endl;
内容的提问来源于stack exchange,提问作者נהוראי יפרח
相关产品推荐
相关产品推荐

