Skip to content

Dokodemo-door

Dokodemo-door ("الباب إلى أي مكان" باليابانية) هو معالج بروتوكول وارد فقط للوكالة الشفافة. يقبل الاتصالات على أي منفذ ويحوّلها إلى وجهة مُهيّأة أو محددة ديناميكياً. وهو الآلية الأساسية لـ TProxy وإعادة التوجيه الشفافة وتحويل المنافذ ذات الوجهة الثابتة.

نظرة عامة

  • الاتجاه: وارد فقط
  • النقل: TCP + UDP (قابل للتهيئة)
  • التشفير: غير متاح (شفاف)
  • المصادقة: غير متاح
  • حالات الاستخدام: الوكيل الشفاف (iptables REDIRECT/TPROXY)، تحويل المنافذ، اعتراض DNS

البنية المعمارية

لا يملك Dokodemo-door بروتوكولاً خاصاً به — يأخذ ببساطة الاتصالات الواردة ويحوّلها. تُحدّد الوجهة بإحدى ثلاث آليات:

  1. وجهة ثابتة: العنوان والمنفذ محددان في الإعدادات
  2. متابعة إعادة التوجيه: استخدام الوجهة الأصلية من iptables REDIRECT/TPROXY (المُستخلصة من البيانات الوصفية للاتصال الصادر)
  3. تعيين المنافذ: ربط المنفذ الوارد بعنوان/منفذ وجهة مختلف
mermaid
graph TD
    A[Incoming Connection] --> B{FollowRedirect?}
    B -->|Yes| C[Get original dest from OS/metadata]
    B -->|No| D{Fixed address configured?}
    D -->|Yes| E[Use config address:port]
    D -->|No| F[Use connection local address]
    C --> G[Dispatch to routing]
    E --> G
    F --> G

الإعدادات

go
type DokodemoDoor struct {
    policyManager policy.Manager
    config        *Config
    address       net.Address   // Fixed destination address
    port          net.Port      // Fixed destination port
    portMap       map[string]string  // Port remapping
    sockopt       *session.Sockopt   // Socket options (for mark)
}

المصدر: proxy/dokodemo/dokodemo.go:33-40

حقول الإعدادات الرئيسية:

الحقلالوصف
Addressعنوان الوجهة المُحدد مسبقاً
Portمنفذ الوجهة المُحدد مسبقاً
Networksأنواع الشبكات المسموحة (TCP أو UDP أو كلاهما)
FollowRedirectإذا كان true، استخدم الوجهة الأصلية من البيانات الوصفية للاتصال
UserLevelمستوى السياسة
PortMapخريطة "المنفذ_الوارد" -> "المضيف:المنفذ" للتوجيه لكل منفذ

معالجة الاتصال

الملف: proxy/dokodemo/dokodemo.go:69-192

تحديد الوجهة

go
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network,
    conn stat.Connection, dispatcher routing.Dispatcher) error {

    dest := net.Destination{
        Network: network,
        Address: d.address,    // from config
        Port:    d.port,       // from config
    }

    if d.config.FollowRedirect {
        // Use the destination from outbound metadata
        // (set by iptables REDIRECT/TPROXY or sniffing)
        outbounds := session.OutboundsFromContext(ctx)
        if len(outbounds) > 0 {
            ob := outbounds[len(outbounds)-1]
            if ob.Target.IsValid() {
                dest = ob.Target
            }
        }
    }
}

المصدر: proxy/dokodemo/dokodemo.go:69-128

كشف اسم خادم TLS

عند تفعيل FollowRedirect وكان الاتصال الوارد عبر TLS، يمكن لـ dokodemo استخراج SNI (إشارة اسم الخادم) للتوجيه القائم على النطاق:

go
if tlsConn, ok := iConn.(tls.Interface); ok && !destinationOverridden {
    if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" {
        dest.Address = net.DomainAddress(serverName)
        destinationOverridden = true
        ctx = session.ContextWithMitmServerName(ctx, serverName)
    }
}

المصدر: proxy/dokodemo/dokodemo.go:115-124

تعيين المنافذ

عند تهيئة PortMap، يُستخدم المنفذ الوارد للبحث عن وجهة مختلفة:

go
if d.portMap != nil && d.portMap[port] != "" {
    h, p, _ := net.SplitHostPort(d.portMap[port])
    if len(h) > 0 {
        dest.Address = net.ParseAddress(h)
    }
    if len(p) > 0 {
        dest.Port = net.Port(strconv.Atoi(p))
    }
}

المصدر: proxy/dokodemo/dokodemo.go:93-101

معالجة TCP مقابل UDP

TCP: buf.NewReader(conn) وbuf.NewWriter(conn) القياسية:

go
if dest.Network == net.Network_TCP {
    reader = buf.NewReader(conn)
} else {
    reader = buf.NewPacketReader(conn)
}

المصدر: proxy/dokodemo/dokodemo.go:146-150

UDP مع TProxy: للوكالة الشفافة لـ UDP على Linux، يحتاج الخادم إلى "تزييف" عنوان المصدر عند إرسال حزم الرد. يُستخدم FakeUDP() لهذا:

