نقل TCP
مقدمة
نقل TCP هو النقل الافتراضي والأكثر أساسية في Xray-core. يوفر اتصالات TCP خام مع أمان TLS/REALITY اختياري، وتمويه رؤوس HTTP، وضبط متقدم على مستوى المقبس. يغطي هذا المستند تدفق الاتصال/الاستماع، ودعم خيارات المقبس، وسباق Happy Eyeballs ثنائي المكدس، ودعم البروكسي الشفاف (TProxy).
تسجيل البروتوكول
يتم تسجيل نقل TCP تحت الاسم "tcp" (transport/internet/tcp/tcp.go:3):
const protocolName = "tcp"يتم التسجيل في ثلاث دوال init():
- المتصل:
tcp/dialer.go:110-112--RegisterTransportDialer("tcp", Dial) - المستمع:
tcp/hub.go:138-140--RegisterTransportListener("tcp", ListenTCP) - منشئ الإعدادات:
tcp/config.go:8-12--RegisterProtocolConfigCreator("tcp", ...)
تدفق الاتصال
نقطة الدخول
tcp.Dial (tcp/dialer.go:20-108) يتم استدعاؤها بواسطة طبقة توجيه النقل:
func Dial(ctx context.Context, dest net.Destination,
streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
// Apply TLS or REALITY...
// Apply header authenticator...
return stat.Connection(conn), nil
}تطبيق طبقة الأمان
بعد إنشاء اتصال TCP الخام، يتم تطبيق الأمان بترتيب الأولوية (tcp/dialer.go:27-93):
- TLS: إذا أعادت
tls.ConfigFromStreamSettingsقيمة غير فارغة، يتم إجراء مصافحة TLS. يُستخدم بصمة uTLS إذا تم تكوين بصمة (tls.UClient)، وإلا يُستخدم TLS القياسي من Go (tls.Client). - REALITY: إذا أعادت
reality.ConfigFromStreamSettingsقيمة غير فارغة (ولم يكن TLS مُعدًا)، يتم إجراء مصافحة REALITY عبرreality.UClient.
تمويه الرؤوس
بعد الأمان، يمكن لـ ConnectionAuthenticator اختياري تغليف الاتصال (tcp/dialer.go:95-106):
tcpSettings := streamSettings.ProtocolSettings.(*Config)
if tcpSettings.HeaderSettings != nil {
headerConfig, _ := tcpSettings.HeaderSettings.GetInstance()
auth, _ := internet.CreateConnectionAuthenticator(headerConfig)
conn = auth.Client(conn)
}هذا يمكّن تمويه رؤوس HTTP حيث يتم تغليف تيار TCP ليبدو كحركة مرور HTTP عادية.
تدفق الاستماع
نقطة الدخول
tcp.ListenTCP (tcp/hub.go:30-95) ينشئ مستمع TCP:
func ListenTCP(ctx context.Context, address net.Address, port net.Port,
streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
// ...
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
// Configure TLS / REALITY / header auth
go l.keepAccepting()
return l, nil
}حلقة قبول الاتصالات
الروتين المتزامن keepAccepting (tcp/hub.go:97-126) يقبل الاتصالات ويطبق الأمان:
func (v *Listener) keepAccepting() {
for {
conn, err := v.listener.Accept()
// ...
go func() {
if v.tlsConfig != nil {
conn = tls.Server(conn, v.tlsConfig)
} else if v.realityConfig != nil {
conn, err = reality.Server(conn, v.realityConfig)
}
if v.authConfig != nil {
conn = v.authConfig.Server(conn)
}
v.addConn(stat.Connection(conn))
}()
}
}دعم مقبس نطاق Unix
يدعم كل من الاتصال والاستماع مقابس نطاق Unix. يتحقق المستمع من port == 0 للتبديل إلى وضع Unix (tcp/hub.go:44-55).
بروتوكول PROXY
يدمج مستمع TCP قبول بروتوكول PROXY من كل من إعدادات TCP وإعدادات المقبس (tcp/hub.go:37-41):
streamSettings.SocketSettings.AcceptProxyProtocol =
l.config.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocolعند التفعيل، يقوم internet.ListenSystem بتغليف المستمع بـ proxyproto.Listener (system_listener.go:170-173).
متصل النظام: خيارات المقبس
DefaultSystemDialer.Dial (transport/internet/system_dialer.go:51-146) هو المكان الذي يتم فيه إنشاء اتصالات نظام التشغيل الخام. يقوم بتكوين:
- Keep-Alive: إعدادات افتراضية مشابهة لـ Chrome (45 ثانية خمول، 45 ثانية فاصل) عبر
net.KeepAliveConfigفي Go 1.24+ (system_dialer.go:91-117) - Multipath TCP:
dialer.SetMultipathTCP(true)عند تعيينTcpMptcp(system_dialer.go:121-123) - التحكم بالمقبس: رد نداء
Controlيطبق خيارات خاصة بالمنصة عبرapplyOutboundSocketOptions(system_dialer.go:124-143)
خيارات مقبس Linux (sockopt_linux.go)
تطبيق Linux (transport/internet/sockopt_linux.go:43-138) يدعم:
| الخيار | استدعاء النظام | حقل الإعداد |
|---|---|---|
| SO_MARK | SOL_SOCKET, SO_MARK | config.Mark |
| SO_BINDTODEVICE | BindToDevice | config.Interface |
| TCP_FASTOPEN_CONNECT | SOL_TCP, TCP_FASTOPEN_CONNECT | config.Tfo (صادر) |
| TCP_FASTOPEN | SOL_TCP, TCP_FASTOPEN | config.Tfo (وارد) |
| TCP_CONGESTION | SOL_TCP, TCP_CONGESTION | config.TcpCongestion |
| TCP_WINDOW_CLAMP | IPPROTO_TCP, TCP_WINDOW_CLAMP | config.TcpWindowClamp |
| TCP_USER_TIMEOUT | IPPROTO_TCP, TCP_USER_TIMEOUT | config.TcpUserTimeout |
| TCP_MAXSEG | IPPROTO_TCP, TCP_MAXSEG | config.TcpMaxSeg |
| IP_TRANSPARENT | SOL_IP, IP_TRANSPARENT | config.Tproxy |
| IPV6_RECVORIGDSTADDR | متنوعة | config.ReceiveOriginalDestAddress |
| مخصص | مستوى/خيار محدد من المستخدم | config.CustomSockopt |
خيارات مقبس macOS (sockopt_darwin.go)
تطبيق Darwin (transport/internet/sockopt_darwin.go:103-192) له اختلافات خاصة بالمنصة:
- TCP_FASTOPEN يستخدم ثوابت المنصة:
TCP_FASTOPEN_SERVER = 0x01،TCP_FASTOPEN_CLIENT = 0x02(sockopt_darwin.go:19-21) - ربط الواجهة يستخدم
IP_BOUND_IF/IPV6_BOUND_IFبدلاً منSO_BINDTODEVICE(sockopt_darwin.go:136-151) - البروكسي الشفاف يستخدم
/dev/pfو ioctlDIOCNATLOOKللبحث عن الوجهة الأصلية (sockopt_darwin.go:40-101)
تحليل قيمة TFO
يتم تحليل قيمة إعداد TFO بشكل خاص (transport/internet/sockopt.go:21-30):
func (v *SocketConfig) ParseTFOValue() int {
if v.Tfo == 0 { return -1 } // not set
tfo := int(v.Tfo)
if tfo < 0 { tfo = 0 } // explicitly disabled
return tfo
}على Linux الصادر، أي قيمة موجبة تصبح 1 لـ TCP_FASTOPEN_CONNECT. على Linux الوارد، يتم تمرير القيمة مباشرة إلى TCP_FASTOPEN كطول قائمة الانتظار.
خيارات المقبس المخصصة
آلية CustomSockopt (sockopt_linux.go:93-129) تسمح باستدعاءات setsockopt عشوائية:
for _, custom := range config.CustomSockopt {
if custom.System != "" && custom.System != runtime.GOOS { continue }
if !strings.HasPrefix(network, custom.Network) { continue }
// Apply int or string setsockopt
}هذا يدعم التصفية حسب نظام التشغيل ونوع الشبكة (مثلاً، "tcp" يطابق tcp4/tcp6).
Happy Eyeballs (RFC 8305)
عند حل عناوين IP متعددة وتفعيل Happy Eyeballs، ينفذ TcpRaceDial (transport/internet/happy_eyeballs.go:16-97) معيار RFC 8305:
ترتيب عناوين IP
sortIPs (happy_eyeballs.go:100-156) يتناوب بين عناوين IPv4 و IPv6:
func sortIPs(ips []net.IP, prioritizeIPv6 bool, interleave uint32) []net.IP {
// Separate into ip4 and ip6 slices
// Interleave: alternate ip4/ip6 based on interleave count
// prioritizeIPv6 controls which family goes first
}مع قيمة interleave=1 الافتراضية، تبدو النتيجة كالتالي: [v6, v4, v6, v4, ...] (أو [v4, v6, ...] إذا لم يكن IPv6 مفضلاً).
خوارزمية سباق الاتصال
sequenceDiagram
participant C as TcpRaceDial
participant T as Timer
participant G1 as Goroutine 1
participant G2 as Goroutine 2
participant G3 as Goroutine 3
C->>T: Start (0ms delay for first)
T->>G1: tcpTryDial(IP[0])
T->>T: Reset(tryDelayMs)
T->>G2: tcpTryDial(IP[1])
G1-->>C: result{conn, nil}
C->>C: Cancel context (stop others)
C->>C: Wait for active goroutines
C-->>C: Return winning connالمعاملات الرئيسية من إعدادات HappyEyeballs:
- TryDelayMs: التأخير بين بدء كل محاولة جديدة (الافتراضي 250 مللي ثانية وفقًا لـ RFC 8305)
- MaxConcurrentTry: الحد الأقصى لمحاولات الاتصال المتزامنة
- PrioritizeIpv6: ما إذا كان IPv6 يأتي أولاً في القائمة المتناوبة
- Interleave: عدد العناوين من عائلة واحدة قبل التبديل
أول اتصال ينجح يفوز. يتم إغلاق جميع الاتصالات الأخرى. تتعامل الخوارزمية مع الإلغاء والفشل بشكل سلس (happy_eyeballs.go:35-96).
شروط التفعيل
يُستخدم Happy Eyeballs فقط عند استيفاء جميع الشروط (dialer.go:263):
- إعدادات HappyEyeballs ليست فارغة
- TryDelayMs > 0 و MaxConcurrentTry > 0
- تم حل عنوانين IP على الأقل
- لا يوجد DialerProxy مُعدّ
- الوجهة هي TCP
البروكسي الشفاف (TProxy)
Linux
على Linux، يعمل TProxy عن طريق تعيين IP_TRANSPARENT على المقابس (sockopt_linux.go:131-135)، مما يسمح للعملية بالربط بعناوين غير محلية. يتم استرداد الوجهة الأصلية باستخدام SO_ORIGINAL_DST (tcp/sockopt_linux.go:18-52):
func GetOriginalDestination(conn stat.Connection) (net.Destination, error) {
// Uses getsockopt(SO_ORIGINAL_DST) to recover the original destination
// from a redirected connection (via iptables REDIRECT/TPROXY)
}macOS
على macOS، يتم استرداد الوجهة الأصلية عبر PF (sockopt_darwin.go:40-101) باستخدام ioctl DIOCNATLOOK على /dev/pf.
مستمع النظام
DefaultListener.Listen (transport/internet/system_listener.go:78-175) ينشئ مستمعين على مستوى نظام التشغيل مع:
- التحكم بالمقبس: يطبق
applyInboundSocketOptionsعبرgetControlFunc(system_listener.go:24-42) - SO_REUSEPORT: يتم تعيينه دائمًا على المستمعين (
system_listener.go:39) - مقابس نطاق Unix: يدعم المقابس المجردة (بادئة
@على Linux)، وأذونات الملفات، وقفل الملفات (system_listener.go:115-166) - بروتوكول PROXY: يغلف بـ
proxyproto.Listenerعند التفعيل (system_listener.go:170-173) - Multipath TCP:
lc.SetMultipathTCP(true)عند التكوين (system_listener.go:111-113)
ملاحظات التنفيذ
- البروتوكول الافتراضي: عندما لا يتم تحديد نقل، يُستخدم TCP (
config.go:59-63). - تغليف UDP: لوجهات UDP، ينشئ
DefaultSystemDialerكائنPacketConnWrapperالذي ينفذnet.Connفوقnet.PacketConn(system_dialer.go:54-89). - إعدادات Keep-alive الافتراضية: يحاكي المتصل سلوك Chrome في Keep-alive: 45 ثانية خمول + 45 ثانية فاصل (
system_dialer.go:91-96). المستمعون يعطلون Keep-alive افتراضيًا (system_listener.go:92). - FakePacketConn: يُستخدم لتغليف اتصالات TCP كـ
PacketConnلسيناريوهات QUIC-over-TCP (system_dialer.go:258-283). - مهلة المتصل: مُعينة بشكل ثابت عند 16 ثانية (
system_dialer.go:113). - REALITY على المستمع: عند تكوين REALITY، يبدأ المستمع روتينًا متزامنًا لـ
DetectPostHandshakeRecordsLens(tcp/hub.go:78).