Skip to content

بروتوكول VLESS

VLESS هو البروتوكول الرئيسي في Xray-core — بروتوكول وكيل خفيف وقابل للتوسعة بدون تشفير مدمج (يعتمد على أمان طبقة النقل). يدعم TCP وUDP والتعدد (multiplexing) وXTLS Vision (تمرير TLS المباشر) وXUDP (عنونة UDP لكل حزمة) والوكيل العكسي.

المصدر: proxy/vless/، proxy/vless/encoding/، proxy/vless/inbound/، proxy/vless/outbound/

صيغة السلك (Wire Format)

ترويسة الطلب

+----------+------+----------+-----------+---------+------+----------+
| Version  | UUID | Addon    | Command   | Port    | Addr | Addr     |
| (1 byte) | (16) | Len+Data | (1 byte)  | (2 BE)  | Type | Value    |
+----------+------+----------+-----------+---------+------+----------+

Version: 0x00 (always 0)
UUID:    16 bytes (user identity, raw bytes of UUID)
Addon:   1 byte length + protobuf Addons message (or 0x00 for no addons)
Command: 0x01=TCP, 0x02=UDP, 0x03=Mux, 0x04=Reverse
Port:    2 bytes big-endian (omitted for Mux/Reverse)
AddrType: 0x01=IPv4, 0x02=Domain, 0x03=IPv6
Address: 4 bytes (IPv4), 1+N bytes (domain length+domain), 16 bytes (IPv6)

ترويسة الاستجابة

+----------+----------+
| Version  | Addon    |
| (1 byte) | Len+Data |
+----------+----------+

Version: echoes request version (0x00)
Addon:   1 byte length + protobuf (or 0x00)

الإضافات (Addons) بصيغة Protobuf

protobuf
message Addons {
    string Flow = 1;  // e.g., "xtls-rprx-vision"
    bytes  Seed = 2;  // padding seed
}

عندما يكون Flow فارغاً/بدون قيمة، يكون طول الإضافة 0 (بايت صفري واحد). عندما يتم تعيين Flow (مثل Vision)، تُرمّز الإضافات بصيغة protobuf.

ترميز المحتوى (Body)

الوضعصيغة المحتوى
TCP (بدون Vision)تدفق خام (بدون تأطير)
UDPحزم بطول مُسبق: [2B length BE][payload]
Muxتأطير mux القياسي (انظر Mux)
Visionتدفق مُغلّف بـ Vision (انظر XTLS Vision)
Mux + XUDPتأطير XUDP (انظر XUDP)

ترميز الطلب (encoding.go:30)

go
func EncodeRequestHeader(writer, request, requestAddons) error {
    buffer.WriteByte(request.Version)           // 1 byte: version
    buffer.Write(account.ID.Bytes())            // 16 bytes: UUID
    EncodeHeaderAddons(&buffer, requestAddons)  // addons
    buffer.WriteByte(byte(request.Command))     // 1 byte: command
    if command != Mux && command != Rvs {
        addrParser.WriteAddressPort(&buffer, addr, port)  // address
    }
    writer.Write(buffer.Bytes())
}

فك ترميز الطلب (encoding.go:64)

go
func DecodeRequestHeader(isfb, first, reader, validator) (userSentID, request, addons, isFallback, error) {
    // Read version (1 byte)
    // Read UUID (16 bytes)
    // Validate user: validator.Get(id)
    // If user not found and fallback enabled: return isFallback=true
    // Read addons (protobuf)
    // Read command (1 byte)
    // Read address based on command type
}

يتحكم المعامل isfb في تفعيل آلية التراجع (fallback). إذا لم يتطابق UUID مع أي مستخدم وكان التراجع مُفعّلاً، يتم تحويل الاتصال إلى خادم بديل.

المعالج الوارد

هيكل المعالج (inbound/inbound.go:74)

go
type Handler struct {
    inboundHandlerManager  feature_inbound.Manager
    policyManager          policy.Manager
    stats                  stats.Manager
    validator              vless.Validator      // UUID→user mapping
    decryption             *encryption.ServerInstance  // ML-KEM-768 (optional)
    outboundHandlerManager outbound.Manager
    defaultDispatcher      routing.Dispatcher
    fallbacks              map[string]map[string]map[string]*Fallback  // name→alpn→path
}

تدفق المعالجة (inbound/inbound.go:267)

