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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:15:06