بروتوكول ShadowTLS
ShadowTLS هو بروتوكول طبقة نقل يخفي حركة مرور الوكيل كحركة مرور TLS شرعية عن طريق اختطاف مصافحة TLS مع خادم حقيقي. يدعم ثلاثة إصدارات من البروتوكول بتطور متزايد.
المصدر: protocol/shadowtls/inbound.go، protocol/shadowtls/outbound.go، sing-shadowtls
مفهوم البروتوكول
على عكس الوكلاء التقليديين القائمة على TLS التي تولد شهاداتها الخاصة (القابلة للكشف عبر فحص الشهادات)، يجري ShadowTLS مصافحة TLS حقيقية مع خادم شرعي (مثل www.microsoft.com)، مما يجعل المصافحة غير قابلة للتمييز عن حركة مرور HTTPS العادية بالنسبة للمراقبين. بعد المصافحة، يتم اختطاف قناة البيانات لنقل حركة مرور الوكيل.
إصدارات البروتوكول
الإصدار 1
الإصدار الأبسط. يبدأ العميل مصافحة TLS عبر خادم ShadowTLS، الذي يرحلها إلى خادم TLS حقيقي ("خادم المصافحة"). بعد اكتمال المصافحة، يُعاد استخدام اتصال TLS لبيانات الوكيل.
القيد: يفرض TLS 1.2 لضمان سلوك مصافحة متوقع.
if options.Version == 1 {
options.TLS.MinVersion = "1.2"
options.TLS.MaxVersion = "1.2"
}الإصدار 2
يضيف مصادقة قائمة على كلمة المرور. يمكن للخادم التمييز بين عملاء ShadowTLS الشرعيين والمسابير. يدعم خوادم مصافحة لكل SNI:
if options.Version > 1 {
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
for _, entry := range options.HandshakeForServerName.Entries() {
handshakeForServerName[entry.Key] = shadowtls.HandshakeConfig{
Server: entry.Value.ServerOptions.Build(),
Dialer: handshakeDialer,
}
}
}الإصدار 3
الإصدار الأكثر تقدماً. يقدم ربط القناة القائم على معرف الجلسة -- يقوم العميل والخادم بتضمين بيانات المصادقة داخل معرف جلسة TLS، مما يتيح التحقق بدون رحلة ذهاب وإياب إضافية.
case 3:
if idConfig, loaded := tlsConfig.(tls.WithSessionIDGenerator); loaded {
// استخدام خطاف معرف جلسة مكتبة TLS
tlsHandshakeFunc = func(ctx, conn, sessionIDGenerator) error {
idConfig.SetSessionIDGenerator(sessionIDGenerator)
return tls.ClientHandshake(ctx, conn, tlsConfig)
}
} else {
// الرجوع إلى TLS القياسي مع حقن معرف الجلسة يدوياً
stdTLSConfig := tlsConfig.STDConfig()
tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(password, stdTLSConfig)
}بنية الوارد (Inbound)
type Inbound struct {
inbound.Adapter
router adapter.Router
logger logger.ContextLogger
listener *listener.Listener
service *shadowtls.Service
}تكوين الخدمة
service, _ := shadowtls.NewService(shadowtls.ServiceConfig{
Version: options.Version,
Password: options.Password,
Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User {
return (shadowtls.User)(it)
}),
Handshake: shadowtls.HandshakeConfig{
Server: options.Handshake.ServerOptions.Build(),
Dialer: handshakeDialer,
},
HandshakeForServerName: handshakeForServerName, // توجيه لكل SNI
StrictMode: options.StrictMode,
WildcardSNI: shadowtls.WildcardSNI(options.WildcardSNI),
Handler: (*inboundHandler)(inbound),
Logger: logger,
})الحقول الرئيسية:
- Handshake: خادم المصافحة الهدف الافتراضي
- HandshakeForServerName: خريطة SNI ← خادم المصافحة لدعم النطاقات المتعددة
- StrictMode: رفض الاتصالات التي تفشل في المصادقة (مقابل التحويل الصامت)
- WildcardSNI: قبول أي قيمة SNI (مفيد لسيناريوهات CDN)
SNI بحرف البدل
يتحكم خيار WildcardSNI في كيفية معالجة SNI:
serverIsDomain := options.Handshake.ServerIsDomain()
if options.WildcardSNI != option.ShadowTLSWildcardSNIOff {
serverIsDomain = true // فرض حل النطاق لحرف البدل
}تدفق الاتصال (الوارد)
func (h *Inbound) NewConnectionEx(ctx, conn, metadata, onClose) {
// تعالج خدمة ShadowTLS كامل ترحيل المصافحة واستخراج البيانات
err := h.service.NewConnection(ctx, conn, metadata.Source, metadata.Destination, onClose)
N.CloseOnHandshakeFailure(conn, onClose, err)
}بعد أن تستخرج خدمة ShadowTLS تيار البيانات الحقيقي، تستدعي معالج الوارد:
type inboundHandler Inbound
func (h *inboundHandler) NewConnectionEx(ctx, conn, source, destination, onClose) {
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.Source = source
metadata.Destination = destination
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
metadata.User = userName
}
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}بنية الصادر (Outbound)
type Outbound struct {
outbound.Adapter
client *shadowtls.Client
}صادر ShadowTLS يدعم TCP فقط ويعمل كغلاف نقل -- عادة ما يتم ربطه مع بروتوكول آخر (مثل Shadowsocks):
func (h *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
switch network {
case "tcp":
return h.client.DialContext(ctx) // يُرجع اتصالاً "نظيفاً"
default:
return nil, os.ErrInvalid // UDP غير مدعوم
}
}
func (h *Outbound) ListenPacket(ctx, destination) (net.PacketConn, error) {
return nil, os.ErrInvalid // UDP غير مدعوم
}متطلب TLS
يتطلب صادر ShadowTLS تفعيل TLS إلزامياً:
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}تكوين العميل
client, _ := shadowtls.NewClient(shadowtls.ClientConfig{
Version: options.Version,
Password: options.Password,
Server: options.ServerOptions.Build(),
Dialer: outboundDialer,
TLSHandshake: tlsHandshakeFunc, // مصافحة خاصة بالإصدار
Logger: logger,
})مصافحة TLS الخاصة بالإصدار
var tlsHandshakeFunc shadowtls.TLSHandshakeFunc
switch options.Version {
case 1, 2:
// بسيط: فقط إجراء مصافحة TLS
tlsHandshakeFunc = func(ctx, conn, _ TLSSessionIDGeneratorFunc) error {
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
}
case 3:
// معقد: حقن مولد معرف الجلسة لربط القناة
tlsHandshakeFunc = func(ctx, conn, sessionIDGenerator) error {
idConfig.SetSessionIDGenerator(sessionIDGenerator)
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
}
}كيف يعمل ShadowTLS (بالتفصيل)
العميل خادم ShadowTLS خادم TLS الحقيقي
| | |
|--- TLS ClientHello ---->|--- TLS ClientHello ----->|
| | |
|<-- TLS ServerHello -----|<-- TLS ServerHello ------|
|<-- Certificate ---------|<-- Certificate ----------|
|<-- ServerHelloDone -----|<-- ServerHelloDone ------|
| | |
|--- ClientKeyExchange -->|--- ClientKeyExchange --->|
|--- ChangeCipherSpec --->|--- ChangeCipherSpec ---->|
|--- Finished ----------->|--- Finished ------------>|
| | |
|<-- ChangeCipherSpec ----|<-- ChangeCipherSpec -----|
|<-- Finished ------------|<-- Finished -------------|
| | |
| [اكتملت مصافحة TLS - المراقب يرى شهادة صالحة] |
| | |
|=== بيانات الوكيل =======>| [البيانات لم تعد تُرسل |
|<=== بيانات الوكيل =======| إلى خادم TLS الحقيقي] |بعد المصافحة، يقوم خادم ShadowTLS بما يلي:
- قطع الاتصال عن خادم TLS الحقيقي
- استخراج تيار بيانات الوكيل من العميل
- تحويله إلى المعالج الداخلي المكون
نمط الاستخدام المعتاد
يُستخدم ShadowTLS كتحويلة (detour) لبروتوكول آخر:
{
"outbounds": [
{
"type": "shadowsocks",
"tag": "ss-out",
"detour": "shadowtls-out",
"method": "2022-blake3-aes-256-gcm",
"password": "ss-password"
},
{
"type": "shadowtls",
"tag": "shadowtls-out",
"server": "my-server.com",
"server_port": 443,
"version": 3,
"password": "shadowtls-password",
"tls": {
"enabled": true,
"server_name": "www.microsoft.com"
}
}
]
}يتم نفق اتصال Shadowsocks عبر غلاف ShadowTLS، الذي يجري المصافحة بشهادة www.microsoft.com الحقيقية.
مثال على التكوين (الوارد)
{
"type": "shadowtls",
"tag": "shadowtls-in",
"listen": "::",
"listen_port": 443,
"version": 3,
"users": [
{ "name": "user1", "password": "user-password" }
],
"handshake": {
"server": "www.microsoft.com",
"server_port": 443
},
"handshake_for_server_name": {
"www.google.com": {
"server": "www.google.com",
"server_port": 443
}
},
"strict_mode": true
}