Skip to content

TUN 与 IP 协议栈概述

TUN 入站通过创建虚拟网络接口,并使用用户空间 IP 协议栈将原始 IP 数据包转换为应用层连接,供 Xray-core 进行路由分发。

源码proxy/tun/

架构

mermaid
flowchart LR
    App([应用程序]) -->|"IP 数据包"| TUN["TUN 接口<br/>(内核)"]
    TUN -->|"原始 L3 帧"| EP["Link Endpoint<br/>(gVisor 桥接)"]
    EP -->|"L3→L4"| Stack["gVisor IP 协议栈<br/>(TCP/UDP)"]

    Stack -->|"TCP: gonet.TCPConn"| Handler["TUN Handler"]
    Stack -->|"UDP: 原始数据包"| UDPH["UDP Handler<br/>(Full-Cone)"]

    Handler -->|"DispatchLink()"| Dispatcher["Xray Dispatcher"]
    UDPH -->|"HandleConnection()"| Handler

    Dispatcher -->|"经路由分发"| Outbound["出站<br/>(Freedom/VLESS/...)"]

组件

TUN 接口(tun.gotun_*.go

平台相关的 TUN 设备创建:

go
type Tun interface {
    Start() error  // begin reading/writing
    Close() error
    // Platform-specific: creates fd, sets MTU, etc.
}

type TunOptions struct {
    Name string  // e.g., "utun5" (macOS), "tun0" (Linux)
    MTU  uint32  // default: 1500
}

每个平台都有各自的 NewTun() 实现:

  • Linuxtun_linux.go):使用 ioctl 创建 TUN 设备
  • macOStun_darwin.go):使用 utun 系统接口
  • Windowstun_windows.go):使用 Wintun 驱动
  • Androidtun_android.go):使用从 Java 层传入的 fd

