Freedom و Blackhole
Freedom وBlackhole هما معالجان صادران فقط يقعان على طرفي نقيض: Freedom يتصل مباشرة بالوجهة (كما لو لا يوجد وكيل)، بينما Blackhole يُسقط كل حركة المرور بصمت.
Freedom (الصادر المباشر)
Freedom هو المعالج الصادر الافتراضي للاتصالات المباشرة. يتصل بالوجهة مباشرة، ويدعم استراتيجيات تحليل أسماء النطاقات، وتجزئة TCP لمكافحة الرقابة، وحقن ضوضاء UDP، وبروتوكول PROXY لتمرير معلومات العميل إلى الخوادم الخلفية.
نظرة عامة
- الاتجاه: صادر فقط
- النقل: TCP + UDP
- التشفير: غير متاح (اتصال مباشر)
- حالة الاستخدام: الوصول المباشر للإنترنت، الوصول للشبكة المحلية، المعالج الصادر الأخير في سلاسل التوجيه
تدفق الاتصال
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 للاتصالات الصادرة:
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 وتم تحليل الوجهة من نطاق، تتكيف الاستراتيجية لتفضيل نفس عائلة العناوين:
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):
type FragmentWriter struct {
fragment *Fragment
writer io.Writer
count uint64
}المصدر: proxy/freedom/freedom.go:483-487
وضعان للتجزئة:
- تجزئة TLS ClientHello (
PacketsFrom=0, PacketsTo=1): يُجزّئ فقط أول سجل TLS (البايت 0 يجب أن يكون0x16= مصافحة). يقسم بيانات المصافحة ضمن سجلات TLS مع الحفاظ على تأطير TLS صالح:
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
- تجزئة الحزم العامة (
PacketsFrom > 0): يُجزّئ الحزم من N إلى M إلى أجزاء بأحجام عشوائية مع تأخيرات اختيارية بينها.
المصدر: proxy/freedom/freedom.go:547-568
معاملات التجزئة:
| المعامل | الوصف |
|---|---|
PacketsFrom / PacketsTo | نطاق أرقام الحزم للتجزئة (0-1 لوضع TLS) |
LengthMin / LengthMax | نطاق حجم الجزء العشوائي |
IntervalMin / IntervalMax | تأخير عشوائي بين الأجزاء (بالمللي ثانية) |
MaxSplitMin / MaxSplitMax | أقصى عدد تقسيمات لكل حزمة |
حقن ضوضاء UDP
يمكن لـ Freedom حقن حزم "ضوضاء" قبل أول حزمة UDP حقيقية لإرباك فحص الحزم العميق:
type NoisePacketWriter struct {
buf.Writer
noises []*Noise
firstWrite bool
UDPOverride net.Destination
remoteAddr net.Address
}المصدر: proxy/freedom/freedom.go:423-429
تُرسل الضوضاء قبل الكتابة الأولى، مع تخطي DNS (المنفذ 53):
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) عند الاتصال بالخوادم الخلفية:
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 تجاوز عنوان/منفذ الوجهة:
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:
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 على معلومات الوجهة لكل حزمة وتدعم تحليل النطاقات مع التخزين المؤقت:
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 وتُخزّن مؤقتاً لضمان توجيه متسق:
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 هو معالج صادر بسيط يُسقط كل حركة المرور. يُرسل اختيارياً استجابة مختصرة قبل الإغلاق.
نظرة عامة
- الاتجاه: صادر فقط
- النقل: غير متاح
- حالة الاستخدام: حظر الوجهات، حظر الإعلانات، إسقاط حركة المرور بناءً على التوجيه
المعالج
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
السلوك الرئيسي:
- كتابة استجابة اختيارية إلى الكاتب
- الانتظار ثانية واحدة (إذا أُرسلت استجابة) لضمان التسليم
- مقاطعة الكاتب (إغلاق الاتصال)
- لا يُجري أي اتصال صادر
أنواع الاستجابة
NoneResponse (الافتراضي)
لا يكتب شيئاً، يُغلق فوراً:
func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }المصدر: proxy/blackhole/config.go:25
HTTPResponse
يكتب استجابة HTTP 403 Forbidden:
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 في الإعدادات:
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
- CanSpliceCopy = 1: يُعيّن Freedom أدنى مستوى splice إيجابي لأنه يمرّر البايتات مباشرة. عند دمجه مع معالج وارد يدعم splice أيضاً، يُفعّل هذا التحويل بدون نسخ الحقيقي على Linux.
المصدر: proxy/freedom/freedom.go:86
- منطق إعادة المحاولة: يعيد Freedom محاولة إنشاء الاتصال حتى 5 مرات مع تراجع أسي (يبدأ من 100 مللي ثانية).
المصدر: proxy/freedom/freedom.go:113
بدون وارد: لا يُنفّذ Freedom واجهة
Inbound. هو صادر فقط.مهلة خمول الاتصال: مثل جميع المعالجات الصادرة، يستخدم Freedom
signal.CancelAfterInactivity()لإغلاق الاتصالات الخاملة.الوضع المُجمّع للتجزئة: عندما يكون
IntervalMaxيساوي 0، تُجمّع جميع أجزاء TLS في استدعاء كتابة واحد بدلاً من إرسالها منفصلة. يُقلّل هذا من استدعاءات النظام مع الاستمرار في إنشاء سجلات TLS متعددة.
المصدر: proxy/freedom/freedom.go:520-522
Blackhole
بدون استخدام المتصل: يتجاهل Blackhole معامل
dialerتماماً. لا يُجري أي اتصال صادر أبداً.مقاطعة وليس إغلاق: يستدعي المعالج
common.Interrupt(link.Writer)بدلاً من الإغلاق العادي، مما يُشير إلى إنهاء غير طبيعي للمعالج الوارد.تأخير ثانية واحدة: عند إرسال استجابة HTTP، يضمن الانتظار لثانية واحدة وصول الاستجابة إلى العميل قبل إنهاء الاتصال. بدون هذا، قد تُفقد الاستجابة في مخزن النواة المؤقت.
المصدر: proxy/blackhole/blackhole.go:38-39