mermaid
flowchart TB
    Conn([Connection]) --> Decrypt{ML-KEM-768<br/>decryption?}
    Decrypt -->|Yes| Handshake["Post-quantum handshake"]
    Decrypt -->|No| ReadFirst
    Handshake --> ReadFirst["Read first bytes"]
    ReadFirst --> Decode["DecodeRequestHeader()"]
    Decode --> Valid{Valid UUID?}

    Valid -->|Yes| SetUser["Set user in context"]
    Valid -->|No, fallback on| Fallback["Fallback handler"]
    Valid -->|No, fallback off| Reject["Reject connection"]

    SetUser --> CheckMux{Is Mux?}
    CheckMux -->|"Mux + XUDP"| XUDP["XUDP: dispatch<br/>each UDP packet"]
    CheckMux -->|"Mux (not XUDP)"| Mux["Mux: dispatch<br/>multiplexed streams"]
    CheckMux -->|"TCP/UDP"| Dispatch["dispatcher.Dispatch(ctx, dest)"]

    Dispatch --> Copy["Bidirectional copy:<br/>client ↔ pipe"]

    Fallback --> DetectSNI["Get TLS SNI + ALPN"]
    DetectSNI --> LookupFB["Lookup fallback:<br/>name → alpn → path"]
    LookupFB --> ProxyFB["Proxy to fallback<br/>dest (with first bytes)"]

كشف Mux مقابل XUDP (inbound/inbound.go:176)

go
func isMuxAndNotXUDP(request, first) bool {
    if request.Command != protocol.RequestCommandMux {
        return false
    }
    if first.Len() < 7 {
        return true  // not enough data, assume regular mux
    }
    firstBytes := first.Bytes()
    // XUDP: session ID = 0, network type = UDP (2)
    return !(firstBytes[2] == 0 &&  // ID high
             firstBytes[3] == 0 &&  // ID low
             firstBytes[6] == 2)    // Network type: UDP
}

يتم كشف XUDP بالتحقق مما إذا كان إطار mux الأول يحمل معرّف جلسة 0 ونوع شبكة UDP.

نظام التراجع (Fallback)

نظام التراجع هو آلية توجيه متعددة المستويات للاتصالات التي لا تتطابق مع VLESS:

fallbacks[name][alpn][path] → Fallback{Dest, Xver}

المستوى 1 — اسم الخادم (SNI):

  • من حالة اتصال TLS/REALITY
  • يسمح بعدة نطاقات على نفس المنفذ

المستوى 2 — ALPN:

  • من البروتوكول المتفاوض عليه عبر TLS (h2، http/1.1)
  • يوجّه HTTP/2 مقابل HTTP/1.1 بشكل مختلف

المستوى 3 — مسار HTTP:

  • يُحلّل من سطر طلب HTTP الأول
  • يوجّه مسارات مختلفة إلى خوادم خلفية مختلفة

يمكن أن تكون وجهات التراجع:

  • عنوان TCP (127.0.0.1:80)
  • مقبس Unix (/dev/shm/nginx.sock)
  • مقبس مجرد (@name)

يمكن إضافة ترويسات بروتوكول PROXY (الإصدار 1/2) عبر إعداد Xver.

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

تدفق المعالجة (outbound/outbound.go:136)

go
func (h *Handler) Process(ctx, link, dialer) error {
    // 1. Dial transport connection (with optional pre-connect pool)
    conn, _ := dialer.Dial(ctx, rec.Destination)

    // 2. Determine command
    if target.Network == UDP { command = UDP }
    if target.Address == "v1.mux.cool" { command = Mux }

    // 3. Handle XUDP: for UDP with cone NAT, convert to mux
    if command == UDP && (flow == XRV || cone) {
        command = Mux
        address = "v1.mux.cool"
        port = 666
    }

    // 4. Encode request header
    EncodeRequestHeader(conn, request, requestAddons)

    // 5. Create body writer (may be Vision or XUDP)
    serverWriter = EncodeBodyAddons(conn, request, requestAddons, ...)
    if command == Mux && port == 666 {
        serverWriter = xudp.NewPacketWriter(serverWriter, target, globalID)
    }

    // 6. Upload: link.Reader → serverWriter
    // 7. Download: serverReader → link.Writer
    task.Run(ctx, postRequest, getResponse)
}

كشف XTLS Vision (الصادر)

لتدفق Vision، يصل المعالج الصادر إلى الحالة الداخلية لـ TLS عبر unsafe.Pointer:

go
// Access Go TLS internal fields
t = reflect.TypeOf(tlsConn.Conn).Elem()
p = uintptr(unsafe.Pointer(tlsConn.Conn))
i, _ := t.FieldByName("input")    // *bytes.Reader
r, _ := t.FieldByName("rawInput") // *bytes.Buffer
input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))

