Skip to content

Freedom و Blackhole

Freedom وBlackhole هما معالجان صادران فقط يقعان على طرفي نقيض: Freedom يتصل مباشرة بالوجهة (كما لو لا يوجد وكيل)، بينما Blackhole يُسقط كل حركة المرور بصمت.


Freedom (الصادر المباشر)

Freedom هو المعالج الصادر الافتراضي للاتصالات المباشرة. يتصل بالوجهة مباشرة، ويدعم استراتيجيات تحليل أسماء النطاقات، وتجزئة TCP لمكافحة الرقابة، وحقن ضوضاء UDP، وبروتوكول PROXY لتمرير معلومات العميل إلى الخوادم الخلفية.

نظرة عامة

  • الاتجاه: صادر فقط
  • النقل: TCP + UDP
  • التشفير: غير متاح (اتصال مباشر)
  • حالة الاستخدام: الوصول المباشر للإنترنت، الوصول للشبكة المحلية، المعالج الصادر الأخير في سلاسل التوجيه

تدفق الاتصال

mermaid
graph LR
    A[Routing Decision] --> B[Freedom Handler]
    B --> C{Domain Strategy?}
    C -->|AsIs| D[Dial as-is]
    C -->|UseIP/ForceIP| E[DNS Lookup]
    E --> D
    D --> F{Fragment?}
    F -->|Yes| G[Fragment Writer]
    F -->|No| H[Direct Writer]
    G --> I[Destination]
    H --> I

استراتيجية النطاق

يدعم Freedom استراتيجيات تحليل DNS للاتصالات الصادرة:

go
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
    ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
    if err != nil && h.config.DomainStrategy.ForceIP() {
        return err  // ForceIP fails if DNS fails
    }
    dialDest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
}

المصدر: proxy/freedom/freedom.go:114-134

الاستراتيجية الديناميكية لـ UDP: عندما يكون الهدف الأصلي عنوان IP وتم تحليل الوجهة من نطاق، تتكيف الاستراتيجية لتفضيل نفس عائلة العناوين:

go
if destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil {
    strategy = strategy.GetDynamicStrategy(origTargetAddr.Family())
}

المصدر: proxy/freedom/freedom.go:117-119

تجزئة TCP

يقسم إعداد Fragment رسائل TLS ClientHello إلى مقاطع TCP أصغر لتجاوز فحص الحزم العميق (DPI):

go
type FragmentWriter struct {
    fragment *Fragment
    writer   io.Writer
    count    uint64
}

المصدر: proxy/freedom/freedom.go:483-487

وضعان للتجزئة:

  1. تجزئة TLS ClientHello (PacketsFrom=0, PacketsTo=1): يُجزّئ فقط أول سجل TLS (البايت 0 يجب أن يكون 0x16 = مصافحة). يقسم بيانات المصافحة ضمن سجلات TLS مع الحفاظ على تأطير TLS صالح:
go
if f.count != 1 || len(b) <= 5 || b[0] != 22 {
    return f.writer.Write(b)  // Not TLS or not first packet
}
// Split TLS record into multiple records with random sizes
for from := 0; ; {
    to := from + int(crypto.RandBetween(LengthMin, LengthMax))
    // Create new TLS record header for each fragment
    copy(buff[:3], b)        // Content type + version
    buff[3] = byte(l >> 8)   // Fragment length high
    buff[4] = byte(l)        // Fragment length low
    // Write fragment with optional delay
}

المصدر: proxy/freedom/freedom.go:492-545

  1. تجزئة الحزم العامة (PacketsFrom > 0): يُجزّئ الحزم من N إلى M إلى أجزاء بأحجام عشوائية مع تأخيرات اختيارية بينها.

المصدر: proxy/freedom/freedom.go:547-568

معاملات التجزئة:

