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.

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