Skip to content

نظرة عامة على TUN ومكدس IP ​

يُنشئ مدخل TUN واجهة شبكة افتراضية ويستخدم مكدس IP في فضاء المستخدم لتحويل حزم IP الخام إلى اتصالات على مستوى التطبيق يمكن لـ Xray-core توجيهها.

المصدر: proxy/tun/

البنية المعمارية ​

mermaid
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 خاص بكل منصة:

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
    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:

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)
}

الفرق الرئيسي عن المداخل الأخرى: يستخدم 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 وجهة عشوائية.

نقطة نهاية الرابط (stack_gvisor_endpoint.go) ​

تربط نقطة نهاية الرابط معالجة الحزم في gVisor بواصف ملف TUN:

go
type tunEndpoint struct {
    tun        Tun
    dispatcher stack.NetworkDispatcher
    mtu        uint32
}
  • الوارد: يقرأ حزمًا خامة من fd الخاص بـ TUN ويسلمها إلى مكدس gVisor
  • الصادر: يُنشئ gVisor حزم الاستجابة ويكتبها إلى fd الخاص بـ TUN

ملاحظات التنفيذ ​

  1. gVisor هو التبعية الثقيلة: إنه مكدس TCP/IP كامل في فضاء المستخدم (أكثر من 100 ألف سطر). يمكن النظر في بدائل مثل lwIP أو smoltcp أو واجهات برمجة التطبيقات الخاصة بالمنصة.

  2. TUN خاص بالمنصة: لكل نظام تشغيل واجهات إنشاء TUN مختلفة. على Android، يتم تمرير fd من خدمة VPN.

  3. لا يوجد منفذ استماع: يُصرّح مدخل TUN عن Network() []net.Network { return []net.Network{} } — فهو لا يستمع على أي منفذ. تأتي حركة المرور من جهاز TUN.

  4. DispatchLink متزامن: على عكس Dispatch()، تحجب DispatchLink() التنفيذ. يتم إنشاء goroutine لكل اتصال (بواسطة معيد توجيه gVisor).

  5. أحجام مخازن TCP مهمة: أحجام مخازن TCP في gVisor (8 ميجابايت للاستقبال، 6 ميجابايت للإرسال) مُعدّة لإنتاجية عالية. إذا كانت صغيرة جدًا فالأداء ضعيف. إذا كانت كبيرة جدًا فهي هدر للذاكرة.

  6. تعطيل RACK/TLP: TCPRecovery(0) يُعطل استرداد فقدان RACK/TLP لإصلاح توقف الاتصالات تحت الحمل العالي — وهي مشكلة معروفة في gVisor.

تحليل تقني لأغراض إعادة التنفيذ.