Skip to content

Обзор TUN и IP-стека

TUN inbound создаёт виртуальный сетевой интерфейс и использует пользовательский 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["IP-стек gVisor<br/>(TCP/UDP)"]

    Stack -->|"TCP: gonet.TCPConn"| Handler["Обработчик TUN"]
    Stack -->|"UDP: необработанные пакеты"| UDPH["Обработчик UDP<br/>(Full-Cone)"]

    Handler -->|"DispatchLink()"| Dispatcher["Диспетчер Xray"]
    UDPH -->|"HandleConnection()"| Handler

    Dispatcher -->|"через маршрутизацию"| Outbound["Исходящее соединение<br/>(Freedom/VLESS/...)"]

Компоненты

TUN-интерфейс (tun.go, tun_*.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():

  • Linux (tun_linux.go): Использует ioctl для создания TUN-устройства
  • macOS (tun_darwin.go): Использует системный интерфейс utun
  • Windows (tun_windows.go): Использует драйвер Wintun
  • Android (tun_android.go): Использует fd, переданный из Java-слоя

IP-стек gVisor (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.go)

Обработчик связывает соединения 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 as Обработчик
    participant TUN as TUN-устройство
    participant Stack as Стек gVisor
    participant Dispatcher as Диспетчер

    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

TCP-форвардер gVisor перехватывает все 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)
}

Ключевое отличие от других inbound: 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 — платформозависимый: У каждой ОС свои API для создания TUN. На Android fd передаётся из VPN-сервиса.

  3. Нет слушающего порта: TUN inbound объявляет Network() []net.Network { return []net.Network{} } — он не слушает ни один порт. Трафик поступает с TUN-устройства.

  4. DispatchLink — синхронный: В отличие от Dispatch(), DispatchLink() блокирует выполнение. Горутина создаётся для каждого соединения (форвардером gVisor).

  5. Размеры TCP-буферов имеют значение: Размеры TCP-буферов gVisor (8 МБ RX, 6 МБ TX) настроены для высокой пропускной способности. Слишком маленькие → низкая производительность. Слишком большие → расход памяти.

  6. RACK/TLP отключены: TCPRecovery(0) отключает восстановление при потерях RACK/TLP, чтобы исправить зависания соединений под высокой нагрузкой — известная особенность gVisor.

Технический анализ для целей повторной реализации.