Skip to content

WireGuard

يدمج Xray-core بروتوكول WireGuard كبروتوكول صادر ووارد، مستخدماً تنفيذ wireguard-go في مساحة المستخدم ومكدس شبكة gVisor (netstack) لإنشاء واجهات نفق افتراضية بالكامل في مساحة المستخدم. يسمح هذا بنفق WireGuard دون الحاجة إلى صلاحيات جهاز TUN على مستوى نظام التشغيل (على معظم المنصات).

نظرة عامة

  • الاتجاه: وارد + صادر
  • النقل: UDP (يعمل بروتوكول WireGuard عبر UDP)
  • التشفير: إطار عمل بروتوكول Noise (Curve25519, ChaCha20-Poly1305, BLAKE2s)
  • المصادقة: أزواج مفاتيح عامة/خاصة + مفتاح مُشترك مسبق اختياري
  • UDP: دعم كامل (عبر مكدس الشبكة في مساحة المستخدم)
  • TCP: دعم كامل (عبر مكدس الشبكة في مساحة المستخدم)
  • TUN النواة: مدعوم على Linux (اختياري، معطّل افتراضياً للوارد)

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

mermaid
graph TD
    subgraph "Xray-core Process"
        A[Outbound Handler] --> B[gVisor netstack]
        B --> C[wireguard-go Device]
        C --> D[netBindClient]
        D --> E[internet.Dialer]
    end
    E --> F[WireGuard Peer UDP]

    subgraph "Inbound (Server Mode)"
        G[UDP Listener] --> H[netBindServer]
        H --> I[wireguard-go Device]
        I --> J[gVisor netstack]
        J --> K[TCP/UDP Forwarder]
        K --> L[Routing Dispatcher]
    end

المكونات الرئيسية

المكونالملفالغرض
Handler (صادر)proxy/wireguard/client.goنفق WireGuard الصادر
Server (وارد)proxy/wireguard/server.goنقطة نهاية WireGuard الواردة
واجهة Tunnelproxy/wireguard/tun.goجهاز TUN مجرد
gvisorNetproxy/wireguard/tun.goTUN افتراضي مبني على gVisor
netBindClientproxy/wireguard/bind.goنقل UDP من جانب العميل
netBindServerproxy/wireguard/bind.goنقل UDP من جانب الخادم
حزمة gvisortunproxy/wireguard/gvisortun/إعداد مكدس شبكة gVisor

واجهة النفق

go
type Tunnel interface {
    BuildDevice(ipc string, bind conn.Bind) error
    DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
    DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error)
    Close() error
}

المصدر: proxy/wireguard/tun.go:33-38

تُجرّد واجهة Tunnel جهاز الشبكة الافتراضي. بعد استدعاء BuildDevice() مع سلسلة إعدادات IPC، يوفر النفق واجهات Go القياسية net.Conn لـ TCP وUDP.

مكدس شبكة gVisor

التنفيذ الأساسي يستخدم مكدس TCP/IP في مساحة المستخدم الخاص بـ gVisor:

go
func createGVisorTun(localAddresses []netip.Addr, mtu int,
    handler promiscuousModeHandler) (Tunnel, error) {

    tun, n, stack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)
    // ...
}

المصدر: proxy/wireguard/tun.go:139-144

الوضع المختلط (الخادم)

عند تقديم دالة handler (وضع الخادم)، يُعدّ gVisor مُحوّلات TCP وUDP تلتقط جميع الحزم الواردة:

go
// TCP Forwarder
tcpForwarder := tcp.NewForwarder(stack, 0, 65535, func(r *tcp.ForwarderRequest) {
    ep, err := r.CreateEndpoint(&wq)
    r.Complete(false)
    ep.SocketOptions().SetKeepAlive(true)
    handler(net.TCPDestination(...), gonet.NewTCPConn(&wq, ep))
})
stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)

// UDP Forwarder
udpForwarder := udp.NewForwarder(stack, func(r *udp.ForwarderRequest) {
    ep, _ := r.CreateEndpoint(&wq)
    ep.SocketOptions().SetLinger(tcpip.LingerOption{Enabled: true, Timeout: 15 * time.Second})
    handler(net.UDPDestination(...), gonet.NewUDPConn(&wq, ep))
})
stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)