المعاملالوصف
PacketsFrom / PacketsToنطاق أرقام الحزم للتجزئة (0-1 لوضع TLS)
LengthMin / LengthMaxنطاق حجم الجزء العشوائي
IntervalMin / IntervalMaxتأخير عشوائي بين الأجزاء (بالمللي ثانية)
MaxSplitMin / MaxSplitMaxأقصى عدد تقسيمات لكل حزمة

حقن ضوضاء UDP

يمكن لـ Freedom حقن حزم "ضوضاء" قبل أول حزمة UDP حقيقية لإرباك فحص الحزم العميق:

go
type NoisePacketWriter struct {
    buf.Writer
    noises      []*Noise
    firstWrite  bool
    UDPOverride net.Destination
    remoteAddr  net.Address
}

المصدر: proxy/freedom/freedom.go:423-429

تُرسل الضوضاء قبل الكتابة الأولى، مع تخطي DNS (المنفذ 53):

go
if w.UDPOverride.Port == 53 {
    return w.Writer.WriteMultiBuffer(mb)  // Skip noise for DNS
}
for _, n := range w.noises {
    // Filter by ApplyTo: "ipv4", "ipv6", or "ip"
    // Send fixed or random noise packet
    // Optional delay between noise packets
}

المصدر: proxy/freedom/freedom.go:432-481

دعم بروتوكول PROXY

يمكن لـ Freedom إضافة ترويسات بروتوكول PROXY (الإصدار 1 أو 2) عند الاتصال بالخوادم الخلفية:

go
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
    version := byte(h.config.ProxyProtocol)
    srcAddr := inbound.Source.RawNetAddr()
    dstAddr := rawConn.RemoteAddr()
    header := proxyproto.HeaderProxyFromAddrs(version, srcAddr, dstAddr)
    header.WriteTo(rawConn)
}

المصدر: proxy/freedom/freedom.go:141-150

تجاوز الوجهة

يمكن لـ Freedom تجاوز عنوان/منفذ الوجهة:

go
if h.config.DestinationOverride != nil {
    server := h.config.DestinationOverride.Server
    if isValidAddress(server.Address) {
        destination.Address = server.Address.AsAddress()
    }
    if server.Port != 0 {
        destination.Port = net.Port(server.Port)
    }
}

المصدر: proxy/freedom/freedom.go:97-107

Splice (النسخ بدون نسخ)

على Linux، يدعم Freedom عملية splice للتحويل TCP بدون نسخ عندما يكون النقل TCP خام بدون TLS:

go
if destination.Network == net.Network_TCP && useSplice &&
    proxy.IsRAWTransportWithoutSecurity(conn) {
    return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer)
}

المصدر: proxy/freedom/freedom.go:214-222

علم useSplice يكون true افتراضياً ويمكن التحكم به عبر متغير البيئة XRAY_FREEDOM_SPLICE.

المصدر: proxy/freedom/freedom.go:31-49

معالجة حزم UDP

تحافظ معالجة UDP في Freedom على معلومات الوجهة لكل حزمة وتدعم تحليل النطاقات مع التخزين المؤقت:

go
type PacketWriter struct {
    *internet.PacketConnWrapper
    Handler         *Handler
    UDPOverride     net.Destination
    ResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]  // DNS cache
    LocalAddr       net.Address
}

المصدر: proxy/freedom/freedom.go:340-352

تُحلّل أسماء النطاقات في وجهات UDP وتُخزّن مؤقتاً لضمان توجيه متسق:

go
if b.UDP.Address.Family().IsDomain() {
    if ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
        b.UDP.Address = ip  // Use cached resolution
    } else {
        // Resolve and cache
    }
}

المصدر: proxy/freedom/freedom.go:370-401


Blackhole (المصرف الفارغ)

Blackhole هو معالج صادر بسيط يُسقط كل حركة المرور. يُرسل اختيارياً استجابة مختصرة قبل الإغلاق.

نظرة عامة

  • الاتجاه: صادر فقط
  • النقل: غير متاح
  • حالة الاستخدام: حظر الوجهات، حظر الإعلانات، إسقاط حركة المرور بناءً على التوجيه

