Skip to content

محرك التوجيه

يُقيِّم المُوجِّه الاتصالات الواردة مقابل قائمة من القواعد ويختار معالج الصادر الذي يجب أن يعالج حركة المرور. يدعم مطابقة النطاقات/IP/المنافذ، وقواعد بيانات GeoIP/GeoSite، وتوزيع الحمل.

المصدر: app/router/router.go، app/router/condition.go، app/router/strategy_*.go

هيكل Router

go
// 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
}

تقييم القواعد

تُقيَّم القواعد بالتسلسل — أول قاعدة مُطابقة تفوز:

go
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
go
func (r *Router) applyDomainStrategy(ctx routing.Context) routing.Context {
    switch r.domainStrategy {
    case Config_IpIfNonMatch:
        // الجولة الأولى: المحاولة بالنطاق
        // إذا لم يتطابق: حل النطاق→IP، إعادة المحاولة
    case Config_IpOnDemand:
        // الحل فقط عند مواجهة قواعد مبنية على IP
    }
}

شروط القواعد

كل قاعدة لها Condition يفحص سياق التوجيه:

go
type Rule struct {
    Condition Condition
    Tag       string     // وسم الصادر
    RuleTag   string     // معرف القاعدة للتسجيل
    Balancer  *Balancer  // أو nil
}

type Condition interface {
    Apply(ctx routing.Context) bool
}

أنواع الشروط

الشرطالحقلالوصف
DomainMatcherdomainمطابقة نطاق الهدف (full، substr، regex، domain)
GeoIPMatcheripمطابقة IP الهدف مع قاعدة بيانات GeoIP
MultiGeoIPMatchergeoipمُطابقات GeoIP متعددة (رموز البلدان)
PortMatcherportمطابقة منفذ الهدف أو نطاق المنافذ
PortRangeMatcherportListمطابقة المنفذ مع النطاقات
NetworkMatchernetworkTCP، UDP، أو كليهما
ProtocolMatcherprotocolالبروتوكول المُستشعَر (http، tls، bittorrent)
UserMatcheruserالبريد الإلكتروني للمستخدم المُصادَق
InboundTagMatcherinboundTagوسم معالج الوارد
AttributeMatcherattrsسمات HTTP (المُستشعَرة)
ConditionChan(مركب)AND لشروط متعددة

سياق التوجيه

يوفر سياق التوجيه جميع الحقول التي يمكن للقواعد مطابقتها:

go
// 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 التي توفر مُطابقات فعالة:

أنواع المُطابقات

go
// 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:

go
// common/strmatcher/mph_matcher.go
type MphIndexMatcher struct {
    rules   []matcherGroup  // مجموعات المُطابقات لكل حاوية تجزئة
    values  []uint32        // جدول التجزئة
    level0  []uint32
    level1  []uint32
    // ... جداول بحث MPH
}

يوفر هذا بحثاً بتعقيد O(1) لأنماط المطابقة التامة ولاحقة النطاق، وهو أسرع بشكل كبير من المسح الخطي.

GeoIP / GeoSite

GeoIP

تستخدم المطابقة الجغرافية المبنية على IP قائمة مُرتَّبة من نطاقات CIDR مع بحث ثنائي:

go
// 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):

go
type GeoSiteList struct {
    Entry []*GeoSite  // رمز البلد → قائمة النطاقات
}
type GeoSite struct {
    CountryCode string
    Domain      []*Domain  // مع Type (full/domain/substr/regex)
}

توزيع الحمل

عندما تشير قاعدة إلى موازن حمل بدلاً من وسم مباشر:

go
type Balancer struct {
    selectors []string          // أنماط وسوم الصادر
    strategy  BalancingStrategy // round-robin، random، least-ping، least-load
    ohm       outbound.Manager
}

الاستراتيجيات

الاستراتيجيةالوصف
randomاختيار عشوائي بين الصادرات المُطابقة
roundRobinتدوير تسلسلي
leastPingأقل RTT من فحوصات Observatory
leastLoadأقل اتصالات نشطة / أفضل صحة
go
func (b *Balancer) PickOutbound() (string, error) {
    candidates := b.getMatchingOutbounds()
    return b.strategy.Pick(candidates)
}

تدفق سياق التوجيه

mermaid
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

ملاحظات التنفيذ

  1. ترتيب القواعد مهم: أول تطابق يفوز. يتوقع المستخدمون أن تُقيَّم قواعدهم من الأعلى إلى الأسفل.

  2. استراتيجية النطاق حرجة: IPIfNonMatch يقوم بجولتين — أولاً بالنطاق، ثم بعناوين IP المحلولة. هذا يؤثر على الأداء (بحث DNS عند عدم التطابق).

  3. بحث ثنائي لـ GeoIP: نطاقات IP مُرتَّبة مسبقاً عند التحميل. استخدم البحث الثنائي، وليس المسح الخطي.

  4. MPH للنطاقات: لأكثر من 100 ألف قاعدة نطاق (شائع مع GeoSite)، يوفر MPH تعقيد O(1) مقابل O(n). بدونه، تصبح مطابقة النطاقات عنق زجاجة.

  5. RouteTarget مقابل Target: عند استخدام استشعار routeOnly، يرى المُوجِّه RouteTarget (النطاق المُستشعَر) لكن الصادر يستخدم Target (IP الأصلي).

  6. صحة موازن الحمل: leastPing و leastLoad يعتمدان على ميزة Observatory التي تفحص صحة الصادرات دورياً.

تحليل تقني لأغراض إعادة التنفيذ.