تكشف هذه المخازن المؤقتة الداخلية لـ TLS عن اكتمال مصافحة TLS الداخلية، مما يسمح لـ Vision بالتحول إلى التمرير المباشر. انظر XTLS Vision للتفاصيل.

معالجة UDP

بدون XUDP (بطول مُسبق بسيط)

لـ UDP المباشر بدون cone NAT:

[2B length BE][UDP payload]
[2B length BE][UDP payload]
...

تُكتب بواسطة MultiLengthPacketWriter، وتُقرأ بواسطة LengthPacketReader.

XUDP (دعم cone NAT)

لـ UDP مع دعم cone NAT، يُغلّف UDP في إطارات mux مع عنونة لكل حزمة. يُعيّن المعالج الصادر:

go
request.Command = Mux
request.Address = "v1.mux.cool"
request.Port = 666  // magic port indicating XUDP

انظر XUDP لصيغة الإطار.

مجمّع الاتصالات المُسبقة (Pre-Connect Pool)

يمكن للمعالج الصادر الحفاظ على اتصالات مُنشأة مسبقاً لتقليل زمن الاستجابة:

go
if h.testpre > 0 {
    // Launch N goroutines that continuously dial and buffer connections
    // Connections expire after 2 minutes
    h.preConns = make(chan *ConnExpire)
    for range h.testpre {
        go func() {
            for {
                conn := dialer.Dial(ctx, dest)
                h.preConns <- &ConnExpire{Conn: conn, Expire: time.Now().Add(2*time.Minute)}
                time.Sleep(200ms)
            }
        }()
    }
}

تشفير ML-KEM-768 ما بعد الكم

يدعم VLESS تشفيراً اختيارياً مقاوماً للحوسبة الكمية عبر ML-KEM-768 (المعروف سابقاً بـ Kyber):

go
// Server side (inbound)
if h.decryption != nil {
    connection, err = h.decryption.Handshake(connection, nil)
}

// Client side (outbound)
if h.encryption != nil {
    conn, err = h.encryption.Handshake(conn)
}

يضيف هذا طبقة تغليف مفاتيح قبل بروتوكول VLESS، مما يوفر أماناً مقاوماً للحوسبة الكمية بشكل مستقل عن طبقة النقل.

الوكيل العكسي

يدعم VLESS الوكيل العكسي ثنائي الاتجاه عبر mux:

  • العميل (الصادر): يُنشئ اتصال mux مع الخادم بأمر Rvs (0x04)
  • الخادم (الوارد): يكشف المستخدم العكسي، ويُنشئ معالج Reverse صادر
  • عمّال الجسر يُعدّدون حركة المرور عبر الاتصال العكسي
go
// Server creates reverse outbound on-demand
r := &Reverse{tag: a.Reverse.Tag, picker: picker, client: muxClient}
outboundManager.AddHandler(ctx, r)

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

  1. UUID بالبايتات الخام: ليس سلسلة نصية. يُرسل UUID ذو 16 بايت مباشرة، وليس كسلسلة سداسية عشرية أو base64.

  2. ترميز الإضافات: عندما يكون Flow فارغاً، يُكتب بايت 0x00 واحد. عند تعيينه، تُرمّز رسالة Addons بصيغة protobuf مع طولها كبادئة (بايت واحد).

  3. التراجع على مستوى الاتصال: إذا لم يتطابق UUID، يتم تحويل الاتصال بالكامل (بما في ذلك البايتات المقروءة مسبقاً) إلى وجهة التراجع. تُضاف ترويسة بروتوكول PROXY (الإصدار 1/2) إذا كانت مُهيّأة.

  4. منفذ XUDP السحري: port == 666 مع العنوان v1.mux.cool يشير إلى وضع XUDP. يجب على الخادم كشف هذا لاستخدام تأطير XUDP.

  5. Vision غير قابل للنقل: خدعة unsafe.Pointer لقراءة الحالة الداخلية لـ TLS تعمل فقط مع تنفيذات Go TLS المحددة. تحتاج اللغات الأخرى إلى مقاربات بديلة (مثل تنفيذ TLS مخصص مع حالة مكشوفة).

  6. إعادة استخدام الاتصال: يسمح مجمّع الاتصالات المُسبقة وmux بإعادة استخدام الاتصال. بدون mux، كل تدفق TCP = اتصال VLESS واحد.

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