المعالج

go
type Handler struct {
    response ResponseConfig
}

func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
    ob.Name = "blackhole"
    nBytes := h.response.WriteTo(link.Writer)
    if nBytes > 0 {
        time.Sleep(time.Second)  // Wait for response delivery
    }
    common.Interrupt(link.Writer)
    return nil
}

المصدر: proxy/blackhole/blackhole.go:16-43

السلوك الرئيسي:

  1. كتابة استجابة اختيارية إلى الكاتب
  2. الانتظار ثانية واحدة (إذا أُرسلت استجابة) لضمان التسليم
  3. مقاطعة الكاتب (إغلاق الاتصال)
  4. لا يُجري أي اتصال صادر

أنواع الاستجابة

NoneResponse (الافتراضي)

لا يكتب شيئاً، يُغلق فوراً:

go
func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }

المصدر: proxy/blackhole/config.go:25

HTTPResponse

يكتب استجابة HTTP 403 Forbidden:

go
const http403response = `HTTP/1.1 403 Forbidden
Connection: close
Cache-Control: max-age=3600, public
Content-Length: 0


`

func (*HTTPResponse) WriteTo(writer buf.Writer) int32 {
    b := buf.New()
    b.WriteString(http403response)
    n := b.Len()
    writer.WriteMultiBuffer(buf.MultiBuffer{b})
    return n
}

المصدر: proxy/blackhole/config.go:8-34

الإعدادات

يُحدّد نوع الاستجابة بحقل Response في الإعدادات:

go
func (c *Config) GetInternalResponse() (ResponseConfig, error) {
    if c.GetResponse() == nil {
        return new(NoneResponse), nil
    }
    config, err := c.GetResponse().GetInstance()
    return config.(ResponseConfig), nil
}

المصدر: proxy/blackhole/config.go:37-47

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

Freedom

  1. CanSpliceCopy = 1: يُعيّن Freedom أدنى مستوى splice إيجابي لأنه يمرّر البايتات مباشرة. عند دمجه مع معالج وارد يدعم splice أيضاً، يُفعّل هذا التحويل بدون نسخ الحقيقي على Linux.

المصدر: proxy/freedom/freedom.go:86

  1. منطق إعادة المحاولة: يعيد Freedom محاولة إنشاء الاتصال حتى 5 مرات مع تراجع أسي (يبدأ من 100 مللي ثانية).

المصدر: proxy/freedom/freedom.go:113

  1. بدون وارد: لا يُنفّذ Freedom واجهة Inbound. هو صادر فقط.

  2. مهلة خمول الاتصال: مثل جميع المعالجات الصادرة، يستخدم Freedom signal.CancelAfterInactivity() لإغلاق الاتصالات الخاملة.

  3. الوضع المُجمّع للتجزئة: عندما يكون IntervalMax يساوي 0، تُجمّع جميع أجزاء TLS في استدعاء كتابة واحد بدلاً من إرسالها منفصلة. يُقلّل هذا من استدعاءات النظام مع الاستمرار في إنشاء سجلات TLS متعددة.

المصدر: proxy/freedom/freedom.go:520-522

Blackhole

  1. بدون استخدام المتصل: يتجاهل Blackhole معامل dialer تماماً. لا يُجري أي اتصال صادر أبداً.

  2. مقاطعة وليس إغلاق: يستدعي المعالج common.Interrupt(link.Writer) بدلاً من الإغلاق العادي، مما يُشير إلى إنهاء غير طبيعي للمعالج الوارد.

  3. تأخير ثانية واحدة: عند إرسال استجابة HTTP، يضمن الانتظار لثانية واحدة وصول الاستجابة إلى العميل قبل إنهاء الاتصال. بدون هذا، قد تُفقد الاستجابة في مخزن النواة المؤقت.

المصدر: proxy/blackhole/blackhole.go:38-39

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