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

Go自定义TCP三次握手时自动发送RST包的原因及解决方法

自定义TCP实现中自动发送RST包的问题

环境

  • 操作系统:MacOS
  • Go版本:go 1.23.3
  • Github项目:tcplay

求助内容

我在Go中不使用net包,基于socket从零构建自定义TCP包用于学习。实现TCP三次握手时,客户端发送SYN包给服务器后能收到SYN-ACK,但程序会自动发送RST包,而我并未显式实现该行为。请问:

  1. 为什么会自动发送RST包?
  2. 如何在自定义实现中阻止或处理该行为?

补充信息

我怀疑问题与Go或操作系统处理socket及未确认状态的方式有关,希望得到指导建议。

相关代码

func (c *TCPConnection) receivePacket() (*TCPHeader, error) {
    log.Println("Entering receivePacket function")
    defer log.Println("Exiting receivePacket function")

    buf := make([]byte, 65535)
    for {

        log.Println("Receiving packet...")
// program stuck here
// it doesnt receive any packet
        n, _, err := syscall.Recvfrom(c.rawSocket, buf, 0)
        if err != nil {
            log.Printf("Error receiving packet: %v", err)
            return nil, fmt.Errorf("failed to receive packet: %v", err)
        }

        log.Printf("Received %d bytes", n)
        if n < 20 {
            log.Println("Packet too small, skipping")
            continue
        }

        ipHeaderLen := int(buf[0]&0x0F) * 4
        if n < ipHeaderLen+20 {
            log.Printf("Packet size (%d) is smaller than IP header length (%d), skipping", n, ipHeaderLen+20)
            continue
        }

        ipProtocol := buf[9]
        if ipProtocol != 6 { // TCP protocol number
            log.Printf("Protocol is not TCP (protocol number: %d), skipping", ipProtocol)
            continue
        }

        tcpHeaderData := buf[ipHeaderLen : ipHeaderLen+20]

        tcpHeader := &TCPHeader{
            SourcePort:   binary.BigEndian.Uint16(tcpHeaderData[0:2]),
            DestPort:     binary.BigEndian.Uint16(tcpHeaderData[2:4]),
            SeqNum:       binary.BigEndian.Uint32(tcpHeaderData[4:8]),
            AckNum:       binary.BigEndian.Uint32(tcpHeaderData[8:12]),
            HeaderLen:    tcpHeaderData[12] >> 4,
            ControlFlags: tcpHeaderData[13] & 0x3F,
            WindowSize:   binary.BigEndian.Uint16(tcpHeaderData[14:16]),
            Checksum:     binary.BigEndian.Uint16(tcpHeaderData[16:18]),
            UrgentPtr:    binary.BigEndian.Uint16(tcpHeaderData[18:20]),
        }

        log.Printf("Parsed TCP header: %+v", tcpHeader)

        if tcpHeader.SourcePort == c.destPort && tcpHeader.DestPort == c.srcPort {
            log.Println("Matching packet found, returning TCP header")
            return tcpHeader, nil
        } else {
            log.Printf("Packet does not match. SourcePort=%d, DestPort=%d", tcpHeader.SourcePort, tcpHeader.DestPort)
        }
    }
}

已尝试操作

  • 使用raw socket发送SYN包;
  • 服务器采用examples文件夹中基于net包的简单示例应用;
  • 通过抓包确认已收到SYN-ACK,但系统自动发送了RST包。

问题原因与解决方法

为什么会自动发送RST包?

当操作系统内核收到SYN-ACK包,但本地没有对应的TCP连接条目(内核不知道你用raw socket手动发起的连接)时,会判定这是无效数据包,自动回复RST包拒绝该“不存在”的连接。

你的程序用raw socket直接构造TCP包发送,但内核TCP协议栈完全感知不到这个连接,所以服务器返回SYN-ACK时,内核查不到对应连接,就会自动发送RST终止连接。同时,你的receivePacket函数卡在syscall.Recvfrom,是因为内核优先截获了SYN-ACK包处理,没有将其传给用户空间的raw socket程序。

如何阻止自动发送RST包?

  1. 设置IP_HDRINCL选项+绕过内核TCP栈:
    创建raw socket时开启IP_HDRINCL选项,手动构造IP头和TCP头;同时绑定端口时设置SO_REUSEADDR和SO_REUSEPORT,避免内核占用目标端口。

  2. 使用BPF过滤流量:
    通过伯克利包过滤器(BPF)设置规则,只让你的程序接收目标端口的TCP包,阻止内核TCP栈处理这些流量。例如过滤源端口为服务器端口、目的端口为你使用的客户端端口的TCP包,这样内核不会收到该类包,也就不会发送RST。

  3. 修改sysctl全局参数(MacOS):
    执行以下命令设置net.inet.tcp.blackhole为2,让内核忽略无对应连接的TCP包,不发送RST:

    sudo sysctl -w net.inet.tcp.blackhole=2
    

    注意该参数是全局生效,测试完成后建议改回默认值(0)。

代码优化建议

你的receivePacket无法接收包,核心原因是内核没把SYN-ACK传给raw socket。在MacOS下,需要结合BPF过滤或socket选项,让数据包绕过内核TCP栈直接到达你的程序。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 23:33:16