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

如何使用gvisor的gonet包实现TCP拨号并创建自定义网络栈

基于gvisor自定义网络栈实现TCP连接及问题排查

一、用gvisor自定义网络栈+gonet.DialTCP建立TCP连接

替代标准库net.DialContext的实现,需要先初始化gvisor的网络栈,再通过gonet封装成标准库风格的连接。以下是完整示例:

package main

import (
    "context"
    "fmt"
    "net"

    "gvisor.dev/gvisor/pkg/tcpip"
    "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
    "gvisor.dev/gvisor/pkg/tcpip/stack"
    "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
    "gvisor.dev/gvisor/pkg/waiter"
)

func main() {
    // 1. 初始化gvisor网络栈,加载IPv4和TCP协议
    netStack := stack.New(stack.Options{
        NetworkProtocols:   []stack.NetworkProtocol{stack.NewARPProtocol(), stack.NewIPv4Protocol()},
        TransportProtocols: []stack.TransportProtocol{tcp.NewProtocol()},
    })

    // 2. 创建虚拟网卡(这里用loopback,如需TUN/TAP可替换为tun.New())
    linkEP, err := netStack.CreateLoopbackInterface()
    if err != nil {
        panic(fmt.Sprintf("创建网卡失败: %v", err))
    }

    // 3. 给网卡配置IP地址
    localIP := tcpip.Address(net.ParseIP("127.0.0.1").To4())
    if err := netStack.AddAddress(linkEP, tcpip.ProtocolNumber(4), localIP.WithPrefixLength(8)); err != nil {
        panic(fmt.Sprintf("配置IP失败: %v", err))
    }

    // 4. 设置默认路由,让栈能访问外部网络
    netStack.SetRouteTable([]tcpip.Route{
        {
            Destination: tcpip.AddressPrefix{Address: tcpip.Address(net.IPv4zero), PrefixLength: 0},
            Gateway:     tcpip.Address(0),
            NIC:         linkEP.ID(),
        },
    })

    // 5. 用gonet.DialTCP逻辑建立连接
    ctx := context.Background()
    remoteAddr := &net.TCPAddr{IP: net.ParseIP("8.8.8.8"), Port: 53}
    tcpRemoteAddr := tcpip.FullAddress{
        Addr: tcpip.Address(remoteAddr.IP.To4()),
        Port: uint16(remoteAddr.Port),
    }

    // 创建等待队列,用于处理连接事件
    wq := waiter.Queue{}
    // 创建TCP端点
    ep, err := netStack.NewEndpoint(tcp.ProtocolNumber, &wq)
    if err != nil {
        panic(fmt.Sprintf("创建TCP端点失败: %v", err))
    }
    defer ep.Close()

    // 发起TCP连接
    if err := ep.Connect(tcpRemoteAddr); err != nil {
        panic(fmt.Sprintf("连接失败: %v", err))
    }

    // 转为标准库风格的TCPConn,后续用法和net.Conn一致
    conn := gonet.NewTCPConn(&wq, ep)
    defer conn.Close()

    // 测试发送DNS查询包
    dnsQuery := []byte{0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01}
    if _, err := conn.Write(dnsQuery); err != nil {
        panic(fmt.Sprintf("发送数据失败: %v", err))
    }

    // 读取返回数据
    buf := make([]byte, 1024)
    n, err := conn.Read(buf)
    if err != nil {
        panic(fmt.Sprintf("读取数据失败: %v", err))
    }
    fmt.Printf("收到返回数据(十六进制): %x\n", buf[:n])
}

如果需要使用TUN/TAP设备而非loopback,将CreateLoopbackInterface()替换为TUN设备的创建逻辑,比如:

import "gvisor.dev/gvisor/pkg/tcpip/link/tun"

// 创建TUN设备,需提前在主机上创建或让代码自动创建
linkEP, err := tun.New("tun0", false /* 非TAP模式 */)

二、解决tun_tcp_connect示例收不到返回包的问题

你遇到的“只发不收”问题,大概率是网络路由、ARP配置或防火墙的问题,按以下步骤排查:

  • 检查TUN设备路由配置
    确保主机的路由表将目标IP的流量导向TUN设备,同时返回流量能回到TUN设备。执行以下命令:

    # 添加目标IP路由到TUN设备(假设设备名为tun0)
    ip route add 8.8.8.8/32 dev tun0
    # 开启主机IP转发
    sysctl -w net.ipv4.ip_forward=1
    
  • 配置静态ARP条目
    如果是纯IP模式的TUN设备(无以太网层),gvisor栈无法自动处理ARP请求,需要手动添加静态ARP:

    // 在设置路由后添加,gatewayIP为主机侧TUN设备的IP
    gatewayIP := tcpip.Address(net.ParseIP("192.168.123.1").To4())
    netStack.SetArpEntry(gatewayIP, linkEP.LinkAddress())
    
  • 关闭/调整防火墙
    主机防火墙可能拦截了TUN设备的入站流量,临时关闭测试:

    # 临时关闭iptables
    iptables -F
    iptables -X
    iptables -P INPUT ACCEPT
    iptables -P OUTPUT ACCEPT
    

    测试正常后再添加精细化规则,允许TUN设备的双向流量。

  • 核对网络参数一致性
    确保gvisor栈内的网卡IP、子网掩码和主机侧TUN设备的IP在同一网段,比如主机侧TUN IP是192.168.123.1/24,栈内网卡IP就要设为192.168.123.2/24,网关指向192.168.123.1。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 10:43:17