المصدر: proxy/wireguard/tun.go:150-201

المعالج الصادر

الملف: proxy/wireguard/client.go

التهيئة

go
func New(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
    endpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf)
    return &Handler{
        conf:      conf,
        dns:       d,
        endpoints: endpoints,
        hasIPv4:   hasIPv4,
        hasIPv6:   hasIPv6,
    }, nil
}

المصدر: proxy/wireguard/client.go:62-78

يُهيّأ جهاز WireGuard بشكل كسول عند أول استخدام ويُعاد إنشاؤه عند تغير المتصل:

go
func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer) error {
    h.wgLock.Lock()
    defer h.wgLock.Unlock()

    if h.bind != nil && h.bind.dialer == dialer && h.net != nil {
        return nil  // Already initialized with same dialer
    }
    // Create new bind and tunnel
    h.bind = &netBindClient{
        netBind: netBind{dns: h.dns, ...},
        ctx:     ctx,
        dialer:  dialer,
        reserved: h.conf.Reserved,
    }
    h.net, err = h.makeVirtualTun()
}

المصدر: proxy/wireguard/client.go:94-142

معالجة الاتصالات

go
func (h *Handler) Process(ctx context.Context, link *transport.Link,
    dialer internet.Dialer) error {

    // 1. Initialize/reuse WireGuard tunnel
    h.processWireGuard(ctx, dialer)

    // 2. Resolve DNS if destination is a domain
    if addr.Family().IsDomain() {
        ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
            IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
            IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
        })
    }

    // 3. Dial through the virtual tunnel
    if command == protocol.RequestCommandTCP {
        conn, err := h.net.DialContextTCPAddrPort(ctx, addrPort)
        // Copy data bidirectionally
    } else {
        conn, err := h.net.DialUDPAddrPort(netip.AddrPort{}, addrPort)
        // Copy data bidirectionally
    }
}

المصدر: proxy/wireguard/client.go:145-252

إعدادات IPC

يُهيّأ جهاز WireGuard عبر سلسلة طلب IPC (بنفس صيغة wg set):

go
func (h *Handler) createIPCRequest() string {
    var request strings.Builder
    request.WriteString(fmt.Sprintf("private_key=%s\n", h.conf.SecretKey))

    for _, peer := range h.conf.Peers {
        request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
        if peer.PreSharedKey != "" {
            request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
        }
        request.WriteString(fmt.Sprintf("endpoint=%s:%s\n", addr, port))
        for _, ip := range peer.AllowedIps {
            request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip))
        }
        if peer.KeepAlive != 0 {
            request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
        }
    }
    return request.String()
}

المصدر: proxy/wireguard/client.go:272-338

تحليل DNS لنقطة النهاية: إذا كانت نقطة نهاية النظير نطاقاً، يتم تحليلها باستخدام إما IP المُحلّل مسبقاً للمتصل أو عميل DNS:

go
if addr.Family().IsDomain() {
    dialerIp := h.bind.dialer.DestIpAddress()
    if dialerIp != nil {
        addr = net.ParseAddress(dialerIp.String())
    } else {
        ips, _, _ = h.dns.LookupIP(addr.Domain(), ...)
        addr = net.IPAddress(ips[dice.Roll(len(ips))])
    }
}

المصدر: proxy/wireguard/client.go:296-321

المعالج الوارد (الخادم)

الملف: proxy/wireguard/server.go

دعم الشبكة

يقبل الخادم اتصالات UDP فقط (بروتوكول نقل WireGuard):

go
func (*Server) Network() []net.Network {
    return []net.Network{net.Network_UDP}
}

المصدر: proxy/wireguard/server.go:75-77

معالجة الحزم

يقرأ الخادم حزم UDP من طبقة النقل ويُغذّيها إلى جهاز WireGuard:

go
func (s *Server) Process(ctx context.Context, network net.Network,
    conn stat.Connection, dispatcher routing.Dispatcher) error {

    reader := buf.NewPacketReader(conn)
    for {
        mpayload, err := reader.ReadMultiBuffer()
        for _, payload := range mpayload {
            v, ok := <-s.bindServer.readQueue
            // Feed packet to wireguard-go
            v.bytes = payload.Read(v.buff)
            v.endpoint = nep
            v.waiter.Done()
        }
    }
}

