محرك التوجيه
يُقيِّم المُوجِّه الاتصالات الواردة مقابل قائمة من القواعد ويختار معالج الصادر الذي يجب أن يعالج حركة المرور. يدعم مطابقة النطاقات/IP/المنافذ، وقواعد بيانات GeoIP/GeoSite، وتوزيع الحمل.
المصدر: app/router/router.go، app/router/condition.go، app/router/strategy_*.go
هيكل Router
// app/router/router.go
type Router struct {
domainStrategy Config_DomainStrategy
rules []*Rule
balancers map[string]*Balancer
dns dns.Client
ctx context.Context
ohm outbound.Manager
dispatcher routing.Dispatcher
}تقييم القواعد
تُقيَّم القواعد بالتسلسل — أول قاعدة مُطابقة تفوز:
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
// تطبيق استراتيجية النطاق (قد يحل DNS)
ctx = r.applyDomainStrategy(ctx)
for _, rule := range r.rules {
if rule.Apply(ctx) {
return rule, ctx, nil
}
}
return nil, ctx, common.ErrNoClue
}استراتيجية النطاق
تتحكم استراتيجية النطاق فيما إذا كان المُوجِّه يحل أسماء النطاقات قبل تقييم القواعد:
| الاستراتيجية | السلوك |
|---|---|
AsIs | استخدام النطاق كما هو؛ عدم حل DNS |
IPIfNonMatch | المحاولة بالنطاق أولاً؛ إذا لم تتطابق أي قاعدة، حل إلى IP وإعادة المحاولة |
IPOnDemand | حل النطاق إلى IP فقط عندما تحتاج قاعدة مطابقة IP |
func (r *Router) applyDomainStrategy(ctx routing.Context) routing.Context {
switch r.domainStrategy {
case Config_IpIfNonMatch:
// الجولة الأولى: المحاولة بالنطاق
// إذا لم يتطابق: حل النطاق→IP، إعادة المحاولة
case Config_IpOnDemand:
// الحل فقط عند مواجهة قواعد مبنية على IP
}
}شروط القواعد
كل قاعدة لها Condition يفحص سياق التوجيه:
type Rule struct {
Condition Condition
Tag string // وسم الصادر
RuleTag string // معرف القاعدة للتسجيل
Balancer *Balancer // أو nil
}
type Condition interface {
Apply(ctx routing.Context) bool
}أنواع الشروط
| الشرط | الحقل | الوصف |
|---|---|---|
DomainMatcher | domain | مطابقة نطاق الهدف (full، substr، regex، domain) |
GeoIPMatcher | ip | مطابقة IP الهدف مع قاعدة بيانات GeoIP |
MultiGeoIPMatcher | geoip | مُطابقات GeoIP متعددة (رموز البلدان) |
PortMatcher | port | مطابقة منفذ الهدف أو نطاق المنافذ |
PortRangeMatcher | portList | مطابقة المنفذ مع النطاقات |
NetworkMatcher | network | TCP، UDP، أو كليهما |
ProtocolMatcher | protocol | البروتوكول المُستشعَر (http، tls، bittorrent) |
UserMatcher | user | البريد الإلكتروني للمستخدم المُصادَق |
InboundTagMatcher | inboundTag | وسم معالج الوارد |
AttributeMatcher | attrs | سمات HTTP (المُستشعَرة) |
ConditionChan | (مركب) | AND لشروط متعددة |
سياق التوجيه
يوفر سياق التوجيه جميع الحقول التي يمكن للقواعد مطابقتها:
// features/routing/session/context.go
type Context struct {
Inbound *session.Inbound // المصدر، الوسم، المستخدم
Outbound *session.Outbound // الهدف، routeTarget
Content *session.Content // البروتوكول المُستشعَر، السمات
}
func (ctx *Context) GetTargetDomain() string
func (ctx *Context) GetTargetIPs() []net.IP
func (ctx *Context) GetSourceIPs() []net.IP
func (ctx *Context) GetInboundTag() string
func (ctx *Context) GetUser() string
func (ctx *Context) GetProtocol() string
func (ctx *Context) GetAttributes() map[string]stringمطابقة النطاقات
تستخدم مطابقة النطاقات حزمة strmatcher التي توفر مُطابقات فعالة:
أنواع المُطابقات
// common/strmatcher/strmatcher.go
const (
Full Type = 0 // مطابقة تامة: "example.com"
Substr Type = 1 // يحتوي: "example" يُطابق "test.example.com"
Domain Type = 2 // لاحقة النطاق: "example.com" يُطابق "a.b.example.com"
Regex Type = 3 // تعبير نمطي
)تحسين MPH (Minimal Perfect Hash)
لقوائم النطاقات الكبيرة (GeoSite)، يستخدم Xray-core دالة Minimal Perfect Hash:
// common/strmatcher/mph_matcher.go
type MphIndexMatcher struct {
rules []matcherGroup // مجموعات المُطابقات لكل حاوية تجزئة
values []uint32 // جدول التجزئة
level0 []uint32
level1 []uint32
// ... جداول بحث MPH
}يوفر هذا بحثاً بتعقيد O(1) لأنماط المطابقة التامة ولاحقة النطاق، وهو أسرع بشكل كبير من المسح الخطي.
GeoIP / GeoSite
GeoIP
تستخدم المطابقة الجغرافية المبنية على IP قائمة مُرتَّبة من نطاقات CIDR مع بحث ثنائي:
// app/router/condition_geoip.go
type GeoIPMatcher struct {
ip4 []ipv6 // نطاقات IPv4 مُرتَّبة (مُعيَّنة إلى IPv6)
ip6 []ipv6 // نطاقات IPv6 مُرتَّبة
}
func (m *GeoIPMatcher) Match(ip net.IP) bool {
// بحث ثنائي في قائمة النطاقات المُرتَّبة
}GeoSite
تُحمَّل المطابقة الجغرافية المبنية على النطاقات من ملفات .dat (مُسلسلة بـ protobuf):
type GeoSiteList struct {
Entry []*GeoSite // رمز البلد → قائمة النطاقات
}
type GeoSite struct {
CountryCode string
Domain []*Domain // مع Type (full/domain/substr/regex)
}توزيع الحمل
عندما تشير قاعدة إلى موازن حمل بدلاً من وسم مباشر:
type Balancer struct {
selectors []string // أنماط وسوم الصادر
strategy BalancingStrategy // round-robin، random، least-ping، least-load
ohm outbound.Manager
}الاستراتيجيات
| الاستراتيجية | الوصف |
|---|---|
random | اختيار عشوائي بين الصادرات المُطابقة |
roundRobin | تدوير تسلسلي |
leastPing | أقل RTT من فحوصات Observatory |
leastLoad | أقل اتصالات نشطة / أفضل صحة |
func (b *Balancer) PickOutbound() (string, error) {
candidates := b.getMatchingOutbounds()
return b.strategy.Pick(candidates)
}تدفق سياق التوجيه
flowchart TB
Dispatch["المُوزِّع يستقبل<br/>(ctx, destination)"]
Sniff["الاستشعار: اكتشاف النطاق<br/>من TLS SNI / HTTP Host"]
Dispatch --> Sniff
Sniff --> SetCtx["تعيين سياق التوجيه:<br/>النطاق، عناوين IP، المنفذ، البروتوكول،<br/>وسم الوارد، المستخدم"]
SetCtx --> Strategy{استراتيجية النطاق؟}
Strategy -->|AsIs| Eval["تقييم القواعد بالتسلسل"]
Strategy -->|IPIfNonMatch| Eval
Strategy -->|IPOnDemand| Eval
Eval --> Match{تطابق القاعدة؟}
Match -->|نعم| Check{له موازن حمل؟}
Check -->|نعم| Balance["Balancer.Pick()"]
Check -->|لا| Tag["استخدام rule.Tag"]
Balance --> Handler["الحصول على معالج الصادر"]
Tag --> Handler
Match -->|لا، قواعد أخرى| Eval
Match -->|لا قواعد متبقية + IPIfNonMatch| Resolve["حل النطاق→IP"]
Resolve --> Eval2["إعادة التقييم مع عناوين IP"]
Match -->|لا قواعد متبقية| Default["استخدام الصادر الافتراضي"]
Eval2 --> Match2{تطابق القاعدة؟}
Match2 -->|نعم| Check
Match2 -->|لا| Defaultملاحظات التنفيذ
ترتيب القواعد مهم: أول تطابق يفوز. يتوقع المستخدمون أن تُقيَّم قواعدهم من الأعلى إلى الأسفل.
استراتيجية النطاق حرجة:
IPIfNonMatchيقوم بجولتين — أولاً بالنطاق، ثم بعناوين IP المحلولة. هذا يؤثر على الأداء (بحث DNS عند عدم التطابق).بحث ثنائي لـ GeoIP: نطاقات IP مُرتَّبة مسبقاً عند التحميل. استخدم البحث الثنائي، وليس المسح الخطي.
MPH للنطاقات: لأكثر من 100 ألف قاعدة نطاق (شائع مع GeoSite)، يوفر MPH تعقيد O(1) مقابل O(n). بدونه، تصبح مطابقة النطاقات عنق زجاجة.
RouteTarget مقابل Target: عند استخدام استشعار
routeOnly، يرى المُوجِّهRouteTarget(النطاق المُستشعَر) لكن الصادر يستخدمTarget(IP الأصلي).صحة موازن الحمل:
leastPingوleastLoadيعتمدان على ميزة Observatory التي تفحص صحة الصادرات دورياً.