FreeBSD下RAW Socket接收TCP数据包失败问题求助
问题分析与解决方案
核心问题
你的代码在FreeBSD下无法通过RAW Socket接收TCP数据包,主要源于代码逻辑错误和FreeBSD与Linux的RAW Socket行为差异。
一、代码逻辑硬伤
代码存在致命逻辑错误,导致程序接收到非TCP包或无效IP头时直接终止,而非跳过无效包继续接收:
- 检测到数据包协议非TCP时,直接关闭socket返回-1,而非跳过该包
- IP头长度无效时,同样直接终止程序
在Linux下,SOCK_RAW + IPPROTO_TCP套接字仅传递TCP包,该问题未暴露;但FreeBSD的RAW Socket行为不同,可能收到其他类型包或解析异常,导致程序直接退出,看起来像是未收到TCP包。
修复方式
将以下错误逻辑:
if (iphdrlen < 20) { close(sock); return -1; } /* Verification of the received packet's protocol. */ if (iph->protocol != IPPROTO_TCP) { close(sock); return -1; }
修改为:
if (iphdrlen < 20) { // 跳过无效IP头的数据包,继续接收下一个 continue; } /* Verification of the received packet's protocol. */ if (iph->protocol != IPPROTO_TCP) { // 跳过非TCP数据包,继续接收下一个 continue; }
同时,原代码用time(NULL)(秒级精度)计算毫秒级超时,精度不足,建议替换为gettimeofday实现精确计时:
struct timeval start_time; gettimeofday(&start_time, NULL); // 循环内计算超时: struct timeval current_time; gettimeofday(¤t_time, NULL); long elapsed_time = (current_time.tv_sec - start_time.tv_sec) * 1000 + (current_time.tv_usec - start_time.tv_usec) / 1000; if (elapsed_time >= recv_timeout_ms) { // 超时处理 }
二、FreeBSD RAW Socket的特性限制
FreeBSD与Linux的RAW Socket对TCP数据包的处理逻辑存在差异:
- Linux下,
SOCK_RAW + IPPROTO_TCP套接字可接收所有TCP数据包,无论内核TCP栈是否处理 - FreeBSD下,默认仅将内核TCP栈无法处理的TCP包(如无对应监听端口的SYN、RST包)传递给用户态RAW Socket;已被内核TCP栈处理的包(如已建立连接的数据包)不会转发给RAW Socket
解决方式
要让FreeBSD的RAW Socket接收所有TCP数据包,需启用TCP_RECVALL套接字选项(FreeBSD 10+支持,需root权限),在创建socket后添加以下代码:
// 启用TCP_RECVALL选项,接收所有TCP数据包 int recvall = 1; if (setsockopt(sock, IPPROTO_TCP, TCP_RECVALL, &recvall, sizeof(recvall)) == -1) { #ifdef PRINT_ERRORS perror("recv_tcp_packet/setsockopt-error(TCP_RECVALL)"); #endif close(sock); return -1; }
如果TCP_RECVALL不可用,可尝试启用IP层的IP_RECVALL选项(会接收所有IP包,需自行过滤TCP):
int recvall = 1; if (setsockopt(sock, IPPROTO_IP, IP_RECVALL, &recvall, sizeof(recvall)) == -1) { #ifdef PRINT_ERRORS perror("recv_tcp_packet/setsockopt-error(IP_RECVALL)"); #endif close(sock); return -1; }
三、关于pcap接收失败的说明
如果使用pcap也无法接收,大概率是以下原因:
- 未指定正确的网络接口(需绑定到数据包实际经过的网卡)
- 过滤规则设置错误(需设置为
tcp) - 未以root权限运行程序
修改后的关键代码片段
int recv_tcp_packet(const char* dest_ip, int recv_timeout_ms, unsigned char** buffer) { /* Set target. */ struct in_addr dest; dest.s_addr = inet_addr(dest_ip); /* Temporary buffer */ unsigned char* read_buffer = *buffer; /* Creation sock. */ int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (sock == -1) { #ifdef PRINT_ERRORS perror("recv_tcp_packet/create-socket-error"); #endif return -1; } // 启用TCP_RECVALL,接收所有TCP数据包 int recvall = 1; if (setsockopt(sock, IPPROTO_TCP, TCP_RECVALL, &recvall, sizeof(recvall)) == -1) { #ifdef PRINT_ERRORS perror("recv_tcp_packet/setsockopt-error(TCP_RECVALL)"); #endif close(sock); return -1; } /* Set timeout on socket. */ struct timeval timeout; timeout.tv_sec = recv_timeout_ms / 1000; timeout.tv_usec = (recv_timeout_ms % 1000) * 1000; int result = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); if (result == -1) { #ifdef PRINT_ERRORS perror("recv_tcp_packet/setsockopt-error(timeout)"); #endif close(sock); return -1; } struct sockaddr saddr; struct timeval start_time; gettimeofday(&start_time, NULL); /* Infinite loop, on accepting all TCP packets. */ for (;;) { /* Updating the IP size with each new packet. */ int saddr_size = sizeof(saddr); int data_size = recvfrom(sock, read_buffer, RECV_BUFFER_SIZE, 0, &saddr, (socklen_t*)&saddr_size); if (data_size == -1) { #ifdef PRINT_ERRORS perror("recv_tcp_packet/recvfrom-error"); #endif close(sock); return -1; } /* Creating an IP stub to verify packet sorting. */ struct ip_header* iph = (struct ip_header*)read_buffer; unsigned short iphdrlen = (iph->ihl) * 4; if (iphdrlen < 20) { // 跳过无效IP头的数据包 continue; } /* Verification of the received packet's protocol. */ if (iph->protocol != IPPROTO_TCP) { // 跳过非TCP数据包 continue; } /* Obtains the sender's IP from the RECEIVED packet. */ struct sockaddr_in source; memset(&source, 0, sizeof(source)); source.sin_addr.s_addr = iph->saddr; /* Comparing the IP from the received packet with the IP * to which the packet was sent. */ if (source.sin_addr.s_addr != dest.s_addr) { struct timeval current_time; gettimeofday(¤t_time, NULL); long elapsed_time = (current_time.tv_sec - start_time.tv_sec) * 1000 + (current_time.tv_usec - start_time.tv_usec) / 1000; /* Timeout on a windowless loop where packets are received. */ if (elapsed_time >= recv_timeout_ms) { #ifdef PRINT_ERRORS printf("recv_tcp_packet/timeout: a timeout on the packet receiving loop has occurred.\n"); #endif close(sock); return -1; } continue; } else { #ifdef PACKET_TRACE struct tcp_header* tcph = (struct tcp_header*)(read_buffer + iphdrlen); // 修正指针位置:跳过IP头 char ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(source.sin_addr), ip_str, INET_ADDRSTRLEN); printf("TCP & RCVD %d bytes from %s: ttl=%d ident=%d window=%d seq=%u sum=0x%x\n", data_size, ip_str, iph->ttl, ntohs(iph->id), ntohs(tcph->window), ntohl(tcph->seq), ntohs(iph->check)); #endif /* Success read, fill the buffer. */ *buffer = read_buffer; close(sock); return data_size; // 返回实际接收字节数更合理 } } close(sock); return -1; }
注:原代码中TCP头指针计算错误,需跳过IP头长度;打印时需用ntohs/ntohl转换网络字节序到主机字节序,否则数值显示异常。
内容的提问来源于stack exchange,提问作者lomaster
相关产品推荐
相关产品推荐

