Skip to content

نقل TCP

مقدمة

نقل TCP هو النقل الافتراضي والأكثر أساسية في Xray-core. يوفر اتصالات TCP خام مع أمان TLS/REALITY اختياري، وتمويه رؤوس HTTP، وضبط متقدم على مستوى المقبس. يغطي هذا المستند تدفق الاتصال/الاستماع، ودعم خيارات المقبس، وسباق Happy Eyeballs ثنائي المكدس، ودعم البروكسي الشفاف (TProxy).

تسجيل البروتوكول

يتم تسجيل نقل TCP تحت الاسم "tcp" (transport/internet/tcp/tcp.go:3):

go
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) يتم استدعاؤها بواسطة طبقة توجيه النقل:

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

  1. TLS: إذا أعادت tls.ConfigFromStreamSettings قيمة غير فارغة، يتم إجراء مصافحة TLS. يُستخدم بصمة uTLS إذا تم تكوين بصمة (tls.UClient)، وإلا يُستخدم TLS القياسي من Go (tls.Client).
  2. REALITY: إذا أعادت reality.ConfigFromStreamSettings قيمة غير فارغة (ولم يكن TLS مُعدًا)، يتم إجراء مصافحة REALITY عبر reality.UClient.

تمويه الرؤوس

بعد الأمان، يمكن لـ ConnectionAuthenticator اختياري تغليف الاتصال (tcp/dialer.go:95-106):

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

go
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) يقبل الاتصالات ويطبق الأمان:

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

go
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_MARKSOL_SOCKET, SO_MARKconfig.Mark
SO_BINDTODEVICEBindToDeviceconfig.Interface
TCP_FASTOPEN_CONNECTSOL_TCP, TCP_FASTOPEN_CONNECTconfig.Tfo (صادر)
TCP_FASTOPENSOL_TCP, TCP_FASTOPENconfig.Tfo (وارد)
TCP_CONGESTIONSOL_TCP, TCP_CONGESTIONconfig.TcpCongestion
TCP_WINDOW_CLAMPIPPROTO_TCP, TCP_WINDOW_CLAMPconfig.TcpWindowClamp
TCP_USER_TIMEOUTIPPROTO_TCP, TCP_USER_TIMEOUTconfig.TcpUserTimeout
TCP_MAXSEGIPPROTO_TCP, TCP_MAXSEGconfig.TcpMaxSeg
IP_TRANSPARENTSOL_IP, IP_TRANSPARENTconfig.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 و ioctl DIOCNATLOOK للبحث عن الوجهة الأصلية (sockopt_darwin.go:40-101)

تحليل قيمة TFO

يتم تحليل قيمة إعداد TFO بشكل خاص (transport/internet/sockopt.go:21-30):

go
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 عشوائية:

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

go
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 مفضلاً).

خوارزمية سباق الاتصال

mermaid
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):

go
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).

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