使用ipv4.NewRawConn与gopacket实现TCP三次握手遇阻求助
问题:Go手动构造TCP SYN包后无法接收SYN+ACK,客户端自动回复RST
问题描述
尝试通过Go语言结合github.com/google/gopacket手动构造TCP SYN包完成三次握手,使用nc -vvln 2222作为监听服务端。运行程序后,iprawconn.ReadFrom(buf)无法读取服务端返回的SYN+ACK包,Wireshark捕获到客户端自动回复了RST包。
代码示例
package main import ( "fmt" "log" "net" "github.com/google/gopacket" "github.com/google/gopacket/layers" "golang.org/x/net/ipv4" ) func main() { proto := layers.IPProtocolTCP c, err := net.ListenPacket(fmt.Sprintf("ip4:%d", proto), "127.0.0.1") if err != nil { log.Fatal(err) } defer c.Close() iprawconn, err := ipv4.NewRawConn(c) if err != nil { log.Fatal(err) } tcp := layers.TCP{ SrcPort: 43124, DstPort: 2222, SYN: true, ACK: false, Window: 65535, Seq: 231, } // payload := []byte{'a', 'b', 'c', '\n'} options := gopacket.SerializeOptions{ // ComputeChecksums: true, FixLengths: true, } buffer := gopacket.NewSerializeBuffer() err = gopacket.SerializeLayers(buffer, options, &tcp, // gopacket.Payload(payload), ) if err != nil { fmt.Printf("[-] Serialize error: %s\n", err.Error()) return } b := buffer.Bytes() fmt.Println(b) h := &ipv4.Header{ Version: ipv4.Version, Len: ipv4.HeaderLen, TotalLen: ipv4.HeaderLen + len(b), // ID: 12345, Protocol: int(proto), // Src: net.ParseIP("192.168.25.1").To4(), Dst: net.ParseIP("127.0.0.1").To4(), TTL: 64, } if err := iprawconn.WriteTo(h, b, nil); err != nil { log.Println(err) } buf := make([]byte, 1024) for { _, p, _, err := iprawconn.ReadFrom(buf) if err != nil { fmt.Printf("Error reading: %#v\n", err) return } fmt.Printf("Message received: %s\n", string(p)) fmt.Printf("Message received: %s\n", string(buf)) } }
错误原因及修复方案
1. TCP校验和未计算
代码中注释了ComputeChecksums: true,导致TCP包的校验和无效。服务端收到无效SYN包后可能直接丢弃,即使回复SYN+ACK,客户端内核也会因校验错误触发异常处理。另外,TCP校验和需要基于IP伪头部(包含源/目的IP等信息)计算,因此必须给TCP层指定源IP和目的IP:
tcp := layers.TCP{ SrcPort: 43124, DstPort: 2222, SYN: true, ACK: false, Window: 65535, Seq: 231, SrcIP: net.ParseIP("127.0.0.1").To4(), // 添加源IP DstIP: net.ParseIP("127.0.0.1").To4(), // 添加目的IP } options := gopacket.SerializeOptions{ ComputeChecksums: true, // 开启校验和计算 FixLengths: true, }
2. IP头部未指定源地址
IP头部的Src字段被注释,虽然内核可能自动填充,但手动构造IP包时明确指定源地址是必要的,同时能保证TCP伪头部计算的正确性:
h := &ipv4.Header{ Version: ipv4.Version, Len: ipv4.HeaderLen, TotalLen: ipv4.HeaderLen + len(b), Protocol: int(proto), Src: net.ParseIP("127.0.0.1").To4(), // 启用源IP设置 Dst: net.ParseIP("127.0.0.1").To4(), TTL: 64, }
3. 内核自动回复RST包
这是核心问题:当服务端回复SYN+ACK时,客户端系统内核检查到没有对应本地端口43124的socket连接,会自动发送RST包终止连接,而你的RawConn无法先于内核捕获到SYN+ACK包。
解决方法是使用gopacket/pcap库直接操作网卡,绕过内核TCP栈:
- 打开本地网卡,设置BPF过滤规则捕获目标TCP包
- 手动发送SYN包后,通过pcap捕获SYN+ACK
- 再手动构造ACK包回复,完成三次握手
简化示例思路:
// 打开本地回环网卡 handle, err := pcap.OpenLive("lo", 1600, true, pcap.BlockForever) if err != nil { log.Fatal(err) } defer handle.Close() // 设置过滤规则,只捕获目标端口的SYN/ACK包 err = handle.SetBPFFilter("tcp port 2222 and tcp[tcpflags] & (tcp-syn|tcp-ack) != 0") if err != nil { log.Fatal(err) } // 序列化并发送构造好的SYN包(包含正确校验和、IP/TCP头部) // ... // 捕获SYN+ACK包并回复ACK packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { tcpLayer := packet.Layer(layers.LayerTypeTCP) if tcpLayer == nil { continue } tcp, _ := tcpLayer.(*layers.TCP) if tcp.SYN && tcp.ACK { // 构造ACK包:Seq为原SYN的Seq+1,ACK为SYN+ACK的Seq+1 ackTCP := layers.TCP{ SrcPort: tcp.DstPort, DstPort: tcp.SrcPort, SYN: false, ACK: true, Seq: 231 + 1, Ack: tcp.Seq + 1, Window: 65535, SrcIP: net.ParseIP("127.0.0.1").To4(), DstIP: net.ParseIP("127.0.0.1").To4(), } // 序列化IP和TCP层并发送ACK包 // ... break } }
4. 序列号处理
回复ACK包时,必须使用正确的序列号规则:ACK号应为服务端SYN+ACK包的Seq值+1,客户端自己的Seq值应为原SYN包的Seq值+1。
内容的提问来源于stack exchange,提问作者mirobertod
相关产品推荐
相关产品推荐

