TUN 与 IP 协议栈概述
TUN 入站通过创建虚拟网络接口,并使用用户空间 IP 协议栈将原始 IP 数据包转换为应用层连接,供 Xray-core 进行路由分发。
源码:proxy/tun/
架构
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.go、tun_*.go)
平台相关的 TUN 设备创建:
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):使用从 Java 层传入的 fd
gVisor IP 协议栈(stack_gvisor.go)
用户空间 TCP/IP 协议栈处理原始 IP 数据包:
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 的调度器:
type Handler struct {
ctx context.Context
config *Config
stack Stack
policyManager policy.Manager
dispatcher routing.Dispatcher
tag string
sniffingRequest session.SniffingRequest
}生命周期
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 数据包:
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)都经过此方法处理:
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 协议栈配置
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(stack_gvisor_endpoint.go)
Link Endpoint 将 gVisor 的数据包处理与 TUN 文件描述符桥接起来:
type tunEndpoint struct {
tun Tun
dispatcher stack.NetworkDispatcher
mtu uint32
}- 入站方向:从 TUN fd 读取原始数据包 → 传递给 gVisor 协议栈
- 出站方向:gVisor 生成响应数据包 → 写入 TUN fd
实现要点
gVisor 是重量级依赖:它是一个完整的用户空间 TCP/IP 协议栈(约 100K+ 行代码)。可考虑 lwIP、smoltcp 或平台特定 API 等替代方案。
TUN 是平台相关的:每个操作系统有不同的 TUN 创建 API。在 Android 上,fd 从 VPN 服务层传入。
无监听端口:TUN 入站声明
Network() []net.Network { return []net.Network{} }——它不监听任何端口,流量来自 TUN 设备。DispatchLink 是同步的:与
Dispatch()不同,DispatchLink()会阻塞。每个连接的 goroutine 由 gVisor 转发器创建。TCP 缓冲区大小很重要:gVisor 的 TCP 缓冲区大小(8MB 接收、6MB 发送)经过调优以获取高吞吐量。过小会导致性能下降,过大则浪费内存。
RACK/TLP 已禁用:
TCPRecovery(0)禁用了 RACK/TLP 丢包恢复机制,以修复高负载下的连接卡顿问题——这是一个已知的 gVisor 问题。