gVisor IP 协议栈(stack_gvisor.go

用户空间 TCP/IP 协议栈处理原始 IP 数据包:

go
type stackGVisor struct {
    ctx         context.Context
    tun         GVisorTun           // bridge to TUN device
    idleTimeout time.Duration
    handler     *Handler            // connection handler
    stack       *stack.Stack        // gVisor network stack
    endpoint    stack.LinkEndpoint  // link to TUN
}

Handler(handler.go

Handler 将 gVisor 连接桥接到 Xray 的调度器:

go
type Handler struct {
    ctx             context.Context
    config          *Config
    stack           Stack
    policyManager   policy.Manager
    dispatcher      routing.Dispatcher
    tag             string
    sniffingRequest session.SniffingRequest
}

生命周期

mermaid
sequenceDiagram
    participant Config
    participant Handler
    participant TUN as TUN 设备
    participant Stack as gVisor 协议栈
    participant Dispatcher

    Config->>Handler: Init(ctx, pm, dispatcher)
    Handler->>TUN: NewTun(options)
    TUN-->>Handler: tunInterface
    Handler->>Stack: NewStack(ctx, options, handler)
    Stack-->>Handler: stackGVisor
    Handler->>Stack: Start()
    Stack->>Stack: 创建 gVisor 协议栈<br/>(IPv4, IPv6, TCP, UDP)
    Stack->>Stack: 设置 TCP 转发器
    Stack->>Stack: 设置 UDP 转发器
    Handler->>TUN: Start()
    TUN->>TUN: 开始读取数据包

    Note over TUN,Stack: 运行中...

    TUN->>Stack: 原始 IP 数据包
    Stack->>Stack: 解析 L3/L4 头部
    alt TCP
        Stack->>Stack: TCP 握手
        Stack->>Handler: HandleConnection(tcpConn, dest)
    else UDP
        Stack->>Handler: HandlePacket(src, dst, data)
    end
    Handler->>Dispatcher: DispatchLink(ctx, dest, link)

TCP 处理

gVisor 的 TCP 转发器拦截所有 TCP SYN 数据包:

go
tcpForwarder := tcp.NewForwarder(ipStack, 0, 65535, func(r *tcp.ForwarderRequest) {
    go func(r *tcp.ForwarderRequest) {
        var wq waiter.Queue
        var id = r.ID()

        // Complete TCP 3-way handshake
        ep, err := r.CreateEndpoint(&wq)

        // Configure socket options
        options := ep.SocketOptions()
        options.SetKeepAlive(false)
        options.SetReuseAddress(true)

        // Create Go net.Conn from gVisor endpoint
        conn := gonet.NewTCPConn(&wq, ep)

        // Dispatch to Xray routing
        handler.HandleConnection(conn,
            net.TCPDestination(
                net.IPAddress(id.LocalAddress.AsSlice()),
                net.Port(id.LocalPort),
            ))

        ep.Close()
        r.Complete(false)
    }(r)
})

在 gVisor 侧,"本地地址"实际上是原始数据包的目的地址(即应用程序要连接的服务器地址)。

UDP 处理(Full-Cone NAT)

UDP 使用自定义处理器而非 gVisor 的转发器,以支持 Full-Cone NAT。详见 UDP Full-Cone NAT

HandleConnection(handler.go:104

每个连接(TCP 或 UDP)都经过此方法处理:

go
func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
    defer conn.Close()

    ctx := context.WithCancel(t.ctx)
    ctx = session.ContextWithInbound(ctx, &session.Inbound{
        Name:          "tun",
        Tag:           t.tag,
        CanSpliceCopy: 3,  // TUN cannot splice
        Source:        net.DestinationFromAddr(conn.RemoteAddr()),
    })
    ctx = session.ContextWithContent(ctx, &session.Content{
        SniffingRequest: t.sniffingRequest,
    })

    // Create transport link from connection
    link := &transport.Link{
        Reader: buf.NewReader(conn),
        Writer: buf.NewWriter(conn),
    }

    // Synchronous dispatch (blocks until transfer completes)
    t.dispatcher.DispatchLink(ctx, destination, link)
}

与其他入站的关键区别:TUN 使用 DispatchLink()(同步方式)而非 Dispatch()(异步方式),因为连接已经由 gVisor 建立完成。

gVisor 协议栈配置

go
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
    opts := stack.Options{
        NetworkProtocols:   []stack.NetworkProtocolFactory{
            ipv4.NewProtocol,
            ipv6.NewProtocol,
        },
        TransportProtocols: []stack.TransportProtocolFactory{
            tcp.NewProtocol,
            udp.NewProtocol,
        },
        HandleLocal: false,
    }
    gStack := stack.New(opts)

    // Create NIC (Network Interface Card)
    gStack.CreateNIC(defaultNIC, ep)

    // Route ALL traffic through this NIC
    gStack.SetRouteTable([]tcpip.Route{
        {Destination: header.IPv4EmptySubnet, NIC: defaultNIC},
        {Destination: header.IPv6EmptySubnet, NIC: defaultNIC},
    })

    // Enable spoofing (accept any destination IP)
    gStack.SetSpoofing(defaultNIC, true)
    // Enable promiscuous (accept any destination MAC)
    gStack.SetPromiscuousMode(defaultNIC, true)

    // TCP tuning
    gStack.SetTransportProtocolOption(tcp.ProtocolNumber,
        &tcpip.CongestionControlOption("cubic"))
    gStack.SetTransportProtocolOption(tcp.ProtocolNumber,
        &tcpip.TCPSACKEnabled(true))
    // ... buffer sizes, etc.
}

Spoofing + Promiscuous 模式至关重要:如果不启用,gVisor 只会接受发往自身 IP 的数据包,但 TUN 流量的目的 IP 是任意的。

Link Endpoint 将 gVisor 的数据包处理与 TUN 文件描述符桥接起来:

go
type tunEndpoint struct {
    tun        Tun
    dispatcher stack.NetworkDispatcher
    mtu        uint32
}
  • 入站方向:从 TUN fd 读取原始数据包 → 传递给 gVisor 协议栈
  • 出站方向:gVisor 生成响应数据包 → 写入 TUN fd

实现要点

  1. gVisor 是重量级依赖:它是一个完整的用户空间 TCP/IP 协议栈(约 100K+ 行代码)。可考虑 lwIP、smoltcp 或平台特定 API 等替代方案。

  2. TUN 是平台相关的:每个操作系统有不同的 TUN 创建 API。在 Android 上,fd 从 VPN 服务层传入。

  3. 无监听端口:TUN 入站声明 Network() []net.Network { return []net.Network{} }——它不监听任何端口,流量来自 TUN 设备。

  4. DispatchLink 是同步的:与 Dispatch() 不同,DispatchLink() 会阻塞。每个连接的 goroutine 由 gVisor 转发器创建。

  5. TCP 缓冲区大小很重要:gVisor 的 TCP 缓冲区大小(8MB 接收、6MB 发送)经过调优以获取高吞吐量。过小会导致性能下降,过大则浪费内存。

  6. RACK/TLP 已禁用TCPRecovery(0) 禁用了 RACK/TLP 丢包恢复机制,以修复高负载下的连接卡顿问题——这是一个已知的 gVisor 问题。

用于重新实现目的的技术分析。