You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FreeBSD下RAW Socket接收TCP数据包失败问题求助

问题分析与解决方案

核心问题

你的代码在FreeBSD下无法通过RAW Socket接收TCP数据包,主要源于代码逻辑错误和FreeBSD与Linux的RAW Socket行为差异。

一、代码逻辑硬伤

代码存在致命逻辑错误,导致程序接收到非TCP包或无效IP头时直接终止,而非跳过无效包继续接收:

  1. 检测到数据包协议非TCP时,直接关闭socket返回-1,而非跳过该包
  2. 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(&current_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也无法接收,大概率是以下原因:

  1. 未指定正确的网络接口(需绑定到数据包实际经过的网卡)
  2. 过滤规则设置错误(需设置为tcp)
  3. 未以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(&current_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 12:22:31