المصدر: proxy/wireguard/server.go:80-120

تحويل الاتصالات

عندما يستقبل مكدس gVisor اتصالات TCP/UDP مفكوكة التشفير من النفق، يوزّعها معالج forwardConnection:

go
func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
    defer conn.Close()

    link, err := s.info.dispatcher.Dispatch(ctx, dest)
    // Bidirectional copy between tunnel conn and dispatched link
    task.Run(ctx, requestDone, responseDone)
}

المصدر: proxy/wireguard/server.go:122-190

اختيار وضع TUN

الملف: proxy/wireguard/config.go

يختار المعالج بين TUN النواة وgVisor TUN:

go
func (c *DeviceConfig) createTun() tunCreator {
    if !c.IsClient {
        return createGVisorTun  // Server always uses gVisor
    }
    if c.NoKernelTun {
        return createGVisorTun
    }
    if kernelTunSupported, _ := KernelTunSupported(); !kernelTunSupported {
        return createGVisorTun
    }
    return createKernelTun  // Linux only
}

المصدر: proxy/wireguard/config.go:33-54

TUN النواة (Linux)

الملف: proxy/wireguard/tun_linux.go

على Linux مع الصلاحيات المناسبة، يمكن استخدام جهاز TUN حقيقي للنواة لأداء أفضل. تتعامل النواة مع معالجة TCP/IP بشكل أصلي.

TUN الافتراضي (المنصات الأخرى)

الملف: proxy/wireguard/tun_default.go

على المنصات غير Linux، يتوفر gVisor TUN فقط.

استراتيجية النطاق

يدعم WireGuard استراتيجيات تحليل DNS مع احتياطي:

الاستراتيجيةالتفضيلالاحتياطي
FORCE_IPIPv4 + IPv6بدون
FORCE_IP4IPv4 فقطبدون
FORCE_IP6IPv6 فقطبدون
FORCE_IP46IPv4IPv6
FORCE_IP64IPv6IPv4

المصدر: proxy/wireguard/config.go:9-31

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

  1. التهيئة الكسولة: لا يُنشأ نفق WireGuard وقت التهيئة. يُهيّأ عند أول استدعاء Process() ويُعاد استخدامه للاتصالات اللاحقة. إذا تغير المتصل (مثل توجيه صادر مختلف)، يُعاد إنشاء النفق.

  2. البايتات المحجوزة: يُعيّن حقل Reserved في الإعدادات 3 بايتات في ترويسة UDP الخاصة بـ WireGuard، تُستخدم من قبل بعض مقدمي الخدمات (مثل Cloudflare WARP) للتوجيه.

المصدر: proxy/wireguard/client.go:129

  1. أمان الخيوط: يستخدم المعالج sync.Mutex (wgLock) لحماية إنشاء وتبديل النفق. تتشارك عدة اتصالات متزامنة نفس النفق.

  2. دورة حياة الاتصال: كل اتصال TCP/UDP عبر النفق يُدار بشكل مستقل مع سياسات مهلة Xray القياسية. يستمر النفق نفسه عبر الاتصالات.

  3. keep-alive لـ TCP في gVisor: اتصالات TCP عبر مُحوّل الخادم تُفعّل فيها خاصية keep-alive لمنع الاتصالات المعلّقة:

go
ep.SocketOptions().SetKeepAlive(true)

المصدر: proxy/wireguard/tun.go:168

  1. مهلة UDP linger: نقاط نهاية UDP من جانب الخادم لها مهلة linger تبلغ 15 ثانية لضمان تحرير الموارد في الوقت المناسب:
go
ep.SocketOptions().SetLinger(tcpip.LingerOption{Enabled: true, Timeout: 15 * time.Second})

المصدر: proxy/wireguard/tun.go:191-193

  1. بدون تشفير في طبقة Xray: يتعامل WireGuard مع كل التشفير داخلياً عبر بروتوكول Noise. يقوم Xray ببساطة بتمرير حزم IP المفكوكة التشفير عبر مكدس الشبكة الافتراضي. يعكس إعداد CanSpliceCopy = 3 هذا.

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