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

基于原始套接字(SOCK_RAW)实现TCP握手时连接重置且recvfrom阻塞问题

解决RAW套接字实现TCP握手时内核自动发送RST的问题

嘿,这个问题我太熟悉了——用SOCK_RAW手动搞TCP握手时,内核总是抢着发RST,简直像个爱管闲事的家伙!你看到的RST根本不是你的程序发的,是操作系统内核搞的鬼,原因很简单:

当服务器返回的SYN+ACK报文到达你的机器时,内核检查本地的TCP连接表,发现没有对应这个四元组(源IP/端口、目的IP/端口)的连接记录,它就会认为这是一个无效的报文,直接发送RST来终止这个“不存在”的连接。同时,内核可能已经把SYN+ACK报文拦截了,根本没传到你的recvfrom()调用里,这就是程序阻塞的原因。

下面给你几个具体的解决步骤,按顺序来:

1. 先阻止内核处理目标SYN+ACK报文

最直接的方法是用iptables规则,把服务器发过来的SYN+ACK报文拦截,不让内核看到:

# 替换成你的目标服务器IP和端口
iptables -A INPUT -p tcp -s 服务器IP --sport 服务器端口 --tcp-flags SYN,ACK SYN,ACK -j DROP

这样内核就不会触发自动RST的逻辑,报文会被送到你的RAW套接字里。

2. 正确配置RAW套接字选项

创建套接字时,一定要开启IP_HDRINCL选项,让你手动构造完整的IP头部,避免内核自动添加的IP头和你构造的TCP头不匹配:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>

int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sockfd < 0) {
    perror("socket creation failed");
    exit(EXIT_FAILURE);
}

// 开启IP_HDRINCL,手动构造IP头
int opt = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt)) < 0) {
    perror("setsockopt IP_HDRINCL failed");
    close(sockfd);
    exit(EXIT_FAILURE);
}

3. 确保TCP报文的关键字段正确

手动构造TCP报文时,几个字段绝对不能错:

  • 序列号(seq):你发送SYN时的seq可以随机生成,比如用rand()或者时间戳
  • 确认号(ack):收到服务器的SYN+ACK后,ack必须是服务器的seq + 1
  • 标志位:SYN报文要把SYN位置1,SYN+ACK回复要把SYN和ACK位都置1,ACK报文把ACK位置1
  • 校验和:TCP校验和必须包含伪头部(IP源地址、目的地址、协议号、TCP长度),这个是高频出错点!

给你一个实用的TCP校验和计算函数:

#include <stdint.h>
#include <string.h>
#include <netinet/in.h>

uint16_t tcp_checksum(uint8_t *tcp_packet, int tcp_len, struct in_addr src_ip, struct in_addr dst_ip) {
    // 构造TCP伪头部
    uint8_t pseudo_header[12];
    memcpy(pseudo_header, &src_ip, 4);
    memcpy(pseudo_header + 4, &dst_ip, 4);
    pseudo_header[8] = 0;
    pseudo_header[9] = IPPROTO_TCP;
    uint16_t tcp_len_net = htons(tcp_len);
    memcpy(pseudo_header + 10, &tcp_len_net, 2);

    // 合并伪头部和TCP报文,计算校验和
    uint32_t sum = 0;
    int i;

    // 计算伪头部
    for (i = 0; i < 12; i += 2) {
        sum += ((uint16_t)pseudo_header[i] << 8) | pseudo_header[i + 1];
    }

    // 计算TCP报文
    for (i = 0; i < tcp_len; i += 2) {
        if (i + 1 < tcp_len) {
            sum += ((uint16_t)tcp_packet[i] << 8) | tcp_packet[i + 1];
        } else {
            sum += (uint16_t)tcp_packet[i] << 8;
        }
    }

    // 折叠校验和
    while (sum >> 16) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    return ~sum;
}

4. 确保recvfrom能捕获到报文

RAW套接字需要root权限运行,另外你可以在recvfrom()时过滤来源地址,只处理目标服务器的报文:

#define BUFFER_SIZE 4096
uint8_t buffer[BUFFER_SIZE];

struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&src_addr, &addr_len);

if (recv_len > 0) {
    // 检查来源是否是目标服务器
    if (src_addr.sin_addr.s_addr == inet_addr("服务器IP") && src_addr.sin_port == htons(服务器端口)) {
        // 处理SYN+ACK报文的逻辑写在这里
        printf("Received SYN+ACK from server\n");
    }
}

按照这几步来,你应该就能解决内核发RST和recvfrom()阻塞的问题了。

内容的提问来源于stack exchange,提问作者machacamoya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:57:53