gVisor IP 协议栈
gVisor(gvisor.dev/gvisor)提供了完整的用户空间 TCP/IP 协议栈实现。Xray-core 使用它将来自 TUN 接口的原始 IP 数据包处理为应用层连接。
源码:proxy/tun/stack_gvisor.go、proxy/tun/stack_gvisor_endpoint.go
为什么选择 gVisor?
TUN 设备工作在第 3 层(IP 数据包),而 Xray-core 的代理协议工作在第 4 层及以上(TCP 流、UDP 数据报)。用户空间 IP 协议栈正是弥合这一差距的桥梁:
TUN 设备 → 原始 IP 数据包 (L3)
gVisor TCP/IP 协议栈 → TCP 连接、UDP 数据包 (L4)
Xray Handler → 应用层连接 (L7)协议栈架构
flowchart TB
subgraph TUN["TUN 设备(内核)"]
FD["文件描述符"]
end
subgraph Endpoint["Link Endpoint"]
RX["读取循环:<br/>TUN fd → gVisor"]
TX["写入: gVisor → TUN fd"]
end
subgraph gVisor["gVisor 协议栈"]
NIC["NIC(网络接口)"]
IPv4["IPv4 协议"]
IPv6["IPv6 协议"]
TCP["TCP 协议"]
UDP["UDP 协议"]
TCPFwd["TCP 转发器"]
UDPHandler["UDP 处理器"]
end
FD --> RX
RX --> NIC
NIC --> IPv4
NIC --> IPv6
IPv4 --> TCP
IPv4 --> UDP
IPv6 --> TCP
IPv6 --> UDP
TCP --> TCPFwd
UDP --> UDPHandler
TCPFwd -->|"gonet.TCPConn"| Handler["Xray TUN Handler"]
UDPHandler -->|"原始数据包数据"| UDPConn["UDP 连接处理器"]
gVisor -->|"响应数据包"| TX
TX --> FD协议栈创建
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
gStack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocol, // IPv4 support
ipv6.NewProtocol, // IPv6 support
},
TransportProtocols: []stack.TransportProtocolFactory{
tcp.NewProtocol, // TCP support
udp.NewProtocol, // UDP support
},
HandleLocal: false, // Don't special-case local addresses
})
// Create virtual NIC bound to our endpoint
gStack.CreateNIC(1, ep)
// Accept ALL destination IPs (route everything through this NIC)
gStack.SetRouteTable([]tcpip.Route{
{Destination: header.IPv4EmptySubnet, NIC: 1}, // 0.0.0.0/0
{Destination: header.IPv6EmptySubnet, NIC: 1}, // ::/0
})
// Critical: accept packets for any IP (we're a proxy, not a host)
gStack.SetSpoofing(1, true)
gStack.SetPromiscuousMode(1, true)
}TCP 调优
// Congestion control: CUBIC (standard)
gStack.SetTransportProtocolOption(tcp.ProtocolNumber,
&tcpip.CongestionControlOption("cubic"))
// Selective ACK (improves recovery from packet loss)
gStack.SetTransportProtocolOption(tcp.ProtocolNumber,
&tcpip.TCPSACKEnabled(true))
// Moderate receive buffer (auto-tune buffer sizes)
gStack.SetTransportProtocolOption(tcp.ProtocolNumber,
&tcpip.TCPModerateReceiveBufferOption(true))
// Disable RACK/TLP (workaround for gVisor stall bug)
gStack.SetTransportProtocolOption(tcp.ProtocolNumber,
&tcpip.TCPRecovery(0))
// Buffer sizes
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
Min: 4096, Default: 212992, Max: 8388608, // 4KB → 208KB → 8MB
}
tcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{
Min: 4096, Default: 212992, Max: 6291456, // 4KB → 208KB → 6MB
}Link Endpoint
Link Endpoint 是 TUN 文件描述符与 gVisor 数据包处理之间的桥梁:
type tunEndpoint struct {
tun Tun // TUN device
dispatcher stack.NetworkDispatcher // gVisor packet dispatcher
mtu uint32
}入站路径(TUN → gVisor)
func (ep *tunEndpoint) dispatchLoop() {
for {
// Read raw IP packet from TUN fd
packet := readFromTUN()
// Determine IP version from first nibble
var protocol tcpip.NetworkProtocolNumber
switch packet[0] >> 4 {
case 4: protocol = header.IPv4ProtocolNumber
case 6: protocol = header.IPv6ProtocolNumber
}
// Create PacketBuffer and deliver to gVisor
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(packet),
})
ep.dispatcher.DeliverNetworkPacket(protocol, pkt)
}
}出站路径(gVisor → TUN)
func (ep *tunEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
for _, pkt := range pkts {
// Serialize gVisor packet to bytes
data := pkt.ToView().AsSlice()
// Write to TUN fd
ep.tun.Write(data)
}
}TCP 转发器
所有 TCP 连接都由转发器拦截:
tcpForwarder := tcp.NewForwarder(ipStack,
0, // receive buffer size (0 = use default)
65535, // max in-flight connections
func(r *tcp.ForwarderRequest) {
go handleTCPConnection(r)
},
)
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)转发器的工作流程:
- 接收 SYN 数据包
- 创建 gVisor 端点(内部完成 TCP 三次握手)
- 将端点包装为
gonet.NewTCPConn()(实现net.Conn接口) - 传递给 Xray Handler
UDP 处理
UDP 不使用 gVisor 的转发器,而是在传输层协议处理器级别拦截数据包:
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber,
func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Data().AsRange().ToSlice()
src := net.UDPDestination(
net.IPAddress(id.RemoteAddress.AsSlice()),
net.Port(id.RemotePort),
)
dst := net.UDPDestination(
net.IPAddress(id.LocalAddress.AsSlice()),
net.Port(id.LocalPort),
)
return udpForwarder.HandlePacket(src, dst, data)
},
)为什么不使用 gVisor 的 UDP 转发器? 因为 gVisor 的转发器会为每个目的地址创建独立连接,这不支持 Full-Cone NAT(即来自任意地址的返回数据包都应被接受)。
原始 UDP 返回路径
对于 UDP 响应,Xray 必须构造原始 IP+UDP 数据包并注入回 gVisor 协议栈:
func (t *stackGVisor) writeRawUDPPacket(payload, src, dst) error {
// Build UDP header
udpHdr := header.UDP(...)
udpHdr.Encode(&header.UDPFields{
SrcPort: src.Port,
DstPort: dst.Port,
Length: udpLen,
})
// Calculate checksum
udpHdr.SetChecksum(...)
// Build IP header (v4 or v6)
if isIPv4 {
ipHdr := header.IPv4(...)
ipHdr.Encode(&header.IPv4Fields{
TotalLength: ...,
TTL: 64,
Protocol: header.UDPProtocolNumber,
SrcAddr: srcIP,
DstAddr: dstIP,
})
ipHdr.SetChecksum(...)
}
// Inject packet back into the stack
t.stack.WriteRawPacket(defaultNIC, ipProtocol, packetData)
}这个原始数据包通过 gVisor 协议栈返回到 TUN 设备,再传递给原始应用程序。
内存注意事项
gVisor 会为以下内容分配内存:
- 每个连接的 TCP 缓冲区(每个连接最多 8MB 接收 + 6MB 发送)
- 在途数据包的包缓冲区
- 协议状态(TCP 序列号、定时器等)
对于处理数千连接的代理来说,这些内存开销可能非常可观。缓冲区自动调优(TCPModerateReceiveBufferOption)通过从小缓冲区开始、按需增长来缓解这一问题。
实现要点
gVisor 是可选的:你也可以使用更简单的方案(lwIP、smoltcp),但 gVisor 提供了最完整的 TCP 实现(SACK、CUBIC、正确的重传机制等)。
Spoofing + Promiscuous 模式是必须的:如果不启用,gVisor 会拒绝不是发往已知 IP 的数据包。作为代理,每个目的 IP 都是有效的。
RACK/TLP 变通方案:禁用 RACK/TLP 恢复机制(
TCPRecovery(0))是针对 gVisor 在高负载下连接卡顿 bug 的变通方案。需关注新版 gVisor 是否已修复此问题。通过原始数据包处理 UDP:自定义 UDP 处理(绕过 gVisor 的 UDP 转发器)是 Full-Cone NAT 所必需的。原始数据包的构造(IP+UDP 头部、校验和)必须正确,否则数据包会被丢弃。
MTU 很重要:TUN 的 MTU(默认 1500)影响最大数据包大小。MSS 由 MTU 推导得出。MTU 不匹配会导致分片或丢包。