From 8edf471c9f422b2cf7c58738b51be8c1020e0d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 10 Jun 2025 17:24:53 +0000 Subject: [PATCH 1/7] Freedom: Cache UDP resolve result --- proxy/freedom/freedom.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 779992f7..14a16afe 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -346,6 +346,12 @@ type PacketWriter struct { *Handler context.Context UDPOverride net.Destination + + // Dest of udp packets might be a domain, we will resolve them to IP + // But resolver will return a random one if the domain has many IPs + // Resulting in these packets being sent to many different IPs randomly + // So, cache and keep the resolve result + resolvedUDPAddr map[string]net.Address } func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { @@ -365,9 +371,14 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { b.UDP.Port = w.UDPOverride.Port } if w.Handler.config.hasStrategy() && b.UDP.Address.Family().IsDomain() { + if ip := w.resolvedUDPAddr[b.UDP.Address.Domain()]; ip != nil { + b.UDP.Address = ip + } + } else { ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil) if ip != nil { b.UDP.Address = ip + w.resolvedUDPAddr[b.UDP.Address.Domain()] = ip } } destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr()) From 9004838704f6e0aa59be1060247efbb8b8b2e669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 10 Jun 2025 17:29:13 +0000 Subject: [PATCH 2/7] map init --- proxy/freedom/freedom.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 14a16afe..c69fca1a 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -334,6 +334,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride Handler: h, Context: ctx, UDPOverride: UDPOverride, + resolvedUDPAddr: make(map[string]net.Address), } } From 892e92bb27fca779d0f89f3a4bbb78d8bf91436d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 10 Jun 2025 17:49:21 +0000 Subject: [PATCH 3/7] Fix --- proxy/freedom/freedom.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index c69fca1a..7cab2dc5 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -374,12 +374,12 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { if w.Handler.config.hasStrategy() && b.UDP.Address.Family().IsDomain() { if ip := w.resolvedUDPAddr[b.UDP.Address.Domain()]; ip != nil { b.UDP.Address = ip - } - } else { - ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil) - if ip != nil { - b.UDP.Address = ip - w.resolvedUDPAddr[b.UDP.Address.Domain()] = ip + } else { + ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil) + if ip != nil { + b.UDP.Address = ip + w.resolvedUDPAddr[b.UDP.Address.Domain()] = ip + } } } destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr()) From f4246e9314a610596291061613c587aeafbbf3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 10 Jun 2025 20:02:57 +0000 Subject: [PATCH 4/7] Add init check --- proxy/freedom/freedom.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 7cab2dc5..0399c3d1 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -328,13 +328,22 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride counter = statConn.WriteCounter } if c, ok := iConn.(*internet.PacketConnWrapper); ok { + // If target is a domain, it will be resolved in dialer + // check this behavior and add it to map + outbounds := session.OutboundsFromContext(ctx) + targetAddr := outbounds[len(outbounds)-1].Target.Address + resolvedUDPAddr := make(map[string]net.Address) + if targetAddr.Family().IsDomain() { + RemoteAddress, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) + resolvedUDPAddr[targetAddr.String()] = net.ParseAddress(RemoteAddress) + } return &PacketWriter{ PacketConnWrapper: c, Counter: counter, Handler: h, Context: ctx, UDPOverride: UDPOverride, - resolvedUDPAddr: make(map[string]net.Address), + resolvedUDPAddr: resolvedUDPAddr, } } From 9436956e2a291a686a30ac721cd2c0fe1bb9c3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Fri, 13 Jun 2025 08:30:58 +0000 Subject: [PATCH 5/7] Add and use new TypedSyncMap --- common/utils/TypedSyncMap.go | 59 ++++++++++++++++++++++++++++++++++++ proxy/freedom/freedom.go | 9 +++--- 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 common/utils/TypedSyncMap.go diff --git a/common/utils/TypedSyncMap.go b/common/utils/TypedSyncMap.go new file mode 100644 index 00000000..7512c0fb --- /dev/null +++ b/common/utils/TypedSyncMap.go @@ -0,0 +1,59 @@ +package util + +import ( + "sync" +) + +// TypedSyncMap is a wrapper of sync.Map that provides type-safe for keys and values. +// No need to use type assertions every time, so you can have more time to enjoy other things like GochiUsa +type TypedSyncMap[K, V any] struct { + syncMap *sync.Map +} + +func NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] { + return &TypedSyncMap[K, V]{ + syncMap: &sync.Map{}, + } +} + +func (m *TypedSyncMap[K, V]) Clear() { + m.syncMap.Clear() +} + +func (m *TypedSyncMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) { + return m.syncMap.CompareAndDelete(key, old) +} + +func (m *TypedSyncMap[K, V]) CompareAndSwap(key K, old V, new V) (swapped bool) { + return m.syncMap.CompareAndSwap(key, old, new) +} + +func (m *TypedSyncMap[K, V]) Delete(key K) { + m.syncMap.Delete(key) +} + +func (m *TypedSyncMap[K, V]) Load(key K) (value V, ok bool) { + anyValue, ok := m.syncMap.Load(key) + return anyValue.(V), ok +} + +func (m *TypedSyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) { + anyValue, loaded := m.syncMap.LoadAndDelete(key) + return anyValue.(V), loaded +} +func (m *TypedSyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { + anyActual, loaded := m.syncMap.LoadOrStore(key, value) + return anyActual.(V), loaded +} +func (m *TypedSyncMap[K, V]) Range(f func(key K, value V) bool) { + m.syncMap.Range(func(key, value any) bool { + return f(key.(K), value.(V)) + }) +} +func (m *TypedSyncMap[K, V]) Store(key K, value V) { + m.syncMap.Store(key, value) +} +func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) { + anyPrevious, loaded := m.syncMap.Swap(key, value) + return anyPrevious.(V), loaded +} \ No newline at end of file diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 0399c3d1..e4f49d9d 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -27,6 +27,7 @@ import ( "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" + "github.com/xtls/xray-core/common/utils" ) var useSplice bool @@ -343,7 +344,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride Handler: h, Context: ctx, UDPOverride: UDPOverride, - resolvedUDPAddr: resolvedUDPAddr, + resolvedUDPAddr: util.NewTypedSyncMap[string, net.Address](), } } @@ -361,7 +362,7 @@ type PacketWriter struct { // But resolver will return a random one if the domain has many IPs // Resulting in these packets being sent to many different IPs randomly // So, cache and keep the resolve result - resolvedUDPAddr map[string]net.Address + resolvedUDPAddr *util.TypedSyncMap[string, net.Address] } func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { @@ -381,13 +382,13 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { b.UDP.Port = w.UDPOverride.Port } if w.Handler.config.hasStrategy() && b.UDP.Address.Family().IsDomain() { - if ip := w.resolvedUDPAddr[b.UDP.Address.Domain()]; ip != nil { + if ip, ok := w.resolvedUDPAddr.Load(b.UDP.Address.Domain()); ok { b.UDP.Address = ip } else { ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil) if ip != nil { b.UDP.Address = ip - w.resolvedUDPAddr[b.UDP.Address.Domain()] = ip + w.resolvedUDPAddr.Store(b.UDP.Address.Domain(), ip) } } } From 4bc9f8ed3773f46fb153095a9cd9a3b3408eb439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Fri, 13 Jun 2025 09:19:35 +0000 Subject: [PATCH 6/7] Refine --- common/utils/TypedSyncMap.go | 28 +++++++++++++++++++++++----- proxy/freedom/freedom.go | 4 ++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/common/utils/TypedSyncMap.go b/common/utils/TypedSyncMap.go index 7512c0fb..0cddc8eb 100644 --- a/common/utils/TypedSyncMap.go +++ b/common/utils/TypedSyncMap.go @@ -1,4 +1,4 @@ -package util +package utils import ( "sync" @@ -6,6 +6,7 @@ import ( // TypedSyncMap is a wrapper of sync.Map that provides type-safe for keys and values. // No need to use type assertions every time, so you can have more time to enjoy other things like GochiUsa +// If sync.Map returned nil, it will return the zero value of the type V. type TypedSyncMap[K, V any] struct { syncMap *sync.Map } @@ -34,26 +35,43 @@ func (m *TypedSyncMap[K, V]) Delete(key K) { func (m *TypedSyncMap[K, V]) Load(key K) (value V, ok bool) { anyValue, ok := m.syncMap.Load(key) - return anyValue.(V), ok + // anyValue might be nil + if anyValue != nil { + value = anyValue.(V) + } + return value, ok } func (m *TypedSyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) { anyValue, loaded := m.syncMap.LoadAndDelete(key) - return anyValue.(V), loaded + if anyValue != nil { + value = anyValue.(V) + } + return value, loaded } + func (m *TypedSyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { anyActual, loaded := m.syncMap.LoadOrStore(key, value) - return anyActual.(V), loaded + if anyActual != nil { + actual = anyActual.(V) + } + return actual, loaded } + func (m *TypedSyncMap[K, V]) Range(f func(key K, value V) bool) { m.syncMap.Range(func(key, value any) bool { return f(key.(K), value.(V)) }) } + func (m *TypedSyncMap[K, V]) Store(key K, value V) { m.syncMap.Store(key, value) } + func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) { anyPrevious, loaded := m.syncMap.Swap(key, value) - return anyPrevious.(V), loaded + if anyPrevious != nil { + previous = anyPrevious.(V) + } + return previous, loaded } \ No newline at end of file diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index e4f49d9d..cb5c7ead 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -344,7 +344,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride Handler: h, Context: ctx, UDPOverride: UDPOverride, - resolvedUDPAddr: util.NewTypedSyncMap[string, net.Address](), + resolvedUDPAddr: utils.NewTypedSyncMap[string, net.Address](), } } @@ -362,7 +362,7 @@ type PacketWriter struct { // But resolver will return a random one if the domain has many IPs // Resulting in these packets being sent to many different IPs randomly // So, cache and keep the resolve result - resolvedUDPAddr *util.TypedSyncMap[string, net.Address] + resolvedUDPAddr *utils.TypedSyncMap[string, net.Address] } func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { From 936fc0507f182803fe309b825becbab9773b2376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 14 Jun 2025 08:29:48 +0000 Subject: [PATCH 7/7] Use LoadOrStore --- proxy/freedom/freedom.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index cb5c7ead..8584791c 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -387,8 +387,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { } else { ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil) if ip != nil { - b.UDP.Address = ip - w.resolvedUDPAddr.Store(b.UDP.Address.Domain(), ip) + b.UDP.Address, _ = w.resolvedUDPAddr.LoadOrStore(b.UDP.Address.Domain(), ip) } } }