بروتوكول 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
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)
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)
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)
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)
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)
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)
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:
// 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 مع عنونة لكل حزمة. يُعيّن المعالج الصادر:
request.Command = Mux
request.Address = "v1.mux.cool"
request.Port = 666 // magic port indicating XUDPانظر XUDP لصيغة الإطار.
مجمّع الاتصالات المُسبقة (Pre-Connect Pool)
يمكن للمعالج الصادر الحفاظ على اتصالات مُنشأة مسبقاً لتقليل زمن الاستجابة:
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):
// 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صادر - عمّال الجسر يُعدّدون حركة المرور عبر الاتصال العكسي
// Server creates reverse outbound on-demand
r := &Reverse{tag: a.Reverse.Tag, picker: picker, client: muxClient}
outboundManager.AddHandler(ctx, r)ملاحظات التنفيذ
UUID بالبايتات الخام: ليس سلسلة نصية. يُرسل UUID ذو 16 بايت مباشرة، وليس كسلسلة سداسية عشرية أو base64.
ترميز الإضافات: عندما يكون Flow فارغاً، يُكتب بايت
0x00واحد. عند تعيينه، تُرمّز رسالة Addons بصيغة protobuf مع طولها كبادئة (بايت واحد).التراجع على مستوى الاتصال: إذا لم يتطابق UUID، يتم تحويل الاتصال بالكامل (بما في ذلك البايتات المقروءة مسبقاً) إلى وجهة التراجع. تُضاف ترويسة بروتوكول PROXY (الإصدار 1/2) إذا كانت مُهيّأة.
منفذ XUDP السحري:
port == 666مع العنوانv1.mux.coolيشير إلى وضع XUDP. يجب على الخادم كشف هذا لاستخدام تأطير XUDP.Vision غير قابل للنقل: خدعة
unsafe.Pointerلقراءة الحالة الداخلية لـ TLS تعمل فقط مع تنفيذات Go TLS المحددة. تحتاج اللغات الأخرى إلى مقاربات بديلة (مثل تنفيذ TLS مخصص مع حالة مكشوفة).إعادة استخدام الاتصال: يسمح مجمّع الاتصالات المُسبقة وmux بإعادة استخدام الاتصال. بدون mux، كل تدفق TCP = اتصال VLESS واحد.