بروتوكول VMess
VMess هو بروتوكول الوكيل المشفر الأصلي لـ V2Ray/Xray. يوفر تشفيراً مُصادقاً مع خيارات تشفير متعددة، وحماية ضد إعادة التشغيل مبنية على الوقت، وحشو/تقنيع اختياري لمقاومة تحليل حركة المرور.
نظرة عامة
- الاتجاه: وارد + صادر
- النقل: TCP، مقبس UNIX
- التشفير: AES-128-GCM، ChaCha20-Poly1305، بدون
- المصادقة: مبنية على UUID مع تشفير ترويسة AEAD
- Mux: مدعوم عبر النطاق الافتراضي
v1.mux.coolوXUDP
صيغة السلك (Wire Format)
الطلب (من العميل إلى الخادم)
يتكون طلب VMess AEAD من طبقتين: غلاف خارجي مشفر بـ AEAD، وترويسة الأمر الداخلية.
الغلاف الخارجي (ترويسة AEAD)
+----------+---------------------------+--------------+---------------------------+
| Auth ID | Encrypted Payload Length | Conn Nonce | Encrypted Payload |
| 16 bytes | 2 + 16 bytes (GCM tag) | 8 bytes | variable + 16 bytes (tag) |
+----------+---------------------------+--------------+---------------------------+المصدر: proxy/vmess/aead/encrypt.go:14-61
معرّف المصادقة (Auth ID) (16 بايت): كتلة مشفرة بـ AES-ECB تحتوي على:
+---------------+-----------+----------+
| Timestamp | Random | CRC32 |
| 8 bytes (BE) | 4 bytes | 4 bytes |
+---------------+-----------+----------+يُشتق مفتاح AES لتشفير Auth ID عبر:
aesKey = KDF16(cmdKey, "AES Auth ID Encryption")المصدر: proxy/vmess/aead/authid.go:26-40
التحقق من الوقت: يفك الخادم تشفير Auth ID لكل مستخدم معروف، ويتحقق من CRC32، ويتأكد من أن الطابع الزمني ضمن 120 ثانية من وقت الخادم.
المصدر: proxy/vmess/aead/authid.go:99-121
تشفير طول الحمولة: AES-128-GCM بمفتاح/nonce مشتق من:
key = KDF16(cmdKey, "VMess Header AEAD Key_Length", authID, connNonce)
nonce = KDF(cmdKey, "VMess Header AEAD Nonce_Length", authID, connNonce)[:12]
// Additional data = authIDتشفير الحمولة: AES-128-GCM بمفتاح/nonce مشتق من:
key = KDF16(cmdKey, "VMess Header AEAD Key", authID, connNonce)
nonce = KDF(cmdKey, "VMess Header AEAD Nonce", authID, connNonce)[:12]
// Additional data = authIDالمصدر: proxy/vmess/aead/encrypt.go:30-51
ترويسة الأمر الداخلية (الحمولة المفكوكة التشفير)
+-----+--------+--------+---------+--------+----------+-----+---------+---------+-------+
| Ver | BodyIV | BodyKey | RespHdr | Option | Security | Rsv | Command | Address | Pad | FNV1a |
| 1B | 16B | 16B | 1B | 1B | 1B | 1B | 1B | var | 0-15B | 4B |
+-----+--------+--------+---------+--------+----------+-----+---------+---------+-------+المصدر: proxy/vmess/encoding/client.go:63-101
| الحقل | الحجم | الوصف |
|---|---|---|
| Version | 1 بايت | دائماً 0x01 |
| Body IV | 16 بايت | IV عشوائي لتشفير المحتوى |
| Body Key | 16 بايت | مفتاح عشوائي لتشفير المحتوى |
| Response Header | 1 بايت | بايت عشوائي يجب أن يُعيده الخادم |
| Option | 1 بايت | قناع بتات: ChunkStream(0x01)، ChunkMasking(0x04)، GlobalPadding(0x08)، AuthenticatedLength(0x10) |
| Security | 1 بايت | البتات العليا 4 = طول الحشو (0-15)، البتات السفلى 4 = نوع التشفير |
| Reserved | 1 بايت | دائماً 0x00 |
| Command | 1 بايت | 0x01=TCP، 0x02=UDP، 0x03=Mux |
| Address | متغير | المنفذ(2 بايت, BE) + نوع العنوان(1 بايت) + العنوان |
| Padding | 0-15 بايت | حشو عشوائي |
| FNV1a | 4 بايت | تجزئة FNV-1a لجميع الحقول السابقة (للتكامل) |
أنواع التشفير (البتات السفلى 4):
| القيمة | التشفير |
|---|---|
| 0x00 | AUTO / UNKNOWN |
| 0x03 | AES-128-GCM |
| 0x04 | ChaCha20-Poly1305 |
| 0x05 | بدون |
المصدر: proxy/vmess/encoding/server.go:114-124
الاستجابة (من الخادم إلى العميل)
ترويسة الاستجابة مشفرة أيضاً بـ AEAD:
+----------------------------+----------------------------+
| Encrypted Length (2+16B) | Encrypted Header (var+16B) |
+----------------------------+----------------------------+المفاتيح مشتقة من مفتاح/IV محتوى الاستجابة:
responseBodyKey = SHA256(requestBodyKey)[:16]
responseBodyIV = SHA256(requestBodyIV)[:16]
lengthKey = KDF16(responseBodyKey, "AEAD Resp Header Len Key")
lengthNonce = KDF(responseBodyIV, "AEAD Resp Header Len IV")[:12]
payloadKey = KDF16(responseBodyKey, "AEAD Resp Header Key")
payloadNonce = KDF(responseBodyIV, "AEAD Resp Header IV")[:12]المصدر: proxy/vmess/encoding/client.go:179-253، proxy/vmess/encoding/server.go:328-369
ترويسة الاستجابة بعد فك التشفير:
+----------+--------+---------+---------+
| RespHdr | Option | CmdID | CmdLen | CmdData |
| 1B | 1B | 1B | 1B | var |
+----------+--------+---------+---------+يجب أن يتطابق بايت RespHdr مع البايت العشوائي من الطلب.
تشفير المحتوى
يُنقل محتوى البيانات كأجزاء مُصادقة:
graph LR
subgraph "Each Chunk"
A[Length 2B] --> B[Payload] --> C[Auth Tag]
endترميز حجم الجزء (مع التقنيع): عند تفعيل ChunkMasking، يقوم تدفق SHAKE128 بعملية XOR على حقل الطول ذي البايتين:
// ShakeSizeParser
mask = SHAKE128(bodyIV).next_2_bytes()
wire_length = actual_length XOR maskالمصدر: proxy/vmess/encoding/auth.go:51-87
توليد nonce لـ AEAD المحتوى:
func GenerateChunkNonce(nonce []byte, size uint32) BytesGenerator {
c := copy(nonce)
count := uint16(0)
return func() []byte {
binary.BigEndian.PutUint16(c, count)
count++
return c[:size]
}
}يكون nonce هو IV المحتوى مع استبدال أول بايتين بعداد متزايد.
المصدر: proxy/vmess/encoding/client.go:332-340
اشتقاق مفتاح ChaCha20-Poly1305: يُوسّع مفتاح المحتوى ذو 16 بايت إلى 32 بايت:
func GenerateChacha20Poly1305Key(b []byte) []byte {
key := make([]byte, 32)
t := md5.Sum(b)
copy(key, t[:])
t = md5.Sum(key[:16])
copy(key[16:], t[:])
return key
}المصدر: proxy/vmess/encoding/auth.go:42-49
إشارة الإنهاء: عندما لا يكون NoTerminationSignal مُعيّناً، يشير جزء فارغ (طول=0) إلى نهاية التدفق.
المصدر: proxy/vmess/outbound/outbound.go:185-189
دالة اشتقاق المفاتيح (KDF)
يستخدم VMess دالة KDF مبنية على HMAC-SHA256 متداخلة:
func KDF(key []byte, path ...string) []byte {
hmacf := hmac.New(sha256.New, []byte("VMess AEAD KDF"))
for _, v := range path {
hmacf = hmac.New(func() hash.Hash {
// uses previous hmac as inner hash
return hmacf
}, []byte(v))
}
hmacf.Write(key)
return hmacf.Sum(nil)
}المصدر: proxy/vmess/aead/kdf.go:13-28
ثوابت أملاح KDF:
| الثابت | القيمة |
|---|---|
KDFSaltConstVMessAEADKDF | "VMess AEAD KDF" |
KDFSaltConstAuthIDEncryptionKey | "AES Auth ID Encryption" |
KDFSaltConstVMessHeaderPayloadAEADKey | "VMess Header AEAD Key" |
KDFSaltConstVMessHeaderPayloadAEADIV | "VMess Header AEAD Nonce" |
KDFSaltConstVMessHeaderPayloadLengthAEADKey | "VMess Header AEAD Key_Length" |
KDFSaltConstVMessHeaderPayloadLengthAEADIV | "VMess Header AEAD Nonce_Length" |
KDFSaltConstAEADRespHeaderLenKey | "AEAD Resp Header Len Key" |
KDFSaltConstAEADRespHeaderLenIV | "AEAD Resp Header Len IV" |
KDFSaltConstAEADRespHeaderPayloadKey | "AEAD Resp Header Key" |
KDFSaltConstAEADRespHeaderPayloadIV | "AEAD Resp Header IV" |
المصدر: proxy/vmess/aead/consts.go:1-14
مصادقة المستخدم
مفتاح الأمر (CmdKey)
يُشتق CmdKey من UUID المستخدم:
func NewID(uuid uuid.UUID) *ID {
// cmdKey = MD5(uuid.Bytes() + []byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
}المصدر: common/protocol/id.go
مُدقق المستخدم المؤقت (TimedUserValidator)
يحتفظ الخادم بـ TimedUserValidator الذي:
- يخزّن جميع مفاتيح CmdKey للمستخدمين في
AuthIDDecoderHolder - لكل اتصال وارد، يحاول فك تشفير AES لـ Auth ID ذي 16 بايت بمفتاح كل مستخدم
- يتحقق من تجزئة CRC32، ونطاق الطابع الزمني (+-120 ثانية)، ومرشح إعادة التشغيل
func (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {
for _, v := range a.decoders {
t, z, _, d := v.dec.Decode(authID)
if z != crc32.ChecksumIEEE(d[:12]) { continue }
if math.Abs(float64(t) - float64(time.Now().Unix())) > 120 { return nil, ErrInvalidTime }
if !a.filter.Check(authID) { return nil, ErrReplay }
return v.ticket, nil
}
return nil, ErrNotFound
}المصدر: proxy/vmess/aead/authid.go:99-121
بذرة السلوك (Behavior Seed)
تُحسب "بذرة سلوك" حتمية من جميع معرّفات المستخدمين باستخدام HMAC-SHA256 + CRC64. تتحكم هذه البذرة في نمط التصريف (أطوال قراءة عشوائية قبل إغلاق الاتصالات غير الصالحة) لمنع هجمات الاستقصاء.
المصدر: proxy/vmess/validator.go:114-123
المعالج الوارد
الملف: proxy/vmess/inbound/inbound.go
يقوم المعالج الوارد بما يلي:
- تعيين مهلة قراءة المصافحة من السياسة
- إنشاء
ServerSessionمعTimedUserValidator - استدعاء
DecodeRequestHeader()للمصادقة والتحليل - التوزيع إلى موزّع التوجيه
- تشغيل الطلب (القراءة من العميل، الكتابة إلى الرابط) والاستجابة (القراءة من الرابط، الكتابة إلى العميل) كمهام متوازية
الأسطر الرئيسية: proxy/vmess/inbound/inbound.go:226-319
المعالج الصادر
الملف: proxy/vmess/outbound/outbound.go
يقوم المعالج الصادر بما يلي:
- اختيار الخادم والمستخدم من الإعدادات
- تحديد نوع التشفير من إعدادات الحساب
- تفعيل
ChunkMaskingوGlobalPaddingتلقائياً لتشفيرات AEAD - دعم
SecurityType_ZERO(بدون تشفير، بدون تجزئة) لحالات استخدام XTLS - إنشاء
ClientSessionبمفتاح/IV عشوائي للمحتوى - ترميز ترويسة الطلب + المحتوى، ثم قراءة الاستجابة
الأسطر الرئيسية: proxy/vmess/outbound/outbound.go:57-225
ملاحظات التنفيذ
إزالة البروتوكول القديم: تمت إزالة مصادقة VMess القديمة غير AEAD (المبنية على MD5) بالكامل. يُدعم AEAD فقط. إذا لم يتمكن الخادم من مطابقة أي مستخدم عبر فك تشفير AEAD، يُرجع خطأ مع تصريف حتمي.
حماية إعادة تشغيل الجلسة: طبقتان —
AuthIDDecoderHolder.filter(مرشح خريطة 120 ثانية على Auth IDs) وSessionHistory(ذاكرة مؤقتة لمدة 3 دقائق من مجموعات{user, bodyKey, bodyIV}).الحشو: عند تفعيل
GlobalPadding، تُرجعShakeSizeParser.NextPaddingLen()القيمةshake128(IV) % 64، مما يضيف 0-63 بايت من الحشو العشوائي لكل جزء.تجربة الطول المُصادق: عند التفعيل عبر
TestsEnabled: "AuthenticatedLength"، تُشفّر أحجام الأجزاء نفسها بـ AEAD باستخدام مفتاح منفصل مشتق منKDF16(bodyKey, "auth_len").UDP عبر Mux: يُغلّف VMess حركة UDP كاتصالات Mux إلى
v1.mux.cool:666، مستخدماً تأطير XUDP.صيغة العنوان: يستخدم VMess ترتيب المنفذ-ثم-العنوان (بخلاف SOCKS5 الذي يستخدم العنوان-ثم-المنفذ). بايتات نوع العنوان:
0x01=IPv4،0x02=نطاق،0x03=IPv6.
المصدر: proxy/vmess/encoding/encoding.go:12-17