نظرة عامة على TUN ومكدس IP
يُنشئ مدخل TUN واجهة شبكة افتراضية ويستخدم مكدس IP في فضاء المستخدم لتحويل حزم IP الخام إلى اتصالات على مستوى التطبيق يمكن لـ Xray-core توجيهها.
المصدر: proxy/tun/
البنية المعمارية
flowchart LR
App([Application]) -->|"IP packets"| TUN["TUN Interface<br/>(kernel)"]
TUN -->|"raw L3 frames"| EP["Link Endpoint<br/>(gVisor bridge)"]
EP -->|"L3→L4"| Stack["gVisor IP Stack<br/>(TCP/UDP)"]
Stack -->|"TCP: gonet.TCPConn"| Handler["TUN Handler"]
Stack -->|"UDP: raw packets"| UDPH["UDP Handler<br/>(Full-Cone)"]
Handler -->|"DispatchLink()"| Dispatcher["Xray Dispatcher"]
UDPH -->|"HandleConnection()"| Handler
Dispatcher -->|"via routing"| Outbound["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): يستخدم fd الممرر من طبقة Java
مكدس IP الخاص بـ gVisor (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.go)
يربط المعالج اتصالات 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 Device
participant Stack as gVisor Stack
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: Create gVisor stack<br/>(IPv4, IPv6, TCP, UDP)
Stack->>Stack: Set TCP forwarder
Stack->>Stack: Set UDP forwarder
Handler->>TUN: Start()
TUN->>TUN: Begin reading packets
Note over TUN,Stack: Running...
TUN->>Stack: Raw IP packet
Stack->>Stack: Parse L3/L4 headers
alt TCP
Stack->>Stack: TCP handshake
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:
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 وجهة عشوائية.
نقطة نهاية الرابط (stack_gvisor_endpoint.go)
تربط نقطة نهاية الرابط معالجة الحزم في gVisor بواصف ملف TUN:
type tunEndpoint struct {
tun Tun
dispatcher stack.NetworkDispatcher
mtu uint32
}- الوارد: يقرأ حزمًا خامة من fd الخاص بـ TUN ويسلمها إلى مكدس gVisor
- الصادر: يُنشئ gVisor حزم الاستجابة ويكتبها إلى fd الخاص بـ TUN
ملاحظات التنفيذ
gVisor هو التبعية الثقيلة: إنه مكدس TCP/IP كامل في فضاء المستخدم (أكثر من 100 ألف سطر). يمكن النظر في بدائل مثل lwIP أو smoltcp أو واجهات برمجة التطبيقات الخاصة بالمنصة.
TUN خاص بالمنصة: لكل نظام تشغيل واجهات إنشاء TUN مختلفة. على Android، يتم تمرير fd من خدمة VPN.
لا يوجد منفذ استماع: يُصرّح مدخل TUN عن
Network() []net.Network { return []net.Network{} }— فهو لا يستمع على أي منفذ. تأتي حركة المرور من جهاز TUN.DispatchLinkمتزامن: على عكسDispatch()، تحجبDispatchLink()التنفيذ. يتم إنشاء goroutine لكل اتصال (بواسطة معيد توجيه gVisor).أحجام مخازن TCP مهمة: أحجام مخازن TCP في gVisor (8 ميجابايت للاستقبال، 6 ميجابايت للإرسال) مُعدّة لإنتاجية عالية. إذا كانت صغيرة جدًا فالأداء ضعيف. إذا كانت كبيرة جدًا فهي هدر للذاكرة.
تعطيل RACK/TLP:
TCPRecovery(0)يُعطل استرداد فقدان RACK/TLP لإصلاح توقف الاتصالات تحت الحمل العالي — وهي مشكلة معروفة في gVisor.