Skip to content

传输层架构概述

简介

Xray-core 的传输层提供了一套可插拔的网络通信抽象。每种传输协议(TCP、WebSocket、gRPC、HTTPUpgrade、SplitHTTP、mKCP)在启动时将自身注册到全局注册表中,核心层根据配置分发拨号/监听调用。独立的安全层(TLS、REALITY)可以独立地包装任何传输协议。本文档涵盖注册表模式、核心接口、MemoryStreamConfig 配置对象,以及运行时如何选择传输协议。

关键接口

Dialer 接口

Dialer 接口(transport/internet/dialer.go:22-31)是出站连接的高层抽象:

go
type Dialer interface {
    Dial(ctx context.Context, destination net.Destination) (stat.Connection, error)
    DestIpAddress() net.IP
    SetOutboundGateway(ctx context.Context, ob *session.Outbound)
}

在内部,传输层为每个协议的拨号器使用更底层的函数签名:

go
// dialer.go:34
type dialFunc func(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error)

Listener 接口

Listener 接口(transport/internet/tcp_hub.go:25-28)表示服务端监听器:

go
type Listener interface {
    Close() error
    Addr() net.Addr
}

每种传输协议还注册一个 ListenFunctcp_hub.go:23):

go
type ListenFunc func(ctx context.Context, address net.Address, port net.Port,
    settings *MemoryStreamConfig, handler ConnHandler) (Listener, error)

其中 ConnHandler 的定义为 func(stat.Connection)

SystemDialer 接口

SystemDialertransport/internet/system_dialer.go:18-21)是进行操作系统级别实际连接的最底层抽象:

go
type SystemDialer interface {
    Dial(ctx context.Context, source net.Address, destination net.Destination,
        sockopt *SocketConfig) (net.Conn, error)
    DestIpAddress() net.IP
}

默认实现(DefaultSystemDialer)通过 RawConn.Control 应用套接字选项后调用 Go 的 net.Dialer.DialContext

注册表模式

传输拨号器注册表

每种传输协议在 init() 函数中通过 RegisterTransportDialerdialer.go:39-45)注册其拨号器:

go
var transportDialerCache = make(map[string]dialFunc)

func RegisterTransportDialer(protocol string, dialer dialFunc) error {
    if _, found := transportDialerCache[protocol]; found {
        return errors.New(protocol, " dialer already registered").AtError()
    }
    transportDialerCache[protocol] = dialer
    return nil
}

来自 transport/internet/tcp/dialer.go:110-112 的示例:

go
func init() {
    common.Must(internet.RegisterTransportDialer(protocolName, Dial))
}

传输监听器注册表

监听器通过 RegisterTransportListenertcp_hub.go:13-19)以类似方式注册:

go
var transportListenerCache = make(map[string]ListenFunc)

func RegisterTransportListener(protocol string, listener ListenFunc) error {
    if _, found := transportListenerCache[protocol]; found {
        return errors.New(protocol, " listener already registered.").AtError()
    }
    transportListenerCache[protocol] = listener
    return nil
}

配置创建器注册表

每种传输协议还注册一个配置工厂(config.go:32-38):

go
var globalTransportConfigCreatorCache = make(map[string]ConfigCreator)

func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {
    if _, found := globalTransportConfigCreatorCache[name]; found {
        return errors.New("protocol ", name, " is already registered").AtError()
    }
    globalTransportConfigCreatorCache[name] = creator
    return nil
}

此函数在每种传输协议的 config.goinit() 函数中被调用,例如 transport/internet/tcp/config.go:8-12

已注册的协议名称

传输协议协议名称拨号器文件监听器文件
TCP"tcp"tcp/dialer.gotcp/hub.go
WebSocket"websocket"websocket/dialer.gowebsocket/hub.go
gRPC"grpc"grpc/dial.gogrpc/hub.go
HTTPUpgrade"httpupgrade"httpupgrade/dialer.gohttpupgrade/hub.go
SplitHTTP"splithttp"splithttp/dialer.gosplithttp/hub.go
mKCP"mkcp"kcp/dialer.gokcp/listener.go

MemoryStreamConfig

MemoryStreamConfigtransport/internet/memory_settings.go:9-19)是 protobuf StreamConfig 的已解析内存形式,避免了运行时重复的 protobuf 反序列化:

go
type MemoryStreamConfig struct {
    Destination      *net.Destination
    ProtocolName     string
    ProtocolSettings interface{}
    SecurityType     string
    SecuritySettings interface{}
    TcpmaskManager   *finalmask.TcpmaskManager
    UdpmaskManager   *finalmask.UdpmaskManager
    SocketSettings   *SocketConfig
    DownloadSettings *MemoryStreamConfig
}

关键字段:

  • ProtocolName:例如 "tcp""websocket""grpc" —— 决定使用哪个已注册的拨号器/监听器。
  • ProtocolSettings:传输协议特定的配置(如 *tcp.Config*websocket.Config),通过类型断言进行转换。
  • SecurityType / SecuritySettings"tls""reality" 及对应的配置对象。
  • SocketSettings:底层套接字选项(SocketConfig protobuf)。
  • DownloadSettings:SplitHTTP 用于单独下载流配置。
  • TcpmaskManager / UdpmaskManager:数据包混淆层(finalmask)。

转换函数 ToMemoryStreamConfigmemory_settings.go:22-78)解析 protobuf StreamConfig

go
func ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) {
    ets, err := s.GetEffectiveTransportSettings()
    // ... 从 StreamConfig 字段构建 MemoryStreamConfig
}

