صادرات Direct وBlock وDNS
تخدم هذه الأنواع الثلاثة من الصادرات وظائف توجيه أساسية: direct يتصل بالوجهة بدون وكيل، وblock يرفض جميع الاتصالات، وdns يعترض حركة مرور DNS للحل الداخلي.
المصدر: protocol/direct/outbound.go، protocol/direct/inbound.go، protocol/direct/loopback_detect.go، protocol/block/outbound.go، protocol/dns/outbound.go، protocol/dns/handle.go
صادر Direct
البنية
type Outbound struct {
outbound.Adapter
ctx context.Context
logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer
domainStrategy C.DomainStrategy
fallbackDelay time.Duration
isEmpty bool
}ينفذ صادر Direct واجهات متصل متعددة:
var (
_ N.ParallelDialer = (*Outbound)(nil)
_ dialer.ParallelNetworkDialer = (*Outbound)(nil)
_ dialer.DirectDialer = (*Outbound)(nil)
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
)دعم الشبكة
يدعم Direct بروتوكولات TCP وUDP وICMP (لـ ping/traceroute):
outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag,
[]string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions)قيد التحويلة
لا يمكن لصادر Direct استخدام تحويلة (detour) (سيكون ذلك دائرياً):
if options.Detour != "" {
return nil, E.New("`detour` is not supported in direct context")
}كشف الفراغ
يتتبع صادر Direct ما إذا كان لديه تكوين غير افتراضي. يُستخدم هذا من قبل الموجه لتحسين قرارات التوجيه:
outbound.isEmpty = reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true})إنشاء الاتصال
func (h *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
return h.dialer.DialContext(ctx, network, destination)
}الاتصال المتوازي
يدعم صادر Direct خوارزمية Happy Eyeballs (محاولات اتصال IPv4/IPv6 متوازية):
func (h *Outbound) DialParallel(ctx, network, destination, destinationAddresses) (net.Conn, error) {
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination,
destinationAddresses, destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)
}ICMP / المسار المباشر
يدعم صادر Direct اتصالات ICMP لـ ping/traceroute عبر واجهة DirectRouteOutbound:
func (h *Outbound) NewDirectRouteConnection(metadata, routeContext, timeout) (tun.DirectRouteDestination, error) {
destination, _ := ping.ConnectDestination(ctx, h.logger,
common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control,
metadata.Destination.Addr, routeContext, timeout)
return destination, nil
}اتصال استراتيجية الشبكة
يدعم الصادر خيارات استراتيجية شبكة متقدمة لاتصالات المسارات المتعددة:
func (h *Outbound) DialParallelNetwork(ctx, network, destination, destinationAddresses,
networkStrategy, networkType, fallbackNetworkType, fallbackDelay) (net.Conn, error) {
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination,
destinationAddresses, destinationAddresses[0].Is6(),
networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
}وارد Direct
يقبل وارد Direct اتصالات TCP/UDP الخام ويوجهها مع تجاوز وجهة اختياري:
type Inbound struct {
inbound.Adapter
overrideOption int // 0=لا شيء، 1=العنوان+المنفذ، 2=العنوان، 3=المنفذ
overrideDestination M.Socksaddr
}خيارات التجاوز
if options.OverrideAddress != "" && options.OverridePort != 0 {
inbound.overrideOption = 1 // استبدال كل من العنوان والمنفذ
} else if options.OverrideAddress != "" {
inbound.overrideOption = 2 // استبدال العنوان فقط
} else if options.OverridePort != 0 {
inbound.overrideOption = 3 // استبدال المنفذ فقط
}كشف الحلقات
يمنع loopBackDetector حلقات التوجيه عن طريق تتبع الاتصالات:
type loopBackDetector struct {
networkManager adapter.NetworkManager
connMap map[netip.AddrPort]netip.AddrPort // TCP
packetConnMap map[uint16]uint16 // UDP (قائم على المنفذ)
}يغلف الاتصالات الصادرة ويفحص الاتصالات الواردة مقابل الخريطة:
func (l *loopBackDetector) CheckConn(source, local netip.AddrPort) bool {
destination, loaded := l.connMap[source]
return loaded && destination != local
}ملاحظة: كشف الحلقات معطل حالياً في الكود المصدري لكن البنية التحتية لا تزال موجودة.
صادر Block
أبسط صادر -- يرفض جميع الاتصالات بـ EPERM:
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
}
func New(ctx, router, logger, tag, _ option.StubOptions) (adapter.Outbound, error) {
return &Outbound{
Adapter: outbound.NewAdapter(C.TypeBlock, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil),
logger: logger,
}, nil
}
func (h *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
h.logger.InfoContext(ctx, "blocked connection to ", destination)
return nil, syscall.EPERM
}
func (h *Outbound) ListenPacket(ctx, destination) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "blocked packet connection to ", destination)
return nil, syscall.EPERM
}التفاصيل الرئيسية:
- يستخدم
option.StubOptions(بنية فارغة) لأنه لا حاجة لتكوين - يُرجع
syscall.EPERM(وليس خطأ عام)، يمكن اكتشافه بواسطة المتصلين - يدعم كل من TCP وUDP (كلاهما محظور)
صادر DNS
يعترض صادر DNS الاتصالات التي تحمل حركة مرور DNS ويحلها باستخدام موجه DNS الداخلي.
البنية
type Outbound struct {
outbound.Adapter
router adapter.DNSRouter
logger logger.ContextLogger
}الاتصال العادي غير مدعوم
لا يدعم صادر DNS DialContext أو ListenPacket العادية:
func (d *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
return nil, os.ErrInvalid
}بدلاً من ذلك، ينفذ NewConnectionEx وNewPacketConnectionEx لمعالجة رسائل DNS مباشرة.
DNS عبر التيار (TCP)
تتم معالجة اتصالات DNS عبر TCP في حلقة، بقراءة رسائل DNS مسبوقة بالطول:
func (d *Outbound) NewConnectionEx(ctx, conn, metadata, onClose) {
metadata.Destination = M.Socksaddr{}
for {
conn.SetReadDeadline(time.Now().Add(C.DNSTimeout))
err := HandleStreamDNSRequest(ctx, d.router, conn, metadata)
if err != nil {
conn.Close()
return
}
}
}تنسيق بيانات DNS عبر التيار
يستخدم DNS عبر TCP بادئة طول 2 بايت:
func HandleStreamDNSRequest(ctx, router, conn, metadata) error {
// 1. قراءة بادئة الطول 2 بايت
var queryLength uint16
binary.Read(conn, binary.BigEndian, &queryLength)
// 2. قراءة رسالة DNS
buffer := buf.NewSize(int(queryLength))
buffer.ReadFullFrom(conn, int(queryLength))
// 3. فك التعبئة والتوجيه
var message mDNS.Msg
message.Unpack(buffer.Bytes())
// 4. التبادل عبر موجه DNS (غير متزامن)
go func() {
response, _ := router.Exchange(ctx, &message, adapter.DNSQueryOptions{})
// كتابة الاستجابة مسبوقة بالطول
binary.BigEndian.PutUint16(responseBuffer.ExtendHeader(2), uint16(len(n)))
conn.Write(responseBuffer.Bytes())
}()
}DNS عبر الحزم (UDP)
تتم معالجة حزم DNS عبر UDP بشكل متزامن مع مهلة خمول:
func (d *Outbound) NewPacketConnectionEx(ctx, conn, metadata, onClose) {
NewDNSPacketConnection(ctx, d.router, conn, nil, metadata)
}معالج الحزم:
- يقرأ حزم DNS من الاتصال
- يفك تعبئة كل حزمة كرسالة DNS
- يتبادل عبر موجه DNS في goroutine
- يكتب الاستجابة مع دعم اقتطاع DNS
- يستخدم ملغياً مع
C.DNSTimeoutلكشف الخمول
go func() {
response, _ := router.Exchange(ctx, &message, adapter.DNSQueryOptions{})
responseBuffer, _ := dns.TruncateDNSMessage(&message, response, 1024)
conn.WritePacket(responseBuffer, destination)
}()أمثلة على التكوين
Direct
{
"type": "direct",
"tag": "direct-out"
}Direct مع استراتيجية النطاق
{
"type": "direct",
"tag": "direct-out",
"domain_strategy": "prefer_ipv4"
}Block
{
"type": "block",
"tag": "block-out"
}DNS
{
"type": "dns",
"tag": "dns-out"
}وارد Direct (مع تجاوز)
{
"type": "direct",
"tag": "direct-in",
"listen": "::",
"listen_port": 5353,
"override_address": "8.8.8.8",
"override_port": 53
}