Go自定义TCP三次握手时自动发送RST包的原因及解决方法
环境
- 操作系统:MacOS
- Go版本:go 1.23.3
- Github项目:tcplay
求助内容
我在Go中不使用net包,基于socket从零构建自定义TCP包用于学习。实现TCP三次握手时,客户端发送SYN包给服务器后能收到SYN-ACK,但程序会自动发送RST包,而我并未显式实现该行为。请问:
- 为什么会自动发送RST包?
- 如何在自定义实现中阻止或处理该行为?
补充信息
我怀疑问题与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包?
设置IP_HDRINCL选项+绕过内核TCP栈:
创建raw socket时开启IP_HDRINCL选项,手动构造IP头和TCP头;同时绑定端口时设置SO_REUSEADDR和SO_REUSEPORT,避免内核占用目标端口。使用BPF过滤流量:
通过伯克利包过滤器(BPF)设置规则,只让你的程序接收目标端口的TCP包,阻止内核TCP栈处理这些流量。例如过滤源端口为服务器端口、目的端口为你使用的客户端端口的TCP包,这样内核不会收到该类包,也就不会发送RST。修改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