传输协议选择流程

出站(拨号)

主入口点是 internet.Dialdialer.go:48-75):

go
func Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error) {
    if dest.Network == net.Network_TCP {
        if streamSettings == nil {
            s, _ := ToMemoryStreamConfig(nil)
            streamSettings = s
        }
        protocol := streamSettings.ProtocolName
        dialer := transportDialerCache[protocol]
        if dialer == nil {
            return nil, errors.New(protocol, " dialer not registered")
        }
        return dialer(ctx, dest, streamSettings)
    }
    if dest.Network == net.Network_UDP {
        udpDialer := transportDialerCache["udp"]
        // ...
    }
}

streamSettings 为 nil 时,ToMemoryStreamConfig(nil) 会生成默认配置,其中 ProtocolName = "tcp"(来自 config.go:58-64)。

入站(监听)

监听通过 ListenTCPListenUnixtcp_hub.go:52-79)进行:

go
func ListenTCP(ctx context.Context, address net.Address, port net.Port,
    settings *MemoryStreamConfig, handler ConnHandler) (Listener, error) {
    // ...
    protocol := settings.ProtocolName
    listenFunc := transportListenerCache[protocol]
    listener, err := listenFunc(ctx, address, port, settings, handler)
    return listener, nil
}

配置解析

StreamConfig.GetEffectiveProtocol 方法(config.go:58-64)默认返回 "tcp"

go
func (c *StreamConfig) GetEffectiveProtocol() string {
    if c == nil || len(c.ProtocolName) == 0 {
        return "tcp"
    }
    return c.ProtocolName
}

传输协议特定的设置通过 GetTransportSettingsForconfig.go:71-81)获取,该方法遍历 TransportSettings 列表寻找匹配的协议名称。如果未找到,则通过已注册的 ConfigCreator 创建默认配置。

安全设置通过 GetEffectiveSecuritySettingsconfig.go:83-90)以类似方式解析。

架构图

mermaid
flowchart TD
    subgraph "应用层"
        OUT[出站处理器]
        IN[入站处理器]
    end

    subgraph "传输分发"
        DIAL["internet.Dial()"]
        LISTEN["internet.ListenTCP()"]
        MSC[MemoryStreamConfig]
    end

    subgraph "注册表 (map[string]func)"
        DR[transportDialerCache]
        LR[transportListenerCache]
        CR[globalTransportConfigCreatorCache]
    end

    subgraph "传输协议实现"
        TCP["tcp.Dial / tcp.ListenTCP"]
        WS["websocket.Dial / websocket.ListenWS"]
        GRPC["grpc.Dial / grpc.Listen"]
        HU["httpupgrade.Dial / httpupgrade.ListenHTTPUpgrade"]
        SH["splithttp.Dial / splithttp.ListenXH"]
        KCP["kcp.DialKCP / kcp.ListenKCP"]
    end

    subgraph "安全层"
        TLS["tls.Client / tls.Server"]
        REALITY["reality.UClient / reality.Server"]
    end

    subgraph "系统层"
        SD["internet.DialSystem"]
        SL["internet.ListenSystem"]
        OS["OS net.Dialer / net.ListenConfig"]
    end

    OUT --> DIAL
    IN --> LISTEN
    DIAL --> MSC --> DR
    LISTEN --> MSC --> LR

    DR --> TCP & WS & GRPC & HU & SH & KCP
    LR --> TCP & WS & GRPC & HU & SH & KCP

    TCP --> TLS & REALITY
    TCP --> SD
    WS --> TLS
    WS --> SD
    GRPC --> TLS & REALITY
    GRPC --> SD
    HU --> TLS
    HU --> SD
    SH --> TLS & REALITY
    SH --> SD
    KCP --> TLS
    KCP --> SL

    SD --> OS
    SL --> OS

DomainStrategy 与 DNS 解析

在进行系统级连接之前,DialSystemdialer.go:227-283)根据配置的 DomainStrategy 处理 DNS 解析。策略表(config.go:15-28)包含 11 种选项:

策略行为优先回退
AsIs不解析--
UseIP解析两种IPv4+IPv6
UseIPv4解析 v4IPv4
UseIPv6解析 v6IPv6
UseIPv4v6解析 v4,回退 v6IPv4IPv6
UseIPv6v4解析 v6,回退 v4IPv6IPv4
ForceIP/v4/v6/v4v6/v6v4同上但无结果时报错......

当启用 Happy Eyeballs 且解析到多个 IP 时,将调用 TcpRaceDial 进行并发连接尝试(参见 TCP 传输)。

实现说明

  • 线程安全:注册表映射(transportDialerCachetransportListenerCacheglobalTransportConfigCreatorCache)仅在 init() 期间写入,因此运行时读取不需要互斥锁。
  • nil StreamConfig:向 DialListenTCP 传入 nil 会自动创建默认的 TCP 配置。
  • DialerProxyDialSystemdialer.go:271-280)支持通过 sockopt.DialerProxy 链接到另一个出站处理器,实现代理套代理的拓扑结构。
  • AddressPortStrategyDialSystem 可以使用 SRV 或 TXT DNS 记录覆盖目标地址/端口(dialer.go:139-224)。
  • SystemDialer 替换UseAlternativeSystemDialersystem_dialer.go:232-237)允许替换整个系统拨号器,在 Android 等平台上使用。
  • PacketConnWrapper:UDP "连接"实际上是 ListenPacket + WriteTo 包装成 net.Conn 接口(system_dialer.go:152-204)。

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