go
if destinationOverridden {
    back := conn.RemoteAddr().(*net.UDPAddr)
    addr := &net.UDPAddr{
        IP:   dest.Address.IP(),
        Port: int(dest.Port),
    }
    pConn, err := FakeUDP(addr, mark)
    writer = NewPacketWriter(pConn, &dest, mark, back)
}

المصدر: proxy/dokodemo/dokodemo.go:160-182

FakeUDP (Linux TProxy)

الملف: proxy/dokodemo/fakeudp_linux.go

على Linux، يُنشئ FakeUDP مقبس UDP مرتبط بعنوان الوجهة باستخدام خيار المقبس IP_TRANSPARENT، مما يسمح للنواة بقبول الحزم الموجهة لأي عنوان. تُرسل حزم الرد من عنوان المصدر "المُزيّف" هذا باستخدام WriteTo().

الملف: proxy/dokodemo/fakeudp_other.go

على الأنظمة غير Linux، يُرجع FakeUDP خطأ لأن TProxy خاص بـ Linux.

كاتب الحزم (PacketWriter)

يتعامل PacketWriter مع كتابة حزم الرد UDP إلى العميل، مع دعم عناوين وجهات متعددة (لاستجابات DNS من خوادم مختلفة):

go
type PacketWriter struct {
    conn  net.PacketConn
    conns map[net.Destination]net.PacketConn  // cached per-dest fake sockets
    mark  int                                   // SO_MARK value
    back  *net.UDPAddr                          // client's address
}

المصدر: proxy/dokodemo/dokodemo.go:205-210

كل وجهة فريدة تحصل على مقبس UDP مُزيّف خاص بها. يُنشئ الكاتب مقابس جديدة عند الحاجة:

go
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
    if b.UDP != nil && b.UDP.Address.Family().IsIP() {
        conn := w.conns[*b.UDP]
        if conn == nil {
            conn, _ = FakeUDP(&net.UDPAddr{IP: b.UDP.Address.IP(), Port: int(b.UDP.Port)}, w.mark)
            w.conns[*b.UDP] = conn
        }
        conn.WriteTo(b.Bytes(), w.back)
    }
}

المصدر: proxy/dokodemo/dokodemo.go:212-253

يستخدم Dokodemo دالة dispatcher.DispatchLink() بدلاً من dispatcher.Dispatch(). تمرّر القارئ/الكاتب مباشرة وتحظر حتى اكتمال الاتصال:

go
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
    Reader: reader,
    Writer: writer,
}); err != nil {
    return errors.New("failed to dispatch request").Base(err)
}
return nil  // DispatchLink blocks until outbound finishes

المصدر: proxy/dokodemo/dokodemo.go:185-192

أنماط الاستخدام

iptables REDIRECT (TCP)

bash
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 12345

الإعدادات: FollowRedirect: true، الشبكات: TCP

تُعيد النواة كتابة الوجهة إلى 127.0.0.1:12345، لكن يمكن لـ Xray استعادة الوجهة الأصلية من SO_ORIGINAL_DST.

iptables TPROXY (TCP + UDP)

bash
iptables -t mangle -A PREROUTING -p udp -j TPROXY --to-port 12345 --tproxy-mark 1

الإعدادات: FollowRedirect: true، الشبكات: TCP+UDP، مع علامات المقبس المناسبة.

وجهة ثابتة (تحويل المنافذ)

الإعدادات: Address: "10.0.0.1"، Port: 8080، FollowRedirect: false

جميع الاتصالات الواردة على منفذ الاستماع تُحوّل إلى 10.0.0.1:8080.

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

  1. CanSpliceCopy = 1: يُعيّن Dokodemo أدنى مستوى splice لأنه لا يوجد عبء بروتوكول — تمر البايتات الخام دون أي ترميز أو تأطير.

المصدر: proxy/dokodemo/dokodemo.go:132

  1. التحقق من الشبكة: تتطلب دالة Init() تحديد شبكة واحدة على الأقل. تُسبب شريحة Networks الفارغة خطأ.

المصدر: proxy/dokodemo/dokodemo.go:44-46

  1. العنوان الاحتياطي: إذا لم يُهيّأ عنوان وكان FollowRedirect معطّلاً، يستخدم dokodemo عنوان الاتصال المحلي كاحتياطي، مختاراً 127.0.0.1 أو ::1 بناءً على ما إذا كان العنوان المحلي يحتوي على نقطة.

المصدر: proxy/dokodemo/dokodemo.go:79-90

  1. نشر علامة المقبس: تُنشر قيمة sockopt.Mark من إعدادات الوارد إلى مقابس FakeUDP، مما يضمن التفاعل الصحيح مع جدول التوجيه على Linux.

  2. دعم MITM: عند تفعيل كشف TLS SNI، يُعيّن dokodemo قيم MitmServerName وMitmAlpn11 على السياق، مما يُمكّن المعالجات اللاحقة من إجراء اعتراض TLS.

المصدر: proxy/dokodemo/dokodemo.go:119-123

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