بروتوكول Trojan
Trojan هو بروتوكول مُصمّم ليكون غير قابل للتمييز عن حركة TLS/HTTPS العادية. يعتمد كلياً على طبقة نقل TLS للتشفير — يُرسل البروتوكول نفسه المصادقة والأوامر بنص عادي عبر نفق TLS. عندما يصل طلب غير صالح، يمكن للخادم "التراجع" إلى خادم ويب حقيقي، مما يجعل الوكيل غير قابل للكشف.
نظرة عامة
- الاتجاه: وارد + صادر
- النقل: TCP، مقبس UNIX (يجب استخدام نقل TLS/REALITY)
- التشفير: بدون (مُفوّض لطبقة نقل TLS)
- المصادقة: سلسلة سداسية عشرية SHA224(كلمة المرور)
- Mux: غير مدعوم أصلاً (لكن تغليف XUDP ممكن عبر طبقات أخرى)
- التراجع: تراجع مُدمج متعدد المستويات (SNI، ALPN، المسار)
صيغة السلك (Wire Format)
طلب TCP (من العميل إلى الخادم)
+----------------------------------------------+------+-----+----------+------+
| SHA224(password) hex | CRLF | Cmd | Address | CRLF |
| 56 bytes ASCII | 2B | 1B | variable | 2B |
+----------------------------------------------+------+-----+----------+------+
| Payload ... |
+-----------------------------------------------------------------------------+المصدر: proxy/trojan/protocol.go:64-95
| الحقل | الحجم | الوصف |
|---|---|---|
| تجزئة كلمة المرور | 56 بايت | SHA-224 مُرمّز بالسداسي عشري لكلمة المرور النصية |
| CRLF | 2 بايت | \r\n (0x0D 0x0A) |
| الأمر | 1 بايت | 0x01 = TCP Connect، 0x03 = UDP Associate |
| العنوان | متغير | بأسلوب SOCKS5: نوع العنوان(1 بايت) + العنوان + المنفذ(2 بايت, BE) |
| CRLF | 2 بايت | \r\n (0x0D 0x0A) |
أنواع العناوين (مطابقة لـ SOCKS5):
| البايت | النوع |
|---|---|
0x01 | IPv4 (4 بايت) |
0x03 | نطاق (1 بايت للطول + سلسلة نصية) |
0x04 | IPv6 (16 بايت) |
المصدر: proxy/trojan/protocol.go:16-21
بعد الترويسة، تكون البيانات المتبقية على تدفق TCP هي الحمولة الخام.
استجابة TCP
يُرسل الخادم الحمولة الخام — بدون ترويسة استجابة. وذلك لأن البروتوكول مصمّم ليبدو كحركة TLS عادية.
تأطير UDP
عندما يكون الأمر UDP (0x03)، تُؤطّر الحمولة لكل حزمة:
+----------+---------+------+---------+
| Address | Length | CRLF | Payload |
| variable | 2B BE | 2B | Length |
+----------+---------+------+---------+
| next packet ... |
+-------------------------------------+المصدر: proxy/trojan/protocol.go:125-150
| الحقل | الحجم | الوصف |
|---|---|---|
| العنوان | متغير | بأسلوب SOCKS5: نوع العنوان(1 بايت) + العنوان + المنفذ(2 بايت, BE) |
| الطول | 2 بايت | uint16 بترتيب big-endian، طول الحمولة (أقصى 8192) |
| CRLF | 2 بايت | \r\n |
| الحمولة | بايتات بحسب الطول | حزمة UDP |
يقرأ PacketReader على جانب الخادم كل حزمة مُؤطّرة ويُرفق الوجهة كـ buffer.UDP:
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
addr, port, err := addrParser.ReadAddressPort(nil, r)
// ...
remain := int(binary.BigEndian.Uint16(lengthBuf[:]))
if remain > maxLength { return nil, errors.New("oversize payload") }
// read CRLF, then read `remain` bytes of payload
}المصدر: proxy/trojan/protocol.go:220-262
مصادقة كلمة المرور
تُجزّأ كلمة المرور بـ SHA-224 وتُرمّز بالسداسي عشري لإنتاج سلسلة ASCII بطول 56 بايت:
func hexSha224(password string) []byte {
buf := make([]byte, 56)
hash := sha256.New224()
hash.Write([]byte(password))
hex.Encode(buf, hash.Sum(nil))
return buf
}المصدر: proxy/trojan/config.go:43-49
يخزّن MemoryAccount كلاً من كلمة المرور النصية والمفتاح السداسي عشري المحسوب مسبقاً بطول 56 بايت.
المُدقق (Validator)
يستخدم Validator مثيلين من sync.Map:
users: يربط تجزئة SHA224 المُرمّزة بالسداسي عشري ->*protocol.MemoryUseremail: يربط البريد الإلكتروني بأحرف صغيرة ->*protocol.MemoryUser
المصدر: proxy/trojan/validator.go:12-82
المعالج الوارد (الخادم)
الملف: proxy/trojan/server.go
معالجة الاتصال
sequenceDiagram
participant C as Client
participant S as Server
participant F as Fallback
C->>S: TLS ClientHello
S->>C: TLS ServerHello + Certificate
C->>S: SHA224(password) + CRLF + Command + Address + CRLF + Payload
alt Valid password
S->>S: Parse command & address
alt TCP Command
S->>C: Raw proxied response
else UDP Command
S->>C: Framed UDP packets
end
else Invalid or short data
S->>F: Forward entire connection to fallback
endيقرأ الخادم المخزن المؤقت الأول (حتى buf.Size بايت) ويُجري تحققاً سريعاً:
if firstLen < 58 || first.Byte(56) != '\r' {
// Not trojan protocol - fallback
shouldFallback = true
} else {
user = s.validator.Get(hexString(first.BytesTo(56)))
if user == nil {
shouldFallback = true
}
}المصدر: proxy/trojan/server.go:176-201
النقطة الجوهرية: يجب أن تكون أول 56 بايت تجزئة SHA224 سداسية عشرية صالحة، متبوعة بـ \r في الموضع 56. يتم التحقق من هذا قبل تحليل الترويسة بالكامل.
آلية التراجع
عندما يكون التراجع مُهيّأ، يتم تحويل الاتصالات غير الصالحة بشفافية إلى خادم آخر. يدعم نظام التراجع ثلاثة مستويات من المطابقة:
- SNI (
name): يُطابق مع ServerName لـ TLS - ALPN (
alpn): يُطابق مع بروتوكول ALPN المتفاوض عليه - المسار (
path): يُطابق مع مسار طلب HTTP (المستخرج من البايتات الأولى)
type Fallback struct {
Name string // SNI match
Alpn string // ALPN match
Path string // HTTP path match
Dest string // Destination address (e.g., "127.0.0.1:8080")
Type string // Network type ("tcp" or "unix")
Xver uint64 // PROXY protocol version (0=none, 1=v1, 2=v2)
}بنية بيانات التراجع هي خريطة متداخلة من 3 مستويات: map[name]map[alpn]map[path]*Fallback
المصدر: proxy/trojan/server.go:66-113
يدعم معالج التراجع أيضاً بروتوكول PROXY (النص v1 والثنائي v2) لتمرير عنوان IP الحقيقي للعميل إلى الخادم الخلفي:
// PROXY protocol v1
"PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"
// PROXY protocol v2
"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" // signature
"\x21\x11\x00\x0C" // v2 + PROXY + AF_INET + STREAM + 12 bytesالمصدر: proxy/trojan/server.go:493-522
المعالج الصادر (العميل)
الملف: proxy/trojan/client.go
يقوم معالج العميل بما يلي:
- الاتصال بالخادم عبر النقل المُهيّأ (عادةً TLS)
- إنشاء
ConnWriterالذي يكتب الترويسة بشكل كسول عند أول استدعاءWrite() - لـ UDP، يُغلّف بـ
PacketWriterلتأطير كل حزمة - إرسال الحمولة الأولى مع الترويسة لتحسين 0-RTT
connWriter := &ConnWriter{
Writer: bufferWriter,
Target: destination,
Account: account,
}
// First write triggers header send
buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100)المصدر: proxy/trojan/client.go:99-128
تُجمّع ConnWriter.writeHeader():
buffer.Write(c.Account.Key) // 56-byte SHA224 hex
buffer.Write(crlf) // \r\n
buffer.WriteByte(command) // 0x01 or 0x03
addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port)
buffer.Write(crlf) // \r\nالمصدر: proxy/trojan/protocol.go:64-95
ملاحظات التنفيذ
متطلب TLS: لا يوفر Trojan أي تشفير بنفسه. بدون TLS، تُنقل تجزئة كلمة المرور وجميع حركة المرور بنص واضح. البروتوكول مصمّم للاستخدام دائماً مع نقل TLS أو REALITY.
التراجع حيوي للتخفي: عندما لا يُعثر على ترويسة Trojan صالحة، يجب على الخادم تحويل الاتصال إلى خادم ويب حقيقي. هذا يعني أن فحص المنافذ والاستقصاء لا يمكنهما تمييز الخادم عن موقع HTTPS عادي.
بدون ترويسة استجابة: بخلاف VMess، لا توجد ترويسة من الخادم إلى العميل على الإطلاق. هذا يُبسّط التنفيذ لكنه يعني أن العميل لا يستطيع كشف أخطاء الخادم على مستوى البروتوكول.
حد أقصى لحمولة UDP: كل إطار UDP محدود بـ 8192 بايت (
maxLength = 8192). تُرفض الحزم التي تتجاوز هذا الحد.ConnReader ذو حالة: يُستدعى
ConnReader.ParseHeader()مرة واحدة فقط. بعد ذلك، يمرّرRead()مباشرة إلى القارئ الأساسي. يمنع علمheaderParsedإعادة التحليل.دعم الشبكة: يدعم الخادم
net.Network_TCPوnet.Network_UNIX، لكن ليس UDP الخام. يتم نقل حركة UDP كبيانات مُؤطّرة داخل اتصال TCP/TLS.
المصدر: proxy/trojan/server.go:144-146